diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca20382ed8..3362ac23bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,7 +64,7 @@ jobs: go_version: "1.21" node_version: "18" buf_version: "latest" - go_lint_version: "v1.53.2" + go_lint_version: "v1.55.2" core_cache_key: ${{ needs.core.outputs.cache_key }} core_cache_path: ${{ needs.core.outputs.cache_path }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 61ae27667b..104c512776 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -85,7 +85,7 @@ jobs: - uses: actions/setup-go@v4 with: - go-version: ${{ github.event.inputs.go_version }} + go-version: ${{ inputs.go_version }} - uses: actions/cache/restore@v3 timeout-minutes: 1 diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index ea4c1fc5bf..16bc80ee7c 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -314,6 +314,11 @@ OIDC: Path: /oauth/v2/device_authorization # ZITADEL_OIDC_CUSTOMENDPOINTS_DEVICEAUTH_PATH DefaultLoginURLV2: "/login?authRequest=" # ZITADEL_OIDC_DEFAULTLOGINURLV2 DefaultLogoutURLV2: "/logout?post_logout_redirect=" # ZITADEL_OIDC_DEFAULTLOGOUTURLV2 + Features: + # Wheter projection triggers are used in the new Introspection implementation. + TriggerIntrospectionProjections: false + # Allows fallback to the Legacy Introspection implementation + LegacyIntrospection: false SAML: ProviderConfig: diff --git a/go.mod b/go.mod index 14fdb73cbd..f2c81e8281 100644 --- a/go.mod +++ b/go.mod @@ -61,19 +61,19 @@ require ( github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203 github.com/ttacon/libphonenumber v1.2.1 github.com/zitadel/logging v0.5.0 - github.com/zitadel/oidc/v3 v3.2.1 + github.com/zitadel/oidc/v3 v3.4.0 github.com/zitadel/passwap v0.4.0 github.com/zitadel/saml v0.1.2 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.0 - go.opentelemetry.io/otel v1.20.0 + go.opentelemetry.io/otel v1.21.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 go.opentelemetry.io/otel/exporters/prometheus v0.43.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.20.0 - go.opentelemetry.io/otel/metric v1.20.0 + go.opentelemetry.io/otel/metric v1.21.0 go.opentelemetry.io/otel/sdk v1.20.0 go.opentelemetry.io/otel/sdk/metric v1.20.0 - go.opentelemetry.io/otel/trace v1.20.0 + go.opentelemetry.io/otel/trace v1.21.0 golang.org/x/crypto v0.15.0 golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 golang.org/x/net v0.18.0 @@ -168,7 +168,7 @@ require ( github.com/jackc/pgproto3/v2 v2.3.2 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jonboulle/clockwork v0.4.0 // indirect + github.com/jonboulle/clockwork v0.4.0 github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/go-types v0.0.0-20210723172823-2deba1f80ba7 // indirect github.com/kevinburke/rest v0.0.0-20231107185522-a9c371f90234 // indirect diff --git a/go.sum b/go.sum index eebfdaa3a3..b8e51d74e3 100644 --- a/go.sum +++ b/go.sum @@ -866,8 +866,8 @@ github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zitadel/logging v0.5.0 h1:Kunouvqse/efXy4UDvFw5s3vP+Z4AlHo3y8wF7stXHA= github.com/zitadel/logging v0.5.0/go.mod h1:IzP5fzwFhzzyxHkSmfF8dsyqFsQRJLLcQmwhIBzlGsE= -github.com/zitadel/oidc/v3 v3.2.1 h1:1Nw8D/vBM8huFteBYdG8777/OQd0m+mfB3U5JUr61ZA= -github.com/zitadel/oidc/v3 v3.2.1/go.mod h1:1snUiV6wqAWJ1psTrVrBpBittxf3WNoLwm0KCf6S8C4= +github.com/zitadel/oidc/v3 v3.4.0 h1:JkbNnrk/7IG+NOBoZp/P0kx6tPcBvnCekSqDTPCOok4= +github.com/zitadel/oidc/v3 v3.4.0/go.mod h1:jUnLnx5ihKlo88cSEduZkKlzeMrjzcWVZ8fTzKBxZKY= github.com/zitadel/passwap v0.4.0 h1:cMaISx+Ve7ilgG7Q8xOli4Z6IWr8Gndss+jeBk5A3O0= github.com/zitadel/passwap v0.4.0/go.mod h1:yHaDM4A68yRkdic5BZ4iUNoc19hT+kYt8n1/Nz+I87g= github.com/zitadel/saml v0.1.2 h1:RICwNTuP2upX4A1sZ8iq1rv4/x3DhZHzFx1e5bTKoTo= @@ -890,8 +890,8 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.0 h1:1eHu3/pUSWaOgltNK3WJFaywKsTIr/PwvHyDmi0lQA0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.0/go.mod h1:HyABWq60Uy1kjJSa2BVOxUVao8Cdick5AWSKPutqy6U= -go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc= -go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= @@ -900,14 +900,14 @@ go.opentelemetry.io/otel/exporters/prometheus v0.43.0 h1:Skkl6akzvdWweXX6LLAY29t go.opentelemetry.io/otel/exporters/prometheus v0.43.0/go.mod h1:nZStMoc1H/YJpRjSx9IEX4abBMekORTLQcTUT1CgLkg= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.20.0 h1:4s9HxB4azeeQkhY0GE5wZlMj4/pz8tE5gx2OQpGUw58= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.20.0/go.mod h1:djVA3TUJ2fSdMX0JE5XxFBOaZzprElJoP7fD4vnV2SU= -go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA= -go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/sdk v1.20.0 h1:5Jf6imeFZlZtKv9Qbo6qt2ZkmWtdWx/wzcCbNUlAWGM= go.opentelemetry.io/otel/sdk v1.20.0/go.mod h1:rmkSx1cZCm/tn16iWDn1GQbLtsW/LvsdEEFzCSRM6V0= go.opentelemetry.io/otel/sdk/metric v1.20.0 h1:5eD40l/H2CqdKmbSV7iht2KMK0faAIL2pVYzJOWobGk= go.opentelemetry.io/otel/sdk/metric v1.20.0/go.mod h1:AGvpC+YF/jblITiafMTYgvRBUiwi9hZf0EYE2E5XlS8= -go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ= -go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= diff --git a/internal/actions/object/metadata.go b/internal/actions/object/metadata.go index 61311fe508..55bf239094 100644 --- a/internal/actions/object/metadata.go +++ b/internal/actions/object/metadata.go @@ -34,6 +34,27 @@ func UserMetadataListFromQuery(c *actions.FieldConfig, metadata *query.UserMetad return c.Runtime.ToValue(result) } +func UserMetadataListFromSlice(c *actions.FieldConfig, metadata []query.UserMetadata) goja.Value { + result := &userMetadataList{ + // 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)), + } + for i, md := range metadata { + result.Metadata[i] = &userMetadata{ + CreationDate: md.CreationDate, + ChangeDate: md.ChangeDate, + ResourceOwner: md.ResourceOwner, + Sequence: md.Sequence, + Key: md.Key, + Value: metadataByteArrayToValue(md.Value, c.Runtime), + } + } + + return c.Runtime.ToValue(result) +} + func metadataByteArrayToValue(val []byte, runtime *goja.Runtime) goja.Value { var value interface{} if !json.Valid(val) { diff --git a/internal/actions/object/user_grant.go b/internal/actions/object/user_grant.go index 73de8bd0a0..21267a611c 100644 --- a/internal/actions/object/user_grant.go +++ b/internal/actions/object/user_grant.go @@ -90,6 +90,36 @@ func UserGrantsFromQuery(c *actions.FieldConfig, userGrants *query.UserGrants) g return c.Runtime.ToValue(grantList) } +func UserGrantsFromSlice(c *actions.FieldConfig, userGrants []query.UserGrant) goja.Value { + if userGrants == nil { + return c.Runtime.ToValue(nil) + } + grantList := &userGrantList{ + Count: uint64(len(userGrants)), + Grants: make([]*userGrant, len(userGrants)), + } + + for i, grant := range userGrants { + grantList.Grants[i] = &userGrant{ + Id: grant.ID, + ProjectGrantId: grant.GrantID, + State: grant.State, + CreationDate: grant.CreationDate, + ChangeDate: grant.ChangeDate, + Sequence: grant.Sequence, + UserId: grant.UserID, + Roles: grant.Roles, + UserResourceOwner: grant.UserResourceOwner, + UserGrantResourceOwner: grant.ResourceOwner, + UserGrantResourceOwnerName: grant.OrgName, + ProjectId: grant.ProjectID, + ProjectName: grant.ProjectName, + } + } + + return c.Runtime.ToValue(grantList) +} + func UserGrantsToDomain(userID string, actionUserGrants []UserGrant) []*domain.UserGrant { if actionUserGrants == nil { return nil diff --git a/internal/api/grpc/admin/export.go b/internal/api/grpc/admin/export.go index 30ae171f74..ec90866853 100644 --- a/internal/api/grpc/admin/export.go +++ b/internal/api/grpc/admin/export.go @@ -549,7 +549,7 @@ func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, w if err != nil { return nil, nil, nil, nil, err } - users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{orgSearch}}, false) + users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{orgSearch}}) if err != nil { return nil, nil, nil, nil, err } @@ -630,7 +630,7 @@ func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, w return nil, nil, nil, nil, err } - keys, err := s.query.SearchAuthNKeysData(ctx, &query.AuthNKeySearchQueries{Queries: []query.SearchQuery{userIDQuery, orgIDQuery}}, false) + keys, err := s.query.SearchAuthNKeysData(ctx, &query.AuthNKeySearchQueries{Queries: []query.SearchQuery{userIDQuery, orgIDQuery}}) if err != nil { return nil, nil, nil, nil, err } @@ -836,7 +836,7 @@ func (s *Server) getProjectsAndApps(ctx context.Context, org string) ([]*v1_pb.D if err != nil { return nil, nil, nil, nil, nil, err } - keys, err := s.query.SearchAuthNKeysData(ctx, &query.AuthNKeySearchQueries{Queries: []query.SearchQuery{appIDQuery, projectIDQuery, orgIDQuery}}, false) + keys, err := s.query.SearchAuthNKeysData(ctx, &query.AuthNKeySearchQueries{Queries: []query.SearchQuery{appIDQuery, projectIDQuery, orgIDQuery}}) if err != nil { return nil, nil, nil, nil, nil, err } diff --git a/internal/api/grpc/admin/org.go b/internal/api/grpc/admin/org.go index 7b95494d01..f3beb383e5 100644 --- a/internal/api/grpc/admin/org.go +++ b/internal/api/grpc/admin/org.go @@ -105,7 +105,7 @@ func (s *Server) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgDomain str if err != nil { return nil, err } - users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{loginName}}, false) + users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{loginName}}) if err != nil { return nil, err } diff --git a/internal/api/grpc/auth/email.go b/internal/api/grpc/auth/email.go index 7c336ca1fc..3e4f636c7f 100644 --- a/internal/api/grpc/auth/email.go +++ b/internal/api/grpc/auth/email.go @@ -11,7 +11,7 @@ import ( ) func (s *Server) GetMyEmail(ctx context.Context, _ *auth_pb.GetMyEmailRequest) (*auth_pb.GetMyEmailResponse, error) { - email, err := s.query.GetHumanEmail(ctx, authz.GetCtxData(ctx).UserID, false) + email, err := s.query.GetHumanEmail(ctx, authz.GetCtxData(ctx).UserID) if err != nil { return nil, err } diff --git a/internal/api/grpc/auth/phone.go b/internal/api/grpc/auth/phone.go index 5d5b722afc..6134295f3c 100644 --- a/internal/api/grpc/auth/phone.go +++ b/internal/api/grpc/auth/phone.go @@ -11,7 +11,7 @@ import ( ) func (s *Server) GetMyPhone(ctx context.Context, _ *auth_pb.GetMyPhoneRequest) (*auth_pb.GetMyPhoneResponse, error) { - phone, err := s.query.GetHumanPhone(ctx, authz.GetCtxData(ctx).UserID, false) + phone, err := s.query.GetHumanPhone(ctx, authz.GetCtxData(ctx).UserID) if err != nil { return nil, err } diff --git a/internal/api/grpc/auth/profile.go b/internal/api/grpc/auth/profile.go index 9d5d02c2ca..50f973c616 100644 --- a/internal/api/grpc/auth/profile.go +++ b/internal/api/grpc/auth/profile.go @@ -10,7 +10,7 @@ import ( ) func (s *Server) GetMyProfile(ctx context.Context, req *auth_pb.GetMyProfileRequest) (*auth_pb.GetMyProfileResponse, error) { - profile, err := s.query.GetHumanProfile(ctx, authz.GetCtxData(ctx).UserID, false) + profile, err := s.query.GetHumanProfile(ctx, authz.GetCtxData(ctx).UserID) if err != nil { return nil, err } diff --git a/internal/api/grpc/auth/user.go b/internal/api/grpc/auth/user.go index 03a6e2f86a..6b90a940ef 100644 --- a/internal/api/grpc/auth/user.go +++ b/internal/api/grpc/auth/user.go @@ -19,7 +19,7 @@ import ( ) func (s *Server) GetMyUser(ctx context.Context, _ *auth_pb.GetMyUserRequest) (*auth_pb.GetMyUserResponse, error) { - user, err := s.query.GetUserByID(ctx, true, authz.GetCtxData(ctx).UserID, false) + user, err := s.query.GetUserByID(ctx, true, authz.GetCtxData(ctx).UserID) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/org.go b/internal/api/grpc/management/org.go index e42082ed25..aed1394d65 100644 --- a/internal/api/grpc/management/org.go +++ b/internal/api/grpc/management/org.go @@ -329,7 +329,7 @@ func (s *Server) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgDomain, or } queries = append(queries, owner) } - users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: queries}, false) + users, err := s.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: queries}) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/project_application.go b/internal/api/grpc/management/project_application.go index ef18563c1e..09612ce072 100644 --- a/internal/api/grpc/management/project_application.go +++ b/internal/api/grpc/management/project_application.go @@ -16,7 +16,7 @@ import ( ) func (s *Server) GetAppByID(ctx context.Context, req *mgmt_pb.GetAppByIDRequest) (*mgmt_pb.GetAppByIDResponse, error) { - app, err := s.query.AppByProjectAndAppID(ctx, true, req.ProjectId, req.AppId, false) + app, err := s.query.AppByProjectAndAppID(ctx, true, req.ProjectId, req.AppId) if err != nil { return nil, err } @@ -259,7 +259,7 @@ func (s *Server) GetAppKey(ctx context.Context, req *mgmt_pb.GetAppKeyRequest) ( if err != nil { return nil, err } - key, err := s.query.GetAuthNKeyByID(ctx, true, req.KeyId, false, resourceOwner, aggregateID, objectID) + key, err := s.query.GetAuthNKeyByID(ctx, true, req.KeyId, resourceOwner, aggregateID, objectID) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go index ae50c14693..754b02755a 100644 --- a/internal/api/grpc/management/user.go +++ b/internal/api/grpc/management/user.go @@ -31,7 +31,7 @@ func (s *Server) getUserByID(ctx context.Context, id string) (*query.User, error if err != nil { return nil, err } - user, err := s.query.GetUserByID(ctx, true, id, false, owner) + user, err := s.query.GetUserByID(ctx, true, id, owner) if err != nil { return nil, err } @@ -53,7 +53,7 @@ func (s *Server) GetUserByLoginNameGlobal(ctx context.Context, req *mgmt_pb.GetU if err != nil { return nil, err } - user, err := s.query.GetUser(ctx, true, false, loginName) + user, err := s.query.GetUser(ctx, true, loginName) if err != nil { return nil, err } @@ -72,7 +72,7 @@ func (s *Server) ListUsers(ctx context.Context, req *mgmt_pb.ListUsersRequest) ( if err != nil { return nil, err } - res, err := s.query.SearchUsers(ctx, queries, false) + res, err := s.query.SearchUsers(ctx, queries) if err != nil { return nil, err } @@ -128,7 +128,7 @@ func (s *Server) IsUserUnique(ctx context.Context, req *mgmt_pb.IsUserUniqueRequ if !policy.UserLoginMustBeDomain { orgID = "" } - unique, err := s.query.IsUserUnique(ctx, req.UserName, req.Email, orgID, false) + unique, err := s.query.IsUserUnique(ctx, req.UserName, req.Email, orgID) if err != nil { return nil, err } @@ -406,7 +406,7 @@ func (s *Server) GetHumanProfile(ctx context.Context, req *mgmt_pb.GetHumanProfi if err != nil { return nil, err } - profile, err := s.query.GetHumanProfile(ctx, req.UserId, false, owner) + profile, err := s.query.GetHumanProfile(ctx, req.UserId, owner) if err != nil { return nil, err } @@ -440,7 +440,7 @@ func (s *Server) GetHumanEmail(ctx context.Context, req *mgmt_pb.GetHumanEmailRe if err != nil { return nil, err } - email, err := s.query.GetHumanEmail(ctx, req.UserId, false, owner) + email, err := s.query.GetHumanEmail(ctx, req.UserId, owner) if err != nil { return nil, err } @@ -506,7 +506,7 @@ func (s *Server) GetHumanPhone(ctx context.Context, req *mgmt_pb.GetHumanPhoneRe if err != nil { return nil, err } - phone, err := s.query.GetHumanPhone(ctx, req.UserId, false, owner) + phone, err := s.query.GetHumanPhone(ctx, req.UserId, owner) if err != nil { return nil, err } @@ -753,7 +753,7 @@ func (s *Server) GetMachineKeyByIDs(ctx context.Context, req *mgmt_pb.GetMachine if err != nil { return nil, err } - key, err := s.query.GetAuthNKeyByID(ctx, true, req.KeyId, false, resourceOwner, aggregateID) + key, err := s.query.GetAuthNKeyByID(ctx, true, req.KeyId, resourceOwner, aggregateID) if err != nil { return nil, err } diff --git a/internal/api/grpc/session/v2/session.go b/internal/api/grpc/session/v2/session.go index 0d85aabddb..874d9348e4 100644 --- a/internal/api/grpc/session/v2/session.go +++ b/internal/api/grpc/session/v2/session.go @@ -490,7 +490,7 @@ type userSearchByID struct { } func (u userSearchByID) search(ctx context.Context, q *query.Queries) (*query.User, error) { - return q.GetUserByID(ctx, true, u.id, false) + return q.GetUserByID(ctx, true, u.id) } type userSearchByLoginName struct { @@ -498,5 +498,5 @@ type userSearchByLoginName struct { } func (u userSearchByLoginName) search(ctx context.Context, q *query.Queries) (*query.User, error) { - return q.GetUser(ctx, true, false, u.loginNameQuery) + return q.GetUser(ctx, true, u.loginNameQuery) } diff --git a/internal/api/grpc/user/v2/user.go b/internal/api/grpc/user/v2/user.go index 5ee56f2b68..4301bc097d 100644 --- a/internal/api/grpc/user/v2/user.go +++ b/internal/api/grpc/user/v2/user.go @@ -360,7 +360,7 @@ func (s *Server) checkIntentToken(token string, intentID string) error { } func (s *Server) ListAuthenticationMethodTypes(ctx context.Context, req *user.ListAuthenticationMethodTypesRequest) (*user.ListAuthenticationMethodTypesResponse, error) { - authMethods, err := s.query.ListActiveUserAuthMethodTypes(ctx, req.GetUserId(), false) + authMethods, err := s.query.ListActiveUserAuthMethodTypes(ctx, req.GetUserId()) if err != nil { return nil, err } diff --git a/internal/api/oidc/access_token.go b/internal/api/oidc/access_token.go new file mode 100644 index 0000000000..d01badda98 --- /dev/null +++ b/internal/api/oidc/access_token.go @@ -0,0 +1,104 @@ +package oidc + +import ( + "context" + "errors" + "strings" + "time" + + "github.com/zitadel/oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v3/pkg/op" + + "github.com/zitadel/zitadel/internal/command" + errz "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/user/model" +) + +type accessToken struct { + tokenID string + userID string + subject string + clientID string + audience []string + scope []string + tokenCreation time.Time + tokenExpiration time.Time + isPAT bool +} + +func (s *Server) verifyAccessToken(ctx context.Context, tkn string) (*accessToken, error) { + var tokenID, subject string + + if tokenIDSubject, err := s.Provider().Crypto().Decrypt(tkn); err == nil { + split := strings.Split(tokenIDSubject, ":") + if len(split) != 2 { + return nil, errors.New("invalid token format") + } + tokenID, subject = split[0], split[1] + } else { + verifier := op.NewAccessTokenVerifier(op.IssuerFromContext(ctx), s.keySet) + claims, err := op.VerifyAccessToken[*oidc.AccessTokenClaims](ctx, tkn, verifier) + if err != nil { + return nil, err + } + tokenID, subject = claims.JWTID, claims.Subject + } + + if strings.HasPrefix(tokenID, command.IDPrefixV2) { + token, err := s.query.ActiveAccessTokenByToken(ctx, tokenID) + if err != nil { + return nil, err + } + return accessTokenV2(tokenID, subject, token), nil + } + + token, err := s.repo.TokenByIDs(ctx, subject, tokenID) + if err != nil { + return nil, errz.ThrowPermissionDenied(err, "OIDC-Dsfb2", "token is not valid or has expired") + } + return accessTokenV1(tokenID, subject, token), nil +} + +func accessTokenV1(tokenID, subject string, token *model.TokenView) *accessToken { + return &accessToken{ + tokenID: tokenID, + userID: token.UserID, + subject: subject, + clientID: token.ApplicationID, + audience: token.Audience, + scope: token.Scopes, + tokenCreation: token.CreationDate, + tokenExpiration: token.Expiration, + isPAT: token.IsPAT, + } +} + +func accessTokenV2(tokenID, subject string, token *query.OIDCSessionAccessTokenReadModel) *accessToken { + return &accessToken{ + tokenID: tokenID, + userID: token.UserID, + subject: subject, + clientID: token.ClientID, + audience: token.Audience, + scope: token.Scope, + tokenCreation: token.AccessTokenCreation, + tokenExpiration: token.AccessTokenExpiration, + } +} + +func (s *Server) assertClientScopesForPAT(ctx context.Context, token *accessToken, clientID, projectID string) error { + token.audience = append(token.audience, clientID) + projectIDQuery, err := query.NewProjectRoleProjectIDSearchQuery(projectID) + if err != nil { + return errz.ThrowInternal(err, "OIDC-Cyc78", "Errors.Internal") + } + roles, err := s.query.SearchProjectRoles(ctx, s.features.TriggerIntrospectionProjections, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}}) + if err != nil { + return err + } + for _, role := range roles.ProjectRoles { + token.scope = append(token.scope, ScopeProjectRolePrefix+role.Key) + } + return nil +} diff --git a/internal/api/oidc/auth_request.go b/internal/api/oidc/auth_request.go index 23fe695ff3..fa1e919f6b 100644 --- a/internal/api/oidc/auth_request.go +++ b/internal/api/oidc/auth_request.go @@ -102,7 +102,7 @@ func (o *OPStorage) audienceFromProjectID(ctx context.Context, projectID string) if err != nil { return nil, err } - appIDs, err := o.query.SearchClientIDs(ctx, &query.AppSearchQueries{Queries: []query.SearchQuery{projectIDQuery}}, false) + appIDs, err := o.query.SearchClientIDs(ctx, &query.AppSearchQueries{Queries: []query.SearchQuery{projectIDQuery}}) if err != nil { return nil, err } @@ -432,7 +432,7 @@ func (o *OPStorage) assertProjectRoleScopes(ctx context.Context, clientID string return scopes, nil } } - projectID, err := o.query.ProjectIDFromOIDCClientID(ctx, clientID, false) + projectID, err := o.query.ProjectIDFromOIDCClientID(ctx, clientID) if err != nil { return nil, errors.ThrowPreconditionFailed(nil, "OIDC-AEG4d", "Errors.Internal") } diff --git a/internal/api/oidc/client.go b/internal/api/oidc/client.go index 56afe114ca..7035f3db56 100644 --- a/internal/api/oidc/client.go +++ b/internal/api/oidc/client.go @@ -43,7 +43,7 @@ const ( func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (_ op.Client, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - client, err := o.query.AppByOIDCClientID(ctx, id, false) + client, err := o.query.AppByOIDCClientID(ctx, id) if err != nil { return nil, err } @@ -94,7 +94,7 @@ func (o *OPStorage) GetKeyByIDAndIssuer(ctx context.Context, keyID, issuer strin } func (o *OPStorage) ValidateJWTProfileScopes(ctx context.Context, subject string, scopes []string) ([]string, error) { - user, err := o.query.GetUserByID(ctx, true, subject, false) + user, err := o.query.GetUserByID(ctx, true, subject) if err != nil { return nil, err } @@ -108,7 +108,7 @@ func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secr UserID: oidcCtx, OrgID: oidcCtx, }) - app, err := o.query.AppByClientID(ctx, id, false) + app, err := o.query.AppByClientID(ctx, id) if err != nil { return err } @@ -149,7 +149,7 @@ func (o *OPStorage) SetUserinfoFromScopes(ctx context.Context, userInfo *oidc.Us ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() if applicationID != "" { - app, err := o.query.AppByOIDCClientID(ctx, applicationID, false) + app, err := o.query.AppByOIDCClientID(ctx, applicationID) if err != nil { return err } @@ -184,7 +184,7 @@ func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection if err != nil { return err } - projectID, err := o.query.ProjectIDFromClientID(ctx, clientID, false) + projectID, err := o.query.ProjectIDFromClientID(ctx, clientID) if err != nil { return errors.ThrowPermissionDenied(nil, "OIDC-Adfg5", "client not found") } @@ -198,7 +198,7 @@ func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection if err != nil { return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired") } - projectID, err := o.query.ProjectIDFromClientID(ctx, clientID, false) + projectID, err := o.query.ProjectIDFromClientID(ctx, clientID) if err != nil { return errors.ThrowPermissionDenied(nil, "OIDC-Adfg5", "client not found") } @@ -219,7 +219,7 @@ func (o *OPStorage) ClientCredentialsTokenRequest(ctx context.Context, clientID if err != nil { return nil, err } - user, err := o.query.GetUser(ctx, false, false, loginname) + user, err := o.query.GetUser(ctx, false, loginname) if err != nil { return nil, err } @@ -240,7 +240,7 @@ func (o *OPStorage) ClientCredentials(ctx context.Context, clientID, clientSecre if err != nil { return nil, err } - user, err := o.query.GetUser(ctx, false, false, loginname) + user, err := o.query.GetUser(ctx, false, loginname) if err != nil { return nil, err } @@ -259,7 +259,7 @@ func (o *OPStorage) isOriginAllowed(ctx context.Context, clientID, origin string if origin == "" { return nil } - app, err := o.query.AppByOIDCClientID(ctx, clientID, false) + app, err := o.query.AppByOIDCClientID(ctx, clientID) if err != nil { return err } @@ -331,7 +331,7 @@ func (o *OPStorage) checkOrgScopes(ctx context.Context, user *query.User, scopes func (o *OPStorage) setUserinfo(ctx context.Context, userInfo *oidc.UserInfo, userID, applicationID string, scopes []string, roleAudience []string) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - user, err := o.query.GetUserByID(ctx, true, userID, false) + user, err := o.query.GetUserByID(ctx, true, userID) if err != nil { return err } @@ -645,7 +645,7 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie } func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, userGrants *query.UserGrants, claims map[string]interface{}) (map[string]interface{}, error) { - user, err := o.query.GetUserByID(ctx, true, userID, false) + user, err := o.query.GetUserByID(ctx, true, userID) if err != nil { return nil, err } @@ -764,7 +764,7 @@ func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID strin if (applicationID == "" || len(requestedRoles) == 0) && len(roleAudience) == 0 { return nil, nil, nil } - projectID, err := o.query.ProjectIDFromClientID(ctx, applicationID, false) + projectID, err := o.query.ProjectIDFromClientID(ctx, applicationID) // applicationID might contain a username (e.g. client credentials) -> ignore the not found if err != nil && !errors.IsNotFound(err) { return nil, nil, err @@ -795,7 +795,7 @@ func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID strin if len(requestedRoles) > 0 { for _, requestedRole := range requestedRoles { for _, grant := range grants.UserGrants { - checkGrantedRoles(roles, grant, requestedRole, grant.ProjectID == projectID) + checkGrantedRoles(roles, *grant, requestedRole, grant.ProjectID == projectID) } } return grants, roles, nil @@ -823,7 +823,7 @@ func (o *OPStorage) assertUserMetaData(ctx context.Context, userID string) (map[ } func (o *OPStorage) assertUserResourceOwner(ctx context.Context, userID string) (map[string]string, error) { - user, err := o.query.GetUserByID(ctx, true, userID, false) + user, err := o.query.GetUserByID(ctx, true, userID) if err != nil { return nil, err } @@ -838,7 +838,7 @@ func (o *OPStorage) assertUserResourceOwner(ctx context.Context, userID string) }, nil } -func checkGrantedRoles(roles *projectsRoles, grant *query.UserGrant, requestedRole string, isRequested bool) { +func checkGrantedRoles(roles *projectsRoles, grant query.UserGrant, requestedRole string, isRequested bool) { for _, grantedRole := range grant.Roles { if requestedRole == grantedRole { roles.Add(grant.ProjectID, grantedRole, grant.ResourceOwner, grant.OrgPrimaryDomain, isRequested) @@ -854,6 +854,26 @@ type projectsRoles struct { requestProjectID string } +func newProjectRoles(projectID string, grants []query.UserGrant, requestedRoles []string) *projectsRoles { + roles := new(projectsRoles) + // if specific roles where requested, check if they are granted and append them in the roles list + if len(requestedRoles) > 0 { + for _, requestedRole := range requestedRoles { + for _, grant := range grants { + checkGrantedRoles(roles, grant, requestedRole, grant.ProjectID == projectID) + } + } + return roles + } + // no specific roles were requested, so convert any grants into roles + for _, grant := range grants { + for _, role := range grant.Roles { + roles.Add(grant.ProjectID, role, grant.ResourceOwner, grant.OrgPrimaryDomain, grant.ProjectID == projectID) + } + } + return roles +} + func (p *projectsRoles) Add(projectID, roleKey, orgID, domain string, isRequested bool) { if p.projects == nil { p.projects = make(map[string]projectRoles, 1) diff --git a/internal/api/oidc/client_integration_test.go b/internal/api/oidc/client_integration_test.go index 0165051f08..f1f5ed8466 100644 --- a/internal/api/oidc/client_integration_test.go +++ b/internal/api/oidc/client_integration_test.go @@ -48,7 +48,7 @@ func TestOPStorage_SetUserinfoFromToken(t *testing.T) { assertUserinfo(t, userinfo) } -func TestOPStorage_SetIntrospectionFromToken(t *testing.T) { +func TestServer_Introspect(t *testing.T) { project, err := Tester.CreateProject(CTX) require.NoError(t, err) app, err := Tester.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId()) diff --git a/internal/api/oidc/introspect.go b/internal/api/oidc/introspect.go new file mode 100644 index 0000000000..d48bf001ec --- /dev/null +++ b/internal/api/oidc/introspect.go @@ -0,0 +1,200 @@ +package oidc + +import ( + "context" + "database/sql" + "errors" + "slices" + "time" + + "github.com/zitadel/oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v3/pkg/op" + + "github.com/zitadel/zitadel/internal/crypto" + errz "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/telemetry/tracing" +) + +func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionRequest]) (resp *op.Response, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + if s.features.LegacyIntrospection { + return s.LegacyServer.Introspect(ctx, r) + } + if s.features.TriggerIntrospectionProjections { + // Execute all triggers in one concurrent sweep. + query.TriggerIntrospectionProjections(ctx) + } + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + clientChan := make(chan *instrospectionClientResult) + go s.instrospectionClientAuth(ctx, r.Data.ClientCredentials, clientChan) + + tokenChan := make(chan *introspectionTokenResult) + go s.introspectionToken(ctx, r.Data.Token, tokenChan) + + var ( + client *instrospectionClientResult + token *introspectionTokenResult + ) + + // make sure both channels are always read, + // and cancel the context on first error + for i := 0; i < 2; i++ { + var resErr error + + select { + case client = <-clientChan: + resErr = client.err + case token = <-tokenChan: + resErr = token.err + } + + if resErr == nil { + continue + } + cancel() + + // we only care for the first error that occurred, + // as the next error is most probably a context error. + if err == nil { + err = resErr + } + } + + // only client auth errors should be returned + var target *oidc.Error + if errors.As(err, &target) && target.ErrorType == oidc.UnauthorizedClient { + return nil, err + } + + // remaining errors shoudn't be returned to the client, + // so we catch errors here, log them and return the response + // with active: false + defer func() { + if err != nil { + s.getLogger(ctx).ErrorContext(ctx, "oidc introspection", "err", err) + resp, err = op.NewResponse(new(oidc.IntrospectionResponse)), nil + } + }() + + if err != nil { + return nil, err + } + + // TODO: can we get rid of this separate query? + if token.isPAT { + if err = s.assertClientScopesForPAT(ctx, token.accessToken, client.clientID, client.projectID); err != nil { + return nil, err + } + } + + if err = validateIntrospectionAudience(token.audience, client.clientID, client.projectID); err != nil { + return nil, err + } + userInfo, err := s.userInfo(ctx, token.userID, client.projectID, token.scope, []string{client.projectID}) + if err != nil { + return nil, err + } + introspectionResp := &oidc.IntrospectionResponse{ + Active: true, + Scope: token.scope, + ClientID: token.clientID, + TokenType: oidc.BearerToken, + Expiration: oidc.FromTime(token.tokenExpiration), + IssuedAt: oidc.FromTime(token.tokenCreation), + NotBefore: oidc.FromTime(token.tokenCreation), + Audience: token.audience, + Issuer: op.IssuerFromContext(ctx), + JWTID: token.tokenID, + } + introspectionResp.SetUserInfo(userInfo) + return op.NewResponse(introspectionResp), nil +} + +type instrospectionClientResult struct { + clientID string + projectID string + err error +} + +func (s *Server) instrospectionClientAuth(ctx context.Context, cc *op.ClientCredentials, rc chan<- *instrospectionClientResult) { + ctx, span := tracing.NewSpan(ctx) + + clientID, projectID, err := func() (string, string, error) { + client, err := s.clientFromCredentials(ctx, cc) + if err != nil { + return "", "", err + } + + if cc.ClientAssertion != "" { + verifier := op.NewJWTProfileVerifierKeySet(keySetMap(client.PublicKeys), op.IssuerFromContext(ctx), time.Hour, time.Second) + if _, err := op.VerifyJWTAssertion(ctx, cc.ClientAssertion, verifier); err != nil { + return "", "", oidc.ErrUnauthorizedClient().WithParent(err) + } + } else { + if err := crypto.CompareHash(client.ClientSecret, []byte(cc.ClientSecret), s.hashAlg); err != nil { + return "", "", oidc.ErrUnauthorizedClient().WithParent(err) + } + } + + return client.ClientID, client.ProjectID, nil + }() + + span.EndWithError(err) + + rc <- &instrospectionClientResult{ + clientID: clientID, + projectID: projectID, + err: err, + } +} + +// clientFromCredentials parses the client ID early, +// and makes a single query for the client for either auth methods. +func (s *Server) clientFromCredentials(ctx context.Context, cc *op.ClientCredentials) (client *query.IntrospectionClient, err error) { + if cc.ClientAssertion != "" { + claims := new(oidc.JWTTokenRequest) + if _, err := oidc.ParseToken(cc.ClientAssertion, claims); err != nil { + return nil, oidc.ErrUnauthorizedClient().WithParent(err) + } + client, err = s.query.GetIntrospectionClientByID(ctx, claims.Issuer, true) + } else { + client, err = s.query.GetIntrospectionClientByID(ctx, cc.ClientID, false) + } + if errors.Is(err, sql.ErrNoRows) { + return nil, oidc.ErrUnauthorizedClient().WithParent(err) + } + // any other error is regarded internal and should not be reported back to the client. + return client, err +} + +type introspectionTokenResult struct { + *accessToken + err error +} + +func (s *Server) introspectionToken(ctx context.Context, tkn string, rc chan<- *introspectionTokenResult) { + ctx, span := tracing.NewSpan(ctx) + token, err := s.verifyAccessToken(ctx, tkn) + span.EndWithError(err) + + rc <- &introspectionTokenResult{ + accessToken: token, + err: err, + } +} + +func validateIntrospectionAudience(audience []string, clientID, projectID string) error { + if slices.ContainsFunc(audience, func(entry string) bool { + return entry == clientID || entry == projectID + }) { + return nil + } + + return errz.ThrowPermissionDenied(nil, "OIDC-sdg3G", "token is not valid for this client") +} diff --git a/internal/api/oidc/jwt-profile.go b/internal/api/oidc/jwt-profile.go index e2592a5734..a0bf491bbd 100644 --- a/internal/api/oidc/jwt-profile.go +++ b/internal/api/oidc/jwt-profile.go @@ -12,7 +12,7 @@ import ( func (o *OPStorage) JWTProfileTokenType(ctx context.Context, request op.TokenRequest) (op.AccessTokenType, error) { mapJWTProfileScopesToAudience(ctx, request) - user, err := o.query.GetUserByID(ctx, false, request.GetSubject(), false) + user, err := o.query.GetUserByID(ctx, false, request.GetSubject()) if err != nil { return 0, err } diff --git a/internal/api/oidc/key.go b/internal/api/oidc/key.go index 3a2a6ae32c..6f9e51407a 100644 --- a/internal/api/oidc/key.go +++ b/internal/api/oidc/key.go @@ -3,14 +3,17 @@ package oidc import ( "context" "fmt" + "sync" "time" "github.com/go-jose/go-jose/v3" + "github.com/jonboulle/clockwork" "github.com/zitadel/logging" "github.com/zitadel/oidc/v3/pkg/op" "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/query" @@ -19,6 +22,145 @@ import ( "github.com/zitadel/zitadel/internal/telemetry/tracing" ) +// keySetCache implements oidc.KeySet for Access Token verification. +// Public Keys are cached in a 2-dimensional map of Instance ID and Key ID. +// When a key is not present the queryKey function is called to obtain the key +// from the database. +type keySetCache struct { + mtx sync.RWMutex + instanceKeys map[string]map[string]query.PublicKey + queryKey func(ctx context.Context, keyID string, current time.Time) (query.PublicKey, error) + clock clockwork.Clock +} + +// newKeySet initializes a keySetCache and starts a purging Go routine, +// which runs once every purgeInterval. +// When the passed context is done, the purge routine will terminate. +func newKeySet(background context.Context, purgeInterval time.Duration, queryKey func(ctx context.Context, keyID string, current time.Time) (query.PublicKey, error)) *keySetCache { + k := &keySetCache{ + instanceKeys: make(map[string]map[string]query.PublicKey), + queryKey: queryKey, + clock: clockwork.FromContext(background), // defaults to real clock + } + go k.purgeOnInterval(background, k.clock.NewTicker(purgeInterval)) + return k +} + +func (k *keySetCache) purgeOnInterval(background context.Context, ticker clockwork.Ticker) { + defer ticker.Stop() + for { + select { + case <-background.Done(): + return + case <-ticker.Chan(): + } + + // do the actual purging + k.mtx.Lock() + for instanceID, keys := range k.instanceKeys { + for keyID, key := range keys { + if key.Expiry().Before(k.clock.Now()) { + delete(keys, keyID) + } + } + if len(keys) == 0 { + delete(k.instanceKeys, instanceID) + } + } + k.mtx.Unlock() + } +} + +func (k *keySetCache) setKey(instanceID, keyID string, key query.PublicKey) { + k.mtx.Lock() + defer k.mtx.Unlock() + + if keys, ok := k.instanceKeys[instanceID]; ok { + keys[keyID] = key + return + } + + k.instanceKeys[instanceID] = map[string]query.PublicKey{keyID: key} +} + +func (k *keySetCache) getKey(ctx context.Context, keyID string) (_ *jose.JSONWebKey, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + instanceID := authz.GetInstance(ctx).InstanceID() + + k.mtx.RLock() + key, ok := k.instanceKeys[instanceID][keyID] + k.mtx.RUnlock() + + if ok { + if key.Expiry().After(k.clock.Now()) { + return jsonWebkey(key), nil + } + return nil, errors.ThrowInvalidArgument(nil, "OIDC-Zoh9E", "Errors.Key.ExpireBeforeNow") + } + + key, err = k.queryKey(ctx, keyID, k.clock.Now()) + if err != nil { + return nil, err + } + k.setKey(instanceID, keyID, key) + return jsonWebkey(key), nil +} + +// VerifySignature implements the oidc.KeySet interface. +func (k *keySetCache) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) (_ []byte, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + if len(jws.Signatures) != 1 { + return nil, errors.ThrowInvalidArgument(nil, "OIDC-Gid9s", "Errors.Token.Invalid") + } + key, err := k.getKey(ctx, jws.Signatures[0].Header.KeyID) + if err != nil { + return nil, err + } + return jws.Verify(key) +} + +func jsonWebkey(key query.PublicKey) *jose.JSONWebKey { + return &jose.JSONWebKey{ + KeyID: key.ID(), + Algorithm: key.Algorithm(), + Use: key.Use().String(), + Key: key.Key(), + } +} + +// keySetMap is a mapping of key IDs to public key data. +type keySetMap map[string][]byte + +// getKey finds the keyID and parses the public key data +// into a JSONWebKey. +func (k keySetMap) getKey(keyID string) (*jose.JSONWebKey, error) { + pubKey, err := crypto.BytesToPublicKey(k[keyID]) + if err != nil { + return nil, err + } + return &jose.JSONWebKey{ + Key: pubKey, + KeyID: keyID, + Use: domain.KeyUsageSigning.String(), + }, nil +} + +// VerifySignature implements the oidc.KeySet interface. +func (k keySetMap) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) { + if len(jws.Signatures) != 1 { + return nil, errors.ThrowInvalidArgument(nil, "OIDC-Eeth6", "Errors.Token.Invalid") + } + key, err := k.getKey(jws.Signatures[0].Header.KeyID) + if err != nil { + return nil, err + } + return jws.Verify(key) +} + const ( locksTable = "projections.locks" signingKey = "signing_key" diff --git a/internal/api/oidc/key_test.go b/internal/api/oidc/key_test.go new file mode 100644 index 0000000000..d1c191e800 --- /dev/null +++ b/internal/api/oidc/key_test.go @@ -0,0 +1,244 @@ +package oidc + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/go-jose/go-jose/v3" + "github.com/jonboulle/clockwork" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/query" +) + +type publicKey struct { + id string + alg string + use domain.KeyUsage + seq uint64 + expiry time.Time + key any +} + +func (k *publicKey) ID() string { + return k.id +} + +func (k *publicKey) Algorithm() string { + return k.alg +} + +func (k *publicKey) Use() domain.KeyUsage { + return k.use +} + +func (k *publicKey) Sequence() uint64 { + return k.seq +} + +func (k *publicKey) Expiry() time.Time { + return k.expiry +} + +func (k *publicKey) Key() any { + return k.key +} + +var ( + clock = clockwork.NewFakeClock() + keyDB = map[string]*publicKey{ + "key1": { + id: "key1", + alg: "alg", + use: domain.KeyUsageSigning, + seq: 1, + expiry: clock.Now().Add(time.Minute), + }, + "key2": { + id: "key2", + alg: "alg", + use: domain.KeyUsageSigning, + seq: 3, + expiry: clock.Now().Add(10 * time.Hour), + }, + } +) + +func queryKeyDB(_ context.Context, keyID string, current time.Time) (query.PublicKey, error) { + if key, ok := keyDB[keyID]; ok { + return key, nil + } + return nil, errors.New("not found") +} + +func Test_keySetCache(t *testing.T) { + background, cancel := context.WithCancel( + clockwork.AddToContext(context.Background(), clock), + ) + defer cancel() + + // create an empty keySet with a purge go routine, runs every Hour + keySet := newKeySet(background, time.Hour, queryKeyDB) + ctx := authz.NewMockContext("instanceID", "orgID", "userID") + + // query error + _, err := keySet.getKey(ctx, "key9") + require.Error(t, err) + + want := &jose.JSONWebKey{ + KeyID: "key1", + Algorithm: "alg", + Use: domain.KeyUsageSigning.String(), + } + + // get key first time, populate the cache + got, err := keySet.getKey(ctx, "key1") + require.NoError(t, err) + assert.Equal(t, want, got) + + // move time forward + clock.Advance(5 * time.Minute) + time.Sleep(time.Millisecond) + + // key should still be in cache + keySet.mtx.RLock() + _, ok := keySet.instanceKeys["instanceID"]["key1"] + require.True(t, ok) + keySet.mtx.RUnlock() + + // the key is expired, should error + _, err = keySet.getKey(ctx, "key1") + require.Error(t, err) + + want = &jose.JSONWebKey{ + KeyID: "key2", + Algorithm: "alg", + Use: domain.KeyUsageSigning.String(), + } + + // get the second key from DB + got, err = keySet.getKey(ctx, "key2") + require.NoError(t, err) + assert.Equal(t, want, got) + + // move time forward + clock.Advance(time.Hour) + time.Sleep(time.Millisecond) + + // first key shoud be purged, second still present + keySet.mtx.RLock() + _, ok = keySet.instanceKeys["instanceID"]["key1"] + require.False(t, ok) + _, ok = keySet.instanceKeys["instanceID"]["key2"] + require.True(t, ok) + keySet.mtx.RUnlock() + + // get the second key from cache + got, err = keySet.getKey(ctx, "key2") + require.NoError(t, err) + assert.Equal(t, want, got) + + // move time forward + clock.Advance(10 * time.Hour) + time.Sleep(time.Millisecond) + + // now the cache should be empty + keySet.mtx.RLock() + assert.Empty(t, keySet.instanceKeys) + keySet.mtx.RUnlock() +} + +func Test_keySetCache_VerifySignature(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + k := newKeySet(ctx, time.Second, queryKeyDB) + + tests := []struct { + name string + jws *jose.JSONWebSignature + }{ + { + name: "invalid token", + jws: &jose.JSONWebSignature{}, + }, + { + name: "key not found", + jws: &jose.JSONWebSignature{ + Signatures: []jose.Signature{{ + Header: jose.Header{ + KeyID: "xxx", + }, + }}, + }, + }, + { + name: "verify error", + jws: &jose.JSONWebSignature{ + Signatures: []jose.Signature{{ + Header: jose.Header{ + KeyID: "key1", + }, + }}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := k.VerifySignature(ctx, tt.jws) + require.Error(t, err) + }) + } +} + +func Test_keySetMap_VerifySignature(t *testing.T) { + tests := []struct { + name string + k keySetMap + jws *jose.JSONWebSignature + }{ + { + name: "invalid signature", + k: keySetMap{ + "key1": []byte("foo"), + }, + jws: &jose.JSONWebSignature{}, + }, + { + name: "parse error", + k: keySetMap{ + "key1": []byte("foo"), + }, + jws: &jose.JSONWebSignature{ + Signatures: []jose.Signature{{ + Header: jose.Header{ + KeyID: "key1", + }, + }}, + }, + }, + { + name: "verify error", + k: keySetMap{ + "key1": []byte("-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsvX9P58JFxEs5C+L+H7W\nduFSWL5EPzber7C2m94klrSV6q0bAcrYQnGwFOlveThsY200hRbadKaKjHD7qIKH\nDEe0IY2PSRht33Jye52AwhkRw+M3xuQH/7R8LydnsNFk2KHpr5X2SBv42e37LjkE\nslKSaMRgJW+v0KZ30piY8QsdFRKKaVg5/Ajt1YToM1YVsdHXJ3vmXFMtypLdxwUD\ndIaLEX6pFUkU75KSuEQ/E2luT61Q3ta9kOWm9+0zvi7OMcbdekJT7mzcVnh93R1c\n13ZhQCLbh9A7si8jKFtaMWevjayrvqQABEcTN9N4Hoxcyg6l4neZtRDk75OMYcqm\nDQIDAQAB\n-----END RSA PUBLIC KEY-----\n"), + }, + jws: &jose.JSONWebSignature{ + Signatures: []jose.Signature{{ + Header: jose.Header{ + KeyID: "key1", + }, + }}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := tt.k.VerifySignature(context.Background(), tt.jws) + require.Error(t, err) + }) + } +} diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go index 3e1780ef56..e615efabcf 100644 --- a/internal/api/oidc/op.go +++ b/internal/api/oidc/op.go @@ -45,6 +45,7 @@ type Config struct { DeviceAuth *DeviceAuthorizationConfig DefaultLoginURLV2 string DefaultLogoutURLV2 string + Features Features } type EndpointConfig struct { @@ -63,6 +64,11 @@ type Endpoint struct { URL string } +type Features struct { + TriggerIntrospectionProjections bool + LegacyIntrospection bool +} + type OPStorage struct { repo repository.Repository command *command.Commands @@ -120,7 +126,15 @@ func NewServer( server := &Server{ LegacyServer: op.NewLegacyServer(provider, endpoints(config.CustomEndpoints)), + features: config.Features, + repo: repo, + 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, + assetAPIPrefix: assets.AssetAPI(externalSecure), } metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount} server.Handler = op.RegisterLegacyServer(server, op.WithHTTPMiddleware( diff --git a/internal/api/oidc/server.go b/internal/api/oidc/server.go index 6e250ebeaf..fe16078f34 100644 --- a/internal/api/oidc/server.go +++ b/internal/api/oidc/server.go @@ -4,16 +4,32 @@ import ( "context" "net/http" + "github.com/zitadel/logging" "github.com/zitadel/oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/op" + "golang.org/x/exp/slog" + "github.com/zitadel/zitadel/internal/auth/repository" + "github.com/zitadel/zitadel/internal/command" + "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/telemetry/tracing" ) type Server struct { http.Handler *op.LegacyServer + features Features + + repo repository.Repository + query *query.Queries + command *command.Commands + keySet *keySetCache + + fallbackLogger *slog.Logger + hashAlg crypto.HashAlgorithm signingKeyAlgorithm string + assetAPIPrefix func(ctx context.Context) string } func endpoints(endpointConfig *EndpointConfig) op.Endpoints { @@ -59,6 +75,13 @@ func endpoints(endpointConfig *EndpointConfig) op.Endpoints { return endpoints } +func (s *Server) getLogger(ctx context.Context) *slog.Logger { + if logger, ok := logging.FromContext(ctx); ok { + return logger + } + return s.fallbackLogger +} + func (s *Server) IssuerFromRequest(r *http.Request) string { return s.Provider().IssuerFromRequest(r) } @@ -161,13 +184,6 @@ func (s *Server) DeviceToken(ctx context.Context, r *op.ClientRequest[oidc.Devic return s.LegacyServer.DeviceToken(ctx, r) } -func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionRequest]) (_ *op.Response, err error) { - ctx, span := tracing.NewSpan(ctx) - defer func() { span.EndWithError(err) }() - - return s.LegacyServer.Introspect(ctx, r) -} - func (s *Server) UserInfo(ctx context.Context, r *op.Request[oidc.UserInfoRequest]) (_ *op.Response, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/api/oidc/userinfo.go b/internal/api/oidc/userinfo.go new file mode 100644 index 0000000000..3b6a2053bc --- /dev/null +++ b/internal/api/oidc/userinfo.go @@ -0,0 +1,276 @@ +package oidc + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "slices" + "strings" + + "github.com/dop251/goja" + "github.com/zitadel/logging" + "github.com/zitadel/oidc/v3/pkg/oidc" + + "github.com/zitadel/zitadel/internal/actions" + "github.com/zitadel/zitadel/internal/actions/object" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/query" +) + +func (s *Server) userInfo(ctx context.Context, userID, projectID string, scope, roleAudience []string) (_ *oidc.UserInfo, err error) { + roleAudience, requestedRoles := prepareRoles(ctx, projectID, scope, roleAudience) + qu, err := s.query.GetOIDCUserInfo(ctx, userID, roleAudience) + if err != nil { + return nil, err + } + + userInfo := userInfoToOIDC(projectID, qu, scope, roleAudience, requestedRoles, s.assetAPIPrefix(ctx)) + return userInfo, s.userinfoFlows(ctx, qu, userInfo) +} + +// prepareRoles scans the requested scopes, appends to roleAudiendce and returns the requestedRoles. +// +// When [ScopeProjectsRoles] is present and roleAudience was empty, +// project IDs with the [domain.ProjectIDScope] prefix are added to the roleAudience. +// +// Scopes with [ScopeProjectRolePrefix] are added to requestedRoles. +// +// If the resulting requestedRoles or roleAudience are not not empty, +// the current projectID will always be parts or roleAudience. +// Else nil, nil is returned. +func prepareRoles(ctx context.Context, projectID string, scope, roleAudience []string) (ra, requestedRoles []string) { + // if all roles are requested take the audience for those from the scopes + if slices.Contains(scope, ScopeProjectsRoles) && len(roleAudience) == 0 { + roleAudience = domain.AddAudScopeToAudience(ctx, roleAudience, scope) + } + requestedRoles = make([]string, 0, len(scope)) + for _, s := range scope { + if role, ok := strings.CutPrefix(s, ScopeProjectRolePrefix); ok { + requestedRoles = append(requestedRoles, role) + } + } + if len(requestedRoles) == 0 && len(roleAudience) == 0 { + return nil, nil + } + + if projectID != "" && !slices.Contains(roleAudience, projectID) { + roleAudience = append(roleAudience, projectID) + } + return roleAudience, requestedRoles +} + +func userInfoToOIDC(projectID string, user *query.OIDCUserInfo, scope, roleAudience, requestedRoles []string, assetPrefix string) *oidc.UserInfo { + out := new(oidc.UserInfo) + for _, s := range scope { + switch s { + case oidc.ScopeOpenID: + out.Subject = user.User.ID + case oidc.ScopeEmail: + out.UserInfoEmail = userInfoEmailToOIDC(user.User) + case oidc.ScopeProfile: + out.UserInfoProfile = userInfoProfileToOidc(user.User, assetPrefix) + case oidc.ScopePhone: + out.UserInfoPhone = userInfoPhoneToOIDC(user.User) + case oidc.ScopeAddress: + //TODO: handle address for human users as soon as implemented + case ScopeUserMetaData: + setUserInfoMetadata(user.Metadata, out) + case ScopeResourceOwner: + setUserInfoOrgClaims(user, out) + default: + if claim, ok := strings.CutPrefix(s, domain.OrgDomainPrimaryScope); ok { + out.AppendClaims(domain.OrgDomainPrimaryClaim, claim) + } + if claim, ok := strings.CutPrefix(s, domain.OrgIDScope); ok { + out.AppendClaims(domain.OrgIDClaim, claim) + setUserInfoOrgClaims(user, out) + } + } + } + + // prevent returning obtained grants if none where requested + if (projectID != "" && len(requestedRoles) > 0) || len(roleAudience) > 0 { + setUserInfoRoleClaims(out, newProjectRoles(projectID, user.UserGrants, requestedRoles)) + } + return out +} + +func userInfoEmailToOIDC(user *query.User) oidc.UserInfoEmail { + if human := user.Human; human != nil { + return oidc.UserInfoEmail{ + Email: string(human.Email), + EmailVerified: oidc.Bool(human.IsEmailVerified), + } + } + return oidc.UserInfoEmail{} +} + +func userInfoProfileToOidc(user *query.User, assetPrefix string) oidc.UserInfoProfile { + if human := user.Human; human != nil { + return oidc.UserInfoProfile{ + Name: human.DisplayName, + GivenName: human.FirstName, + FamilyName: human.LastName, + Nickname: human.NickName, + Picture: domain.AvatarURL(assetPrefix, user.ResourceOwner, user.Human.AvatarKey), + Gender: getGender(human.Gender), + Locale: oidc.NewLocale(human.PreferredLanguage), + UpdatedAt: oidc.FromTime(user.ChangeDate), + PreferredUsername: user.PreferredLoginName, + } + } + if machine := user.Machine; machine != nil { + return oidc.UserInfoProfile{ + Name: machine.Name, + UpdatedAt: oidc.FromTime(user.ChangeDate), + PreferredUsername: user.PreferredLoginName, + } + } + return oidc.UserInfoProfile{} +} + +func userInfoPhoneToOIDC(user *query.User) oidc.UserInfoPhone { + if human := user.Human; human != nil { + return oidc.UserInfoPhone{ + PhoneNumber: string(human.Phone), + PhoneNumberVerified: human.IsPhoneVerified, + } + } + return oidc.UserInfoPhone{} +} + +func setUserInfoMetadata(metadata []query.UserMetadata, out *oidc.UserInfo) { + if len(metadata) == 0 { + return + } + mdmap := make(map[string]string, len(metadata)) + for _, md := range metadata { + mdmap[md.Key] = base64.RawURLEncoding.EncodeToString(md.Value) + } + out.AppendClaims(ClaimUserMetaData, mdmap) +} + +func setUserInfoOrgClaims(user *query.OIDCUserInfo, out *oidc.UserInfo) { + if org := user.Org; org != nil { + out.AppendClaims(ClaimResourceOwner+"id", org.ID) + out.AppendClaims(ClaimResourceOwner+"name", org.Name) + out.AppendClaims(ClaimResourceOwner+"primary_domain", org.PrimaryDomain) + } +} + +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, qu *query.OIDCUserInfo, userInfo *oidc.UserInfo) error { + queriedActions, err := s.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreUserinfoCreation, qu.User.ResourceOwner) + if err != nil { + return err + } + + ctxFields := actions.SetContextFields( + actions.SetFields("v1", + actions.SetFields("claims", userinfoClaims(userInfo)), + actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} { + return func(call goja.FunctionCall) goja.Value { + return object.UserFromQuery(c, qu.User) + } + }), + actions.SetFields("user", + actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} { + return func(goja.FunctionCall) goja.Value { + return object.UserMetadataListFromSlice(c, qu.Metadata) + } + }), + actions.SetFields("grants", func(c *actions.FieldConfig) interface{} { + return object.UserGrantsFromSlice(c, qu.UserGrants) + }), + ), + ), + ) + + for _, action := range queriedActions { + actionCtx, cancel := context.WithTimeout(ctx, action.Timeout()) + claimLogs := []string{} + + apiFields := actions.WithAPIFields( + actions.SetFields("v1", + actions.SetFields("userinfo", + actions.SetFields("setClaim", func(key string, value interface{}) { + if userInfo.Claims[key] == nil { + userInfo.AppendClaims(key, value) + return + } + claimLogs = append(claimLogs, fmt.Sprintf("key %q already exists", key)) + }), + actions.SetFields("appendLogIntoClaims", func(entry string) { + claimLogs = append(claimLogs, entry) + }), + ), + actions.SetFields("claims", + actions.SetFields("setClaim", func(key string, value interface{}) { + if userInfo.Claims[key] == nil { + userInfo.AppendClaims(key, value) + return + } + claimLogs = append(claimLogs, fmt.Sprintf("key %q already exists", key)) + }), + actions.SetFields("appendLogIntoClaims", func(entry string) { + claimLogs = append(claimLogs, entry) + }), + ), + actions.SetFields("user", + actions.SetFields("setMetadata", func(call goja.FunctionCall) goja.Value { + if len(call.Arguments) != 2 { + panic("exactly 2 (key, value) arguments expected") + } + key := call.Arguments[0].Export().(string) + val := call.Arguments[1].Export() + + value, err := json.Marshal(val) + if err != nil { + logging.WithError(err).Debug("unable to marshal") + panic(err) + } + + metadata := &domain.Metadata{ + Key: key, + Value: value, + } + if _, err = s.command.SetUserMetadata(ctx, metadata, userInfo.Subject, qu.User.ResourceOwner); err != nil { + logging.WithError(err).Info("unable to set md in action") + panic(err) + } + return nil + }), + ), + ), + ) + + err = actions.Run( + actionCtx, + ctxFields, + apiFields, + action.Script, + action.Name, + append(actions.ActionToOptions(action), actions.WithHTTP(actionCtx), actions.WithUUID(actionCtx))..., + ) + cancel() + if err != nil { + return err + } + if len(claimLogs) > 0 { + userInfo.AppendClaims(fmt.Sprintf(ClaimActionLogFormat, action.Name), claimLogs) + } + } + + return nil +} diff --git a/internal/api/oidc/userinfo_test.go b/internal/api/oidc/userinfo_test.go new file mode 100644 index 0000000000..f0bc05e8ca --- /dev/null +++ b/internal/api/oidc/userinfo_test.go @@ -0,0 +1,434 @@ +package oidc + +import ( + "context" + "encoding/base64" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/zitadel/oidc/v3/pkg/oidc" + "golang.org/x/text/language" + + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/query" +) + +func Test_prepareRoles(t *testing.T) { + type args struct { + projectID string + scope []string + roleAudience []string + } + tests := []struct { + name string + args args + wantRa []string + wantRequestedRoles []string + }{ + { + name: "empty scope and roleAudience", + args: args{ + projectID: "projID", + scope: nil, + roleAudience: nil, + }, + wantRa: nil, + wantRequestedRoles: nil, + }, + { + name: "some scope and roleAudience", + args: args{ + projectID: "projID", + scope: []string{"openid", "profile"}, + roleAudience: []string{"project2"}, + }, + wantRa: []string{"project2", "projID"}, + wantRequestedRoles: []string{}, + }, + { + name: "scope projects roles", + args: args{ + projectID: "projID", + scope: []string{ScopeProjectsRoles, domain.ProjectIDScope + "project2" + domain.AudSuffix}, + roleAudience: nil, + }, + wantRa: []string{"project2", "projID"}, + wantRequestedRoles: []string{}, + }, + { + name: "scope project role prefix", + args: args{ + projectID: "projID", + scope: []string{"openid", "profile", ScopeProjectRolePrefix + "foo", ScopeProjectRolePrefix + "bar"}, + roleAudience: nil, + }, + wantRa: []string{"projID"}, + wantRequestedRoles: []string{"foo", "bar"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotRa, gotRequestedRoles := prepareRoles(context.Background(), tt.args.projectID, tt.args.scope, tt.args.roleAudience) + assert.Equal(t, tt.wantRa, gotRa, "roleAudience") + assert.Equal(t, tt.wantRequestedRoles, gotRequestedRoles, "requestedRoles") + }) + } +} + +func Test_userInfoToOIDC(t *testing.T) { + metadata := []query.UserMetadata{ + { + Key: "key1", + Value: []byte{1, 2, 3}, + }, + { + Key: "key2", + Value: []byte{4, 5, 6}, + }, + } + organization := &query.UserInfoOrg{ + ID: "orgID", + Name: "orgName", + PrimaryDomain: "orgDomain", + } + humanUserInfo := &query.OIDCUserInfo{ + User: &query.User{ + ID: "human1", + CreationDate: time.Unix(123, 456), + ChangeDate: time.Unix(567, 890), + ResourceOwner: "orgID", + Sequence: 22, + State: domain.UserStateActive, + Type: domain.UserTypeHuman, + Username: "username", + LoginNames: []string{"foo", "bar"}, + PreferredLoginName: "foo", + Human: &query.Human{ + FirstName: "user", + LastName: "name", + NickName: "foobar", + DisplayName: "xxx", + AvatarKey: "picture.png", + PreferredLanguage: language.Dutch, + Gender: domain.GenderDiverse, + Email: "foo@bar.com", + IsEmailVerified: true, + Phone: "+31123456789", + IsPhoneVerified: true, + }, + }, + Metadata: metadata, + Org: organization, + UserGrants: []query.UserGrant{ + { + ID: "ug1", + CreationDate: time.Unix(444, 444), + ChangeDate: time.Unix(555, 555), + Sequence: 55, + Roles: []string{"role1", "role2"}, + GrantID: "grantID", + State: domain.UserGrantStateActive, + UserID: "human1", + Username: "username", + ResourceOwner: "orgID", + ProjectID: "project1", + OrgName: "orgName", + OrgPrimaryDomain: "orgDomain", + ProjectName: "projectName", + UserResourceOwner: "org1", + }, + }, + } + machineUserInfo := &query.OIDCUserInfo{ + User: &query.User{ + ID: "machine1", + CreationDate: time.Unix(123, 456), + ChangeDate: time.Unix(567, 890), + ResourceOwner: "orgID", + Sequence: 23, + State: domain.UserStateActive, + Type: domain.UserTypeMachine, + Username: "machine", + PreferredLoginName: "meanMachine", + Machine: &query.Machine{ + Name: "machine", + Description: "I'm a robot", + }, + }, + Org: organization, + UserGrants: []query.UserGrant{ + { + ID: "ug1", + CreationDate: time.Unix(444, 444), + ChangeDate: time.Unix(555, 555), + Sequence: 55, + Roles: []string{"role1", "role2"}, + GrantID: "grantID", + State: domain.UserGrantStateActive, + UserID: "human1", + Username: "username", + ResourceOwner: "orgID", + ProjectID: "project1", + OrgName: "orgName", + OrgPrimaryDomain: "orgDomain", + ProjectName: "projectName", + UserResourceOwner: "org1", + }, + }, + } + + type args struct { + projectID string + user *query.OIDCUserInfo + scope []string + roleAudience []string + requestedRoles []string + } + tests := []struct { + name string + args args + want *oidc.UserInfo + }{ + { + name: "human, empty", + args: args{ + projectID: "project1", + user: humanUserInfo, + }, + want: &oidc.UserInfo{}, + }, + { + name: "machine, empty", + args: args{ + projectID: "project1", + user: machineUserInfo, + }, + want: &oidc.UserInfo{}, + }, + { + name: "human, scope openid", + args: args{ + projectID: "project1", + user: humanUserInfo, + scope: []string{oidc.ScopeOpenID}, + }, + want: &oidc.UserInfo{ + Subject: "human1", + }, + }, + { + name: "machine, scope openid", + args: args{ + projectID: "project1", + user: machineUserInfo, + scope: []string{oidc.ScopeOpenID}, + }, + want: &oidc.UserInfo{ + Subject: "machine1", + }, + }, + { + name: "human, scope email", + args: args{ + projectID: "project1", + user: humanUserInfo, + scope: []string{oidc.ScopeEmail}, + }, + want: &oidc.UserInfo{ + UserInfoEmail: oidc.UserInfoEmail{ + Email: "foo@bar.com", + EmailVerified: true, + }, + }, + }, + { + name: "machine, scope email", + args: args{ + projectID: "project1", + user: machineUserInfo, + scope: []string{oidc.ScopeEmail}, + }, + want: &oidc.UserInfo{ + UserInfoEmail: oidc.UserInfoEmail{}, + }, + }, + { + name: "human, scope profile", + args: args{ + projectID: "project1", + user: humanUserInfo, + scope: []string{oidc.ScopeProfile}, + }, + want: &oidc.UserInfo{ + UserInfoProfile: oidc.UserInfoProfile{ + Name: "xxx", + GivenName: "user", + FamilyName: "name", + Nickname: "foobar", + Picture: "https://foo.com/assets/orgID/picture.png", + Gender: "diverse", + Locale: oidc.NewLocale(language.Dutch), + UpdatedAt: oidc.FromTime(time.Unix(567, 890)), + PreferredUsername: "foo", + }, + }, + }, + { + name: "machine, scope profile", + args: args{ + projectID: "project1", + user: machineUserInfo, + scope: []string{oidc.ScopeProfile}, + }, + want: &oidc.UserInfo{ + UserInfoProfile: oidc.UserInfoProfile{ + Name: "machine", + UpdatedAt: oidc.FromTime(time.Unix(567, 890)), + PreferredUsername: "meanMachine", + }, + }, + }, + { + name: "human, scope phone", + args: args{ + projectID: "project1", + user: humanUserInfo, + scope: []string{oidc.ScopePhone}, + }, + want: &oidc.UserInfo{ + UserInfoPhone: oidc.UserInfoPhone{ + PhoneNumber: "+31123456789", + PhoneNumberVerified: true, + }, + }, + }, + { + name: "machine, scope phone", + args: args{ + projectID: "project1", + user: machineUserInfo, + scope: []string{oidc.ScopePhone}, + }, + want: &oidc.UserInfo{ + UserInfoPhone: oidc.UserInfoPhone{}, + }, + }, + { + name: "human, scope metadata", + args: args{ + projectID: "project1", + user: humanUserInfo, + scope: []string{ScopeUserMetaData}, + }, + want: &oidc.UserInfo{ + Claims: map[string]any{ + ClaimUserMetaData: map[string]string{ + "key1": base64.RawURLEncoding.EncodeToString([]byte{1, 2, 3}), + "key2": base64.RawURLEncoding.EncodeToString([]byte{4, 5, 6}), + }, + }, + }, + }, + { + name: "machine, scope metadata, none found", + args: args{ + projectID: "project1", + user: machineUserInfo, + scope: []string{ScopeUserMetaData}, + }, + want: &oidc.UserInfo{}, + }, + { + name: "machine, scope resource owner", + args: args{ + projectID: "project1", + user: machineUserInfo, + scope: []string{ScopeResourceOwner}, + }, + want: &oidc.UserInfo{ + Claims: map[string]any{ + ClaimResourceOwner + "id": "orgID", + ClaimResourceOwner + "name": "orgName", + ClaimResourceOwner + "primary_domain": "orgDomain", + }, + }, + }, + { + name: "human, scope org primary domain prefix", + args: args{ + projectID: "project1", + user: humanUserInfo, + scope: []string{domain.OrgDomainPrimaryScope + "foo.com"}, + }, + want: &oidc.UserInfo{ + Claims: map[string]any{ + domain.OrgDomainPrimaryClaim: "foo.com", + }, + }, + }, + { + name: "machine, scope org id", + args: args{ + projectID: "project1", + user: machineUserInfo, + scope: []string{domain.OrgIDScope + "orgID"}, + }, + want: &oidc.UserInfo{ + Claims: map[string]any{ + domain.OrgIDClaim: "orgID", + ClaimResourceOwner + "id": "orgID", + ClaimResourceOwner + "name": "orgName", + ClaimResourceOwner + "primary_domain": "orgDomain", + }, + }, + }, + { + name: "human, roleAudience", + args: args{ + projectID: "project1", + user: humanUserInfo, + roleAudience: []string{"project1"}, + }, + want: &oidc.UserInfo{ + Claims: map[string]any{ + ClaimProjectRoles: projectRoles{ + "role1": {"orgID": "orgDomain"}, + "role2": {"orgID": "orgDomain"}, + }, + fmt.Sprintf(ClaimProjectRolesFormat, "project1"): projectRoles{ + "role1": {"orgID": "orgDomain"}, + "role2": {"orgID": "orgDomain"}, + }, + }, + }, + }, + { + name: "human, requested roles", + args: args{ + projectID: "project1", + user: humanUserInfo, + roleAudience: []string{"project1"}, + requestedRoles: []string{"role2"}, + }, + want: &oidc.UserInfo{ + Claims: map[string]any{ + ClaimProjectRoles: projectRoles{ + "role2": {"orgID": "orgDomain"}, + }, + fmt.Sprintf(ClaimProjectRolesFormat, "project1"): projectRoles{ + "role2": {"orgID": "orgDomain"}, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assetPrefix := "https://foo.com/assets" + got := userInfoToOIDC(tt.args.projectID, tt.args.user, tt.args.scope, tt.args.roleAudience, tt.args.requestedRoles, assetPrefix) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/internal/api/saml/storage.go b/internal/api/saml/storage.go index cfe97ee3d4..cad83d86a5 100644 --- a/internal/api/saml/storage.go +++ b/internal/api/saml/storage.go @@ -55,7 +55,7 @@ type Storage struct { } func (p *Storage) GetEntityByID(ctx context.Context, entityID string) (*serviceprovider.ServiceProvider, error) { - app, err := p.query.AppBySAMLEntityID(ctx, entityID, false) + app, err := p.query.AppBySAMLEntityID(ctx, entityID) if err != nil { return nil, err } @@ -72,7 +72,7 @@ func (p *Storage) GetEntityByID(ctx context.Context, entityID string) (*servicep } func (p *Storage) GetEntityIDByAppID(ctx context.Context, appID string) (string, error) { - app, err := p.query.AppByID(ctx, appID, false) + app, err := p.query.AppByID(ctx, appID) if err != nil { return "", err } @@ -133,7 +133,7 @@ func (p *Storage) AuthRequestByID(ctx context.Context, id string) (_ models.Auth func (p *Storage) SetUserinfoWithUserID(ctx context.Context, applicationID string, userinfo models.AttributeSetter, userID string, attributes []int) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - user, err := p.query.GetUserByID(ctx, true, userID, false) + user, err := p.query.GetUserByID(ctx, true, userID) if err != nil { return err } @@ -163,7 +163,7 @@ func (p *Storage) SetUserinfoWithLoginName(ctx context.Context, userinfo models. if err != nil { return err } - user, err := p.query.GetUser(ctx, true, false, loginNameSQ) + user, err := p.query.GetUser(ctx, true, loginNameSQ) if err != nil { return err } @@ -314,7 +314,7 @@ func (p *Storage) getCustomAttributes(ctx context.Context, user *query.User, use } func (p *Storage) getGrants(ctx context.Context, userID, applicationID string) (*query.UserGrants, error) { - projectID, err := p.query.ProjectIDFromClientID(ctx, applicationID, false) + projectID, err := p.query.ProjectIDFromClientID(ctx, applicationID) if err != nil { return nil, err } diff --git a/internal/api/ui/login/custom_action.go b/internal/api/ui/login/custom_action.go index d2acffb9ac..83c3bdeab0 100644 --- a/internal/api/ui/login/custom_action.go +++ b/internal/api/ui/login/custom_action.go @@ -347,7 +347,7 @@ func (l *Login) runPostCreationActions( actions.SetFields("v1", actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} { return func(call goja.FunctionCall) goja.Value { - user, err := l.query.GetUserByID(actionCtx, true, userID, false) + user, err := l.query.GetUserByID(actionCtx, true, userID) if err != nil { panic(err) } diff --git a/internal/api/ui/login/external_provider_handler.go b/internal/api/ui/login/external_provider_handler.go index ca9633780a..3bab7b90e6 100644 --- a/internal/api/ui/login/external_provider_handler.go +++ b/internal/api/ui/login/external_provider_handler.go @@ -677,7 +677,7 @@ func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, aut // updateExternalUser will update the existing user (email, phone, profile) with data provided by the IDP func (l *Login) updateExternalUser(ctx context.Context, authReq *domain.AuthRequest, externalUser *domain.ExternalUser) error { - user, err := l.query.GetUserByID(ctx, true, authReq.UserID, false) + user, err := l.query.GetUserByID(ctx, true, authReq.UserID) if err != nil { return err } diff --git a/internal/api/ui/login/init_password_handler.go b/internal/api/ui/login/init_password_handler.go index aac05bbb92..e6939e0c09 100644 --- a/internal/api/ui/login/init_password_handler.go +++ b/internal/api/ui/login/init_password_handler.go @@ -101,7 +101,7 @@ func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authRe l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) return } - user, err := l.query.GetUser(setContext(r.Context(), userOrg), false, false, loginName) + user, err := l.query.GetUser(setContext(r.Context(), userOrg), false, loginName) if err != nil { l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) return @@ -144,7 +144,7 @@ func (l *Login) renderInitPassword(w http.ResponseWriter, r *http.Request, authR } } if authReq == nil { - user, err := l.query.GetUserByID(r.Context(), false, userID, false) + user, err := l.query.GetUserByID(r.Context(), false, userID) if err == nil { l.customTexts(r.Context(), translator, user.ResourceOwner) } diff --git a/internal/api/ui/login/init_user_handler.go b/internal/api/ui/login/init_user_handler.go index fb4b8b8459..df2f940d6a 100644 --- a/internal/api/ui/login/init_user_handler.go +++ b/internal/api/ui/login/init_user_handler.go @@ -146,7 +146,7 @@ func (l *Login) renderInitUser(w http.ResponseWriter, r *http.Request, authReq * } } if authReq == nil { - user, err := l.query.GetUserByID(r.Context(), false, userID, false) + user, err := l.query.GetUserByID(r.Context(), false, userID) if err == nil { l.customTexts(r.Context(), translator, user.ResourceOwner) } diff --git a/internal/api/ui/login/login.go b/internal/api/ui/login/login.go index 9c41bc61e9..dc8e834fcd 100644 --- a/internal/api/ui/login/login.go +++ b/internal/api/ui/login/login.go @@ -174,7 +174,7 @@ func (l *Login) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgName string if err != nil { return nil, err } - users, err := l.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{loginName}}, false) + users, err := l.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{loginName}}) if err != nil { return nil, err } diff --git a/internal/api/ui/login/mail_verify_handler.go b/internal/api/ui/login/mail_verify_handler.go index d26c0c68ba..bfcb322fa2 100644 --- a/internal/api/ui/login/mail_verify_handler.go +++ b/internal/api/ui/login/mail_verify_handler.go @@ -100,7 +100,7 @@ func (l *Login) renderMailVerification(w http.ResponseWriter, r *http.Request, a profileData: l.getProfileData(authReq), } if authReq == nil { - user, err := l.query.GetUserByID(r.Context(), false, userID, false) + user, err := l.query.GetUserByID(r.Context(), false, userID) if err == nil { l.customTexts(r.Context(), translator, user.ResourceOwner) } diff --git a/internal/api/ui/login/mfa_init_sms.go b/internal/api/ui/login/mfa_init_sms.go index 6443d6172e..965806c90b 100644 --- a/internal/api/ui/login/mfa_init_sms.go +++ b/internal/api/ui/login/mfa_init_sms.go @@ -29,7 +29,7 @@ type smsInitFormData struct { // It will also add a successful OTP SMS check to the auth request. // If there's no verified phone number, the potential last phone number will be used to render the registration page func (l *Login) handleRegisterOTPSMS(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { - user, err := l.query.GetNotifyUserByID(r.Context(), true, authReq.UserID, false) + user, err := l.query.GetNotifyUserByID(r.Context(), true, authReq.UserID) if err != nil { l.renderError(w, r, authReq, err) return diff --git a/internal/api/ui/login/password_complexity_policy_handler.go b/internal/api/ui/login/password_complexity_policy_handler.go index ee0bacdaec..805194a077 100644 --- a/internal/api/ui/login/password_complexity_policy_handler.go +++ b/internal/api/ui/login/password_complexity_policy_handler.go @@ -22,7 +22,7 @@ func (l *Login) getPasswordComplexityPolicy(r *http.Request, orgID string) *iam_ } func (l *Login) getPasswordComplexityPolicyByUserID(r *http.Request, userID string) *iam_model.PasswordComplexityPolicyView { - user, err := l.query.GetUserByID(r.Context(), false, userID, false) + user, err := l.query.GetUserByID(r.Context(), false, userID) if err != nil { logging.WithFields("userID", userID).OnError(err).Error("could not load user for password complexity policy") return nil diff --git a/internal/api/ui/login/password_reset_handler.go b/internal/api/ui/login/password_reset_handler.go index c85ca97b74..ea8cd43321 100644 --- a/internal/api/ui/login/password_reset_handler.go +++ b/internal/api/ui/login/password_reset_handler.go @@ -23,7 +23,7 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) { l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) return } - user, err := l.query.GetUser(setContext(r.Context(), authReq.UserOrgID), true, false, loginName) + user, err := l.query.GetUser(setContext(r.Context(), authReq.UserOrgID), true, loginName) if err != nil { if authReq.LoginPolicy.IgnoreUnknownUsernames && errors.IsNotFound(err) { err = nil diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 445910b2f6..5da4dcee64 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -113,7 +113,7 @@ type projectProvider interface { } type applicationProvider interface { - AppByOIDCClientID(context.Context, string, bool) (*query.App, error) + AppByOIDCClientID(context.Context, string) (*query.App, error) } type customTextProvider interface { @@ -140,7 +140,7 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *dom if err != nil { return nil, err } - appIDs, err := repo.Query.SearchClientIDs(ctx, &query.AppSearchQueries{Queries: []query.SearchQuery{projectIDQuery}}, false) + appIDs, err := repo.Query.SearchClientIDs(ctx, &query.AppSearchQueries{Queries: []query.SearchQuery{projectIDQuery}}) if err != nil { return nil, err } @@ -1351,7 +1351,7 @@ func (repo *AuthRequestRepo) hasSucceededPage(ctx context.Context, request *doma if _, ok := request.Request.(*domain.AuthRequestOIDC); !ok { return false, nil } - app, err := provider.AppByOIDCClientID(ctx, request.ApplicationID, false) + app, err := provider.AppByOIDCClientID(ctx, request.ApplicationID) if err != nil { return false, err } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index 5eff6ac6ce..f9ea9c28cb 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -272,7 +272,7 @@ type mockApp struct { app *query.App } -func (m *mockApp) AppByOIDCClientID(ctx context.Context, id string, _ bool) (*query.App, error) { +func (m *mockApp) AppByOIDCClientID(ctx context.Context, id string) (*query.App, error) { if m.app != nil { return m.app, nil } diff --git a/internal/auth/repository/eventsourcing/view/user.go b/internal/auth/repository/eventsourcing/view/user.go index 2eaf538273..7dee9e090c 100644 --- a/internal/auth/repository/eventsourcing/view/user.go +++ b/internal/auth/repository/eventsourcing/view/user.go @@ -85,7 +85,7 @@ func (v *View) UserByPhoneAndResourceOwner(ctx context.Context, phone, resourceO } func (v *View) userByID(ctx context.Context, instanceID string, queries ...query.SearchQuery) (*model.UserView, error) { - queriedUser, err := v.query.GetNotifyUser(ctx, true, false, queries...) + queriedUser, err := v.query.GetNotifyUser(ctx, true, queries...) if err != nil { return nil, err } diff --git a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go index bcc4cfd34f..823a6da4db 100644 --- a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go +++ b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go @@ -171,7 +171,7 @@ func (repo *TokenVerifierRepo) checkAuthentication(ctx context.Context, authMeth if domain.HasMFA(authMethods) { return nil } - availableAuthMethods, forceMFA, forceMFALocalOnly, err := repo.Query.ListUserAuthMethodTypesRequired(setCallerCtx(ctx, userID), userID, false) + availableAuthMethods, forceMFA, forceMFALocalOnly, err := repo.Query.ListUserAuthMethodTypesRequired(setCallerCtx(ctx, userID), userID) if err != nil { return err } diff --git a/internal/authz/repository/eventsourcing/view/application.go b/internal/authz/repository/eventsourcing/view/application.go index 22170d8ba8..8958f2e7e8 100644 --- a/internal/authz/repository/eventsourcing/view/application.go +++ b/internal/authz/repository/eventsourcing/view/application.go @@ -9,7 +9,7 @@ import ( ) func (v *View) ApplicationByOIDCClientID(ctx context.Context, clientID string) (*query.App, error) { - return v.Query.AppByOIDCClientID(ctx, clientID, false) + return v.Query.AppByOIDCClientID(ctx, clientID) } func (v *View) ApplicationByProjecIDAndAppName(ctx context.Context, projectID, appName string) (_ *query.App, err error) { diff --git a/internal/eventstore/handler/v2/handler.go b/internal/eventstore/handler/v2/handler.go index c03d695dd8..cfa21b05ab 100644 --- a/internal/eventstore/handler/v2/handler.go +++ b/internal/eventstore/handler/v2/handler.go @@ -456,3 +456,8 @@ func (h *Handler) eventQuery(currentState *state) *eventstore.SearchQueryBuilder return builder } + +// ProjectionName returns the name of the unlying projection. +func (h *Handler) ProjectionName() string { + return h.projection.Name() +} diff --git a/internal/integration/integration.go b/internal/integration/integration.go index 61d7f9d90d..15cffe43b4 100644 --- a/internal/integration/integration.go +++ b/internal/integration/integration.go @@ -215,7 +215,7 @@ func (s *Tester) createMachineUser(ctx context.Context, username string, userTyp usernameQuery, err := query.NewUserUsernameSearchQuery(username, query.TextEquals) logging.OnError(err).Fatal("user query") - user, err := s.Queries.GetUser(ctx, true, true, usernameQuery) + user, err := s.Queries.GetUser(ctx, true, usernameQuery) if errors.Is(err, sql.ErrNoRows) { _, err = s.Commands.AddMachine(ctx, &command.Machine{ ObjectRoot: models.ObjectRoot{ @@ -227,7 +227,7 @@ func (s *Tester) createMachineUser(ctx context.Context, username string, userTyp AccessTokenType: domain.OIDCTokenTypeJWT, }) logging.WithFields("username", username).OnError(err).Fatal("add machine user") - user, err = s.Queries.GetUser(ctx, true, true, usernameQuery) + user, err = s.Queries.GetUser(ctx, true, usernameQuery) } logging.WithFields("username", username).OnError(err).Fatal("get user") diff --git a/internal/notification/handlers/mock/queries.mock.go b/internal/notification/handlers/mock/queries.mock.go index f9c2b2d895..2fd152d629 100644 --- a/internal/notification/handlers/mock/queries.mock.go +++ b/internal/notification/handlers/mock/queries.mock.go @@ -6,37 +6,38 @@ package mock import ( context "context" + reflect "reflect" + gomock "github.com/golang/mock/gomock" domain "github.com/zitadel/zitadel/internal/domain" query "github.com/zitadel/zitadel/internal/query" language "golang.org/x/text/language" - reflect "reflect" ) -// MockQueries is a mock of Queries interface +// MockQueries is a mock of Queries interface. type MockQueries struct { ctrl *gomock.Controller recorder *MockQueriesMockRecorder } -// MockQueriesMockRecorder is the mock recorder for MockQueries +// MockQueriesMockRecorder is the mock recorder for MockQueries. type MockQueriesMockRecorder struct { mock *MockQueries } -// NewMockQueries creates a new mock instance +// NewMockQueries creates a new mock instance. func NewMockQueries(ctrl *gomock.Controller) *MockQueries { mock := &MockQueries{ctrl: ctrl} mock.recorder = &MockQueriesMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockQueries) EXPECT() *MockQueriesMockRecorder { return m.recorder } -// ActiveLabelPolicyByOrg mocks base method +// ActiveLabelPolicyByOrg mocks base method. func (m *MockQueries) ActiveLabelPolicyByOrg(arg0 context.Context, arg1 string, arg2 bool) (*query.LabelPolicy, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ActiveLabelPolicyByOrg", arg0, arg1, arg2) @@ -45,13 +46,13 @@ func (m *MockQueries) ActiveLabelPolicyByOrg(arg0 context.Context, arg1 string, return ret0, ret1 } -// ActiveLabelPolicyByOrg indicates an expected call of ActiveLabelPolicyByOrg +// ActiveLabelPolicyByOrg indicates an expected call of ActiveLabelPolicyByOrg. func (mr *MockQueriesMockRecorder) ActiveLabelPolicyByOrg(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ActiveLabelPolicyByOrg", reflect.TypeOf((*MockQueries)(nil).ActiveLabelPolicyByOrg), arg0, arg1, arg2) } -// CustomTextListByTemplate mocks base method +// CustomTextListByTemplate mocks base method. func (m *MockQueries) CustomTextListByTemplate(arg0 context.Context, arg1, arg2 string, arg3 bool) (*query.CustomTexts, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CustomTextListByTemplate", arg0, arg1, arg2, arg3) @@ -60,13 +61,13 @@ func (m *MockQueries) CustomTextListByTemplate(arg0 context.Context, arg1, arg2 return ret0, ret1 } -// CustomTextListByTemplate indicates an expected call of CustomTextListByTemplate +// CustomTextListByTemplate indicates an expected call of CustomTextListByTemplate. func (mr *MockQueriesMockRecorder) CustomTextListByTemplate(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CustomTextListByTemplate", reflect.TypeOf((*MockQueries)(nil).CustomTextListByTemplate), arg0, arg1, arg2, arg3) } -// GetDefaultLanguage mocks base method +// GetDefaultLanguage mocks base method. func (m *MockQueries) GetDefaultLanguage(arg0 context.Context) language.Tag { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetDefaultLanguage", arg0) @@ -74,17 +75,17 @@ func (m *MockQueries) GetDefaultLanguage(arg0 context.Context) language.Tag { return ret0 } -// GetDefaultLanguage indicates an expected call of GetDefaultLanguage +// GetDefaultLanguage indicates an expected call of GetDefaultLanguage. func (mr *MockQueriesMockRecorder) GetDefaultLanguage(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultLanguage", reflect.TypeOf((*MockQueries)(nil).GetDefaultLanguage), arg0) } -// GetNotifyUserByID mocks base method -func (m *MockQueries) GetNotifyUserByID(arg0 context.Context, arg1 bool, arg2 string, arg3 bool, arg4 ...query.SearchQuery) (*query.NotifyUser, error) { +// GetNotifyUserByID mocks base method. +func (m *MockQueries) GetNotifyUserByID(arg0 context.Context, arg1 bool, arg2 string, arg3 ...query.SearchQuery) (*query.NotifyUser, error) { m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1, arg2, arg3} - for _, a := range arg4 { + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetNotifyUserByID", varargs...) @@ -93,14 +94,14 @@ func (m *MockQueries) GetNotifyUserByID(arg0 context.Context, arg1 bool, arg2 st return ret0, ret1 } -// GetNotifyUserByID indicates an expected call of GetNotifyUserByID -func (mr *MockQueriesMockRecorder) GetNotifyUserByID(arg0, arg1, arg2, arg3 interface{}, arg4 ...interface{}) *gomock.Call { +// GetNotifyUserByID indicates an expected call of GetNotifyUserByID. +func (mr *MockQueriesMockRecorder) GetNotifyUserByID(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1, arg2, arg3}, arg4...) + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotifyUserByID", reflect.TypeOf((*MockQueries)(nil).GetNotifyUserByID), varargs...) } -// MailTemplateByOrg mocks base method +// MailTemplateByOrg mocks base method. func (m *MockQueries) MailTemplateByOrg(arg0 context.Context, arg1 string, arg2 bool) (*query.MailTemplate, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "MailTemplateByOrg", arg0, arg1, arg2) @@ -109,13 +110,13 @@ func (m *MockQueries) MailTemplateByOrg(arg0 context.Context, arg1 string, arg2 return ret0, ret1 } -// MailTemplateByOrg indicates an expected call of MailTemplateByOrg +// MailTemplateByOrg indicates an expected call of MailTemplateByOrg. func (mr *MockQueriesMockRecorder) MailTemplateByOrg(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MailTemplateByOrg", reflect.TypeOf((*MockQueries)(nil).MailTemplateByOrg), arg0, arg1, arg2) } -// NotificationPolicyByOrg mocks base method +// NotificationPolicyByOrg mocks base method. func (m *MockQueries) NotificationPolicyByOrg(arg0 context.Context, arg1 bool, arg2 string, arg3 bool) (*query.NotificationPolicy, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NotificationPolicyByOrg", arg0, arg1, arg2, arg3) @@ -124,13 +125,13 @@ func (m *MockQueries) NotificationPolicyByOrg(arg0 context.Context, arg1 bool, a return ret0, ret1 } -// NotificationPolicyByOrg indicates an expected call of NotificationPolicyByOrg +// NotificationPolicyByOrg indicates an expected call of NotificationPolicyByOrg. func (mr *MockQueriesMockRecorder) NotificationPolicyByOrg(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotificationPolicyByOrg", reflect.TypeOf((*MockQueries)(nil).NotificationPolicyByOrg), arg0, arg1, arg2, arg3) } -// NotificationProviderByIDAndType mocks base method +// NotificationProviderByIDAndType mocks base method. func (m *MockQueries) NotificationProviderByIDAndType(arg0 context.Context, arg1 string, arg2 domain.NotificationProviderType) (*query.DebugNotificationProvider, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NotificationProviderByIDAndType", arg0, arg1, arg2) @@ -139,13 +140,13 @@ func (m *MockQueries) NotificationProviderByIDAndType(arg0 context.Context, arg1 return ret0, ret1 } -// NotificationProviderByIDAndType indicates an expected call of NotificationProviderByIDAndType +// NotificationProviderByIDAndType indicates an expected call of NotificationProviderByIDAndType. func (mr *MockQueriesMockRecorder) NotificationProviderByIDAndType(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotificationProviderByIDAndType", reflect.TypeOf((*MockQueries)(nil).NotificationProviderByIDAndType), arg0, arg1, arg2) } -// SMSProviderConfig mocks base method +// SMSProviderConfig mocks base method. func (m *MockQueries) SMSProviderConfig(arg0 context.Context, arg1 ...query.SearchQuery) (*query.SMSConfig, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0} @@ -158,14 +159,14 @@ func (m *MockQueries) SMSProviderConfig(arg0 context.Context, arg1 ...query.Sear return ret0, ret1 } -// SMSProviderConfig indicates an expected call of SMSProviderConfig +// SMSProviderConfig indicates an expected call of SMSProviderConfig. func (mr *MockQueriesMockRecorder) SMSProviderConfig(arg0 interface{}, arg1 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SMSProviderConfig", reflect.TypeOf((*MockQueries)(nil).SMSProviderConfig), varargs...) } -// SMTPConfigByAggregateID mocks base method +// SMTPConfigByAggregateID mocks base method. func (m *MockQueries) SMTPConfigByAggregateID(arg0 context.Context, arg1 string) (*query.SMTPConfig, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SMTPConfigByAggregateID", arg0, arg1) @@ -174,13 +175,13 @@ func (m *MockQueries) SMTPConfigByAggregateID(arg0 context.Context, arg1 string) return ret0, ret1 } -// SMTPConfigByAggregateID indicates an expected call of SMTPConfigByAggregateID +// SMTPConfigByAggregateID indicates an expected call of SMTPConfigByAggregateID. func (mr *MockQueriesMockRecorder) SMTPConfigByAggregateID(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SMTPConfigByAggregateID", reflect.TypeOf((*MockQueries)(nil).SMTPConfigByAggregateID), arg0, arg1) } -// SearchInstanceDomains mocks base method +// SearchInstanceDomains mocks base method. func (m *MockQueries) SearchInstanceDomains(arg0 context.Context, arg1 *query.InstanceDomainSearchQueries) (*query.InstanceDomains, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SearchInstanceDomains", arg0, arg1) @@ -189,13 +190,13 @@ func (m *MockQueries) SearchInstanceDomains(arg0 context.Context, arg1 *query.In return ret0, ret1 } -// SearchInstanceDomains indicates an expected call of SearchInstanceDomains +// SearchInstanceDomains indicates an expected call of SearchInstanceDomains. func (mr *MockQueriesMockRecorder) SearchInstanceDomains(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchInstanceDomains", reflect.TypeOf((*MockQueries)(nil).SearchInstanceDomains), arg0, arg1) } -// SearchMilestones mocks base method +// SearchMilestones mocks base method. func (m *MockQueries) SearchMilestones(arg0 context.Context, arg1 []string, arg2 *query.MilestonesSearchQueries) (*query.Milestones, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SearchMilestones", arg0, arg1, arg2) @@ -204,13 +205,13 @@ func (m *MockQueries) SearchMilestones(arg0 context.Context, arg1 []string, arg2 return ret0, ret1 } -// SearchMilestones indicates an expected call of SearchMilestones +// SearchMilestones indicates an expected call of SearchMilestones. func (mr *MockQueriesMockRecorder) SearchMilestones(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchMilestones", reflect.TypeOf((*MockQueries)(nil).SearchMilestones), arg0, arg1, arg2) } -// SessionByID mocks base method +// SessionByID mocks base method. func (m *MockQueries) SessionByID(arg0 context.Context, arg1 bool, arg2, arg3 string) (*query.Session, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SessionByID", arg0, arg1, arg2, arg3) @@ -219,7 +220,7 @@ func (m *MockQueries) SessionByID(arg0 context.Context, arg1 bool, arg2, arg3 st return ret0, ret1 } -// SessionByID indicates an expected call of SessionByID +// SessionByID indicates an expected call of SessionByID. func (mr *MockQueriesMockRecorder) SessionByID(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SessionByID", reflect.TypeOf((*MockQueries)(nil).SessionByID), arg0, arg1, arg2, arg3) diff --git a/internal/notification/handlers/queries.go b/internal/notification/handlers/queries.go index 8e4154e12d..cd50a26b41 100644 --- a/internal/notification/handlers/queries.go +++ b/internal/notification/handlers/queries.go @@ -15,7 +15,7 @@ import ( type Queries interface { ActiveLabelPolicyByOrg(ctx context.Context, orgID string, withOwnerRemoved bool) (*query.LabelPolicy, error) MailTemplateByOrg(ctx context.Context, orgID string, withOwnerRemoved bool) (*query.MailTemplate, error) - GetNotifyUserByID(ctx context.Context, shouldTriggered bool, userID string, withOwnerRemoved bool, queries ...query.SearchQuery) (*query.NotifyUser, error) + GetNotifyUserByID(ctx context.Context, shouldTriggered bool, userID string, queries ...query.SearchQuery) (*query.NotifyUser, error) CustomTextListByTemplate(ctx context.Context, aggregateID, template string, withOwnerRemoved bool) (*query.CustomTexts, error) SearchInstanceDomains(ctx context.Context, queries *query.InstanceDomainSearchQueries) (*query.InstanceDomains, error) SessionByID(ctx context.Context, shouldTriggerBulk bool, id, sessionToken string) (*query.Session, error) diff --git a/internal/notification/handlers/user_notifier.go b/internal/notification/handlers/user_notifier.go index c54cae11d3..6f15b994f9 100644 --- a/internal/notification/handlers/user_notifier.go +++ b/internal/notification/handlers/user_notifier.go @@ -155,7 +155,7 @@ func (u *userNotifier) reduceInitCodeAdded(event eventstore.Event) (*handler.Sta return err } - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID, false) + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) if err != nil { return err } @@ -212,7 +212,7 @@ func (u *userNotifier) reduceEmailCodeAdded(event eventstore.Event) (*handler.St return err } - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID, false) + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) if err != nil { return err } @@ -268,7 +268,7 @@ func (u *userNotifier) reducePasswordCodeAdded(event eventstore.Event) (*handler return err } - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID, false) + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) if err != nil { return err } @@ -361,7 +361,7 @@ func (u *userNotifier) reduceOTPSMS( return nil, err } - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, userID, false) + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, userID) if err != nil { return nil, err } @@ -479,7 +479,7 @@ func (u *userNotifier) reduceOTPEmail( return nil, err } - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, userID, false) + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, userID) if err != nil { return nil, err } @@ -532,7 +532,7 @@ func (u *userNotifier) reduceDomainClaimed(event eventstore.Event) (*handler.Sta return err } - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID, false) + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) if err != nil { return err } @@ -586,7 +586,7 @@ func (u *userNotifier) reducePasswordlessCodeRequested(event eventstore.Event) ( return err } - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID, false) + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) if err != nil { return err } @@ -646,7 +646,7 @@ func (u *userNotifier) reducePasswordChanged(event eventstore.Event) (*handler.S return err } - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID, false) + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) if err != nil { return err } @@ -696,7 +696,7 @@ func (u *userNotifier) reducePhoneCodeAdded(event eventstore.Event) (*handler.St return err } - notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID, false) + notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID) if err != nil { return err } diff --git a/internal/notification/handlers/user_notifier_test.go b/internal/notification/handlers/user_notifier_test.go index 797aa7b874..2f2b0bfda7 100644 --- a/internal/notification/handlers/user_notifier_test.go +++ b/internal/notification/handlers/user_notifier_test.go @@ -1373,7 +1373,7 @@ func expectTemplateQueries(queries *mock.MockQueries, template string) { }, }, nil) queries.EXPECT().MailTemplateByOrg(gomock.Any(), gomock.Any(), gomock.Any()).Return(&query.MailTemplate{Template: []byte(template)}, nil) - queries.EXPECT().GetNotifyUserByID(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&query.NotifyUser{ + queries.EXPECT().GetNotifyUserByID(gomock.Any(), gomock.Any(), gomock.Any()).Return(&query.NotifyUser{ ID: userID, ResourceOwner: orgID, LastEmail: lastEmail, diff --git a/internal/query/app.go b/internal/query/app.go index 7f39d4d82e..0877d68c0f 100644 --- a/internal/query/app.go +++ b/internal/query/app.go @@ -126,10 +126,6 @@ var ( name: projection.AppColumnSequence, table: appsTable, } - AppColumnOwnerRemoved = Column{ - name: projection.AppColumnOwnerRemoved, - table: appsTable, - } ) var ( @@ -249,7 +245,7 @@ var ( } ) -func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bool, projectID, appID string, withOwnerRemoved bool) (app *App, err error) { +func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bool, projectID, appID string) (app *App, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -266,9 +262,6 @@ func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bo AppColumnProjectID.identifier(): projectID, AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[AppColumnOwnerRemoved.identifier()] = false - } query, args, err := stmt.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-AFDgg", "Errors.Query.SQLStatement") @@ -281,7 +274,7 @@ func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bo return app, err } -func (q *Queries) AppByID(ctx context.Context, appID string, withOwnerRemoved bool) (app *App, err error) { +func (q *Queries) AppByID(ctx context.Context, appID string) (app *App, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -290,9 +283,6 @@ func (q *Queries) AppByID(ctx context.Context, appID string, withOwnerRemoved bo AppColumnID.identifier(): appID, AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[AppColumnOwnerRemoved.identifier()] = false - } query, args, err := stmt.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-immt9", "Errors.Query.SQLStatement") @@ -305,7 +295,7 @@ func (q *Queries) AppByID(ctx context.Context, appID string, withOwnerRemoved bo return app, err } -func (q *Queries) AppBySAMLEntityID(ctx context.Context, entityID string, withOwnerRemoved bool) (app *App, err error) { +func (q *Queries) AppBySAMLEntityID(ctx context.Context, entityID string) (app *App, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -314,9 +304,6 @@ func (q *Queries) AppBySAMLEntityID(ctx context.Context, entityID string, withOw AppSAMLConfigColumnEntityID.identifier(): entityID, AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[AppColumnOwnerRemoved.identifier()] = false - } query, args, err := stmt.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-JgUop", "Errors.Query.SQLStatement") @@ -354,7 +341,7 @@ func (q *Queries) ProjectByClientID(ctx context.Context, appID string) (project return project, err } -func (q *Queries) ProjectIDFromOIDCClientID(ctx context.Context, appID string, withOwnerRemoved bool) (id string, err error) { +func (q *Queries) ProjectIDFromOIDCClientID(ctx context.Context, appID string) (id string, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -363,9 +350,6 @@ func (q *Queries) ProjectIDFromOIDCClientID(ctx context.Context, appID string, w AppOIDCConfigColumnClientID.identifier(): appID, AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[AppColumnOwnerRemoved.identifier()] = false - } query, args, err := stmt.Where(eq).ToSql() if err != nil { return "", errors.ThrowInternal(err, "QUERY-7d92U", "Errors.Query.SQLStatement") @@ -378,15 +362,12 @@ func (q *Queries) ProjectIDFromOIDCClientID(ctx context.Context, appID string, w return id, err } -func (q *Queries) ProjectIDFromClientID(ctx context.Context, appID string, withOwnerRemoved bool) (id string, err error) { +func (q *Queries) ProjectIDFromClientID(ctx context.Context, appID string) (id string, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() stmt, scan := prepareProjectIDByAppQuery(ctx, q.client) eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()} - if !withOwnerRemoved { - eq[AppColumnOwnerRemoved.identifier()] = false - } where := sq.And{ eq, sq.Or{ @@ -407,7 +388,7 @@ func (q *Queries) ProjectIDFromClientID(ctx context.Context, appID string, withO return id, err } -func (q *Queries) ProjectByOIDCClientID(ctx context.Context, id string, withOwnerRemoved bool) (project *Project, err error) { +func (q *Queries) ProjectByOIDCClientID(ctx context.Context, id string) (project *Project, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -416,9 +397,6 @@ func (q *Queries) ProjectByOIDCClientID(ctx context.Context, id string, withOwne AppOIDCConfigColumnClientID.identifier(): id, AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[AppColumnOwnerRemoved.identifier()] = false - } query, args, err := stmt.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-XhJi4", "Errors.Query.SQLStatement") @@ -431,7 +409,7 @@ func (q *Queries) ProjectByOIDCClientID(ctx context.Context, id string, withOwne return project, err } -func (q *Queries) AppByOIDCClientID(ctx context.Context, clientID string, withOwnerRemoved bool) (app *App, err error) { +func (q *Queries) AppByOIDCClientID(ctx context.Context, clientID string) (app *App, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -440,9 +418,6 @@ func (q *Queries) AppByOIDCClientID(ctx context.Context, clientID string, withOw AppOIDCConfigColumnClientID.identifier(): clientID, AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[AppColumnOwnerRemoved.identifier()] = false - } query, args, err := stmt.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-JgVop", "Errors.Query.SQLStatement") @@ -455,15 +430,12 @@ func (q *Queries) AppByOIDCClientID(ctx context.Context, clientID string, withOw return app, err } -func (q *Queries) AppByClientID(ctx context.Context, clientID string, withOwnerRemoved bool) (app *App, err error) { +func (q *Queries) AppByClientID(ctx context.Context, clientID string) (app *App, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() stmt, scan := prepareAppQuery(ctx, q.client) eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()} - if !withOwnerRemoved { - eq[AppColumnOwnerRemoved.identifier()] = false - } query, args, err := stmt.Where(sq.And{ eq, sq.Or{ @@ -488,9 +460,6 @@ func (q *Queries) SearchApps(ctx context.Context, queries *AppSearchQueries, wit query, scan := prepareAppsQuery(ctx, q.client) eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()} - if !withOwnerRemoved { - eq[AppColumnOwnerRemoved.identifier()] = false - } stmt, args, err := queries.toQuery(query).Where(eq).ToSql() if err != nil { return nil, errors.ThrowInvalidArgument(err, "QUERY-fajp8", "Errors.Query.InvalidRequest") @@ -507,15 +476,12 @@ func (q *Queries) SearchApps(ctx context.Context, queries *AppSearchQueries, wit return apps, err } -func (q *Queries) SearchClientIDs(ctx context.Context, queries *AppSearchQueries, withOwnerRemoved bool) (ids []string, err error) { +func (q *Queries) SearchClientIDs(ctx context.Context, queries *AppSearchQueries) (ids []string, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() query, scan := prepareClientIDsQuery(ctx, q.client) eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()} - if !withOwnerRemoved { - eq[AppColumnOwnerRemoved.identifier()] = false - } stmt, args, err := queries.toQuery(query).Where(eq).ToSql() if err != nil { return nil, errors.ThrowInvalidArgument(err, "QUERY-fajp8", "Errors.Query.InvalidRequest") diff --git a/internal/query/app_test.go b/internal/query/app_test.go index 424fd89983..599a9724aa 100644 --- a/internal/query/app_test.go +++ b/internal/query/app_test.go @@ -15,98 +15,98 @@ import ( ) var ( - expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps5.id,` + - ` projections.apps5.name,` + - ` projections.apps5.project_id,` + - ` projections.apps5.creation_date,` + - ` projections.apps5.change_date,` + - ` projections.apps5.resource_owner,` + - ` projections.apps5.state,` + - ` projections.apps5.sequence,` + + expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps6.id,` + + ` projections.apps6.name,` + + ` projections.apps6.project_id,` + + ` projections.apps6.creation_date,` + + ` projections.apps6.change_date,` + + ` projections.apps6.resource_owner,` + + ` projections.apps6.state,` + + ` projections.apps6.sequence,` + // api config - ` projections.apps5_api_configs.app_id,` + - ` projections.apps5_api_configs.client_id,` + - ` projections.apps5_api_configs.auth_method,` + + ` projections.apps6_api_configs.app_id,` + + ` projections.apps6_api_configs.client_id,` + + ` projections.apps6_api_configs.auth_method,` + // oidc config - ` projections.apps5_oidc_configs.app_id,` + - ` projections.apps5_oidc_configs.version,` + - ` projections.apps5_oidc_configs.client_id,` + - ` projections.apps5_oidc_configs.redirect_uris,` + - ` projections.apps5_oidc_configs.response_types,` + - ` projections.apps5_oidc_configs.grant_types,` + - ` projections.apps5_oidc_configs.application_type,` + - ` projections.apps5_oidc_configs.auth_method_type,` + - ` projections.apps5_oidc_configs.post_logout_redirect_uris,` + - ` projections.apps5_oidc_configs.is_dev_mode,` + - ` projections.apps5_oidc_configs.access_token_type,` + - ` projections.apps5_oidc_configs.access_token_role_assertion,` + - ` projections.apps5_oidc_configs.id_token_role_assertion,` + - ` projections.apps5_oidc_configs.id_token_userinfo_assertion,` + - ` projections.apps5_oidc_configs.clock_skew,` + - ` projections.apps5_oidc_configs.additional_origins,` + - ` projections.apps5_oidc_configs.skip_native_app_success_page,` + + ` projections.apps6_oidc_configs.app_id,` + + ` projections.apps6_oidc_configs.version,` + + ` projections.apps6_oidc_configs.client_id,` + + ` projections.apps6_oidc_configs.redirect_uris,` + + ` projections.apps6_oidc_configs.response_types,` + + ` projections.apps6_oidc_configs.grant_types,` + + ` projections.apps6_oidc_configs.application_type,` + + ` projections.apps6_oidc_configs.auth_method_type,` + + ` projections.apps6_oidc_configs.post_logout_redirect_uris,` + + ` projections.apps6_oidc_configs.is_dev_mode,` + + ` projections.apps6_oidc_configs.access_token_type,` + + ` projections.apps6_oidc_configs.access_token_role_assertion,` + + ` projections.apps6_oidc_configs.id_token_role_assertion,` + + ` projections.apps6_oidc_configs.id_token_userinfo_assertion,` + + ` projections.apps6_oidc_configs.clock_skew,` + + ` projections.apps6_oidc_configs.additional_origins,` + + ` projections.apps6_oidc_configs.skip_native_app_success_page,` + //saml config - ` projections.apps5_saml_configs.app_id,` + - ` projections.apps5_saml_configs.entity_id,` + - ` projections.apps5_saml_configs.metadata,` + - ` projections.apps5_saml_configs.metadata_url` + - ` FROM projections.apps5` + - ` LEFT JOIN projections.apps5_api_configs ON projections.apps5.id = projections.apps5_api_configs.app_id AND projections.apps5.instance_id = projections.apps5_api_configs.instance_id` + - ` LEFT JOIN projections.apps5_oidc_configs ON projections.apps5.id = projections.apps5_oidc_configs.app_id AND projections.apps5.instance_id = projections.apps5_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps5_saml_configs ON projections.apps5.id = projections.apps5_saml_configs.app_id AND projections.apps5.instance_id = projections.apps5_saml_configs.instance_id` + + ` projections.apps6_saml_configs.app_id,` + + ` projections.apps6_saml_configs.entity_id,` + + ` projections.apps6_saml_configs.metadata,` + + ` projections.apps6_saml_configs.metadata_url` + + ` FROM projections.apps6` + + ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + + ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) - expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps5.id,` + - ` projections.apps5.name,` + - ` projections.apps5.project_id,` + - ` projections.apps5.creation_date,` + - ` projections.apps5.change_date,` + - ` projections.apps5.resource_owner,` + - ` projections.apps5.state,` + - ` projections.apps5.sequence,` + + expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps6.id,` + + ` projections.apps6.name,` + + ` projections.apps6.project_id,` + + ` projections.apps6.creation_date,` + + ` projections.apps6.change_date,` + + ` projections.apps6.resource_owner,` + + ` projections.apps6.state,` + + ` projections.apps6.sequence,` + // api config - ` projections.apps5_api_configs.app_id,` + - ` projections.apps5_api_configs.client_id,` + - ` projections.apps5_api_configs.auth_method,` + + ` projections.apps6_api_configs.app_id,` + + ` projections.apps6_api_configs.client_id,` + + ` projections.apps6_api_configs.auth_method,` + // oidc config - ` projections.apps5_oidc_configs.app_id,` + - ` projections.apps5_oidc_configs.version,` + - ` projections.apps5_oidc_configs.client_id,` + - ` projections.apps5_oidc_configs.redirect_uris,` + - ` projections.apps5_oidc_configs.response_types,` + - ` projections.apps5_oidc_configs.grant_types,` + - ` projections.apps5_oidc_configs.application_type,` + - ` projections.apps5_oidc_configs.auth_method_type,` + - ` projections.apps5_oidc_configs.post_logout_redirect_uris,` + - ` projections.apps5_oidc_configs.is_dev_mode,` + - ` projections.apps5_oidc_configs.access_token_type,` + - ` projections.apps5_oidc_configs.access_token_role_assertion,` + - ` projections.apps5_oidc_configs.id_token_role_assertion,` + - ` projections.apps5_oidc_configs.id_token_userinfo_assertion,` + - ` projections.apps5_oidc_configs.clock_skew,` + - ` projections.apps5_oidc_configs.additional_origins,` + - ` projections.apps5_oidc_configs.skip_native_app_success_page,` + + ` projections.apps6_oidc_configs.app_id,` + + ` projections.apps6_oidc_configs.version,` + + ` projections.apps6_oidc_configs.client_id,` + + ` projections.apps6_oidc_configs.redirect_uris,` + + ` projections.apps6_oidc_configs.response_types,` + + ` projections.apps6_oidc_configs.grant_types,` + + ` projections.apps6_oidc_configs.application_type,` + + ` projections.apps6_oidc_configs.auth_method_type,` + + ` projections.apps6_oidc_configs.post_logout_redirect_uris,` + + ` projections.apps6_oidc_configs.is_dev_mode,` + + ` projections.apps6_oidc_configs.access_token_type,` + + ` projections.apps6_oidc_configs.access_token_role_assertion,` + + ` projections.apps6_oidc_configs.id_token_role_assertion,` + + ` projections.apps6_oidc_configs.id_token_userinfo_assertion,` + + ` projections.apps6_oidc_configs.clock_skew,` + + ` projections.apps6_oidc_configs.additional_origins,` + + ` projections.apps6_oidc_configs.skip_native_app_success_page,` + //saml config - ` projections.apps5_saml_configs.app_id,` + - ` projections.apps5_saml_configs.entity_id,` + - ` projections.apps5_saml_configs.metadata,` + - ` projections.apps5_saml_configs.metadata_url,` + + ` projections.apps6_saml_configs.app_id,` + + ` projections.apps6_saml_configs.entity_id,` + + ` projections.apps6_saml_configs.metadata,` + + ` projections.apps6_saml_configs.metadata_url,` + ` COUNT(*) OVER ()` + - ` FROM projections.apps5` + - ` LEFT JOIN projections.apps5_api_configs ON projections.apps5.id = projections.apps5_api_configs.app_id AND projections.apps5.instance_id = projections.apps5_api_configs.instance_id` + - ` LEFT JOIN projections.apps5_oidc_configs ON projections.apps5.id = projections.apps5_oidc_configs.app_id AND projections.apps5.instance_id = projections.apps5_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps5_saml_configs ON projections.apps5.id = projections.apps5_saml_configs.app_id AND projections.apps5.instance_id = projections.apps5_saml_configs.instance_id` + + ` FROM projections.apps6` + + ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + + ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) - expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps5_api_configs.client_id,` + - ` projections.apps5_oidc_configs.client_id` + - ` FROM projections.apps5` + - ` LEFT JOIN projections.apps5_api_configs ON projections.apps5.id = projections.apps5_api_configs.app_id AND projections.apps5.instance_id = projections.apps5_api_configs.instance_id` + - ` LEFT JOIN projections.apps5_oidc_configs ON projections.apps5.id = projections.apps5_oidc_configs.app_id AND projections.apps5.instance_id = projections.apps5_oidc_configs.instance_id` + + expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps6_api_configs.client_id,` + + ` projections.apps6_oidc_configs.client_id` + + ` FROM projections.apps6` + + ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + + ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) - expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps5.project_id` + - ` FROM projections.apps5` + - ` LEFT JOIN projections.apps5_api_configs ON projections.apps5.id = projections.apps5_api_configs.app_id AND projections.apps5.instance_id = projections.apps5_api_configs.instance_id` + - ` LEFT JOIN projections.apps5_oidc_configs ON projections.apps5.id = projections.apps5_oidc_configs.app_id AND projections.apps5.instance_id = projections.apps5_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps5_saml_configs ON projections.apps5.id = projections.apps5_saml_configs.app_id AND projections.apps5.instance_id = projections.apps5_saml_configs.instance_id` + + expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps6.project_id` + + ` FROM projections.apps6` + + ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + + ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) expectedProjectByAppQuery = regexp.QuoteMeta(`SELECT projections.projects4.id,` + ` projections.projects4.creation_date,` + @@ -120,10 +120,10 @@ var ( ` projections.projects4.has_project_check,` + ` projections.projects4.private_labeling_setting` + ` FROM projections.projects4` + - ` JOIN projections.apps5 ON projections.projects4.id = projections.apps5.project_id AND projections.projects4.instance_id = projections.apps5.instance_id` + - ` LEFT JOIN projections.apps5_api_configs ON projections.apps5.id = projections.apps5_api_configs.app_id AND projections.apps5.instance_id = projections.apps5_api_configs.instance_id` + - ` LEFT JOIN projections.apps5_oidc_configs ON projections.apps5.id = projections.apps5_oidc_configs.app_id AND projections.apps5.instance_id = projections.apps5_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps5_saml_configs ON projections.apps5.id = projections.apps5_saml_configs.app_id AND projections.apps5.instance_id = projections.apps5_saml_configs.instance_id` + + ` JOIN projections.apps6 ON projections.projects4.id = projections.apps6.project_id AND projections.projects4.instance_id = projections.apps6.instance_id` + + ` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + + ` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) appCols = database.TextArray[string]{ diff --git a/internal/query/authn_key.go b/internal/query/authn_key.go index a9ef61e0b9..21ff3aee1d 100644 --- a/internal/query/authn_key.go +++ b/internal/query/authn_key.go @@ -76,10 +76,6 @@ var ( name: projection.AuthNKeyEnabledCol, table: authNKeyTable, } - AuthNKeyOwnerRemovedCol = Column{ - name: projection.AuthNKeyOwnerRemovedCol, - table: authNKeyTable, - } ) type AuthNKeys struct { @@ -139,9 +135,6 @@ func (q *Queries) SearchAuthNKeys(ctx context.Context, queries *AuthNKeySearchQu AuthNKeyColumnEnabled.identifier(): true, AuthNKeyColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[AuthNKeyOwnerRemovedCol.identifier()] = false - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInvalidArgument(err, "QUERY-SAf3f", "Errors.Query.InvalidRequest") @@ -159,7 +152,7 @@ func (q *Queries) SearchAuthNKeys(ctx context.Context, queries *AuthNKeySearchQu return authNKeys, err } -func (q *Queries) SearchAuthNKeysData(ctx context.Context, queries *AuthNKeySearchQueries, withOwnerRemoved bool) (authNKeys *AuthNKeysData, err error) { +func (q *Queries) SearchAuthNKeysData(ctx context.Context, queries *AuthNKeySearchQueries) (authNKeys *AuthNKeysData, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -169,9 +162,6 @@ func (q *Queries) SearchAuthNKeysData(ctx context.Context, queries *AuthNKeySear AuthNKeyColumnEnabled.identifier(): true, AuthNKeyColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[AuthNKeyOwnerRemovedCol.identifier()] = false - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInvalidArgument(err, "QUERY-SAg3f", "Errors.Query.InvalidRequest") @@ -188,7 +178,7 @@ func (q *Queries) SearchAuthNKeysData(ctx context.Context, queries *AuthNKeySear return authNKeys, err } -func (q *Queries) GetAuthNKeyByID(ctx context.Context, shouldTriggerBulk bool, id string, withOwnerRemoved bool, queries ...SearchQuery) (key *AuthNKey, err error) { +func (q *Queries) GetAuthNKeyByID(ctx context.Context, shouldTriggerBulk bool, id string, queries ...SearchQuery) (key *AuthNKey, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -208,9 +198,6 @@ func (q *Queries) GetAuthNKeyByID(ctx context.Context, shouldTriggerBulk bool, i AuthNKeyColumnEnabled.identifier(): true, AuthNKeyColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[AuthNKeyOwnerRemovedCol.identifier()] = false - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-AGhg4", "Errors.Query.SQLStatement") @@ -239,20 +226,6 @@ func (q *Queries) GetAuthNKeyPublicKeyByIDAndIdentifier(ctx context.Context, id AuthNKeyColumnExpiration.identifier(): time.Now(), }, } - if !withOwnerRemoved { - eq = sq.And{ - sq.Eq{ - AuthNKeyColumnID.identifier(): id, - AuthNKeyColumnIdentifier.identifier(): identifier, - AuthNKeyColumnEnabled.identifier(): true, - AuthNKeyColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), - AuthNKeyOwnerRemovedCol.identifier(): false, - }, - sq.Gt{ - AuthNKeyColumnExpiration.identifier(): time.Now(), - }, - } - } query, args, err := stmt.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-DAb32", "Errors.Query.SQLStatement") diff --git a/internal/query/embed/introspection_client_by_id.sql b/internal/query/embed/introspection_client_by_id.sql new file mode 100644 index 0000000000..b8c85806b0 --- /dev/null +++ b/internal/query/embed/introspection_client_by_id.sql @@ -0,0 +1,23 @@ +with config as ( + select app_id, client_id, client_secret + from projections.apps6_api_configs + where instance_id = $1 + and client_id = $2 + union + select app_id, client_id, client_secret + from projections.apps6_oidc_configs + where instance_id = $1 + and client_id = $2 +), +keys as ( + select identifier as client_id, json_object_agg(id, encode(public_key, 'base64')) as public_keys + from projections.authn_keys2 + where $3 = true -- when argument is false, don't waste time on trying to query for keys. + and instance_id = $1 + and identifier = $2 + and expiration > current_timestamp + group by identifier +) +select config.client_id, config.client_secret, apps.project_id, keys.public_keys from config +join projections.apps6 apps on apps.id = config.app_id +left join keys on keys.client_id = config.client_id; diff --git a/internal/query/embed/userinfo_by_id.sql b/internal/query/embed/userinfo_by_id.sql new file mode 100644 index 0000000000..128a9591be --- /dev/null +++ b/internal/query/embed/userinfo_by_id.sql @@ -0,0 +1,92 @@ +-- deallocate q; +-- prepare q (text, text, text[]) as + +with usr as ( + select u.id, u.creation_date, u.change_date, u.sequence, u.state, u.resource_owner, u.username, n.login_name as preferred_login_name + from projections.users9 u + left join projections.login_names3 n on u.id = n.user_id and u.instance_id = n.instance_id + where u.id = $1 + and u.instance_id = $2 + and n.is_primary = true +), +human as ( + select $1 as user_id, row_to_json(r) as human from ( + select first_name, last_name, nick_name, display_name, avatar_key, email, is_email_verified, phone, is_phone_verified + from projections.users9_humans + where user_id = $1 + and instance_id = $2 + ) r +), +machine as ( + select $1 as user_id, row_to_json(r) as machine from ( + select name, description + from projections.users9_machines + where user_id = $1 + and instance_id = $2 + ) r +), +-- find the user's metadata +metadata as ( + select json_agg(row_to_json(r)) as metadata from ( + select creation_date, change_date, sequence, resource_owner, key, encode(value, 'base64') as value + from projections.user_metadata5 + where user_id = $1 + and instance_id = $2 + ) r +), +-- get all user grants, needed for the orgs query +user_grants as ( + select id, grant_id, state, creation_date, change_date, sequence, user_id, roles, resource_owner, project_id + from projections.user_grants3 + where user_id = $1 + and instance_id = $2 + and project_id = any($3) +), +-- filter all orgs we are interested in. +orgs as ( + select id, name, primary_domain + from projections.orgs1 + where id in ( + select resource_owner from user_grants + union + select resource_owner from usr + ) + and instance_id = $2 +), +-- find the user's org +user_org as ( + select row_to_json(r) as organization from ( + select name, primary_domain + from orgs o + join usr u on o.id = u.resource_owner + ) r +), +-- join user grants to orgs, projects and user +grants as ( + select json_agg(row_to_json(r)) as grants from ( + select g.*, + o.name as org_name, o.primary_domain as org_primary_domain, + p.name as project_name, u.resource_owner as user_resource_owner + from user_grants g + left join orgs o on o.id = g.resource_owner + left join projections.projects4 p on p.id = g.project_id + left join usr u on u.id = g.user_id + where p.instance_id = $2 + ) r +) +-- build the final result JSON +select json_build_object( + 'user', ( + select row_to_json(r) as usr from ( + select u.*, h.human, m.machine + from usr u + left join human h on u.id = h.user_id + left join machine m on u.id = m.user_id + ) r + ), + 'org', (select organization from user_org), + 'metadata', (select metadata from metadata), + 'user_grants', (select grants from grants) +); + +-- execute q('231965491734773762','230690539048009730', '{"236645808328409090","240762134579904514"}') \ No newline at end of file diff --git a/internal/query/event.go b/internal/query/event.go index c44187d9ac..074b5c83cf 100644 --- a/internal/query/event.go +++ b/internal/query/event.go @@ -120,7 +120,7 @@ func (q *Queries) convertEvent(ctx context.Context, event eventstore.Event, user } func (q *Queries) editorUserByID(ctx context.Context, userID string) *EventEditor { - user, err := q.GetUserByID(ctx, false, userID, false) + user, err := q.GetUserByID(ctx, false, userID) if err != nil { return &EventEditor{ID: userID} } diff --git a/internal/query/iam_member_test.go b/internal/query/iam_member_test.go index e76c3c3790..476e4e2358 100644 --- a/internal/query/iam_member_test.go +++ b/internal/query/iam_member_test.go @@ -21,21 +21,21 @@ var ( ", members.user_id" + ", members.roles" + ", projections.login_names3.login_name" + - ", projections.users8_humans.email" + - ", projections.users8_humans.first_name" + - ", projections.users8_humans.last_name" + - ", projections.users8_humans.display_name" + - ", projections.users8_machines.name" + - ", projections.users8_humans.avatar_key" + - ", projections.users8.type" + + ", projections.users9_humans.email" + + ", projections.users9_humans.first_name" + + ", projections.users9_humans.last_name" + + ", projections.users9_humans.display_name" + + ", projections.users9_machines.name" + + ", projections.users9_humans.avatar_key" + + ", projections.users9.type" + ", COUNT(*) OVER () " + "FROM projections.instance_members4 AS members " + - "LEFT JOIN projections.users8_humans " + - "ON members.user_id = projections.users8_humans.user_id AND members.instance_id = projections.users8_humans.instance_id " + - "LEFT JOIN projections.users8_machines " + - "ON members.user_id = projections.users8_machines.user_id AND members.instance_id = projections.users8_machines.instance_id " + - "LEFT JOIN projections.users8 " + - "ON members.user_id = projections.users8.id AND members.instance_id = projections.users8.instance_id " + + "LEFT JOIN projections.users9_humans " + + "ON members.user_id = projections.users9_humans.user_id AND members.instance_id = projections.users9_humans.instance_id " + + "LEFT JOIN projections.users9_machines " + + "ON members.user_id = projections.users9_machines.user_id AND members.instance_id = projections.users9_machines.instance_id " + + "LEFT JOIN projections.users9 " + + "ON members.user_id = projections.users9.id AND members.instance_id = projections.users9.instance_id " + "LEFT JOIN projections.login_names3 " + "ON members.user_id = projections.login_names3.user_id AND members.instance_id = projections.login_names3.instance_id " + "AS OF SYSTEM TIME '-1 ms' " + diff --git a/internal/query/introspection.go b/internal/query/introspection.go new file mode 100644 index 0000000000..0e190da25d --- /dev/null +++ b/internal/query/introspection.go @@ -0,0 +1,63 @@ +package query + +import ( + "context" + "database/sql" + _ "embed" + "sync" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/database" + "github.com/zitadel/zitadel/internal/eventstore/handler/v2" + "github.com/zitadel/zitadel/internal/query/projection" + "github.com/zitadel/zitadel/internal/telemetry/tracing" +) + +// introspectionTriggerHandlers slice can only be created after zitadel +// is fully initialized, otherwise the handlers are nil. +// OnceValue takes care of creating the slice on the first request +// and than will always return the same slice on subsequent requests. +var introspectionTriggerHandlers = sync.OnceValue(func() []*handler.Handler { + return append(oidcUserInfoTriggerHandlers(), + projection.AppProjection, + projection.OIDCSettingsProjection, + projection.AuthNKeyProjection, + ) +}) + +func TriggerIntrospectionProjections(ctx context.Context) { + triggerBatch(ctx, introspectionTriggerHandlers()...) +} + +type IntrospectionClient struct { + ClientID string + ClientSecret *crypto.CryptoValue + ProjectID string + PublicKeys database.Map[[]byte] +} + +//go:embed embed/introspection_client_by_id.sql +var introspectionClientByIDQuery string + +func (q *Queries) GetIntrospectionClientByID(ctx context.Context, clientID string, getKeys bool) (_ *IntrospectionClient, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + var ( + instanceID = authz.GetInstance(ctx).InstanceID() + client = new(IntrospectionClient) + ) + + err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { + return row.Scan(&client.ClientID, &client.ClientSecret, &client.ProjectID, &client.PublicKeys) + }, + introspectionClientByIDQuery, + instanceID, clientID, getKeys, + ) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/query/introspection_test.go b/internal/query/introspection_test.go new file mode 100644 index 0000000000..d666adc51f --- /dev/null +++ b/internal/query/introspection_test.go @@ -0,0 +1,108 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + _ "embed" + "encoding/json" + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/crypto" + "github.com/zitadel/zitadel/internal/database" +) + +func TestQueries_GetIntrospectionClientByID(t *testing.T) { + secret := &crypto.CryptoValue{ + CryptoType: crypto.TypeHash, + Algorithm: "alg", + KeyID: "keyID", + Crypted: []byte("secret"), + } + encSecret, err := json.Marshal(secret) + require.NoError(t, err) + + pubkeys := database.Map[[]byte]{ + "key1": {1, 2, 3}, + "key2": {4, 5, 6}, + } + encPubkeys, err := pubkeys.Value() + require.NoError(t, err) + + expQuery := regexp.QuoteMeta(introspectionClientByIDQuery) + type args struct { + clientID string + getKeys bool + } + tests := []struct { + name string + args args + mock sqlExpectation + want *IntrospectionClient + wantErr error + }{ + { + name: "query error", + args: args{ + clientID: "clientID", + getKeys: false, + }, + mock: mockQueryErr(expQuery, sql.ErrConnDone, "instanceID", "clientID", false), + wantErr: sql.ErrConnDone, + }, + { + name: "success, secret", + args: args{ + clientID: "clientID", + getKeys: false, + }, + mock: mockQuery(expQuery, + []string{"client_id", "client_secret", "project_id", "public_keys"}, + []driver.Value{"clientID", encSecret, "projectID", nil}, + "instanceID", "clientID", false), + want: &IntrospectionClient{ + ClientID: "clientID", + ClientSecret: secret, + ProjectID: "projectID", + PublicKeys: nil, + }, + }, + { + name: "success, keys", + args: args{ + clientID: "clientID", + getKeys: true, + }, + mock: mockQuery(expQuery, + []string{"client_id", "client_secret", "project_id", "public_keys"}, + []driver.Value{"clientID", nil, "projectID", encPubkeys}, + "instanceID", "clientID", true), + want: &IntrospectionClient{ + ClientID: "clientID", + ClientSecret: nil, + ProjectID: "projectID", + PublicKeys: pubkeys, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + execMock(t, tt.mock, func(db *sql.DB) { + q := &Queries{ + client: &database.DB{ + DB: db, + Database: &prepareDB{}, + }, + } + ctx := authz.NewMockContext("instanceID", "orgID", "userID") + got, err := q.GetIntrospectionClientByID(ctx, tt.args.clientID, tt.args.getKeys) + require.ErrorIs(t, err, tt.wantErr) + assert.Equal(t, tt.want, got) + }) + }) + } +} diff --git a/internal/query/key.go b/internal/query/key.go index ac00d0624c..7447b11343 100644 --- a/internal/query/key.go +++ b/internal/query/key.go @@ -13,7 +13,9 @@ import ( "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/query/projection" + "github.com/zitadel/zitadel/internal/repository/keypair" "github.com/zitadel/zitadel/internal/telemetry/tracing" ) @@ -349,3 +351,88 @@ func preparePrivateKeysQuery(ctx context.Context, db prepareDatabase) (sq.Select }, nil } } + +type PublicKeyReadModel struct { + eventstore.ReadModel + + Algorithm string + Key *crypto.CryptoValue + Expiry time.Time + Usage domain.KeyUsage +} + +func NewPublicKeyReadModel(keyID, resourceOwner string) *PublicKeyReadModel { + return &PublicKeyReadModel{ + ReadModel: eventstore.ReadModel{ + AggregateID: keyID, + ResourceOwner: resourceOwner, + }, + } +} + +func (wm *PublicKeyReadModel) AppendEvents(events ...eventstore.Event) { + wm.ReadModel.AppendEvents(events...) +} + +func (wm *PublicKeyReadModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *keypair.AddedEvent: + wm.Algorithm = e.Algorithm + wm.Key = e.PublicKey.Key + wm.Expiry = e.PublicKey.Expiry + wm.Usage = e.Usage + default: + } + } + return wm.ReadModel.Reduce() +} + +func (wm *PublicKeyReadModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + AwaitOpenTransactions(). + ResourceOwner(wm.ResourceOwner). + AddQuery(). + AggregateTypes(keypair.AggregateType). + AggregateIDs(wm.AggregateID). + EventTypes(keypair.AddedEventType). + Builder() +} + +func (q *Queries) GetActivePublicKeyByID(ctx context.Context, keyID string, current time.Time) (_ PublicKey, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + model := NewPublicKeyReadModel(keyID, authz.GetInstance(ctx).InstanceID()) + if err := q.eventstore.FilterToQueryReducer(ctx, model); err != nil { + return nil, err + } + if model.Algorithm == "" || model.Key == nil { + return nil, errors.ThrowNotFound(err, "QUERY-Ahf7x", "Errors.Key.NotFound") + } + if model.Expiry.Before(current) { + return nil, errors.ThrowInvalidArgument(err, "QUERY-ciF4k", "Errors.Key.ExpireBeforeNow") + } + keyValue, err := crypto.Decrypt(model.Key, q.keyEncryptionAlgorithm) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-Ie4oh", "Errors.Internal") + } + publicKey, err := crypto.BytesToPublicKey(keyValue) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-Kai2Z", "Errors.Internal") + } + + return &rsaPublicKey{ + key: key{ + id: model.AggregateID, + creationDate: model.CreationDate, + changeDate: model.ChangeDate, + sequence: model.ProcessedSequence, + resourceOwner: model.ResourceOwner, + algorithm: model.Algorithm, + use: model.Usage, + }, + expiry: model.Expiry, + publicKey: publicKey, + }, nil +} diff --git a/internal/query/key_test.go b/internal/query/key_test.go index a600b23eee..7ca9af35d5 100644 --- a/internal/query/key_test.go +++ b/internal/query/key_test.go @@ -1,18 +1,28 @@ package query import ( + "context" "crypto/rsa" "database/sql" "database/sql/driver" "errors" "fmt" + "io" "math/big" "regexp" "testing" + "time" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" errs "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/eventstore" + key_repo "github.com/zitadel/zitadel/internal/repository/keypair" ) var ( @@ -247,3 +257,232 @@ func fromBase16(base16 string) *big.Int { } return i } + +const pubKey = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs38btwb3c7r0tMaQpGvB +mY+mPwMU/LpfuPoC0k2t4RsKp0fv40SMl50CRrHgk395wch8PMPYbl3+8TtYAJuy +rFALIj3Ff1UcKIk0hOH5DDsfh7/q2wFuncTmS6bifYo8CfSq2vDGnM7nZnEvxY/M +fSydZdcmIqlkUpfQmtzExw9+tSe5Dxq6gn5JtlGgLgZGt69r5iMMrTEGhhVAXzNu +MZbmlCoBru+rC8ITlTX/0V1ZcsSbL8tYWhthyu9x6yjo1bH85wiVI4gs0MhU8f2a ++kjL/KGZbR14Ua2eo6tonBZLC5DHWM2TkYXgRCDPufjcgmzN0Lm91E4P8KvBcvly +6QIDAQAB +-----END PUBLIC KEY----- +` + +func TestQueries_GetActivePublicKeyByID(t *testing.T) { + now := time.Now() + future := now.Add(time.Hour) + + tests := []struct { + name string + eventstore func(*testing.T) *eventstore.Eventstore + encryption func(*testing.T) *crypto.MockEncryptionAlgorithm + want *rsaPublicKey + wantErr error + }{ + { + name: "filter error", + eventstore: expectEventstore( + expectFilterError(io.ErrClosedPipe), + ), + wantErr: io.ErrClosedPipe, + }, + { + name: "not found error", + eventstore: expectEventstore( + expectFilter(), + ), + wantErr: errs.ThrowNotFound(nil, "QUERY-Ahf7x", "Errors.Key.NotFound"), + }, + { + name: "expired error", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher(key_repo.NewAddedEvent(context.Background(), + &eventstore.Aggregate{ + ID: "keyID", + Type: key_repo.AggregateType, + ResourceOwner: "instanceID", + InstanceID: "instanceID", + Version: key_repo.AggregateVersion, + }, + domain.KeyUsageSigning, "alg", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "keyID", + Crypted: []byte("private"), + }, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "keyID", + Crypted: []byte("public"), + }, + now.Add(-time.Hour), + now.Add(-time.Hour), + )), + ), + ), + wantErr: errs.ThrowInvalidArgument(nil, "QUERY-ciF4k", "Errors.Key.ExpireBeforeNow"), + }, + { + name: "decrypt error", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher(key_repo.NewAddedEvent(context.Background(), + &eventstore.Aggregate{ + ID: "keyID", + Type: key_repo.AggregateType, + ResourceOwner: "instanceID", + InstanceID: "instanceID", + Version: key_repo.AggregateVersion, + }, + domain.KeyUsageSigning, "alg", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "keyID", + Crypted: []byte("private"), + }, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "keyID", + Crypted: []byte("public"), + }, + future, + future, + )), + ), + ), + encryption: func(t *testing.T) *crypto.MockEncryptionAlgorithm { + encryption := crypto.NewMockEncryptionAlgorithm(gomock.NewController(t)) + expect := encryption.EXPECT() + expect.Algorithm().Return("alg") + expect.DecryptionKeyIDs().Return([]string{}) + return encryption + }, + wantErr: errs.ThrowInternal(nil, "QUERY-Ie4oh", "Errors.Internal"), + }, + { + name: "parse error", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher(key_repo.NewAddedEvent(context.Background(), + &eventstore.Aggregate{ + ID: "keyID", + Type: key_repo.AggregateType, + ResourceOwner: "instanceID", + InstanceID: "instanceID", + Version: key_repo.AggregateVersion, + }, + domain.KeyUsageSigning, "alg", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "keyID", + Crypted: []byte("private"), + }, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "keyID", + Crypted: []byte("public"), + }, + future, + future, + )), + ), + ), + encryption: func(t *testing.T) *crypto.MockEncryptionAlgorithm { + encryption := crypto.NewMockEncryptionAlgorithm(gomock.NewController(t)) + expect := encryption.EXPECT() + expect.Algorithm().Return("alg") + expect.DecryptionKeyIDs().Return([]string{"keyID"}) + expect.Decrypt([]byte("public"), "keyID").Return([]byte("foo"), nil) + return encryption + }, + wantErr: errs.ThrowInternal(nil, "QUERY-Kai2Z", "Errors.Internal"), + }, + { + name: "success", + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher(key_repo.NewAddedEvent(context.Background(), + &eventstore.Aggregate{ + ID: "keyID", + Type: key_repo.AggregateType, + ResourceOwner: "instanceID", + InstanceID: "instanceID", + Version: key_repo.AggregateVersion, + }, + domain.KeyUsageSigning, "alg", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "keyID", + Crypted: []byte("private"), + }, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "alg", + KeyID: "keyID", + Crypted: []byte("public"), + }, + future, + future, + )), + ), + ), + encryption: func(t *testing.T) *crypto.MockEncryptionAlgorithm { + encryption := crypto.NewMockEncryptionAlgorithm(gomock.NewController(t)) + expect := encryption.EXPECT() + expect.Algorithm().Return("alg") + expect.DecryptionKeyIDs().Return([]string{"keyID"}) + expect.Decrypt([]byte("public"), "keyID").Return([]byte(pubKey), nil) + return encryption + }, + want: &rsaPublicKey{ + key: key{ + id: "keyID", + resourceOwner: "instanceID", + algorithm: "alg", + use: domain.KeyUsageSigning, + }, + expiry: future, + publicKey: func() *rsa.PublicKey { + publicKey, err := crypto.BytesToPublicKey([]byte(pubKey)) + if err != nil { + panic(err) + } + return publicKey + }(), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + q := &Queries{ + eventstore: tt.eventstore(t), + } + if tt.encryption != nil { + q.keyEncryptionAlgorithm = tt.encryption(t) + } + ctx := authz.NewMockContext("instanceID", "orgID", "loginClient") + key, err := q.GetActivePublicKeyByID(ctx, "keyID", now) + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + return + } + require.NoError(t, err) + require.NotNil(t, key) + + got := key.(*rsaPublicKey) + assert.WithinDuration(t, tt.want.expiry, got.expiry, time.Second) + tt.want.expiry = time.Time{} + got.expiry = time.Time{} + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/internal/query/org_member_test.go b/internal/query/org_member_test.go index bcd3adbf95..7dc80dffae 100644 --- a/internal/query/org_member_test.go +++ b/internal/query/org_member_test.go @@ -21,24 +21,24 @@ var ( ", members.user_id" + ", members.roles" + ", projections.login_names3.login_name" + - ", projections.users8_humans.email" + - ", projections.users8_humans.first_name" + - ", projections.users8_humans.last_name" + - ", projections.users8_humans.display_name" + - ", projections.users8_machines.name" + - ", projections.users8_humans.avatar_key" + - ", projections.users8.type" + + ", projections.users9_humans.email" + + ", projections.users9_humans.first_name" + + ", projections.users9_humans.last_name" + + ", projections.users9_humans.display_name" + + ", projections.users9_machines.name" + + ", projections.users9_humans.avatar_key" + + ", projections.users9.type" + ", COUNT(*) OVER () " + "FROM projections.org_members4 AS members " + - "LEFT JOIN projections.users8_humans " + - "ON members.user_id = projections.users8_humans.user_id " + - "AND members.instance_id = projections.users8_humans.instance_id " + - "LEFT JOIN projections.users8_machines " + - "ON members.user_id = projections.users8_machines.user_id " + - "AND members.instance_id = projections.users8_machines.instance_id " + - "LEFT JOIN projections.users8 " + - "ON members.user_id = projections.users8.id " + - "AND members.instance_id = projections.users8.instance_id " + + "LEFT JOIN projections.users9_humans " + + "ON members.user_id = projections.users9_humans.user_id " + + "AND members.instance_id = projections.users9_humans.instance_id " + + "LEFT JOIN projections.users9_machines " + + "ON members.user_id = projections.users9_machines.user_id " + + "AND members.instance_id = projections.users9_machines.instance_id " + + "LEFT JOIN projections.users9 " + + "ON members.user_id = projections.users9.id " + + "AND members.instance_id = projections.users9.instance_id " + "LEFT JOIN projections.login_names3 " + "ON members.user_id = projections.login_names3.user_id " + "AND members.instance_id = projections.login_names3.instance_id " + diff --git a/internal/query/project_grant_member_test.go b/internal/query/project_grant_member_test.go index 43eb926899..2cf413913c 100644 --- a/internal/query/project_grant_member_test.go +++ b/internal/query/project_grant_member_test.go @@ -21,24 +21,24 @@ var ( ", members.user_id" + ", members.roles" + ", projections.login_names3.login_name" + - ", projections.users8_humans.email" + - ", projections.users8_humans.first_name" + - ", projections.users8_humans.last_name" + - ", projections.users8_humans.display_name" + - ", projections.users8_machines.name" + - ", projections.users8_humans.avatar_key" + - ", projections.users8.type" + + ", projections.users9_humans.email" + + ", projections.users9_humans.first_name" + + ", projections.users9_humans.last_name" + + ", projections.users9_humans.display_name" + + ", projections.users9_machines.name" + + ", projections.users9_humans.avatar_key" + + ", projections.users9.type" + ", COUNT(*) OVER () " + "FROM projections.project_grant_members4 AS members " + - "LEFT JOIN projections.users8_humans " + - "ON members.user_id = projections.users8_humans.user_id " + - "AND members.instance_id = projections.users8_humans.instance_id " + - "LEFT JOIN projections.users8_machines " + - "ON members.user_id = projections.users8_machines.user_id " + - "AND members.instance_id = projections.users8_machines.instance_id " + - "LEFT JOIN projections.users8 " + - "ON members.user_id = projections.users8.id " + - "AND members.instance_id = projections.users8.instance_id " + + "LEFT JOIN projections.users9_humans " + + "ON members.user_id = projections.users9_humans.user_id " + + "AND members.instance_id = projections.users9_humans.instance_id " + + "LEFT JOIN projections.users9_machines " + + "ON members.user_id = projections.users9_machines.user_id " + + "AND members.instance_id = projections.users9_machines.instance_id " + + "LEFT JOIN projections.users9 " + + "ON members.user_id = projections.users9.id " + + "AND members.instance_id = projections.users9.instance_id " + "LEFT JOIN projections.login_names3 " + "ON members.user_id = projections.login_names3.user_id " + "AND members.instance_id = projections.login_names3.instance_id " + diff --git a/internal/query/project_member_test.go b/internal/query/project_member_test.go index 45caf42dc9..b280750247 100644 --- a/internal/query/project_member_test.go +++ b/internal/query/project_member_test.go @@ -21,24 +21,24 @@ var ( ", members.user_id" + ", members.roles" + ", projections.login_names3.login_name" + - ", projections.users8_humans.email" + - ", projections.users8_humans.first_name" + - ", projections.users8_humans.last_name" + - ", projections.users8_humans.display_name" + - ", projections.users8_machines.name" + - ", projections.users8_humans.avatar_key" + - ", projections.users8.type" + + ", projections.users9_humans.email" + + ", projections.users9_humans.first_name" + + ", projections.users9_humans.last_name" + + ", projections.users9_humans.display_name" + + ", projections.users9_machines.name" + + ", projections.users9_humans.avatar_key" + + ", projections.users9.type" + ", COUNT(*) OVER () " + "FROM projections.project_members4 AS members " + - "LEFT JOIN projections.users8_humans " + - "ON members.user_id = projections.users8_humans.user_id " + - "AND members.instance_id = projections.users8_humans.instance_id " + - "LEFT JOIN projections.users8_machines " + - "ON members.user_id = projections.users8_machines.user_id " + - "AND members.instance_id = projections.users8_machines.instance_id " + - "LEFT JOIN projections.users8 " + - "ON members.user_id = projections.users8.id " + - "AND members.instance_id = projections.users8.instance_id " + + "LEFT JOIN projections.users9_humans " + + "ON members.user_id = projections.users9_humans.user_id " + + "AND members.instance_id = projections.users9_humans.instance_id " + + "LEFT JOIN projections.users9_machines " + + "ON members.user_id = projections.users9_machines.user_id " + + "AND members.instance_id = projections.users9_machines.instance_id " + + "LEFT JOIN projections.users9 " + + "ON members.user_id = projections.users9.id " + + "AND members.instance_id = projections.users9.instance_id " + "LEFT JOIN projections.login_names3 " + "ON members.user_id = projections.login_names3.user_id " + "AND members.instance_id = projections.login_names3.instance_id " + diff --git a/internal/query/projection/app.go b/internal/query/projection/app.go index 3ed632c239..97e7da21f8 100644 --- a/internal/query/projection/app.go +++ b/internal/query/projection/app.go @@ -15,7 +15,7 @@ import ( ) const ( - AppProjectionTable = "projections.apps5" + AppProjectionTable = "projections.apps6" AppAPITable = AppProjectionTable + "_" + appAPITableSuffix AppOIDCTable = AppProjectionTable + "_" + appOIDCTableSuffix AppSAMLTable = AppProjectionTable + "_" + appSAMLTableSuffix @@ -29,7 +29,6 @@ const ( AppColumnInstanceID = "instance_id" AppColumnState = "state" AppColumnSequence = "sequence" - AppColumnOwnerRemoved = "owner_removed" appAPITableSuffix = "api_configs" AppAPIConfigColumnAppID = "app_id" @@ -89,11 +88,9 @@ func (*appProjection) Init() *old_handler.Check { handler.NewColumn(AppColumnInstanceID, handler.ColumnTypeText), handler.NewColumn(AppColumnState, handler.ColumnTypeEnum), handler.NewColumn(AppColumnSequence, handler.ColumnTypeInt64), - handler.NewColumn(AppColumnOwnerRemoved, handler.ColumnTypeBool, handler.Default(false)), }, handler.NewPrimaryKey(AppColumnInstanceID, AppColumnID), handler.WithIndex(handler.NewIndex("project_id", []string{AppColumnProjectID})), - handler.WithIndex(handler.NewIndex("owner_removed", []string{AppColumnOwnerRemoved})), ), handler.NewSuffixedTable([]*handler.InitColumn{ handler.NewColumn(AppAPIConfigColumnAppID, handler.ColumnTypeText), diff --git a/internal/query/projection/app_test.go b/internal/query/projection/app_test.go index f595d6a36d..8ce659be25 100644 --- a/internal/query/projection/app_test.go +++ b/internal/query/projection/app_test.go @@ -46,7 +46,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.apps5 (id, name, project_id, creation_date, change_date, resource_owner, instance_id, state, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.apps6 (id, name, project_id, creation_date, change_date, resource_owner, instance_id, state, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "app-id", "my-app", @@ -83,7 +83,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps5 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.apps6 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "my-app", anyArg{}, @@ -136,7 +136,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps5 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.apps6 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ domain.AppStateInactive, anyArg{}, @@ -168,7 +168,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps5 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.apps6 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ domain.AppStateActive, anyArg{}, @@ -200,7 +200,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps5 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.apps6 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "app-id", "instance-id", @@ -227,7 +227,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps5 WHERE (project_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.apps6 WHERE (project_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -254,7 +254,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps5 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.apps6 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, @@ -285,7 +285,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.apps5_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.apps6_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "app-id", "instance-id", @@ -295,7 +295,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -329,7 +329,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps5_api_configs SET (client_secret, auth_method) = ($1, $2) WHERE (app_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps6_api_configs SET (client_secret, auth_method) = ($1, $2) WHERE (app_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, domain.APIAuthMethodTypePrivateKeyJWT, @@ -338,7 +338,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -391,7 +391,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps5_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.apps6_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ anyArg{}, "app-id", @@ -399,7 +399,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -447,7 +447,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.apps5_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)", + expectedStmt: "INSERT INTO projections.apps6_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)", expectedArgs: []interface{}{ "app-id", "instance-id", @@ -471,7 +471,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -518,7 +518,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps5_oidc_configs SET (version, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) WHERE (app_id = $16) AND (instance_id = $17)", + expectedStmt: "UPDATE projections.apps6_oidc_configs SET (version, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) WHERE (app_id = $16) AND (instance_id = $17)", expectedArgs: []interface{}{ domain.OIDCVersionV1, database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"}, @@ -540,7 +540,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -593,7 +593,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps5_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.apps6_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ anyArg{}, "app-id", @@ -601,7 +601,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -630,7 +630,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps5 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.apps6 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", diff --git a/internal/query/projection/authn_key.go b/internal/query/projection/authn_key.go index 3d007dbca9..bc14c917eb 100644 --- a/internal/query/projection/authn_key.go +++ b/internal/query/projection/authn_key.go @@ -30,7 +30,6 @@ const ( AuthNKeyPublicKeyCol = "public_key" AuthNKeyTypeCol = "type" AuthNKeyEnabledCol = "enabled" - AuthNKeyOwnerRemovedCol = "owner_removed" ) type authNKeyProjection struct{} @@ -59,12 +58,10 @@ func (*authNKeyProjection) Init() *old_handler.Check { handler.NewColumn(AuthNKeyPublicKeyCol, handler.ColumnTypeBytes), handler.NewColumn(AuthNKeyEnabledCol, handler.ColumnTypeBool, handler.Default(true)), handler.NewColumn(AuthNKeyTypeCol, handler.ColumnTypeEnum, handler.Default(0)), - handler.NewColumn(AuthNKeyOwnerRemovedCol, handler.ColumnTypeBool, handler.Default(false)), }, handler.NewPrimaryKey(AuthNKeyInstanceIDCol, AuthNKeyIDCol), handler.WithIndex(handler.NewIndex("enabled", []string{AuthNKeyEnabledCol})), handler.WithIndex(handler.NewIndex("identifier", []string{AuthNKeyIdentifierCol})), - handler.WithIndex(handler.NewIndex("owner_removed", []string{AuthNKeyOwnerRemovedCol})), ), ) } diff --git a/internal/query/projection/user.go b/internal/query/projection/user.go index cafbbfee7d..3451e6fc37 100644 --- a/internal/query/projection/user.go +++ b/internal/query/projection/user.go @@ -15,7 +15,7 @@ import ( ) const ( - UserTable = "projections.users8" + UserTable = "projections.users9" UserHumanTable = UserTable + "_" + UserHumanSuffix UserMachineTable = UserTable + "_" + UserMachineSuffix UserNotifyTable = UserTable + "_" + UserNotifySuffix @@ -29,7 +29,6 @@ const ( UserInstanceIDCol = "instance_id" UserUsernameCol = "username" UserTypeCol = "type" - UserOwnerRemovedCol = "owner_removed" UserHumanSuffix = "humans" HumanUserIDCol = "user_id" @@ -94,12 +93,10 @@ func (*userProjection) Init() *old_handler.Check { handler.NewColumn(UserInstanceIDCol, handler.ColumnTypeText), handler.NewColumn(UserUsernameCol, handler.ColumnTypeText), handler.NewColumn(UserTypeCol, handler.ColumnTypeEnum), - handler.NewColumn(UserOwnerRemovedCol, handler.ColumnTypeBool, handler.Default(false)), }, handler.NewPrimaryKey(UserInstanceIDCol, UserIDCol), handler.WithIndex(handler.NewIndex("username", []string{UserUsernameCol})), handler.WithIndex(handler.NewIndex("resource_owner", []string{UserResourceOwnerCol})), - handler.WithIndex(handler.NewIndex("owner_removed", []string{UserOwnerRemovedCol})), ), handler.NewSuffixedTable([]*handler.InitColumn{ handler.NewColumn(HumanUserIDCol, handler.ColumnTypeText), diff --git a/internal/query/projection/user_metadata.go b/internal/query/projection/user_metadata.go index 326860cdf0..e1b155294c 100644 --- a/internal/query/projection/user_metadata.go +++ b/internal/query/projection/user_metadata.go @@ -13,7 +13,7 @@ import ( ) const ( - UserMetadataProjectionTable = "projections.user_metadata4" + UserMetadataProjectionTable = "projections.user_metadata5" UserMetadataColumnUserID = "user_id" UserMetadataColumnCreationDate = "creation_date" @@ -23,7 +23,6 @@ const ( UserMetadataColumnInstanceID = "instance_id" UserMetadataColumnKey = "key" UserMetadataColumnValue = "value" - UserMetadataColumnOwnerRemoved = "owner_removed" ) type userMetadataProjection struct{} @@ -47,11 +46,9 @@ func (*userMetadataProjection) Init() *old_handler.Check { handler.NewColumn(UserMetadataColumnInstanceID, handler.ColumnTypeText), handler.NewColumn(UserMetadataColumnKey, handler.ColumnTypeText), handler.NewColumn(UserMetadataColumnValue, handler.ColumnTypeBytes, handler.Nullable()), - handler.NewColumn(UserMetadataColumnOwnerRemoved, handler.ColumnTypeBool, handler.Default(false)), }, handler.NewPrimaryKey(UserMetadataColumnInstanceID, UserMetadataColumnUserID, UserMetadataColumnKey), handler.WithIndex(handler.NewIndex("resource_owner", []string{UserGrantResourceOwner})), - handler.WithIndex(handler.NewIndex("owner_removed", []string{UserMetadataColumnOwnerRemoved})), ), ) } diff --git a/internal/query/projection/user_metadata_test.go b/internal/query/projection/user_metadata_test.go index fcf8b26d2f..9d0bb60ce2 100644 --- a/internal/query/projection/user_metadata_test.go +++ b/internal/query/projection/user_metadata_test.go @@ -41,7 +41,7 @@ func TestUserMetadataProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.user_metadata4 (instance_id, user_id, key, resource_owner, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (instance_id, user_id, key) DO UPDATE SET (resource_owner, creation_date, change_date, sequence, value) = (EXCLUDED.resource_owner, EXCLUDED.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)", + expectedStmt: "INSERT INTO projections.user_metadata5 (instance_id, user_id, key, resource_owner, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (instance_id, user_id, key) DO UPDATE SET (resource_owner, creation_date, change_date, sequence, value) = (EXCLUDED.resource_owner, EXCLUDED.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)", expectedArgs: []interface{}{ "instance-id", "agg-id", @@ -76,7 +76,7 @@ func TestUserMetadataProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.user_metadata4 WHERE (user_id = $1) AND (key = $2) AND (instance_id = $3)", + expectedStmt: "DELETE FROM projections.user_metadata5 WHERE (user_id = $1) AND (key = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "agg-id", "key", @@ -104,7 +104,7 @@ func TestUserMetadataProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.user_metadata4 WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.user_metadata5 WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -131,7 +131,7 @@ func TestUserMetadataProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.user_metadata4 WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.user_metadata5 WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -158,7 +158,7 @@ func TestUserMetadataProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.user_metadata4 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.user_metadata5 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", @@ -185,7 +185,7 @@ func TestUserMetadataProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.user_metadata4 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.user_metadata5 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, diff --git a/internal/query/projection/user_test.go b/internal/query/projection/user_test.go index 66b4f65942..da4f2a860c 100644 --- a/internal/query/projection/user_test.go +++ b/internal/query/projection/user_test.go @@ -50,7 +50,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users8 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users9 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -64,7 +64,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", + expectedStmt: "INSERT INTO projections.users9_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -79,7 +79,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users9_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -119,7 +119,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users8 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users9 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -133,7 +133,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", + expectedStmt: "INSERT INTO projections.users9_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -148,7 +148,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users9_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -183,7 +183,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users8 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users9 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -197,7 +197,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", + expectedStmt: "INSERT INTO projections.users9_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -212,7 +212,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users9_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -252,7 +252,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users8 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users9 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -266,7 +266,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", + expectedStmt: "INSERT INTO projections.users9_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -281,7 +281,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users9_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -321,7 +321,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users8 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users9 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -335,7 +335,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", + expectedStmt: "INSERT INTO projections.users9_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -350,7 +350,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users9_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -385,7 +385,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users8 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users9 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -399,7 +399,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", + expectedStmt: "INSERT INTO projections.users9_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -414,7 +414,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users9_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -444,7 +444,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ domain.UserStateInitial, "agg-id", @@ -472,7 +472,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ domain.UserStateInitial, "agg-id", @@ -500,7 +500,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ domain.UserStateActive, "agg-id", @@ -528,7 +528,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9 SET state = $1 WHERE (id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ domain.UserStateActive, "agg-id", @@ -556,7 +556,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users9 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, domain.UserStateLocked, @@ -586,7 +586,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users9 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, domain.UserStateActive, @@ -616,7 +616,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users9 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, domain.UserStateInactive, @@ -646,7 +646,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users9 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, domain.UserStateActive, @@ -676,7 +676,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.users8 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.users9 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -705,7 +705,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users9 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, "username", @@ -737,7 +737,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.users9 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ anyArg{}, "id@temporary.domain", @@ -774,7 +774,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -783,7 +783,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", + expectedStmt: "UPDATE projections.users9_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", expectedArgs: []interface{}{ "first-name", "last-name", @@ -823,7 +823,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -832,7 +832,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", + expectedStmt: "UPDATE projections.users9_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)", expectedArgs: []interface{}{ "first-name", "last-name", @@ -867,7 +867,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -876,7 +876,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ domain.PhoneNumber("+41 00 000 00 00"), false, @@ -885,7 +885,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ &sql.NullString{String: "+41 00 000 00 00", Valid: true}, "agg-id", @@ -915,7 +915,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -924,7 +924,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ domain.PhoneNumber("+41 00 000 00 00"), false, @@ -933,7 +933,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ &sql.NullString{String: "+41 00 000 00 00", Valid: true}, "agg-id", @@ -961,7 +961,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -970,7 +970,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ nil, nil, @@ -979,7 +979,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ nil, nil, @@ -1008,7 +1008,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1017,7 +1017,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ nil, nil, @@ -1026,7 +1026,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ nil, nil, @@ -1055,7 +1055,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1064,7 +1064,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1072,7 +1072,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "UPDATE projections.users9_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1099,7 +1099,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1108,7 +1108,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1116,7 +1116,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "UPDATE projections.users9_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1145,7 +1145,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1154,7 +1154,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ domain.EmailAddress("email@zitadel.com"), false, @@ -1163,7 +1163,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ &sql.NullString{String: "email@zitadel.com", Valid: true}, "agg-id", @@ -1193,7 +1193,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1202,7 +1202,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ domain.EmailAddress("email@zitadel.com"), false, @@ -1211,7 +1211,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ &sql.NullString{String: "email@zitadel.com", Valid: true}, "agg-id", @@ -1239,7 +1239,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1248,7 +1248,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1256,7 +1256,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "UPDATE projections.users9_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1283,7 +1283,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1292,7 +1292,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1300,7 +1300,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)", + expectedStmt: "UPDATE projections.users9_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1329,7 +1329,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1338,7 +1338,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "users/agg-id/avatar", "agg-id", @@ -1366,7 +1366,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1375,7 +1375,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ nil, "agg-id", @@ -1406,7 +1406,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users8 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users9 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -1420,7 +1420,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users9_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1454,7 +1454,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.users8 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.users9 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "agg-id", anyArg{}, @@ -1468,7 +1468,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "INSERT INTO projections.users8_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.users9_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -1501,7 +1501,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1510,7 +1510,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ "machine-name", "description", @@ -1541,7 +1541,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1550,7 +1550,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_machines SET name = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_machines SET name = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "machine-name", "agg-id", @@ -1580,7 +1580,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1589,7 +1589,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_machines SET description = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_machines SET description = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ "description", "agg-id", @@ -1638,7 +1638,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1647,7 +1647,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_machines SET has_secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_machines SET has_secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ true, "agg-id", @@ -1675,7 +1675,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.users8 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -1684,7 +1684,7 @@ func TestUserProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.users8_machines SET has_secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.users9_machines SET has_secret = $1 WHERE (user_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ false, "agg-id", @@ -1712,7 +1712,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.users8 WHERE (instance_id = $1) AND (resource_owner = $2)", + expectedStmt: "DELETE FROM projections.users9 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedArgs: []interface{}{ "instance-id", "agg-id", @@ -1739,7 +1739,7 @@ func TestUserProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.users8 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.users9 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, diff --git a/internal/query/query.go b/internal/query/query.go index cb50ce1fcd..70d6723082 100644 --- a/internal/query/query.go +++ b/internal/query/query.go @@ -9,15 +9,16 @@ import ( "time" "github.com/rakyll/statik/fs" + "github.com/zitadel/logging" "golang.org/x/text/language" "github.com/zitadel/zitadel/internal/api/authz" - internal_authz "github.com/zitadel/zitadel/internal/api/authz" sd "github.com/zitadel/zitadel/internal/config/systemdefaults" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/query/projection" "github.com/zitadel/zitadel/internal/repository/action" "github.com/zitadel/zitadel/internal/repository/authrequest" @@ -32,15 +33,17 @@ import ( "github.com/zitadel/zitadel/internal/repository/session" usr_repo "github.com/zitadel/zitadel/internal/repository/user" "github.com/zitadel/zitadel/internal/repository/usergrant" + "github.com/zitadel/zitadel/internal/telemetry/tracing" ) type Queries struct { eventstore *eventstore.Eventstore client *database.DB - idpConfigEncryption crypto.EncryptionAlgorithm - sessionTokenVerifier func(ctx context.Context, sessionToken string, sessionID string, tokenID string) (err error) - checkPermission domain.PermissionCheck + keyEncryptionAlgorithm crypto.EncryptionAlgorithm + idpConfigEncryption crypto.EncryptionAlgorithm + sessionTokenVerifier func(ctx context.Context, sessionToken string, sessionID string, tokenID string) (err error) + checkPermission domain.PermissionCheck DefaultLanguage language.Tag LoginDir http.FileSystem @@ -65,7 +68,7 @@ func StartQueries( sessionTokenVerifier func(ctx context.Context, sessionToken string, sessionID string, tokenID string) (err error), permissionCheck func(q *Queries) domain.PermissionCheck, defaultAuditLogRetention time.Duration, - systemAPIUsers map[string]*internal_authz.SystemAPIUser, + systemAPIUsers map[string]*authz.SystemAPIUser, ) (repo *Queries, err error) { statikLoginFS, err := fs.NewWithNamespace("login") if err != nil { @@ -86,8 +89,16 @@ func StartQueries( LoginTranslationFileContents: make(map[string][]byte), NotificationTranslationFileContents: make(map[string][]byte), zitadelRoles: zitadelRoles, + keyEncryptionAlgorithm: keyEncryptionAlgorithm, + idpConfigEncryption: idpConfigEncryption, sessionTokenVerifier: sessionTokenVerifier, - defaultAuditLogRetention: defaultAuditLogRetention, + multifactors: domain.MultifactorConfigs{ + OTP: domain.OTPConfig{ + CryptoMFA: otpEncryption, + Issuer: defaults.Multifactors.OTP.Issuer, + }, + }, + defaultAuditLogRetention: defaultAuditLogRetention, } iam_repo.RegisterEventMappers(repo.eventstore) usr_repo.RegisterEventMappers(repo.eventstore) @@ -103,14 +114,6 @@ func StartQueries( quota.RegisterEventMappers(repo.eventstore) limits.RegisterEventMappers(repo.eventstore) - repo.idpConfigEncryption = idpConfigEncryption - repo.multifactors = domain.MultifactorConfigs{ - OTP: domain.OTPConfig{ - CryptoMFA: otpEncryption, - Issuer: defaults.Multifactors.OTP.Issuer, - }, - } - repo.checkPermission = permissionCheck(repo) err = projection.Create(ctx, sqlClient, es, projections, keyEncryptionAlgorithm, certEncryptionAlgorithm, systemAPIUsers) @@ -145,3 +148,24 @@ func init() { &authRequestByIDQuery, ) } + +// triggerBatch calls Trigger on every handler in a separate Go routine. +// The returned context is the context returned by the Trigger that finishes last. +func triggerBatch(ctx context.Context, handlers ...*handler.Handler) { + var wg sync.WaitGroup + wg.Add(len(handlers)) + + for _, h := range handlers { + go func(ctx context.Context, h *handler.Handler) { + name := h.ProjectionName() + _, traceSpan := tracing.NewNamedSpan(ctx, fmt.Sprintf("Trigger%s", name)) + _, err := h.Trigger(ctx, handler.WithAwaitRunning()) + logging.OnError(err).WithField("projection", name).Debug("trigger failed") + traceSpan.EndWithError(err) + + wg.Done() + }(ctx, h) + } + + wg.Wait() +} diff --git a/internal/query/query_test.go b/internal/query/query_test.go index fc16ee9fad..f1dee69814 100644 --- a/internal/query/query_test.go +++ b/internal/query/query_test.go @@ -1,11 +1,92 @@ package query import ( + "database/sql" "testing" + "time" "github.com/stretchr/testify/assert" + + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/eventstore/repository" + "github.com/zitadel/zitadel/internal/eventstore/repository/mock" + action_repo "github.com/zitadel/zitadel/internal/repository/action" + "github.com/zitadel/zitadel/internal/repository/authrequest" + "github.com/zitadel/zitadel/internal/repository/feature" + "github.com/zitadel/zitadel/internal/repository/idpintent" + iam_repo "github.com/zitadel/zitadel/internal/repository/instance" + key_repo "github.com/zitadel/zitadel/internal/repository/keypair" + "github.com/zitadel/zitadel/internal/repository/limits" + "github.com/zitadel/zitadel/internal/repository/oidcsession" + "github.com/zitadel/zitadel/internal/repository/org" + proj_repo "github.com/zitadel/zitadel/internal/repository/project" + quota_repo "github.com/zitadel/zitadel/internal/repository/quota" + "github.com/zitadel/zitadel/internal/repository/session" + usr_repo "github.com/zitadel/zitadel/internal/repository/user" + "github.com/zitadel/zitadel/internal/repository/usergrant" ) +type expect func(mockRepository *mock.MockRepository) + +func expectEventstore(expects ...expect) func(*testing.T) *eventstore.Eventstore { + return func(t *testing.T) *eventstore.Eventstore { + m := mock.NewRepo(t) + for _, e := range expects { + e(m) + } + es := eventstore.NewEventstore( + &eventstore.Config{ + Querier: m.MockQuerier, + Pusher: m.MockPusher, + }, + ) + iam_repo.RegisterEventMappers(es) + org.RegisterEventMappers(es) + usr_repo.RegisterEventMappers(es) + proj_repo.RegisterEventMappers(es) + usergrant.RegisterEventMappers(es) + key_repo.RegisterEventMappers(es) + action_repo.RegisterEventMappers(es) + session.RegisterEventMappers(es) + idpintent.RegisterEventMappers(es) + authrequest.RegisterEventMappers(es) + oidcsession.RegisterEventMappers(es) + quota_repo.RegisterEventMappers(es) + limits.RegisterEventMappers(es) + feature.RegisterEventMappers(es) + return es + } +} + +func expectFilter(events ...eventstore.Event) expect { + return func(m *mock.MockRepository) { + m.ExpectFilterEvents(events...) + } +} +func expectFilterError(err error) expect { + return func(m *mock.MockRepository) { + m.ExpectFilterEventsError(err) + } +} + +func eventFromEventPusher(event eventstore.Command) *repository.Event { + data, _ := eventstore.EventData(event) + return &repository.Event{ + InstanceID: event.Aggregate().InstanceID, + ID: "", + Seq: 0, + CreationDate: time.Time{}, + Typ: event.Type(), + Data: data, + EditorUser: event.Creator(), + Version: event.Aggregate().Version, + AggregateID: event.Aggregate().ID, + AggregateType: event.Aggregate().Type, + ResourceOwner: sql.NullString{String: event.Aggregate().ResourceOwner, Valid: event.Aggregate().ResourceOwner != ""}, + Constraints: event.UniqueConstraints(), + } +} + func Test_cleanStaticQueries(t *testing.T) { query := `select foo, diff --git a/internal/query/sessions_test.go b/internal/query/sessions_test.go index 357ceb598b..d4f621073a 100644 --- a/internal/query/sessions_test.go +++ b/internal/query/sessions_test.go @@ -31,7 +31,7 @@ var ( ` projections.sessions8.user_resource_owner,` + ` projections.sessions8.user_checked_at,` + ` projections.login_names3.login_name,` + - ` projections.users8_humans.display_name,` + + ` projections.users9_humans.display_name,` + ` projections.sessions8.password_checked_at,` + ` projections.sessions8.intent_checked_at,` + ` projections.sessions8.webauthn_checked_at,` + @@ -48,8 +48,8 @@ var ( ` projections.sessions8.expiration` + ` FROM projections.sessions8` + ` LEFT JOIN projections.login_names3 ON projections.sessions8.user_id = projections.login_names3.user_id AND projections.sessions8.instance_id = projections.login_names3.instance_id` + - ` LEFT JOIN projections.users8_humans ON projections.sessions8.user_id = projections.users8_humans.user_id AND projections.sessions8.instance_id = projections.users8_humans.instance_id` + - ` LEFT JOIN projections.users8 ON projections.sessions8.user_id = projections.users8.id AND projections.sessions8.instance_id = projections.users8.instance_id` + + ` LEFT JOIN projections.users9_humans ON projections.sessions8.user_id = projections.users9_humans.user_id AND projections.sessions8.instance_id = projections.users9_humans.instance_id` + + ` LEFT JOIN projections.users9 ON projections.sessions8.user_id = projections.users9.id AND projections.sessions8.instance_id = projections.users9.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) expectedSessionsQuery = regexp.QuoteMeta(`SELECT projections.sessions8.id,` + ` projections.sessions8.creation_date,` + @@ -62,7 +62,7 @@ var ( ` projections.sessions8.user_resource_owner,` + ` projections.sessions8.user_checked_at,` + ` projections.login_names3.login_name,` + - ` projections.users8_humans.display_name,` + + ` projections.users9_humans.display_name,` + ` projections.sessions8.password_checked_at,` + ` projections.sessions8.intent_checked_at,` + ` projections.sessions8.webauthn_checked_at,` + @@ -75,8 +75,8 @@ var ( ` COUNT(*) OVER ()` + ` FROM projections.sessions8` + ` LEFT JOIN projections.login_names3 ON projections.sessions8.user_id = projections.login_names3.user_id AND projections.sessions8.instance_id = projections.login_names3.instance_id` + - ` LEFT JOIN projections.users8_humans ON projections.sessions8.user_id = projections.users8_humans.user_id AND projections.sessions8.instance_id = projections.users8_humans.instance_id` + - ` LEFT JOIN projections.users8 ON projections.sessions8.user_id = projections.users8.id AND projections.sessions8.instance_id = projections.users8.instance_id` + + ` LEFT JOIN projections.users9_humans ON projections.sessions8.user_id = projections.users9_humans.user_id AND projections.sessions8.instance_id = projections.users9_humans.instance_id` + + ` LEFT JOIN projections.users9 ON projections.sessions8.user_id = projections.users9.id AND projections.sessions8.instance_id = projections.users9.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) sessionCols = []string{ diff --git a/internal/query/testdata/userinfo_human.json b/internal/query/testdata/userinfo_human.json new file mode 100644 index 0000000000..4744b9299d --- /dev/null +++ b/internal/query/testdata/userinfo_human.json @@ -0,0 +1,47 @@ +{ + "user": { + "id": "231965491734773762", + "creation_date": "2023-09-15T06:10:07.434142+00:00", + "change_date": "2023-11-14T13:27:02.072318+00:00", + "sequence": 1148, + "state": 1, + "resource_owner": "231848297847848962", + "username": "tim+tesmail@zitadel.com", + "preferred_login_name": "tim+tesmail@zitadel.com@demo.localhost", + "human": { + "first_name": "Tim", + "last_name": "Mohlmann", + "nick_name": "muhlemmer", + "display_name": "Tim Mohlmann", + "avatar_key": null, + "email": "tim+tesmail@zitadel.com", + "is_email_verified": true, + "phone": "+40123456789", + "is_phone_verified": false + }, + "machine": null + }, + "org": { + "name": "demo", + "primary_domain": "demo.localhost" + }, + "metadata": [ + { + "creation_date": "2023-11-14T13:26:03.553702+00:00", + "change_date": "2023-11-14T13:26:03.553702+00:00", + "sequence": 1147, + "resource_owner": "231848297847848962", + "key": "bar", + "value": "Zm9v" + }, + { + "creation_date": "2023-11-14T13:25:57.171368+00:00", + "change_date": "2023-11-14T13:25:57.171368+00:00", + "sequence": 1146, + "resource_owner": "231848297847848962", + "key": "foo", + "value": "YmFy" + } + ], + "user_grants": null +} diff --git a/internal/query/testdata/userinfo_human_grants.json b/internal/query/testdata/userinfo_human_grants.json new file mode 100644 index 0000000000..fec383acf2 --- /dev/null +++ b/internal/query/testdata/userinfo_human_grants.json @@ -0,0 +1,86 @@ +{ + "user": { + "id": "231965491734773762", + "creation_date": "2023-09-15T06:10:07.434142+00:00", + "change_date": "2023-11-14T13:27:02.072318+00:00", + "sequence": 1148, + "state": 1, + "resource_owner": "231848297847848962", + "username": "tim+tesmail@zitadel.com", + "preferred_login_name": "tim+tesmail@zitadel.com@demo.localhost", + "human": { + "first_name": "Tim", + "last_name": "Mohlmann", + "nick_name": "muhlemmer", + "display_name": "Tim Mohlmann", + "avatar_key": null, + "email": "tim+tesmail@zitadel.com", + "is_email_verified": true, + "phone": "+40123456789", + "is_phone_verified": false + }, + "machine": null + }, + "org": { + "name": "demo", + "primary_domain": "demo.localhost" + }, + "metadata": [ + { + "creation_date": "2023-11-14T13:26:03.553702+00:00", + "change_date": "2023-11-14T13:26:03.553702+00:00", + "sequence": 1147, + "resource_owner": "231848297847848962", + "key": "bar", + "value": "Zm9v" + }, + { + "creation_date": "2023-11-14T13:25:57.171368+00:00", + "change_date": "2023-11-14T13:25:57.171368+00:00", + "sequence": 1146, + "resource_owner": "231848297847848962", + "key": "foo", + "value": "YmFy" + } + ], + "user_grants": [ + { + "id": "240749256523120642", + "grant_id": "", + "state": 1, + "creation_date": "2023-11-14T20:28:59.168208+00:00", + "change_date": "2023-11-14T20:50:58.822391+00:00", + "sequence": 2, + "user_id": "231965491734773762", + "roles": [ + "role1", + "role2" + ], + "resource_owner": "231848297847848962", + "project_id": "236645808328409090", + "org_name": "demo", + "org_primary_domain": "demo.localhost", + "project_name": "tests", + "user_resource_owner": "231848297847848962" + }, + { + "id": "240762315572510722", + "grant_id": "", + "state": 1, + "creation_date": "2023-11-14T22:38:42.967317+00:00", + "change_date": "2023-11-14T22:38:42.967317+00:00", + "sequence": 1, + "user_id": "231965491734773762", + "roles": [ + "role3", + "role4" + ], + "resource_owner": "231848297847848962", + "project_id": "240762134579904514", + "org_name": "demo", + "org_primary_domain": "demo.localhost", + "project_name": "tests2", + "user_resource_owner": "231848297847848962" + } + ] +} diff --git a/internal/query/testdata/userinfo_human_no_md.json b/internal/query/testdata/userinfo_human_no_md.json new file mode 100644 index 0000000000..dffbf0851a --- /dev/null +++ b/internal/query/testdata/userinfo_human_no_md.json @@ -0,0 +1,30 @@ +{ + "user": { + "id": "231965491734773762", + "creation_date": "2023-09-15T06:10:07.434142+00:00", + "change_date": "2023-11-14T13:27:02.072318+00:00", + "sequence": 1148, + "state": 1, + "resource_owner": "231848297847848962", + "username": "tim+tesmail@zitadel.com", + "preferred_login_name": "tim+tesmail@zitadel.com@demo.localhost", + "human": { + "first_name": "Tim", + "last_name": "Mohlmann", + "nick_name": "muhlemmer", + "display_name": "Tim Mohlmann", + "avatar_key": null, + "email": "tim+tesmail@zitadel.com", + "is_email_verified": true, + "phone": "+40123456789", + "is_phone_verified": false + }, + "machine": null + }, + "org": { + "name": "demo", + "primary_domain": "demo.localhost" + }, + "metadata": null, + "user_grants": null +} diff --git a/internal/query/testdata/userinfo_machine.json b/internal/query/testdata/userinfo_machine.json new file mode 100644 index 0000000000..b2b13d18e4 --- /dev/null +++ b/internal/query/testdata/userinfo_machine.json @@ -0,0 +1,40 @@ +{ + "user": { + "id": "240707570677841922", + "creation_date": "2023-11-14T13:34:52.473732+00:00", + "change_date": "2023-11-14T13:35:02.861342+00:00", + "sequence": 2, + "state": 1, + "resource_owner": "231848297847848962", + "username": "tests", + "preferred_login_name": "tests@demo.localhost", + "human": null, + "machine": { + "name": "tests", + "description": "My test service user" + } + }, + "org": { + "name": "demo", + "primary_domain": "demo.localhost" + }, + "metadata": [ + { + "creation_date": "2023-11-14T13:35:30.126849+00:00", + "change_date": "2023-11-14T13:35:30.126849+00:00", + "sequence": 3, + "resource_owner": "231848297847848962", + "key": "first", + "value": "SGVsbG8gV29ybGQh" + }, + { + "creation_date": "2023-11-14T13:35:44.028343+00:00", + "change_date": "2023-11-14T13:35:44.028343+00:00", + "sequence": 4, + "resource_owner": "231848297847848962", + "key": "second", + "value": "QnllIFdvcmxkIQ==" + } + ], + "user_grants": null +} diff --git a/internal/query/testdata/userinfo_not_found.json b/internal/query/testdata/userinfo_not_found.json new file mode 100644 index 0000000000..ab27684269 --- /dev/null +++ b/internal/query/testdata/userinfo_not_found.json @@ -0,0 +1,6 @@ +{ + "user": null, + "org": null, + "metadata": null, + "user_grants": null +} diff --git a/internal/query/user.go b/internal/query/user.go index 1d4ac16004..144efec1dc 100644 --- a/internal/query/user.go +++ b/internal/query/user.go @@ -5,11 +5,9 @@ import ( "database/sql" errs "errors" "strings" - "sync" "time" sq "github.com/Masterminds/squirrel" - "github.com/zitadel/logging" "golang.org/x/text/language" "github.com/zitadel/zitadel/internal/api/authz" @@ -17,7 +15,6 @@ import ( "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/errors" - "github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/query/projection" "github.com/zitadel/zitadel/internal/telemetry/tracing" ) @@ -28,32 +25,32 @@ type Users struct { } type User struct { - ID string - CreationDate time.Time - ChangeDate time.Time - ResourceOwner string - Sequence uint64 - State domain.UserState - Type domain.UserType - Username string - LoginNames database.TextArray[string] - PreferredLoginName string - Human *Human - Machine *Machine + ID string `json:"id,omitempty"` + CreationDate time.Time `json:"creation_date,omitempty"` + ChangeDate time.Time `json:"change_date,omitempty"` + ResourceOwner string `json:"resource_owner,omitempty"` + Sequence uint64 `json:"sequence,omitempty"` + State domain.UserState `json:"state,omitempty"` + Type domain.UserType `json:"type,omitempty"` + Username string `json:"username,omitempty"` + LoginNames database.TextArray[string] `json:"login_names,omitempty"` + PreferredLoginName string `json:"preferred_login_name,omitempty"` + Human *Human `json:"human,omitempty"` + Machine *Machine `json:"machine,omitempty"` } type Human struct { - FirstName string - LastName string - NickName string - DisplayName string - AvatarKey string - PreferredLanguage language.Tag - Gender domain.Gender - Email domain.EmailAddress - IsEmailVerified bool - Phone domain.PhoneNumber - IsPhoneVerified bool + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + NickName string `json:"nick_name,omitempty"` + DisplayName string `json:"display_name,omitempty"` + AvatarKey string `json:"avatar_key,omitempty"` + PreferredLanguage language.Tag `json:"preferred_language,omitempty"` + Gender domain.Gender `json:"gender,omitempty"` + Email domain.EmailAddress `json:"email,omitempty"` + IsEmailVerified bool `json:"is_email_verified,omitempty"` + Phone domain.PhoneNumber `json:"phone,omitempty"` + IsPhoneVerified bool `json:"is_phone_verified,omitempty"` } type Profile struct { @@ -92,10 +89,10 @@ type Phone struct { } type Machine struct { - Name string - Description string - HasSecret bool - AccessTokenType domain.OIDCTokenType + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + HasSecret bool `json:"has_secret,omitempty"` + AccessTokenType domain.OIDCTokenType `json:"access_token_type,omitempty"` } type NotifyUser struct { @@ -170,10 +167,6 @@ var ( name: projection.UserTypeCol, table: userTable, } - UserOwnerRemovedCol = Column{ - name: projection.UserOwnerRemovedCol, - table: userTable, - } userLoginNamesTable = loginNameTable.setAlias("login_names") userLoginNamesUserIDCol = LoginNameUserIDCol.setTable(userLoginNamesTable) @@ -320,11 +313,7 @@ var ( } ) -func addUserWithoutOwnerRemoved(eq map[string]interface{}) { - eq[UserOwnerRemovedCol.identifier()] = false -} - -func (q *Queries) GetUserByID(ctx context.Context, shouldTriggerBulk bool, userID string, withOwnerRemoved bool, queries ...SearchQuery) (user *User, err error) { +func (q *Queries) GetUserByID(ctx context.Context, shouldTriggerBulk bool, userID string, queries ...SearchQuery) (user *User, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -340,9 +329,6 @@ func (q *Queries) GetUserByID(ctx context.Context, shouldTriggerBulk bool, userI UserIDCol.identifier(): userID, UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - addUserWithoutOwnerRemoved(eq) - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-FBg21", "Errors.Query.SQLStatment") @@ -355,7 +341,7 @@ func (q *Queries) GetUserByID(ctx context.Context, shouldTriggerBulk bool, userI return user, err } -func (q *Queries) GetUser(ctx context.Context, shouldTriggerBulk bool, withOwnerRemoved bool, queries ...SearchQuery) (user *User, err error) { +func (q *Queries) GetUser(ctx context.Context, shouldTriggerBulk bool, queries ...SearchQuery) (user *User, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -370,9 +356,6 @@ func (q *Queries) GetUser(ctx context.Context, shouldTriggerBulk bool, withOwner eq := sq.Eq{ UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - addUserWithoutOwnerRemoved(eq) - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-Dnhr2", "Errors.Query.SQLStatment") @@ -385,7 +368,7 @@ func (q *Queries) GetUser(ctx context.Context, shouldTriggerBulk bool, withOwner return user, err } -func (q *Queries) GetHumanProfile(ctx context.Context, userID string, withOwnerRemoved bool, queries ...SearchQuery) (profile *Profile, err error) { +func (q *Queries) GetHumanProfile(ctx context.Context, userID string, queries ...SearchQuery) (profile *Profile, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -397,9 +380,6 @@ func (q *Queries) GetHumanProfile(ctx context.Context, userID string, withOwnerR UserIDCol.identifier(): userID, UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[UserOwnerRemovedCol.identifier()] = false - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-Dgbg2", "Errors.Query.SQLStatment") @@ -412,7 +392,7 @@ func (q *Queries) GetHumanProfile(ctx context.Context, userID string, withOwnerR return profile, err } -func (q *Queries) GetHumanEmail(ctx context.Context, userID string, withOwnerRemoved bool, queries ...SearchQuery) (email *Email, err error) { +func (q *Queries) GetHumanEmail(ctx context.Context, userID string, queries ...SearchQuery) (email *Email, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -424,9 +404,6 @@ func (q *Queries) GetHumanEmail(ctx context.Context, userID string, withOwnerRem UserIDCol.identifier(): userID, UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[UserOwnerRemovedCol.identifier()] = false - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-BHhj3", "Errors.Query.SQLStatment") @@ -439,7 +416,7 @@ func (q *Queries) GetHumanEmail(ctx context.Context, userID string, withOwnerRem return email, err } -func (q *Queries) GetHumanPhone(ctx context.Context, userID string, withOwnerRemoved bool, queries ...SearchQuery) (phone *Phone, err error) { +func (q *Queries) GetHumanPhone(ctx context.Context, userID string, queries ...SearchQuery) (phone *Phone, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -451,9 +428,6 @@ func (q *Queries) GetHumanPhone(ctx context.Context, userID string, withOwnerRem UserIDCol.identifier(): userID, UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[UserOwnerRemovedCol.identifier()] = false - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-Dg43g", "Errors.Query.SQLStatment") @@ -466,7 +440,7 @@ func (q *Queries) GetHumanPhone(ctx context.Context, userID string, withOwnerRem return phone, err } -func (q *Queries) GetNotifyUserByID(ctx context.Context, shouldTriggered bool, userID string, withOwnerRemoved bool, queries ...SearchQuery) (user *NotifyUser, err error) { +func (q *Queries) GetNotifyUserByID(ctx context.Context, shouldTriggered bool, userID string, queries ...SearchQuery) (user *NotifyUser, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -482,9 +456,6 @@ func (q *Queries) GetNotifyUserByID(ctx context.Context, shouldTriggered bool, u UserIDCol.identifier(): userID, UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - addUserWithoutOwnerRemoved(eq) - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-Err3g", "Errors.Query.SQLStatment") @@ -497,7 +468,7 @@ func (q *Queries) GetNotifyUserByID(ctx context.Context, shouldTriggered bool, u return user, err } -func (q *Queries) GetNotifyUser(ctx context.Context, shouldTriggered bool, withOwnerRemoved bool, queries ...SearchQuery) (user *NotifyUser, err error) { +func (q *Queries) GetNotifyUser(ctx context.Context, shouldTriggered bool, queries ...SearchQuery) (user *NotifyUser, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -512,9 +483,6 @@ func (q *Queries) GetNotifyUser(ctx context.Context, shouldTriggered bool, withO eq := sq.Eq{ UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - addUserWithoutOwnerRemoved(eq) - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-Err3g", "Errors.Query.SQLStatment") @@ -527,15 +495,12 @@ func (q *Queries) GetNotifyUser(ctx context.Context, shouldTriggered bool, withO return user, err } -func (q *Queries) SearchUsers(ctx context.Context, queries *UserSearchQueries, withOwnerRemoved bool) (users *Users, err error) { +func (q *Queries) SearchUsers(ctx context.Context, queries *UserSearchQueries) (users *Users, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() query, scan := prepareUsersQuery(ctx, q.client) eq := sq.Eq{UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID()} - if !withOwnerRemoved { - addUserWithoutOwnerRemoved(eq) - } stmt, args, err := queries.toQuery(query).Where(eq). ToSql() if err != nil { @@ -554,7 +519,7 @@ func (q *Queries) SearchUsers(ctx context.Context, queries *UserSearchQueries, w return users, err } -func (q *Queries) IsUserUnique(ctx context.Context, username, email, resourceOwner string, withOwnerRemoved bool) (isUnique bool, err error) { +func (q *Queries) IsUserUnique(ctx context.Context, username, email, resourceOwner string) (isUnique bool, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -585,9 +550,6 @@ func (q *Queries) IsUserUnique(ctx context.Context, username, email, resourceOwn query = q.toQuery(query) } eq := sq.Eq{UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID()} - if !withOwnerRemoved { - eq[UserOwnerRemovedCol.identifier()] = false - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return false, errors.ThrowInternal(err, "QUERY-Dg43g", "Errors.Query.SQLStatment") @@ -715,23 +677,7 @@ func NewUserLoginNameExistsQuery(value string, comparison TextComparison) (Searc } func triggerUserProjections(ctx context.Context) { - wg := sync.WaitGroup{} - wg.Add(2) - func() { - _, traceSpan := tracing.NewNamedSpan(ctx, "TriggerUserProjection") - _, err := projection.UserProjection.Trigger(ctx, handler.WithAwaitRunning()) - logging.OnError(err).Debug("trigger failed") - traceSpan.EndWithError(err) - wg.Done() - }() - func() { - _, traceSpan := tracing.NewNamedSpan(ctx, "TriggerLoginNameProjection") - _, err := projection.LoginNameProjection.Trigger(ctx, handler.WithAwaitRunning()) - traceSpan.EndWithError(err) - logging.OnError(err).Debug("trigger failed") - wg.Done() - }() - wg.Wait() + triggerBatch(ctx, projection.UserProjection, projection.LoginNameProjection) } func prepareLoginNamesQuery() (string, []interface{}, error) { diff --git a/internal/query/user_auth_method.go b/internal/query/user_auth_method.go index 9568179775..9341c13e6f 100644 --- a/internal/query/user_auth_method.go +++ b/internal/query/user_auth_method.go @@ -141,7 +141,7 @@ func (q *Queries) SearchUserAuthMethods(ctx context.Context, queries *UserAuthMe return userAuthMethods, err } -func (q *Queries) ListActiveUserAuthMethodTypes(ctx context.Context, userID string, withOwnerRemoved bool) (userAuthMethodTypes *AuthMethodTypes, err error) { +func (q *Queries) ListActiveUserAuthMethodTypes(ctx context.Context, userID string) (userAuthMethodTypes *AuthMethodTypes, err error) { ctxData := authz.GetCtxData(ctx) if ctxData.UserID != userID { if err := q.checkPermission(ctx, domain.PermissionUserRead, ctxData.OrgID, userID); err != nil { @@ -156,9 +156,6 @@ func (q *Queries) ListActiveUserAuthMethodTypes(ctx context.Context, userID stri UserIDCol.identifier(): userID, UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[UserOwnerRemovedCol.identifier()] = false - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInvalidArgument(err, "QUERY-Sfdrg", "Errors.Query.InvalidRequest") @@ -175,7 +172,7 @@ func (q *Queries) ListActiveUserAuthMethodTypes(ctx context.Context, userID stri return userAuthMethodTypes, err } -func (q *Queries) ListUserAuthMethodTypesRequired(ctx context.Context, userID string, withOwnerRemoved bool) (userAuthMethodTypes []domain.UserAuthMethodType, forceMFA, forceMFALocalOnly bool, err error) { +func (q *Queries) ListUserAuthMethodTypesRequired(ctx context.Context, userID string) (userAuthMethodTypes []domain.UserAuthMethodType, forceMFA, forceMFALocalOnly bool, err error) { ctxData := authz.GetCtxData(ctx) if ctxData.UserID != userID { if err := q.checkPermission(ctx, domain.PermissionUserRead, ctxData.OrgID, userID); err != nil { @@ -190,9 +187,6 @@ func (q *Queries) ListUserAuthMethodTypesRequired(ctx context.Context, userID st UserIDCol.identifier(): userID, UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[UserOwnerRemovedCol.identifier()] = false - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, false, false, errors.ThrowInvalidArgument(err, "QUERY-E5ut4", "Errors.Query.InvalidRequest") diff --git a/internal/query/user_auth_method_test.go b/internal/query/user_auth_method_test.go index 978e0e81a2..a9aae1d1c5 100644 --- a/internal/query/user_auth_method_test.go +++ b/internal/query/user_auth_method_test.go @@ -39,38 +39,38 @@ var ( "method_type", "count", } - prepareActiveAuthMethodTypesStmt = `SELECT projections.users8_notifications.password_set,` + + prepareActiveAuthMethodTypesStmt = `SELECT projections.users9_notifications.password_set,` + ` auth_method_types.method_type,` + ` user_idps_count.count` + - ` FROM projections.users8` + - ` LEFT JOIN projections.users8_notifications ON projections.users8.id = projections.users8_notifications.user_id AND projections.users8.instance_id = projections.users8_notifications.instance_id` + + ` FROM projections.users9` + + ` LEFT JOIN projections.users9_notifications ON projections.users9.id = projections.users9_notifications.user_id AND projections.users9.instance_id = projections.users9_notifications.instance_id` + ` LEFT JOIN (SELECT DISTINCT(auth_method_types.method_type), auth_method_types.user_id, auth_method_types.instance_id FROM projections.user_auth_methods4 AS auth_method_types` + ` WHERE auth_method_types.state = $1) AS auth_method_types` + - ` ON auth_method_types.user_id = projections.users8.id AND auth_method_types.instance_id = projections.users8.instance_id` + + ` ON auth_method_types.user_id = projections.users9.id AND auth_method_types.instance_id = projections.users9.instance_id` + ` LEFT JOIN (SELECT user_idps_count.user_id, user_idps_count.instance_id, COUNT(user_idps_count.user_id) AS count FROM projections.idp_user_links3 AS user_idps_count` + ` GROUP BY user_idps_count.user_id, user_idps_count.instance_id) AS user_idps_count` + - ` ON user_idps_count.user_id = projections.users8.id AND user_idps_count.instance_id = projections.users8.instance_id` + + ` ON user_idps_count.user_id = projections.users9.id AND user_idps_count.instance_id = projections.users9.instance_id` + ` AS OF SYSTEM TIME '-1 ms` prepareActiveAuthMethodTypesCols = []string{ "password_set", "method_type", "idps_count", } - prepareAuthMethodTypesRequiredStmt = `SELECT projections.users8_notifications.password_set,` + + prepareAuthMethodTypesRequiredStmt = `SELECT projections.users9_notifications.password_set,` + ` auth_method_types.method_type,` + ` user_idps_count.count,` + ` auth_methods_force_mfa.force_mfa,` + ` auth_methods_force_mfa.force_mfa_local_only` + - ` FROM projections.users8` + - ` LEFT JOIN projections.users8_notifications ON projections.users8.id = projections.users8_notifications.user_id AND projections.users8.instance_id = projections.users8_notifications.instance_id` + + ` FROM projections.users9` + + ` LEFT JOIN projections.users9_notifications ON projections.users9.id = projections.users9_notifications.user_id AND projections.users9.instance_id = projections.users9_notifications.instance_id` + ` LEFT JOIN (SELECT DISTINCT(auth_method_types.method_type), auth_method_types.user_id, auth_method_types.instance_id FROM projections.user_auth_methods4 AS auth_method_types` + ` WHERE auth_method_types.state = $1) AS auth_method_types` + - ` ON auth_method_types.user_id = projections.users8.id AND auth_method_types.instance_id = projections.users8.instance_id` + + ` ON auth_method_types.user_id = projections.users9.id AND auth_method_types.instance_id = projections.users9.instance_id` + ` LEFT JOIN (SELECT user_idps_count.user_id, user_idps_count.instance_id, COUNT(user_idps_count.user_id) AS count FROM projections.idp_user_links3 AS user_idps_count` + ` GROUP BY user_idps_count.user_id, user_idps_count.instance_id) AS user_idps_count` + - ` ON user_idps_count.user_id = projections.users8.id AND user_idps_count.instance_id = projections.users8.instance_id` + + ` ON user_idps_count.user_id = projections.users9.id AND user_idps_count.instance_id = projections.users9.instance_id` + ` LEFT JOIN (SELECT auth_methods_force_mfa.force_mfa, auth_methods_force_mfa.force_mfa_local_only, auth_methods_force_mfa.instance_id, auth_methods_force_mfa.aggregate_id FROM projections.login_policies5 AS auth_methods_force_mfa ORDER BY auth_methods_force_mfa.is_default) AS auth_methods_force_mfa` + - ` ON (auth_methods_force_mfa.aggregate_id = projections.users8.instance_id OR auth_methods_force_mfa.aggregate_id = projections.users8.resource_owner) AND auth_methods_force_mfa.instance_id = projections.users8.instance_id` + + ` ON (auth_methods_force_mfa.aggregate_id = projections.users9.instance_id OR auth_methods_force_mfa.aggregate_id = projections.users9.resource_owner) AND auth_methods_force_mfa.instance_id = projections.users9.instance_id` + ` AS OF SYSTEM TIME '-1 ms ` prepareAuthMethodTypesRequiredCols = []string{ diff --git a/internal/query/user_grant.go b/internal/query/user_grant.go index ef97705144..170084d899 100644 --- a/internal/query/user_grant.go +++ b/internal/query/user_grant.go @@ -22,32 +22,32 @@ import ( type UserGrant struct { // ID represents the aggregate id (id of the user grant) - ID string - CreationDate time.Time - ChangeDate time.Time - Sequence uint64 - Roles database.TextArray[string] + ID string `json:"id,omitempty"` + CreationDate time.Time `json:"creation_date,omitempty"` + ChangeDate time.Time `json:"change_date,omitempty"` + Sequence uint64 `json:"sequence,omitempty"` + Roles database.TextArray[string] `json:"roles,omitempty"` // GrantID represents the project grant id - GrantID string - State domain.UserGrantState + GrantID string `json:"grant_id,omitempty"` + State domain.UserGrantState `json:"state,omitempty"` - UserID string - Username string - UserType domain.UserType - UserResourceOwner string - FirstName string - LastName string - Email string - DisplayName string - AvatarURL string - PreferredLoginName string + UserID string `json:"user_id,omitempty"` + Username string `json:"username,omitempty"` + UserType domain.UserType `json:"user_type,omitempty"` + UserResourceOwner string `json:"user_resource_owner,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + Email string `json:"email,omitempty"` + DisplayName string `json:"display_name,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + PreferredLoginName string `json:"preferred_login_name,omitempty"` - ResourceOwner string - OrgName string - OrgPrimaryDomain string + ResourceOwner string `json:"resource_owner,omitempty"` + OrgName string `json:"org_name,omitempty"` + OrgPrimaryDomain string `json:"org_primary_domain,omitempty"` - ProjectID string - ProjectName string + ProjectID string `json:"project_id,omitempty"` + ProjectName string `json:"project_name,omitempty"` } type UserGrants struct { diff --git a/internal/query/user_grant_test.go b/internal/query/user_grant_test.go index f17ef0e2bf..962c6a1ed6 100644 --- a/internal/query/user_grant_test.go +++ b/internal/query/user_grant_test.go @@ -23,14 +23,14 @@ var ( ", projections.user_grants3.roles" + ", projections.user_grants3.state" + ", projections.user_grants3.user_id" + - ", projections.users8.username" + - ", projections.users8.type" + - ", projections.users8.resource_owner" + - ", projections.users8_humans.first_name" + - ", projections.users8_humans.last_name" + - ", projections.users8_humans.email" + - ", projections.users8_humans.display_name" + - ", projections.users8_humans.avatar_key" + + ", projections.users9.username" + + ", projections.users9.type" + + ", projections.users9.resource_owner" + + ", projections.users9_humans.first_name" + + ", projections.users9_humans.last_name" + + ", projections.users9_humans.email" + + ", projections.users9_humans.display_name" + + ", projections.users9_humans.avatar_key" + ", projections.login_names3.login_name" + ", projections.user_grants3.resource_owner" + ", projections.orgs1.name" + @@ -38,8 +38,8 @@ var ( ", projections.user_grants3.project_id" + ", projections.projects4.name" + " FROM projections.user_grants3" + - " LEFT JOIN projections.users8 ON projections.user_grants3.user_id = projections.users8.id AND projections.user_grants3.instance_id = projections.users8.instance_id" + - " LEFT JOIN projections.users8_humans ON projections.user_grants3.user_id = projections.users8_humans.user_id AND projections.user_grants3.instance_id = projections.users8_humans.instance_id" + + " LEFT JOIN projections.users9 ON projections.user_grants3.user_id = projections.users9.id AND projections.user_grants3.instance_id = projections.users9.instance_id" + + " LEFT JOIN projections.users9_humans ON projections.user_grants3.user_id = projections.users9_humans.user_id AND projections.user_grants3.instance_id = projections.users9_humans.instance_id" + " LEFT JOIN projections.orgs1 ON projections.user_grants3.resource_owner = projections.orgs1.id AND projections.user_grants3.instance_id = projections.orgs1.instance_id" + " LEFT JOIN projections.projects4 ON projections.user_grants3.project_id = projections.projects4.id AND projections.user_grants3.instance_id = projections.projects4.instance_id" + " LEFT JOIN projections.login_names3 ON projections.user_grants3.user_id = projections.login_names3.user_id AND projections.user_grants3.instance_id = projections.login_names3.instance_id" + @@ -78,14 +78,14 @@ var ( ", projections.user_grants3.roles" + ", projections.user_grants3.state" + ", projections.user_grants3.user_id" + - ", projections.users8.username" + - ", projections.users8.type" + - ", projections.users8.resource_owner" + - ", projections.users8_humans.first_name" + - ", projections.users8_humans.last_name" + - ", projections.users8_humans.email" + - ", projections.users8_humans.display_name" + - ", projections.users8_humans.avatar_key" + + ", projections.users9.username" + + ", projections.users9.type" + + ", projections.users9.resource_owner" + + ", projections.users9_humans.first_name" + + ", projections.users9_humans.last_name" + + ", projections.users9_humans.email" + + ", projections.users9_humans.display_name" + + ", projections.users9_humans.avatar_key" + ", projections.login_names3.login_name" + ", projections.user_grants3.resource_owner" + ", projections.orgs1.name" + @@ -94,8 +94,8 @@ var ( ", projections.projects4.name" + ", COUNT(*) OVER ()" + " FROM projections.user_grants3" + - " LEFT JOIN projections.users8 ON projections.user_grants3.user_id = projections.users8.id AND projections.user_grants3.instance_id = projections.users8.instance_id" + - " LEFT JOIN projections.users8_humans ON projections.user_grants3.user_id = projections.users8_humans.user_id AND projections.user_grants3.instance_id = projections.users8_humans.instance_id" + + " LEFT JOIN projections.users9 ON projections.user_grants3.user_id = projections.users9.id AND projections.user_grants3.instance_id = projections.users9.instance_id" + + " LEFT JOIN projections.users9_humans ON projections.user_grants3.user_id = projections.users9_humans.user_id AND projections.user_grants3.instance_id = projections.users9_humans.instance_id" + " LEFT JOIN projections.orgs1 ON projections.user_grants3.resource_owner = projections.orgs1.id AND projections.user_grants3.instance_id = projections.orgs1.instance_id" + " LEFT JOIN projections.projects4 ON projections.user_grants3.project_id = projections.projects4.id AND projections.user_grants3.instance_id = projections.projects4.instance_id" + " LEFT JOIN projections.login_names3 ON projections.user_grants3.user_id = projections.login_names3.user_id AND projections.user_grants3.instance_id = projections.login_names3.instance_id" + diff --git a/internal/query/user_metadata.go b/internal/query/user_metadata.go index 68cf65aa6a..a4f0fd2e9f 100644 --- a/internal/query/user_metadata.go +++ b/internal/query/user_metadata.go @@ -24,12 +24,12 @@ type UserMetadataList struct { } type UserMetadata struct { - CreationDate time.Time - ChangeDate time.Time - ResourceOwner string - Sequence uint64 - Key string - Value []byte + CreationDate time.Time `json:"creation_date,omitempty"` + ChangeDate time.Time `json:"change_date,omitempty"` + ResourceOwner string `json:"resource_owner,omitempty"` + Sequence uint64 `json:"sequence,omitempty"` + Key string `json:"key,omitempty"` + Value []byte `json:"value,omitempty"` } type UserMetadataSearchQueries struct { @@ -74,10 +74,6 @@ var ( name: projection.UserMetadataColumnValue, table: userMetadataTable, } - UserMetadataOwnerRemovedCol = Column{ - name: projection.UserMetadataColumnOwnerRemoved, - table: userMetadataTable, - } ) func (q *Queries) GetUserMetadataByKey(ctx context.Context, shouldTriggerBulk bool, userID, key string, withOwnerRemoved bool, queries ...SearchQuery) (metadata *UserMetadata, err error) { @@ -100,9 +96,6 @@ func (q *Queries) GetUserMetadataByKey(ctx context.Context, shouldTriggerBulk bo UserMetadataKeyCol.identifier(): key, UserMetadataInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[UserMetadataOwnerRemovedCol.identifier()] = false - } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-aDGG2", "Errors.Query.SQLStatment") @@ -131,9 +124,6 @@ func (q *Queries) SearchUserMetadata(ctx context.Context, shouldTriggerBulk bool UserMetadataUserIDCol.identifier(): userID, UserMetadataInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(), } - if !withOwnerRemoved { - eq[UserMetadataOwnerRemovedCol.identifier()] = false - } stmt, args, err := queries.toQuery(query).Where(eq).ToSql() if err != nil { return nil, errors.ThrowInternal(err, "QUERY-Egbgd", "Errors.Query.SQLStatment") diff --git a/internal/query/user_metadata_test.go b/internal/query/user_metadata_test.go index 62f7cfb6a3..1fbd04cc7b 100644 --- a/internal/query/user_metadata_test.go +++ b/internal/query/user_metadata_test.go @@ -12,13 +12,13 @@ import ( ) var ( - userMetadataQuery = `SELECT projections.user_metadata4.creation_date,` + - ` projections.user_metadata4.change_date,` + - ` projections.user_metadata4.resource_owner,` + - ` projections.user_metadata4.sequence,` + - ` projections.user_metadata4.key,` + - ` projections.user_metadata4.value` + - ` FROM projections.user_metadata4` + + userMetadataQuery = `SELECT projections.user_metadata5.creation_date,` + + ` projections.user_metadata5.change_date,` + + ` projections.user_metadata5.resource_owner,` + + ` projections.user_metadata5.sequence,` + + ` projections.user_metadata5.key,` + + ` projections.user_metadata5.value` + + ` FROM projections.user_metadata5` + ` AS OF SYSTEM TIME '-1 ms'` userMetadataCols = []string{ "creation_date", @@ -28,14 +28,14 @@ var ( "key", "value", } - userMetadataListQuery = `SELECT projections.user_metadata4.creation_date,` + - ` projections.user_metadata4.change_date,` + - ` projections.user_metadata4.resource_owner,` + - ` projections.user_metadata4.sequence,` + - ` projections.user_metadata4.key,` + - ` projections.user_metadata4.value,` + + userMetadataListQuery = `SELECT projections.user_metadata5.creation_date,` + + ` projections.user_metadata5.change_date,` + + ` projections.user_metadata5.resource_owner,` + + ` projections.user_metadata5.sequence,` + + ` projections.user_metadata5.key,` + + ` projections.user_metadata5.value,` + ` COUNT(*) OVER ()` + - ` FROM projections.user_metadata4` + ` FROM projections.user_metadata5` userMetadataListCols = []string{ "creation_date", "change_date", diff --git a/internal/query/user_test.go b/internal/query/user_test.go index 55899ba748..1076dd8729 100644 --- a/internal/query/user_test.go +++ b/internal/query/user_test.go @@ -22,43 +22,43 @@ var ( preferredLoginNameQuery = `SELECT preferred_login_name.user_id, preferred_login_name.login_name, preferred_login_name.instance_id` + ` FROM projections.login_names3 AS preferred_login_name` + ` WHERE preferred_login_name.is_primary = $1` - userQuery = `SELECT projections.users8.id,` + - ` projections.users8.creation_date,` + - ` projections.users8.change_date,` + - ` projections.users8.resource_owner,` + - ` projections.users8.sequence,` + - ` projections.users8.state,` + - ` projections.users8.type,` + - ` projections.users8.username,` + + userQuery = `SELECT projections.users9.id,` + + ` projections.users9.creation_date,` + + ` projections.users9.change_date,` + + ` projections.users9.resource_owner,` + + ` projections.users9.sequence,` + + ` projections.users9.state,` + + ` projections.users9.type,` + + ` projections.users9.username,` + ` login_names.loginnames,` + ` preferred_login_name.login_name,` + - ` projections.users8_humans.user_id,` + - ` projections.users8_humans.first_name,` + - ` projections.users8_humans.last_name,` + - ` projections.users8_humans.nick_name,` + - ` projections.users8_humans.display_name,` + - ` projections.users8_humans.preferred_language,` + - ` projections.users8_humans.gender,` + - ` projections.users8_humans.avatar_key,` + - ` projections.users8_humans.email,` + - ` projections.users8_humans.is_email_verified,` + - ` projections.users8_humans.phone,` + - ` projections.users8_humans.is_phone_verified,` + - ` projections.users8_machines.user_id,` + - ` projections.users8_machines.name,` + - ` projections.users8_machines.description,` + - ` projections.users8_machines.has_secret,` + - ` projections.users8_machines.access_token_type,` + + ` projections.users9_humans.user_id,` + + ` projections.users9_humans.first_name,` + + ` projections.users9_humans.last_name,` + + ` projections.users9_humans.nick_name,` + + ` projections.users9_humans.display_name,` + + ` projections.users9_humans.preferred_language,` + + ` projections.users9_humans.gender,` + + ` projections.users9_humans.avatar_key,` + + ` projections.users9_humans.email,` + + ` projections.users9_humans.is_email_verified,` + + ` projections.users9_humans.phone,` + + ` projections.users9_humans.is_phone_verified,` + + ` projections.users9_machines.user_id,` + + ` projections.users9_machines.name,` + + ` projections.users9_machines.description,` + + ` projections.users9_machines.has_secret,` + + ` projections.users9_machines.access_token_type,` + ` COUNT(*) OVER ()` + - ` FROM projections.users8` + - ` LEFT JOIN projections.users8_humans ON projections.users8.id = projections.users8_humans.user_id AND projections.users8.instance_id = projections.users8_humans.instance_id` + - ` LEFT JOIN projections.users8_machines ON projections.users8.id = projections.users8_machines.user_id AND projections.users8.instance_id = projections.users8_machines.instance_id` + + ` FROM projections.users9` + + ` LEFT JOIN projections.users9_humans ON projections.users9.id = projections.users9_humans.user_id AND projections.users9.instance_id = projections.users9_humans.instance_id` + + ` LEFT JOIN projections.users9_machines ON projections.users9.id = projections.users9_machines.user_id AND projections.users9.instance_id = projections.users9_machines.instance_id` + ` LEFT JOIN` + ` (` + loginNamesQuery + `) AS login_names` + - ` ON login_names.user_id = projections.users8.id AND login_names.instance_id = projections.users8.instance_id` + + ` ON login_names.user_id = projections.users9.id AND login_names.instance_id = projections.users9.instance_id` + ` LEFT JOIN` + ` (` + preferredLoginNameQuery + `) AS preferred_login_name` + - ` ON preferred_login_name.user_id = projections.users8.id AND preferred_login_name.instance_id = projections.users8.instance_id` + + ` ON preferred_login_name.user_id = projections.users9.id AND preferred_login_name.instance_id = projections.users9.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` userCols = []string{ "id", @@ -92,21 +92,21 @@ var ( "access_token_type", "count", } - profileQuery = `SELECT projections.users8.id,` + - ` projections.users8.creation_date,` + - ` projections.users8.change_date,` + - ` projections.users8.resource_owner,` + - ` projections.users8.sequence,` + - ` projections.users8_humans.user_id,` + - ` projections.users8_humans.first_name,` + - ` projections.users8_humans.last_name,` + - ` projections.users8_humans.nick_name,` + - ` projections.users8_humans.display_name,` + - ` projections.users8_humans.preferred_language,` + - ` projections.users8_humans.gender,` + - ` projections.users8_humans.avatar_key` + - ` FROM projections.users8` + - ` LEFT JOIN projections.users8_humans ON projections.users8.id = projections.users8_humans.user_id AND projections.users8.instance_id = projections.users8_humans.instance_id` + + profileQuery = `SELECT projections.users9.id,` + + ` projections.users9.creation_date,` + + ` projections.users9.change_date,` + + ` projections.users9.resource_owner,` + + ` projections.users9.sequence,` + + ` projections.users9_humans.user_id,` + + ` projections.users9_humans.first_name,` + + ` projections.users9_humans.last_name,` + + ` projections.users9_humans.nick_name,` + + ` projections.users9_humans.display_name,` + + ` projections.users9_humans.preferred_language,` + + ` projections.users9_humans.gender,` + + ` projections.users9_humans.avatar_key` + + ` FROM projections.users9` + + ` LEFT JOIN projections.users9_humans ON projections.users9.id = projections.users9_humans.user_id AND projections.users9.instance_id = projections.users9_humans.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` profileCols = []string{ "id", @@ -123,16 +123,16 @@ var ( "gender", "avatar_key", } - emailQuery = `SELECT projections.users8.id,` + - ` projections.users8.creation_date,` + - ` projections.users8.change_date,` + - ` projections.users8.resource_owner,` + - ` projections.users8.sequence,` + - ` projections.users8_humans.user_id,` + - ` projections.users8_humans.email,` + - ` projections.users8_humans.is_email_verified` + - ` FROM projections.users8` + - ` LEFT JOIN projections.users8_humans ON projections.users8.id = projections.users8_humans.user_id AND projections.users8.instance_id = projections.users8_humans.instance_id` + + emailQuery = `SELECT projections.users9.id,` + + ` projections.users9.creation_date,` + + ` projections.users9.change_date,` + + ` projections.users9.resource_owner,` + + ` projections.users9.sequence,` + + ` projections.users9_humans.user_id,` + + ` projections.users9_humans.email,` + + ` projections.users9_humans.is_email_verified` + + ` FROM projections.users9` + + ` LEFT JOIN projections.users9_humans ON projections.users9.id = projections.users9_humans.user_id AND projections.users9.instance_id = projections.users9_humans.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` emailCols = []string{ "id", @@ -144,16 +144,16 @@ var ( "email", "is_email_verified", } - phoneQuery = `SELECT projections.users8.id,` + - ` projections.users8.creation_date,` + - ` projections.users8.change_date,` + - ` projections.users8.resource_owner,` + - ` projections.users8.sequence,` + - ` projections.users8_humans.user_id,` + - ` projections.users8_humans.phone,` + - ` projections.users8_humans.is_phone_verified` + - ` FROM projections.users8` + - ` LEFT JOIN projections.users8_humans ON projections.users8.id = projections.users8_humans.user_id AND projections.users8.instance_id = projections.users8_humans.instance_id` + + phoneQuery = `SELECT projections.users9.id,` + + ` projections.users9.creation_date,` + + ` projections.users9.change_date,` + + ` projections.users9.resource_owner,` + + ` projections.users9.sequence,` + + ` projections.users9_humans.user_id,` + + ` projections.users9_humans.phone,` + + ` projections.users9_humans.is_phone_verified` + + ` FROM projections.users9` + + ` LEFT JOIN projections.users9_humans ON projections.users9.id = projections.users9_humans.user_id AND projections.users9.instance_id = projections.users9_humans.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` phoneCols = []string{ "id", @@ -165,14 +165,14 @@ var ( "phone", "is_phone_verified", } - userUniqueQuery = `SELECT projections.users8.id,` + - ` projections.users8.state,` + - ` projections.users8.username,` + - ` projections.users8_humans.user_id,` + - ` projections.users8_humans.email,` + - ` projections.users8_humans.is_email_verified` + - ` FROM projections.users8` + - ` LEFT JOIN projections.users8_humans ON projections.users8.id = projections.users8_humans.user_id AND projections.users8.instance_id = projections.users8_humans.instance_id` + + userUniqueQuery = `SELECT projections.users9.id,` + + ` projections.users9.state,` + + ` projections.users9.username,` + + ` projections.users9_humans.user_id,` + + ` projections.users9_humans.email,` + + ` projections.users9_humans.is_email_verified` + + ` FROM projections.users9` + + ` LEFT JOIN projections.users9_humans ON projections.users9.id = projections.users9_humans.user_id AND projections.users9.instance_id = projections.users9_humans.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` userUniqueCols = []string{ "id", @@ -182,40 +182,40 @@ var ( "email", "is_email_verified", } - notifyUserQuery = `SELECT projections.users8.id,` + - ` projections.users8.creation_date,` + - ` projections.users8.change_date,` + - ` projections.users8.resource_owner,` + - ` projections.users8.sequence,` + - ` projections.users8.state,` + - ` projections.users8.type,` + - ` projections.users8.username,` + + notifyUserQuery = `SELECT projections.users9.id,` + + ` projections.users9.creation_date,` + + ` projections.users9.change_date,` + + ` projections.users9.resource_owner,` + + ` projections.users9.sequence,` + + ` projections.users9.state,` + + ` projections.users9.type,` + + ` projections.users9.username,` + ` login_names.loginnames,` + ` preferred_login_name.login_name,` + - ` projections.users8_humans.user_id,` + - ` projections.users8_humans.first_name,` + - ` projections.users8_humans.last_name,` + - ` projections.users8_humans.nick_name,` + - ` projections.users8_humans.display_name,` + - ` projections.users8_humans.preferred_language,` + - ` projections.users8_humans.gender,` + - ` projections.users8_humans.avatar_key,` + - ` projections.users8_notifications.user_id,` + - ` projections.users8_notifications.last_email,` + - ` projections.users8_notifications.verified_email,` + - ` projections.users8_notifications.last_phone,` + - ` projections.users8_notifications.verified_phone,` + - ` projections.users8_notifications.password_set,` + + ` projections.users9_humans.user_id,` + + ` projections.users9_humans.first_name,` + + ` projections.users9_humans.last_name,` + + ` projections.users9_humans.nick_name,` + + ` projections.users9_humans.display_name,` + + ` projections.users9_humans.preferred_language,` + + ` projections.users9_humans.gender,` + + ` projections.users9_humans.avatar_key,` + + ` projections.users9_notifications.user_id,` + + ` projections.users9_notifications.last_email,` + + ` projections.users9_notifications.verified_email,` + + ` projections.users9_notifications.last_phone,` + + ` projections.users9_notifications.verified_phone,` + + ` projections.users9_notifications.password_set,` + ` COUNT(*) OVER ()` + - ` FROM projections.users8` + - ` LEFT JOIN projections.users8_humans ON projections.users8.id = projections.users8_humans.user_id AND projections.users8.instance_id = projections.users8_humans.instance_id` + - ` LEFT JOIN projections.users8_notifications ON projections.users8.id = projections.users8_notifications.user_id AND projections.users8.instance_id = projections.users8_notifications.instance_id` + + ` FROM projections.users9` + + ` LEFT JOIN projections.users9_humans ON projections.users9.id = projections.users9_humans.user_id AND projections.users9.instance_id = projections.users9_humans.instance_id` + + ` LEFT JOIN projections.users9_notifications ON projections.users9.id = projections.users9_notifications.user_id AND projections.users9.instance_id = projections.users9_notifications.instance_id` + ` LEFT JOIN` + ` (` + loginNamesQuery + `) AS login_names` + - ` ON login_names.user_id = projections.users8.id AND login_names.instance_id = projections.users8.instance_id` + + ` ON login_names.user_id = projections.users9.id AND login_names.instance_id = projections.users9.instance_id` + ` LEFT JOIN` + ` (` + preferredLoginNameQuery + `) AS preferred_login_name` + - ` ON preferred_login_name.user_id = projections.users8.id AND preferred_login_name.instance_id = projections.users8.instance_id` + + ` ON preferred_login_name.user_id = projections.users9.id AND preferred_login_name.instance_id = projections.users9.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` notifyUserCols = []string{ "id", @@ -246,43 +246,43 @@ var ( "password_set", "count", } - usersQuery = `SELECT projections.users8.id,` + - ` projections.users8.creation_date,` + - ` projections.users8.change_date,` + - ` projections.users8.resource_owner,` + - ` projections.users8.sequence,` + - ` projections.users8.state,` + - ` projections.users8.type,` + - ` projections.users8.username,` + + usersQuery = `SELECT projections.users9.id,` + + ` projections.users9.creation_date,` + + ` projections.users9.change_date,` + + ` projections.users9.resource_owner,` + + ` projections.users9.sequence,` + + ` projections.users9.state,` + + ` projections.users9.type,` + + ` projections.users9.username,` + ` login_names.loginnames,` + ` preferred_login_name.login_name,` + - ` projections.users8_humans.user_id,` + - ` projections.users8_humans.first_name,` + - ` projections.users8_humans.last_name,` + - ` projections.users8_humans.nick_name,` + - ` projections.users8_humans.display_name,` + - ` projections.users8_humans.preferred_language,` + - ` projections.users8_humans.gender,` + - ` projections.users8_humans.avatar_key,` + - ` projections.users8_humans.email,` + - ` projections.users8_humans.is_email_verified,` + - ` projections.users8_humans.phone,` + - ` projections.users8_humans.is_phone_verified,` + - ` projections.users8_machines.user_id,` + - ` projections.users8_machines.name,` + - ` projections.users8_machines.description,` + - ` projections.users8_machines.has_secret,` + - ` projections.users8_machines.access_token_type,` + + ` projections.users9_humans.user_id,` + + ` projections.users9_humans.first_name,` + + ` projections.users9_humans.last_name,` + + ` projections.users9_humans.nick_name,` + + ` projections.users9_humans.display_name,` + + ` projections.users9_humans.preferred_language,` + + ` projections.users9_humans.gender,` + + ` projections.users9_humans.avatar_key,` + + ` projections.users9_humans.email,` + + ` projections.users9_humans.is_email_verified,` + + ` projections.users9_humans.phone,` + + ` projections.users9_humans.is_phone_verified,` + + ` projections.users9_machines.user_id,` + + ` projections.users9_machines.name,` + + ` projections.users9_machines.description,` + + ` projections.users9_machines.has_secret,` + + ` projections.users9_machines.access_token_type,` + ` COUNT(*) OVER ()` + - ` FROM projections.users8` + - ` LEFT JOIN projections.users8_humans ON projections.users8.id = projections.users8_humans.user_id AND projections.users8.instance_id = projections.users8_humans.instance_id` + - ` LEFT JOIN projections.users8_machines ON projections.users8.id = projections.users8_machines.user_id AND projections.users8.instance_id = projections.users8_machines.instance_id` + + ` FROM projections.users9` + + ` LEFT JOIN projections.users9_humans ON projections.users9.id = projections.users9_humans.user_id AND projections.users9.instance_id = projections.users9_humans.instance_id` + + ` LEFT JOIN projections.users9_machines ON projections.users9.id = projections.users9_machines.user_id AND projections.users9.instance_id = projections.users9_machines.instance_id` + ` LEFT JOIN` + ` (` + loginNamesQuery + `) AS login_names` + - ` ON login_names.user_id = projections.users8.id AND login_names.instance_id = projections.users8.instance_id` + + ` ON login_names.user_id = projections.users9.id AND login_names.instance_id = projections.users9.instance_id` + ` LEFT JOIN` + ` (` + preferredLoginNameQuery + `) AS preferred_login_name` + - ` ON preferred_login_name.user_id = projections.users8.id AND preferred_login_name.instance_id = projections.users8.instance_id` + + ` ON preferred_login_name.user_id = projections.users9.id AND preferred_login_name.instance_id = projections.users9.instance_id` + ` AS OF SYSTEM TIME '-1 ms'` usersCols = []string{ "id", diff --git a/internal/query/userinfo.go b/internal/query/userinfo.go new file mode 100644 index 0000000000..c42a893319 --- /dev/null +++ b/internal/query/userinfo.go @@ -0,0 +1,76 @@ +package query + +import ( + "context" + "database/sql" + _ "embed" + "encoding/json" + "sync" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/database" + "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/eventstore/handler/v2" + "github.com/zitadel/zitadel/internal/query/projection" + "github.com/zitadel/zitadel/internal/telemetry/tracing" +) + +// oidcUserInfoTriggerHandlers slice can only be created after zitadel +// is fully initialized, otherwise the handlers are nil. +// OnceValue takes care of creating the slice on the first request +// and than will always return the same slice on subsequent requests. +var oidcUserInfoTriggerHandlers = sync.OnceValue(func() []*handler.Handler { + return []*handler.Handler{ + projection.UserProjection, + projection.UserMetadataProjection, + projection.UserGrantProjection, + projection.OrgProjection, + projection.ProjectProjection, + } +}) + +func TriggerOIDCUserInfoProjections(ctx context.Context) { + triggerBatch(ctx, oidcUserInfoTriggerHandlers()...) +} + +//go:embed embed/userinfo_by_id.sql +var oidcUserInfoQuery string + +func (q *Queries) GetOIDCUserInfo(ctx context.Context, userID string, roleAudience []string) (_ *OIDCUserInfo, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + var data []byte + err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { + return row.Scan(&data) + }, + oidcUserInfoQuery, + userID, authz.GetInstance(ctx).InstanceID(), database.TextArray[string](roleAudience), + ) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-Oath6", "Errors.Internal") + } + + userInfo := new(OIDCUserInfo) + if err = json.Unmarshal(data, userInfo); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-Vohs6", "Errors.Internal") + } + if userInfo.User == nil { + return nil, errors.ThrowNotFound(nil, "QUERY-ahs4S", "Errors.User.NotFound") + } + + return userInfo, nil +} + +type OIDCUserInfo struct { + User *User `json:"user,omitempty"` + Metadata []UserMetadata `json:"metadata,omitempty"` + Org *UserInfoOrg `json:"org,omitempty"` + UserGrants []UserGrant `json:"user_grants,omitempty"` +} + +type UserInfoOrg struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + PrimaryDomain string `json:"primary_domain,omitempty"` +} diff --git a/internal/query/userinfo_test.go b/internal/query/userinfo_test.go new file mode 100644 index 0000000000..4edf74fb57 --- /dev/null +++ b/internal/query/userinfo_test.go @@ -0,0 +1,336 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + _ "embed" + "encoding/json" + "regexp" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/database" + "github.com/zitadel/zitadel/internal/errors" +) + +var ( + //go:embed testdata/userinfo_not_found.json + testdataUserInfoNotFound string + //go:embed testdata/userinfo_human_no_md.json + testdataUserInfoHumanNoMD string + //go:embed testdata/userinfo_human.json + testdataUserInfoHuman string + //go:embed testdata/userinfo_human_grants.json + testdataUserInfoHumanGrants string + //go:embed testdata/userinfo_machine.json + testdataUserInfoMachine string + + // timeLocation does a single parse of the testdata and extracts a time.Location, + // so that it may be used during test assertion. + // Because depending on the environment json.Unmarshal parses + // the 00:00 timezones differently. + // On my local system is parses to an empty timezone with 0 offset, + // but in github action is pares into UTC. + timeLocation = func() *time.Location { + referenceInfo := new(OIDCUserInfo) + err := json.Unmarshal([]byte(testdataUserInfoHumanNoMD), referenceInfo) + if err != nil { + panic(err) + } + return referenceInfo.User.CreationDate.Location() + }() +) + +func TestQueries_GetOIDCUserInfo(t *testing.T) { + expQuery := regexp.QuoteMeta(oidcUserInfoQuery) + type args struct { + userID string + roleAudience []string + } + tests := []struct { + name string + args args + mock sqlExpectation + want *OIDCUserInfo + wantErr error + }{ + { + name: "query error", + args: args{ + userID: "231965491734773762", + }, + mock: mockQueryErr(expQuery, sql.ErrConnDone, "231965491734773762", "instanceID", nil), + wantErr: sql.ErrConnDone, + }, + { + name: "unmarshal error", + args: args{ + userID: "231965491734773762", + }, + mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{`~~~`}, "231965491734773762", "instanceID", nil), + wantErr: errors.ThrowInternal(nil, "QUERY-Vohs6", "Errors.Internal"), + }, + { + name: "user not found", + args: args{ + userID: "231965491734773762", + }, + mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{testdataUserInfoNotFound}, "231965491734773762", "instanceID", nil), + wantErr: errors.ThrowNotFound(nil, "QUERY-ahs4S", "Errors.User.NotFound"), + }, + { + name: "human without metadata", + args: args{ + userID: "231965491734773762", + }, + mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{testdataUserInfoHumanNoMD}, "231965491734773762", "instanceID", nil), + want: &OIDCUserInfo{ + User: &User{ + ID: "231965491734773762", + CreationDate: time.Date(2023, time.September, 15, 6, 10, 7, 434142000, timeLocation), + ChangeDate: time.Date(2023, time.November, 14, 13, 27, 2, 72318000, timeLocation), + Sequence: 1148, + State: 1, + ResourceOwner: "231848297847848962", + Username: "tim+tesmail@zitadel.com", + PreferredLoginName: "tim+tesmail@zitadel.com@demo.localhost", + Human: &Human{ + FirstName: "Tim", + LastName: "Mohlmann", + NickName: "muhlemmer", + DisplayName: "Tim Mohlmann", + AvatarKey: "", + Email: "tim+tesmail@zitadel.com", + IsEmailVerified: true, + Phone: "+40123456789", + IsPhoneVerified: false, + }, + Machine: nil, + }, + Org: &UserInfoOrg{ + Name: "demo", + PrimaryDomain: "demo.localhost", + }, + Metadata: nil, + }, + }, + { + name: "human with metadata", + args: args{ + userID: "231965491734773762", + }, + mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{testdataUserInfoHuman}, "231965491734773762", "instanceID", nil), + want: &OIDCUserInfo{ + User: &User{ + ID: "231965491734773762", + CreationDate: time.Date(2023, time.September, 15, 6, 10, 7, 434142000, timeLocation), + ChangeDate: time.Date(2023, time.November, 14, 13, 27, 2, 72318000, timeLocation), + Sequence: 1148, + State: 1, + ResourceOwner: "231848297847848962", + Username: "tim+tesmail@zitadel.com", + PreferredLoginName: "tim+tesmail@zitadel.com@demo.localhost", + Human: &Human{ + FirstName: "Tim", + LastName: "Mohlmann", + NickName: "muhlemmer", + DisplayName: "Tim Mohlmann", + AvatarKey: "", + Email: "tim+tesmail@zitadel.com", + IsEmailVerified: true, + Phone: "+40123456789", + IsPhoneVerified: false, + }, + Machine: nil, + }, + Org: &UserInfoOrg{ + Name: "demo", + PrimaryDomain: "demo.localhost", + }, + Metadata: []UserMetadata{ + { + CreationDate: time.Date(2023, time.November, 14, 13, 26, 3, 553702000, timeLocation), + ChangeDate: time.Date(2023, time.November, 14, 13, 26, 3, 553702000, timeLocation), + Sequence: 1147, + ResourceOwner: "231848297847848962", + Key: "bar", + Value: []byte("foo"), + }, + { + CreationDate: time.Date(2023, time.November, 14, 13, 25, 57, 171368000, timeLocation), + ChangeDate: time.Date(2023, time.November, 14, 13, 25, 57, 171368000, timeLocation), + Sequence: 1146, + ResourceOwner: "231848297847848962", + Key: "foo", + Value: []byte("bar"), + }, + }, + }, + }, + { + name: "human with metadata and grants", + args: args{ + userID: "231965491734773762", + roleAudience: []string{"236645808328409090", "240762134579904514"}, + }, + mock: mockQuery(expQuery, + []string{"json_build_object"}, + []driver.Value{testdataUserInfoHumanGrants}, + "231965491734773762", "instanceID", database.TextArray[string]{"236645808328409090", "240762134579904514"}, + ), + want: &OIDCUserInfo{ + User: &User{ + ID: "231965491734773762", + CreationDate: time.Date(2023, time.September, 15, 6, 10, 7, 434142000, timeLocation), + ChangeDate: time.Date(2023, time.November, 14, 13, 27, 2, 72318000, timeLocation), + Sequence: 1148, + State: 1, + ResourceOwner: "231848297847848962", + Username: "tim+tesmail@zitadel.com", + PreferredLoginName: "tim+tesmail@zitadel.com@demo.localhost", + Human: &Human{ + FirstName: "Tim", + LastName: "Mohlmann", + NickName: "muhlemmer", + DisplayName: "Tim Mohlmann", + AvatarKey: "", + Email: "tim+tesmail@zitadel.com", + IsEmailVerified: true, + Phone: "+40123456789", + IsPhoneVerified: false, + }, + Machine: nil, + }, + Org: &UserInfoOrg{ + Name: "demo", + PrimaryDomain: "demo.localhost", + }, + Metadata: []UserMetadata{ + { + CreationDate: time.Date(2023, time.November, 14, 13, 26, 3, 553702000, timeLocation), + ChangeDate: time.Date(2023, time.November, 14, 13, 26, 3, 553702000, timeLocation), + Sequence: 1147, + ResourceOwner: "231848297847848962", + Key: "bar", + Value: []byte("foo"), + }, + { + CreationDate: time.Date(2023, time.November, 14, 13, 25, 57, 171368000, timeLocation), + ChangeDate: time.Date(2023, time.November, 14, 13, 25, 57, 171368000, timeLocation), + Sequence: 1146, + ResourceOwner: "231848297847848962", + Key: "foo", + Value: []byte("bar"), + }, + }, + UserGrants: []UserGrant{ + { + ID: "240749256523120642", + GrantID: "", + State: 1, + CreationDate: time.Date(2023, time.November, 14, 20, 28, 59, 168208000, timeLocation), + ChangeDate: time.Date(2023, time.November, 14, 20, 50, 58, 822391000, timeLocation), + Sequence: 2, + UserID: "231965491734773762", + Roles: []string{ + "role1", + "role2", + }, + ResourceOwner: "231848297847848962", + ProjectID: "236645808328409090", + OrgName: "demo", + OrgPrimaryDomain: "demo.localhost", + ProjectName: "tests", + UserResourceOwner: "231848297847848962", + }, + { + ID: "240762315572510722", + GrantID: "", + State: 1, + CreationDate: time.Date(2023, time.November, 14, 22, 38, 42, 967317000, timeLocation), + ChangeDate: time.Date(2023, time.November, 14, 22, 38, 42, 967317000, timeLocation), + Sequence: 1, + UserID: "231965491734773762", + Roles: []string{ + "role3", + "role4", + }, + ResourceOwner: "231848297847848962", + ProjectID: "240762134579904514", + OrgName: "demo", + OrgPrimaryDomain: "demo.localhost", + ProjectName: "tests2", + UserResourceOwner: "231848297847848962", + }, + }, + }, + }, + { + name: "machine with metadata", + args: args{ + userID: "240707570677841922", + }, + mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{testdataUserInfoMachine}, "240707570677841922", "instanceID", nil), + want: &OIDCUserInfo{ + User: &User{ + ID: "240707570677841922", + CreationDate: time.Date(2023, time.November, 14, 13, 34, 52, 473732000, timeLocation), + ChangeDate: time.Date(2023, time.November, 14, 13, 35, 2, 861342000, timeLocation), + Sequence: 2, + State: 1, + ResourceOwner: "231848297847848962", + Username: "tests", + PreferredLoginName: "tests@demo.localhost", + Human: nil, + Machine: &Machine{ + Name: "tests", + Description: "My test service user", + }, + }, + Org: &UserInfoOrg{ + Name: "demo", + PrimaryDomain: "demo.localhost", + }, + Metadata: []UserMetadata{ + { + CreationDate: time.Date(2023, time.November, 14, 13, 35, 30, 126849000, timeLocation), + ChangeDate: time.Date(2023, time.November, 14, 13, 35, 30, 126849000, timeLocation), + Sequence: 3, + ResourceOwner: "231848297847848962", + Key: "first", + Value: []byte("Hello World!"), + }, + { + CreationDate: time.Date(2023, time.November, 14, 13, 35, 44, 28343000, timeLocation), + ChangeDate: time.Date(2023, time.November, 14, 13, 35, 44, 28343000, timeLocation), + Sequence: 4, + ResourceOwner: "231848297847848962", + Key: "second", + Value: []byte("Bye World!"), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + execMock(t, tt.mock, func(db *sql.DB) { + q := &Queries{ + client: &database.DB{ + DB: db, + Database: &prepareDB{}, + }, + } + ctx := authz.NewMockContext("instanceID", "orgID", "loginClient") + + got, err := q.GetOIDCUserInfo(ctx, tt.args.userID, tt.args.roleAudience) + require.ErrorIs(t, err, tt.wantErr) + assert.Equal(t, tt.want, got) + }) + }) + } +} diff --git a/internal/static/i18n/bg.yaml b/internal/static/i18n/bg.yaml index e9290b96bb..90b236f78e 100644 --- a/internal/static/i18n/bg.yaml +++ b/internal/static/i18n/bg.yaml @@ -441,6 +441,7 @@ Errors: UserSession: NotFound: UserSession не е намерена Key: + NotFound: Ключът не е намерен ExpireBeforeNow: Срокът на годност е в миналото Login: LoginPolicy: diff --git a/internal/static/i18n/cs.yaml b/internal/static/i18n/cs.yaml index f18148b7b2..81dc605b73 100644 --- a/internal/static/i18n/cs.yaml +++ b/internal/static/i18n/cs.yaml @@ -428,6 +428,7 @@ Errors: UserSession: NotFound: UserSession nenalezena Key: + NotFound: Klíč nenalezen ExpireBeforeNow: Datum expirace je v minulosti Login: LoginPolicy: diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index 4c8eea97ff..cfcaf9f5a6 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -429,6 +429,7 @@ Errors: UserSession: NotFound: Benutzer Sitzung konnte nicht gefunden werden Key: + NotFound: Schlüssel nicht gefunden ExpireBeforeNow: Das Ablaufdatum liegt in der Vergangenheit Login: LoginPolicy: diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index 62aaba68fc..f993dde467 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -429,6 +429,7 @@ Errors: UserSession: NotFound: UserSession not found Key: + NotFound: Key not found ExpireBeforeNow: The expiration date is in the past Login: LoginPolicy: diff --git a/internal/static/i18n/es.yaml b/internal/static/i18n/es.yaml index 69b3bbf261..4cb5da4518 100644 --- a/internal/static/i18n/es.yaml +++ b/internal/static/i18n/es.yaml @@ -429,6 +429,7 @@ Errors: UserSession: NotFound: UserSession no encontrado Key: + NotFound: Clave no encontrada ExpireBeforeNow: La fecha de caducidad está en el pasado Login: LoginPolicy: diff --git a/internal/static/i18n/fr.yaml b/internal/static/i18n/fr.yaml index 6258812560..423c91e357 100644 --- a/internal/static/i18n/fr.yaml +++ b/internal/static/i18n/fr.yaml @@ -429,6 +429,7 @@ Errors: UserSession: NotFound: UserSession non trouvé Key: + NotFound: Clé introuvable ExpireBeforeNow: La date d'expiration est dans le passé Login: LoginPolicy: diff --git a/internal/static/i18n/it.yaml b/internal/static/i18n/it.yaml index 1407e8a377..282f205579 100644 --- a/internal/static/i18n/it.yaml +++ b/internal/static/i18n/it.yaml @@ -430,6 +430,7 @@ Errors: UserSession: NotFound: Sessione non trovata Key: + NotFound: Chiave non trovata ExpireBeforeNow: La data di scadenza è passata Login: LoginPolicy: diff --git a/internal/static/i18n/ja.yaml b/internal/static/i18n/ja.yaml index 36b39782c4..a716f7c906 100644 --- a/internal/static/i18n/ja.yaml +++ b/internal/static/i18n/ja.yaml @@ -418,6 +418,7 @@ Errors: UserSession: NotFound: ユーザーが見つかりません Key: + NotFound: キーが見つかりません ExpireBeforeNow: 有効期限が過去です Login: LoginPolicy: diff --git a/internal/static/i18n/mk.yaml b/internal/static/i18n/mk.yaml index 0283fdf4b2..ea33e18b91 100644 --- a/internal/static/i18n/mk.yaml +++ b/internal/static/i18n/mk.yaml @@ -429,6 +429,7 @@ Errors: UserSession: NotFound: Корисничката сесија не е пронајдена Key: + NotFound: Клучот не е пронајден ExpireBeforeNow: Датумот на истекување е во минатото Login: LoginPolicy: diff --git a/internal/static/i18n/pl.yaml b/internal/static/i18n/pl.yaml index f8bb6cd003..d20da0d1e8 100644 --- a/internal/static/i18n/pl.yaml +++ b/internal/static/i18n/pl.yaml @@ -429,6 +429,7 @@ Errors: UserSession: NotFound: Sesja użytkownika nie znaleziona Key: + NotFound: Klucz nie odnaleziony ExpireBeforeNow: Data ważności jest już przeszła Login: LoginPolicy: diff --git a/internal/static/i18n/pt.yaml b/internal/static/i18n/pt.yaml index 867d15d67c..06035c93ee 100644 --- a/internal/static/i18n/pt.yaml +++ b/internal/static/i18n/pt.yaml @@ -427,6 +427,7 @@ Errors: UserSession: NotFound: Sessão do usuário não encontrada Key: + NotFound: Chave não encontrada ExpireBeforeNow: A data de expiração está no passado Login: LoginPolicy: diff --git a/internal/static/i18n/ru.yaml b/internal/static/i18n/ru.yaml index 2b89e5cbee..792ce7b9c8 100644 --- a/internal/static/i18n/ru.yaml +++ b/internal/static/i18n/ru.yaml @@ -418,6 +418,7 @@ Errors: UserSession: NotFound: UserSession не найден Key: + NotFound: Ключ не найден ExpireBeforeNow: Срок годности в прошлом Login: LoginPolicy: diff --git a/internal/static/i18n/zh.yaml b/internal/static/i18n/zh.yaml index d8660c5faf..a5cee656fb 100644 --- a/internal/static/i18n/zh.yaml +++ b/internal/static/i18n/zh.yaml @@ -429,6 +429,7 @@ Errors: UserSession: NotFound: 用户会话不存在 Key: + NotFound: 找不到钥匙 ExpireBeforeNow: 过期日期是过去的无效日期 Login: LoginPolicy: