mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 21:47:23 +00:00
perf(oidc): optimize client verification (#6999)
* fix some spelling errors * client credential auth * implementation of client auth * improve error handling * unit test command package * unit test database package * unit test query package * cleanup unused tracing func * fix integration tests * errz to zerrors * fix linting and import issues * fix another linting error * integration test with client secret * Revert "integration test with client secret" This reverts commit 0814ba522f0e2254bd8beaaf97d83ad1dc01228d. * add integration tests * client credentials integration test * resolve comments * pin oidc v3.5.0
This commit is contained in:
parent
51cfb9564a
commit
ec03340b67
@ -217,6 +217,7 @@ func startZitadel(config *Config, masterKey string, server chan<- *Server) error
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot start commands: %w", err)
|
||||
}
|
||||
defer commands.Close(ctx) // wait for background jobs
|
||||
|
||||
clock := clockpkg.New()
|
||||
actionsExecutionStdoutEmitter, err := logstore.NewEmitter[*record.ExecutionLog](ctx, clock, &logstore.EmitterConfig{Enabled: config.LogStore.Execution.Stdout.Enabled}, stdout.NewStdoutEmitter[*record.ExecutionLog]())
|
||||
|
13
go.mod
13
go.mod
@ -13,6 +13,7 @@ require (
|
||||
github.com/allegro/bigcache v1.2.1
|
||||
github.com/benbjohnson/clock v1.3.5
|
||||
github.com/boombuler/barcode v1.0.1
|
||||
github.com/brianvoe/gofakeit/v6 v6.25.0
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.3.5
|
||||
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be
|
||||
github.com/crewjam/saml v0.4.14
|
||||
@ -49,7 +50,6 @@ require (
|
||||
github.com/muhlemmer/gu v0.3.1
|
||||
github.com/muhlemmer/httpforwarded v0.1.0
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/rakyll/statik v0.1.7
|
||||
github.com/rs/cors v1.10.1
|
||||
@ -60,7 +60,7 @@ 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.4.0
|
||||
github.com/zitadel/oidc/v3 v3.5.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
|
||||
@ -74,10 +74,10 @@ require (
|
||||
go.opentelemetry.io/otel/sdk/metric v1.20.0
|
||||
go.opentelemetry.io/otel/trace v1.21.0
|
||||
go.uber.org/mock v0.3.0
|
||||
golang.org/x/crypto v0.15.0
|
||||
golang.org/x/crypto v0.16.0
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678
|
||||
golang.org/x/net v0.18.0
|
||||
golang.org/x/oauth2 v0.14.0
|
||||
golang.org/x/net v0.19.0
|
||||
golang.org/x/oauth2 v0.15.0
|
||||
golang.org/x/sync v0.5.0
|
||||
golang.org/x/text v0.14.0
|
||||
google.golang.org/api v0.150.0
|
||||
@ -107,6 +107,7 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
@ -201,7 +202,7 @@ require (
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||
golang.org/x/sys v0.14.0
|
||||
golang.org/x/sys v0.15.0
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
|
21
go.sum
21
go.sum
@ -122,6 +122,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/brianvoe/gofakeit/v6 v6.25.0 h1:ZpFjktOpLZUeF8q223o0rUuXtA+m5qW5srjvVi+JkXk=
|
||||
github.com/brianvoe/gofakeit/v6 v6.25.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
@ -865,8 +867,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.4.0 h1:JkbNnrk/7IG+NOBoZp/P0kx6tPcBvnCekSqDTPCOok4=
|
||||
github.com/zitadel/oidc/v3 v3.4.0/go.mod h1:jUnLnx5ihKlo88cSEduZkKlzeMrjzcWVZ8fTzKBxZKY=
|
||||
github.com/zitadel/oidc/v3 v3.5.0 h1:z51AN6FPo5UuwYJ1r9nLvHlxpTGYd8QXg5MrtYm/dgM=
|
||||
github.com/zitadel/oidc/v3 v3.5.0/go.mod h1:R8sF5DPR98QQnOoyySsaNqI4NcF/VFMkf/XoYiBUuXQ=
|
||||
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=
|
||||
@ -951,8 +953,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -1040,8 +1042,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -1051,8 +1053,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
|
||||
golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
|
||||
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
|
||||
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -1133,8 +1135,9 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -72,7 +72,7 @@ func MachineToPb(view *query.Machine) *user_pb.Machine {
|
||||
return &user_pb.Machine{
|
||||
Name: view.Name,
|
||||
Description: view.Description,
|
||||
HasSecret: view.HasSecret,
|
||||
HasSecret: view.Secret != nil,
|
||||
AccessTokenType: AccessTokenTypeToPb(view.AccessTokenType),
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
errz "github.com/zitadel/zitadel/internal/errors"
|
||||
zerrors "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/user/model"
|
||||
)
|
||||
@ -55,7 +55,7 @@ func (s *Server) verifyAccessToken(ctx context.Context, tkn string) (*accessToke
|
||||
|
||||
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 nil, zerrors.ThrowPermissionDenied(err, "OIDC-Dsfb2", "token is not valid or has expired")
|
||||
}
|
||||
return accessTokenV1(tokenID, subject, token), nil
|
||||
}
|
||||
@ -91,7 +91,7 @@ func (s *Server) assertClientScopesForPAT(ctx context.Context, token *accessToke
|
||||
token.audience = append(token.audience, clientID)
|
||||
projectIDQuery, err := query.NewProjectRoleProjectIDSearchQuery(projectID)
|
||||
if err != nil {
|
||||
return errz.ThrowInternal(err, "OIDC-Cyc78", "Errors.Internal")
|
||||
return zerrors.ThrowInternal(err, "OIDC-Cyc78", "Errors.Internal")
|
||||
}
|
||||
roles, err := s.query.SearchProjectRoles(ctx, s.features.TriggerIntrospectionProjections, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
|
||||
if err != nil {
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
||||
oidc_api "github.com/zitadel/zitadel/internal/api/oidc"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta"
|
||||
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||
)
|
||||
@ -500,8 +501,7 @@ func exchangeTokens(t testing.TB, clientID, code string) (*oidc.Tokens[*oidc.IDT
|
||||
provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI)
|
||||
require.NoError(t, err)
|
||||
|
||||
codeVerifier := "codeVerifier"
|
||||
return rp.CodeExchange[*oidc.IDTokenClaims](context.Background(), code, provider, rp.WithCodeVerifier(codeVerifier))
|
||||
return rp.CodeExchange[*oidc.IDTokenClaims](context.Background(), code, provider, rp.WithCodeVerifier(integration.CodeVerifier))
|
||||
}
|
||||
|
||||
func refreshTokens(t testing.TB, clientID, refreshToken string) (*oidc.Tokens[*oidc.IDTokenClaims], error) {
|
||||
|
@ -43,32 +43,14 @@ 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)
|
||||
client, err := o.query.GetOIDCClientByID(ctx, id, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if client.State != domain.AppStateActive {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-sdaGg", "client is not active")
|
||||
}
|
||||
projectIDQuery, err := query.NewProjectRoleProjectIDSearchQuery(client.ProjectID)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "OIDC-mPxqP", "Errors.Internal")
|
||||
}
|
||||
projectRoles, err := o.query.SearchProjectRoles(ctx, true, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allowedScopes := make([]string, len(projectRoles.ProjectRoles))
|
||||
for i, role := range projectRoles.ProjectRoles {
|
||||
allowedScopes[i] = ScopeProjectRolePrefix + role.Key
|
||||
}
|
||||
|
||||
accessTokenLifetime, idTokenLifetime, _, _, err := o.getOIDCSettings(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ClientFromBusiness(client, o.defaultLoginURL, o.defaultLoginURLV2, accessTokenLifetime, idTokenLifetime, allowedScopes)
|
||||
return ClientFromBusiness(client, o.defaultLoginURL, o.defaultLoginURLV2), nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) GetKeyByIDAndClientID(ctx context.Context, keyID, userID string) (_ *jose.JSONWebKey, err error) {
|
||||
@ -235,22 +217,10 @@ func (o *OPStorage) ClientCredentialsTokenRequest(ctx context.Context, clientID
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) ClientCredentials(ctx context.Context, clientID, clientSecret string) (op.Client, error) {
|
||||
loginname, err := query.NewUserLoginNamesSearchQuery(clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := o.query.GetUser(ctx, false, loginname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := o.command.VerifyMachineSecret(ctx, user.ID, user.ResourceOwner, clientSecret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &clientCredentialsClient{
|
||||
id: clientID,
|
||||
tokenType: accessTokenTypeToOIDC(user.Machine.AccessTokenType),
|
||||
}, nil
|
||||
// ClientCredentials method is kept to keep the storage interface implemented.
|
||||
// However, it should never be called as the VerifyClient method on the Server is overridden.
|
||||
func (o *OPStorage) ClientCredentials(context.Context, string, string) (op.Client, error) {
|
||||
return nil, errors.ThrowInternal(nil, "OIDC-Su8So", "Errors.Internal")
|
||||
}
|
||||
|
||||
// isOriginAllowed checks whether a call by the client to the endpoint is allowed from the provided origin
|
||||
@ -934,3 +904,67 @@ func userinfoClaims(userInfo *oidc.UserInfo) func(c *actions.FieldConfig) interf
|
||||
return c.Runtime.ToValue(claims)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) VerifyClient(ctx context.Context, r *op.Request[op.ClientCredentials]) (_ op.Client, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if oidc.GrantType(r.Form.Get("grant_type")) == oidc.GrantTypeClientCredentials {
|
||||
return s.clientCredentialsAuth(ctx, r.Data.ClientID, r.Data.ClientSecret)
|
||||
}
|
||||
|
||||
clientID, assertion, err := clientIDFromCredentials(r.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := s.query.GetOIDCClientByID(ctx, clientID, assertion)
|
||||
if errors.IsNotFound(err) {
|
||||
return nil, oidc.ErrInvalidClient().WithParent(err).WithDescription("client not found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err // defaults to server error
|
||||
}
|
||||
if client.State != domain.AppStateActive {
|
||||
return nil, oidc.ErrInvalidClient().WithDescription("client is not active")
|
||||
}
|
||||
|
||||
switch client.AuthMethodType {
|
||||
case domain.OIDCAuthMethodTypeBasic, domain.OIDCAuthMethodTypePost:
|
||||
err = s.verifyClientSecret(ctx, client, r.Data.ClientSecret)
|
||||
case domain.OIDCAuthMethodTypePrivateKeyJWT:
|
||||
err = s.verifyClientAssertion(ctx, client, r.Data.ClientAssertion)
|
||||
case domain.OIDCAuthMethodTypeNone:
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ClientFromBusiness(client, s.defaultLoginURL, s.defaultLoginURLV2), nil
|
||||
}
|
||||
|
||||
func (s *Server) verifyClientAssertion(ctx context.Context, client *query.OIDCClient, assertion string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if assertion == "" {
|
||||
return oidc.ErrInvalidClient().WithDescription("empty client assertion")
|
||||
}
|
||||
verifier := op.NewJWTProfileVerifierKeySet(keySetMap(client.PublicKeys), op.IssuerFromContext(ctx), time.Hour, client.ClockSkew)
|
||||
if _, err := op.VerifyJWTAssertion(ctx, assertion, verifier); err != nil {
|
||||
return oidc.ErrInvalidClient().WithParent(err).WithDescription("invalid assertion")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) verifyClientSecret(ctx context.Context, client *query.OIDCClient, secret string) (err error) {
|
||||
_, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if secret == "" {
|
||||
return oidc.ErrInvalidClient().WithDescription("empty client secret")
|
||||
}
|
||||
if err = crypto.CompareHash(client.ClientSecret, []byte(secret), s.hashAlg); err != nil {
|
||||
return oidc.ErrInvalidClient().WithParent(err).WithDescription("invalid secret")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -9,43 +10,40 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
app *query.App
|
||||
defaultLoginURL string
|
||||
defaultLoginURLV2 string
|
||||
defaultAccessTokenLifetime time.Duration
|
||||
defaultIdTokenLifetime time.Duration
|
||||
allowedScopes []string
|
||||
client *query.OIDCClient
|
||||
defaultLoginURL string
|
||||
defaultLoginURLV2 string
|
||||
allowedScopes []string
|
||||
}
|
||||
|
||||
func ClientFromBusiness(app *query.App, defaultLoginURL, defaultLoginURLV2 string, defaultAccessTokenLifetime, defaultIdTokenLifetime time.Duration, allowedScopes []string) (op.Client, error) {
|
||||
if app.OIDCConfig == nil {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "OIDC-d5bhD", "client is not a proper oidc application")
|
||||
func ClientFromBusiness(client *query.OIDCClient, defaultLoginURL, defaultLoginURLV2 string) op.Client {
|
||||
allowedScopes := make([]string, len(client.ProjectRoleKeys))
|
||||
for i, roleKey := range client.ProjectRoleKeys {
|
||||
allowedScopes[i] = ScopeProjectRolePrefix + roleKey
|
||||
}
|
||||
|
||||
return &Client{
|
||||
app: app,
|
||||
defaultLoginURL: defaultLoginURL,
|
||||
defaultLoginURLV2: defaultLoginURLV2,
|
||||
defaultAccessTokenLifetime: defaultAccessTokenLifetime,
|
||||
defaultIdTokenLifetime: defaultIdTokenLifetime,
|
||||
allowedScopes: allowedScopes},
|
||||
nil
|
||||
client: client,
|
||||
defaultLoginURL: defaultLoginURL,
|
||||
defaultLoginURLV2: defaultLoginURLV2,
|
||||
allowedScopes: allowedScopes,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) ApplicationType() op.ApplicationType {
|
||||
return op.ApplicationType(c.app.OIDCConfig.AppType)
|
||||
return op.ApplicationType(c.client.ApplicationType)
|
||||
}
|
||||
|
||||
func (c *Client) AuthMethod() oidc.AuthMethod {
|
||||
return authMethodToOIDC(c.app.OIDCConfig.AuthMethodType)
|
||||
return authMethodToOIDC(c.client.AuthMethodType)
|
||||
}
|
||||
|
||||
func (c *Client) GetID() string {
|
||||
return c.app.OIDCConfig.ClientID
|
||||
return c.client.ClientID
|
||||
}
|
||||
|
||||
func (c *Client) LoginURL(id string) string {
|
||||
@ -56,28 +54,28 @@ func (c *Client) LoginURL(id string) string {
|
||||
}
|
||||
|
||||
func (c *Client) RedirectURIs() []string {
|
||||
return c.app.OIDCConfig.RedirectURIs
|
||||
return c.client.RedirectURIs
|
||||
}
|
||||
|
||||
func (c *Client) PostLogoutRedirectURIs() []string {
|
||||
return c.app.OIDCConfig.PostLogoutRedirectURIs
|
||||
return c.client.PostLogoutRedirectURIs
|
||||
}
|
||||
|
||||
func (c *Client) ResponseTypes() []oidc.ResponseType {
|
||||
return responseTypesToOIDC(c.app.OIDCConfig.ResponseTypes)
|
||||
return responseTypesToOIDC(c.client.ResponseTypes)
|
||||
}
|
||||
|
||||
func (c *Client) GrantTypes() []oidc.GrantType {
|
||||
return grantTypesToOIDC(c.app.OIDCConfig.GrantTypes)
|
||||
return grantTypesToOIDC(c.client.GrantTypes)
|
||||
}
|
||||
|
||||
func (c *Client) DevMode() bool {
|
||||
return c.app.OIDCConfig.IsDevMode
|
||||
return c.client.IsDevMode
|
||||
}
|
||||
|
||||
func (c *Client) RestrictAdditionalIdTokenScopes() func(scopes []string) []string {
|
||||
return func(scopes []string) []string {
|
||||
if c.app.OIDCConfig.AssertIDTokenRole {
|
||||
if c.client.IDTokenRoleAssertion {
|
||||
return scopes
|
||||
}
|
||||
return removeScopeWithPrefix(scopes, ScopeProjectRolePrefix)
|
||||
@ -86,7 +84,7 @@ func (c *Client) RestrictAdditionalIdTokenScopes() func(scopes []string) []strin
|
||||
|
||||
func (c *Client) RestrictAdditionalAccessTokenScopes() func(scopes []string) []string {
|
||||
return func(scopes []string) []string {
|
||||
if c.app.OIDCConfig.AssertAccessTokenRole {
|
||||
if c.client.AccessTokenRoleAssertion {
|
||||
return scopes
|
||||
}
|
||||
return removeScopeWithPrefix(scopes, ScopeProjectRolePrefix)
|
||||
@ -94,15 +92,15 @@ func (c *Client) RestrictAdditionalAccessTokenScopes() func(scopes []string) []s
|
||||
}
|
||||
|
||||
func (c *Client) AccessTokenLifetime() time.Duration {
|
||||
return c.defaultAccessTokenLifetime //PLANNED: impl from real client
|
||||
return c.client.AccessTokenLifetime
|
||||
}
|
||||
|
||||
func (c *Client) IDTokenLifetime() time.Duration {
|
||||
return c.defaultIdTokenLifetime //PLANNED: impl from real client
|
||||
return c.client.IDTokenLifetime
|
||||
}
|
||||
|
||||
func (c *Client) AccessTokenType() op.AccessTokenType {
|
||||
return accessTokenTypeToOIDC(c.app.OIDCConfig.AccessTokenType)
|
||||
return accessTokenTypeToOIDC(c.client.AccessTokenType)
|
||||
}
|
||||
|
||||
func (c *Client) IsScopeAllowed(scope string) bool {
|
||||
@ -127,20 +125,15 @@ func (c *Client) IsScopeAllowed(scope string) bool {
|
||||
if scope == ScopeProjectsRoles {
|
||||
return true
|
||||
}
|
||||
for _, allowedScope := range c.allowedScopes {
|
||||
if scope == allowedScope {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(c.allowedScopes, scope)
|
||||
}
|
||||
|
||||
func (c *Client) ClockSkew() time.Duration {
|
||||
return c.app.OIDCConfig.ClockSkew
|
||||
return c.client.ClockSkew
|
||||
}
|
||||
|
||||
func (c *Client) IDTokenUserinfoClaimsAssertion() bool {
|
||||
return c.app.OIDCConfig.AssertIDTokenUserinfo
|
||||
return c.client.IDTokenUserinfoAssertion
|
||||
}
|
||||
|
||||
func accessTokenTypeToOIDC(tokenType domain.OIDCTokenType) op.AccessTokenType {
|
||||
@ -229,3 +222,14 @@ func removeScopeWithPrefix(scopes []string, scopePrefix ...string) []string {
|
||||
}
|
||||
return newScopeList
|
||||
}
|
||||
|
||||
func clientIDFromCredentials(cc *op.ClientCredentials) (clientID string, assertion bool, err error) {
|
||||
if cc.ClientAssertion != "" {
|
||||
claims := new(oidc.JWTTokenRequest)
|
||||
if _, err := oidc.ParseToken(cc.ClientAssertion, claims); err != nil {
|
||||
return "", false, oidc.ErrInvalidClient().WithParent(err)
|
||||
}
|
||||
return claims.Issuer, true, nil
|
||||
}
|
||||
return cc.ClientID, false, nil
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
)
|
||||
|
||||
type clientCredentialsRequest struct {
|
||||
@ -28,15 +33,42 @@ func (c *clientCredentialsRequest) GetScopes() []string {
|
||||
return c.scopes
|
||||
}
|
||||
|
||||
func (s *Server) clientCredentialsAuth(ctx context.Context, clientID, clientSecret string) (op.Client, error) {
|
||||
searchQuery, err := query.NewUserLoginNamesSearchQuery(clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := s.query.GetUser(ctx, false, searchQuery)
|
||||
if errors.IsNotFound(err) {
|
||||
return nil, oidc.ErrInvalidClient().WithParent(err).WithDescription("client not found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err // defaults to server error
|
||||
}
|
||||
if user.Machine == nil || user.Machine.Secret == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "OIDC-pieP8", "Errors.User.Machine.Secret.NotExisting")
|
||||
}
|
||||
if err = crypto.CompareHash(user.Machine.Secret, []byte(clientSecret), s.hashAlg); err != nil {
|
||||
s.command.MachineSecretCheckFailed(ctx, user.ID, user.ResourceOwner)
|
||||
return nil, errors.ThrowInvalidArgument(err, "OIDC-VoXo6", "Errors.User.Machine.Secret.Invalid")
|
||||
}
|
||||
|
||||
s.command.MachineSecretCheckSucceeded(ctx, user.ID, user.ResourceOwner)
|
||||
return &clientCredentialsClient{
|
||||
id: clientID,
|
||||
user: user,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type clientCredentialsClient struct {
|
||||
id string
|
||||
tokenType op.AccessTokenType
|
||||
id string
|
||||
user *query.User
|
||||
}
|
||||
|
||||
// AccessTokenType returns the AccessTokenType for the token to be created because of the client credentials request
|
||||
// machine users currently only have opaque tokens ([op.AccessTokenTypeBearer])
|
||||
func (c *clientCredentialsClient) AccessTokenType() op.AccessTokenType {
|
||||
return c.tokenType
|
||||
return accessTokenTypeToOIDC(c.user.Machine.AccessTokenType)
|
||||
}
|
||||
|
||||
// GetID returns the client_id (username of the machine user) for the token to be created because of the client credentials request
|
||||
|
@ -4,20 +4,26 @@ package oidc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/oidc/v3/pkg/client"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rs"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
oidc_api "github.com/zitadel/zitadel/internal/api/oidc"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/authn"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user"
|
||||
)
|
||||
|
||||
func TestOPStorage_SetUserinfoFromToken(t *testing.T) {
|
||||
@ -142,3 +148,245 @@ func assertIntrospection(
|
||||
assert.NotEmpty(t, introspection.Claims[oidc_api.ClaimResourceOwner+"name"])
|
||||
assert.NotEmpty(t, introspection.Claims[oidc_api.ClaimResourceOwner+"primary_domain"])
|
||||
}
|
||||
|
||||
// TestServer_VerifyClient tests verification by running code flow tests
|
||||
// with clients that have different authentication methods.
|
||||
func TestServer_VerifyClient(t *testing.T) {
|
||||
sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
||||
project, err := Tester.CreateProject(CTX)
|
||||
require.NoError(t, err)
|
||||
|
||||
inactiveClient, err := Tester.CreateOIDCInactivateClient(CTX, redirectURI, logoutRedirectURI, project.GetId())
|
||||
require.NoError(t, err)
|
||||
nativeClient, err := Tester.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId())
|
||||
require.NoError(t, err)
|
||||
basicWebClient, err := Tester.CreateOIDCWebClientBasic(CTX, redirectURI, logoutRedirectURI, project.GetId())
|
||||
require.NoError(t, err)
|
||||
jwtWebClient, keyData, err := Tester.CreateOIDCWebClientJWT(CTX, redirectURI, logoutRedirectURI, project.GetId())
|
||||
require.NoError(t, err)
|
||||
|
||||
type clientDetails struct {
|
||||
authReqClientID string
|
||||
clientID string
|
||||
clientSecret string
|
||||
keyData []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
client clientDetails
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty client ID error",
|
||||
client: clientDetails{
|
||||
authReqClientID: nativeClient.GetClientId(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "client not found error",
|
||||
client: clientDetails{
|
||||
authReqClientID: nativeClient.GetClientId(),
|
||||
clientID: "foo",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "client inactive error",
|
||||
client: clientDetails{
|
||||
authReqClientID: nativeClient.GetClientId(),
|
||||
clientID: inactiveClient.GetClientId(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "native client success",
|
||||
client: clientDetails{
|
||||
authReqClientID: nativeClient.GetClientId(),
|
||||
clientID: nativeClient.GetClientId(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "web client basic secret empty error",
|
||||
client: clientDetails{
|
||||
authReqClientID: basicWebClient.GetClientId(),
|
||||
clientID: basicWebClient.GetClientId(),
|
||||
clientSecret: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "web client basic secret invalid error",
|
||||
client: clientDetails{
|
||||
authReqClientID: basicWebClient.GetClientId(),
|
||||
clientID: basicWebClient.GetClientId(),
|
||||
clientSecret: "wrong",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "web client basic secret success",
|
||||
client: clientDetails{
|
||||
authReqClientID: basicWebClient.GetClientId(),
|
||||
clientID: basicWebClient.GetClientId(),
|
||||
clientSecret: basicWebClient.GetClientSecret(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "web client JWT profile empty assertion error",
|
||||
client: clientDetails{
|
||||
authReqClientID: jwtWebClient.GetClientId(),
|
||||
clientID: jwtWebClient.GetClientId(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "web client JWT profile invalid assertion error",
|
||||
client: clientDetails{
|
||||
authReqClientID: jwtWebClient.GetClientId(),
|
||||
clientID: jwtWebClient.GetClientId(),
|
||||
keyData: createInvalidKeyData(t, jwtWebClient),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "web client JWT profile success",
|
||||
client: clientDetails{
|
||||
authReqClientID: jwtWebClient.GetClientId(),
|
||||
clientID: jwtWebClient.GetClientId(),
|
||||
keyData: keyData,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fmt.Printf("\n\n%s\n\n", tt.client.keyData)
|
||||
|
||||
authRequestID, err := Tester.CreateOIDCAuthRequest(CTX, tt.client.authReqClientID, Tester.Users[integration.FirstInstanceUsersKey][integration.Login].ID, redirectURI, oidc.ScopeOpenID)
|
||||
require.NoError(t, err)
|
||||
linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
||||
AuthRequestId: authRequestID,
|
||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||
Session: &oidc_pb.Session{
|
||||
SessionId: sessionID,
|
||||
SessionToken: sessionToken,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// use a new RP so we can inject different credentials
|
||||
var options []rp.Option
|
||||
if tt.client.keyData != nil {
|
||||
options = append(options, rp.WithJWTProfile(rp.SignerFromKeyFile(tt.client.keyData)))
|
||||
}
|
||||
provider, err := rp.NewRelyingPartyOIDC(CTX, Tester.OIDCIssuer(), tt.client.clientID, tt.client.clientSecret, redirectURI, []string{oidc.ScopeOpenID}, options...)
|
||||
require.NoError(t, err)
|
||||
|
||||
// test code exchange
|
||||
code := assertCodeResponse(t, linkResp.GetCallbackUrl())
|
||||
codeOpts := codeExchangeOptions(t, provider)
|
||||
tokens, err := rp.CodeExchange[*oidc.IDTokenClaims](context.Background(), code, provider, codeOpts...)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assertTokens(t, tokens, false)
|
||||
assertIDTokenClaims(t, tokens.IDTokenClaims, armPasskey, startTime, changeTime)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func codeExchangeOptions(t testing.TB, provider rp.RelyingParty) []rp.CodeExchangeOpt {
|
||||
codeOpts := []rp.CodeExchangeOpt{rp.WithCodeVerifier(integration.CodeVerifier)}
|
||||
if signer := provider.Signer(); signer != nil {
|
||||
assertion, err := client.SignedJWTProfileAssertion(provider.OAuthConfig().ClientID, []string{provider.Issuer()}, time.Hour, provider.Signer())
|
||||
require.NoError(t, err)
|
||||
codeOpts = append(codeOpts, rp.WithClientAssertionJWT(assertion))
|
||||
}
|
||||
return codeOpts
|
||||
}
|
||||
|
||||
func createInvalidKeyData(t testing.TB, client *management.AddOIDCAppResponse) []byte {
|
||||
key := domain.ApplicationKey{
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
KeyID: "1",
|
||||
PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"),
|
||||
ApplicationID: client.GetAppId(),
|
||||
ClientID: client.GetClientId(),
|
||||
}
|
||||
data, err := key.Detail()
|
||||
require.NoError(t, err)
|
||||
return data
|
||||
}
|
||||
|
||||
func TestServer_CreateAccessToken_ClientCredentials(t *testing.T) {
|
||||
clientID, clientSecret, err := Tester.CreateOIDCCredentialsClient(CTX)
|
||||
require.NoError(t, err)
|
||||
|
||||
type clientDetails struct {
|
||||
clientID string
|
||||
clientSecret string
|
||||
keyData []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
clientID string
|
||||
clientSecret string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing client ID error",
|
||||
clientID: "",
|
||||
clientSecret: clientSecret,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "client not found error",
|
||||
clientID: "foo",
|
||||
clientSecret: clientSecret,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "machine user without secret error",
|
||||
clientID: func() string {
|
||||
name := gofakeit.Username()
|
||||
_, err := Tester.Client.Mgmt.AddMachineUser(CTX, &management.AddMachineUserRequest{
|
||||
Name: name,
|
||||
UserName: name,
|
||||
AccessTokenType: user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return name
|
||||
}(),
|
||||
clientSecret: clientSecret,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "wrong secret error",
|
||||
clientID: clientID,
|
||||
clientSecret: "bar",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
clientID: clientID,
|
||||
clientSecret: clientSecret,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
provider, err := rp.NewRelyingPartyOIDC(CTX, Tester.OIDCIssuer(), tt.clientID, tt.clientSecret, redirectURI, []string{oidc.ScopeOpenID})
|
||||
require.NoError(t, err)
|
||||
tokens, err := rp.ClientCredentials(CTX, provider, nil)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, tokens)
|
||||
assert.NotEmpty(t, tokens.AccessToken)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
errz "github.com/zitadel/zitadel/internal/errors"
|
||||
zerrors "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
@ -31,14 +31,14 @@ func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionR
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
clientChan := make(chan *instrospectionClientResult)
|
||||
go s.instrospectionClientAuth(ctx, r.Data.ClientCredentials, clientChan)
|
||||
clientChan := make(chan *introspectionClientResult)
|
||||
go s.introspectionClientAuth(ctx, r.Data.ClientCredentials, clientChan)
|
||||
|
||||
tokenChan := make(chan *introspectionTokenResult)
|
||||
go s.introspectionToken(ctx, r.Data.Token, tokenChan)
|
||||
|
||||
var (
|
||||
client *instrospectionClientResult
|
||||
client *introspectionClientResult
|
||||
token *introspectionTokenResult
|
||||
)
|
||||
|
||||
@ -116,13 +116,13 @@ func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionR
|
||||
return op.NewResponse(introspectionResp), nil
|
||||
}
|
||||
|
||||
type instrospectionClientResult struct {
|
||||
type introspectionClientResult struct {
|
||||
clientID string
|
||||
projectID string
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *Server) instrospectionClientAuth(ctx context.Context, cc *op.ClientCredentials, rc chan<- *instrospectionClientResult) {
|
||||
func (s *Server) introspectionClientAuth(ctx context.Context, cc *op.ClientCredentials, rc chan<- *introspectionClientResult) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
|
||||
clientID, projectID, err := func() (string, string, error) {
|
||||
@ -147,7 +147,7 @@ func (s *Server) instrospectionClientAuth(ctx context.Context, cc *op.ClientCred
|
||||
|
||||
span.EndWithError(err)
|
||||
|
||||
rc <- &instrospectionClientResult{
|
||||
rc <- &introspectionClientResult{
|
||||
clientID: clientID,
|
||||
projectID: projectID,
|
||||
err: err,
|
||||
@ -157,15 +157,11 @@ func (s *Server) instrospectionClientAuth(ctx context.Context, cc *op.ClientCred
|
||||
// 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)
|
||||
clientID, assertion, err := clientIDFromCredentials(cc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err = s.query.GetIntrospectionClientByID(ctx, clientID, assertion)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, oidc.ErrUnauthorizedClient().WithParent(err)
|
||||
}
|
||||
@ -196,5 +192,5 @@ func validateIntrospectionAudience(audience []string, clientID, projectID string
|
||||
return nil
|
||||
}
|
||||
|
||||
return errz.ThrowPermissionDenied(nil, "OIDC-sdg3G", "token is not valid for this client")
|
||||
return zerrors.ThrowPermissionDenied(nil, "OIDC-sdg3G", "token is not valid for this client")
|
||||
}
|
||||
|
@ -31,9 +31,9 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
redirectURI = "oidcintegrationtest://callback"
|
||||
redirectURI = "https://callback"
|
||||
redirectURIImplicit = "http://localhost:9999/callback"
|
||||
logoutRedirectURI = "oidcintegrationtest://logged-out"
|
||||
logoutRedirectURI = "https://logged-out"
|
||||
zitadelAudienceScope = domain.ProjectIDScope + domain.ProjectIDScopeZITADEL + domain.AudSuffix
|
||||
)
|
||||
|
||||
|
@ -128,6 +128,9 @@ func NewServer(
|
||||
query: query,
|
||||
command: command,
|
||||
keySet: newKeySet(context.TODO(), time.Hour, query.GetActivePublicKeyByID),
|
||||
defaultLoginURL: fmt.Sprintf("%s%s?%s=", login.HandlerPrefix, login.EndpointLogin, login.QueryAuthRequestID),
|
||||
defaultLoginURLV2: config.DefaultLoginURLV2,
|
||||
defaultLogoutURLV2: config.DefaultLogoutURLV2,
|
||||
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,
|
||||
|
@ -27,6 +27,10 @@ type Server struct {
|
||||
command *command.Commands
|
||||
keySet *keySetCache
|
||||
|
||||
defaultLoginURL string
|
||||
defaultLoginURLV2 string
|
||||
defaultLogoutURLV2 string
|
||||
|
||||
fallbackLogger *slog.Logger
|
||||
hashAlg crypto.HashAlgorithm
|
||||
signingKeyAlgorithm string
|
||||
@ -143,13 +147,6 @@ func (s *Server) DeviceAuthorization(ctx context.Context, r *op.ClientRequest[oi
|
||||
return s.LegacyServer.DeviceAuthorization(ctx, r)
|
||||
}
|
||||
|
||||
func (s *Server) VerifyClient(ctx context.Context, r *op.Request[op.ClientCredentials]) (_ op.Client, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
return s.LegacyServer.VerifyClient(ctx, r)
|
||||
}
|
||||
|
||||
func (s *Server) CodeExchange(ctx context.Context, r *op.ClientRequest[oidc.AccessTokenRequest]) (_ *op.Response, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
@ -9,8 +9,11 @@ import (
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
api_http "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
@ -37,12 +40,15 @@ import (
|
||||
usr_repo "github.com/zitadel/zitadel/internal/repository/user"
|
||||
usr_grant_repo "github.com/zitadel/zitadel/internal/repository/usergrant"
|
||||
"github.com/zitadel/zitadel/internal/static"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
webauthn_helper "github.com/zitadel/zitadel/internal/webauthn"
|
||||
)
|
||||
|
||||
type Commands struct {
|
||||
httpClient *http.Client
|
||||
|
||||
jobs sync.WaitGroup
|
||||
|
||||
checkPermission domain.PermissionCheck
|
||||
newCode cryptoCodeFunc
|
||||
newCodeWithDefault cryptoCodeWithDefaultFunc
|
||||
@ -257,3 +263,54 @@ func samlCertificateAndKeyGenerator(keySize int) func(id string) ([]byte, []byte
|
||||
return pem.EncodeToMemory(keyBlock), pem.EncodeToMemory(certBlock), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Close blocks until all async jobs are finished,
|
||||
// the context expires or after eventstore.PushTimeout.
|
||||
func (c *Commands) Close(ctx context.Context) error {
|
||||
if c.eventstore.PushTimeout != 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, c.eventstore.PushTimeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
c.jobs.Wait()
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// asyncPush attempts to push events to the eventstore in a separate Go routine.
|
||||
// This can be used to speed up request times when the outcome of the push is
|
||||
// not important for business logic but have a pure logging function.
|
||||
// For example this can be used for Secret Check Success and Failed events.
|
||||
// On push error, a log line describing the error will be emitted.
|
||||
func (c *Commands) asyncPush(ctx context.Context, cmds ...eventstore.Command) {
|
||||
// Create a new context, as the request scoped context might get
|
||||
// canceled before we where able to push.
|
||||
// The eventstore has its own PushTimeout setting,
|
||||
// so we don't need to have a context with timeout here.
|
||||
ctx = context.WithoutCancel(ctx)
|
||||
|
||||
c.jobs.Add(1)
|
||||
|
||||
go func() {
|
||||
defer c.jobs.Done()
|
||||
localCtx, span := tracing.NewSpan(ctx)
|
||||
|
||||
_, err := c.eventstore.Push(localCtx, cmds...)
|
||||
if err != nil {
|
||||
for _, cmd := range cmds {
|
||||
logging.WithError(err).Errorf("could not push event %q", cmd.Type())
|
||||
}
|
||||
}
|
||||
|
||||
span.EndWithError(err)
|
||||
}()
|
||||
}
|
||||
|
@ -1,11 +1,19 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/i18n"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -18,5 +26,121 @@ var (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
i18n.SupportLanguages(SupportedLanguages...)
|
||||
m.Run()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestCommands_asyncPush(t *testing.T) {
|
||||
// make sure the test terminates on deadlock
|
||||
background := context.Background()
|
||||
agg := user.NewAggregate("userID", "orgID")
|
||||
cmd := user.NewMachineSecretCheckFailedEvent(background, &agg.Aggregate)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pushCtx func() (context.Context, context.CancelFunc)
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
closeCtx func() (context.Context, context.CancelFunc)
|
||||
wantCloseErr bool
|
||||
}{
|
||||
{
|
||||
name: "push error",
|
||||
pushCtx: func() (context.Context, context.CancelFunc) {
|
||||
return context.WithCancel(background)
|
||||
},
|
||||
eventstore: expectEventstore(
|
||||
expectPushFailed(io.ErrClosedPipe, cmd),
|
||||
),
|
||||
closeCtx: func() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(background, time.Second)
|
||||
},
|
||||
wantCloseErr: false,
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
pushCtx: func() (context.Context, context.CancelFunc) {
|
||||
return context.WithCancel(background)
|
||||
},
|
||||
eventstore: expectEventstore(
|
||||
expectPushSlow(time.Second/10, cmd),
|
||||
),
|
||||
closeCtx: func() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(background, time.Second)
|
||||
},
|
||||
wantCloseErr: false,
|
||||
},
|
||||
{
|
||||
name: "success after push context cancels",
|
||||
pushCtx: func() (context.Context, context.CancelFunc) {
|
||||
ctx, cancel := context.WithCancel(background)
|
||||
cancel()
|
||||
return ctx, cancel
|
||||
},
|
||||
eventstore: expectEventstore(
|
||||
expectPushSlow(time.Second/10, cmd),
|
||||
),
|
||||
closeCtx: func() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(background, time.Second)
|
||||
},
|
||||
wantCloseErr: false,
|
||||
},
|
||||
{
|
||||
name: "success after push context timeout",
|
||||
pushCtx: func() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(background, time.Second/100)
|
||||
},
|
||||
eventstore: expectEventstore(
|
||||
expectPushSlow(time.Second/10, cmd),
|
||||
),
|
||||
closeCtx: func() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(background, time.Second)
|
||||
},
|
||||
wantCloseErr: false,
|
||||
},
|
||||
{
|
||||
name: "success after push context timeout",
|
||||
pushCtx: func() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(background, time.Second/100)
|
||||
},
|
||||
eventstore: expectEventstore(
|
||||
expectPushSlow(time.Second/10, cmd),
|
||||
),
|
||||
closeCtx: func() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(background, time.Second)
|
||||
},
|
||||
wantCloseErr: false,
|
||||
},
|
||||
{
|
||||
name: "close timeout error",
|
||||
pushCtx: func() (context.Context, context.CancelFunc) {
|
||||
return context.WithCancel(background)
|
||||
},
|
||||
eventstore: expectEventstore(
|
||||
expectPushSlow(time.Second/10, cmd),
|
||||
),
|
||||
closeCtx: func() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(background, time.Second/100)
|
||||
},
|
||||
wantCloseErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.eventstore(t),
|
||||
}
|
||||
c.eventstore.PushTimeout = 10 * time.Second
|
||||
pushCtx, cancel := tt.pushCtx()
|
||||
c.asyncPush(pushCtx, cmd)
|
||||
cancel()
|
||||
|
||||
closeCtx, cancel := tt.closeCtx()
|
||||
defer cancel()
|
||||
err := c.Close(closeCtx)
|
||||
if tt.wantCloseErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,13 @@ func eventPusherToEvents(eventsPushes ...eventstore.Command) []*repository.Event
|
||||
|
||||
func expectPush(commands ...eventstore.Command) expect {
|
||||
return func(m *mock.MockRepository) {
|
||||
m.ExpectPush(commands)
|
||||
m.ExpectPush(commands, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func expectPushSlow(sleep time.Duration, commands ...eventstore.Command) expect {
|
||||
return func(m *mock.MockRepository) {
|
||||
m.ExpectPush(commands, sleep)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,6 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@ -111,59 +109,12 @@ func prepareRemoveMachineSecret(a *user.Aggregate) preparation.Validation {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) VerifyMachineSecret(ctx context.Context, userID string, resourceOwner string, secret string) (*domain.ObjectDetails, error) {
|
||||
func (c *Commands) MachineSecretCheckSucceeded(ctx context.Context, userID, resourceOwner string) {
|
||||
agg := user.NewAggregate(userID, resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareVerifyMachineSecret(agg, secret, c.codeAlg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
for _, cmd := range cmds {
|
||||
if cmd.Type() == user.MachineSecretCheckFailedType {
|
||||
logging.OnError(err).Error("could not push event MachineSecretCheckFailed")
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-3kjh", "Errors.User.Machine.Secret.Invalid")
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreatedAt(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().ResourceOwner,
|
||||
}, nil
|
||||
c.asyncPush(ctx, user.NewMachineSecretCheckSucceededEvent(ctx, &agg.Aggregate))
|
||||
}
|
||||
|
||||
func prepareVerifyMachineSecret(a *user.Aggregate, secret string, algorithm crypto.HashAlgorithm) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if a.ResourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-0qp2hus", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if a.ID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bzosjs", "Errors.User.UserIDMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := getMachineWriteModel(ctx, a.ID, a.ResourceOwner, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(writeModel.UserState) {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-569sh2o", "Errors.User.NotExisting")
|
||||
}
|
||||
if writeModel.ClientSecret == nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-x8910n", "Errors.User.Machine.Secret.NotExisting")
|
||||
}
|
||||
err = crypto.CompareHash(writeModel.ClientSecret, []byte(secret), algorithm)
|
||||
if err == nil {
|
||||
return []eventstore.Command{
|
||||
user.NewMachineSecretCheckSucceededEvent(ctx, &a.Aggregate),
|
||||
}, nil
|
||||
}
|
||||
return []eventstore.Command{
|
||||
user.NewMachineSecretCheckFailedEvent(ctx, &a.Aggregate),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
func (c *Commands) MachineSecretCheckFailed(ctx context.Context, userID, resourceOwner string) {
|
||||
agg := user.NewAggregate(userID, resourceOwner)
|
||||
c.asyncPush(ctx, user.NewMachineSecretCheckFailedEvent(ctx, &agg.Aggregate))
|
||||
}
|
||||
|
@ -3,8 +3,10 @@ package command
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@ -319,212 +321,34 @@ func TestCommandSide_RemoveMachineSecret(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_VerifyMachineSecret(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
resourceOwner string
|
||||
secret string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "user invalid, invalid argument error userID",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user invalid, invalid argument error resourceowner",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user existing without secret, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"username",
|
||||
"user",
|
||||
false,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "verify machine secret, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"username",
|
||||
"user",
|
||||
false,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewMachineSecretSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "bcrypt",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("$2a$14$HxC7TAXMeowdqHdSBUfsjOUc0IGajYeApxdYl9lAYC0duZmSkgFia"),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
user.NewMachineSecretCheckSucceededEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
secret: "test",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "verify machine secret, failed",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"username",
|
||||
"user",
|
||||
false,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewMachineSecretSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "bcrypt",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("$2a$14$HxC7TAXMeowdqHdSBUfsjOUc0IGajYeApxdYl9lAYC0duZmSkgFia"),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
user.NewMachineSecretCheckFailedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
secret: "wrong",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
codeAlg: crypto.NewBCrypt(14),
|
||||
}
|
||||
got, err := r.VerifyMachineSecret(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.secret)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
func TestCommands_MachineSecretCheckSucceeded(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
agg := user.NewAggregate("userID", "orgID")
|
||||
cmd := user.NewMachineSecretCheckSucceededEvent(ctx, &agg.Aggregate)
|
||||
|
||||
c := &Commands{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectPushSlow(time.Second/100, cmd),
|
||||
),
|
||||
}
|
||||
c.MachineSecretCheckSucceeded(ctx, "userID", "orgID")
|
||||
require.NoError(t, c.Close(ctx))
|
||||
}
|
||||
|
||||
func TestCommands_MachineSecretCheckFailed(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
agg := user.NewAggregate("userID", "orgID")
|
||||
cmd := user.NewMachineSecretCheckFailedEvent(ctx, &agg.Aggregate)
|
||||
|
||||
c := &Commands{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectPushSlow(time.Second/100, cmd),
|
||||
),
|
||||
}
|
||||
c.MachineSecretCheckFailed(ctx, "userID", "orgID")
|
||||
require.NoError(t, c.Close(ctx))
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package database
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
@ -11,7 +13,7 @@ import (
|
||||
_ "github.com/zitadel/zitadel/internal/database/cockroach"
|
||||
"github.com/zitadel/zitadel/internal/database/dialect"
|
||||
_ "github.com/zitadel/zitadel/internal/database/postgres"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
zerrors "github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@ -89,6 +91,24 @@ func (db *DB) QueryRowContext(ctx context.Context, scan func(row *sql.Row) error
|
||||
return row.Err()
|
||||
}
|
||||
|
||||
func QueryJSONObject[T any](ctx context.Context, db *DB, query string, args ...any) (*T, error) {
|
||||
var data []byte
|
||||
err := db.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
return row.Scan(&data)
|
||||
}, query, args...)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "DATAB-Oath6", "Errors.Internal")
|
||||
}
|
||||
obj := new(T)
|
||||
if err = json.Unmarshal(data, obj); err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "DATAB-Vohs6", "Errors.Internal")
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
const (
|
||||
zitadelAppName = "zitadel"
|
||||
EventstorePusherAppName = "zitadel_es_pusher"
|
||||
@ -106,7 +126,7 @@ func Connect(config Config, useAdmin, isEventPusher bool) (*DB, error) {
|
||||
}
|
||||
|
||||
if err := client.Ping(); err != nil {
|
||||
return nil, errors.ThrowPreconditionFailed(err, "DATAB-0pIWD", "Errors.Database.Connection.Failed")
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "DATAB-0pIWD", "Errors.Database.Connection.Failed")
|
||||
}
|
||||
|
||||
return &DB{
|
||||
|
92
internal/database/database_test.go
Normal file
92
internal/database/database_test.go
Normal file
@ -0,0 +1,92 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database/mock"
|
||||
zerrors "github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestQueryJSONObject(t *testing.T) {
|
||||
type dst struct {
|
||||
A int `json:"a,omitempty"`
|
||||
}
|
||||
const (
|
||||
query = `select $1;`
|
||||
arg = 1
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mock func(*testing.T) *mock.SQLMock
|
||||
want *dst
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "tx error",
|
||||
mock: func(t *testing.T) *mock.SQLMock {
|
||||
return mock.NewSQLMock(t, mock.ExpectBegin(sql.ErrConnDone))
|
||||
},
|
||||
wantErr: zerrors.ThrowInternal(sql.ErrConnDone, "DATAB-Oath6", "Errors.Internal"),
|
||||
},
|
||||
{
|
||||
name: "no rows",
|
||||
mock: func(t *testing.T) *mock.SQLMock {
|
||||
return mock.NewSQLMock(t,
|
||||
mock.ExpectBegin(nil),
|
||||
mock.ExpectQuery(query,
|
||||
mock.WithQueryArgs(arg),
|
||||
mock.WithQueryResult([]string{"json"}, [][]driver.Value{}),
|
||||
),
|
||||
)
|
||||
},
|
||||
wantErr: sql.ErrNoRows,
|
||||
},
|
||||
{
|
||||
name: "unmarshal error",
|
||||
mock: func(t *testing.T) *mock.SQLMock {
|
||||
return mock.NewSQLMock(t,
|
||||
mock.ExpectBegin(nil),
|
||||
mock.ExpectQuery(query,
|
||||
mock.WithQueryArgs(arg),
|
||||
mock.WithQueryResult([]string{"json"}, [][]driver.Value{{`~~~`}}),
|
||||
),
|
||||
mock.ExpectCommit(nil),
|
||||
)
|
||||
},
|
||||
wantErr: zerrors.ThrowInternal(nil, "DATAB-Vohs6", "Errors.Internal"),
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
mock: func(t *testing.T) *mock.SQLMock {
|
||||
return mock.NewSQLMock(t,
|
||||
mock.ExpectBegin(nil),
|
||||
mock.ExpectQuery(query,
|
||||
mock.WithQueryArgs(arg),
|
||||
mock.WithQueryResult([]string{"json"}, [][]driver.Value{{`{"a":1}`}}),
|
||||
),
|
||||
mock.ExpectCommit(nil),
|
||||
)
|
||||
},
|
||||
want: &dst{A: 1},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mock := tt.mock(t)
|
||||
defer mock.Assert(t)
|
||||
db := &DB{
|
||||
DB: mock.DB,
|
||||
}
|
||||
got, err := QueryJSONObject[dst](context.Background(), db, query, arg)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
@ -53,6 +53,15 @@ func ExpectBegin(err error) expectation {
|
||||
}
|
||||
}
|
||||
|
||||
func ExpectCommit(err error) expectation {
|
||||
return func(m sqlmock.Sqlmock) {
|
||||
e := m.ExpectCommit()
|
||||
if err != nil {
|
||||
e.WillReturnError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ExecOpt func(e *sqlmock.ExpectedExec) *sqlmock.ExpectedExec
|
||||
|
||||
func WithExecArgs(args ...driver.Value) ExecOpt {
|
||||
|
@ -75,11 +75,15 @@ func (m *MockRepository) ExpectInstanceIDsError(err error) *MockRepository {
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MockRepository) ExpectPush(expectedCommands []eventstore.Command) *MockRepository {
|
||||
// ExpectPush checks if the expectedCommands are send to the Push method.
|
||||
// The call will sleep at least the amount of passed duration.
|
||||
func (m *MockRepository) ExpectPush(expectedCommands []eventstore.Command, sleep time.Duration) *MockRepository {
|
||||
m.MockPusher.EXPECT().Push(gomock.Any(), gomock.Any()).DoAndReturn(
|
||||
func(ctx context.Context, commands ...eventstore.Command) ([]eventstore.Event, error) {
|
||||
m.MockPusher.ctrl.T.Helper()
|
||||
|
||||
time.Sleep(sleep)
|
||||
|
||||
if len(expectedCommands) != len(commands) {
|
||||
return nil, fmt.Errorf("unexpected amount of commands: want %d, got %d", len(expectedCommands), len(commands))
|
||||
}
|
||||
|
@ -8,26 +8,30 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/zitadel/oidc/v3/pkg/client"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rs"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||
oidc_internal "github.com/zitadel/zitadel/internal/api/oidc"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/app"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/authn"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user"
|
||||
)
|
||||
|
||||
func (s *Tester) CreateOIDCNativeClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string) (*management.AddOIDCAppResponse, error) {
|
||||
func (s *Tester) CreateOIDCClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, appType app.OIDCAppType, authMethod app.OIDCAuthMethodType) (*management.AddOIDCAppResponse, error) {
|
||||
return s.Client.Mgmt.AddOIDCApp(ctx, &management.AddOIDCAppRequest{
|
||||
ProjectId: projectID,
|
||||
Name: fmt.Sprintf("app-%d", time.Now().UnixNano()),
|
||||
RedirectUris: []string{redirectURI},
|
||||
ResponseTypes: []app.OIDCResponseType{app.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
|
||||
GrantTypes: []app.OIDCGrantType{app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE, app.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN},
|
||||
AppType: app.OIDCAppType_OIDC_APP_TYPE_NATIVE,
|
||||
AuthMethodType: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE,
|
||||
AppType: appType,
|
||||
AuthMethodType: authMethod,
|
||||
PostLogoutRedirectUris: []string{logoutRedirectURI},
|
||||
Version: app.OIDCVersion_OIDC_VERSION_1_0,
|
||||
DevMode: false,
|
||||
@ -41,6 +45,46 @@ func (s *Tester) CreateOIDCNativeClient(ctx context.Context, redirectURI, logout
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCNativeClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string) (*management.AddOIDCAppResponse, error) {
|
||||
return s.CreateOIDCClient(ctx, redirectURI, logoutRedirectURI, projectID, app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCWebClientBasic(ctx context.Context, redirectURI, logoutRedirectURI, projectID string) (*management.AddOIDCAppResponse, error) {
|
||||
return s.CreateOIDCClient(ctx, redirectURI, logoutRedirectURI, projectID, app.OIDCAppType_OIDC_APP_TYPE_WEB, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCWebClientJWT(ctx context.Context, redirectURI, logoutRedirectURI, projectID string) (client *management.AddOIDCAppResponse, keyData []byte, err error) {
|
||||
client, err = s.CreateOIDCClient(ctx, redirectURI, logoutRedirectURI, projectID, app.OIDCAppType_OIDC_APP_TYPE_WEB, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
key, err := s.Client.Mgmt.AddAppKey(ctx, &management.AddAppKeyRequest{
|
||||
ProjectId: projectID,
|
||||
AppId: client.GetAppId(),
|
||||
Type: authn.KeyType_KEY_TYPE_JSON,
|
||||
ExpirationDate: timestamppb.New(time.Now().Add(time.Hour)),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return client, key.GetKeyDetails(), nil
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCInactivateClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string) (*management.AddOIDCAppResponse, error) {
|
||||
client, err := s.CreateOIDCNativeClient(ctx, redirectURI, logoutRedirectURI, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = s.Client.Mgmt.DeactivateApp(ctx, &management.DeactivateAppRequest{
|
||||
ProjectId: projectID,
|
||||
AppId: client.GetAppId(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, err
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCImplicitFlowClient(ctx context.Context, redirectURI string) (*management.AddOIDCAppResponse, error) {
|
||||
project, err := s.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
|
||||
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
|
||||
@ -83,14 +127,14 @@ func (s *Tester) CreateAPIClient(ctx context.Context, projectID string) (*manage
|
||||
})
|
||||
}
|
||||
|
||||
const CodeVerifier = "codeVerifier"
|
||||
|
||||
func (s *Tester) CreateOIDCAuthRequest(ctx context.Context, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||
provider, err := s.CreateRelyingParty(ctx, clientID, redirectURI, scope...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
codeVerifier := "codeVerifier"
|
||||
codeChallenge := oidc.NewSHACodeChallenge(codeVerifier)
|
||||
codeChallenge := oidc.NewSHACodeChallenge(CodeVerifier)
|
||||
authURL := rp.AuthURL("state", provider, rp.WithCodeChallenge(codeChallenge))
|
||||
|
||||
req, err := GetRequest(authURL, map[string]string{oidc_internal.LoginClientHeader: loginClient})
|
||||
@ -196,3 +240,22 @@ func CheckRedirect(req *http.Request) (*url.URL, error) {
|
||||
|
||||
return resp.Location()
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCCredentialsClient(ctx context.Context) (string, string, error) {
|
||||
name := gofakeit.Username()
|
||||
user, err := s.Client.Mgmt.AddMachineUser(ctx, &management.AddMachineUserRequest{
|
||||
Name: name,
|
||||
UserName: name,
|
||||
AccessTokenType: user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT,
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
secret, err := s.Client.Mgmt.GenerateMachineSecret(ctx, &management.GenerateMachineSecretRequest{
|
||||
UserId: user.GetUserId(),
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return secret.GetClientId(), secret.GetClientSecret(), nil
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ package smtp
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"net/smtp"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
|
46
internal/query/embed/oidc_client_by_id.sql
Normal file
46
internal/query/embed/oidc_client_by_id.sql
Normal file
@ -0,0 +1,46 @@
|
||||
--deallocate q;
|
||||
--prepare q(text, text, boolean) as
|
||||
|
||||
with client as (
|
||||
select c.instance_id,
|
||||
c.app_id, c.client_id, c.client_secret, c.redirect_uris, c.response_types, c.grant_types,
|
||||
c.application_type, c.auth_method_type, c.post_logout_redirect_uris, c.is_dev_mode,
|
||||
c.access_token_type, c.access_token_role_assertion, c.id_token_role_assertion,
|
||||
c.id_token_userinfo_assertion, c.clock_skew, c.additional_origins, a.project_id, a.state
|
||||
from projections.apps6_oidc_configs c
|
||||
join projections.apps6 a on a.id = c.app_id and a.instance_id = c.instance_id
|
||||
where c.instance_id = $1
|
||||
and c.client_id = $2
|
||||
),
|
||||
roles as (
|
||||
select p.project_id, json_agg(p.role_key) as project_role_keys
|
||||
from projections.project_roles4 p
|
||||
join client c on c.project_id = p.project_id
|
||||
and p.instance_id = c.instance_id
|
||||
group by p.project_id
|
||||
),
|
||||
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
|
||||
),
|
||||
settings as (
|
||||
select instance_id, access_token_lifetime, id_token_lifetime
|
||||
from projections.oidc_settings2
|
||||
where aggregate_id = $1
|
||||
and instance_id = $1
|
||||
)
|
||||
|
||||
select row_to_json(r) as client from (
|
||||
select c.*, r.project_role_keys, k.public_keys, s.access_token_lifetime, s.id_token_lifetime
|
||||
from client c
|
||||
left join roles r on r.project_id = c.project_id
|
||||
left join keys k on k.client_id = c.client_id
|
||||
join settings s on s.instance_id = s.instance_id
|
||||
) r;
|
||||
|
||||
--execute q('230690539048009730', '236647088211951618@tests', true);
|
@ -1,6 +1,6 @@
|
||||
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
|
||||
from projections.users10 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
|
||||
@ -9,7 +9,7 @@ with usr as (
|
||||
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, preferred_language, gender, email, is_email_verified, phone, is_phone_verified
|
||||
from projections.users9_humans
|
||||
from projections.users10_humans
|
||||
where user_id = $1
|
||||
and instance_id = $2
|
||||
) r
|
||||
@ -17,7 +17,7 @@ human as (
|
||||
machine as (
|
||||
select $1 as user_id, row_to_json(r) as machine from (
|
||||
select name, description
|
||||
from projections.users9_machines
|
||||
from projections.users10_machines
|
||||
where user_id = $1
|
||||
and instance_id = $2
|
||||
) r
|
||||
|
@ -21,21 +21,21 @@ var (
|
||||
", members.user_id" +
|
||||
", members.roles" +
|
||||
", projections.login_names3.login_name" +
|
||||
", 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" +
|
||||
", projections.users10_humans.email" +
|
||||
", projections.users10_humans.first_name" +
|
||||
", projections.users10_humans.last_name" +
|
||||
", projections.users10_humans.display_name" +
|
||||
", projections.users10_machines.name" +
|
||||
", projections.users10_humans.avatar_key" +
|
||||
", projections.users10.type" +
|
||||
", COUNT(*) OVER () " +
|
||||
"FROM projections.instance_members4 AS members " +
|
||||
"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.users10_humans " +
|
||||
"ON members.user_id = projections.users10_humans.user_id AND members.instance_id = projections.users10_humans.instance_id " +
|
||||
"LEFT JOIN projections.users10_machines " +
|
||||
"ON members.user_id = projections.users10_machines.user_id AND members.instance_id = projections.users10_machines.instance_id " +
|
||||
"LEFT JOIN projections.users10 " +
|
||||
"ON members.user_id = projections.users10.id AND members.instance_id = projections.users10.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' " +
|
||||
|
61
internal/query/oidc_client.go
Normal file
61
internal/query/oidc_client.go
Normal file
@ -0,0 +1,61 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
zerrors "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
type OIDCClient struct {
|
||||
InstanceID string `json:"instance_id,omitempty"`
|
||||
AppID string `json:"app_id,omitempty"`
|
||||
State domain.AppState `json:"state,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
ClientSecret *crypto.CryptoValue `json:"client_secret,omitempty"`
|
||||
RedirectURIs []string `json:"redirect_uris,omitempty"`
|
||||
ResponseTypes []domain.OIDCResponseType `json:"response_types,omitempty"`
|
||||
GrantTypes []domain.OIDCGrantType `json:"grant_types,omitempty"`
|
||||
ApplicationType domain.OIDCApplicationType `json:"application_type,omitempty"`
|
||||
AuthMethodType domain.OIDCAuthMethodType `json:"auth_method_type,omitempty"`
|
||||
PostLogoutRedirectURIs []string `json:"post_logout_redirect_uris,omitempty"`
|
||||
IsDevMode bool `json:"is_dev_mode,omitempty"`
|
||||
AccessTokenType domain.OIDCTokenType `json:"access_token_type,omitempty"`
|
||||
AccessTokenRoleAssertion bool `json:"access_token_role_assertion,omitempty"`
|
||||
IDTokenRoleAssertion bool `json:"id_token_role_assertion,omitempty"`
|
||||
IDTokenUserinfoAssertion bool `json:"id_token_userinfo_assertion,omitempty"`
|
||||
ClockSkew time.Duration `json:"clock_skew,omitempty"`
|
||||
AdditionalOrigins []string `json:"additional_origins,omitempty"`
|
||||
PublicKeys map[string][]byte `json:"public_keys,omitempty"`
|
||||
ProjectID string `json:"project_id,omitempty"`
|
||||
ProjectRoleKeys []string `json:"project_role_keys,omitempty"`
|
||||
AccessTokenLifetime time.Duration `json:"access_token_lifetime,omitempty"`
|
||||
IDTokenLifetime time.Duration `json:"id_token_lifetime,omitempty"`
|
||||
}
|
||||
|
||||
//go:embed embed/oidc_client_by_id.sql
|
||||
var oidcClientQuery string
|
||||
|
||||
func (q *Queries) GetOIDCClientByID(ctx context.Context, clientID string, getKeys bool) (client *OIDCClient, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
client, err = database.QueryJSONObject[OIDCClient](ctx, q.client, oidcClientQuery,
|
||||
authz.GetInstance(ctx).InstanceID(), clientID, getKeys,
|
||||
)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(err, "QUERY-wu6Ee", "Errors.App.NotFound")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-ieR7R", "Errors.Internal")
|
||||
}
|
||||
return client, err
|
||||
}
|
167
internal/query/oidc_client_test.go
Normal file
167
internal/query/oidc_client_test.go
Normal file
@ -0,0 +1,167 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
_ "embed"
|
||||
"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"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
zerrors "github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed testdata/oidc_client_jwt.json
|
||||
testdataOidcClientJWT string
|
||||
//go:embed testdata/oidc_client_public.json
|
||||
testdataOidcClientPublic string
|
||||
//go:embed testdata/oidc_client_secret.json
|
||||
testdataOidcClientSecret string
|
||||
)
|
||||
|
||||
func TestQueries_GetOIDCClientByID(t *testing.T) {
|
||||
expQuery := regexp.QuoteMeta(oidcClientQuery)
|
||||
cols := []string{"client"}
|
||||
pubkey := `-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2ufAL1b72bIy1ar+Ws6b
|
||||
GohJJQFB7dfRapDqeqM8Ukp6CVdPzq/pOz1viAq50yzWZJryF+2wshFAKGF9A2/B
|
||||
2Yf9bJXPZ/KbkFrYT3NTvYDkvlaSTl9mMnzrU29s48F1PTWKfB+C3aMsOEG1BufV
|
||||
s63qF4nrEPjSbhljIco9FZq4XppIzhMQ0fDdA/+XygCJqvuaL0LibM1KrlUdnu71
|
||||
YekhSJjEPnvOisXIk4IXywoGIOwtjxkDvNItQvaMVldr4/kb6uvbgdWwq5EwBZXq
|
||||
low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx
|
||||
6QIDAQAB
|
||||
-----END RSA PUBLIC KEY-----
|
||||
`
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mock sqlExpectation
|
||||
want *OIDCClient
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "no rows",
|
||||
mock: mockQueryErr(expQuery, sql.ErrNoRows, "instanceID", "clientID", true),
|
||||
wantErr: zerrors.ThrowNotFound(sql.ErrNoRows, "QUERY-wu6Ee", "Errors.App.NotFound"),
|
||||
},
|
||||
{
|
||||
name: "internal error",
|
||||
mock: mockQueryErr(expQuery, sql.ErrConnDone, "instanceID", "clientID", true),
|
||||
wantErr: zerrors.ThrowInternal(sql.ErrConnDone, "QUERY-ieR7R", "Errors.Internal"),
|
||||
},
|
||||
{
|
||||
name: "jwt client",
|
||||
mock: mockQuery(expQuery, cols, []driver.Value{testdataOidcClientJWT}, "instanceID", "clientID", true),
|
||||
want: &OIDCClient{
|
||||
InstanceID: "230690539048009730",
|
||||
AppID: "236647088211886082",
|
||||
State: domain.AppStateActive,
|
||||
ClientID: "236647088211951618@tests",
|
||||
ClientSecret: nil,
|
||||
RedirectURIs: []string{"http://localhost:9999/auth/callback"},
|
||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode, domain.OIDCGrantTypeRefreshToken},
|
||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
||||
AuthMethodType: domain.OIDCAuthMethodTypePrivateKeyJWT,
|
||||
PostLogoutRedirectURIs: []string{"https://example.com/logout"},
|
||||
IsDevMode: true,
|
||||
AccessTokenType: domain.OIDCTokenTypeJWT,
|
||||
AccessTokenRoleAssertion: true,
|
||||
IDTokenRoleAssertion: true,
|
||||
IDTokenUserinfoAssertion: true,
|
||||
ClockSkew: 1000000000,
|
||||
AdditionalOrigins: []string{"https://example.com"},
|
||||
ProjectID: "236645808328409090",
|
||||
PublicKeys: map[string][]byte{"236647201860747266": []byte(pubkey)},
|
||||
ProjectRoleKeys: []string{"role1", "role2"},
|
||||
AccessTokenLifetime: 43200000000000,
|
||||
IDTokenLifetime: 43200000000000,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "public client",
|
||||
mock: mockQuery(expQuery, cols, []driver.Value{testdataOidcClientPublic}, "instanceID", "clientID", true),
|
||||
want: &OIDCClient{
|
||||
InstanceID: "230690539048009730",
|
||||
AppID: "236646457053020162",
|
||||
State: domain.AppStateActive,
|
||||
ClientID: "236646457053085698@tests",
|
||||
ClientSecret: nil,
|
||||
RedirectURIs: []string{"http://localhost:9999/auth/callback"},
|
||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
||||
AuthMethodType: domain.OIDCAuthMethodTypeNone,
|
||||
PostLogoutRedirectURIs: nil,
|
||||
IsDevMode: true,
|
||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||
AccessTokenRoleAssertion: false,
|
||||
IDTokenRoleAssertion: false,
|
||||
IDTokenUserinfoAssertion: false,
|
||||
ClockSkew: 0,
|
||||
AdditionalOrigins: nil,
|
||||
PublicKeys: nil,
|
||||
ProjectID: "236645808328409090",
|
||||
ProjectRoleKeys: []string{"role1", "role2"},
|
||||
AccessTokenLifetime: 43200000000000,
|
||||
IDTokenLifetime: 43200000000000,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "secret client",
|
||||
mock: mockQuery(expQuery, cols, []driver.Value{testdataOidcClientSecret}, "instanceID", "clientID", true),
|
||||
want: &OIDCClient{
|
||||
InstanceID: "230690539048009730",
|
||||
AppID: "236646858984783874",
|
||||
State: domain.AppStateActive,
|
||||
ClientID: "236646858984849410@tests",
|
||||
ClientSecret: &crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeHash,
|
||||
Algorithm: "bcrypt",
|
||||
Crypted: []byte(`$2a$14$OzZ0XEZZEtD13py/EPba2evsS6WcKZ5orVMj9pWHEGEHmLu2h3PFq`),
|
||||
},
|
||||
RedirectURIs: []string{"http://localhost:9999/auth/callback"},
|
||||
ResponseTypes: []domain.OIDCResponseType{0},
|
||||
GrantTypes: []domain.OIDCGrantType{0},
|
||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
||||
AuthMethodType: domain.OIDCAuthMethodTypeBasic,
|
||||
PostLogoutRedirectURIs: nil,
|
||||
IsDevMode: true,
|
||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||
AccessTokenRoleAssertion: false,
|
||||
IDTokenRoleAssertion: false,
|
||||
IDTokenUserinfoAssertion: false,
|
||||
ClockSkew: 0,
|
||||
AdditionalOrigins: nil,
|
||||
PublicKeys: nil,
|
||||
ProjectID: "236645808328409090",
|
||||
ProjectRoleKeys: []string{"role1", "role2"},
|
||||
AccessTokenLifetime: 43200000000000,
|
||||
IDTokenLifetime: 43200000000000,
|
||||
},
|
||||
},
|
||||
}
|
||||
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.GetOIDCClientByID(ctx, "clientID", true)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -21,24 +21,24 @@ var (
|
||||
", members.user_id" +
|
||||
", members.roles" +
|
||||
", projections.login_names3.login_name" +
|
||||
", 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" +
|
||||
", projections.users10_humans.email" +
|
||||
", projections.users10_humans.first_name" +
|
||||
", projections.users10_humans.last_name" +
|
||||
", projections.users10_humans.display_name" +
|
||||
", projections.users10_machines.name" +
|
||||
", projections.users10_humans.avatar_key" +
|
||||
", projections.users10.type" +
|
||||
", COUNT(*) OVER () " +
|
||||
"FROM projections.org_members4 AS members " +
|
||||
"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.users10_humans " +
|
||||
"ON members.user_id = projections.users10_humans.user_id " +
|
||||
"AND members.instance_id = projections.users10_humans.instance_id " +
|
||||
"LEFT JOIN projections.users10_machines " +
|
||||
"ON members.user_id = projections.users10_machines.user_id " +
|
||||
"AND members.instance_id = projections.users10_machines.instance_id " +
|
||||
"LEFT JOIN projections.users10 " +
|
||||
"ON members.user_id = projections.users10.id " +
|
||||
"AND members.instance_id = projections.users10.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 " +
|
||||
|
@ -21,24 +21,24 @@ var (
|
||||
", members.user_id" +
|
||||
", members.roles" +
|
||||
", projections.login_names3.login_name" +
|
||||
", 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" +
|
||||
", projections.users10_humans.email" +
|
||||
", projections.users10_humans.first_name" +
|
||||
", projections.users10_humans.last_name" +
|
||||
", projections.users10_humans.display_name" +
|
||||
", projections.users10_machines.name" +
|
||||
", projections.users10_humans.avatar_key" +
|
||||
", projections.users10.type" +
|
||||
", COUNT(*) OVER () " +
|
||||
"FROM projections.project_grant_members4 AS members " +
|
||||
"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.users10_humans " +
|
||||
"ON members.user_id = projections.users10_humans.user_id " +
|
||||
"AND members.instance_id = projections.users10_humans.instance_id " +
|
||||
"LEFT JOIN projections.users10_machines " +
|
||||
"ON members.user_id = projections.users10_machines.user_id " +
|
||||
"AND members.instance_id = projections.users10_machines.instance_id " +
|
||||
"LEFT JOIN projections.users10 " +
|
||||
"ON members.user_id = projections.users10.id " +
|
||||
"AND members.instance_id = projections.users10.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 " +
|
||||
|
@ -21,24 +21,24 @@ var (
|
||||
", members.user_id" +
|
||||
", members.roles" +
|
||||
", projections.login_names3.login_name" +
|
||||
", 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" +
|
||||
", projections.users10_humans.email" +
|
||||
", projections.users10_humans.first_name" +
|
||||
", projections.users10_humans.last_name" +
|
||||
", projections.users10_humans.display_name" +
|
||||
", projections.users10_machines.name" +
|
||||
", projections.users10_humans.avatar_key" +
|
||||
", projections.users10.type" +
|
||||
", COUNT(*) OVER () " +
|
||||
"FROM projections.project_members4 AS members " +
|
||||
"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.users10_humans " +
|
||||
"ON members.user_id = projections.users10_humans.user_id " +
|
||||
"AND members.instance_id = projections.users10_humans.instance_id " +
|
||||
"LEFT JOIN projections.users10_machines " +
|
||||
"ON members.user_id = projections.users10_machines.user_id " +
|
||||
"AND members.instance_id = projections.users10_machines.instance_id " +
|
||||
"LEFT JOIN projections.users10 " +
|
||||
"ON members.user_id = projections.users10.id " +
|
||||
"AND members.instance_id = projections.users10.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 " +
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
UserTable = "projections.users9"
|
||||
UserTable = "projections.users10"
|
||||
UserHumanTable = UserTable + "_" + UserHumanSuffix
|
||||
UserMachineTable = UserTable + "_" + UserMachineSuffix
|
||||
UserNotifyTable = UserTable + "_" + UserNotifySuffix
|
||||
@ -57,7 +57,7 @@ const (
|
||||
MachineUserInstanceIDCol = "instance_id"
|
||||
MachineNameCol = "name"
|
||||
MachineDescriptionCol = "description"
|
||||
MachineHasSecretCol = "has_secret"
|
||||
MachineSecretCol = "secret"
|
||||
MachineAccessTokenTypeCol = "access_token_type"
|
||||
|
||||
// notify
|
||||
@ -122,7 +122,7 @@ func (*userProjection) Init() *old_handler.Check {
|
||||
handler.NewColumn(MachineUserInstanceIDCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(MachineNameCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(MachineDescriptionCol, handler.ColumnTypeText, handler.Nullable()),
|
||||
handler.NewColumn(MachineHasSecretCol, handler.ColumnTypeBool, handler.Default(false)),
|
||||
handler.NewColumn(MachineSecretCol, handler.ColumnTypeJSONB, handler.Nullable()),
|
||||
handler.NewColumn(MachineAccessTokenTypeCol, handler.ColumnTypeEnum, handler.Default(0)),
|
||||
},
|
||||
handler.NewPrimaryKey(MachineUserInstanceIDCol, MachineUserIDCol),
|
||||
@ -936,7 +936,7 @@ func (p *userProjection) reduceMachineSecretSet(event eventstore.Event) (*handle
|
||||
),
|
||||
handler.AddUpdateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(MachineHasSecretCol, true),
|
||||
handler.NewCol(MachineSecretCol, e.ClientSecret),
|
||||
},
|
||||
[]handler.Condition{
|
||||
handler.NewCond(MachineUserIDCol, e.Aggregate().ID),
|
||||
@ -967,7 +967,7 @@ func (p *userProjection) reduceMachineSecretRemoved(event eventstore.Event) (*ha
|
||||
),
|
||||
handler.AddUpdateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(MachineHasSecretCol, false),
|
||||
handler.NewCol(MachineSecretCol, nil),
|
||||
},
|
||||
[]handler.Condition{
|
||||
handler.NewCond(MachineUserIDCol, e.Aggregate().ID),
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
@ -50,7 +51,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10 (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 +65,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10_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 +80,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users9_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users10_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 +120,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10 (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 +134,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10_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 +149,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users9_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users10_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 +184,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10 (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 +198,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10_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 +213,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users9_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users10_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 +253,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10 (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 +267,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10_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 +282,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users9_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users10_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 +322,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10 (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 +336,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10_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 +351,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users9_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users10_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 +386,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10 (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 +400,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10_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 +415,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users9_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users10_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 +445,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
domain.UserStateInitial,
|
||||
"agg-id",
|
||||
@ -472,7 +473,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
domain.UserStateInitial,
|
||||
"agg-id",
|
||||
@ -500,7 +501,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
domain.UserStateActive,
|
||||
"agg-id",
|
||||
@ -528,7 +529,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10 SET state = $1 WHERE (id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
domain.UserStateActive,
|
||||
"agg-id",
|
||||
@ -556,7 +557,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
domain.UserStateLocked,
|
||||
@ -586,7 +587,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
domain.UserStateActive,
|
||||
@ -616,7 +617,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
domain.UserStateInactive,
|
||||
@ -646,7 +647,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
domain.UserStateActive,
|
||||
@ -676,7 +677,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.users9 WHERE (id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "DELETE FROM projections.users10 WHERE (id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -705,7 +706,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
"username",
|
||||
@ -737,7 +738,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
"id@temporary.domain",
|
||||
@ -774,7 +775,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -783,7 +784,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "UPDATE projections.users10_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 +824,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -832,7 +833,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "UPDATE projections.users10_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 +868,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -876,7 +877,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10_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 +886,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_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 +916,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -924,7 +925,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10_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 +934,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_notifications SET last_phone = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_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 +962,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -970,7 +971,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
nil,
|
||||
nil,
|
||||
@ -979,7 +980,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
nil,
|
||||
nil,
|
||||
@ -1008,7 +1009,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1017,7 +1018,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
nil,
|
||||
nil,
|
||||
@ -1026,7 +1027,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
nil,
|
||||
nil,
|
||||
@ -1055,7 +1056,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1064,7 +1065,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
true,
|
||||
"agg-id",
|
||||
@ -1072,7 +1073,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "UPDATE projections.users10_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -1099,7 +1100,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1108,7 +1109,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_humans SET is_phone_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
true,
|
||||
"agg-id",
|
||||
@ -1116,7 +1117,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "UPDATE projections.users10_notifications SET verified_phone = last_phone WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -1145,7 +1146,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1154,7 +1155,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10_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 +1164,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_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 +1194,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1202,7 +1203,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10_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 +1212,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_notifications SET last_email = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_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 +1240,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1248,7 +1249,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
true,
|
||||
"agg-id",
|
||||
@ -1256,7 +1257,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "UPDATE projections.users10_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -1283,7 +1284,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1292,7 +1293,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_humans SET is_email_verified = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
true,
|
||||
"agg-id",
|
||||
@ -1300,7 +1301,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "UPDATE projections.users10_notifications SET verified_email = last_email WHERE (user_id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -1329,7 +1330,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1338,7 +1339,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
"users/agg-id/avatar",
|
||||
"agg-id",
|
||||
@ -1366,7 +1367,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1375,7 +1376,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_humans SET avatar_key = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
nil,
|
||||
"agg-id",
|
||||
@ -1406,7 +1407,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10 (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 +1421,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users9_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users10_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -1454,7 +1455,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
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)",
|
||||
expectedStmt: "INSERT INTO projections.users10 (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 +1469,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.users9_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedStmt: "INSERT INTO projections.users10_machines (user_id, instance_id, name, description, access_token_type) VALUES ($1, $2, $3, $4, $5)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -1501,7 +1502,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1510,7 +1511,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
"machine-name",
|
||||
"description",
|
||||
@ -1541,7 +1542,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1550,7 +1551,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_machines SET name = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_machines SET name = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
"machine-name",
|
||||
"agg-id",
|
||||
@ -1580,7 +1581,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1589,7 +1590,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_machines SET description = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_machines SET description = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
"description",
|
||||
"agg-id",
|
||||
@ -1627,7 +1628,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
user.MachineSecretSetType,
|
||||
user.AggregateType,
|
||||
[]byte(`{
|
||||
"client_secret": {}
|
||||
"clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"deadbeef"}
|
||||
}`),
|
||||
), user.MachineSecretSetEventMapper),
|
||||
},
|
||||
@ -1638,7 +1639,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1647,9 +1648,13 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_machines SET has_secret = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
true,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeHash,
|
||||
Algorithm: "bcrypt",
|
||||
Crypted: []byte{117, 230, 157, 109, 231, 159},
|
||||
},
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
@ -1659,7 +1664,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reduceMachineSecretSet",
|
||||
name: "reduceMachineSecretRemoved",
|
||||
args: args{
|
||||
event: getEvent(
|
||||
testEvent(
|
||||
@ -1675,7 +1680,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedStmt: "UPDATE projections.users10 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -1684,9 +1689,9 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "UPDATE projections.users9_machines SET has_secret = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedStmt: "UPDATE projections.users10_machines SET secret = $1 WHERE (user_id = $2) AND (instance_id = $3)",
|
||||
expectedArgs: []interface{}{
|
||||
false,
|
||||
nil,
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
@ -1712,7 +1717,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.users9 WHERE (instance_id = $1) AND (resource_owner = $2)",
|
||||
expectedStmt: "DELETE FROM projections.users10 WHERE (instance_id = $1) AND (resource_owner = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"instance-id",
|
||||
"agg-id",
|
||||
@ -1739,7 +1744,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.users9 WHERE (instance_id = $1)",
|
||||
expectedStmt: "DELETE FROM projections.users10 WHERE (instance_id = $1)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
},
|
||||
|
@ -3,12 +3,11 @@ package query
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
errs "errors"
|
||||
"errors"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/call"
|
||||
zitadel_errors "github.com/zitadel/zitadel/internal/errors"
|
||||
@ -166,7 +165,7 @@ func prepareQuotaNotificationsQuery(ctx context.Context, db prepareDatabase) (sq
|
||||
var nextDueThreshold sql.NullInt16
|
||||
err := rows.Scan(&cfg.ID, &cfg.CallURL, &cfg.Percent, &cfg.Repeat, &nextDueThreshold)
|
||||
if err != nil {
|
||||
if errs.Is(err, sql.ErrNoRows) {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zitadel_errors.ThrowNotFound(err, "QUERY-bbqWb", "Errors.QuotaNotification.NotExisting")
|
||||
}
|
||||
return nil, zitadel_errors.ThrowInternal(err, "QUERY-8copS", "Errors.Internal")
|
||||
|
@ -31,7 +31,7 @@ var (
|
||||
` projections.sessions8.user_resource_owner,` +
|
||||
` projections.sessions8.user_checked_at,` +
|
||||
` projections.login_names3.login_name,` +
|
||||
` projections.users9_humans.display_name,` +
|
||||
` projections.users10_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.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` +
|
||||
` LEFT JOIN projections.users10_humans ON projections.sessions8.user_id = projections.users10_humans.user_id AND projections.sessions8.instance_id = projections.users10_humans.instance_id` +
|
||||
` LEFT JOIN projections.users10 ON projections.sessions8.user_id = projections.users10.id AND projections.sessions8.instance_id = projections.users10.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.users9_humans.display_name,` +
|
||||
` projections.users10_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.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` +
|
||||
` LEFT JOIN projections.users10_humans ON projections.sessions8.user_id = projections.users10_humans.user_id AND projections.sessions8.instance_id = projections.users10_humans.instance_id` +
|
||||
` LEFT JOIN projections.users10 ON projections.sessions8.user_id = projections.users10.id AND projections.sessions8.instance_id = projections.users10.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`)
|
||||
|
||||
sessionCols = []string{
|
||||
|
27
internal/query/testdata/oidc_client_jwt.json
vendored
Normal file
27
internal/query/testdata/oidc_client_jwt.json
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"instance_id": "230690539048009730",
|
||||
"app_id": "236647088211886082",
|
||||
"client_id": "236647088211951618@tests",
|
||||
"client_secret": null,
|
||||
"redirect_uris": ["http://localhost:9999/auth/callback"],
|
||||
"response_types": [0],
|
||||
"grant_types": [0, 2],
|
||||
"application_type": 0,
|
||||
"auth_method_type": 3,
|
||||
"post_logout_redirect_uris": ["https://example.com/logout"],
|
||||
"is_dev_mode": true,
|
||||
"access_token_type": 1,
|
||||
"access_token_role_assertion": true,
|
||||
"id_token_role_assertion": true,
|
||||
"id_token_userinfo_assertion": true,
|
||||
"clock_skew": 1000000000,
|
||||
"additional_origins": ["https://example.com"],
|
||||
"project_id": "236645808328409090",
|
||||
"state": 1,
|
||||
"project_role_keys": ["role1", "role2"],
|
||||
"public_keys": {
|
||||
"236647201860747266": "LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFB\nT0NBUThBTUlJQkNnS0NBUUVBMnVmQUwxYjcyYkl5MWFyK1dzNmIKR29oSkpRRkI3ZGZSYXBEcWVx\nTThVa3A2Q1ZkUHpxL3BPejF2aUFxNTB5eldaSnJ5Risyd3NoRkFLR0Y5QTIvQgoyWWY5YkpYUFov\nS2JrRnJZVDNOVHZZRGt2bGFTVGw5bU1uenJVMjlzNDhGMVBUV0tmQitDM2FNc09FRzFCdWZWCnM2\nM3FGNG5yRVBqU2JobGpJY285RlpxNFhwcEl6aE1RMGZEZEEvK1h5Z0NKcXZ1YUwwTGliTTFLcmxV\nZG51NzEKWWVraFNKakVQbnZPaXNYSWs0SVh5d29HSU93dGp4a0R2Tkl0UXZhTVZsZHI0L2tiNnV2\nYmdkV3dxNUV3QlpYcQpsb3cya3lKb3YzOFY0VWsySThrdVhwTGNucnB3NVRpbzJvb2lVRTI3YjB2\nSFpxQktPZWk5VW84OHFDcm4zRUt4CjZRSURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0t\nLS0K"
|
||||
},
|
||||
"access_token_lifetime": 43200000000000,
|
||||
"id_token_lifetime": 43200000000000
|
||||
}
|
25
internal/query/testdata/oidc_client_public.json
vendored
Normal file
25
internal/query/testdata/oidc_client_public.json
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"instance_id": "230690539048009730",
|
||||
"app_id": "236646457053020162",
|
||||
"client_id": "236646457053085698@tests",
|
||||
"client_secret": null,
|
||||
"redirect_uris": ["http://localhost:9999/auth/callback"],
|
||||
"response_types": [0],
|
||||
"grant_types": [0],
|
||||
"application_type": 0,
|
||||
"auth_method_type": 2,
|
||||
"post_logout_redirect_uris": null,
|
||||
"is_dev_mode": true,
|
||||
"access_token_type": 0,
|
||||
"access_token_role_assertion": false,
|
||||
"id_token_role_assertion": false,
|
||||
"id_token_userinfo_assertion": false,
|
||||
"clock_skew": 0,
|
||||
"additional_origins": null,
|
||||
"project_id": "236645808328409090",
|
||||
"state": 1,
|
||||
"project_role_keys": ["role1", "role2"],
|
||||
"public_keys": null,
|
||||
"access_token_lifetime": 43200000000000,
|
||||
"id_token_lifetime": 43200000000000
|
||||
}
|
30
internal/query/testdata/oidc_client_secret.json
vendored
Normal file
30
internal/query/testdata/oidc_client_secret.json
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"instance_id": "230690539048009730",
|
||||
"app_id": "236646858984783874",
|
||||
"client_id": "236646858984849410@tests",
|
||||
"client_secret": {
|
||||
"KeyID": "",
|
||||
"Crypted": "JDJhJDE0JE96WjBYRVpaRXREMTNweS9FUGJhMmV2c1M2V2NLWjVvclZNajlwV0hFR0VIbUx1MmgzUEZx",
|
||||
"Algorithm": "bcrypt",
|
||||
"CryptoType": 1
|
||||
},
|
||||
"redirect_uris": ["http://localhost:9999/auth/callback"],
|
||||
"response_types": [0],
|
||||
"grant_types": [0],
|
||||
"application_type": 0,
|
||||
"auth_method_type": 0,
|
||||
"post_logout_redirect_uris": null,
|
||||
"is_dev_mode": true,
|
||||
"access_token_type": 0,
|
||||
"access_token_role_assertion": false,
|
||||
"id_token_role_assertion": false,
|
||||
"id_token_userinfo_assertion": false,
|
||||
"clock_skew": 0,
|
||||
"additional_origins": null,
|
||||
"project_id": "236645808328409090",
|
||||
"state": 1,
|
||||
"project_role_keys": ["role1", "role2"],
|
||||
"public_keys": null,
|
||||
"access_token_lifetime": 43200000000000,
|
||||
"id_token_lifetime": 43200000000000
|
||||
}
|
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/call"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
@ -91,7 +92,7 @@ type Phone struct {
|
||||
type Machine struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
HasSecret bool `json:"has_secret,omitempty"`
|
||||
Secret *crypto.CryptoValue `json:"secret,omitempty"`
|
||||
AccessTokenType domain.OIDCTokenType `json:"access_token_type,omitempty"`
|
||||
}
|
||||
|
||||
@ -270,8 +271,8 @@ var (
|
||||
name: projection.MachineDescriptionCol,
|
||||
table: machineTable,
|
||||
}
|
||||
MachineHasSecretCol = Column{
|
||||
name: projection.MachineHasSecretCol,
|
||||
MachineSecretCol = Column{
|
||||
name: projection.MachineSecretCol,
|
||||
table: machineTable,
|
||||
}
|
||||
MachineAccessTokenTypeCol = Column{
|
||||
@ -740,7 +741,7 @@ func prepareUserQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
|
||||
MachineUserIDCol.identifier(),
|
||||
MachineNameCol.identifier(),
|
||||
MachineDescriptionCol.identifier(),
|
||||
MachineHasSecretCol.identifier(),
|
||||
MachineSecretCol.identifier(),
|
||||
MachineAccessTokenTypeCol.identifier(),
|
||||
countColumn.identifier(),
|
||||
).
|
||||
@ -777,7 +778,7 @@ func prepareUserQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
|
||||
machineID := sql.NullString{}
|
||||
name := sql.NullString{}
|
||||
description := sql.NullString{}
|
||||
hasSecret := sql.NullBool{}
|
||||
var secret *crypto.CryptoValue
|
||||
accessTokenType := sql.NullInt32{}
|
||||
|
||||
err := row.Scan(
|
||||
@ -806,7 +807,7 @@ func prepareUserQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
|
||||
&machineID,
|
||||
&name,
|
||||
&description,
|
||||
&hasSecret,
|
||||
&secret,
|
||||
&accessTokenType,
|
||||
&count,
|
||||
)
|
||||
@ -838,7 +839,7 @@ func prepareUserQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
|
||||
u.Machine = &Machine{
|
||||
Name: name.String,
|
||||
Description: description.String,
|
||||
HasSecret: hasSecret.Bool,
|
||||
Secret: secret,
|
||||
AccessTokenType: domain.OIDCTokenType(accessTokenType.Int32),
|
||||
}
|
||||
}
|
||||
@ -1210,7 +1211,7 @@ func prepareUsersQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilde
|
||||
MachineUserIDCol.identifier(),
|
||||
MachineNameCol.identifier(),
|
||||
MachineDescriptionCol.identifier(),
|
||||
MachineHasSecretCol.identifier(),
|
||||
MachineSecretCol.identifier(),
|
||||
MachineAccessTokenTypeCol.identifier(),
|
||||
countColumn.identifier()).
|
||||
From(userTable.identifier()).
|
||||
@ -1249,7 +1250,7 @@ func prepareUsersQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilde
|
||||
machineID := sql.NullString{}
|
||||
name := sql.NullString{}
|
||||
description := sql.NullString{}
|
||||
hasSecret := sql.NullBool{}
|
||||
secret := new(crypto.CryptoValue)
|
||||
accessTokenType := sql.NullInt32{}
|
||||
|
||||
err := rows.Scan(
|
||||
@ -1278,7 +1279,7 @@ func prepareUsersQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilde
|
||||
&machineID,
|
||||
&name,
|
||||
&description,
|
||||
&hasSecret,
|
||||
secret,
|
||||
&accessTokenType,
|
||||
&count,
|
||||
)
|
||||
@ -1309,7 +1310,7 @@ func prepareUsersQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilde
|
||||
u.Machine = &Machine{
|
||||
Name: name.String,
|
||||
Description: description.String,
|
||||
HasSecret: hasSecret.Bool,
|
||||
Secret: secret,
|
||||
AccessTokenType: domain.OIDCTokenType(accessTokenType.Int32),
|
||||
}
|
||||
}
|
||||
|
@ -39,38 +39,38 @@ var (
|
||||
"method_type",
|
||||
"count",
|
||||
}
|
||||
prepareActiveAuthMethodTypesStmt = `SELECT projections.users9_notifications.password_set,` +
|
||||
prepareActiveAuthMethodTypesStmt = `SELECT projections.users10_notifications.password_set,` +
|
||||
` auth_method_types.method_type,` +
|
||||
` user_idps_count.count` +
|
||||
` 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` +
|
||||
` FROM projections.users10` +
|
||||
` LEFT JOIN projections.users10_notifications ON projections.users10.id = projections.users10_notifications.user_id AND projections.users10.instance_id = projections.users10_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.users9.id AND auth_method_types.instance_id = projections.users9.instance_id` +
|
||||
` ON auth_method_types.user_id = projections.users10.id AND auth_method_types.instance_id = projections.users10.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.users9.id AND user_idps_count.instance_id = projections.users9.instance_id` +
|
||||
` ON user_idps_count.user_id = projections.users10.id AND user_idps_count.instance_id = projections.users10.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms`
|
||||
prepareActiveAuthMethodTypesCols = []string{
|
||||
"password_set",
|
||||
"method_type",
|
||||
"idps_count",
|
||||
}
|
||||
prepareAuthMethodTypesRequiredStmt = `SELECT projections.users9_notifications.password_set,` +
|
||||
prepareAuthMethodTypesRequiredStmt = `SELECT projections.users10_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.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` +
|
||||
` FROM projections.users10` +
|
||||
` LEFT JOIN projections.users10_notifications ON projections.users10.id = projections.users10_notifications.user_id AND projections.users10.instance_id = projections.users10_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.users9.id AND auth_method_types.instance_id = projections.users9.instance_id` +
|
||||
` ON auth_method_types.user_id = projections.users10.id AND auth_method_types.instance_id = projections.users10.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.users9.id AND user_idps_count.instance_id = projections.users9.instance_id` +
|
||||
` ON user_idps_count.user_id = projections.users10.id AND user_idps_count.instance_id = projections.users10.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.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` +
|
||||
` ON (auth_methods_force_mfa.aggregate_id = projections.users10.instance_id OR auth_methods_force_mfa.aggregate_id = projections.users10.resource_owner) AND auth_methods_force_mfa.instance_id = projections.users10.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms
|
||||
`
|
||||
prepareAuthMethodTypesRequiredCols = []string{
|
||||
|
@ -23,14 +23,14 @@ var (
|
||||
", projections.user_grants3.roles" +
|
||||
", projections.user_grants3.state" +
|
||||
", projections.user_grants3.user_id" +
|
||||
", 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.users10.username" +
|
||||
", projections.users10.type" +
|
||||
", projections.users10.resource_owner" +
|
||||
", projections.users10_humans.first_name" +
|
||||
", projections.users10_humans.last_name" +
|
||||
", projections.users10_humans.email" +
|
||||
", projections.users10_humans.display_name" +
|
||||
", projections.users10_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.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.users10 ON projections.user_grants3.user_id = projections.users10.id AND projections.user_grants3.instance_id = projections.users10.instance_id" +
|
||||
" LEFT JOIN projections.users10_humans ON projections.user_grants3.user_id = projections.users10_humans.user_id AND projections.user_grants3.instance_id = projections.users10_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.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.users10.username" +
|
||||
", projections.users10.type" +
|
||||
", projections.users10.resource_owner" +
|
||||
", projections.users10_humans.first_name" +
|
||||
", projections.users10_humans.last_name" +
|
||||
", projections.users10_humans.email" +
|
||||
", projections.users10_humans.display_name" +
|
||||
", projections.users10_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.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.users10 ON projections.user_grants3.user_id = projections.users10.id AND projections.user_grants3.instance_id = projections.users10.instance_id" +
|
||||
" LEFT JOIN projections.users10_humans ON projections.user_grants3.user_id = projections.users10_humans.user_id AND projections.user_grants3.instance_id = projections.users10_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" +
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
errs "github.com/zitadel/zitadel/internal/errors"
|
||||
@ -22,43 +23,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.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,` +
|
||||
userQuery = `SELECT projections.users10.id,` +
|
||||
` projections.users10.creation_date,` +
|
||||
` projections.users10.change_date,` +
|
||||
` projections.users10.resource_owner,` +
|
||||
` projections.users10.sequence,` +
|
||||
` projections.users10.state,` +
|
||||
` projections.users10.type,` +
|
||||
` projections.users10.username,` +
|
||||
` login_names.loginnames,` +
|
||||
` preferred_login_name.login_name,` +
|
||||
` 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,` +
|
||||
` projections.users10_humans.user_id,` +
|
||||
` projections.users10_humans.first_name,` +
|
||||
` projections.users10_humans.last_name,` +
|
||||
` projections.users10_humans.nick_name,` +
|
||||
` projections.users10_humans.display_name,` +
|
||||
` projections.users10_humans.preferred_language,` +
|
||||
` projections.users10_humans.gender,` +
|
||||
` projections.users10_humans.avatar_key,` +
|
||||
` projections.users10_humans.email,` +
|
||||
` projections.users10_humans.is_email_verified,` +
|
||||
` projections.users10_humans.phone,` +
|
||||
` projections.users10_humans.is_phone_verified,` +
|
||||
` projections.users10_machines.user_id,` +
|
||||
` projections.users10_machines.name,` +
|
||||
` projections.users10_machines.description,` +
|
||||
` projections.users10_machines.secret,` +
|
||||
` projections.users10_machines.access_token_type,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` 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` +
|
||||
` FROM projections.users10` +
|
||||
` LEFT JOIN projections.users10_humans ON projections.users10.id = projections.users10_humans.user_id AND projections.users10.instance_id = projections.users10_humans.instance_id` +
|
||||
` LEFT JOIN projections.users10_machines ON projections.users10.id = projections.users10_machines.user_id AND projections.users10.instance_id = projections.users10_machines.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + loginNamesQuery + `) AS login_names` +
|
||||
` ON login_names.user_id = projections.users9.id AND login_names.instance_id = projections.users9.instance_id` +
|
||||
` ON login_names.user_id = projections.users10.id AND login_names.instance_id = projections.users10.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + preferredLoginNameQuery + `) AS preferred_login_name` +
|
||||
` ON preferred_login_name.user_id = projections.users9.id AND preferred_login_name.instance_id = projections.users9.instance_id` +
|
||||
` ON preferred_login_name.user_id = projections.users10.id AND preferred_login_name.instance_id = projections.users10.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
userCols = []string{
|
||||
"id",
|
||||
@ -71,7 +72,7 @@ var (
|
||||
"username",
|
||||
"loginnames",
|
||||
"login_name",
|
||||
//human
|
||||
// human
|
||||
"user_id",
|
||||
"first_name",
|
||||
"last_name",
|
||||
@ -84,29 +85,29 @@ var (
|
||||
"is_email_verified",
|
||||
"phone",
|
||||
"is_phone_verified",
|
||||
//machine
|
||||
// machine
|
||||
"user_id",
|
||||
"name",
|
||||
"description",
|
||||
"has_secret",
|
||||
"secret",
|
||||
"access_token_type",
|
||||
"count",
|
||||
}
|
||||
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` +
|
||||
profileQuery = `SELECT projections.users10.id,` +
|
||||
` projections.users10.creation_date,` +
|
||||
` projections.users10.change_date,` +
|
||||
` projections.users10.resource_owner,` +
|
||||
` projections.users10.sequence,` +
|
||||
` projections.users10_humans.user_id,` +
|
||||
` projections.users10_humans.first_name,` +
|
||||
` projections.users10_humans.last_name,` +
|
||||
` projections.users10_humans.nick_name,` +
|
||||
` projections.users10_humans.display_name,` +
|
||||
` projections.users10_humans.preferred_language,` +
|
||||
` projections.users10_humans.gender,` +
|
||||
` projections.users10_humans.avatar_key` +
|
||||
` FROM projections.users10` +
|
||||
` LEFT JOIN projections.users10_humans ON projections.users10.id = projections.users10_humans.user_id AND projections.users10.instance_id = projections.users10_humans.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
profileCols = []string{
|
||||
"id",
|
||||
@ -123,16 +124,16 @@ var (
|
||||
"gender",
|
||||
"avatar_key",
|
||||
}
|
||||
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` +
|
||||
emailQuery = `SELECT projections.users10.id,` +
|
||||
` projections.users10.creation_date,` +
|
||||
` projections.users10.change_date,` +
|
||||
` projections.users10.resource_owner,` +
|
||||
` projections.users10.sequence,` +
|
||||
` projections.users10_humans.user_id,` +
|
||||
` projections.users10_humans.email,` +
|
||||
` projections.users10_humans.is_email_verified` +
|
||||
` FROM projections.users10` +
|
||||
` LEFT JOIN projections.users10_humans ON projections.users10.id = projections.users10_humans.user_id AND projections.users10.instance_id = projections.users10_humans.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
emailCols = []string{
|
||||
"id",
|
||||
@ -144,16 +145,16 @@ var (
|
||||
"email",
|
||||
"is_email_verified",
|
||||
}
|
||||
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` +
|
||||
phoneQuery = `SELECT projections.users10.id,` +
|
||||
` projections.users10.creation_date,` +
|
||||
` projections.users10.change_date,` +
|
||||
` projections.users10.resource_owner,` +
|
||||
` projections.users10.sequence,` +
|
||||
` projections.users10_humans.user_id,` +
|
||||
` projections.users10_humans.phone,` +
|
||||
` projections.users10_humans.is_phone_verified` +
|
||||
` FROM projections.users10` +
|
||||
` LEFT JOIN projections.users10_humans ON projections.users10.id = projections.users10_humans.user_id AND projections.users10.instance_id = projections.users10_humans.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
phoneCols = []string{
|
||||
"id",
|
||||
@ -165,14 +166,14 @@ var (
|
||||
"phone",
|
||||
"is_phone_verified",
|
||||
}
|
||||
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` +
|
||||
userUniqueQuery = `SELECT projections.users10.id,` +
|
||||
` projections.users10.state,` +
|
||||
` projections.users10.username,` +
|
||||
` projections.users10_humans.user_id,` +
|
||||
` projections.users10_humans.email,` +
|
||||
` projections.users10_humans.is_email_verified` +
|
||||
` FROM projections.users10` +
|
||||
` LEFT JOIN projections.users10_humans ON projections.users10.id = projections.users10_humans.user_id AND projections.users10.instance_id = projections.users10_humans.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
userUniqueCols = []string{
|
||||
"id",
|
||||
@ -182,40 +183,40 @@ var (
|
||||
"email",
|
||||
"is_email_verified",
|
||||
}
|
||||
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,` +
|
||||
notifyUserQuery = `SELECT projections.users10.id,` +
|
||||
` projections.users10.creation_date,` +
|
||||
` projections.users10.change_date,` +
|
||||
` projections.users10.resource_owner,` +
|
||||
` projections.users10.sequence,` +
|
||||
` projections.users10.state,` +
|
||||
` projections.users10.type,` +
|
||||
` projections.users10.username,` +
|
||||
` login_names.loginnames,` +
|
||||
` preferred_login_name.login_name,` +
|
||||
` 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,` +
|
||||
` projections.users10_humans.user_id,` +
|
||||
` projections.users10_humans.first_name,` +
|
||||
` projections.users10_humans.last_name,` +
|
||||
` projections.users10_humans.nick_name,` +
|
||||
` projections.users10_humans.display_name,` +
|
||||
` projections.users10_humans.preferred_language,` +
|
||||
` projections.users10_humans.gender,` +
|
||||
` projections.users10_humans.avatar_key,` +
|
||||
` projections.users10_notifications.user_id,` +
|
||||
` projections.users10_notifications.last_email,` +
|
||||
` projections.users10_notifications.verified_email,` +
|
||||
` projections.users10_notifications.last_phone,` +
|
||||
` projections.users10_notifications.verified_phone,` +
|
||||
` projections.users10_notifications.password_set,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` 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` +
|
||||
` FROM projections.users10` +
|
||||
` LEFT JOIN projections.users10_humans ON projections.users10.id = projections.users10_humans.user_id AND projections.users10.instance_id = projections.users10_humans.instance_id` +
|
||||
` LEFT JOIN projections.users10_notifications ON projections.users10.id = projections.users10_notifications.user_id AND projections.users10.instance_id = projections.users10_notifications.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + loginNamesQuery + `) AS login_names` +
|
||||
` ON login_names.user_id = projections.users9.id AND login_names.instance_id = projections.users9.instance_id` +
|
||||
` ON login_names.user_id = projections.users10.id AND login_names.instance_id = projections.users10.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + preferredLoginNameQuery + `) AS preferred_login_name` +
|
||||
` ON preferred_login_name.user_id = projections.users9.id AND preferred_login_name.instance_id = projections.users9.instance_id` +
|
||||
` ON preferred_login_name.user_id = projections.users10.id AND preferred_login_name.instance_id = projections.users10.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
notifyUserCols = []string{
|
||||
"id",
|
||||
@ -228,7 +229,7 @@ var (
|
||||
"username",
|
||||
"loginnames",
|
||||
"login_name",
|
||||
//human
|
||||
// human
|
||||
"user_id",
|
||||
"first_name",
|
||||
"last_name",
|
||||
@ -237,7 +238,7 @@ var (
|
||||
"preferred_language",
|
||||
"gender",
|
||||
"avatar_key",
|
||||
//machine
|
||||
// machine
|
||||
"user_id",
|
||||
"last_email",
|
||||
"verified_email",
|
||||
@ -246,43 +247,43 @@ var (
|
||||
"password_set",
|
||||
"count",
|
||||
}
|
||||
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,` +
|
||||
usersQuery = `SELECT projections.users10.id,` +
|
||||
` projections.users10.creation_date,` +
|
||||
` projections.users10.change_date,` +
|
||||
` projections.users10.resource_owner,` +
|
||||
` projections.users10.sequence,` +
|
||||
` projections.users10.state,` +
|
||||
` projections.users10.type,` +
|
||||
` projections.users10.username,` +
|
||||
` login_names.loginnames,` +
|
||||
` preferred_login_name.login_name,` +
|
||||
` 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,` +
|
||||
` projections.users10_humans.user_id,` +
|
||||
` projections.users10_humans.first_name,` +
|
||||
` projections.users10_humans.last_name,` +
|
||||
` projections.users10_humans.nick_name,` +
|
||||
` projections.users10_humans.display_name,` +
|
||||
` projections.users10_humans.preferred_language,` +
|
||||
` projections.users10_humans.gender,` +
|
||||
` projections.users10_humans.avatar_key,` +
|
||||
` projections.users10_humans.email,` +
|
||||
` projections.users10_humans.is_email_verified,` +
|
||||
` projections.users10_humans.phone,` +
|
||||
` projections.users10_humans.is_phone_verified,` +
|
||||
` projections.users10_machines.user_id,` +
|
||||
` projections.users10_machines.name,` +
|
||||
` projections.users10_machines.description,` +
|
||||
` projections.users10_machines.secret,` +
|
||||
` projections.users10_machines.access_token_type,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` 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` +
|
||||
` FROM projections.users10` +
|
||||
` LEFT JOIN projections.users10_humans ON projections.users10.id = projections.users10_humans.user_id AND projections.users10.instance_id = projections.users10_humans.instance_id` +
|
||||
` LEFT JOIN projections.users10_machines ON projections.users10.id = projections.users10_machines.user_id AND projections.users10.instance_id = projections.users10_machines.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + loginNamesQuery + `) AS login_names` +
|
||||
` ON login_names.user_id = projections.users9.id AND login_names.instance_id = projections.users9.instance_id` +
|
||||
` ON login_names.user_id = projections.users10.id AND login_names.instance_id = projections.users10.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + preferredLoginNameQuery + `) AS preferred_login_name` +
|
||||
` ON preferred_login_name.user_id = projections.users9.id AND preferred_login_name.instance_id = projections.users9.instance_id` +
|
||||
` ON preferred_login_name.user_id = projections.users10.id AND preferred_login_name.instance_id = projections.users10.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
usersCols = []string{
|
||||
"id",
|
||||
@ -295,7 +296,7 @@ var (
|
||||
"username",
|
||||
"loginnames",
|
||||
"login_name",
|
||||
//human
|
||||
// human
|
||||
"user_id",
|
||||
"first_name",
|
||||
"last_name",
|
||||
@ -308,11 +309,11 @@ var (
|
||||
"is_email_verified",
|
||||
"phone",
|
||||
"is_phone_verified",
|
||||
//machine
|
||||
// machine
|
||||
"user_id",
|
||||
"name",
|
||||
"description",
|
||||
"has_secret",
|
||||
"secret",
|
||||
"access_token_type",
|
||||
"count",
|
||||
}
|
||||
@ -365,7 +366,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
"username",
|
||||
database.TextArray[string]{"login_name1", "login_name2"},
|
||||
"login_name1",
|
||||
//human
|
||||
// human
|
||||
"id",
|
||||
"first_name",
|
||||
"last_name",
|
||||
@ -378,7 +379,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
true,
|
||||
"phone",
|
||||
true,
|
||||
//machine
|
||||
// machine
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
@ -432,7 +433,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
"username",
|
||||
database.TextArray[string]{"login_name1", "login_name2"},
|
||||
"login_name1",
|
||||
//human
|
||||
// human
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
@ -445,11 +446,11 @@ func Test_UserPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
//machine
|
||||
// machine
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
nil,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
1,
|
||||
},
|
||||
@ -469,7 +470,71 @@ func Test_UserPrepares(t *testing.T) {
|
||||
Machine: &Machine{
|
||||
Name: "name",
|
||||
Description: "description",
|
||||
HasSecret: true,
|
||||
Secret: nil,
|
||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareUserQuery machine with secret found",
|
||||
prepare: prepareUserQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQuery(
|
||||
regexp.QuoteMeta(userQuery),
|
||||
userCols,
|
||||
[]driver.Value{
|
||||
"id",
|
||||
testNow,
|
||||
testNow,
|
||||
"resource_owner",
|
||||
uint64(20211108),
|
||||
domain.UserStateActive,
|
||||
domain.UserTypeMachine,
|
||||
"username",
|
||||
database.TextArray[string]{"login_name1", "login_name2"},
|
||||
"login_name1",
|
||||
// human
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// machine
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
`{"CryptoType":1,"Algorithm":"bcrypt","Crypted":"deadbeef"}`,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
1,
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &User{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
ResourceOwner: "resource_owner",
|
||||
Sequence: 20211108,
|
||||
State: domain.UserStateActive,
|
||||
Type: domain.UserTypeMachine,
|
||||
Username: "username",
|
||||
LoginNames: database.TextArray[string]{"login_name1", "login_name2"},
|
||||
PreferredLoginName: "login_name1",
|
||||
Machine: &Machine{
|
||||
Name: "name",
|
||||
Description: "description",
|
||||
Secret: &crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeHash,
|
||||
Algorithm: "bcrypt",
|
||||
Crypted: []byte{117, 230, 157, 109, 231, 159},
|
||||
},
|
||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||
},
|
||||
},
|
||||
@ -875,7 +940,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
"username",
|
||||
database.TextArray[string]{"login_name1", "login_name2"},
|
||||
"login_name1",
|
||||
//human
|
||||
// human
|
||||
"id",
|
||||
"first_name",
|
||||
"last_name",
|
||||
@ -938,7 +1003,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
"username",
|
||||
database.TextArray[string]{"login_name1", "login_name2"},
|
||||
"login_name1",
|
||||
//human
|
||||
// human
|
||||
"id",
|
||||
"first_name",
|
||||
"last_name",
|
||||
@ -1019,7 +1084,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
"username",
|
||||
database.TextArray[string]{"login_name1", "login_name2"},
|
||||
"login_name1",
|
||||
//human
|
||||
// human
|
||||
"id",
|
||||
"first_name",
|
||||
"last_name",
|
||||
@ -1032,7 +1097,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
true,
|
||||
"phone",
|
||||
true,
|
||||
//machine
|
||||
// machine
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
@ -1094,7 +1159,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
"username",
|
||||
database.TextArray[string]{"login_name1", "login_name2"},
|
||||
"login_name1",
|
||||
//human
|
||||
// human
|
||||
"id",
|
||||
"first_name",
|
||||
"last_name",
|
||||
@ -1107,7 +1172,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
true,
|
||||
"phone",
|
||||
true,
|
||||
//machine
|
||||
// machine
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
@ -1125,7 +1190,7 @@ func Test_UserPrepares(t *testing.T) {
|
||||
"username",
|
||||
database.TextArray[string]{"login_name1", "login_name2"},
|
||||
"login_name1",
|
||||
//human
|
||||
// human
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
@ -1138,11 +1203,11 @@ func Test_UserPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
//machine
|
||||
// machine
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
`{"CryptoType":1,"Algorithm":"bcrypt","Crypted":"deadbeef"}`,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
},
|
||||
},
|
||||
@ -1190,9 +1255,13 @@ func Test_UserPrepares(t *testing.T) {
|
||||
LoginNames: database.TextArray[string]{"login_name1", "login_name2"},
|
||||
PreferredLoginName: "login_name1",
|
||||
Machine: &Machine{
|
||||
Name: "name",
|
||||
Description: "description",
|
||||
HasSecret: true,
|
||||
Name: "name",
|
||||
Description: "description",
|
||||
Secret: &crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeHash,
|
||||
Algorithm: "bcrypt",
|
||||
Crypted: []byte{117, 230, 157, 109, 231, 159},
|
||||
},
|
||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||
},
|
||||
},
|
||||
|
@ -4,12 +4,12 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
zerrors "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"
|
||||
@ -40,23 +40,17 @@ func (q *Queries) GetOIDCUserInfo(ctx context.Context, userID string, roleAudien
|
||||
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,
|
||||
userInfo, err := database.QueryJSONObject[OIDCUserInfo](ctx, q.client, oidcUserInfoQuery,
|
||||
userID, authz.GetInstance(ctx).InstanceID(), database.TextArray[string](roleAudience),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Oath6", "Errors.Internal")
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, zerrors.ThrowNotFound(err, "QUERY-Eey2a", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
userInfo := new(OIDCUserInfo)
|
||||
if err = json.Unmarshal(data, userInfo); err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Vohs6", "Errors.Internal")
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Oath6", "Errors.Internal")
|
||||
}
|
||||
if userInfo.User == nil {
|
||||
return nil, errors.ThrowNotFound(nil, "QUERY-ahs4S", "Errors.User.NotFound")
|
||||
return nil, zerrors.ThrowNotFound(nil, "QUERY-ahs4S", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
return userInfo, nil
|
||||
|
@ -68,14 +68,6 @@ func TestQueries_GetOIDCUserInfo(t *testing.T) {
|
||||
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{
|
||||
|
Loading…
x
Reference in New Issue
Block a user