diff --git a/internal/api/authz/context.go b/internal/api/authz/context.go index 329cbe7fc5..3877c6c214 100644 --- a/internal/api/authz/context.go +++ b/internal/api/authz/context.go @@ -116,19 +116,9 @@ func VerifyTokenAndCreateCtxData(ctx context.Context, token, orgID, orgDomain st return CtxData{}, zerrors.ThrowUnauthenticated(errors.Join(err, sysTokenErr), "AUTH-7fs1e", "Errors.Token.Invalid") } } - var projectID string - var origins []string - if clientID != "" { - projectID, origins, err = t.ProjectIDAndOriginsByClientID(ctx, clientID) - if err != nil { - return CtxData{}, zerrors.ThrowPermissionDenied(err, "AUTH-GHpw2", "could not read projectid by clientid") - } - // We used to check origins for every token, but service users shouldn't be used publicly (native app / SPA). - // Therefore, mostly won't send an origin and aren't able to configure them anyway. - // For the current time we will only check origins for tokens issued to users through apps (code / implicit flow). - if err := checkOrigin(ctx, origins); err != nil { - return CtxData{}, err - } + projectID, err := projectIDAndCheckOriginForClientID(ctx, clientID, t) + if err != nil { + return CtxData{}, err } if orgID == "" && orgDomain == "" { orgID = resourceOwner @@ -151,6 +141,22 @@ func VerifyTokenAndCreateCtxData(ctx context.Context, token, orgID, orgDomain st }, nil } +func projectIDAndCheckOriginForClientID(ctx context.Context, clientID string, t APITokenVerifier) (string, error) { + if clientID == "" { + return "", nil + } + projectID, origins, err := t.ProjectIDAndOriginsByClientID(ctx, clientID) + logging.WithFields("clientID", clientID).OnError(err).Debug("could not check projectID and origin of clientID (might be service account)") + + // We used to check origins for every token, but service users shouldn't be used publicly (native app / SPA). + // Therefore, mostly won't send an origin and aren't able to configure them anyway. + // For the current time we will only check origins for tokens issued to users through apps (code / implicit flow). + if projectID == "" { + return "", nil + } + return projectID, checkOrigin(ctx, origins) +} + func SetCtxData(ctx context.Context, ctxData CtxData) context.Context { return context.WithValue(ctx, dataKey, ctxData) } diff --git a/internal/api/oidc/integration_test/token_client_credentials_test.go b/internal/api/oidc/integration_test/token_client_credentials_test.go index 372baf163d..d43f40e53e 100644 --- a/internal/api/oidc/integration_test/token_client_credentials_test.go +++ b/internal/api/oidc/integration_test/token_client_credentials_test.go @@ -3,6 +3,7 @@ package oidc_test import ( + "slices" "testing" "time" @@ -14,6 +15,8 @@ import ( oidc_api "github.com/zitadel/zitadel/internal/api/oidc" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/integration" + "github.com/zitadel/zitadel/pkg/grpc/auth" "github.com/zitadel/zitadel/pkg/grpc/management" "github.com/zitadel/zitadel/pkg/grpc/user" ) @@ -105,6 +108,17 @@ func TestServer_ClientCredentialsExchange(t *testing.T) { updated: machine.GetDetails().GetChangeDate().AsTime(), }, }, + { + name: "openid, profile, email, zitadel", + clientID: clientID, + clientSecret: clientSecret, + scope: []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, domain.ProjectScopeZITADEL}, + wantClaims: claims{ + name: name, + username: name, + updated: machine.GetDetails().GetChangeDate().AsTime(), + }, + }, { name: "org id and domain scope", clientID: clientID, @@ -173,6 +187,13 @@ func TestServer_ClientCredentialsExchange(t *testing.T) { assert.Empty(t, userinfo.UserInfoEmail) assert.Empty(t, userinfo.UserInfoPhone) assert.Empty(t, userinfo.Address) + + _, err = Instance.Client.Auth.GetMyUser(integration.WithAuthorizationToken(CTX, tokens.AccessToken), &auth.GetMyUserRequest{}) + if slices.Contains(tt.scope, domain.ProjectScopeZITADEL) { + require.NoError(t, err) + } else { + require.Error(t, err) + } }) } } diff --git a/internal/api/oidc/integration_test/token_jwt_profile_test.go b/internal/api/oidc/integration_test/token_jwt_profile_test.go index 4315b0b30d..ac483cf620 100644 --- a/internal/api/oidc/integration_test/token_jwt_profile_test.go +++ b/internal/api/oidc/integration_test/token_jwt_profile_test.go @@ -3,6 +3,7 @@ package oidc_test import ( + "slices" "testing" "time" @@ -15,6 +16,8 @@ import ( oidc_api "github.com/zitadel/zitadel/internal/api/oidc" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/integration" + "github.com/zitadel/zitadel/pkg/grpc/auth" ) func TestServer_JWTProfile(t *testing.T) { @@ -54,6 +57,16 @@ func TestServer_JWTProfile(t *testing.T) { updated: user.GetDetails().GetChangeDate().AsTime(), }, }, + { + name: "openid, profile, email, zitadel", + keyData: keyData, + scope: []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, domain.ProjectScopeZITADEL}, + wantClaims: claims{ + name: name, + username: name, + updated: user.GetDetails().GetChangeDate().AsTime(), + }, + }, { name: "org id and domain scope", keyData: keyData, @@ -129,6 +142,13 @@ func TestServer_JWTProfile(t *testing.T) { assert.Empty(t, userinfo.UserInfoEmail) assert.Empty(t, userinfo.UserInfoPhone) assert.Empty(t, userinfo.Address) + + _, err = Instance.Client.Auth.GetMyUser(integration.WithAuthorizationToken(CTX, tokens.AccessToken), &auth.GetMyUserRequest{}) + if slices.Contains(tt.scope, domain.ProjectScopeZITADEL) { + require.NoError(t, err) + } else { + require.Error(t, err) + } }) } } diff --git a/internal/domain/request.go b/internal/domain/request.go index 7c2c57436a..1b54cfa41c 100644 --- a/internal/domain/request.go +++ b/internal/domain/request.go @@ -9,6 +9,7 @@ const ( ProjectIDScope = "urn:zitadel:iam:org:project:id:" ProjectIDScopeZITADEL = "zitadel" AudSuffix = ":aud" + ProjectScopeZITADEL = ProjectIDScope + ProjectIDScopeZITADEL + AudSuffix SelectIDPScope = "urn:zitadel:iam:org:idp:id:" )