diff --git a/internal/actions/object/metadata.go b/internal/actions/object/metadata.go index c49ceec799..55bf239094 100644 --- a/internal/actions/object/metadata.go +++ b/internal/actions/object/metadata.go @@ -36,7 +36,7 @@ func UserMetadataListFromQuery(c *actions.FieldConfig, metadata *query.UserMetad func UserMetadataListFromSlice(c *actions.FieldConfig, metadata []query.UserMetadata) goja.Value { result := &userMetadataList{ - // Count was the only field ever queries from the DB in the old implementation, + // Count was the only field ever queried from the DB in the old implementation, // so Sequence and LastRun are omitted. Count: uint64(len(metadata)), Metadata: make([]*userMetadata, len(metadata)), diff --git a/internal/api/oidc/client.go b/internal/api/oidc/client.go index eea825ff8f..7f9e8a2c22 100644 --- a/internal/api/oidc/client.go +++ b/internal/api/oidc/client.go @@ -27,7 +27,6 @@ import ( ) const ( - // TODO: remove declarations: (moved to domain package) ScopeProjectRolePrefix = "urn:zitadel:iam:org:project:role:" ScopeProjectsRoles = "urn:zitadel:iam:org:projects:roles" ClaimProjectRoles = "urn:zitadel:iam:org:project:roles" @@ -180,6 +179,21 @@ func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() + if strings.HasPrefix(tokenID, command.IDPrefixV2) { + token, err := o.query.ActiveAccessTokenByToken(ctx, tokenID) + if err != nil { + return err + } + projectID, err := o.query.ProjectIDFromClientID(ctx, clientID) + if err != nil { + return errors.ThrowPermissionDenied(nil, "OIDC-Adfg5", "client not found") + } + return o.introspect(ctx, introspection, + tokenID, token.UserID, token.ClientID, clientID, projectID, + token.Audience, token.Scope, + token.AccessTokenCreation, token.AccessTokenExpiration) + } + token, err := o.repo.TokenByIDs(ctx, subject, tokenID) if err != nil { return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired") @@ -370,7 +384,7 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo *oidc.UserInfo, us if err != nil { return err } - setUserInfoRoleClaims(userInfo, projectRoles) + o.setUserInfoRoleClaims(userInfo, projectRoles) return o.userinfoFlows(ctx, user, userGrants, userInfo) } @@ -432,7 +446,7 @@ func (o *OPStorage) setUserInfoResourceOwner(ctx context.Context, userInfo *oidc return nil } -func setUserInfoRoleClaims(userInfo *oidc.UserInfo, roles *projectsRoles) { +func (o *OPStorage) setUserInfoRoleClaims(userInfo *oidc.UserInfo, roles *projectsRoles) { if roles != nil && len(roles.projects) > 0 { if roles, ok := roles.projects[roles.requestProjectID]; ok { userInfo.AppendClaims(ClaimProjectRoles, roles) diff --git a/internal/api/oidc/introspect.go b/internal/api/oidc/introspect.go index 4974feb885..f5c97f9812 100644 --- a/internal/api/oidc/introspect.go +++ b/internal/api/oidc/introspect.go @@ -186,7 +186,7 @@ func (s *Server) introspectionToken(ctx context.Context, accessToken string, rc } tokenID, subject = split[0], split[1] } else { - verifier := op.NewAccessTokenVerifier(op.IssuerFromContext(ctx), s.storage.keySet) + verifier := op.NewAccessTokenVerifier(op.IssuerFromContext(ctx), s.keySet) claims, err := op.VerifyAccessToken[*oidc.AccessTokenClaims](ctx, accessToken, verifier) if err != nil { return nil, err diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go index 2857c3bad3..eac16964c0 100644 --- a/internal/api/oidc/op.go +++ b/internal/api/oidc/op.go @@ -68,7 +68,6 @@ type OPStorage struct { command *command.Commands query *query.Queries eventstore *eventstore.Eventstore - keySet *keySetCache defaultLoginURL string defaultLoginURLV2 string defaultLogoutURLV2 string @@ -123,6 +122,8 @@ func NewServer( storage: storage, LegacyServer: op.NewLegacyServer(provider, endpoints(config.CustomEndpoints)), query: query, + command: command, + keySet: newKeySet(context.TODO(), time.Hour, query.GetActivePublicKeyByID), fallbackLogger: fallbackLogger, hashAlg: crypto.NewBCrypt(10), // as we are only verifying in oidc, the cost is already part of the hash string and the config here is irrelevant. signingKeyAlgorithm: config.SigningKeyAlgorithm, @@ -179,13 +180,12 @@ func createOPConfig(config Config, defaultLogoutRedirectURI string, cryptoKey [] return opConfig, nil } -func newStorage(config Config, command *command.Commands, queries *query.Queries, repo repository.Repository, encAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, db *database.DB, externalSecure bool) *OPStorage { +func newStorage(config Config, command *command.Commands, query *query.Queries, repo repository.Repository, encAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, db *database.DB, externalSecure bool) *OPStorage { return &OPStorage{ repo: repo, command: command, - query: queries, + query: query, eventstore: es, - keySet: newKeySet(context.TODO(), time.Hour, queries.GetActivePublicKeyByID), defaultLoginURL: fmt.Sprintf("%s%s?%s=", login.HandlerPrefix, login.EndpointLogin, login.QueryAuthRequestID), defaultLoginURLV2: config.DefaultLoginURLV2, defaultLogoutURLV2: config.DefaultLogoutURLV2, diff --git a/internal/api/oidc/server.go b/internal/api/oidc/server.go index c5a27c7d10..e8801038cb 100644 --- a/internal/api/oidc/server.go +++ b/internal/api/oidc/server.go @@ -9,6 +9,7 @@ import ( "github.com/zitadel/logging" "github.com/zitadel/oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/op" + "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/query" @@ -22,6 +23,7 @@ type Server struct { query *query.Queries command *command.Commands + keySet *keySetCache fallbackLogger *slog.Logger hashAlg crypto.HashAlgorithm diff --git a/internal/api/oidc/userinfo.go b/internal/api/oidc/userinfo.go index 2105f978f5..4a4168cff9 100644 --- a/internal/api/oidc/userinfo.go +++ b/internal/api/oidc/userinfo.go @@ -248,6 +248,17 @@ func setUserInfoOrgClaims(user *query.OIDCUserInfo, out *oidc.UserInfo) { } } +func setUserInfoRoleClaims(userInfo *oidc.UserInfo, roles *projectsRoles) { + if roles != nil && len(roles.projects) > 0 { + if roles, ok := roles.projects[roles.requestProjectID]; ok { + userInfo.AppendClaims(ClaimProjectRoles, roles) + } + for projectID, roles := range roles.projects { + userInfo.AppendClaims(fmt.Sprintf(ClaimProjectRolesFormat, projectID), roles) + } + } +} + func (s *Server) userinfoFlows(ctx context.Context, user *query.OIDCUserInfo, userGrants *query.UserGrants, userInfo *oidc.UserInfo) error { queriedActions, err := s.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreUserinfoCreation, user.User.ResourceOwner, false) if err != nil { diff --git a/internal/query/access_token.go b/internal/query/access_token.go index f2db438737..664796d2d2 100644 --- a/internal/query/access_token.go +++ b/internal/query/access_token.go @@ -52,8 +52,11 @@ func (wm *OIDCSessionAccessTokenReadModel) Reduce() error { return wm.WriteModel.Reduce() } -func (wm *OIDCSessionAccessTokenReadModel) addQuery(b *eventstore.SearchQueryBuilder) *eventstore.SearchQueryBuilder { - return b.AddQuery(). +func (wm *OIDCSessionAccessTokenReadModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + AwaitOpenTransactions(). + AllowTimeTravel(). + AddQuery(). AggregateTypes(oidcsession.AggregateType). AggregateIDs(wm.AggregateID). EventTypes( @@ -65,14 +68,6 @@ func (wm *OIDCSessionAccessTokenReadModel) addQuery(b *eventstore.SearchQueryBui Builder() } -func (wm *OIDCSessionAccessTokenReadModel) Query() *eventstore.SearchQueryBuilder { - return wm.addQuery( - eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). - AwaitOpenTransactions(). - AllowTimeTravel(), - ) -} - func (wm *OIDCSessionAccessTokenReadModel) reduceAdded(e *oidcsession.AddedEvent) { wm.UserID = e.UserID wm.SessionID = e.SessionID diff --git a/internal/query/embed/userinfo_by_id.sql b/internal/query/embed/userinfo_by_id.sql index 6dd35a8fb2..3b02a6531c 100644 --- a/internal/query/embed/userinfo_by_id.sql +++ b/internal/query/embed/userinfo_by_id.sql @@ -45,6 +45,6 @@ select json_build_object( left join machine m on u.id = m.user_id ) r ), - 'organization', (select organization from org), + 'org', (select organization from org), 'metadata', (select metadata from metadata) ); diff --git a/internal/query/key.go b/internal/query/key.go index 0cdb29df14..64dde168e0 100644 --- a/internal/query/key.go +++ b/internal/query/key.go @@ -387,6 +387,7 @@ func (wm *PublicKeyReadModel) Reduce() error { func (wm *PublicKeyReadModel) Query() *eventstore.SearchQueryBuilder { return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + AwaitOpenTransactions(). ResourceOwner(wm.ResourceOwner). AddQuery(). AggregateTypes(keypair.AggregateType). diff --git a/internal/query/userinfo.go b/internal/query/userinfo.go index 59ea4fb3c9..6acf1ed4b9 100644 --- a/internal/query/userinfo.go +++ b/internal/query/userinfo.go @@ -9,12 +9,16 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/telemetry/tracing" ) //go:embed embed/userinfo_by_id.sql var oidcUserInfoQuery string func (q *Queries) GetOIDCUserInfo(ctx context.Context, userID string) (_ *OIDCUserInfo, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + userInfo := new(OIDCUserInfo) err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { var data []byte