perf(oidc): optimize the introspection endpoint (#6909)

* get key by id and cache them

* userinfo from events for v2 tokens

* improve keyset caching

* concurrent token and client checks

* client and project in single query

* logging and otel

* drop owner_removed column on apps and authN tables

* userinfo and project roles in go routines

* get  oidc user info from projections and add actions

* add avatar URL

* some cleanup

* pull oidc work branch

* remove storage from server

* add config flag for experimental introspection

* legacy introspection flag

* drop owner_removed column on user projections

* drop owner_removed column on useer_metadata

* query userinfo unit test

* query introspection client test

* add user_grants to the userinfo query

* handle PAT scopes

* bring triggers back

* test instance keys query

* add userinfo unit tests

* unit test keys

* go mod tidy

* solve some bugs

* fix missing preferred login name

* do not run triggers in go routines, they seem to deadlock

* initialize the trigger handlers late with a sync.OnceValue

* Revert "do not run triggers in go routines, they seem to deadlock"

This reverts commit 2a03da2127b7dc74552ec25d4772282a82cc1cba.

* add missing translations

* chore: update go version for linting

* pin oidc version

* parse a global time location for query test

* fix linter complains

* upgrade go lint

* fix more linting issues

---------

Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
This commit is contained in:
Tim Möhlmann 2023-11-21 14:11:38 +02:00 committed by GitHub
parent ad3563d58b
commit ba9b807854
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 3528 additions and 808 deletions

View File

@ -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 }}

View File

@ -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

View File

@ -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:

10
go.mod
View File

@ -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

16
go.sum
View File

@ -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=

View File

@ -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) {

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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)

View File

@ -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())

View File

@ -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")
}

View File

@ -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
}

View File

@ -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"

View File

@ -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)
})
}
}

View File

@ -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(

View File

@ -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) }()

View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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()
}

View File

@ -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")

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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,

View File

@ -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")

View File

@ -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]{

View File

@ -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")

View File

@ -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;

View File

@ -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"}')

View File

@ -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}
}

View File

@ -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' " +

View File

@ -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
}

View File

@ -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)
})
})
}
}

View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -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 " +

View File

@ -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 " +

View File

@ -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 " +

View File

@ -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),

View File

@ -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",

View File

@ -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})),
),
)
}

View File

@ -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),

View File

@ -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})),
),
)
}

View File

@ -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",
},

View File

@ -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",
},

View File

@ -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()
}

View File

@ -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,

View File

@ -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{

View File

@ -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
}

View File

@ -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"
}
]
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,6 @@
{
"user": null,
"org": null,
"metadata": null,
"user_grants": null
}

View File

@ -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) {

View File

@ -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")

View File

@ -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{

View File

@ -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 {

View File

@ -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" +

View File

@ -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")

View File

@ -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",

View File

@ -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",

View File

@ -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"`
}

View File

@ -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)
})
})
}
}

View File

@ -441,6 +441,7 @@ Errors:
UserSession:
NotFound: UserSession не е намерена
Key:
NotFound: Ключът не е намерен
ExpireBeforeNow: Срокът на годност е в миналото
Login:
LoginPolicy:

View File

@ -428,6 +428,7 @@ Errors:
UserSession:
NotFound: UserSession nenalezena
Key:
NotFound: Klíč nenalezen
ExpireBeforeNow: Datum expirace je v minulosti
Login:
LoginPolicy:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -430,6 +430,7 @@ Errors:
UserSession:
NotFound: Sessione non trovata
Key:
NotFound: Chiave non trovata
ExpireBeforeNow: La data di scadenza è passata
Login:
LoginPolicy:

View File

@ -418,6 +418,7 @@ Errors:
UserSession:
NotFound: ユーザーが見つかりません
Key:
NotFound: キーが見つかりません
ExpireBeforeNow: 有効期限が過去です
Login:
LoginPolicy:

View File

@ -429,6 +429,7 @@ Errors:
UserSession:
NotFound: Корисничката сесија не е пронајдена
Key:
NotFound: Клучот не е пронајден
ExpireBeforeNow: Датумот на истекување е во минатото
Login:
LoginPolicy:

View File

@ -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:

Some files were not shown because too many files have changed in this diff Show More