Skip to content
Snippets Groups Projects
supertoken.go 7.74 KiB
Newer Older
  • Learn to ignore specific revisions
  • Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    package supertoken
    
    import (
    	"database/sql/driver"
    	"fmt"
    	"time"
    
    	"github.com/dgrijalva/jwt-go"
    
    	"github.com/jmoiron/sqlx"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	uuid "github.com/satori/go.uuid"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	"github.com/zachmann/mytoken/internal/config"
    
    	"github.com/zachmann/mytoken/internal/db"
    	"github.com/zachmann/mytoken/internal/db/dbrepo/supertokenrepo/transfercoderepo"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	response "github.com/zachmann/mytoken/internal/endpoints/token/super/pkg"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	"github.com/zachmann/mytoken/internal/jws"
    
    	"github.com/zachmann/mytoken/internal/model"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	"github.com/zachmann/mytoken/internal/supertoken/capabilities"
    
    	eventService "github.com/zachmann/mytoken/internal/supertoken/event"
    	event "github.com/zachmann/mytoken/internal/supertoken/event/pkg"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	"github.com/zachmann/mytoken/internal/supertoken/restrictions"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	"github.com/zachmann/mytoken/internal/utils/issuerUtils"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    )
    
    // SuperToken is a mytoken SuperToken
    type SuperToken struct {
    
    	Issuer               string                    `json:"iss"`
    	Subject              string                    `json:"sub"`
    	ExpiresAt            int64                     `json:"exp,omitempty"`
    	NotBefore            int64                     `json:"nbf"`
    	IssuedAt             int64                     `json:"iat"`
    	ID                   uuid.UUID                 `json:"jti"`
    	Audience             string                    `json:"aud"`
    	OIDCSubject          string                    `json:"oidc_sub"`
    	OIDCIssuer           string                    `json:"oidc_iss"`
    	Restrictions         restrictions.Restrictions `json:"restrictions,omitempty"`
    	Capabilities         capabilities.Capabilities `json:"capabilities"`
    	SubtokenCapabilities capabilities.Capabilities `json:"subtoken_capabilities,omitempty"`
    	jwt                  string
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    }
    
    
    func (st *SuperToken) verifyID() bool {
    
    	if len(st.ID.String()) == 0 {
    		return false
    	}
    	return true
    }
    
    
    func (st *SuperToken) verifySubject() bool {
    
    	if len(st.Subject) == 0 {
    		return false
    	}
    	if st.Subject != issuerUtils.CombineSubIss(st.OIDCSubject, st.OIDCIssuer) {
    		return false
    	}
    	return true
    }
    
    
    // VerifyCapabilities verifies that this super token has the required capabilities
    
    func (st *SuperToken) VerifyCapabilities(required ...capabilities.Capability) bool {
    	if st.Capabilities == nil || len(st.Capabilities) == 0 {
    		return false
    	}
    	for _, c := range required {
    		if !st.Capabilities.Has(c) {
    			return false
    		}
    	}
    	return true
    }
    
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    // NewSuperToken creates a new SuperToken
    
    func NewSuperToken(oidcSub, oidcIss string, r restrictions.Restrictions, c, sc capabilities.Capabilities) *SuperToken {
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	now := time.Now().Unix()
    	st := &SuperToken{
    
    		ID:                   uuid.NewV4(),
    		IssuedAt:             now,
    		NotBefore:            now,
    		Issuer:               config.Get().IssuerURL,
    		Subject:              issuerUtils.CombineSubIss(oidcSub, oidcIss),
    		Audience:             config.Get().IssuerURL,
    		OIDCIssuer:           oidcIss,
    		OIDCSubject:          oidcSub,
    		Capabilities:         c,
    		SubtokenCapabilities: sc,
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	}
    	if len(r) > 0 {
    		st.Restrictions = r
    		exp := r.GetExpires()
    		if exp != 0 {
    			st.ExpiresAt = exp
    		}
    		nbf := r.GetNotBefore()
    		if nbf != 0 && nbf > now {
    			st.NotBefore = nbf
    		}
    	}
    	return st
    }
    
    
    func (st *SuperToken) expiresIn() uint64 {
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	now := time.Now().Unix()
    	expAt := st.ExpiresAt
    
    	if expAt > 0 && expAt > now {
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    		return uint64(expAt - now)
    	}
    	return 0
    }
    
    
    // Valid checks if this SuperToken is valid
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    func (st *SuperToken) Valid() error {
    	standardClaims := jwt.StandardClaims{
    		Audience:  st.Audience,
    		ExpiresAt: st.ExpiresAt,
    		Id:        st.ID.String(),
    		IssuedAt:  st.IssuedAt,
    		Issuer:    st.Issuer,
    		NotBefore: st.NotBefore,
    		Subject:   st.Subject,
    	}
    	if err := standardClaims.Valid(); err != nil {
    		return err
    	}
    	if ok := standardClaims.VerifyIssuer(config.Get().IssuerURL, true); !ok {
    
    		return fmt.Errorf("invalid issuer")
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	}
    	if ok := standardClaims.VerifyAudience(config.Get().IssuerURL, true); !ok {
    
    		return fmt.Errorf("invalid Audience")
    	}
    
    	if ok := st.verifyID(); !ok {
    
    		return fmt.Errorf("invalid id")
    	}
    
    	if ok := st.verifySubject(); !ok {
    
    		return fmt.Errorf("invalid subject")
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	}
    	return nil
    }
    
    
    // ToSuperTokenResponse returns a SuperTokenResponse for this token. It requires that jwt is set or that the jwt is passed as argument; if not passed as argument toJWT must have been called earlier on this token to set jwt. This is always the case, if the token has been stored.
    
    func (st *SuperToken) toSuperTokenResponse(jwt string) response.SuperTokenResponse {
    	res := st.toTokenResponse()
    	res.SuperToken = jwt
    	res.SuperTokenType = model.ResponseTypeToken
    	return res
    }
    
    
    func (st *SuperToken) toShortSuperTokenResponse(jwt string) (response.SuperTokenResponse, error) {
    
    	shortToken, err := transfercoderepo.NewShortToken(jwt)
    
    	if err != nil {
    		return response.SuperTokenResponse{}, err
    	}
    	if err = shortToken.Store(nil); err != nil {
    
    		return response.SuperTokenResponse{}, err
    	}
    	res := st.toTokenResponse()
    
    	res.SuperToken = shortToken.String()
    
    	res.SuperTokenType = model.ResponseTypeShortToken
    	return res, nil
    }
    
    func (st *SuperToken) toTokenResponse() response.SuperTokenResponse {
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	return response.SuperTokenResponse{
    
    		ExpiresIn:            st.expiresIn(),
    
    		Restrictions:         st.Restrictions,
    		Capabilities:         st.Capabilities,
    		SubtokenCapabilities: st.SubtokenCapabilities,
    
    // CreateTransferCode creates a transfer code for the passed super token
    
    func CreateTransferCode(stid uuid.UUID, jwt string, newST bool, responseType model.ResponseType, clientMetaData model.ClientMetaData) (string, uint64, error) {
    	transferCode, err := transfercoderepo.NewTransferCode(jwt, newST, responseType)
    	if err != nil {
    		return "", 0, err
    	}
    	err = db.Transact(func(tx *sqlx.Tx) error {
    		if err = transferCode.Store(tx); err != nil {
    			return err
    		}
    		return eventService.LogEvent(tx, &event.Event{
    			Type:    event.STEventTransferCodeCreated,
    			Comment: fmt.Sprintf("token type: %s", responseType.String()),
    		}, stid, clientMetaData)
    	})
    	expiresIn := uint64(config.Get().Features.Polling.PollingCodeExpiresAfter)
    	return transferCode.String(), expiresIn, err
    
    // ToTokenResponse creates a SuperTokenResponse for this SuperToken according to the passed model.ResponseType
    func (st *SuperToken) ToTokenResponse(responseType model.ResponseType, networkData model.ClientMetaData, jwt string) (response.SuperTokenResponse, error) {
    
    	if len(jwt) == 0 {
    		jwt = st.jwt
    	}
    
    	switch responseType {
    	case model.ResponseTypeShortToken:
    		if config.Get().Features.ShortTokens.Enabled {
    
    			return st.toShortSuperTokenResponse(jwt)
    
    		}
    	case model.ResponseTypeTransferCode:
    
    		transferCode, expiresIn, err := CreateTransferCode(st.ID, jwt, true, model.ResponseTypeToken, networkData)
    
    		res := st.toTokenResponse()
    		res.TransferCode = transferCode
    		res.SuperTokenType = model.ResponseTypeTransferCode
    		res.ExpiresIn = expiresIn
    		return res, err
    	}
    	return st.toSuperTokenResponse(jwt), nil
    }
    
    
    // ToJWT returns the SuperToken as JWT
    func (st *SuperToken) ToJWT() (string, error) {
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	if st.jwt != "" {
    		return st.jwt, nil
    	}
    	var err error
    	st.jwt, err = jwt.NewWithClaims(jwt.GetSigningMethod(config.Get().Signing.Alg), st).SignedString(jws.GetPrivateKey())
    	return st.jwt, err
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    }
    
    // Value implements the driver.Valuer interface.
    func (st *SuperToken) Value() (driver.Value, error) {
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    }
    
    // Scan implements the sql.Scanner interface.
    func (st *SuperToken) Scan(src interface{}) error {
    	tmp, err := ParseJWT(src.(string))
    	if err != nil {
    		return err
    	}
    	*st = *tmp
    	return nil
    }
    
    
    // ParseJWT parses a token string into a SuperToken
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    func ParseJWT(token string) (*SuperToken, error) {
    	tok, err := jwt.ParseWithClaims(token, &SuperToken{}, func(t *jwt.Token) (interface{}, error) {
    		return jws.GetPublicKey(), nil
    	})
    	if err != nil {
    		return nil, err
    	}
    
    	if st, ok := tok.Claims.(*SuperToken); ok && tok.Valid {
    		return st, nil
    	}
    
    	return nil, fmt.Errorf("token not valid")
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    }