From 57368d151bc26acde0579f5f6f188f67e5a193ff Mon Sep 17 00:00:00 2001 From: Livio Amstutz Date: Fri, 17 Dec 2021 16:11:18 +0100 Subject: [PATCH] fix: assert roles when using refresh token (#2868) --- internal/api/oidc/auth_request.go | 37 ++++++++++------- internal/api/oidc/client.go | 66 ++++++++++++++++++++----------- 2 files changed, 64 insertions(+), 39 deletions(-) diff --git a/internal/api/oidc/auth_request.go b/internal/api/oidc/auth_request.go index 05c2e0bb8a..639b20244d 100644 --- a/internal/api/oidc/auth_request.go +++ b/internal/api/oidc/auth_request.go @@ -25,17 +25,9 @@ func (o *OPStorage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest if !ok { return nil, errors.ThrowPreconditionFailed(nil, "OIDC-sd436", "no user agent id") } - projectID, err := o.query.ProjectIDFromOIDCClientID(ctx, req.ClientID) + req.Scopes, err = o.assertProjectRoleScopes(ctx, req.ClientID, req.Scopes) if err != nil { - return nil, errors.ThrowPreconditionFailed(nil, "OIDC-AEG4d", "Errors.Internal") - } - project, err := o.query.ProjectByID(ctx, projectID) - if err != nil { - return nil, errors.ThrowPreconditionFailed(nil, "OIDC-w4wIn", "Errors.Internal") - } - req.Scopes, err = o.assertProjectRoleScopes(project, req.Scopes) - if err != nil { - return nil, errors.ThrowPreconditionFailed(nil, "OIDC-Gqrfg", "Errors.Internal") + return nil, errors.ThrowPreconditionFailed(err, "OIDC-Gqrfg", "Errors.Internal") } authRequest := CreateAuthRequestToBusiness(ctx, req, userAgentID, userID) //TODO: ensure splitting of command and query side durring auth request and login refactoring @@ -117,8 +109,15 @@ func (o *OPStorage) CreateAccessAndRefreshTokens(ctx context.Context, req op.Tok ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() userAgentID, applicationID, userOrgID, authTime, authMethodsReferences := getInfoFromRequest(req) + scopes, err := o.assertProjectRoleScopes(ctx, applicationID, req.GetScopes()) + if err != nil { + return "", "", time.Time{}, errors.ThrowPreconditionFailed(err, "OIDC-Df2fq", "Errors.Internal") + } + if request, ok := req.(op.RefreshTokenRequest); ok { + request.SetCurrentScopes(scopes) + } resp, token, err := o.command.AddAccessAndRefreshToken(ctx, userOrgID, userAgentID, applicationID, req.GetSubject(), - refreshToken, req.GetAudience(), req.GetScopes(), authMethodsReferences, o.defaultAccessTokenLifetime, + refreshToken, req.GetAudience(), scopes, authMethodsReferences, o.defaultAccessTokenLifetime, o.defaultRefreshTokenIdleExpiration, o.defaultRefreshTokenExpiration, authTime) //PLANNED: lifetime from client if err != nil { if errors.IsErrorInvalidArgument(err) { @@ -209,15 +208,23 @@ func (o *OPStorage) GetKeySet(ctx context.Context) (_ *jose.JSONWebKeySet, err e return o.repo.GetKeySet(ctx) } -func (o *OPStorage) assertProjectRoleScopes(project *query.Project, scopes []string) ([]string, error) { - if !project.ProjectRoleAssertion { - return scopes, nil - } +func (o *OPStorage) assertProjectRoleScopes(ctx context.Context, clientID string, scopes []string) ([]string, error) { for _, scope := range scopes { if strings.HasPrefix(scope, ScopeProjectRolePrefix) { return scopes, nil } } + projectID, err := o.query.ProjectIDFromOIDCClientID(ctx, clientID) + if err != nil { + return nil, errors.ThrowPreconditionFailed(nil, "OIDC-AEG4d", "Errors.Internal") + } + project, err := o.query.ProjectByID(ctx, projectID) + if err != nil { + return nil, errors.ThrowPreconditionFailed(nil, "OIDC-w4wIn", "Errors.Internal") + } + if !project.ProjectRoleAssertion { + return scopes, nil + } projectIDQuery, err := query.NewProjectRoleProjectIDSearchQuery(project.ID) if err != nil { return nil, errors.ThrowInternal(err, "OIDC-Cyc78", "Errors.Internal") diff --git a/internal/api/oidc/client.go b/internal/api/oidc/client.go index 461a4b4279..f99534376a 100644 --- a/internal/api/oidc/client.go +++ b/internal/api/oidc/client.go @@ -129,10 +129,51 @@ func (o *OPStorage) SetUserinfoFromToken(ctx context.Context, userInfo oidc.User return errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed") } } - return o.SetUserinfoFromScopes(ctx, userInfo, token.UserID, token.ApplicationID, token.Scopes) + return o.setUserinfo(ctx, userInfo, token.UserID, token.ApplicationID, token.Scopes) } func (o *OPStorage) SetUserinfoFromScopes(ctx context.Context, userInfo oidc.UserInfoSetter, userID, applicationID string, scopes []string) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + if applicationID != "" { + app, err := o.query.AppByOIDCClientID(ctx, applicationID) + if err != nil { + return err + } + if app.OIDCConfig.AssertIDTokenRole { + scopes, err = o.assertProjectRoleScopes(ctx, applicationID, scopes) + if err != nil { + return errors.ThrowPreconditionFailed(err, "OIDC-Dfe2s", "Errors.Internal") + } + } + } + return o.setUserinfo(ctx, userInfo, userID, applicationID, scopes) +} + +func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection oidc.IntrospectionResponse, tokenID, subject, clientID string) error { + token, err := o.repo.TokenByID(ctx, subject, tokenID) + if err != nil { + return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired") + } + projectID, err := o.query.ProjectIDFromClientID(ctx, clientID) + if err != nil { + return errors.ThrowPermissionDenied(nil, "OIDC-Adfg5", "client not found") + } + for _, aud := range token.Audience { + if aud == clientID || aud == projectID { + err := o.setUserinfo(ctx, introspection, token.UserID, clientID, token.Scopes) + if err != nil { + return err + } + introspection.SetScopes(token.Scopes) + introspection.SetClientID(token.ApplicationID) + return nil + } + } + return errors.ThrowPermissionDenied(nil, "OIDC-sdg3G", "token is not valid for this client") +} + +func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSetter, userID, applicationID string, scopes []string) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() user, err := o.repo.UserByID(ctx, userID) @@ -216,29 +257,6 @@ func (o *OPStorage) SetUserinfoFromScopes(ctx context.Context, userInfo oidc.Use return nil } -func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection oidc.IntrospectionResponse, tokenID, subject, clientID string) error { - token, err := o.repo.TokenByID(ctx, subject, tokenID) - if err != nil { - return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired") - } - projectID, err := o.query.ProjectIDFromClientID(ctx, clientID) - if err != nil { - return errors.ThrowPermissionDenied(nil, "OIDC-Adfg5", "client not found") - } - for _, aud := range token.Audience { - if aud == clientID || aud == projectID { - err := o.SetUserinfoFromScopes(ctx, introspection, token.UserID, clientID, token.Scopes) - if err != nil { - return err - } - introspection.SetScopes(token.Scopes) - introspection.SetClientID(token.ApplicationID) - return nil - } - } - return errors.ThrowPermissionDenied(nil, "OIDC-sdg3G", "token is not valid for this client") -} - func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (claims map[string]interface{}, err error) { roles := make([]string, 0) for _, scope := range scopes {