Skip to content
Snippets Groups Projects
accessTokenEndpoint.go 7.16 KiB
Newer Older
  • Learn to ignore specific revisions
  • package access
    
    import (
    	"strings"
    
    	"github.com/gofiber/fiber/v2"
    
    	"github.com/jmoiron/sqlx"
    
    	response "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg"
    	"github.com/oidc-mytoken/server/internal/utils/cookies"
    	"github.com/oidc-mytoken/server/shared/mytoken/rotation"
    
    	log "github.com/sirupsen/logrus"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	"github.com/oidc-mytoken/api/v0"
    
    	"github.com/oidc-mytoken/server/internal/config"
    	"github.com/oidc-mytoken/server/internal/db"
    	"github.com/oidc-mytoken/server/internal/db/dbrepo/accesstokenrepo"
    
    	dbhelper "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/mytokenrepohelper"
    
    	"github.com/oidc-mytoken/server/internal/db/dbrepo/refreshtokenrepo"
    
    	request "github.com/oidc-mytoken/server/internal/endpoints/token/access/pkg"
    	serverModel "github.com/oidc-mytoken/server/internal/model"
    	"github.com/oidc-mytoken/server/internal/oidc/refresh"
    	"github.com/oidc-mytoken/server/internal/utils/ctxUtils"
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	"github.com/oidc-mytoken/server/shared/model"
    
    	eventService "github.com/oidc-mytoken/server/shared/mytoken/event"
    	event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg"
    	mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg"
    	"github.com/oidc-mytoken/server/shared/mytoken/restrictions"
    
    	"github.com/oidc-mytoken/server/shared/mytoken/universalmytoken"
    
    	"github.com/oidc-mytoken/server/shared/utils"
    
    	"github.com/oidc-mytoken/server/shared/utils/jwtutils"
    
    // HandleAccessTokenEndpoint handles request on the access token endpoint
    
    func HandleAccessTokenEndpoint(ctx *fiber.Ctx) error {
    	log.Debug("Handle access token request")
    
    	req := request.NewAccessTokenRequest()
    
    	if err := ctx.BodyParser(&req); err != nil {
    
    		return serverModel.ErrorToBadRequestErrorResponse(err).Send(ctx)
    
    	}
    	log.Trace("Parsed access token request")
    
    	if req.Mytoken.JWT == "" {
    
    		req.Mytoken = req.RefreshToken
    	}
    
    	if req.GrantType != model.GrantTypeMytoken {
    
    		res := serverModel.Response{
    
    			Status:   fiber.StatusBadRequest,
    
    			Response: api.ErrorUnsupportedGrantType,
    
    		}
    		return res.Send(ctx)
    	}
    	log.Trace("Checked grant type")
    
    	if req.Mytoken.JWT == "" {
    
    		req.Mytoken, err = universalmytoken.Parse(ctx.Cookies("mytoken"))
    
    		if err != nil {
    			return serverModel.Response{
    				Status:   fiber.StatusUnauthorized,
    				Response: model.InvalidTokenError(err.Error()),
    			}.Send(ctx)
    		}
    	}
    
    	mt, err := mytoken.ParseJWT(req.Mytoken.JWT)
    
    	if err != nil {
    
    		return (&serverModel.Response{
    
    			Status:   fiber.StatusUnauthorized,
    			Response: model.InvalidTokenError(err.Error()),
    		}).Send(ctx)
    	}
    
    	log.Trace("Parsed mytoken")
    
    	revoked, dbErr := dbhelper.CheckTokenRevoked(nil, mt.ID, mt.SeqNo, mt.Rotation)
    
    	if dbErr != nil {
    
    		return serverModel.ErrorToInternalServerErrorResponse(dbErr).Send(ctx)
    
    	}
    	if revoked {
    
    		return (&serverModel.Response{
    
    			Status:   fiber.StatusUnauthorized,
    
    			Response: model.InvalidTokenError(""),
    
    		}).Send(ctx)
    
    	}
    	log.Trace("Checked token not revoked")
    
    
    	if ok := mt.Restrictions.VerifyForAT(nil, ctx.IP(), mt.ID); !ok {
    
    		return (&serverModel.Response{
    
    			Status:   fiber.StatusForbidden,
    
    			Response: api.ErrorUsageRestricted,
    
    		}).Send(ctx)
    
    	log.Trace("Checked mytoken restrictions")
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    	if ok := mt.VerifyCapabilities(api.CapabilityAT); !ok {
    
    		res := serverModel.Response{
    
    			Status:   fiber.StatusForbidden,
    
    			Response: api.ErrorInsufficientCapabilities,
    
    	log.Trace("Checked mytoken capabilities")
    
    	if req.Issuer == "" {
    
    		req.Issuer = mt.OIDCIssuer
    	} else if req.Issuer != mt.OIDCIssuer {
    
    		res := serverModel.Response{
    
    			Status:   fiber.StatusBadRequest,
    			Response: model.BadRequestError("token not for specified issuer"),
    		}
    		return res.Send(ctx)
    	}
    	log.Trace("Checked issuer")
    
    
    	return handleAccessTokenRefresh(mt, req, *ctxUtils.ClientMetaData(ctx)).Send(ctx)
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    func handleAccessTokenRefresh(mt *mytoken.Mytoken, req request.AccessTokenRequest, networkData api.ClientMetaData) *serverModel.Response {
    
    	provider, ok := config.Get().ProviderByIssuer[req.Issuer]
    	if !ok {
    
    		return &serverModel.Response{
    
    			Status:   fiber.StatusBadRequest,
    
    			Response: api.ErrorUnknownIssuer,
    
    		}
    	}
    
    	scopes := strings.Join(provider.Scopes, " ") // default if no restrictions apply
    	auds := ""                                   // default if no restrictions apply
    
    	var usedRestriction *restrictions.Restriction
    
    	if len(mt.Restrictions) > 0 {
    		possibleRestrictions := mt.Restrictions.GetValidForAT(nil, networkData.IP, mt.ID).WithScopes(utils.SplitIgnoreEmpty(req.Scope, " ")).WithAudiences(utils.SplitIgnoreEmpty(req.Audience, " "))
    
    		if len(possibleRestrictions) == 0 {
    
    			return &serverModel.Response{
    
    				Status:   fiber.StatusForbidden,
    
    				Response: api.ErrorUsageRestricted,
    
    		usedRestriction = &possibleRestrictions[0]
    
    		if req.Scope != "" {
    
    		} else if usedRestriction.Scope != "" {
    
    			scopes = usedRestriction.Scope
    
    		if req.Audience != "" {
    
    		} else if len(usedRestriction.Audiences) > 0 {
    			auds = strings.Join(usedRestriction.Audiences, " ")
    
    	rt, rtFound, dbErr := refreshtokenrepo.GetRefreshToken(nil, mt.ID, req.Mytoken.JWT)
    
    	if dbErr != nil {
    
    		return serverModel.ErrorToInternalServerErrorResponse(dbErr)
    
    	}
    	if !rtFound {
    
    		return &serverModel.Response{
    
    			Status:   fiber.StatusUnauthorized,
    			Response: model.InvalidTokenError("No refresh token attached"),
    
    	oidcRes, oidcErrRes, err := refresh.RefreshFlowAndUpdateDB(provider, mt.ID, req.Mytoken.JWT, rt, scopes, auds)
    
    		return serverModel.ErrorToInternalServerErrorResponse(err)
    
    		return &serverModel.Response{
    
    			Status:   oidcErrRes.Status,
    			Response: model.OIDCError(oidcErrRes.Error, oidcErrRes.ErrorDescription),
    		}
    	}
    
    	retScopes := oidcRes.Scopes
    	if retScopes == "" {
    		retScopes = scopes
    
    	retAudiences, _ := jwtutils.GetAudiencesFromJWT(oidcRes.AccessToken)
    
    	at := accesstokenrepo.AccessToken{
    
    		Token:     oidcRes.AccessToken,
    		IP:        networkData.IP,
    		Comment:   req.Comment,
    
    		Scopes:    utils.SplitIgnoreEmpty(retScopes, " "),
    		Audiences: retAudiences,
    
    	var tokenUpdate *response.MytokenResponse
    
    	if err = db.Transact(func(tx *sqlx.Tx) error {
    		if err = at.Store(tx); err != nil {
    			return err
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    		if err = eventService.LogEvent(tx, eventService.MTEvent{
    
    			Event: event.FromNumber(event.MTEventATCreated, "Used grant_type mytoken"),
    
    Gabriel Zachmann's avatar
    Gabriel Zachmann committed
    		}, networkData); err != nil {
    
    			return err
    		}
    		if usedRestriction != nil {
    
    			if err = usedRestriction.UsedAT(tx, mt.ID); err != nil {
    
    		tokenUpdate, err = rotation.RotateMytokenAfterATForResponse(tx, req.Mytoken.JWT, mt, networkData, req.Mytoken.OriginalTokenType)
    
    		return serverModel.ErrorToInternalServerErrorResponse(err)
    
    
    	rsp := request.AccessTokenResponse{
    		AccessTokenResponse: api.AccessTokenResponse{
    
    			AccessToken: oidcRes.AccessToken,
    			TokenType:   oidcRes.TokenType,
    			ExpiresIn:   oidcRes.ExpiresIn,
    			Scope:       retScopes,
    			Audiences:   retAudiences,
    		},
    	}
    
    	var cake []*fiber.Cookie
    	if tokenUpdate != nil {
    		rsp.TokenUpdate = tokenUpdate
    		cookie := cookies.MytokenCookie(tokenUpdate.Mytoken)
    		cake = []*fiber.Cookie{&cookie}
    	}
    	return &serverModel.Response{
    		Status:   fiber.StatusOK,
    		Response: rsp,
    		Cookies:  cake,
    	}