feat(crypto): use passwap for machine and app secrets (#7657)

* feat(crypto): use passwap for machine and app secrets

* fix command package tests

* add hash generator command test

* naming convention, fix query tests

* rename PasswordHasher and cleanup start commands

* add reducer tests

* fix intergration tests, cleanup old config

* add app secret unit tests

* solve setup panics

* fix push of updated events

* add missing event translations

* update documentation

* solve linter errors

* remove nolint:SA1019 as it doesn't seem to help anyway

* add nolint to deprecated filter usage

* update users migration version

* remove unused ClientSecret from APIConfigChangedEvent

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Tim Möhlmann 2024-04-05 12:35:49 +03:00 committed by GitHub
parent 5931fb8f28
commit 2089992d75
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
135 changed files with 2407 additions and 1779 deletions

View File

@ -115,6 +115,10 @@ core_integration_setup:
core_integration_test: core_integration_setup core_integration_test: core_integration_setup
go test -tags=integration -race -p 1 -coverprofile=profile.cov -coverpkg=./internal/...,./cmd/... ./... go test -tags=integration -race -p 1 -coverprofile=profile.cov -coverpkg=./internal/...,./cmd/... ./...
.PHONY: core_integration_test_fast
core_integration_test_fast: core_integration_setup
go test -tags=integration -p 1 ./...
.PHONY: console_lint .PHONY: console_lint
console_lint: console_lint:
cd console && \ cd console && \

View File

@ -428,7 +428,6 @@ SystemAPIUsers:
SystemDefaults: SystemDefaults:
SecretGenerators: SecretGenerators:
PasswordSaltCost: 14 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_PASSWORDSALTCOST
MachineKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_MACHINEKEYSIZE MachineKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_MACHINEKEYSIZE
ApplicationKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_APPLICATIONKEYSIZE ApplicationKeySize: 2048 # ZITADEL_SYSTEMDEFAULTS_SECRETGENERATORS_APPLICATIONKEYSIZE
PasswordHasher: PasswordHasher:
@ -482,6 +481,13 @@ SystemDefaults:
# - "md5" # - "md5"
# - "scrypt" # - "scrypt"
# - "pbkdf2" # verifier for all pbkdf2 hash modes. # - "pbkdf2" # verifier for all pbkdf2 hash modes.
SecretHasher:
# Set hasher configuration for machine users, API and OIDC client secrets.
# See PasswordHasher for all possible options
Hasher:
Algorithm: "bcrypt" # ZITADEL_SYSTEMDEFAULTS_SECRETHASHER_HASHER_ALGORITHM
Cost: 4 # ZITADEL_SYSTEMDEFAULTS_SECRETHASHER_HASHER_COST
Verifiers:
Multifactors: Multifactors:
OTP: OTP:
# If this is empty, the issuer is the requested domain # If this is empty, the issuer is the requested domain
@ -590,7 +596,6 @@ DefaultInstance:
# date format: 2023-01-01T00:00:00Z # date format: 2023-01-01T00:00:00Z
ExpirationDate: # ZITADEL_DEFAULTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE ExpirationDate: # ZITADEL_DEFAULTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE
SecretGenerators: SecretGenerators:
PasswordSaltCost: 14 # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_PASSWORDSALTCOST
ClientSecret: ClientSecret:
Length: 64 # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_CLIENTSECRET_LENGTH Length: 64 # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_CLIENTSECRET_LENGTH
IncludeLowerLetters: true # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_CLIENTSECRET_INCLUDELOWERLETTERS IncludeLowerLetters: true # ZITADEL_DEFAULTINSTANCE_SECRETGENERATORS_CLIENTSECRET_INCLUDELOWERLETTERS

View File

@ -23,5 +23,5 @@ func (mig *User11AddLowerFieldsToVerifiedEmail) Execute(ctx context.Context, _ e
} }
func (mig *User11AddLowerFieldsToVerifiedEmail) String() string { func (mig *User11AddLowerFieldsToVerifiedEmail) String() string {
return "25_user11_add_lower_fields_to_verified_email" return "25_user12_add_lower_fields_to_verified_email"
} }

View File

@ -1,2 +1,2 @@
ALTER TABLE IF EXISTS projections.users11_notifications ADD COLUMN IF NOT EXISTS verified_email_lower TEXT GENERATED ALWAYS AS (lower(verified_email)) STORED; ALTER TABLE IF EXISTS projections.users12_notifications ADD COLUMN IF NOT EXISTS verified_email_lower TEXT GENERATED ALWAYS AS (lower(verified_email)) STORED;
CREATE INDEX IF NOT EXISTS users11_notifications_email_search ON projections.users11_notifications (instance_id, verified_email_lower); CREATE INDEX IF NOT EXISTS users12_notifications_email_search ON projections.users12_notifications (instance_id, verified_email_lower);

View File

@ -439,7 +439,7 @@ func startAPIs(
} }
apis.RegisterHandlerOnPrefix(openapi.HandlerPrefix, openAPIHandler) apis.RegisterHandlerOnPrefix(openapi.HandlerPrefix, openAPIHandler)
oidcServer, err := oidc.NewServer(config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler, limitingAccessInterceptor, config.Log.Slog()) oidcServer, err := oidc.NewServer(ctx, config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler, limitingAccessInterceptor, config.Log.Slog(), config.SystemDefaults.SecretHasher)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to start oidc provider: %w", err) return nil, fmt.Errorf("unable to start oidc provider: %w", err)
} }

View File

@ -64,7 +64,7 @@ ZITADEL hashes all Passwords and Client Secrets in an non reversible way to furt
Passwords and secrets are always hashed with a random salt and stored as an encoded string that contains the Algorithm, its Parameters, Salt and Hash. Passwords and secrets are always hashed with a random salt and stored as an encoded string that contains the Algorithm, its Parameters, Salt and Hash.
The storage encoding used by ZITADEL is Modular Crypt Format and a full reference can be found in our [Passwap library](https://github.com/zitadel/passwap#encoding). The storage encoding used by ZITADEL is Modular Crypt Format and a full reference can be found in our [Passwap library](https://github.com/zitadel/passwap#encoding).
The following hash algorithms are supported for user passwords: The following hash algorithms are supported:
- argon2i / id[^1] - argon2i / id[^1]
- bcrypt (Default) - bcrypt (Default)
@ -82,8 +82,6 @@ This allows to increase cost along with growing computing power.
ZITADEL allows to import user passwords from systems that use any of the above hashing algorithms. ZITADEL allows to import user passwords from systems that use any of the above hashing algorithms.
::: :::
Client Secrets always use bcrypt.
### Encrypted Secrets ### Encrypted Secrets
Some secrets cannot be hashed because they need to be used in their raw form. These include: Some secrets cannot be hashed because they need to be used in their raw form. These include:

View File

@ -292,7 +292,7 @@ func getFileFromGCS(ctx context.Context, input *admin_pb.ImportDataRequest_GCSIn
return ioutil.ReadAll(reader) return ioutil.ReadAll(reader)
} }
func importOrg1(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, ctxData authz.CtxData, org *admin_pb.DataOrg, success *admin_pb.ImportDataSuccess, count *counts, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode, appSecretGenerator crypto.Generator) error { func importOrg1(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, ctxData authz.CtxData, org *admin_pb.DataOrg, success *admin_pb.ImportDataSuccess, count *counts, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode crypto.Generator) error {
_, err := s.command.AddOrgWithID(ctx, org.GetOrg().GetName(), ctxData.UserID, ctxData.ResourceOwner, org.GetOrgId(), []string{}) _, err := s.command.AddOrgWithID(ctx, org.GetOrg().GetName(), ctxData.UserID, ctxData.ResourceOwner, org.GetOrgId(), []string{})
if err != nil { if err != nil {
*errors = append(*errors, &admin_pb.ImportDataError{Type: "org", Id: org.GetOrgId(), Message: err.Error()}) *errors = append(*errors, &admin_pb.ImportDataError{Type: "org", Id: org.GetOrgId(), Message: err.Error()})
@ -325,7 +325,7 @@ func importOrg1(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataEr
*errors = append(*errors, &admin_pb.ImportDataError{Type: "domain_policy", Id: org.GetOrgId(), Message: err.Error()}) *errors = append(*errors, &admin_pb.ImportDataError{Type: "domain_policy", Id: org.GetOrgId(), Message: err.Error()})
} }
} }
return importResources(ctx, s, errors, successOrg, org, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode, appSecretGenerator) return importResources(ctx, s, errors, successOrg, org, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode)
} }
func importLabelPolicy(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, org *admin_pb.DataOrg) error { func importLabelPolicy(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, org *admin_pb.DataOrg) error {
@ -584,13 +584,13 @@ func importProjects(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDa
return nil return nil
} }
func importOIDCApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts, appSecretGenerator crypto.Generator) error { func importOIDCApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts) error {
if org.OidcApps == nil { if org.OidcApps == nil {
return nil return nil
} }
for _, app := range org.GetOidcApps() { for _, app := range org.GetOidcApps() {
logging.Debugf("import oidcapplication: %s", app.GetAppId()) logging.Debugf("import oidcapplication: %s", app.GetAppId())
_, err := s.command.AddOIDCApplicationWithID(ctx, management.AddOIDCAppRequestToDomain(app.App), org.GetOrgId(), app.GetAppId(), appSecretGenerator) _, err := s.command.AddOIDCApplicationWithID(ctx, management.AddOIDCAppRequestToDomain(app.App), org.GetOrgId(), app.GetAppId())
if err != nil { if err != nil {
*errors = append(*errors, &admin_pb.ImportDataError{Type: "oidc_app", Id: app.GetAppId(), Message: err.Error()}) *errors = append(*errors, &admin_pb.ImportDataError{Type: "oidc_app", Id: app.GetAppId(), Message: err.Error()})
if isCtxTimeout(ctx) { if isCtxTimeout(ctx) {
@ -605,13 +605,13 @@ func importOIDCApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDa
return nil return nil
} }
func importAPIApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts, appSecretGenerator crypto.Generator) error { func importAPIApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts) error {
if org.ApiApps == nil { if org.ApiApps == nil {
return nil return nil
} }
for _, app := range org.GetApiApps() { for _, app := range org.GetApiApps() {
logging.Debugf("import apiapplication: %s", app.GetAppId()) logging.Debugf("import apiapplication: %s", app.GetAppId())
_, err := s.command.AddAPIApplicationWithID(ctx, management.AddAPIAppRequestToDomain(app.GetApp()), org.GetOrgId(), app.GetAppId(), appSecretGenerator) _, err := s.command.AddAPIApplicationWithID(ctx, management.AddAPIAppRequestToDomain(app.GetApp()), org.GetOrgId(), app.GetAppId())
if err != nil { if err != nil {
*errors = append(*errors, &admin_pb.ImportDataError{Type: "api_app", Id: app.GetAppId(), Message: err.Error()}) *errors = append(*errors, &admin_pb.ImportDataError{Type: "api_app", Id: app.GetAppId(), Message: err.Error()})
if isCtxTimeout(ctx) { if isCtxTimeout(ctx) {
@ -700,7 +700,7 @@ func importProjectRoles(ctx context.Context, s *Server, errors *[]*admin_pb.Impo
return nil return nil
} }
func importResources(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode, appSecretGenerator crypto.Generator) error { func importResources(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode crypto.Generator) error {
if err := importOrgDomains(ctx, s, errors, successOrg, org); err != nil { if err := importOrgDomains(ctx, s, errors, successOrg, org); err != nil {
return err return err
} }
@ -742,10 +742,10 @@ func importResources(ctx context.Context, s *Server, errors *[]*admin_pb.ImportD
if err := importProjects(ctx, s, errors, successOrg, org, count); err != nil { if err := importProjects(ctx, s, errors, successOrg, org, count); err != nil {
return err return err
} }
if err := importOIDCApps(ctx, s, errors, successOrg, org, count, appSecretGenerator); err != nil { if err := importOIDCApps(ctx, s, errors, successOrg, org, count); err != nil {
return err return err
} }
if err := importAPIApps(ctx, s, errors, successOrg, org, count, appSecretGenerator); err != nil { if err := importAPIApps(ctx, s, errors, successOrg, org, count); err != nil {
return err return err
} }
if err := importAppKeys(ctx, s, errors, successOrg, org, count); err != nil { if err := importAppKeys(ctx, s, errors, successOrg, org, count); err != nil {
@ -1023,10 +1023,6 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm
success := &admin_pb.ImportDataSuccess{} success := &admin_pb.ImportDataSuccess{}
count := &counts{} count := &counts{}
appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg)
if err != nil {
return nil, nil, err
}
initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.userCodeAlg) initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.userCodeAlg)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -1064,7 +1060,7 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm
count.appKeysCount += len(org.GetAppKeys()) count.appKeysCount += len(org.GetAppKeys())
} }
for _, org := range orgs { for _, org := range orgs {
if err = importOrg1(ctx, s, &errors, ctxData, org, success, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode, appSecretGenerator); err != nil { if err = importOrg1(ctx, s, &errors, ctxData, org, success, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode); err != nil {
return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err return &admin_pb.ImportDataResponse{Errors: errors, Success: success}, count, err
} }
} }

View File

@ -30,7 +30,6 @@ type Server struct {
query *query.Queries query *query.Queries
assetsAPIDomain func(context.Context) string assetsAPIDomain func(context.Context) string
userCodeAlg crypto.EncryptionAlgorithm userCodeAlg crypto.EncryptionAlgorithm
passwordHashAlg crypto.HashAlgorithm
auditLogRetention time.Duration auditLogRetention time.Duration
} }
@ -53,7 +52,6 @@ func CreateServer(
query: query, query: query,
assetsAPIDomain: assets.AssetAPI(externalSecure), assetsAPIDomain: assets.AssetAPI(externalSecure),
userCodeAlg: userCodeAlg, userCodeAlg: userCodeAlg,
passwordHashAlg: crypto.NewBCrypt(sd.SecretGenerators.PasswordSaltCost),
auditLogRetention: auditLogRetention, auditLogRetention: auditLogRetention,
} }
} }

View File

@ -8,7 +8,6 @@ import (
change_grpc "github.com/zitadel/zitadel/internal/api/grpc/change" change_grpc "github.com/zitadel/zitadel/internal/api/grpc/change"
object_grpc "github.com/zitadel/zitadel/internal/api/grpc/object" object_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
project_grpc "github.com/zitadel/zitadel/internal/api/grpc/project" project_grpc "github.com/zitadel/zitadel/internal/api/grpc/project"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/repository/project" "github.com/zitadel/zitadel/internal/repository/project"
@ -81,11 +80,7 @@ func (s *Server) ListAppChanges(ctx context.Context, req *mgmt_pb.ListAppChanges
} }
func (s *Server) AddOIDCApp(ctx context.Context, req *mgmt_pb.AddOIDCAppRequest) (*mgmt_pb.AddOIDCAppResponse, error) { func (s *Server) AddOIDCApp(ctx context.Context, req *mgmt_pb.AddOIDCAppRequest) (*mgmt_pb.AddOIDCAppResponse, error) {
appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) app, err := s.command.AddOIDCApplication(ctx, AddOIDCAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
app, err := s.command.AddOIDCApplication(ctx, AddOIDCAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID, appSecretGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -110,11 +105,7 @@ func (s *Server) AddSAMLApp(ctx context.Context, req *mgmt_pb.AddSAMLAppRequest)
} }
func (s *Server) AddAPIApp(ctx context.Context, req *mgmt_pb.AddAPIAppRequest) (*mgmt_pb.AddAPIAppResponse, error) { func (s *Server) AddAPIApp(ctx context.Context, req *mgmt_pb.AddAPIAppRequest) (*mgmt_pb.AddAPIAppResponse, error) {
appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) app, err := s.command.AddAPIApplication(ctx, AddAPIAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
app, err := s.command.AddAPIApplication(ctx, AddAPIAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID, appSecretGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -209,11 +200,7 @@ func (s *Server) RemoveApp(ctx context.Context, req *mgmt_pb.RemoveAppRequest) (
} }
func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, req *mgmt_pb.RegenerateOIDCClientSecretRequest) (*mgmt_pb.RegenerateOIDCClientSecretResponse, error) { func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, req *mgmt_pb.RegenerateOIDCClientSecretRequest) (*mgmt_pb.RegenerateOIDCClientSecretResponse, error) {
appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) config, err := s.command.ChangeOIDCApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
config, err := s.command.ChangeOIDCApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID, appSecretGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -228,11 +215,7 @@ func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, req *mgmt_pb.Re
} }
func (s *Server) RegenerateAPIClientSecret(ctx context.Context, req *mgmt_pb.RegenerateAPIClientSecretRequest) (*mgmt_pb.RegenerateAPIClientSecretResponse, error) { func (s *Server) RegenerateAPIClientSecret(ctx context.Context, req *mgmt_pb.RegenerateAPIClientSecretRequest) (*mgmt_pb.RegenerateAPIClientSecretResponse, error) {
appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) config, err := s.command.ChangeAPIApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
config, err := s.command.ChangeAPIApplicationSecret(ctx, req.ProjectId, req.AppId, authz.GetCtxData(ctx).OrgID, appSecretGenerator)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -27,7 +27,6 @@ type Server struct {
query *query.Queries query *query.Queries
systemDefaults systemdefaults.SystemDefaults systemDefaults systemdefaults.SystemDefaults
assetAPIPrefix func(context.Context) string assetAPIPrefix func(context.Context) string
passwordHashAlg crypto.HashAlgorithm
userCodeAlg crypto.EncryptionAlgorithm userCodeAlg crypto.EncryptionAlgorithm
externalSecure bool externalSecure bool
} }
@ -44,7 +43,6 @@ func CreateServer(
query: query, query: query,
systemDefaults: sd, systemDefaults: sd,
assetAPIPrefix: assets.AssetAPI(externalSecure), assetAPIPrefix: assets.AssetAPI(externalSecure),
passwordHashAlg: crypto.NewBCrypt(sd.SecretGenerators.PasswordSaltCost),
userCodeAlg: userCodeAlg, userCodeAlg: userCodeAlg,
externalSecure: externalSecure, externalSecure: externalSecure,
} }

View File

@ -800,18 +800,13 @@ func (s *Server) RemoveMachineKey(ctx context.Context, req *mgmt_pb.RemoveMachin
} }
func (s *Server) GenerateMachineSecret(ctx context.Context, req *mgmt_pb.GenerateMachineSecretRequest) (*mgmt_pb.GenerateMachineSecretResponse, error) { func (s *Server) GenerateMachineSecret(ctx context.Context, req *mgmt_pb.GenerateMachineSecretRequest) (*mgmt_pb.GenerateMachineSecretResponse, error) {
// use SecretGeneratorTypeAppSecret as the secrets will be used in the client_credentials grant like a client secret
secretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg)
if err != nil {
return nil, err
}
user, err := s.getUserByID(ctx, req.GetUserId()) user, err := s.getUserByID(ctx, req.GetUserId())
if err != nil { if err != nil {
return nil, err return nil, err
} }
set := new(command.GenerateMachineSecret) set := new(command.GenerateMachineSecret)
details, err := s.command.GenerateMachineSecret(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, secretGenerator, set) details, err := s.command.GenerateMachineSecret(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, set)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -73,7 +73,7 @@ func MachineToPb(view *query.Machine) *user_pb.Machine {
return &user_pb.Machine{ return &user_pb.Machine{
Name: view.Name, Name: view.Name,
Description: view.Description, Description: view.Description,
HasSecret: view.Secret != nil, HasSecret: view.EncodedSecret != "",
AccessTokenType: AccessTokenTypeToPb(view.AccessTokenType), AccessTokenType: AccessTokenTypeToPb(view.AccessTokenType),
} }
} }

View File

@ -109,7 +109,7 @@ func machineToPb(userQ *query.Machine) *user.MachineUser {
return &user.MachineUser{ return &user.MachineUser{
Name: userQ.Name, Name: userQ.Name,
Description: userQ.Description, Description: userQ.Description,
HasSecret: userQ.Secret != nil, HasSecret: userQ.EncodedSecret != "",
AccessTokenType: accessTokenTypeToPb(userQ.AccessTokenType), AccessTokenType: accessTokenTypeToPb(userQ.AccessTokenType),
} }
} }

View File

@ -1050,8 +1050,13 @@ func (s *Server) verifyClientSecret(ctx context.Context, client *query.OIDCClien
if secret == "" { if secret == "" {
return oidc.ErrInvalidClient().WithDescription("empty client secret") return oidc.ErrInvalidClient().WithDescription("empty client secret")
} }
if err = crypto.CompareHash(client.ClientSecret, []byte(secret), s.hashAlg); err != nil { ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify")
updated, err := s.hasher.Verify(client.HashedSecret, secret)
spanPasswordComparison.EndWithError(err)
if err != nil {
s.command.OIDCSecretCheckFailed(ctx, client.AppID, client.ProjectID, client.Settings.ResourceOwner)
return oidc.ErrInvalidClient().WithParent(err).WithDescription("invalid secret") return oidc.ErrInvalidClient().WithParent(err).WithDescription("invalid secret")
} }
s.command.OIDCSecretCheckSucceeded(ctx, client.AppID, client.ProjectID, client.Settings.ResourceOwner, updated)
return nil return nil
} }

View File

@ -7,8 +7,8 @@ import (
"github.com/zitadel/oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -41,15 +41,18 @@ func (s *Server) clientCredentialsAuth(ctx context.Context, clientID, clientSecr
if err != nil { if err != nil {
return nil, err // defaults to server error return nil, err // defaults to server error
} }
if user.Machine == nil || user.Machine.Secret == nil { if user.Machine == nil || user.Machine.EncodedSecret == "" {
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDC-pieP8", "Errors.User.Machine.Secret.NotExisting") return nil, zerrors.ThrowPreconditionFailed(nil, "OIDC-pieP8", "Errors.User.Machine.Secret.NotExisting")
} }
if err = crypto.CompareHash(user.Machine.Secret, []byte(clientSecret), s.hashAlg); err != nil { ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify")
updated, err := s.hasher.Verify(user.Machine.EncodedSecret, clientSecret)
spanPasswordComparison.EndWithError(err)
if err != nil {
s.command.MachineSecretCheckFailed(ctx, user.ID, user.ResourceOwner) s.command.MachineSecretCheckFailed(ctx, user.ID, user.ResourceOwner)
return nil, zerrors.ThrowInvalidArgument(err, "OIDC-VoXo6", "Errors.User.Machine.Secret.Invalid") return nil, zerrors.ThrowInvalidArgument(err, "OIDC-VoXo6", "Errors.User.Machine.Secret.Invalid")
} }
s.command.MachineSecretCheckSucceeded(ctx, user.ID, user.ResourceOwner) s.command.MachineSecretCheckSucceeded(ctx, user.ID, user.ResourceOwner, updated)
return &clientCredentialsClient{ return &clientCredentialsClient{
id: clientID, id: clientID,
user: user, user: user,

View File

@ -11,7 +11,6 @@ import (
"github.com/zitadel/oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
@ -149,8 +148,8 @@ func (s *Server) introspectionClientAuth(ctx context.Context, cc *op.ClientCrede
return client.ClientID, client.ProjectID, nil return client.ClientID, client.ProjectID, nil
} }
if client.ClientSecret != nil { if client.HashedSecret != "" {
if err := crypto.CompareHash(client.ClientSecret, []byte(cc.ClientSecret), s.hashAlg); err != nil { if err := s.introspectionClientSecretAuth(ctx, client, cc.ClientSecret); err != nil {
return "", "", oidc.ErrUnauthorizedClient().WithParent(err) return "", "", oidc.ErrUnauthorizedClient().WithParent(err)
} }
return client.ClientID, client.ProjectID, nil return client.ClientID, client.ProjectID, nil
@ -167,6 +166,35 @@ func (s *Server) introspectionClientAuth(ctx context.Context, cc *op.ClientCrede
} }
} }
var errNoAppType = errors.New("introspection client without app type")
func (s *Server) introspectionClientSecretAuth(ctx context.Context, client *query.IntrospectionClient, secret string) error {
var (
successCommand func(ctx context.Context, appID, projectID, resourceOwner, updated string)
failedCommand func(ctx context.Context, appID, projectID, resourceOwner string)
)
switch client.AppType {
case query.AppTypeAPI:
successCommand = s.command.APISecretCheckSucceeded
failedCommand = s.command.APISecretCheckFailed
case query.AppTypeOIDC:
successCommand = s.command.OIDCSecretCheckSucceeded
failedCommand = s.command.OIDCSecretCheckFailed
default:
return zerrors.ThrowInternal(errNoAppType, "OIDC-ooD5Ot", "Errors.Internal")
}
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify")
updated, err := s.hasher.Verify(client.HashedSecret, secret)
spanPasswordComparison.EndWithError(err)
if err != nil {
failedCommand(ctx, client.AppID, client.ProjectID, client.ResourceOwner)
return err
}
successCommand(ctx, client.AppID, client.ProjectID, client.ResourceOwner, updated)
return nil
}
// clientFromCredentials parses the client ID early, // clientFromCredentials parses the client ID early,
// and makes a single query for the client for either auth methods. // 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) { func (s *Server) clientFromCredentials(ctx context.Context, cc *op.ClientCredentials) (client *query.IntrospectionClient, err error) {

View File

@ -80,6 +80,7 @@ type OPStorage struct {
} }
func NewServer( func NewServer(
ctx context.Context,
config Config, config Config,
defaultLogoutRedirectURI string, defaultLogoutRedirectURI string,
externalSecure bool, externalSecure bool,
@ -93,13 +94,14 @@ func NewServer(
userAgentCookie, instanceHandler func(http.Handler) http.Handler, userAgentCookie, instanceHandler func(http.Handler) http.Handler,
accessHandler *middleware.AccessInterceptor, accessHandler *middleware.AccessInterceptor,
fallbackLogger *slog.Logger, fallbackLogger *slog.Logger,
hashConfig crypto.HashConfig,
) (*Server, error) { ) (*Server, error) {
opConfig, err := createOPConfig(config, defaultLogoutRedirectURI, cryptoKey) opConfig, err := createOPConfig(config, defaultLogoutRedirectURI, cryptoKey)
if err != nil { if err != nil {
return nil, zerrors.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w") return nil, zerrors.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w")
} }
storage := newStorage(config, command, query, repo, encryptionAlg, es, projections, externalSecure) storage := newStorage(config, command, query, repo, encryptionAlg, es, projections, externalSecure)
keyCache := newPublicKeyCache(context.TODO(), config.PublicKeyCacheMaxAge, query.GetPublicKeyByID) keyCache := newPublicKeyCache(ctx, config.PublicKeyCacheMaxAge, query.GetPublicKeyByID)
accessTokenKeySet := newOidcKeySet(keyCache, withKeyExpiryCheck(true)) accessTokenKeySet := newOidcKeySet(keyCache, withKeyExpiryCheck(true))
idTokenHintKeySet := newOidcKeySet(keyCache) idTokenHintKeySet := newOidcKeySet(keyCache)
@ -119,7 +121,10 @@ func NewServer(
if err != nil { if err != nil {
return nil, zerrors.ThrowInternal(err, "OIDC-DAtg3", "cannot create provider") return nil, zerrors.ThrowInternal(err, "OIDC-DAtg3", "cannot create provider")
} }
hasher, err := hashConfig.NewHasher()
if err != nil {
return nil, zerrors.ThrowInternal(err, "OIDC-Aij4e", "cannot create secret hasher")
}
server := &Server{ server := &Server{
LegacyServer: op.NewLegacyServer(provider, endpoints(config.CustomEndpoints)), LegacyServer: op.NewLegacyServer(provider, endpoints(config.CustomEndpoints)),
repo: repo, repo: repo,
@ -133,7 +138,7 @@ func NewServer(
defaultAccessTokenLifetime: config.DefaultAccessTokenLifetime, defaultAccessTokenLifetime: config.DefaultAccessTokenLifetime,
defaultIdTokenLifetime: config.DefaultIdTokenLifetime, defaultIdTokenLifetime: config.DefaultIdTokenLifetime,
fallbackLogger: fallbackLogger, 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. hasher: hasher,
signingKeyAlgorithm: config.SigningKeyAlgorithm, signingKeyAlgorithm: config.SigningKeyAlgorithm,
assetAPIPrefix: assets.AssetAPI(externalSecure), assetAPIPrefix: assets.AssetAPI(externalSecure),
} }

View File

@ -35,7 +35,7 @@ type Server struct {
defaultIdTokenLifetime time.Duration defaultIdTokenLifetime time.Duration
fallbackLogger *slog.Logger fallbackLogger *slog.Logger
hashAlg crypto.HashAlgorithm hasher *crypto.Hasher
signingKeyAlgorithm string signingKeyAlgorithm string
assetAPIPrefix func(ctx context.Context) string assetAPIPrefix func(ctx context.Context) string
} }

View File

@ -6,6 +6,7 @@ import (
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"fmt"
"math/big" "math/big"
"net/http" "net/http"
"strconv" "strconv"
@ -34,8 +35,9 @@ type Commands struct {
jobs sync.WaitGroup jobs sync.WaitGroup
checkPermission domain.PermissionCheck checkPermission domain.PermissionCheck
newCode cryptoCodeFunc newEncryptedCode encrypedCodeFunc
newCodeWithDefault cryptoCodeWithDefaultFunc newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
newHashedSecret hashedSecretFunc
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
static static.Storage static static.Storage
@ -49,8 +51,8 @@ type Commands struct {
smtpEncryption crypto.EncryptionAlgorithm smtpEncryption crypto.EncryptionAlgorithm
smsEncryption crypto.EncryptionAlgorithm smsEncryption crypto.EncryptionAlgorithm
userEncryption crypto.EncryptionAlgorithm userEncryption crypto.EncryptionAlgorithm
userPasswordHasher *crypto.PasswordHasher userPasswordHasher *crypto.Hasher
codeAlg crypto.HashAlgorithm secretHasher *crypto.Hasher
machineKeySize int machineKeySize int
applicationKeySize int applicationKeySize int
domainVerificationAlg crypto.EncryptionAlgorithm domainVerificationAlg crypto.EncryptionAlgorithm
@ -106,6 +108,15 @@ func StartCommands(
idGenerator := id.SonyFlakeGenerator() idGenerator := id.SonyFlakeGenerator()
// reuse the oidcEncryption to be able to handle both tokens in the interceptor later on // reuse the oidcEncryption to be able to handle both tokens in the interceptor later on
sessionAlg := oidcEncryption sessionAlg := oidcEncryption
secretHasher, err := defaults.SecretHasher.NewHasher()
if err != nil {
return nil, fmt.Errorf("secret hasher: %w", err)
}
userPasswordHasher, err := defaults.PasswordHasher.NewHasher()
if err != nil {
return nil, fmt.Errorf("password hasher: %w", err)
}
repo = &Commands{ repo = &Commands{
eventstore: es, eventstore: es,
static: staticStore, static: staticStore,
@ -123,14 +134,20 @@ func StartCommands(
smtpEncryption: smtpEncryption, smtpEncryption: smtpEncryption,
smsEncryption: smsEncryption, smsEncryption: smsEncryption,
userEncryption: userEncryption, userEncryption: userEncryption,
userPasswordHasher: userPasswordHasher,
secretHasher: secretHasher,
machineKeySize: int(defaults.SecretGenerators.MachineKeySize),
applicationKeySize: int(defaults.SecretGenerators.ApplicationKeySize),
domainVerificationAlg: domainVerificationEncryption, domainVerificationAlg: domainVerificationEncryption,
domainVerificationGenerator: crypto.NewEncryptionGenerator(defaults.DomainVerification.VerificationGenerator, domainVerificationEncryption),
domainVerificationValidator: api_http.ValidateDomain,
keyAlgorithm: oidcEncryption, keyAlgorithm: oidcEncryption,
certificateAlgorithm: samlEncryption, certificateAlgorithm: samlEncryption,
webauthnConfig: webAuthN, webauthnConfig: webAuthN,
httpClient: httpClient, httpClient: httpClient,
checkPermission: permissionCheck, checkPermission: permissionCheck,
newCode: newCryptoCode, newEncryptedCode: newEncryptedCode,
newCodeWithDefault: newCryptoCodeWithDefaultConfig, newEncryptedCodeWithDefault: newEncryptedCodeWithDefaultConfig,
sessionTokenCreator: sessionTokenCreator(idGenerator, sessionAlg), sessionTokenCreator: sessionTokenCreator(idGenerator, sessionAlg),
sessionTokenVerifier: sessionTokenVerifier, sessionTokenVerifier: sessionTokenVerifier,
defaultAccessTokenLifetime: defaultAccessTokenLifetime, defaultAccessTokenLifetime: defaultAccessTokenLifetime,
@ -145,25 +162,17 @@ func StartCommands(
GrpcServiceExisting: func(service string) bool { return false }, GrpcServiceExisting: func(service string) bool { return false },
GrpcMethodExisting: func(method string) bool { return false }, GrpcMethodExisting: func(method string) bool { return false },
ActionFunctionExisting: domain.FunctionExists(), ActionFunctionExisting: domain.FunctionExists(),
} multifactors: domain.MultifactorConfigs{
repo.codeAlg = crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost)
repo.userPasswordHasher, err = defaults.PasswordHasher.PasswordHasher()
if err != nil {
return nil, err
}
repo.machineKeySize = int(defaults.SecretGenerators.MachineKeySize)
repo.applicationKeySize = int(defaults.SecretGenerators.ApplicationKeySize)
repo.multifactors = domain.MultifactorConfigs{
OTP: domain.OTPConfig{ OTP: domain.OTPConfig{
CryptoMFA: otpEncryption, CryptoMFA: otpEncryption,
Issuer: defaults.Multifactors.OTP.Issuer, Issuer: defaults.Multifactors.OTP.Issuer,
}, },
},
} }
repo.domainVerificationGenerator = crypto.NewEncryptionGenerator(defaults.DomainVerification.VerificationGenerator, repo.domainVerificationAlg) if defaultSecretGenerators != nil && defaultSecretGenerators.ClientSecret != nil {
repo.domainVerificationValidator = api_http.ValidateDomain repo.newHashedSecret = newHashedSecretWithDefault(secretHasher, defaultSecretGenerators.ClientSecret)
}
return repo, nil return repo, nil
} }

View File

@ -7,27 +7,26 @@ import (
"github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors"
) )
type cryptoCodeFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error) type encrypedCodeFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error)
type cryptoCodeWithDefaultFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (*CryptoCode, error) type encryptedCodeWithDefaultFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (*EncryptedCode, error)
var emptyConfig = &crypto.GeneratorConfig{} var emptyConfig = &crypto.GeneratorConfig{}
type CryptoCode struct { type EncryptedCode struct {
Crypted *crypto.CryptoValue Crypted *crypto.CryptoValue
Plain string Plain string
Expiry time.Duration Expiry time.Duration
} }
func newCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error) { func newEncryptedCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) {
return newCryptoCodeWithDefaultConfig(ctx, filter, typ, alg, emptyConfig) return newEncryptedCodeWithDefaultConfig(ctx, filter, typ, alg, emptyConfig)
} }
func newCryptoCodeWithDefaultConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (*CryptoCode, error) { func newEncryptedCodeWithDefaultConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (*EncryptedCode, error) {
gen, config, err := secretGenerator(ctx, filter, typ, alg, defaultConfig) gen, config, err := encryptedCodeGenerator(ctx, filter, typ, alg, defaultConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -35,41 +34,47 @@ func newCryptoCodeWithDefaultConfig(ctx context.Context, filter preparation.Filt
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &CryptoCode{ return &EncryptedCode{
Crypted: crypted, Crypted: crypted,
Plain: plain, Plain: plain,
Expiry: config.Expiry, Expiry: config.Expiry,
}, nil }, nil
} }
func verifyCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, creation time.Time, expiry time.Duration, crypted *crypto.CryptoValue, plain string) error { func verifyEncryptedCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, creation time.Time, expiry time.Duration, crypted *crypto.CryptoValue, plain string) error {
gen, _, err := secretGenerator(ctx, filter, typ, alg, emptyConfig) gen, _, err := encryptedCodeGenerator(ctx, filter, typ, alg, emptyConfig)
if err != nil { if err != nil {
return err return err
} }
return crypto.VerifyCode(creation, expiry, crypted, plain, gen) return crypto.VerifyCode(creation, expiry, crypted, plain, gen.Alg())
} }
func secretGenerator(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (crypto.Generator, *crypto.GeneratorConfig, error) { func encryptedCodeGenerator(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, defaultConfig *crypto.GeneratorConfig) (crypto.Generator, *crypto.GeneratorConfig, error) {
config, err := secretGeneratorConfigWithDefault(ctx, filter, typ, defaultConfig) config, err := cryptoGeneratorConfigWithDefault(ctx, filter, typ, defaultConfig)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
switch a := alg.(type) { return crypto.NewEncryptionGenerator(*config, alg), config, nil
case crypto.HashAlgorithm: }
return crypto.NewHashGenerator(*config, a), config, nil
case crypto.EncryptionAlgorithm: type hashedSecretFunc func(ctx context.Context, filter preparation.FilterToQueryReducer) (encodedHash, plain string, err error)
return crypto.NewEncryptionGenerator(*config, a), config, nil
default: func newHashedSecretWithDefault(hasher *crypto.Hasher, defaultConfig *crypto.GeneratorConfig) hashedSecretFunc {
return nil, nil, zerrors.ThrowInternalf(nil, "COMMA-RreV6", "Errors.Internal unsupported crypto algorithm type %T", a) return func(ctx context.Context, filter preparation.FilterToQueryReducer) (encodedHash string, plain string, err error) {
config, err := cryptoGeneratorConfigWithDefault(ctx, filter, domain.SecretGeneratorTypeAppSecret, defaultConfig)
if err != nil {
return "", "", err
}
generator := crypto.NewHashGenerator(*config, hasher)
return generator.NewCode()
} }
} }
func secretGeneratorConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType) (*crypto.GeneratorConfig, error) { func cryptoGeneratorConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType) (*crypto.GeneratorConfig, error) {
return secretGeneratorConfigWithDefault(ctx, filter, typ, emptyConfig) return cryptoGeneratorConfigWithDefault(ctx, filter, typ, emptyConfig)
} }
func secretGeneratorConfigWithDefault(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, defaultConfig *crypto.GeneratorConfig) (*crypto.GeneratorConfig, error) { func cryptoGeneratorConfigWithDefault(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, defaultConfig *crypto.GeneratorConfig) (*crypto.GeneratorConfig, error) {
wm := NewInstanceSecretGeneratorConfigWriteModel(ctx, typ) wm := NewInstanceSecretGeneratorConfigWriteModel(ctx, typ)
events, err := filter(ctx, wm.Query()) events, err := filter(ctx, wm.Query())
if err != nil { if err != nil {

View File

@ -8,6 +8,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zitadel/passwap"
"github.com/zitadel/passwap/bcrypt"
"go.uber.org/mock/gomock" "go.uber.org/mock/gomock"
"github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/command/preparation"
@ -15,12 +17,11 @@ import (
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/zerrors"
) )
func mockCode(code string, exp time.Duration) cryptoCodeFunc { func mockEncryptedCode(code string, exp time.Duration) encrypedCodeFunc {
return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error) { return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) {
return &CryptoCode{ return &EncryptedCode{
Crypted: &crypto.CryptoValue{ Crypted: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
Algorithm: "enc", Algorithm: "enc",
@ -33,9 +34,9 @@ func mockCode(code string, exp time.Duration) cryptoCodeFunc {
} }
} }
func mockCodeWithDefault(code string, exp time.Duration) cryptoCodeWithDefaultFunc { func mockEncryptedCodeWithDefault(code string, exp time.Duration) encryptedCodeWithDefaultFunc {
return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.Crypto, _ *crypto.GeneratorConfig) (*CryptoCode, error) { return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.EncryptionAlgorithm, _ *crypto.GeneratorConfig) (*EncryptedCode, error) {
return &CryptoCode{ return &EncryptedCode{
Crypted: &crypto.CryptoValue{ Crypted: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
Algorithm: "enc", Algorithm: "enc",
@ -48,6 +49,12 @@ func mockCodeWithDefault(code string, exp time.Duration) cryptoCodeWithDefaultFu
} }
} }
func mockHashedSecret(secret string) hashedSecretFunc {
return func(_ context.Context, _ preparation.FilterToQueryReducer) (encodedHash string, plain string, err error) {
return secret, secret, nil
}
}
var ( var (
testGeneratorConfig = crypto.GeneratorConfig{ testGeneratorConfig = crypto.GeneratorConfig{
Length: 12, Length: 12,
@ -74,7 +81,7 @@ func testSecretGeneratorAddedEvent(typ domain.SecretGeneratorType) *instance.Sec
func Test_newCryptoCode(t *testing.T) { func Test_newCryptoCode(t *testing.T) {
type args struct { type args struct {
typ domain.SecretGeneratorType typ domain.SecretGeneratorType
alg crypto.Crypto alg crypto.EncryptionAlgorithm
} }
tests := []struct { tests := []struct {
name string name string
@ -87,7 +94,7 @@ func Test_newCryptoCode(t *testing.T) {
eventstore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)), eventstore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)),
args: args{ args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode, typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockHashAlg(gomock.NewController(t)), alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
}, },
wantErr: io.ErrClosedPipe, wantErr: io.ErrClosedPipe,
}, },
@ -98,13 +105,13 @@ func Test_newCryptoCode(t *testing.T) {
)), )),
args: args{ args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode, typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockHashAlg(gomock.NewController(t)), alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
}, },
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := newCryptoCode(context.Background(), tt.eventstore.Filter, tt.args.typ, tt.args.alg) got, err := newEncryptedCode(context.Background(), tt.eventstore.Filter, tt.args.typ, tt.args.alg) //nolint:staticcheck
require.ErrorIs(t, err, tt.wantErr) require.ErrorIs(t, err, tt.wantErr)
if tt.wantErr == nil { if tt.wantErr == nil {
require.NotNil(t, got) require.NotNil(t, got)
@ -120,12 +127,12 @@ func Test_verifyCryptoCode(t *testing.T) {
es := eventstoreExpect(t, expectFilter( es := eventstoreExpect(t, expectFilter(
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)), eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
)) ))
code, err := newCryptoCode(context.Background(), es.Filter, domain.SecretGeneratorTypeVerifyEmailCode, crypto.CreateMockHashAlg(gomock.NewController(t))) code, err := newEncryptedCode(context.Background(), es.Filter, domain.SecretGeneratorTypeVerifyEmailCode, crypto.CreateMockEncryptionAlg(gomock.NewController(t))) //nolint:staticcheck
require.NoError(t, err) require.NoError(t, err)
type args struct { type args struct {
typ domain.SecretGeneratorType typ domain.SecretGeneratorType
alg crypto.Crypto alg crypto.EncryptionAlgorithm
expiry time.Duration expiry time.Duration
crypted *crypto.CryptoValue crypted *crypto.CryptoValue
plain string plain string
@ -141,7 +148,7 @@ func Test_verifyCryptoCode(t *testing.T) {
eventsore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)), eventsore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)),
args: args{ args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode, typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockHashAlg(gomock.NewController(t)), alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
expiry: code.Expiry, expiry: code.Expiry,
crypted: code.Crypted, crypted: code.Crypted,
plain: code.Plain, plain: code.Plain,
@ -155,7 +162,7 @@ func Test_verifyCryptoCode(t *testing.T) {
)), )),
args: args{ args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode, typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockHashAlg(gomock.NewController(t)), alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
expiry: code.Expiry, expiry: code.Expiry,
crypted: code.Crypted, crypted: code.Crypted,
plain: code.Plain, plain: code.Plain,
@ -168,7 +175,7 @@ func Test_verifyCryptoCode(t *testing.T) {
)), )),
args: args{ args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode, typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockHashAlg(gomock.NewController(t)), alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
expiry: code.Expiry, expiry: code.Expiry,
crypted: code.Crypted, crypted: code.Crypted,
plain: "wrong", plain: "wrong",
@ -178,7 +185,7 @@ func Test_verifyCryptoCode(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err := verifyCryptoCode(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, time.Now(), tt.args.expiry, tt.args.crypted, tt.args.plain) err := verifyEncryptedCode(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, time.Now(), tt.args.expiry, tt.args.crypted, tt.args.plain) //nolint:staticcheck
if tt.wantErr { if tt.wantErr {
assert.Error(t, err) assert.Error(t, err)
return return
@ -188,10 +195,10 @@ func Test_verifyCryptoCode(t *testing.T) {
} }
} }
func Test_secretGenerator(t *testing.T) { func Test_cryptoCodeGenerator(t *testing.T) {
type args struct { type args struct {
typ domain.SecretGeneratorType typ domain.SecretGeneratorType
alg crypto.Crypto alg crypto.EncryptionAlgorithm
defaultConfig *crypto.GeneratorConfig defaultConfig *crypto.GeneratorConfig
} }
tests := []struct { tests := []struct {
@ -207,24 +214,11 @@ func Test_secretGenerator(t *testing.T) {
eventsore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)), eventsore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)),
args: args{ args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode, typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockHashAlg(gomock.NewController(t)), alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
defaultConfig: emptyConfig, defaultConfig: emptyConfig,
}, },
wantErr: io.ErrClosedPipe, wantErr: io.ErrClosedPipe,
}, },
{
name: "hash generator",
eventsore: eventstoreExpect(t, expectFilter(
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
)),
args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockHashAlg(gomock.NewController(t)),
defaultConfig: emptyConfig,
},
want: crypto.NewHashGenerator(testGeneratorConfig, crypto.CreateMockHashAlg(gomock.NewController(t))),
wantConf: &testGeneratorConfig,
},
{ {
name: "encryption generator", name: "encryption generator",
eventsore: eventstoreExpect(t, expectFilter( eventsore: eventstoreExpect(t, expectFilter(
@ -238,17 +232,6 @@ func Test_secretGenerator(t *testing.T) {
want: crypto.NewEncryptionGenerator(testGeneratorConfig, crypto.CreateMockEncryptionAlg(gomock.NewController(t))), want: crypto.NewEncryptionGenerator(testGeneratorConfig, crypto.CreateMockEncryptionAlg(gomock.NewController(t))),
wantConf: &testGeneratorConfig, wantConf: &testGeneratorConfig,
}, },
{
name: "hash generator with default config",
eventsore: eventstoreExpect(t, expectFilter()),
args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: crypto.CreateMockHashAlg(gomock.NewController(t)),
defaultConfig: &testGeneratorConfig,
},
want: crypto.NewHashGenerator(testGeneratorConfig, crypto.CreateMockHashAlg(gomock.NewController(t))),
wantConf: &testGeneratorConfig,
},
{ {
name: "encryption generator with default config", name: "encryption generator with default config",
eventsore: eventstoreExpect(t, expectFilter()), eventsore: eventstoreExpect(t, expectFilter()),
@ -260,25 +243,79 @@ func Test_secretGenerator(t *testing.T) {
want: crypto.NewEncryptionGenerator(testGeneratorConfig, crypto.CreateMockEncryptionAlg(gomock.NewController(t))), want: crypto.NewEncryptionGenerator(testGeneratorConfig, crypto.CreateMockEncryptionAlg(gomock.NewController(t))),
wantConf: &testGeneratorConfig, wantConf: &testGeneratorConfig,
}, },
{
name: "unsupported type",
eventsore: eventstoreExpect(t, expectFilter(
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
)),
args: args{
typ: domain.SecretGeneratorTypeVerifyEmailCode,
alg: nil,
defaultConfig: emptyConfig,
},
wantErr: zerrors.ThrowInternalf(nil, "COMMA-RreV6", "Errors.Internal unsupported crypto algorithm type %T", nil),
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, gotConf, err := secretGenerator(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, tt.args.defaultConfig) got, gotConf, err := encryptedCodeGenerator(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, tt.args.defaultConfig) //nolint:staticcheck
require.ErrorIs(t, err, tt.wantErr) require.ErrorIs(t, err, tt.wantErr)
assert.IsType(t, tt.want, got) assert.IsType(t, tt.want, got)
assert.Equal(t, tt.wantConf, gotConf) assert.Equal(t, tt.wantConf, gotConf)
}) })
} }
} }
func Test_newHashedSecretWithDefault(t *testing.T) {
tests := []struct {
name string
eventstore func(*testing.T) *eventstore.Eventstore
wantLen int
wantErr bool
}{
{
name: "filter error",
eventstore: expectEventstore(
expectFilterError(io.ErrClosedPipe),
),
wantErr: true,
},
{
name: "default config",
eventstore: expectEventstore(
expectFilter(),
),
wantLen: 32,
},
{
name: "instance config",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewSecretGeneratorAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
domain.SecretGeneratorTypeAppSecret,
24,
time.Hour*1,
true,
true,
true,
true,
),
),
),
),
wantLen: 24,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hasher := &crypto.Hasher{
Swapper: passwap.NewSwapper(bcrypt.New(bcrypt.MinCost)),
}
defaultConfig := &crypto.GeneratorConfig{
Length: 32,
Expiry: time.Minute,
IncludeLowerLetters: true,
}
generate := newHashedSecretWithDefault(hasher, defaultConfig)
encodedHash, plain, err := generate(context.Background(), tt.eventstore(t).Filter) //nolint:staticcheck
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Len(t, plain, tt.wantLen)
_, err = hasher.Verify(encodedHash, plain)
require.NoError(t, err)
})
}
}

View File

@ -23,6 +23,6 @@ func (e *Email) Validate() error {
return e.Address.Validate() return e.Address.Validate()
} }
func (c *Commands) newEmailCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*CryptoCode, error) { func (c *Commands) newEmailCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) {
return c.newCode(ctx, filter, domain.SecretGeneratorTypeVerifyEmailCode, alg) return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypeVerifyEmailCode, alg)
} }

View File

@ -108,7 +108,7 @@ func (wm *OAuthIDPWriteModel) NewChanges(
name, name,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
authorizationEndpoint, authorizationEndpoint,
tokenEndpoint, tokenEndpoint,
userEndpoint, userEndpoint,
@ -278,7 +278,7 @@ func (wm *OIDCIDPWriteModel) NewChanges(
issuer, issuer,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
idTokenMapping bool, idTokenMapping bool,
options idp.Options, options idp.Options,
@ -636,7 +636,7 @@ func (wm *AzureADIDPWriteModel) NewChanges(
name string, name string,
clientID string, clientID string,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
tenant string, tenant string,
isEmailVerified bool, isEmailVerified bool,
@ -772,7 +772,7 @@ func (wm *GitHubIDPWriteModel) NewChanges(
name, name,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) ([]idp.GitHubIDPChanges, error) { ) ([]idp.GitHubIDPChanges, error) {
@ -904,7 +904,7 @@ func (wm *GitHubEnterpriseIDPWriteModel) NewChanges(
name, name,
clientID string, clientID string,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
authorizationEndpoint, authorizationEndpoint,
tokenEndpoint, tokenEndpoint,
userEndpoint string, userEndpoint string,
@ -1037,7 +1037,7 @@ func (wm *GitLabIDPWriteModel) NewChanges(
name, name,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) ([]idp.GitLabIDPChanges, error) { ) ([]idp.GitLabIDPChanges, error) {
@ -1161,7 +1161,7 @@ func (wm *GitLabSelfHostedIDPWriteModel) NewChanges(
issuer string, issuer string,
clientID string, clientID string,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) ([]idp.GitLabSelfHostedIDPChanges, error) { ) ([]idp.GitLabSelfHostedIDPChanges, error) {
@ -1285,7 +1285,7 @@ func (wm *GoogleIDPWriteModel) NewChanges(
name string, name string,
clientID string, clientID string,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) ([]idp.GoogleIDPChanges, error) { ) ([]idp.GoogleIDPChanges, error) {
@ -1460,7 +1460,7 @@ func (wm *LDAPIDPWriteModel) NewChanges(
userObjectClasses []string, userObjectClasses []string,
userFilters []string, userFilters []string,
timeout time.Duration, timeout time.Duration,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
attributes idp.LDAPAttributes, attributes idp.LDAPAttributes,
options idp.Options, options idp.Options,
) ([]idp.LDAPIDPChanges, error) { ) ([]idp.LDAPIDPChanges, error) {
@ -1653,7 +1653,7 @@ func (wm *AppleIDPWriteModel) NewChanges(
teamID string, teamID string,
keyID string, keyID string,
privateKey []byte, privateKey []byte,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) ([]idp.AppleIDPChanges, error) { ) ([]idp.AppleIDPChanges, error) {
@ -1790,7 +1790,7 @@ func (wm *SAMLIDPWriteModel) NewChanges(
metadata, metadata,
key, key,
certificate []byte, certificate []byte,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
binding string, binding string,
withSignedRequest bool, withSignedRequest bool,
options idp.Options, options idp.Options,

View File

@ -126,7 +126,6 @@ type SetQuotas struct {
} }
type SecretGenerators struct { type SecretGenerators struct {
PasswordSaltCost uint
ClientSecret *crypto.GeneratorConfig ClientSecret *crypto.GeneratorConfig
InitializeUserCode *crypto.GeneratorConfig InitializeUserCode *crypto.GeneratorConfig
EmailVerificationCode *crypto.GeneratorConfig EmailVerificationCode *crypto.GeneratorConfig
@ -457,7 +456,6 @@ func setupMinimalInterfaces(commands *Commands, validations *[]preparation.Valid
}, },
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
}, },
nil,
), ),
commands.AddAPIAppCommand( commands.AddAPIAppCommand(
@ -469,7 +467,6 @@ func setupMinimalInterfaces(commands *Commands, validations *[]preparation.Valid
}, },
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
}, },
nil,
), ),
commands.AddAPIAppCommand( commands.AddAPIAppCommand(
@ -481,10 +478,9 @@ func setupMinimalInterfaces(commands *Commands, validations *[]preparation.Valid
}, },
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
}, },
nil,
), ),
commands.AddOIDCAppCommand(cnsl, nil), commands.AddOIDCAppCommand(cnsl),
SetIAMConsoleID(instanceAgg, &cnsl.ClientID, &ids.consoleAppID), SetIAMConsoleID(instanceAgg, &cnsl.ClientID, &ids.consoleAppID),
) )
} }

View File

@ -8,7 +8,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/instance"
@ -141,12 +140,7 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
domain.OIDCVersionV1, domain.OIDCVersionV1,
"consoleApplicationID", "consoleApplicationID",
"client1@project", "client1@project",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
[]string{"https://test.ch"}, []string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},

View File

@ -61,7 +61,7 @@ func (wm *InstanceOAuthIDPWriteModel) NewChangedEvent(
name, name,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
authorizationEndpoint, authorizationEndpoint,
tokenEndpoint, tokenEndpoint,
userEndpoint, userEndpoint,
@ -171,7 +171,7 @@ func (wm *InstanceOIDCIDPWriteModel) NewChangedEvent(
issuer, issuer,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
idTokenMapping bool, idTokenMapping bool,
options idp.Options, options idp.Options,
@ -344,7 +344,7 @@ func (wm *InstanceAzureADIDPWriteModel) NewChangedEvent(
name, name,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
tenant string, tenant string,
isEmailVerified bool, isEmailVerified bool,
@ -420,7 +420,7 @@ func (wm *InstanceGitHubIDPWriteModel) NewChangedEvent(
name, name,
clientID string, clientID string,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) (*instance.GitHubIDPChangedEvent, error) { ) (*instance.GitHubIDPChangedEvent, error) {
@ -485,7 +485,7 @@ func (wm *InstanceGitHubEnterpriseIDPWriteModel) NewChangedEvent(
name, name,
clientID string, clientID string,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
authorizationEndpoint, authorizationEndpoint,
tokenEndpoint, tokenEndpoint,
userEndpoint string, userEndpoint string,
@ -563,7 +563,7 @@ func (wm *InstanceGitLabIDPWriteModel) NewChangedEvent(
name, name,
clientID string, clientID string,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) (*instance.GitLabIDPChangedEvent, error) { ) (*instance.GitLabIDPChangedEvent, error) {
@ -629,7 +629,7 @@ func (wm *InstanceGitLabSelfHostedIDPWriteModel) NewChangedEvent(
issuer, issuer,
clientID string, clientID string,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) (*instance.GitLabSelfHostedIDPChangedEvent, error) { ) (*instance.GitLabSelfHostedIDPChangedEvent, error) {
@ -695,7 +695,7 @@ func (wm *InstanceGoogleIDPWriteModel) NewChangedEvent(
name, name,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) (*instance.GoogleIDPChangedEvent, error) { ) (*instance.GoogleIDPChangedEvent, error) {
@ -767,7 +767,7 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
userObjectClasses []string, userObjectClasses []string,
userFilters []string, userFilters []string,
timeout time.Duration, timeout time.Duration,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
attributes idp.LDAPAttributes, attributes idp.LDAPAttributes,
options idp.Options, options idp.Options,
) (*instance.LDAPIDPChangedEvent, error) { ) (*instance.LDAPIDPChangedEvent, error) {
@ -848,7 +848,7 @@ func (wm *InstanceAppleIDPWriteModel) NewChangedEvent(
teamID, teamID,
keyID string, keyID string,
privateKey []byte, privateKey []byte,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) (*instance.AppleIDPChangedEvent, error) { ) (*instance.AppleIDPChangedEvent, error) {
@ -912,7 +912,7 @@ func (wm *InstanceSAMLIDPWriteModel) NewChangedEvent(
metadata, metadata,
key, key,
certificate []byte, certificate []byte,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
binding string, binding string,
withSignedRequest bool, withSignedRequest bool,
options idp.Options, options idp.Options,

View File

@ -93,7 +93,7 @@ func (wm *InstanceIDPOIDCConfigWriteModel) NewChangedEvent(
authorizationEndpoint, authorizationEndpoint,
tokenEndpoint, tokenEndpoint,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
idpDisplayNameMapping, idpDisplayNameMapping,
userNameMapping domain.OIDCMappingField, userNameMapping domain.OIDCMappingField,
scopes ...string, scopes ...string,

View File

@ -279,8 +279,8 @@ func (h plainHasher) Verify(encoded, password string) (verifier.Result, error) {
// //
// With `x` set to "foo", the following encoded string would be produced by Hash: // With `x` set to "foo", the following encoded string would be produced by Hash:
// $plain$foo$password // $plain$foo$password
func mockPasswordHasher(x string) *crypto.PasswordHasher { func mockPasswordHasher(x string) *crypto.Hasher {
return &crypto.PasswordHasher{ return &crypto.Hasher{
Swapper: passwap.NewSwapper(plainHasher{x: x}), Swapper: passwap.NewSwapper(plainHasher{x: x}),
Prefixes: []string{"$plain$"}, Prefixes: []string{"$plain$"},
} }

View File

@ -63,7 +63,7 @@ func (wm *OrgOAuthIDPWriteModel) NewChangedEvent(
name, name,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
authorizationEndpoint, authorizationEndpoint,
tokenEndpoint, tokenEndpoint,
userEndpoint, userEndpoint,
@ -173,7 +173,7 @@ func (wm *OrgOIDCIDPWriteModel) NewChangedEvent(
issuer, issuer,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
idTokenMapping bool, idTokenMapping bool,
options idp.Options, options idp.Options,
@ -350,7 +350,7 @@ func (wm *OrgAzureADIDPWriteModel) NewChangedEvent(
name, name,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
tenant string, tenant string,
isEmailVerified bool, isEmailVerified bool,
@ -426,7 +426,7 @@ func (wm *OrgGitHubIDPWriteModel) NewChangedEvent(
name, name,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) (*org.GitHubIDPChangedEvent, error) { ) (*org.GitHubIDPChangedEvent, error) {
@ -492,7 +492,7 @@ func (wm *OrgGitHubEnterpriseIDPWriteModel) NewChangedEvent(
name, name,
clientID string, clientID string,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
authorizationEndpoint, authorizationEndpoint,
tokenEndpoint, tokenEndpoint,
userEndpoint string, userEndpoint string,
@ -571,7 +571,7 @@ func (wm *OrgGitLabIDPWriteModel) NewChangedEvent(
name, name,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) (*org.GitLabIDPChangedEvent, error) { ) (*org.GitLabIDPChangedEvent, error) {
@ -637,7 +637,7 @@ func (wm *OrgGitLabSelfHostedIDPWriteModel) NewChangedEvent(
issuer, issuer,
clientID string, clientID string,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) (*org.GitLabSelfHostedIDPChangedEvent, error) { ) (*org.GitLabSelfHostedIDPChangedEvent, error) {
@ -705,7 +705,7 @@ func (wm *OrgGoogleIDPWriteModel) NewChangedEvent(
name, name,
clientID, clientID,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) (*org.GoogleIDPChangedEvent, error) { ) (*org.GoogleIDPChangedEvent, error) {
@ -777,7 +777,7 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent(
userObjectClasses []string, userObjectClasses []string,
userFilters []string, userFilters []string,
timeout time.Duration, timeout time.Duration,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
attributes idp.LDAPAttributes, attributes idp.LDAPAttributes,
options idp.Options, options idp.Options,
) (*org.LDAPIDPChangedEvent, error) { ) (*org.LDAPIDPChangedEvent, error) {
@ -858,7 +858,7 @@ func (wm *OrgAppleIDPWriteModel) NewChangedEvent(
teamID, teamID,
keyID string, keyID string,
privateKey []byte, privateKey []byte,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
scopes []string, scopes []string,
options idp.Options, options idp.Options,
) (*org.AppleIDPChangedEvent, error) { ) (*org.AppleIDPChangedEvent, error) {
@ -924,7 +924,7 @@ func (wm *OrgSAMLIDPWriteModel) NewChangedEvent(
metadata, metadata,
key, key,
certificate []byte, certificate []byte,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
binding string, binding string,
withSignedRequest bool, withSignedRequest bool,
options idp.Options, options idp.Options,

View File

@ -92,7 +92,7 @@ func (wm *IDPOIDCConfigWriteModel) NewChangedEvent(
authorizationEndpoint, authorizationEndpoint,
tokenEndpoint, tokenEndpoint,
clientSecretString string, clientSecretString string,
secretCrypto crypto.Crypto, secretCrypto crypto.EncryptionAlgorithm,
idpDisplayNameMapping, idpDisplayNameMapping,
userNameMapping domain.OIDCMappingField, userNameMapping domain.OIDCMappingField,
scopes ...string, scopes ...string,

View File

@ -1260,7 +1260,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
type fields struct { type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
newCode cryptoCodeFunc newCode encrypedCodeFunc
keyAlgorithm crypto.EncryptionAlgorithm keyAlgorithm crypto.EncryptionAlgorithm
} }
type args struct { type args struct {
@ -1437,7 +1437,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "orgID", "userID"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "orgID", "userID"),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
}, },
args: args{ args: args{
ctx: authz.WithRequestedDomain(context.Background(), "iam-domain"), ctx: authz.WithRequestedDomain(context.Background(), "iam-domain"),
@ -1616,7 +1616,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
), ),
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "orgID", "userID", "tokenID"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "orgID", "userID", "tokenID"),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
}, },
args: args{ args: args{
@ -1671,7 +1671,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore(t), eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
newCode: tt.fields.newCode, newEncryptedCode: tt.fields.newCode,
keyAlgorithm: tt.fields.keyAlgorithm, keyAlgorithm: tt.fields.keyAlgorithm,
zitadelRoles: []authz.RoleMapping{ zitadelRoles: []authz.RoleMapping{
{ {

View File

@ -16,6 +16,6 @@ type Phone struct {
ReturnCode bool ReturnCode bool
} }
func (c *Commands) newPhoneCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*CryptoCode, error) { func (c *Commands) newPhoneCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) {
return c.newCode(ctx, filter, domain.SecretGeneratorTypeVerifyPhoneCode, alg) return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypeVerifyPhoneCode, alg)
} }

View File

@ -22,7 +22,7 @@ type CommandVerifier interface {
Validate(eventstore.Command) bool Validate(eventstore.Command) bool
} }
// AssertValidation checks if the validation works as inteded // AssertValidation checks if the validation works as intended
func AssertValidation(t *testing.T, ctx context.Context, validation preparation.Validation, filter preparation.FilterToQueryReducer, want Want) { func AssertValidation(t *testing.T, ctx context.Context, validation preparation.Validation, filter preparation.FilterToQueryReducer, want Want) {
t.Helper() t.Helper()
@ -51,7 +51,7 @@ func AssertValidation(t *testing.T, ctx context.Context, validation preparation.
for i, cmd := range want.Commands { for i, cmd := range want.Commands {
if v, ok := cmd.(CommandVerifier); ok { if v, ok := cmd.(CommandVerifier); ok {
if verified := v.Validate(cmds[i]); !verified { if verified := v.Validate(cmds[i]); !verified {
t.Errorf("verification failed on command: = %v, want %v", cmds[i], cmd) t.Errorf("verification failed on command: =\n%v\nwant\n%v", cmds[i], cmd)
} }
continue continue
} }

View File

@ -3,8 +3,6 @@ package command
import ( import (
"context" "context"
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/repository/project" "github.com/zitadel/zitadel/internal/repository/project"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
@ -16,10 +14,6 @@ type AddApp struct {
Name string Name string
} }
func (c *Commands) newAppClientSecret(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.HashAlgorithm) (*CryptoCode, error) {
return c.newCode(ctx, filter, domain.SecretGeneratorTypeAppSecret, alg)
}
func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appChange domain.Application, resourceOwner string) (*domain.ObjectDetails, error) { func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appChange domain.Application, resourceOwner string) (*domain.ObjectDetails, error) {
if projectID == "" || appChange.GetAppID() == "" || appChange.GetApplicationName() == "" { if projectID == "" || appChange.GetAppID() == "" || appChange.GetApplicationName() == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.App.Invalid") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.App.Invalid")

View File

@ -4,10 +4,7 @@ import (
"context" "context"
"strings" "strings"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
project_repo "github.com/zitadel/zitadel/internal/repository/project" project_repo "github.com/zitadel/zitadel/internal/repository/project"
@ -20,11 +17,11 @@ type addAPIApp struct {
AuthMethodType domain.APIAuthMethodType AuthMethodType domain.APIAuthMethodType
ClientID string ClientID string
ClientSecret *crypto.CryptoValue EncodedHash string
ClientSecretPlain string ClientSecretPlain string
} }
func (c *Commands) AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashAlgorithm) preparation.Validation { func (c *Commands) AddAPIAppCommand(app *addAPIApp) preparation.Validation {
return func() (preparation.CreateCommands, error) { return func() (preparation.CreateCommands, error) {
if app.ID == "" { if app.ID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument") return nil, zerrors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument")
@ -44,11 +41,10 @@ func (c *Commands) AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashA
} }
if app.AuthMethodType == domain.APIAuthMethodTypeBasic { if app.AuthMethodType == domain.APIAuthMethodTypeBasic {
code, err := c.newAppClientSecret(ctx, filter, clientSecretAlg) app.EncodedHash, app.ClientSecretPlain, err = c.newHashedSecret(ctx, filter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
app.ClientSecret, app.ClientSecretPlain = code.Crypted, code.Plain
} }
return []eventstore.Command{ return []eventstore.Command{
@ -63,7 +59,7 @@ func (c *Commands) AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashA
&app.Aggregate.Aggregate, &app.Aggregate.Aggregate,
app.ID, app.ID,
app.ClientID, app.ClientID,
app.ClientSecret, app.EncodedHash,
app.AuthMethodType, app.AuthMethodType,
), ),
}, nil }, nil
@ -71,7 +67,7 @@ func (c *Commands) AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashA
} }
} }
func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner, appID string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) { func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner, appID string) (_ *domain.APIApp, err error) {
existingAPI, err := c.getAPIAppWriteModel(ctx, apiApp.AggregateID, appID, resourceOwner) existingAPI, err := c.getAPIAppWriteModel(ctx, apiApp.AggregateID, appID, resourceOwner)
if err != nil { if err != nil {
return nil, err return nil, err
@ -84,10 +80,10 @@ func (c *Commands) AddAPIApplicationWithID(ctx context.Context, apiApp *domain.A
return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-9fnsa", "Errors.Project.NotFound") return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-9fnsa", "Errors.Project.NotFound")
} }
return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID, appSecretGenerator) return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID)
} }
func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) { func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (_ *domain.APIApp, err error) {
if apiApp == nil || apiApp.AggregateID == "" { if apiApp == nil || apiApp.AggregateID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Project.App.Invalid") return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Project.App.Invalid")
} }
@ -105,10 +101,10 @@ func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp,
return nil, err return nil, err
} }
return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID, appSecretGenerator) return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, project, appID)
} }
func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner string, project *domain.Project, appID string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) { func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.APIApp, resourceOwner string, project *domain.Project, appID string) (_ *domain.APIApp, err error) {
apiApp.AppID = appID apiApp.AppID = appID
addedApplication := NewAPIApplicationWriteModel(apiApp.AggregateID, resourceOwner) addedApplication := NewAPIApplicationWriteModel(apiApp.AggregateID, resourceOwner)
@ -118,12 +114,14 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A
project_repo.NewApplicationAddedEvent(ctx, projectAgg, apiApp.AppID, apiApp.AppName), project_repo.NewApplicationAddedEvent(ctx, projectAgg, apiApp.AppID, apiApp.AppName),
} }
var stringPw string var plain string
err = domain.SetNewClientID(apiApp, c.idGenerator, project) err = domain.SetNewClientID(apiApp, c.idGenerator, project)
if err != nil { if err != nil {
return nil, err return nil, err
} }
stringPw, err = domain.SetNewClientSecretIfNeeded(apiApp, appSecretGenerator) plain, err = domain.SetNewClientSecretIfNeeded(apiApp, func() (string, string, error) {
return c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -131,7 +129,7 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A
projectAgg, projectAgg,
apiApp.AppID, apiApp.AppID,
apiApp.ClientID, apiApp.ClientID,
apiApp.ClientSecret, apiApp.EncodedHash,
apiApp.AuthMethodType)) apiApp.AuthMethodType))
addedApplication.AppID = apiApp.AppID addedApplication.AppID = apiApp.AppID
@ -144,7 +142,7 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A
return nil, err return nil, err
} }
result := apiWriteModelToAPIConfig(addedApplication) result := apiWriteModelToAPIConfig(addedApplication)
result.ClientSecretString = stringPw result.ClientSecretString = plain
return result, nil return result, nil
} }
@ -188,7 +186,7 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA
return apiWriteModelToAPIConfig(existingAPI), nil return apiWriteModelToAPIConfig(existingAPI), nil
} }
func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string, appSecretGenerator crypto.Generator) (*domain.APIApp, error) { func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string) (*domain.APIApp, error) {
if projectID == "" || appID == "" { if projectID == "" || appID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing")
} }
@ -203,14 +201,14 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap
if !existingAPI.IsAPI() { if !existingAPI.IsAPI() {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aeH4", "Errors.Project.App.IsNotAPI") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aeH4", "Errors.Project.App.IsNotAPI")
} }
cryptoSecret, stringPW, err := domain.NewClientSecret(appSecretGenerator) encodedHash, plain, err := c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck
if err != nil { if err != nil {
return nil, err return nil, err
} }
projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel) projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret)) pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretChangedEvent(ctx, projectAgg, appID, encodedHash))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -220,7 +218,7 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap
} }
result := apiWriteModelToAPIConfig(existingAPI) result := apiWriteModelToAPIConfig(existingAPI)
result.ClientSecretString = stringPW result.ClientSecretString = plain
return result, err return result, err
} }
@ -233,26 +231,35 @@ func (c *Commands) VerifyAPIClientSecret(ctx context.Context, projectID, appID,
return err return err
} }
if !app.State.Exists() { if !app.State.Exists() {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NoExisting") return zerrors.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NotExisting")
} }
if !app.IsAPI() { if !app.IsAPI() {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-Bf3fw", "Errors.Project.App.IsNotAPI") return zerrors.ThrowInvalidArgument(nil, "COMMAND-Bf3fw", "Errors.Project.App.IsNotAPI")
} }
if app.ClientSecret == nil { if app.HashedSecret == "" {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D3t5g", "Errors.Project.App.APIConfigInvalid") return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D3t5g", "Errors.Project.App.APIConfigInvalid")
} }
projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel) projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel)
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash") ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify")
err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.codeAlg) updated, err := c.secretHasher.Verify(app.HashedSecret, secret)
spanPasswordComparison.EndWithError(err) spanPasswordComparison.EndWithError(err)
if err == nil { if err == nil {
_, err = c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID)) c.apiSecretCheckSucceeded(ctx, projectAgg, app.AppID, updated)
return err return err
} }
_, err = c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID)) c.apiSecretCheckFailed(ctx, projectAgg, app.AppID)
logging.Log("COMMAND-g3f12").OnError(err).Error("could not push event APIClientSecretCheckFailed") return zerrors.ThrowInvalidArgument(err, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid")
return zerrors.ThrowInvalidArgument(nil, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid") }
func (c *Commands) APISecretCheckSucceeded(ctx context.Context, appID, projectID, resourceOwner, updated string) {
agg := project_repo.NewAggregate(projectID, resourceOwner)
c.apiSecretCheckSucceeded(ctx, &agg.Aggregate, appID, updated)
}
func (c *Commands) APISecretCheckFailed(ctx context.Context, appID, projectID, resourceOwner string) {
agg := project_repo.NewAggregate(projectID, resourceOwner)
c.apiSecretCheckFailed(ctx, &agg.Aggregate, appID)
} }
func (c *Commands) getAPIAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (*APIApplicationWriteModel, error) { func (c *Commands) getAPIAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (*APIApplicationWriteModel, error) {
@ -263,3 +270,18 @@ func (c *Commands) getAPIAppWriteModel(ctx context.Context, projectID, appID, re
} }
return appWriteModel, nil return appWriteModel, nil
} }
func (c *Commands) apiSecretCheckSucceeded(ctx context.Context, agg *eventstore.Aggregate, appID, updated string) {
cmds := append(
make([]eventstore.Command, 0, 2),
project_repo.NewAPIConfigSecretCheckSucceededEvent(ctx, agg, appID),
)
if updated != "" {
cmds = append(cmds, project_repo.NewAPIConfigSecretHashUpdatedEvent(ctx, agg, appID, updated))
}
c.asyncPush(ctx, cmds...)
}
func (c *Commands) apiSecretCheckFailed(ctx context.Context, agg *eventstore.Aggregate, appID string) {
c.asyncPush(ctx, project_repo.NewAPIConfigSecretCheckFailedEvent(ctx, agg, appID))
}

View File

@ -15,7 +15,7 @@ type APIApplicationWriteModel struct {
AppID string AppID string
AppName string AppName string
ClientID string ClientID string
ClientSecret *crypto.CryptoValue HashedSecret string
ClientSecretString string ClientSecretString string
AuthMethodType domain.APIAuthMethodType AuthMethodType domain.APIAuthMethodType
State domain.AppState State domain.AppState
@ -83,6 +83,11 @@ func (wm *APIApplicationWriteModel) AppendEvents(events ...eventstore.Event) {
continue continue
} }
wm.WriteModel.AppendEvents(e) wm.WriteModel.AppendEvents(e)
case *project.APIConfigSecretHashUpdatedEvent:
if e.AppID != wm.AppID {
continue
}
wm.WriteModel.AppendEvents(e)
case *project.ProjectRemovedEvent: case *project.ProjectRemovedEvent:
wm.WriteModel.AppendEvents(e) wm.WriteModel.AppendEvents(e)
} }
@ -114,7 +119,9 @@ func (wm *APIApplicationWriteModel) Reduce() error {
case *project.APIConfigChangedEvent: case *project.APIConfigChangedEvent:
wm.appendChangeAPIEvent(e) wm.appendChangeAPIEvent(e)
case *project.APIConfigSecretChangedEvent: case *project.APIConfigSecretChangedEvent:
wm.ClientSecret = e.ClientSecret wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)
case *project.APIConfigSecretHashUpdatedEvent:
wm.HashedSecret = e.HashedSecret
case *project.ProjectRemovedEvent: case *project.ProjectRemovedEvent:
wm.State = domain.AppStateRemoved wm.State = domain.AppStateRemoved
} }
@ -125,7 +132,7 @@ func (wm *APIApplicationWriteModel) Reduce() error {
func (wm *APIApplicationWriteModel) appendAddAPIEvent(e *project.APIConfigAddedEvent) { func (wm *APIApplicationWriteModel) appendAddAPIEvent(e *project.APIConfigAddedEvent) {
wm.api = true wm.api = true
wm.ClientID = e.ClientID wm.ClientID = e.ClientID
wm.ClientSecret = e.ClientSecret wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)
wm.AuthMethodType = e.AuthMethodType wm.AuthMethodType = e.AuthMethodType
} }
@ -150,8 +157,9 @@ func (wm *APIApplicationWriteModel) Query() *eventstore.SearchQueryBuilder {
project.APIConfigAddedType, project.APIConfigAddedType,
project.APIConfigChangedType, project.APIConfigChangedType,
project.APIConfigSecretChangedType, project.APIConfigSecretChangedType,
project.ProjectRemovedType). project.APIConfigSecretHashUpdatedType,
Builder() project.ProjectRemovedType,
).Builder()
} }
func (wm *APIApplicationWriteModel) NewChangedEvent( func (wm *APIApplicationWriteModel) NewChangedEvent(

View File

@ -2,9 +2,13 @@ package command
import ( import (
"context" "context"
"io"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/passwap"
"github.com/zitadel/passwap/bcrypt"
"github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
@ -114,7 +118,7 @@ func TestAddAPIConfig(t *testing.T) {
project.NewAPIConfigAddedEvent(ctx, &agg.Aggregate, project.NewAPIConfigAddedEvent(ctx, &agg.Aggregate,
"appID", "appID",
"clientID@project", "clientID@project",
nil, "",
domain.APIAuthMethodTypePrivateKeyJWT, domain.APIAuthMethodTypePrivateKeyJWT,
), ),
}, },
@ -137,7 +141,6 @@ func TestAddAPIConfig(t *testing.T) {
}, },
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT, AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
}, },
nil,
), tt.args.filter, tt.want) ), tt.args.filter, tt.want)
}) })
} }
@ -145,14 +148,13 @@ func TestAddAPIConfig(t *testing.T) {
func TestCommandSide_AddAPIApplication(t *testing.T) { func TestCommandSide_AddAPIApplication(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
apiApp *domain.APIApp apiApp *domain.APIApp
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.APIApp want *domain.APIApp
@ -167,9 +169,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
{ {
name: "no aggregate id, invalid argument error", name: "no aggregate id, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -183,8 +183,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
{ {
name: "project not existing, not found error", name: "project not existing, not found error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -206,8 +205,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
{ {
name: "invalid app, invalid argument error", name: "invalid app, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(), project.NewProjectAddedEvent(context.Background(),
@ -236,8 +234,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
{ {
name: "create api app basic, ok", name: "create api app basic, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(), project.NewProjectAddedEvent(context.Background(),
@ -256,12 +253,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate, &project.NewAggregate("project1", "org1").Aggregate,
"app1", "app1",
"client1@project", "client1@project",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.APIAuthMethodTypeBasic), domain.APIAuthMethodTypeBasic),
), ),
), ),
@ -277,7 +269,6 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
AuthMethodType: domain.APIAuthMethodTypeBasic, AuthMethodType: domain.APIAuthMethodTypeBasic,
}, },
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.APIApp{ want: &domain.APIApp{
@ -288,7 +279,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
AppID: "app1", AppID: "app1",
AppName: "app", AppName: "app",
ClientID: "client1@project", ClientID: "client1@project",
ClientSecretString: "a", ClientSecretString: "secret",
AuthMethodType: domain.APIAuthMethodTypeBasic, AuthMethodType: domain.APIAuthMethodTypeBasic,
State: domain.AppStateActive, State: domain.AppStateActive,
}, },
@ -297,8 +288,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
{ {
name: "create api app jwt, ok", name: "create api app jwt, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(), project.NewProjectAddedEvent(context.Background(),
@ -317,7 +307,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate, &project.NewAggregate("project1", "org1").Aggregate,
"app1", "app1",
"client1@project", "client1@project",
nil, "",
domain.APIAuthMethodTypePrivateKeyJWT), domain.APIAuthMethodTypePrivateKeyJWT),
), ),
), ),
@ -352,10 +342,14 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
} }
got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner, tt.args.secretGenerator) got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -371,7 +365,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
func TestCommandSide_ChangeAPIApplication(t *testing.T) { func TestCommandSide_ChangeAPIApplication(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -391,9 +385,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
{ {
name: "missing appid, invalid argument error", name: "missing appid, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -414,9 +406,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
{ {
name: "missing aggregateid, invalid argument error", name: "missing aggregateid, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -437,8 +427,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
{ {
name: "app not existing, not found error", name: "app not existing, not found error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -460,8 +449,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
{ {
name: "no changes, precondition error", name: "no changes, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), project.NewApplicationAddedEvent(context.Background(),
@ -475,7 +463,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate, &project.NewAggregate("project1", "org1").Aggregate,
"app1", "app1",
"client1@project", "client1@project",
nil, "",
domain.APIAuthMethodTypePrivateKeyJWT), domain.APIAuthMethodTypePrivateKeyJWT),
), ),
), ),
@ -500,8 +488,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
{ {
name: "change api app, ok", name: "change api app, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), project.NewApplicationAddedEvent(context.Background(),
@ -515,12 +502,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate, &project.NewAggregate("project1", "org1").Aggregate,
"app1", "app1",
"client1@project", "client1@project",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.APIAuthMethodTypeBasic), domain.APIAuthMethodTypeBasic),
), ),
), ),
@ -563,7 +545,11 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
} }
got, err := r.ChangeAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner) got, err := r.ChangeAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
if tt.res.err == nil { if tt.res.err == nil {
@ -581,14 +567,13 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) { func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
appID string appID string
projectID string projectID string
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.APIApp want *domain.APIApp
@ -603,9 +588,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
{ {
name: "no projectid, invalid argument error", name: "no projectid, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -619,9 +602,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
{ {
name: "no appid, invalid argument error", name: "no appid, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -636,8 +617,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
{ {
name: "app not existing, not found error", name: "app not existing, not found error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -654,8 +634,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
{ {
name: "change secret, ok", name: "change secret, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), project.NewApplicationAddedEvent(context.Background(),
@ -669,12 +648,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate, &project.NewAggregate("project1", "org1").Aggregate,
"app1", "app1",
"client1@project", "client1@project",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.APIAuthMethodTypeBasic), domain.APIAuthMethodTypeBasic),
), ),
), ),
@ -682,12 +656,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
project.NewAPIConfigSecretChangedEvent(context.Background(), project.NewAPIConfigSecretChangedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate, &project.NewAggregate("project1", "org1").Aggregate,
"app1", "app1",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
), ),
), ),
), ),
@ -697,7 +666,6 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
projectID: "project1", projectID: "project1",
appID: "app1", appID: "app1",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.APIApp{ want: &domain.APIApp{
@ -708,7 +676,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
AppID: "app1", AppID: "app1",
AppName: "app", AppName: "app",
ClientID: "client1@project", ClientID: "client1@project",
ClientSecretString: "a", ClientSecretString: "secret",
AuthMethodType: domain.APIAuthMethodTypeBasic, AuthMethodType: domain.APIAuthMethodTypeBasic,
State: domain.AppStateActive, State: domain.AppStateActive,
}, },
@ -718,9 +686,13 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
} }
got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner, tt.args.secretGenerator) got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -745,3 +717,105 @@ func newAPIAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner
) )
return event return event
} }
func TestCommands_VerifyAPIClientSecret(t *testing.T) {
hasher := &crypto.Hasher{
Swapper: passwap.NewSwapper(bcrypt.New(bcrypt.MinCost)),
}
hashedSecret, err := hasher.Hash("secret")
require.NoError(t, err)
agg := project.NewAggregate("projectID", "orgID")
tests := []struct {
name string
secret string
eventstore func(*testing.T) *eventstore.Eventstore
wantErr error
}{
{
name: "filter error",
eventstore: expectEventstore(
expectFilterError(io.ErrClosedPipe),
),
wantErr: io.ErrClosedPipe,
},
{
name: "app not exists",
eventstore: expectEventstore(
expectFilter(),
),
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NotExisting"),
},
{
name: "wrong app type",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"),
),
),
),
wantErr: zerrors.ThrowInvalidArgument(nil, "COMMAND-Bf3fw", "Errors.Project.App.IsNotAPI"),
},
{
name: "no secret set",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(), &agg.Aggregate, "appID", "clientID", "", domain.APIAuthMethodTypePrivateKeyJWT),
),
),
),
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-D3t5g", "Errors.Project.App.APIConfigInvalid"),
},
{
name: "check succeeded",
secret: "secret",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(), &agg.Aggregate, "appID", "clientID", hashedSecret, domain.APIAuthMethodTypePrivateKeyJWT),
),
),
expectPush(
project.NewAPIConfigSecretCheckSucceededEvent(context.Background(), &agg.Aggregate, "appID"),
),
),
},
{
name: "check failed",
secret: "wrong!",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(), &agg.Aggregate, "appID", "clientID", hashedSecret, domain.APIAuthMethodTypePrivateKeyJWT),
),
),
expectPush(
project.NewAPIConfigSecretCheckFailedEvent(context.Background(), &agg.Aggregate, "appID"),
),
),
wantErr: zerrors.ThrowInvalidArgument(err, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.eventstore(t),
secretHasher: hasher,
}
err := c.VerifyAPIClientSecret(context.Background(), "projectID", "appID", tt.secret)
c.jobs.Wait()
require.ErrorIs(t, err, tt.wantErr)
})
}
}

View File

@ -6,7 +6,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/eventstore/v1/models"
@ -117,12 +116,7 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate, &project.NewAggregate("project1", "org1").Aggregate,
"app1", "app1",
"client1@project", "client1@project",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.APIAuthMethodTypeBasic), domain.APIAuthMethodTypeBasic),
), ),
), ),
@ -163,12 +157,7 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
&project.NewAggregate("project1", "org1").Aggregate, &project.NewAggregate("project1", "org1").Aggregate,
"app1", "app1",
"client1@project", "client1@project",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.APIAuthMethodTypeBasic), domain.APIAuthMethodTypeBasic),
), ),
), ),

View File

@ -5,11 +5,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/zitadel/logging"
http_util "github.com/zitadel/zitadel/internal/api/http" http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
project_repo "github.com/zitadel/zitadel/internal/repository/project" project_repo "github.com/zitadel/zitadel/internal/repository/project"
@ -36,12 +33,12 @@ type addOIDCApp struct {
SkipSuccessPageForNativeApp bool SkipSuccessPageForNativeApp bool
ClientID string ClientID string
ClientSecret *crypto.CryptoValue ClientSecret string
ClientSecretPlain string ClientSecretPlain string
} }
// AddOIDCAppCommand prepares the commands to add an oidc app. The ClientID will be set during the CreateCommands // AddOIDCAppCommand prepares the commands to add an oidc app. The ClientID will be set during the CreateCommands
func (c *Commands) AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.HashAlgorithm) preparation.Validation { func (c *Commands) AddOIDCAppCommand(app *addOIDCApp) preparation.Validation {
return func() (preparation.CreateCommands, error) { return func() (preparation.CreateCommands, error) {
if app.ID == "" { if app.ID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument") return nil, zerrors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument")
@ -77,11 +74,10 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.Has
} }
if app.AuthMethodType == domain.OIDCAuthMethodTypeBasic || app.AuthMethodType == domain.OIDCAuthMethodTypePost { if app.AuthMethodType == domain.OIDCAuthMethodTypeBasic || app.AuthMethodType == domain.OIDCAuthMethodTypePost {
code, err := c.newAppClientSecret(ctx, filter, clientSecretAlg) app.ClientSecret, app.ClientSecretPlain, err = c.newHashedSecret(ctx, filter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
app.ClientSecret, app.ClientSecretPlain = code.Crypted, code.Plain
} }
return []eventstore.Command{ return []eventstore.Command{
@ -118,7 +114,7 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.Has
} }
} }
func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner, appID string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) { func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner, appID string) (_ *domain.OIDCApp, err error) {
existingApp, err := c.getOIDCAppWriteModel(ctx, oidcApp.AggregateID, appID, resourceOwner) existingApp, err := c.getOIDCAppWriteModel(ctx, oidcApp.AggregateID, appID, resourceOwner)
if err != nil { if err != nil {
return nil, err return nil, err
@ -132,10 +128,10 @@ func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain
return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-3m9s2", "Errors.Project.NotFound") return nil, zerrors.ThrowPreconditionFailed(err, "PROJECT-3m9s2", "Errors.Project.NotFound")
} }
return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID, appSecretGenerator) return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID)
} }
func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) { func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string) (_ *domain.OIDCApp, err error) {
if oidcApp == nil || oidcApp.AggregateID == "" { if oidcApp == nil || oidcApp.AggregateID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Project.App.Invalid") return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Project.App.Invalid")
} }
@ -153,11 +149,10 @@ func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCA
return nil, err return nil, err
} }
return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID, appSecretGenerator) return c.addOIDCApplicationWithID(ctx, oidcApp, resourceOwner, project, appID)
} }
func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, project *domain.Project, appID string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) { func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner string, project *domain.Project, appID string) (_ *domain.OIDCApp, err error) {
addedApplication := NewOIDCApplicationWriteModel(oidcApp.AggregateID, resourceOwner) addedApplication := NewOIDCApplicationWriteModel(oidcApp.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel) projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
@ -167,12 +162,14 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain
project_repo.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName), project_repo.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName),
} }
var stringPw string var plain string
err = domain.SetNewClientID(oidcApp, c.idGenerator, project) err = domain.SetNewClientID(oidcApp, c.idGenerator, project)
if err != nil { if err != nil {
return nil, err return nil, err
} }
stringPw, err = domain.SetNewClientSecretIfNeeded(oidcApp, appSecretGenerator) plain, err = domain.SetNewClientSecretIfNeeded(oidcApp, func() (string, string, error) {
return c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -181,7 +178,7 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain
oidcApp.OIDCVersion, oidcApp.OIDCVersion,
oidcApp.AppID, oidcApp.AppID,
oidcApp.ClientID, oidcApp.ClientID,
oidcApp.ClientSecret, oidcApp.EncodedHash,
trimStringSliceWhiteSpaces(oidcApp.RedirectUris), trimStringSliceWhiteSpaces(oidcApp.RedirectUris),
oidcApp.ResponseTypes, oidcApp.ResponseTypes,
oidcApp.GrantTypes, oidcApp.GrantTypes,
@ -208,7 +205,7 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain
return nil, err return nil, err
} }
result := oidcWriteModelToOIDCConfig(addedApplication) result := oidcWriteModelToOIDCConfig(addedApplication)
result.ClientSecretString = stringPw result.ClientSecretString = plain
result.FillCompliance() result.FillCompliance()
return result, nil return result, nil
} }
@ -270,7 +267,7 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA
return result, nil return result, nil
} }
func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string, appSecretGenerator crypto.Generator) (*domain.OIDCApp, error) { func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string) (*domain.OIDCApp, error) {
if projectID == "" || appID == "" { if projectID == "" || appID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing")
} }
@ -285,14 +282,14 @@ func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, a
if !existingOIDC.IsOIDC() { if !existingOIDC.IsOIDC() {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Ghrh3", "Errors.Project.App.IsNotOIDC") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Ghrh3", "Errors.Project.App.IsNotOIDC")
} }
cryptoSecret, stringPW, err := domain.NewClientSecret(appSecretGenerator) encodedHash, plain, err := c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck
if err != nil { if err != nil {
return nil, err return nil, err
} }
projectAgg := ProjectAggregateFromWriteModel(&existingOIDC.WriteModel) projectAgg := ProjectAggregateFromWriteModel(&existingOIDC.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret)) pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretChangedEvent(ctx, projectAgg, appID, encodedHash))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -302,7 +299,7 @@ func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, a
} }
result := oidcWriteModelToOIDCConfig(existingOIDC) result := oidcWriteModelToOIDCConfig(existingOIDC)
result.ClientSecretString = stringPW result.ClientSecretString = plain
return result, err return result, err
} }
@ -315,26 +312,35 @@ func (c *Commands) VerifyOIDCClientSecret(ctx context.Context, projectID, appID,
return err return err
} }
if !app.State.Exists() { if !app.State.Exists() {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.NotExisting") return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D8hba", "Errors.Project.App.NotExisting")
} }
if !app.IsOIDC() { if !app.IsOIDC() {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-BHgn2", "Errors.Project.App.IsNotOIDC") return zerrors.ThrowInvalidArgument(nil, "COMMAND-BHgn2", "Errors.Project.App.IsNotOIDC")
} }
if app.ClientSecret == nil { if app.HashedSecret == "" {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.OIDCConfigInvalid") return zerrors.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.OIDCConfigInvalid")
} }
projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel) projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel)
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash") ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify")
err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.codeAlg) updated, err := c.secretHasher.Verify(app.HashedSecret, secret)
spanPasswordComparison.EndWithError(err) spanPasswordComparison.EndWithError(err)
if err == nil { if err == nil {
_, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID)) c.oidcSecretCheckSucceeded(ctx, projectAgg, appID, updated)
return err return nil
} }
_, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID)) c.oidcSecretCheckFailed(ctx, projectAgg, appID)
logging.OnError(err).Error("could not push event OIDCClientSecretCheckFailed") return zerrors.ThrowInvalidArgument(err, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid")
return zerrors.ThrowInvalidArgument(nil, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid") }
func (c *Commands) OIDCSecretCheckSucceeded(ctx context.Context, appID, projectID, resourceOwner, updated string) {
agg := project_repo.NewAggregate(projectID, resourceOwner)
c.oidcSecretCheckSucceeded(ctx, &agg.Aggregate, appID, updated)
}
func (c *Commands) OIDCSecretCheckFailed(ctx context.Context, appID, projectID, resourceOwner string) {
agg := project_repo.NewAggregate(projectID, resourceOwner)
c.oidcSecretCheckFailed(ctx, &agg.Aggregate, appID)
} }
func (c *Commands) getOIDCAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (*OIDCApplicationWriteModel, error) { func (c *Commands) getOIDCAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (*OIDCApplicationWriteModel, error) {
@ -366,3 +372,18 @@ func trimStringSliceWhiteSpaces(slice []string) []string {
} }
return slice return slice
} }
func (c *Commands) oidcSecretCheckSucceeded(ctx context.Context, agg *eventstore.Aggregate, appID, updated string) {
cmds := append(
make([]eventstore.Command, 0, 2),
project_repo.NewOIDCConfigSecretCheckSucceededEvent(ctx, agg, appID),
)
if updated != "" {
cmds = append(cmds, project_repo.NewOIDCConfigSecretHashUpdatedEvent(ctx, agg, appID, updated))
}
c.asyncPush(ctx, cmds...)
}
func (c *Commands) oidcSecretCheckFailed(ctx context.Context, agg *eventstore.Aggregate, appID string) {
c.asyncPush(ctx, project_repo.NewOIDCConfigSecretCheckFailedEvent(ctx, agg, appID))
}

View File

@ -17,7 +17,7 @@ type OIDCApplicationWriteModel struct {
AppID string AppID string
AppName string AppName string
ClientID string ClientID string
ClientSecret *crypto.CryptoValue HashedSecret string
ClientSecretString string ClientSecretString string
RedirectUris []string RedirectUris []string
ResponseTypes []domain.OIDCResponseType ResponseTypes []domain.OIDCResponseType
@ -100,6 +100,11 @@ func (wm *OIDCApplicationWriteModel) AppendEvents(events ...eventstore.Event) {
continue continue
} }
wm.WriteModel.AppendEvents(e) wm.WriteModel.AppendEvents(e)
case *project.OIDCConfigSecretHashUpdatedEvent:
if e.AppID != wm.AppID {
continue
}
wm.WriteModel.AppendEvents(e)
case *project.ProjectRemovedEvent: case *project.ProjectRemovedEvent:
wm.WriteModel.AppendEvents(e) wm.WriteModel.AppendEvents(e)
} }
@ -131,7 +136,9 @@ func (wm *OIDCApplicationWriteModel) Reduce() error {
case *project.OIDCConfigChangedEvent: case *project.OIDCConfigChangedEvent:
wm.appendChangeOIDCEvent(e) wm.appendChangeOIDCEvent(e)
case *project.OIDCConfigSecretChangedEvent: case *project.OIDCConfigSecretChangedEvent:
wm.ClientSecret = e.ClientSecret wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)
case *project.OIDCConfigSecretHashUpdatedEvent:
wm.HashedSecret = e.HashedSecret
case *project.ProjectRemovedEvent: case *project.ProjectRemovedEvent:
wm.State = domain.AppStateRemoved wm.State = domain.AppStateRemoved
} }
@ -142,7 +149,7 @@ func (wm *OIDCApplicationWriteModel) Reduce() error {
func (wm *OIDCApplicationWriteModel) appendAddOIDCEvent(e *project.OIDCConfigAddedEvent) { func (wm *OIDCApplicationWriteModel) appendAddOIDCEvent(e *project.OIDCConfigAddedEvent) {
wm.oidc = true wm.oidc = true
wm.ClientID = e.ClientID wm.ClientID = e.ClientID
wm.ClientSecret = e.ClientSecret wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)
wm.RedirectUris = e.RedirectUris wm.RedirectUris = e.RedirectUris
wm.ResponseTypes = e.ResponseTypes wm.ResponseTypes = e.ResponseTypes
wm.GrantTypes = e.GrantTypes wm.GrantTypes = e.GrantTypes
@ -223,8 +230,9 @@ func (wm *OIDCApplicationWriteModel) Query() *eventstore.SearchQueryBuilder {
project.OIDCConfigAddedType, project.OIDCConfigAddedType,
project.OIDCConfigChangedType, project.OIDCConfigChangedType,
project.OIDCConfigSecretChangedType, project.OIDCConfigSecretChangedType,
project.ProjectRemovedType). project.OIDCConfigSecretHashUpdatedType,
Builder() project.ProjectRemovedType,
).Builder()
} }
func (wm *OIDCApplicationWriteModel) NewChangedEvent( func (wm *OIDCApplicationWriteModel) NewChangedEvent(

View File

@ -2,10 +2,14 @@ package command
import ( import (
"context" "context"
"io"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/passwap"
"github.com/zitadel/passwap/bcrypt"
"github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
@ -24,7 +28,6 @@ func TestAddOIDCApp(t *testing.T) {
} }
type args struct { type args struct {
app *addOIDCApp app *addOIDCApp
clientSecretAlg crypto.HashAlgorithm
filter preparation.FilterToQueryReducer filter preparation.FilterToQueryReducer
} }
@ -156,7 +159,7 @@ func TestAddOIDCApp(t *testing.T) {
domain.OIDCVersionV1, domain.OIDCVersionV1,
"id", "id",
"clientID@project", "clientID@project",
nil, "",
[]string{"https://test.ch"}, []string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
@ -221,7 +224,7 @@ func TestAddOIDCApp(t *testing.T) {
domain.OIDCVersionV1, domain.OIDCVersionV1,
"id", "id",
"clientID@project", "clientID@project",
nil, "",
nil, nil,
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
@ -240,17 +243,85 @@ func TestAddOIDCApp(t *testing.T) {
}, },
}, },
}, },
{
name: "with secret",
fields: fields{
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "clientID"),
},
args: args{
app: &addOIDCApp{
AddApp: AddApp{
Aggregate: *agg,
ID: "id",
Name: "name",
},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
Version: domain.OIDCVersionV1,
ApplicationType: domain.OIDCApplicationTypeWeb,
AuthMethodType: domain.OIDCAuthMethodTypeBasic,
AccessTokenType: domain.OIDCTokenTypeBearer,
},
filter: NewMultiFilter().
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
project.NewProjectAddedEvent(
ctx,
&agg.Aggregate,
"project",
false,
false,
false,
domain.PrivateLabelingSettingUnspecified,
),
}, nil
}).
Filter(),
},
want: Want{
Commands: []eventstore.Command{
project.NewApplicationAddedEvent(ctx, &agg.Aggregate,
"id",
"name",
),
project.NewOIDCConfigAddedEvent(ctx, &agg.Aggregate,
domain.OIDCVersionV1,
"id",
"clientID@project",
"secret",
nil,
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypeBasic,
nil,
false,
domain.OIDCTokenTypeBearer,
false,
false,
false,
0,
nil,
false,
),
},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := Commands{ c := Commands{
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
} }
AssertValidation(t, AssertValidation(t,
context.Background(), context.Background(),
c.AddOIDCAppCommand( c.AddOIDCAppCommand(
tt.args.app, tt.args.app,
tt.args.clientSecretAlg,
), tt.args.filter, tt.want) ), tt.args.filter, tt.want)
}) })
} }
@ -258,14 +329,13 @@ func TestAddOIDCApp(t *testing.T) {
func TestCommandSide_AddOIDCApplication(t *testing.T) { func TestCommandSide_AddOIDCApplication(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
oidcApp *domain.OIDCApp oidcApp *domain.OIDCApp
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.OIDCApp want *domain.OIDCApp
@ -280,9 +350,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
{ {
name: "no aggregate id, invalid argument error", name: "no aggregate id, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -296,8 +364,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
{ {
name: "project not existing, not found error", name: "project not existing, not found error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -319,8 +386,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
{ {
name: "invalid app, invalid argument error", name: "invalid app, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(), project.NewProjectAddedEvent(context.Background(),
@ -349,8 +415,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
{ {
name: "create oidc app basic using whitespaces in uris, ok", name: "create oidc app basic using whitespaces in uris, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(), project.NewProjectAddedEvent(context.Background(),
@ -370,12 +435,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
domain.OIDCVersionV1, domain.OIDCVersionV1,
"app1", "app1",
"client1@project", "client1@project",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
[]string{"https://test.ch"}, []string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
@ -419,7 +479,6 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
SkipNativeAppSuccessPage: true, SkipNativeAppSuccessPage: true,
}, },
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.OIDCApp{ want: &domain.OIDCApp{
@ -430,7 +489,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
AppID: "app1", AppID: "app1",
AppName: "app", AppName: "app",
ClientID: "client1@project", ClientID: "client1@project",
ClientSecretString: "a", ClientSecretString: "secret",
AuthMethodType: domain.OIDCAuthMethodTypePost, AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1, OIDCVersion: domain.OIDCVersionV1,
RedirectUris: []string{"https://test.ch"}, RedirectUris: []string{"https://test.ch"},
@ -454,8 +513,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
{ {
name: "create oidc app basic, ok", name: "create oidc app basic, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(), project.NewProjectAddedEvent(context.Background(),
@ -475,12 +533,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
domain.OIDCVersionV1, domain.OIDCVersionV1,
"app1", "app1",
"client1@project", "client1@project",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
[]string{"https://test.ch"}, []string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
@ -524,7 +577,6 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
SkipNativeAppSuccessPage: true, SkipNativeAppSuccessPage: true,
}, },
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.OIDCApp{ want: &domain.OIDCApp{
@ -535,7 +587,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
AppID: "app1", AppID: "app1",
AppName: "app", AppName: "app",
ClientID: "client1@project", ClientID: "client1@project",
ClientSecretString: "a", ClientSecretString: "secret",
AuthMethodType: domain.OIDCAuthMethodTypePost, AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1, OIDCVersion: domain.OIDCVersionV1,
RedirectUris: []string{"https://test.ch"}, RedirectUris: []string{"https://test.ch"},
@ -560,10 +612,14 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
} }
got, err := r.AddOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner, tt.args.secretGenerator) got, err := r.AddOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -709,12 +765,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
domain.OIDCVersionV1, domain.OIDCVersionV1,
"app1", "app1",
"client1@project", "client1@project",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
[]string{"https://test.ch"}, []string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
@ -783,12 +834,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
domain.OIDCVersionV1, domain.OIDCVersionV1,
"app1", "app1",
"client1@project", "client1@project",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
[]string{"https://test.ch"}, []string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
@ -857,12 +903,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
domain.OIDCVersionV1, domain.OIDCVersionV1,
"app1", "app1",
"client1@project", "client1@project",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
[]string{"https://test.ch"}, []string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
@ -965,14 +1006,13 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
appID string appID string
projectID string projectID string
resourceOwner string resourceOwner string
secretGenerator crypto.Generator
} }
type res struct { type res struct {
want *domain.OIDCApp want *domain.OIDCApp
@ -987,9 +1027,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
{ {
name: "no projectid, invalid argument error", name: "no projectid, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1003,9 +1041,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
{ {
name: "no appid, invalid argument error", name: "no appid, invalid argument error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1020,8 +1056,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
{ {
name: "app not existing, not found error", name: "app not existing, not found error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -1038,8 +1073,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
{ {
name: "change secret, ok", name: "change secret, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), project.NewApplicationAddedEvent(context.Background(),
@ -1054,12 +1088,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
domain.OIDCVersionV1, domain.OIDCVersionV1,
"app1", "app1",
"client1@project", "client1@project",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
[]string{"https://test.ch"}, []string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode}, []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
@ -1081,12 +1110,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
project.NewOIDCConfigSecretChangedEvent(context.Background(), project.NewOIDCConfigSecretChangedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate, &project.NewAggregate("project1", "org1").Aggregate,
"app1", "app1",
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
), ),
), ),
), ),
@ -1096,7 +1120,6 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
projectID: "project1", projectID: "project1",
appID: "app1", appID: "app1",
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),
}, },
res: res{ res: res{
want: &domain.OIDCApp{ want: &domain.OIDCApp{
@ -1107,7 +1130,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
AppID: "app1", AppID: "app1",
AppName: "app", AppName: "app",
ClientID: "client1@project", ClientID: "client1@project",
ClientSecretString: "a", ClientSecretString: "secret",
AuthMethodType: domain.OIDCAuthMethodTypePost, AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1, OIDCVersion: domain.OIDCVersionV1,
RedirectUris: []string{"https://test.ch"}, RedirectUris: []string{"https://test.ch"},
@ -1129,11 +1152,15 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(*testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
} }
got, err := r.ChangeOIDCApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner, tt.args.secretGenerator) got, err := r.ChangeOIDCApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -1165,3 +1192,165 @@ func newOIDCAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner
) )
return event return event
} }
func TestCommands_VerifyOIDCClientSecret(t *testing.T) {
hasher := &crypto.Hasher{
Swapper: passwap.NewSwapper(bcrypt.New(bcrypt.MinCost)),
}
hashedSecret, err := hasher.Hash("secret")
require.NoError(t, err)
agg := project.NewAggregate("projectID", "orgID")
tests := []struct {
name string
secret string
eventstore func(*testing.T) *eventstore.Eventstore
wantErr error
}{
{
name: "filter error",
eventstore: expectEventstore(
expectFilterError(io.ErrClosedPipe),
),
wantErr: io.ErrClosedPipe,
},
{
name: "app not exists",
eventstore: expectEventstore(
expectFilter(),
),
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-D8hba", "Errors.Project.App.NotExisting"),
},
{
name: "wrong app type",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"),
),
),
),
wantErr: zerrors.ThrowInvalidArgument(nil, "COMMAND-BHgn2", "Errors.Project.App.IsNotOIDC"),
},
{
name: "no secret set",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"),
),
eventFromEventPusher(
project.NewOIDCConfigAddedEvent(context.Background(),
&agg.Aggregate,
domain.OIDCVersionV1,
"appID",
"client1@project",
"",
[]string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypePost,
[]string{"https://test.ch/logout"},
true,
domain.OIDCTokenTypeBearer,
true,
true,
true,
time.Second*1,
[]string{"https://sub.test.ch"},
false,
),
),
),
),
wantErr: zerrors.ThrowPreconditionFailed(nil, "COMMAND-D6hba", "Errors.Project.App.OIDCConfigInvalid"),
},
{
name: "check succeeded",
secret: "secret",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"),
),
eventFromEventPusher(
project.NewOIDCConfigAddedEvent(context.Background(),
&agg.Aggregate,
domain.OIDCVersionV1,
"appID",
"client1@project",
hashedSecret,
[]string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypePost,
[]string{"https://test.ch/logout"},
true,
domain.OIDCTokenTypeBearer,
true,
true,
true,
time.Second*1,
[]string{"https://sub.test.ch"},
false,
),
),
),
expectPush(
project.NewOIDCConfigSecretCheckSucceededEvent(context.Background(), &agg.Aggregate, "appID"),
),
),
},
{
name: "check failed",
secret: "wrong!",
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(), &agg.Aggregate, "appID", "appName"),
),
eventFromEventPusher(
project.NewOIDCConfigAddedEvent(context.Background(),
&agg.Aggregate,
domain.OIDCVersionV1,
"appID",
"client1@project",
hashedSecret,
[]string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypePost,
[]string{"https://test.ch/logout"},
true,
domain.OIDCTokenTypeBearer,
true,
true,
true,
time.Second*1,
[]string{"https://sub.test.ch"},
false,
),
),
),
expectPush(
project.NewOIDCConfigSecretCheckFailedEvent(context.Background(), &agg.Aggregate, "appID"),
),
),
wantErr: zerrors.ThrowInvalidArgument(err, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.eventstore(t),
secretHasher: hasher,
}
err := c.VerifyOIDCClientSecret(context.Background(), "projectID", "appID", tt.secret)
c.jobs.Wait()
require.ErrorIs(t, err, tt.wantErr)
})
}
}

View File

@ -31,11 +31,11 @@ type SessionCommands struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
eventCommands []eventstore.Command eventCommands []eventstore.Command
hasher *crypto.PasswordHasher hasher *crypto.Hasher
intentAlg crypto.EncryptionAlgorithm intentAlg crypto.EncryptionAlgorithm
totpAlg crypto.EncryptionAlgorithm totpAlg crypto.EncryptionAlgorithm
otpAlg crypto.EncryptionAlgorithm otpAlg crypto.EncryptionAlgorithm
createCode cryptoCodeWithDefaultFunc createCode encryptedCodeWithDefaultFunc
createToken func(sessionID string) (id string, token string, err error) createToken func(sessionID string) (id string, token string, err error)
now func() time.Time now func() time.Time
} }
@ -49,7 +49,7 @@ func (c *Commands) NewSessionCommands(cmds []SessionCommand, session *SessionWri
intentAlg: c.idpConfigEncryption, intentAlg: c.idpConfigEncryption,
totpAlg: c.multifactors.OTP.CryptoMFA, totpAlg: c.multifactors.OTP.CryptoMFA,
otpAlg: c.userEncryption, otpAlg: c.userEncryption,
createCode: c.newCodeWithDefault, createCode: c.newEncryptedCodeWithDefault,
createToken: c.sessionTokenCreator, createToken: c.sessionTokenCreator,
now: time.Now, now: time.Now,
} }

View File

@ -120,7 +120,7 @@ func CheckOTPSMS(code string) SessionCommand {
if challenge == nil { if challenge == nil {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SF3tv", "Errors.User.Code.NotFound") return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SF3tv", "Errors.User.Code.NotFound")
} }
err = crypto.VerifyCodeWithAlgorithm(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg) err = crypto.VerifyCode(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg)
if err != nil { if err != nil {
return err return err
} }
@ -138,7 +138,7 @@ func CheckOTPEmail(code string) SessionCommand {
if challenge == nil { if challenge == nil {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-zF3g3", "Errors.User.Code.NotFound") return zerrors.ThrowPreconditionFailed(nil, "COMMAND-zF3g3", "Errors.User.Code.NotFound")
} }
err = crypto.VerifyCodeWithAlgorithm(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg) err = crypto.VerifyCode(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg)
if err != nil { if err != nil {
return err return err
} }

View File

@ -20,7 +20,7 @@ func TestCommands_CreateOTPSMSChallengeReturnCode(t *testing.T) {
type fields struct { type fields struct {
userID string userID string
eventstore func(*testing.T) *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
createCode cryptoCodeWithDefaultFunc createCode encryptedCodeWithDefaultFunc
} }
type res struct { type res struct {
err error err error
@ -65,7 +65,7 @@ func TestCommands_CreateOTPSMSChallengeReturnCode(t *testing.T) {
), ),
), ),
), ),
createCode: mockCodeWithDefault("1234567", 5*time.Minute), createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute),
}, },
res: res{ res: res{
returnCode: "1234567", returnCode: "1234567",
@ -122,7 +122,7 @@ func TestCommands_CreateOTPSMSChallenge(t *testing.T) {
type fields struct { type fields struct {
userID string userID string
eventstore func(*testing.T) *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
createCode cryptoCodeWithDefaultFunc createCode encryptedCodeWithDefaultFunc
} }
type res struct { type res struct {
err error err error
@ -166,7 +166,7 @@ func TestCommands_CreateOTPSMSChallenge(t *testing.T) {
), ),
), ),
), ),
createCode: mockCodeWithDefault("1234567", 5*time.Minute), createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute),
}, },
res: res{ res: res{
commands: []eventstore.Command{ commands: []eventstore.Command{
@ -292,7 +292,7 @@ func TestCommands_CreateOTPEmailChallengeURLTemplate(t *testing.T) {
type fields struct { type fields struct {
userID string userID string
eventstore func(*testing.T) *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
createCode cryptoCodeWithDefaultFunc createCode encryptedCodeWithDefaultFunc
} }
type args struct { type args struct {
urlTmpl string urlTmpl string
@ -361,7 +361,7 @@ func TestCommands_CreateOTPEmailChallengeURLTemplate(t *testing.T) {
), ),
), ),
), ),
createCode: mockCodeWithDefault("1234567", 5*time.Minute), createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute),
}, },
res: res{ res: res{
commands: []eventstore.Command{ commands: []eventstore.Command{
@ -421,7 +421,7 @@ func TestCommands_CreateOTPEmailChallengeReturnCode(t *testing.T) {
type fields struct { type fields struct {
userID string userID string
eventstore func(*testing.T) *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
createCode cryptoCodeWithDefaultFunc createCode encryptedCodeWithDefaultFunc
} }
type res struct { type res struct {
err error err error
@ -465,7 +465,7 @@ func TestCommands_CreateOTPEmailChallengeReturnCode(t *testing.T) {
), ),
), ),
), ),
createCode: mockCodeWithDefault("1234567", 5*time.Minute), createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute),
}, },
res: res{ res: res{
returnCode: "1234567", returnCode: "1234567",
@ -523,7 +523,7 @@ func TestCommands_CreateOTPEmailChallenge(t *testing.T) {
type fields struct { type fields struct {
userID string userID string
eventstore func(*testing.T) *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
createCode cryptoCodeWithDefaultFunc createCode encryptedCodeWithDefaultFunc
} }
type res struct { type res struct {
err error err error
@ -566,7 +566,7 @@ func TestCommands_CreateOTPEmailChallenge(t *testing.T) {
), ),
), ),
), ),
createCode: mockCodeWithDefault("1234567", 5*time.Minute), createCode: mockEncryptedCodeWithDefault("1234567", 5*time.Minute),
}, },
res: res{ res: res{
commands: []eventstore.Command{ commands: []eventstore.Command{

View File

@ -476,8 +476,8 @@ func ExistsUser(ctx context.Context, filter preparation.FilterToQueryReducer, id
return exists, nil return exists, nil
} }
func (c *Commands) newUserInitCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*CryptoCode, error) { func (c *Commands) newUserInitCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) {
return c.newCode(ctx, filter, domain.SecretGeneratorTypeInitCode, alg) return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypeInitCode, alg)
} }
func userWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, resourceOwner string) (*UserWriteModel, error) { func userWriteModelByID(ctx context.Context, filter preparation.FilterToQueryReducer, userID, resourceOwner string) (*UserWriteModel, error) {

View File

@ -12,6 +12,7 @@ import (
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/repository/user" "github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -77,7 +78,7 @@ type AddLink struct {
IDPExternalID string IDPExternalID string
} }
func (h *AddHuman) Validate(hasher *crypto.PasswordHasher) (err error) { func (h *AddHuman) Validate(hasher *crypto.Hasher) (err error) {
if err := h.Email.Validate(); err != nil { if err := h.Email.Validate(); err != nil {
return err return err
} }
@ -164,7 +165,7 @@ type humanCreationCommand interface {
} }
//nolint:gocognit //nolint:gocognit
func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto.PasswordHasher, codeAlg crypto.EncryptionAlgorithm, allowInitMail bool) preparation.Validation { func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto.Hasher, codeAlg crypto.EncryptionAlgorithm, allowInitMail bool) preparation.Validation {
return func() (_ preparation.CreateCommands, err error) { return func() (_ preparation.CreateCommands, err error) {
if err := human.Validate(hasher); err != nil { if err := human.Validate(hasher); err != nil {
return nil, err return nil, err
@ -329,17 +330,19 @@ func (c *Commands) addHumanCommandCheckID(ctx context.Context, filter preparatio
return nil return nil
} }
func addHumanCommandPassword(ctx context.Context, filter preparation.FilterToQueryReducer, createCmd humanCreationCommand, human *AddHuman, hasher *crypto.PasswordHasher) (err error) { func addHumanCommandPassword(ctx context.Context, filter preparation.FilterToQueryReducer, createCmd humanCreationCommand, human *AddHuman, hasher *crypto.Hasher) (err error) {
if human.Password != "" { if human.Password != "" {
if err = humanValidatePassword(ctx, filter, human.Password); err != nil { if err = humanValidatePassword(ctx, filter, human.Password); err != nil {
return err return err
} }
secret, err := hasher.Hash(human.Password) _, spanHash := tracing.NewNamedSpan(ctx, "passwap.Hash")
encodedHash, err := hasher.Hash(human.Password)
spanHash.EndWithError(err)
if err != nil { if err != nil {
return err return err
} }
createCmd.AddPasswordData(secret, human.PasswordChangeRequired) createCmd.AddPasswordData(encodedHash, human.PasswordChangeRequired)
return nil return nil
} }
@ -589,7 +592,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
human.EnsureDisplayName() human.EnsureDisplayName()
if human.Password != nil { if human.Password != nil {
if err := human.HashPasswordIfExisting(pwPolicy, c.userPasswordHasher, human.Password.ChangeRequired); err != nil { if err := human.HashPasswordIfExisting(ctx, pwPolicy, c.userPasswordHasher, human.Password.ChangeRequired); err != nil {
return nil, nil, err return nil, nil, err
} }
} }

View File

@ -81,7 +81,7 @@ func (c *Commands) VerifyHumanEmail(ctx context.Context, userID, code, resourceo
} }
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, emailCodeGenerator) err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, emailCodeGenerator.Alg())
if err == nil { if err == nil {
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanEmailVerifiedEvent(ctx, userAgg)) pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanEmailVerifiedEvent(ctx, userAgg))
if err != nil { if err != nil {

View File

@ -67,7 +67,7 @@ func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwne
} }
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, initCodeGenerator) err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, initCodeGenerator.Alg())
if err != nil { if err != nil {
_, err = c.eventstore.Push(ctx, user.NewHumanInitializedCheckFailedEvent(ctx, userAgg)) _, err = c.eventstore.Push(ctx, user.NewHumanInitializedCheckFailedEvent(ctx, userAgg))
logging.WithFields("userID", userAgg.ID).OnError(err).Error("NewHumanInitializedCheckFailedEvent push failed") logging.WithFields("userID", userAgg.ID).OnError(err).Error("NewHumanInitializedCheckFailedEvent push failed")

View File

@ -292,7 +292,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
func TestCommandSide_VerifyInitCode(t *testing.T) { func TestCommandSide_VerifyInitCode(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
userPasswordHasher *crypto.PasswordHasher userPasswordHasher *crypto.Hasher
} }
type args struct { type args struct {
ctx context.Context ctx context.Context

View File

@ -455,7 +455,7 @@ func (c *Commands) sendHumanOTP(
if !existingOTP.OTPAdded() { if !existingOTP.OTPAdded() {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SFD52", "Errors.User.MFA.OTP.NotReady") return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SFD52", "Errors.User.MFA.OTP.NotReady")
} }
config, err := secretGeneratorConfigWithDefault(ctx, c.eventstore.Filter, secretGeneratorType, defaultSecretGenerator) config, err := cryptoGeneratorConfigWithDefault(ctx, c.eventstore.Filter, secretGeneratorType, defaultSecretGenerator) //nolint:staticcheck
if err != nil { if err != nil {
return err return err
} }
@ -515,7 +515,7 @@ func (c *Commands) humanCheckOTP(
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-S34gh", "Errors.User.Code.NotFound") return zerrors.ThrowPreconditionFailed(nil, "COMMAND-S34gh", "Errors.User.Code.NotFound")
} }
userAgg := &user.NewAggregate(userID, existingOTP.ResourceOwner()).Aggregate userAgg := &user.NewAggregate(userID, existingOTP.ResourceOwner()).Aggregate
err = crypto.VerifyCodeWithAlgorithm(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption) err = crypto.VerifyCode(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption)
if err == nil { if err == nil {
_, err = c.eventstore.Push(ctx, checkSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))) _, err = c.eventstore.Push(ctx, checkSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
return err return err

View File

@ -53,7 +53,7 @@ func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID,
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Code.NotFound") return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Code.NotFound")
} }
err = crypto.VerifyCodeWithAlgorithm(wm.CodeCreationDate, wm.CodeExpiry, wm.Code, code, c.userEncryption) err = crypto.VerifyCode(wm.CodeCreationDate, wm.CodeExpiry, wm.Code, code, c.userEncryption)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -36,11 +36,11 @@ func (wm *HumanPasswordWriteModel) Reduce() error {
for _, event := range wm.Events { for _, event := range wm.Events {
switch e := event.(type) { switch e := event.(type) {
case *user.HumanAddedEvent: case *user.HumanAddedEvent:
wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash)
wm.SecretChangeRequired = e.ChangeRequired wm.SecretChangeRequired = e.ChangeRequired
wm.UserState = domain.UserStateActive wm.UserState = domain.UserStateActive
case *user.HumanRegisteredEvent: case *user.HumanRegisteredEvent:
wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash)
wm.SecretChangeRequired = e.ChangeRequired wm.SecretChangeRequired = e.ChangeRequired
wm.UserState = domain.UserStateActive wm.UserState = domain.UserStateActive
case *user.HumanInitialCodeAddedEvent: case *user.HumanInitialCodeAddedEvent:
@ -48,7 +48,7 @@ func (wm *HumanPasswordWriteModel) Reduce() error {
case *user.HumanInitializedCheckSucceededEvent: case *user.HumanInitializedCheckSucceededEvent:
wm.UserState = domain.UserStateActive wm.UserState = domain.UserStateActive
case *user.HumanPasswordChangedEvent: case *user.HumanPasswordChangedEvent:
wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.EncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash)
wm.SecretChangeRequired = e.ChangeRequired wm.SecretChangeRequired = e.ChangeRequired
wm.Code = nil wm.Code = nil
wm.PasswordCheckFailedCount = 0 wm.PasswordCheckFailedCount = 0

View File

@ -23,7 +23,7 @@ import (
func TestCommandSide_SetOneTimePassword(t *testing.T) { func TestCommandSide_SetOneTimePassword(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
userPasswordHasher *crypto.PasswordHasher userPasswordHasher *crypto.Hasher
checkPermission domain.PermissionCheck checkPermission domain.PermissionCheck
} }
type args struct { type args struct {
@ -270,7 +270,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm userEncryption crypto.EncryptionAlgorithm
userPasswordHasher *crypto.PasswordHasher userPasswordHasher *crypto.Hasher
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -598,7 +598,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
func TestCommandSide_ChangePassword(t *testing.T) { func TestCommandSide_ChangePassword(t *testing.T) {
type fields struct { type fields struct {
userPasswordHasher *crypto.PasswordHasher userPasswordHasher *crypto.Hasher
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -1202,7 +1202,7 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
func TestCommandSide_CheckPassword(t *testing.T) { func TestCommandSide_CheckPassword(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
userPasswordHasher *crypto.PasswordHasher userPasswordHasher *crypto.Hasher
} }
type args struct { type args struct {
ctx context.Context ctx context.Context

View File

@ -79,7 +79,7 @@ func (c *Commands) VerifyHumanPhone(ctx context.Context, userID, code, resourceo
} }
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel) userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, phoneCodeGenerator) err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, phoneCodeGenerator.Alg())
if err == nil { if err == nil {
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPhoneVerifiedEvent(ctx, userAgg)) pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPhoneVerifiedEvent(ctx, userAgg))
if err != nil { if err != nil {
@ -115,7 +115,7 @@ func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID,
if existingPhone.IsPhoneVerified { if existingPhone.IsPhoneVerified {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M9sf", "Errors.User.Phone.AlreadyVerified") return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2M9sf", "Errors.User.Phone.AlreadyVerified")
} }
config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -28,9 +28,9 @@ func TestCommandSide_AddHuman(t *testing.T) {
type fields struct { type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
userPasswordHasher *crypto.PasswordHasher userPasswordHasher *crypto.Hasher
codeAlg crypto.EncryptionAlgorithm codeAlg crypto.EncryptionAlgorithm
newCode cryptoCodeFunc newCode encrypedCodeFunc
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -245,7 +245,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -312,7 +312,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -380,7 +380,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -450,7 +450,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"), userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -521,7 +521,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"), userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("emailCode", time.Hour), newCode: mockEncryptedCode("emailCode", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -593,7 +593,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"), userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("emailCode", time.Hour), newCode: mockEncryptedCode("emailCode", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -996,7 +996,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"), userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("phonecode", time.Hour), newCode: mockEncryptedCode("phonecode", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1061,7 +1061,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1136,7 +1136,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"), userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("phoneCode", time.Hour), newCode: mockEncryptedCode("phoneCode", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1204,7 +1204,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
), ),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1242,7 +1242,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
userPasswordHasher: tt.fields.userPasswordHasher, userPasswordHasher: tt.fields.userPasswordHasher,
userEncryption: tt.fields.codeAlg, userEncryption: tt.fields.codeAlg,
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
newCode: tt.fields.newCode, newEncryptedCode: tt.fields.newCode,
} }
err := r.AddHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail) err := r.AddHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail)
if tt.res.err == nil { if tt.res.err == nil {
@ -1266,7 +1266,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
userPasswordHasher *crypto.PasswordHasher userPasswordHasher *crypto.Hasher
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -2483,7 +2483,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
userPasswordHasher *crypto.PasswordHasher userPasswordHasher *crypto.Hasher
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -4328,7 +4328,7 @@ func TestAddHumanCommand(t *testing.T) {
type args struct { type args struct {
human *AddHuman human *AddHuman
orgID string orgID string
hasher *crypto.PasswordHasher hasher *crypto.Hasher
filter preparation.FilterToQueryReducer filter preparation.FilterToQueryReducer
codeAlg crypto.EncryptionAlgorithm codeAlg crypto.EncryptionAlgorithm
allowInitMail bool allowInitMail bool

View File

@ -575,7 +575,7 @@ func (c *Commands) humanVerifyPasswordlessInitCode(ctx context.Context, userID,
if err != nil { if err != nil {
return err return err
} }
err = crypto.VerifyCode(initCode.ChangeDate, initCode.Expiration, initCode.CryptoCode, verificationCode, passwordlessCodeGenerator) err = crypto.VerifyCode(initCode.ChangeDate, initCode.Expiration, initCode.CryptoCode, verificationCode, passwordlessCodeGenerator.Alg())
if err != nil || initCode.State != domain.PasswordlessInitCodeStateActive { if err != nil || initCode.State != domain.PasswordlessInitCodeStateActive {
userAgg := UserAggregateFromWriteModel(&initCode.WriteModel) userAgg := UserAggregateFromWriteModel(&initCode.WriteModel)
_, err = c.eventstore.Push(ctx, usr_repo.NewHumanPasswordlessInitCodeCheckFailedEvent(ctx, userAgg, codeID)) _, err = c.eventstore.Push(ctx, usr_repo.NewHumanPasswordlessInitCodeCheckFailedEvent(ctx, userAgg, codeID))

View File

@ -18,8 +18,7 @@ type MachineWriteModel struct {
Description string Description string
UserState domain.UserState UserState domain.UserState
AccessTokenType domain.OIDCTokenType AccessTokenType domain.OIDCTokenType
HashedSecret string
ClientSecret *crypto.CryptoValue
} }
func NewMachineWriteModel(userID, resourceOwner string) *MachineWriteModel { func NewMachineWriteModel(userID, resourceOwner string) *MachineWriteModel {
@ -71,9 +70,11 @@ func (wm *MachineWriteModel) Reduce() error {
case *user.UserRemovedEvent: case *user.UserRemovedEvent:
wm.UserState = domain.UserStateDeleted wm.UserState = domain.UserStateDeleted
case *user.MachineSecretSetEvent: case *user.MachineSecretSetEvent:
wm.ClientSecret = e.ClientSecret wm.HashedSecret = crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)
case *user.MachineSecretRemovedEvent: case *user.MachineSecretRemovedEvent:
wm.ClientSecret = nil wm.HashedSecret = ""
case *user.MachineSecretHashUpdatedEvent:
wm.HashedSecret = e.HashedSecret
} }
} }
return wm.WriteModel.Reduce() return wm.WriteModel.Reduce()
@ -94,8 +95,9 @@ func (wm *MachineWriteModel) Query() *eventstore.SearchQueryBuilder {
user.UserReactivatedType, user.UserReactivatedType,
user.UserRemovedType, user.UserRemovedType,
user.MachineSecretSetType, user.MachineSecretSetType,
user.MachineSecretRemovedType). user.MachineSecretRemovedType,
Builder() user.MachineSecretHashUpdatedType,
).Builder()
} }
func (wm *MachineWriteModel) NewChangedEvent( func (wm *MachineWriteModel) NewChangedEvent(

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/user" "github.com/zitadel/zitadel/internal/repository/user"
@ -15,9 +14,9 @@ type GenerateMachineSecret struct {
ClientSecret string ClientSecret string
} }
func (c *Commands) GenerateMachineSecret(ctx context.Context, userID string, resourceOwner string, generator crypto.Generator, set *GenerateMachineSecret) (*domain.ObjectDetails, error) { func (c *Commands) GenerateMachineSecret(ctx context.Context, userID string, resourceOwner string, set *GenerateMachineSecret) (*domain.ObjectDetails, error) {
agg := user.NewAggregate(userID, resourceOwner) agg := user.NewAggregate(userID, resourceOwner)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareGenerateMachineSecret(agg, generator, set)) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareGenerateMachineSecret(agg, set)) //nolint:staticcheck
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -34,7 +33,7 @@ func (c *Commands) GenerateMachineSecret(ctx context.Context, userID string, res
}, nil }, nil
} }
func prepareGenerateMachineSecret(a *user.Aggregate, generator crypto.Generator, set *GenerateMachineSecret) preparation.Validation { func (c *Commands) prepareGenerateMachineSecret(a *user.Aggregate, set *GenerateMachineSecret) preparation.Validation {
return func() (_ preparation.CreateCommands, err error) { return func() (_ preparation.CreateCommands, err error) {
if a.ResourceOwner == "" { if a.ResourceOwner == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-x0992n", "Errors.ResourceOwnerMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-x0992n", "Errors.ResourceOwnerMissing")
@ -50,15 +49,14 @@ func prepareGenerateMachineSecret(a *user.Aggregate, generator crypto.Generator,
if !isUserStateExists(writeModel.UserState) { if !isUserStateExists(writeModel.UserState) {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-x8910n", "Errors.User.NotExisting") return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-x8910n", "Errors.User.NotExisting")
} }
encodedHash, plain, err := c.newHashedSecret(ctx, filter)
clientSecret, secretString, err := domain.NewMachineClientSecret(generator)
if err != nil { if err != nil {
return nil, err return nil, err
} }
set.ClientSecret = secretString set.ClientSecret = plain
return []eventstore.Command{ return []eventstore.Command{
user.NewMachineSecretSetEvent(ctx, &a.Aggregate, clientSecret), user.NewMachineSecretSetEvent(ctx, &a.Aggregate, encodedHash),
}, nil }, nil
}, nil }, nil
} }
@ -99,7 +97,7 @@ func prepareRemoveMachineSecret(a *user.Aggregate) preparation.Validation {
if !isUserStateExists(writeModel.UserState) { if !isUserStateExists(writeModel.UserState) {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-x7s802", "Errors.User.NotExisting") return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-x7s802", "Errors.User.NotExisting")
} }
if writeModel.ClientSecret == nil { if writeModel.HashedSecret == "" {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-coi82n", "Errors.User.Machine.Secret.NotExisting") return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-coi82n", "Errors.User.Machine.Secret.NotExisting")
} }
return []eventstore.Command{ return []eventstore.Command{
@ -109,9 +107,16 @@ func prepareRemoveMachineSecret(a *user.Aggregate) preparation.Validation {
} }
} }
func (c *Commands) MachineSecretCheckSucceeded(ctx context.Context, userID, resourceOwner string) { func (c *Commands) MachineSecretCheckSucceeded(ctx context.Context, userID, resourceOwner, updated string) {
agg := user.NewAggregate(userID, resourceOwner) agg := user.NewAggregate(userID, resourceOwner)
c.asyncPush(ctx, user.NewMachineSecretCheckSucceededEvent(ctx, &agg.Aggregate)) cmds := append(
make([]eventstore.Command, 0, 2),
user.NewMachineSecretCheckSucceededEvent(ctx, &agg.Aggregate),
)
if updated != "" {
cmds = append(cmds, user.NewMachineSecretHashUpdatedEvent(ctx, &agg.Aggregate, updated))
}
c.asyncPush(ctx, cmds...)
} }
func (c *Commands) MachineSecretCheckFailed(ctx context.Context, userID, resourceOwner string) { func (c *Commands) MachineSecretCheckFailed(ctx context.Context, userID, resourceOwner string) {

View File

@ -8,7 +8,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/user" "github.com/zitadel/zitadel/internal/repository/user"
@ -17,13 +16,12 @@ import (
func TestCommandSide_GenerateMachineSecret(t *testing.T) { func TestCommandSide_GenerateMachineSecret(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore func(*testing.T) *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
userID string userID string
resourceOwner string resourceOwner string
generator crypto.Generator
set *GenerateMachineSecret set *GenerateMachineSecret
} }
type res struct { type res struct {
@ -40,15 +38,12 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) {
{ {
name: "user invalid, invalid argument error userID", name: "user invalid, invalid argument error userID",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "", userID: "",
resourceOwner: "org1", resourceOwner: "org1",
generator: GetMockSecretGenerator(t),
set: nil, set: nil,
}, },
res: res{ res: res{
@ -58,15 +53,12 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) {
{ {
name: "user invalid, invalid argument error resourceowner", name: "user invalid, invalid argument error resourceowner",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(),
t,
),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
resourceOwner: "", resourceOwner: "",
generator: GetMockSecretGenerator(t),
set: nil, set: nil,
}, },
res: res{ res: res{
@ -76,8 +68,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) {
{ {
name: "user not existing, precondition error", name: "user not existing, precondition error",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter(), expectFilter(),
), ),
}, },
@ -85,7 +76,6 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
resourceOwner: "org1", resourceOwner: "org1",
generator: GetMockSecretGenerator(t),
set: nil, set: nil,
}, },
res: res{ res: res{
@ -95,8 +85,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) {
{ {
name: "add machine secret, ok", name: "add machine secret, ok",
fields: fields{ fields: fields{
eventstore: eventstoreExpect( eventstore: expectEventstore(
t,
expectFilter( expectFilter(
eventFromEventPusher( eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(), user.NewMachineAddedEvent(context.Background(),
@ -112,12 +101,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) {
expectPush( expectPush(
user.NewMachineSecretSetEvent(context.Background(), user.NewMachineSecretSetEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate, &user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
), ),
), ),
), ),
@ -126,7 +110,6 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
userID: "user1", userID: "user1",
resourceOwner: "org1", resourceOwner: "org1",
generator: GetMockSecretGenerator(t),
set: &GenerateMachineSecret{}, set: &GenerateMachineSecret{},
}, },
res: res{ res: res{
@ -134,7 +117,7 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) {
ResourceOwner: "org1", ResourceOwner: "org1",
}, },
secret: &GenerateMachineSecret{ secret: &GenerateMachineSecret{
ClientSecret: "a", ClientSecret: "secret",
}, },
}, },
}, },
@ -142,9 +125,13 @@ func TestCommandSide_GenerateMachineSecret(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore(t),
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
} }
got, err := r.GenerateMachineSecret(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.generator, tt.args.set) got, err := r.GenerateMachineSecret(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.set)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -274,12 +261,7 @@ func TestCommandSide_RemoveMachineSecret(t *testing.T) {
eventFromEventPusher( eventFromEventPusher(
user.NewMachineSecretSetEvent(context.Background(), user.NewMachineSecretSetEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate, &user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{ "secret",
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
), ),
), ),
), ),
@ -333,7 +315,7 @@ func TestCommands_MachineSecretCheckSucceeded(t *testing.T) {
expectPushSlow(time.Second/100, cmd), expectPushSlow(time.Second/100, cmd),
), ),
} }
c.MachineSecretCheckSucceeded(ctx, "userID", "orgID") c.MachineSecretCheckSucceeded(ctx, "userID", "orgID", "")
require.NoError(t, c.Close(ctx)) require.NoError(t, c.Close(ctx))
} }

View File

@ -76,7 +76,7 @@ func (c *Commands) ChangeUserEmailVerified(ctx context.Context, userID, email st
} }
func (c *Commands) changeUserEmailWithCode(ctx context.Context, userID, email string, alg crypto.EncryptionAlgorithm, returnCode bool, urlTmpl string) (*domain.Email, error) { func (c *Commands) changeUserEmailWithCode(ctx context.Context, userID, email string, alg crypto.EncryptionAlgorithm, returnCode bool, urlTmpl string) (*domain.Email, error) {
config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) //nolint:staticcheck
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -85,7 +85,7 @@ func (c *Commands) changeUserEmailWithCode(ctx context.Context, userID, email st
} }
func (c *Commands) resendUserEmailCode(ctx context.Context, userID string, alg crypto.EncryptionAlgorithm, returnCode bool, urlTmpl string) (*domain.Email, error) { func (c *Commands) resendUserEmailCode(ctx context.Context, userID string, alg crypto.EncryptionAlgorithm, returnCode bool, urlTmpl string) (*domain.Email, error) {
config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) //nolint:staticcheck config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) //nolint:staticcheck
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -152,7 +152,7 @@ func (c *Commands) resendUserEmailCodeWithGeneratorEvents(ctx context.Context, u
} }
func (c *Commands) VerifyUserEmail(ctx context.Context, userID, code string, alg crypto.EncryptionAlgorithm) (*domain.ObjectDetails, error) { func (c *Commands) VerifyUserEmail(ctx context.Context, userID, code string, alg crypto.EncryptionAlgorithm) (*domain.ObjectDetails, error) {
config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) //nolint:staticcheck
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -262,7 +262,7 @@ func (c *UserEmailEvents) VerifyCode(ctx context.Context, code string, gen crypt
return zerrors.ThrowInvalidArgument(nil, "COMMAND-Fia4a", "Errors.User.Code.Empty") return zerrors.ThrowInvalidArgument(nil, "COMMAND-Fia4a", "Errors.User.Code.Empty")
} }
err := crypto.VerifyCode(c.model.CodeCreationDate, c.model.CodeExpiry, c.model.Code, code, gen) err := crypto.VerifyCode(c.model.CodeCreationDate, c.model.CodeExpiry, c.model.Code, code, gen.Alg())
if err == nil { if err == nil {
c.events = append(c.events, user.NewHumanEmailVerifiedEvent(ctx, c.aggregate)) c.events = append(c.events, user.NewHumanEmailVerifiedEvent(ctx, c.aggregate))
return nil return nil

View File

@ -51,7 +51,7 @@ type Password struct {
ChangeRequired bool ChangeRequired bool
} }
func (h *ChangeHuman) Validate(hasher *crypto.PasswordHasher) (err error) { func (h *ChangeHuman) Validate(hasher *crypto.Hasher) (err error) {
if h.Email != nil && h.Email.Address != "" { if h.Email != nil && h.Email.Address != "" {
if err := h.Email.Validate(); err != nil { if err := h.Email.Validate(); err != nil {
return err return err
@ -72,7 +72,7 @@ func (h *ChangeHuman) Validate(hasher *crypto.PasswordHasher) (err error) {
return nil return nil
} }
func (p *Password) Validate(hasher *crypto.PasswordHasher) error { func (p *Password) Validate(hasher *crypto.Hasher) error {
if p.EncodedPasswordHash != nil { if p.EncodedPasswordHash != nil {
if !hasher.EncodingSupported(*p.EncodedPasswordHash) { if !hasher.EncodingSupported(*p.EncodedPasswordHash) {
return zerrors.ThrowInvalidArgument(nil, "USER-oz74onzvqr", "Errors.User.Password.NotSupported") return zerrors.ThrowInvalidArgument(nil, "USER-oz74onzvqr", "Errors.User.Password.NotSupported")
@ -373,7 +373,7 @@ func (c *Commands) changeUserPassword(ctx context.Context, cmds []eventstore.Com
// Either have a code to set the password // Either have a code to set the password
if password.PasswordCode != nil { if password.PasswordCode != nil {
if err := crypto.VerifyCodeWithAlgorithm(wm.PasswordCodeCreationDate, wm.PasswordCodeExpiry, wm.PasswordCode, *password.PasswordCode, alg); err != nil { if err := crypto.VerifyCode(wm.PasswordCodeCreationDate, wm.PasswordCodeExpiry, wm.PasswordCode, *password.PasswordCode, alg); err != nil {
return cmds, err return cmds, err
} }
} }

View File

@ -25,8 +25,8 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
type fields struct { type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
userPasswordHasher *crypto.PasswordHasher userPasswordHasher *crypto.Hasher
newCode cryptoCodeFunc newCode encrypedCodeFunc
checkPermission domain.PermissionCheck checkPermission domain.PermissionCheck
} }
type args struct { type args struct {
@ -247,7 +247,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -283,7 +283,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
), ),
checkPermission: newMockPermissionCheckNotAllowed(), checkPermission: newMockPermissionCheckNotAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -349,7 +349,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -420,7 +420,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"), userPasswordHasher: mockPasswordHasher("x"),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -492,7 +492,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"), userPasswordHasher: mockPasswordHasher("x"),
newCode: mockCode("emailCode", time.Hour), newCode: mockEncryptedCode("emailCode", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -565,7 +565,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"), userPasswordHasher: mockPasswordHasher("x"),
newCode: mockCode("emailCode", time.Hour), newCode: mockEncryptedCode("emailCode", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -974,7 +974,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"), userPasswordHasher: mockPasswordHasher("x"),
newCode: mockCode("phonecode", time.Hour), newCode: mockEncryptedCode("phonecode", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1040,7 +1040,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1116,7 +1116,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"), userPasswordHasher: mockPasswordHasher("x"),
newCode: mockCode("phoneCode", time.Hour), newCode: mockEncryptedCode("phoneCode", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1185,7 +1185,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
newCode: mockCode("userinit", time.Hour), newCode: mockEncryptedCode("userinit", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1223,7 +1223,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
eventstore: tt.fields.eventstore(t), eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher, userPasswordHasher: tt.fields.userPasswordHasher,
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
newCode: tt.fields.newCode, newEncryptedCode: tt.fields.newCode,
checkPermission: tt.fields.checkPermission, checkPermission: tt.fields.checkPermission,
} }
err := r.AddUserHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail, tt.args.codeAlg) err := r.AddUserHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail, tt.args.codeAlg)
@ -1247,8 +1247,8 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
func TestCommandSide_ChangeUserHuman(t *testing.T) { func TestCommandSide_ChangeUserHuman(t *testing.T) {
type fields struct { type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
userPasswordHasher *crypto.PasswordHasher userPasswordHasher *crypto.Hasher
newCode cryptoCodeFunc newCode encrypedCodeFunc
checkPermission domain.PermissionCheck checkPermission domain.PermissionCheck
} }
type args struct { type args struct {
@ -1562,7 +1562,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
), ),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
newCode: mockCode("emailCode", time.Hour), newCode: mockEncryptedCode("emailCode", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1741,7 +1741,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
), ),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
newCode: mockCode("emailCode", time.Hour), newCode: mockEncryptedCode("emailCode", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1791,7 +1791,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
), ),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
newCode: mockCode("phoneCode", time.Hour), newCode: mockEncryptedCode("phoneCode", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -1939,7 +1939,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
), ),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
newCode: mockCode("phoneCode", time.Hour), newCode: mockEncryptedCode("phoneCode", time.Hour),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -2546,7 +2546,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore(t), eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher, userPasswordHasher: tt.fields.userPasswordHasher,
newCode: tt.fields.newCode, newEncryptedCode: tt.fields.newCode,
checkPermission: tt.fields.checkPermission, checkPermission: tt.fields.checkPermission,
} }
err := r.ChangeUserHuman(tt.args.ctx, tt.args.human, tt.args.codeAlg) err := r.ChangeUserHuman(tt.args.ctx, tt.args.human, tt.args.codeAlg)

View File

@ -266,7 +266,7 @@ func (wm *UserV2WriteModel) Reduce() error {
case *user.HumanPasswordCheckSucceededEvent: case *user.HumanPasswordCheckSucceededEvent:
wm.PasswordCheckFailedCount = 0 wm.PasswordCheckFailedCount = 0
case *user.HumanPasswordChangedEvent: case *user.HumanPasswordChangedEvent:
wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.PasswordEncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash)
wm.PasswordChangeRequired = e.ChangeRequired wm.PasswordChangeRequired = e.ChangeRequired
wm.EmptyPasswordCode() wm.EmptyPasswordCode()
case *user.HumanPasswordCodeAddedEvent: case *user.HumanPasswordCodeAddedEvent:
@ -470,7 +470,7 @@ func (wm *UserV2WriteModel) reduceHumanAddedEvent(e *user.HumanAddedEvent) {
wm.Email = e.EmailAddress wm.Email = e.EmailAddress
wm.Phone = e.PhoneNumber wm.Phone = e.PhoneNumber
wm.UserState = domain.UserStateActive wm.UserState = domain.UserStateActive
wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.PasswordEncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash)
wm.PasswordChangeRequired = e.ChangeRequired wm.PasswordChangeRequired = e.ChangeRequired
} }
@ -485,7 +485,7 @@ func (wm *UserV2WriteModel) reduceHumanRegisteredEvent(e *user.HumanRegisteredEv
wm.Email = e.EmailAddress wm.Email = e.EmailAddress
wm.Phone = e.PhoneNumber wm.Phone = e.PhoneNumber
wm.UserState = domain.UserStateActive wm.UserState = domain.UserStateActive
wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash) wm.PasswordEncodedHash = crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash)
wm.PasswordChangeRequired = e.ChangeRequired wm.PasswordChangeRequired = e.ChangeRequired
} }

View File

@ -48,7 +48,7 @@ func (c *Commands) verifyUserPasskeyCode(ctx context.Context, userID, resourceOw
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = verifyCryptoCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg, wm.ChangeDate, wm.Expiration, wm.CryptoCode, code) err = verifyEncryptedCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg, wm.ChangeDate, wm.Expiration, wm.CryptoCode, code) //nolint:staticcheck
if err != nil || wm.State != domain.PasswordlessInitCodeStateActive { if err != nil || wm.State != domain.PasswordlessInitCodeStateActive {
c.verifyUserPasskeyCodeFailed(ctx, wm) c.verifyUserPasskeyCodeFailed(ctx, wm)
return nil, zerrors.ThrowInvalidArgument(err, "COMMAND-Eeb2a", "Errors.User.Code.Invalid") return nil, zerrors.ThrowInvalidArgument(err, "COMMAND-Eeb2a", "Errors.User.Code.Invalid")
@ -156,6 +156,6 @@ func (c *Commands) addUserPasskeyCode(ctx context.Context, userID, resourceOwner
}, nil }, nil
} }
func (c *Commands) newPasskeyCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*CryptoCode, error) { func (c *Commands) newPasskeyCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (*EncryptedCode, error) {
return c.newCode(ctx, filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) return c.newEncryptedCode(ctx, filter, domain.SecretGeneratorTypePasswordlessInitCode, alg)
} }

View File

@ -138,7 +138,7 @@ func TestCommands_RegisterUserPasskeyWithCode(t *testing.T) {
es := eventstoreExpect(t, es := eventstoreExpect(t,
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))), expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
) )
code, err := newCryptoCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) code, err := newEncryptedCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) //nolint:staticcheck
require.NoError(t, err) require.NoError(t, err)
userAgg := &user.NewAggregate("user1", "org1").Aggregate userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct { type fields struct {
@ -236,7 +236,7 @@ func TestCommands_verifyUserPasskeyCode(t *testing.T) {
es := eventstoreExpect(t, es := eventstoreExpect(t,
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))), expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
) )
code, err := newCryptoCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) code, err := newEncryptedCode(ctx, es.Filter, domain.SecretGeneratorTypePasswordlessInitCode, alg) //nolint:staticcheck
require.NoError(t, err) require.NoError(t, err)
userAgg := &user.NewAggregate("user1", "org1").Aggregate userAgg := &user.NewAggregate("user1", "org1").Aggregate
@ -457,7 +457,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) {
alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t)) alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t))
userAgg := &user.NewAggregate("user1", "org1").Aggregate userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct { type fields struct {
newCode cryptoCodeFunc newCode encrypedCodeFunc
eventstore func(t *testing.T) *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
} }
@ -475,7 +475,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) {
{ {
name: "id generator error", name: "id generator error",
fields: fields{ fields: fields{
newCode: mockCode("passkey1", time.Hour), newCode: mockEncryptedCode("passkey1", time.Hour),
eventstore: expectEventstore(), eventstore: expectEventstore(),
idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe), idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe),
}, },
@ -488,7 +488,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) {
{ {
name: "success", name: "success",
fields: fields{ fields: fields{
newCode: mockCode("passkey1", time.Minute), newCode: mockEncryptedCode("passkey1", time.Minute),
eventstore: expectEventstore( eventstore: expectEventstore(
expectFilter(eventFromEventPusher( expectFilter(eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -530,7 +530,7 @@ func TestCommands_AddUserPasskeyCode(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &Commands{ c := &Commands{
newCode: tt.fields.newCode, newEncryptedCode: tt.fields.newCode,
eventstore: tt.fields.eventstore(t), eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
} }
@ -546,7 +546,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) {
userAgg := &user.NewAggregate("user1", "org1").Aggregate userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct { type fields struct {
newCode cryptoCodeFunc newCode encrypedCodeFunc
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
} }
@ -565,7 +565,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) {
{ {
name: "template error", name: "template error",
fields: fields{ fields: fields{
newCode: newCryptoCode, newCode: newEncryptedCode,
eventstore: eventstoreExpect(t), eventstore: eventstoreExpect(t),
}, },
args: args{ args: args{
@ -578,7 +578,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) {
{ {
name: "id generator error", name: "id generator error",
fields: fields{ fields: fields{
newCode: newCryptoCode, newCode: newEncryptedCode,
eventstore: eventstoreExpect(t), eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe), idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe),
}, },
@ -592,7 +592,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) {
{ {
name: "success", name: "success",
fields: fields{ fields: fields{
newCode: mockCode("passkey1", time.Minute), newCode: mockEncryptedCode("passkey1", time.Minute),
eventstore: eventstoreExpect(t, eventstore: eventstoreExpect(t,
expectFilter(eventFromEventPusher( expectFilter(eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -638,7 +638,7 @@ func TestCommands_AddUserPasskeyCodeURLTemplate(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &Commands{ c := &Commands{
newCode: tt.fields.newCode, newEncryptedCode: tt.fields.newCode,
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
} }
@ -653,7 +653,7 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) {
alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t)) alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t))
userAgg := &user.NewAggregate("user1", "org1").Aggregate userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct { type fields struct {
newCode cryptoCodeFunc newCode encrypedCodeFunc
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
} }
@ -671,7 +671,7 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) {
{ {
name: "id generator error", name: "id generator error",
fields: fields{ fields: fields{
newCode: newCryptoCode, newCode: newEncryptedCode,
eventstore: eventstoreExpect(t), eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe), idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe),
}, },
@ -684,7 +684,7 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) {
{ {
name: "success", name: "success",
fields: fields{ fields: fields{
newCode: mockCode("passkey1", time.Minute), newCode: mockEncryptedCode("passkey1", time.Minute),
eventstore: eventstoreExpect(t, eventstore: eventstoreExpect(t,
expectFilter(eventFromEventPusher( expectFilter(eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -730,7 +730,7 @@ func TestCommands_AddUserPasskeyCodeReturn(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &Commands{ c := &Commands{
newCode: tt.fields.newCode, newEncryptedCode: tt.fields.newCode,
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
} }
@ -745,7 +745,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t)) alg := crypto.CreateMockEncryptionAlg(gomock.NewController(t))
userAgg := &user.NewAggregate("user1", "org1").Aggregate userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct { type fields struct {
newCode cryptoCodeFunc newCode encrypedCodeFunc
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
idGenerator id.Generator idGenerator id.Generator
} }
@ -763,7 +763,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
{ {
name: "id generator error", name: "id generator error",
fields: fields{ fields: fields{
newCode: newCryptoCode, newCode: newEncryptedCode,
eventstore: eventstoreExpect(t), eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe), idGenerator: id_mock.NewIDGeneratorExpectError(t, io.ErrClosedPipe),
}, },
@ -776,7 +776,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
{ {
name: "crypto error", name: "crypto error",
fields: fields{ fields: fields{
newCode: newCryptoCode, newCode: newEncryptedCode,
eventstore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)), eventstore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"), idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "123"),
}, },
@ -789,7 +789,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
{ {
name: "filter query error", name: "filter query error",
fields: fields{ fields: fields{
newCode: newCryptoCode, newCode: newEncryptedCode,
eventstore: eventstoreExpect(t, eventstore: eventstoreExpect(t,
expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))), expectFilter(eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypePasswordlessInitCode))),
expectFilterError(io.ErrClosedPipe), expectFilterError(io.ErrClosedPipe),
@ -805,7 +805,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
{ {
name: "push error", name: "push error",
fields: fields{ fields: fields{
newCode: mockCode("passkey1", time.Minute), newCode: mockEncryptedCode("passkey1", time.Minute),
eventstore: eventstoreExpect(t, eventstore: eventstoreExpect(t,
expectFilter(eventFromEventPusher( expectFilter(eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -844,7 +844,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
{ {
name: "success", name: "success",
fields: fields{ fields: fields{
newCode: mockCode("passkey1", time.Minute), newCode: mockEncryptedCode("passkey1", time.Minute),
eventstore: eventstoreExpect(t, eventstore: eventstoreExpect(t,
expectFilter(eventFromEventPusher( expectFilter(eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(), user.NewHumanAddedEvent(context.Background(),
@ -890,7 +890,7 @@ func TestCommands_addUserPasskeyCode(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &Commands{ c := &Commands{
newCode: tt.fields.newCode, newEncryptedCode: tt.fields.newCode,
eventstore: tt.fields.eventstore, eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator, idGenerator: tt.fields.idGenerator,
} }

View File

@ -55,7 +55,7 @@ func (c *Commands) requestPasswordReset(ctx context.Context, userID string, retu
return nil, nil, err return nil, nil, err
} }
} }
code, err := c.newCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordResetCode, c.userEncryption) code, err := c.newEncryptedCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordResetCode, c.userEncryption) //nolint:staticcheck
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@ -330,7 +330,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
checkPermission domain.PermissionCheck checkPermission domain.PermissionCheck
eventstore func(t *testing.T) *eventstore.Eventstore eventstore func(t *testing.T) *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm userEncryption crypto.EncryptionAlgorithm
newCode cryptoCodeFunc newCode encrypedCodeFunc
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -452,7 +452,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
), ),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
newCode: mockCode("code", 10*time.Minute), newCode: mockEncryptedCode("code", 10*time.Minute),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -492,7 +492,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
), ),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
newCode: mockCode("code", 10*time.Minute), newCode: mockEncryptedCode("code", 10*time.Minute),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -533,7 +533,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
), ),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
newCode: mockCode("code", 10*time.Minute), newCode: mockEncryptedCode("code", 10*time.Minute),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -575,7 +575,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
), ),
), ),
checkPermission: newMockPermissionCheckAllowed(), checkPermission: newMockPermissionCheckAllowed(),
newCode: mockCode("code", 10*time.Minute), newCode: mockEncryptedCode("code", 10*time.Minute),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
@ -596,7 +596,7 @@ func TestCommands_requestPasswordReset(t *testing.T) {
checkPermission: tt.fields.checkPermission, checkPermission: tt.fields.checkPermission,
eventstore: tt.fields.eventstore(t), eventstore: tt.fields.eventstore(t),
userEncryption: tt.fields.userEncryption, userEncryption: tt.fields.userEncryption,
newCode: tt.fields.newCode, newEncryptedCode: tt.fields.newCode,
} }
got, gotPlainCode, err := c.requestPasswordReset(tt.args.ctx, tt.args.userID, tt.args.returnCode, tt.args.urlTmpl, tt.args.notificationType) got, gotPlainCode, err := c.requestPasswordReset(tt.args.ctx, tt.args.userID, tt.args.returnCode, tt.args.urlTmpl, tt.args.notificationType)
require.ErrorIs(t, err, tt.res.err) require.ErrorIs(t, err, tt.res.err)

View File

@ -55,7 +55,7 @@ func (c *Commands) ResendUserPhoneCodeReturnCode(ctx context.Context, userID str
} }
func (c *Commands) changeUserPhoneWithCode(ctx context.Context, userID, phone string, alg crypto.EncryptionAlgorithm, returnCode bool) (*domain.Phone, error) { func (c *Commands) changeUserPhoneWithCode(ctx context.Context, userID, phone string, alg crypto.EncryptionAlgorithm, returnCode bool) (*domain.Phone, error) {
config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -64,7 +64,7 @@ func (c *Commands) changeUserPhoneWithCode(ctx context.Context, userID, phone st
} }
func (c *Commands) resendUserPhoneCode(ctx context.Context, userID string, alg crypto.EncryptionAlgorithm, returnCode bool) (*domain.Phone, error) { func (c *Commands) resendUserPhoneCode(ctx context.Context, userID string, alg crypto.EncryptionAlgorithm, returnCode bool) (*domain.Phone, error) {
config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -117,7 +117,7 @@ func (c *Commands) resendUserPhoneCodeWithGenerator(ctx context.Context, userID
} }
func (c *Commands) VerifyUserPhone(ctx context.Context, userID, code string, alg crypto.EncryptionAlgorithm) (*domain.ObjectDetails, error) { func (c *Commands) VerifyUserPhone(ctx context.Context, userID, code string, alg crypto.EncryptionAlgorithm) (*domain.ObjectDetails, error) {
config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode) //nolint:staticcheck
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -216,7 +216,7 @@ func (c *UserPhoneEvents) VerifyCode(ctx context.Context, code string, gen crypt
return zerrors.ThrowInvalidArgument(nil, "COMMAND-Fia4a", "Errors.User.Code.Empty") return zerrors.ThrowInvalidArgument(nil, "COMMAND-Fia4a", "Errors.User.Code.Empty")
} }
err := crypto.VerifyCode(c.model.CodeCreationDate, c.model.CodeExpiry, c.model.Code, code, gen) err := crypto.VerifyCode(c.model.CodeCreationDate, c.model.CodeExpiry, c.model.Code, code, gen.Alg())
if err == nil { if err == nil {
c.events = append(c.events, user.NewHumanPhoneVerifiedEvent(ctx, c.aggregate)) c.events = append(c.events, user.NewHumanPhoneVerifiedEvent(ctx, c.aggregate))
return nil return nil

View File

@ -8,7 +8,8 @@ import (
type SystemDefaults struct { type SystemDefaults struct {
SecretGenerators SecretGenerators SecretGenerators SecretGenerators
PasswordHasher crypto.PasswordHashConfig PasswordHasher crypto.HashConfig
SecretHasher crypto.HashConfig
Multifactors MultifactorConfig Multifactors MultifactorConfig
DomainVerification DomainVerification DomainVerification DomainVerification
Notifications Notifications Notifications Notifications
@ -16,7 +17,6 @@ type SystemDefaults struct {
} }
type SecretGenerators struct { type SecretGenerators struct {
PasswordSaltCost int
MachineKeySize uint32 MachineKeySize uint32
ApplicationKeySize uint32 ApplicationKeySize uint32
} }

View File

@ -1,27 +0,0 @@
package crypto
import (
"golang.org/x/crypto/bcrypt"
)
var _ HashAlgorithm = (*BCrypt)(nil)
type BCrypt struct {
cost int
}
func NewBCrypt(cost int) *BCrypt {
return &BCrypt{cost: cost}
}
func (b *BCrypt) Algorithm() string {
return "bcrypt"
}
func (b *BCrypt) Hash(value []byte) ([]byte, error) {
return bcrypt.GenerateFromPassword(value, b.cost)
}
func (b *BCrypt) CompareHash(hashed, value []byte) error {
return bcrypt.CompareHashAndPassword(hashed, value)
}

View File

@ -26,7 +26,7 @@ type GeneratorConfig struct {
type Generator interface { type Generator interface {
Length() uint Length() uint
Expiry() time.Duration Expiry() time.Duration
Alg() Crypto Alg() EncryptionAlgorithm
Runes() []rune Runes() []rune
} }
@ -53,7 +53,7 @@ type encryptionGenerator struct {
alg EncryptionAlgorithm alg EncryptionAlgorithm
} }
func (g *encryptionGenerator) Alg() Crypto { func (g *encryptionGenerator) Alg() EncryptionAlgorithm {
return g.alg return g.alg
} }
@ -64,22 +64,30 @@ func NewEncryptionGenerator(config GeneratorConfig, algorithm EncryptionAlgorith
} }
} }
type hashGenerator struct { type HashGenerator struct {
generator generator
alg HashAlgorithm hasher *Hasher
} }
func (g *hashGenerator) Alg() Crypto { func NewHashGenerator(config GeneratorConfig, hasher *Hasher) *HashGenerator {
return g.alg return &HashGenerator{
}
func NewHashGenerator(config GeneratorConfig, algorithm HashAlgorithm) Generator {
return &hashGenerator{
newGenerator(config), newGenerator(config),
algorithm, hasher,
} }
} }
func (g *HashGenerator) NewCode() (encoded, plain string, err error) {
plain, err = GenerateRandomString(g.Length(), g.Runes())
if err != nil {
return "", "", err
}
encoded, err = g.hasher.Hash(plain)
if err != nil {
return "", "", err
}
return encoded, plain, nil
}
func newGenerator(config GeneratorConfig) generator { func newGenerator(config GeneratorConfig) generator {
var runes []rune var runes []rune
if config.IncludeLowerLetters { if config.IncludeLowerLetters {
@ -120,21 +128,11 @@ func IsCodeExpired(creationDate time.Time, expiry time.Duration) bool {
return creationDate.Add(expiry).Before(time.Now().UTC()) return creationDate.Add(expiry).Before(time.Now().UTC())
} }
func VerifyCode(creationDate time.Time, expiry time.Duration, cryptoCode *CryptoValue, verificationCode string, g Generator) error { func VerifyCode(creationDate time.Time, expiry time.Duration, cryptoCode *CryptoValue, verificationCode string, algorithm EncryptionAlgorithm) error {
return VerifyCodeWithAlgorithm(creationDate, expiry, cryptoCode, verificationCode, g.Alg())
}
func VerifyCodeWithAlgorithm(creationDate time.Time, expiry time.Duration, cryptoCode *CryptoValue, verificationCode string, algorithm Crypto) error {
if IsCodeExpired(creationDate, expiry) { if IsCodeExpired(creationDate, expiry) {
return zerrors.ThrowPreconditionFailed(nil, "CODE-QvUQ4P", "Errors.User.Code.Expired") return zerrors.ThrowPreconditionFailed(nil, "CODE-QvUQ4P", "Errors.User.Code.Expired")
} }
switch alg := algorithm.(type) { return verifyEncryptedCode(cryptoCode, verificationCode, algorithm)
case EncryptionAlgorithm:
return verifyEncryptedCode(cryptoCode, verificationCode, alg)
case HashAlgorithm:
return verifyHashedCode(cryptoCode, verificationCode, alg)
}
return zerrors.ThrowInvalidArgument(nil, "CODE-fW2gNa", "Errors.User.Code.GeneratorAlgNotSupported")
} }
func GenerateRandomString(length uint, chars []rune) (string, error) { func GenerateRandomString(length uint, chars []rune) (string, error) {
@ -173,10 +171,3 @@ func verifyEncryptedCode(cryptoCode *CryptoValue, verificationCode string, alg E
} }
return nil return nil
} }
func verifyHashedCode(cryptoCode *CryptoValue, verificationCode string, alg HashAlgorithm) error {
if cryptoCode == nil {
return zerrors.ThrowInvalidArgument(nil, "CRYPT-2q3r", "cryptoCode must not be nil")
}
return CompareHash(cryptoCode, []byte(verificationCode), alg)
}

View File

@ -5,6 +5,7 @@
// //
// mockgen -source code.go -destination ./code_mock.go -package crypto // mockgen -source code.go -destination ./code_mock.go -package crypto
// //
// Package crypto is a generated GoMock package. // Package crypto is a generated GoMock package.
package crypto package crypto
@ -39,10 +40,10 @@ func (m *MockGenerator) EXPECT() *MockGeneratorMockRecorder {
} }
// Alg mocks base method. // Alg mocks base method.
func (m *MockGenerator) Alg() Crypto { func (m *MockGenerator) Alg() EncryptionAlgorithm {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Alg") ret := m.ctrl.Call(m, "Alg")
ret0, _ := ret[0].(Crypto) ret0, _ := ret[0].(EncryptionAlgorithm)
return ret0 return ret0
} }

View File

@ -60,32 +60,13 @@ func createMockEncryptionAlgorithm(ctrl *gomock.Controller, encryptFunction func
return mCrypto return mCrypto
} }
func CreateMockHashAlg(ctrl *gomock.Controller) HashAlgorithm { func createMockCrypto(t *testing.T) EncryptionAlgorithm {
mCrypto := NewMockHashAlgorithm(ctrl) mCrypto := NewMockEncryptionAlgorithm(gomock.NewController(t))
mCrypto.EXPECT().Algorithm().AnyTimes().Return("hash")
mCrypto.EXPECT().Hash(gomock.Any()).AnyTimes().DoAndReturn(
func(code []byte) ([]byte, error) {
return code, nil
},
)
mCrypto.EXPECT().CompareHash(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(
func(hashed, comparer []byte) error {
if string(hashed) != string(comparer) {
return zerrors.ThrowInternal(nil, "id", "invalid")
}
return nil
},
)
return mCrypto
}
func createMockCrypto(t *testing.T) Crypto {
mCrypto := NewMockCrypto(gomock.NewController(t))
mCrypto.EXPECT().Algorithm().AnyTimes().Return("crypto") mCrypto.EXPECT().Algorithm().AnyTimes().Return("crypto")
return mCrypto return mCrypto
} }
func createMockGenerator(t *testing.T, crypto Crypto) Generator { func createMockGenerator(t *testing.T, crypto EncryptionAlgorithm) Generator {
mGenerator := NewMockGenerator(gomock.NewController(t)) mGenerator := NewMockGenerator(gomock.NewController(t))
mGenerator.EXPECT().Alg().AnyTimes().Return(crypto) mGenerator.EXPECT().Alg().AnyTimes().Return(crypto)
return mGenerator return mGenerator

View File

@ -102,25 +102,10 @@ func TestVerifyCode(t *testing.T) {
}, },
false, false,
}, },
{
"hash alg ok",
args{
creationDate: time.Now(),
expiry: 5 * time.Minute,
cryptoCode: &CryptoValue{
CryptoType: TypeHash,
Algorithm: "hash",
Crypted: []byte("code"),
},
verificationCode: "code",
g: createMockGenerator(t, CreateMockHashAlg(gomock.NewController(t))),
},
false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if err := VerifyCode(tt.args.creationDate, tt.args.expiry, tt.args.cryptoCode, tt.args.verificationCode, tt.args.g); (err != nil) != tt.wantErr { if err := VerifyCode(tt.args.creationDate, tt.args.expiry, tt.args.cryptoCode, tt.args.verificationCode, tt.args.g.Alg()); (err != nil) != tt.wantErr {
t.Errorf("VerifyCode() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("VerifyCode() error = %v, wantErr %v", err, tt.wantErr)
} }
}) })
@ -222,85 +207,3 @@ func Test_verifyEncryptedCode(t *testing.T) {
}) })
} }
} }
func Test_verifyHashedCode(t *testing.T) {
type args struct {
cryptoCode *CryptoValue
verificationCode string
alg HashAlgorithm
}
tests := []struct {
name string
args args
wantErr bool
}{
{
"nil error",
args{
cryptoCode: nil,
verificationCode: "",
alg: CreateMockHashAlg(gomock.NewController(t)),
},
true,
},
{
"wrong cryptotype error",
args{
cryptoCode: &CryptoValue{
CryptoType: TypeEncryption,
Crypted: nil,
},
verificationCode: "",
alg: CreateMockHashAlg(gomock.NewController(t)),
},
true,
},
{
"wrong algorithm error",
args{
cryptoCode: &CryptoValue{
CryptoType: TypeHash,
Algorithm: "hash2",
Crypted: nil,
},
verificationCode: "",
alg: CreateMockHashAlg(gomock.NewController(t)),
},
true,
},
{
"wrong verification code error",
args{
cryptoCode: &CryptoValue{
CryptoType: TypeHash,
Algorithm: "hash",
Crypted: []byte("code"),
},
verificationCode: "wrong",
alg: CreateMockHashAlg(gomock.NewController(t)),
},
true,
},
{
"verification code ok",
args{
cryptoCode: &CryptoValue{
CryptoType: TypeHash,
Algorithm: "hash",
Crypted: []byte("code"),
},
verificationCode: "code",
alg: CreateMockHashAlg(gomock.NewController(t)),
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := verifyHashedCode(tt.args.cryptoCode, tt.args.verificationCode, tt.args.alg); (err != nil) != tt.wantErr {
t.Errorf("verifyHashedCode() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -13,12 +13,8 @@ const (
TypeHash // Depcrecated: use [passwap.Swapper] instead TypeHash // Depcrecated: use [passwap.Swapper] instead
) )
type Crypto interface {
Algorithm() string
}
type EncryptionAlgorithm interface { type EncryptionAlgorithm interface {
Crypto Algorithm() string
EncryptionKeyID() string EncryptionKeyID() string
DecryptionKeyIDs() []string DecryptionKeyIDs() []string
Encrypt(value []byte) ([]byte, error) Encrypt(value []byte) ([]byte, error)
@ -26,13 +22,6 @@ type EncryptionAlgorithm interface {
DecryptString(hashed []byte, keyID string) (string, error) DecryptString(hashed []byte, keyID string) (string, error)
} }
// Depcrecated: use [passwap.Swapper] instead
type HashAlgorithm interface {
Crypto
Hash(value []byte) ([]byte, error)
CompareHash(hashed, comparer []byte) error
}
type CryptoValue struct { type CryptoValue struct {
CryptoType CryptoType CryptoType CryptoType
Algorithm string Algorithm string
@ -59,14 +48,8 @@ func (c *CryptoValue) Scan(src interface{}) error {
type CryptoType int type CryptoType int
func Crypt(value []byte, c Crypto) (*CryptoValue, error) { func Crypt(value []byte, alg EncryptionAlgorithm) (*CryptoValue, error) {
switch alg := c.(type) {
case EncryptionAlgorithm:
return Encrypt(value, alg) return Encrypt(value, alg)
case HashAlgorithm:
return Hash(value, alg)
}
return nil, zerrors.ThrowInternal(nil, "CRYPT-r4IaHZ", "algorithm not supported")
} }
func Encrypt(value []byte, alg EncryptionAlgorithm) (*CryptoValue, error) { func Encrypt(value []byte, alg EncryptionAlgorithm) (*CryptoValue, error) {
@ -108,33 +91,6 @@ func checkEncryptionAlgorithm(value *CryptoValue, alg EncryptionAlgorithm) error
return zerrors.ThrowInvalidArgument(nil, "CRYPT-Kq12vn", "value was encrypted with a different key") return zerrors.ThrowInvalidArgument(nil, "CRYPT-Kq12vn", "value was encrypted with a different key")
} }
func Hash(value []byte, alg HashAlgorithm) (*CryptoValue, error) {
hashed, err := alg.Hash(value)
if err != nil {
return nil, zerrors.ThrowInternal(err, "CRYPT-rBVaJU", "error hashing value")
}
return &CryptoValue{
CryptoType: TypeHash,
Algorithm: alg.Algorithm(),
Crypted: hashed,
}, nil
}
func CompareHash(value *CryptoValue, comparer []byte, alg HashAlgorithm) error {
if value.Algorithm != alg.Algorithm() {
return zerrors.ThrowInvalidArgument(nil, "CRYPT-HF32f", "value was hashed with a different algorithm")
}
return alg.CompareHash(value.Crypted, comparer)
}
func FillHash(value []byte, alg HashAlgorithm) *CryptoValue {
return &CryptoValue{
CryptoType: TypeHash,
Algorithm: alg.Algorithm(),
Crypted: value,
}
}
func CheckToken(alg EncryptionAlgorithm, token string, content string) error { func CheckToken(alg EncryptionAlgorithm, token string, content string) error {
if token == "" { if token == "" {
return zerrors.ThrowPermissionDenied(nil, "CRYPTO-Sfefs", "Errors.Intent.InvalidToken") return zerrors.ThrowPermissionDenied(nil, "CRYPTO-Sfefs", "Errors.Intent.InvalidToken")
@ -152,3 +108,12 @@ func CheckToken(alg EncryptionAlgorithm, token string, content string) error {
} }
return nil return nil
} }
// SecretOrEncodedHash returns the Crypted value from legacy [CryptoValue] if it is not nil.
// otherwise it will returns the encoded hash string.
func SecretOrEncodedHash(secret *CryptoValue, encoded string) string {
if secret != nil {
return string(secret.Crypted)
}
return encoded
}

View File

@ -5,6 +5,7 @@
// //
// mockgen -source crypto.go -destination ./crypto_mock.go -package crypto // mockgen -source crypto.go -destination ./crypto_mock.go -package crypto
// //
// Package crypto is a generated GoMock package. // Package crypto is a generated GoMock package.
package crypto package crypto
@ -14,43 +15,6 @@ import (
gomock "go.uber.org/mock/gomock" gomock "go.uber.org/mock/gomock"
) )
// MockCrypto is a mock of Crypto interface.
type MockCrypto struct {
ctrl *gomock.Controller
recorder *MockCryptoMockRecorder
}
// MockCryptoMockRecorder is the mock recorder for MockCrypto.
type MockCryptoMockRecorder struct {
mock *MockCrypto
}
// NewMockCrypto creates a new mock instance.
func NewMockCrypto(ctrl *gomock.Controller) *MockCrypto {
mock := &MockCrypto{ctrl: ctrl}
mock.recorder = &MockCryptoMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCrypto) EXPECT() *MockCryptoMockRecorder {
return m.recorder
}
// Algorithm mocks base method.
func (m *MockCrypto) Algorithm() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Algorithm")
ret0, _ := ret[0].(string)
return ret0
}
// Algorithm indicates an expected call of Algorithm.
func (mr *MockCryptoMockRecorder) Algorithm() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockCrypto)(nil).Algorithm))
}
// MockEncryptionAlgorithm is a mock of EncryptionAlgorithm interface. // MockEncryptionAlgorithm is a mock of EncryptionAlgorithm interface.
type MockEncryptionAlgorithm struct { type MockEncryptionAlgorithm struct {
ctrl *gomock.Controller ctrl *gomock.Controller
@ -160,69 +124,3 @@ func (mr *MockEncryptionAlgorithmMockRecorder) EncryptionKeyID() *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EncryptionKeyID", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).EncryptionKeyID)) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EncryptionKeyID", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).EncryptionKeyID))
} }
// MockHashAlgorithm is a mock of HashAlgorithm interface.
type MockHashAlgorithm struct {
ctrl *gomock.Controller
recorder *MockHashAlgorithmMockRecorder
}
// MockHashAlgorithmMockRecorder is the mock recorder for MockHashAlgorithm.
type MockHashAlgorithmMockRecorder struct {
mock *MockHashAlgorithm
}
// NewMockHashAlgorithm creates a new mock instance.
func NewMockHashAlgorithm(ctrl *gomock.Controller) *MockHashAlgorithm {
mock := &MockHashAlgorithm{ctrl: ctrl}
mock.recorder = &MockHashAlgorithmMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockHashAlgorithm) EXPECT() *MockHashAlgorithmMockRecorder {
return m.recorder
}
// Algorithm mocks base method.
func (m *MockHashAlgorithm) Algorithm() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Algorithm")
ret0, _ := ret[0].(string)
return ret0
}
// Algorithm indicates an expected call of Algorithm.
func (mr *MockHashAlgorithmMockRecorder) Algorithm() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockHashAlgorithm)(nil).Algorithm))
}
// CompareHash mocks base method.
func (m *MockHashAlgorithm) CompareHash(hashed, comparer []byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CompareHash", hashed, comparer)
ret0, _ := ret[0].(error)
return ret0
}
// CompareHash indicates an expected call of CompareHash.
func (mr *MockHashAlgorithmMockRecorder) CompareHash(hashed, comparer any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompareHash", reflect.TypeOf((*MockHashAlgorithm)(nil).CompareHash), hashed, comparer)
}
// Hash mocks base method.
func (m *MockHashAlgorithm) Hash(value []byte) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Hash", value)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Hash indicates an expected call of Hash.
func (mr *MockHashAlgorithmMockRecorder) Hash(value any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hash", reflect.TypeOf((*MockHashAlgorithm)(nil).Hash), value)
}

View File

@ -60,7 +60,7 @@ func (a *alg) Algorithm() string {
func TestCrypt(t *testing.T) { func TestCrypt(t *testing.T) {
type args struct { type args struct {
value []byte value []byte
c Crypto c EncryptionAlgorithm
} }
tests := []struct { tests := []struct {
name string name string
@ -74,18 +74,6 @@ func TestCrypt(t *testing.T) {
&CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("test")}, &CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("test")},
false, false,
}, },
{
"hash",
args{[]byte("test"), &mockHashCrypto{}},
&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")},
false,
},
{
"wrong type",
args{[]byte("test"), &alg{}},
nil,
true,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -208,66 +196,3 @@ func TestDecryptString(t *testing.T) {
}) })
} }
} }
func TestHash(t *testing.T) {
type args struct {
value []byte
c HashAlgorithm
}
tests := []struct {
name string
args args
want *CryptoValue
wantErr bool
}{
{
"ok",
args{[]byte("test"), &mockHashCrypto{}},
&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Hash(tt.args.value, tt.args.c)
if (err != nil) != tt.wantErr {
t.Errorf("Hash() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Hash() = %v, want %v", got, tt.want)
}
})
}
}
func TestCompareHash(t *testing.T) {
type args struct {
value *CryptoValue
comparer []byte
c HashAlgorithm
}
tests := []struct {
name string
args args
wantErr bool
}{
{
"ok",
args{&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, []byte("test"), &mockHashCrypto{}},
false,
},
{
"wrong",
args{&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, []byte("test2"), &mockHashCrypto{}},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := CompareHash(tt.args.value, tt.args.comparer, tt.args.c); (err != nil) != tt.wantErr {
t.Errorf("CompareHash() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -16,12 +16,12 @@ import (
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
type PasswordHasher struct { type Hasher struct {
*passwap.Swapper *passwap.Swapper
Prefixes []string Prefixes []string
} }
func (h *PasswordHasher) EncodingSupported(encodedHash string) bool { func (h *Hasher) EncodingSupported(encodedHash string) bool {
for _, prefix := range h.Prefixes { for _, prefix := range h.Prefixes {
if strings.HasPrefix(encodedHash, prefix) { if strings.HasPrefix(encodedHash, prefix) {
return true return true
@ -54,12 +54,12 @@ const (
HashModeSHA512 HashMode = "sha512" HashModeSHA512 HashMode = "sha512"
) )
type PasswordHashConfig struct { type HashConfig struct {
Verifiers []HashName Verifiers []HashName
Hasher HasherConfig Hasher HasherConfig
} }
func (c *PasswordHashConfig) PasswordHasher() (*PasswordHasher, error) { func (c *HashConfig) NewHasher() (*Hasher, error) {
verifiers, vPrefixes, err := c.buildVerifiers() verifiers, vPrefixes, err := c.buildVerifiers()
if err != nil { if err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "CRYPT-sahW9", "password hash config invalid") return nil, zerrors.ThrowInvalidArgument(err, "CRYPT-sahW9", "password hash config invalid")
@ -68,7 +68,7 @@ func (c *PasswordHashConfig) PasswordHasher() (*PasswordHasher, error) {
if err != nil { if err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "CRYPT-Que4r", "password hash config invalid") return nil, zerrors.ThrowInvalidArgument(err, "CRYPT-Que4r", "password hash config invalid")
} }
return &PasswordHasher{ return &Hasher{
Swapper: passwap.NewSwapper(hasher, verifiers...), Swapper: passwap.NewSwapper(hasher, verifiers...),
Prefixes: append(hPrefixes, vPrefixes...), Prefixes: append(hPrefixes, vPrefixes...),
}, nil }, nil
@ -105,7 +105,7 @@ var knowVerifiers = map[HashName]prefixVerifier{
}, },
} }
func (c *PasswordHashConfig) buildVerifiers() (verifiers []verifier.Verifier, prefixes []string, err error) { func (c *HashConfig) buildVerifiers() (verifiers []verifier.Verifier, prefixes []string, err error) {
verifiers = make([]verifier.Verifier, len(c.Verifiers)) verifiers = make([]verifier.Verifier, len(c.Verifiers))
prefixes = make([]string, 0, len(c.Verifiers)+1) prefixes = make([]string, 0, len(c.Verifiers)+1)
for i, name := range c.Verifiers { for i, name := range c.Verifiers {

View File

@ -49,7 +49,7 @@ func TestPasswordHasher_EncodingSupported(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
h := &PasswordHasher{ h := &Hasher{
Prefixes: []string{bcrypt.Prefix, argon2.Prefix}, Prefixes: []string{bcrypt.Prefix, argon2.Prefix},
} }
got := h.EncodingSupported(tt.encodedHash) got := h.EncodingSupported(tt.encodedHash)
@ -340,11 +340,11 @@ func TestPasswordHashConfig_PasswordHasher(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &PasswordHashConfig{ c := &HashConfig{
Verifiers: tt.fields.Verifiers, Verifiers: tt.fields.Verifiers,
Hasher: tt.fields.Hasher, Hasher: tt.fields.Hasher,
} }
got, err := c.PasswordHasher() got, err := c.NewHasher()
if tt.wantErr { if tt.wantErr {
assert.Error(t, err) assert.Error(t, err)
return return

View File

@ -11,7 +11,7 @@ type APIApp struct {
AppID string AppID string
AppName string AppName string
ClientID string ClientID string
ClientSecret *crypto.CryptoValue EncodedHash string
ClientSecretString string ClientSecretString string
AuthMethodType APIAuthMethodType AuthMethodType APIAuthMethodType
@ -41,21 +41,21 @@ func (a *APIApp) setClientID(clientID string) {
a.ClientID = clientID a.ClientID = clientID
} }
func (a *APIApp) setClientSecret(clientSecret *crypto.CryptoValue) { func (a *APIApp) setClientSecret(encodedHash string) {
a.ClientSecret = clientSecret a.EncodedHash = encodedHash
} }
func (a *APIApp) requiresClientSecret() bool { func (a *APIApp) requiresClientSecret() bool {
return a.AuthMethodType == APIAuthMethodTypeBasic return a.AuthMethodType == APIAuthMethodTypeBasic
} }
func (a *APIApp) GenerateClientSecretIfNeeded(generator crypto.Generator) (secret string, err error) { func (a *APIApp) GenerateClientSecretIfNeeded(generator *crypto.HashGenerator) (plain string, err error) {
if a.AuthMethodType == APIAuthMethodTypePrivateKeyJWT { if a.AuthMethodType == APIAuthMethodTypePrivateKeyJWT {
return "", nil return "", nil
} }
a.ClientSecret, secret, err = NewClientSecret(generator) a.EncodedHash, plain, err = generator.NewCode()
if err != nil { if err != nil {
return "", err return "", err
} }
return secret, nil return plain, nil
} }

View File

@ -4,16 +4,12 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/id" "github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/zerrors"
) )
type oAuthApplication interface { type oAuthApplication interface {
setClientID(clientID string) setClientID(clientID string)
setClientSecret(secret *crypto.CryptoValue) setClientSecret(encodedHash string)
requiresClientSecret() bool requiresClientSecret() bool
} }
@ -37,23 +33,14 @@ func NewClientID(idGenerator id.Generator, projectName string) (string, error) {
return fmt.Sprintf("%s@%s", rndID, strings.ReplaceAll(strings.ToLower(projectName), " ", "_")), nil return fmt.Sprintf("%s@%s", rndID, strings.ReplaceAll(strings.ToLower(projectName), " ", "_")), nil
} }
func SetNewClientSecretIfNeeded(a oAuthApplication, generator crypto.Generator) (string, error) { func SetNewClientSecretIfNeeded(a oAuthApplication, generate func() (encodedHash, plain string, err error)) (string, error) {
if !a.requiresClientSecret() { if !a.requiresClientSecret() {
return "", nil return "", nil
} }
clientSecret, secretString, err := NewClientSecret(generator) encodedHash, plain, err := generate()
if err != nil { if err != nil {
return "", err return "", err
} }
a.setClientSecret(clientSecret) a.setClientSecret(encodedHash)
return secretString, nil return plain, nil
}
func NewClientSecret(generator crypto.Generator) (*crypto.CryptoValue, string, error) {
cryptoValue, stringSecret, err := crypto.NewCode(generator)
if err != nil {
logging.Log("MODEL-UpnTI").OnError(err).Error("unable to create client secret")
return nil, "", zerrors.ThrowInternal(err, "MODEL-gH2Wl", "Errors.Project.CouldNotGenerateClientSecret")
}
return cryptoValue, stringSecret, nil
} }

View File

@ -5,7 +5,6 @@ import (
"time" "time"
http_util "github.com/zitadel/zitadel/internal/api/http" http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/eventstore/v1/models"
) )
@ -28,7 +27,7 @@ type OIDCApp struct {
AppID string AppID string
AppName string AppName string
ClientID string ClientID string
ClientSecret *crypto.CryptoValue EncodedHash string
ClientSecretString string ClientSecretString string
RedirectUris []string RedirectUris []string
ResponseTypes []OIDCResponseType ResponseTypes []OIDCResponseType
@ -62,8 +61,8 @@ func (a *OIDCApp) setClientID(clientID string) {
a.ClientID = clientID a.ClientID = clientID
} }
func (a *OIDCApp) setClientSecret(clientSecret *crypto.CryptoValue) { func (a *OIDCApp) setClientSecret(encodedHash string) {
a.ClientSecret = clientSecret a.EncodedHash = encodedHash
} }
func (a *OIDCApp) requiresClientSecret() bool { func (a *OIDCApp) requiresClientSecret() bool {

View File

@ -4,6 +4,8 @@ import (
"strings" "strings"
"time" "time"
"golang.org/x/net/context"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models" es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
@ -102,10 +104,10 @@ func (u *Human) EnsureDisplayName() {
u.DisplayName = u.Username u.DisplayName = u.Username
} }
func (u *Human) HashPasswordIfExisting(policy *PasswordComplexityPolicy, hasher *crypto.PasswordHasher, onetime bool) error { func (u *Human) HashPasswordIfExisting(ctx context.Context, policy *PasswordComplexityPolicy, hasher *crypto.Hasher, onetime bool) error {
if u.Password != nil { if u.Password != nil {
u.Password.ChangeRequired = onetime u.Password.ChangeRequired = onetime
return u.Password.HashPasswordIfExisting(policy, hasher) return u.Password.HashPasswordIfExisting(ctx, policy, hasher)
} }
return nil return nil
} }

View File

@ -1,10 +1,12 @@
package domain package domain
import ( import (
"context"
"time" "time"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models" es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -30,7 +32,7 @@ type PasswordCode struct {
NotificationType NotificationType NotificationType NotificationType
} }
func (p *Password) HashPasswordIfExisting(policy *PasswordComplexityPolicy, hasher *crypto.PasswordHasher) error { func (p *Password) HashPasswordIfExisting(ctx context.Context, policy *PasswordComplexityPolicy, hasher *crypto.Hasher) error {
if p.SecretString == "" { if p.SecretString == "" {
return nil return nil
} }
@ -40,7 +42,9 @@ func (p *Password) HashPasswordIfExisting(policy *PasswordComplexityPolicy, hash
if err := policy.Check(p.SecretString); err != nil { if err := policy.Check(p.SecretString); err != nil {
return err return err
} }
_, spanHash := tracing.NewNamedSpan(ctx, "passwap.Hash")
encoded, err := hasher.Hash(p.SecretString) encoded, err := hasher.Hash(p.SecretString)
spanHash.EndWithError(err)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,14 +0,0 @@
package domain
import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/zerrors"
)
func NewMachineClientSecret(generator crypto.Generator) (*crypto.CryptoValue, string, error) {
cryptoValue, stringSecret, err := crypto.NewCode(generator)
if err != nil {
return nil, "", zerrors.ThrowInternal(err, "MODEL-57cjsiw", "Errors.User.Machine.Secret.CouldNotGenerate")
}
return cryptoValue, stringSecret, nil
}

View File

@ -15,98 +15,98 @@ import (
) )
var ( var (
expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps6.id,` + expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps7.id,` +
` projections.apps6.name,` + ` projections.apps7.name,` +
` projections.apps6.project_id,` + ` projections.apps7.project_id,` +
` projections.apps6.creation_date,` + ` projections.apps7.creation_date,` +
` projections.apps6.change_date,` + ` projections.apps7.change_date,` +
` projections.apps6.resource_owner,` + ` projections.apps7.resource_owner,` +
` projections.apps6.state,` + ` projections.apps7.state,` +
` projections.apps6.sequence,` + ` projections.apps7.sequence,` +
// api config // api config
` projections.apps6_api_configs.app_id,` + ` projections.apps7_api_configs.app_id,` +
` projections.apps6_api_configs.client_id,` + ` projections.apps7_api_configs.client_id,` +
` projections.apps6_api_configs.auth_method,` + ` projections.apps7_api_configs.auth_method,` +
// oidc config // oidc config
` projections.apps6_oidc_configs.app_id,` + ` projections.apps7_oidc_configs.app_id,` +
` projections.apps6_oidc_configs.version,` + ` projections.apps7_oidc_configs.version,` +
` projections.apps6_oidc_configs.client_id,` + ` projections.apps7_oidc_configs.client_id,` +
` projections.apps6_oidc_configs.redirect_uris,` + ` projections.apps7_oidc_configs.redirect_uris,` +
` projections.apps6_oidc_configs.response_types,` + ` projections.apps7_oidc_configs.response_types,` +
` projections.apps6_oidc_configs.grant_types,` + ` projections.apps7_oidc_configs.grant_types,` +
` projections.apps6_oidc_configs.application_type,` + ` projections.apps7_oidc_configs.application_type,` +
` projections.apps6_oidc_configs.auth_method_type,` + ` projections.apps7_oidc_configs.auth_method_type,` +
` projections.apps6_oidc_configs.post_logout_redirect_uris,` + ` projections.apps7_oidc_configs.post_logout_redirect_uris,` +
` projections.apps6_oidc_configs.is_dev_mode,` + ` projections.apps7_oidc_configs.is_dev_mode,` +
` projections.apps6_oidc_configs.access_token_type,` + ` projections.apps7_oidc_configs.access_token_type,` +
` projections.apps6_oidc_configs.access_token_role_assertion,` + ` projections.apps7_oidc_configs.access_token_role_assertion,` +
` projections.apps6_oidc_configs.id_token_role_assertion,` + ` projections.apps7_oidc_configs.id_token_role_assertion,` +
` projections.apps6_oidc_configs.id_token_userinfo_assertion,` + ` projections.apps7_oidc_configs.id_token_userinfo_assertion,` +
` projections.apps6_oidc_configs.clock_skew,` + ` projections.apps7_oidc_configs.clock_skew,` +
` projections.apps6_oidc_configs.additional_origins,` + ` projections.apps7_oidc_configs.additional_origins,` +
` projections.apps6_oidc_configs.skip_native_app_success_page,` + ` projections.apps7_oidc_configs.skip_native_app_success_page,` +
//saml config //saml config
` projections.apps6_saml_configs.app_id,` + ` projections.apps7_saml_configs.app_id,` +
` projections.apps6_saml_configs.entity_id,` + ` projections.apps7_saml_configs.entity_id,` +
` projections.apps6_saml_configs.metadata,` + ` projections.apps7_saml_configs.metadata,` +
` projections.apps6_saml_configs.metadata_url` + ` projections.apps7_saml_configs.metadata_url` +
` FROM projections.apps6` + ` FROM projections.apps7` +
` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` +
` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` +
` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + ` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` +
` AS OF SYSTEM TIME '-1 ms'`) ` AS OF SYSTEM TIME '-1 ms'`)
expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps6.id,` + expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps7.id,` +
` projections.apps6.name,` + ` projections.apps7.name,` +
` projections.apps6.project_id,` + ` projections.apps7.project_id,` +
` projections.apps6.creation_date,` + ` projections.apps7.creation_date,` +
` projections.apps6.change_date,` + ` projections.apps7.change_date,` +
` projections.apps6.resource_owner,` + ` projections.apps7.resource_owner,` +
` projections.apps6.state,` + ` projections.apps7.state,` +
` projections.apps6.sequence,` + ` projections.apps7.sequence,` +
// api config // api config
` projections.apps6_api_configs.app_id,` + ` projections.apps7_api_configs.app_id,` +
` projections.apps6_api_configs.client_id,` + ` projections.apps7_api_configs.client_id,` +
` projections.apps6_api_configs.auth_method,` + ` projections.apps7_api_configs.auth_method,` +
// oidc config // oidc config
` projections.apps6_oidc_configs.app_id,` + ` projections.apps7_oidc_configs.app_id,` +
` projections.apps6_oidc_configs.version,` + ` projections.apps7_oidc_configs.version,` +
` projections.apps6_oidc_configs.client_id,` + ` projections.apps7_oidc_configs.client_id,` +
` projections.apps6_oidc_configs.redirect_uris,` + ` projections.apps7_oidc_configs.redirect_uris,` +
` projections.apps6_oidc_configs.response_types,` + ` projections.apps7_oidc_configs.response_types,` +
` projections.apps6_oidc_configs.grant_types,` + ` projections.apps7_oidc_configs.grant_types,` +
` projections.apps6_oidc_configs.application_type,` + ` projections.apps7_oidc_configs.application_type,` +
` projections.apps6_oidc_configs.auth_method_type,` + ` projections.apps7_oidc_configs.auth_method_type,` +
` projections.apps6_oidc_configs.post_logout_redirect_uris,` + ` projections.apps7_oidc_configs.post_logout_redirect_uris,` +
` projections.apps6_oidc_configs.is_dev_mode,` + ` projections.apps7_oidc_configs.is_dev_mode,` +
` projections.apps6_oidc_configs.access_token_type,` + ` projections.apps7_oidc_configs.access_token_type,` +
` projections.apps6_oidc_configs.access_token_role_assertion,` + ` projections.apps7_oidc_configs.access_token_role_assertion,` +
` projections.apps6_oidc_configs.id_token_role_assertion,` + ` projections.apps7_oidc_configs.id_token_role_assertion,` +
` projections.apps6_oidc_configs.id_token_userinfo_assertion,` + ` projections.apps7_oidc_configs.id_token_userinfo_assertion,` +
` projections.apps6_oidc_configs.clock_skew,` + ` projections.apps7_oidc_configs.clock_skew,` +
` projections.apps6_oidc_configs.additional_origins,` + ` projections.apps7_oidc_configs.additional_origins,` +
` projections.apps6_oidc_configs.skip_native_app_success_page,` + ` projections.apps7_oidc_configs.skip_native_app_success_page,` +
//saml config //saml config
` projections.apps6_saml_configs.app_id,` + ` projections.apps7_saml_configs.app_id,` +
` projections.apps6_saml_configs.entity_id,` + ` projections.apps7_saml_configs.entity_id,` +
` projections.apps6_saml_configs.metadata,` + ` projections.apps7_saml_configs.metadata,` +
` projections.apps6_saml_configs.metadata_url,` + ` projections.apps7_saml_configs.metadata_url,` +
` COUNT(*) OVER ()` + ` COUNT(*) OVER ()` +
` FROM projections.apps6` + ` FROM projections.apps7` +
` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` +
` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` +
` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + ` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` +
` AS OF SYSTEM TIME '-1 ms'`) ` AS OF SYSTEM TIME '-1 ms'`)
expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps6_api_configs.client_id,` + expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps7_api_configs.client_id,` +
` projections.apps6_oidc_configs.client_id` + ` projections.apps7_oidc_configs.client_id` +
` FROM projections.apps6` + ` FROM projections.apps7` +
` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` +
` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` +
` AS OF SYSTEM TIME '-1 ms'`) ` AS OF SYSTEM TIME '-1 ms'`)
expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps6.project_id` + expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps7.project_id` +
` FROM projections.apps6` + ` FROM projections.apps7` +
` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` +
` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` +
` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + ` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` +
` AS OF SYSTEM TIME '-1 ms'`) ` AS OF SYSTEM TIME '-1 ms'`)
expectedProjectByAppQuery = regexp.QuoteMeta(`SELECT projections.projects4.id,` + expectedProjectByAppQuery = regexp.QuoteMeta(`SELECT projections.projects4.id,` +
` projections.projects4.creation_date,` + ` projections.projects4.creation_date,` +
@ -120,10 +120,10 @@ var (
` projections.projects4.has_project_check,` + ` projections.projects4.has_project_check,` +
` projections.projects4.private_labeling_setting` + ` projections.projects4.private_labeling_setting` +
` FROM projections.projects4` + ` FROM projections.projects4` +
` JOIN projections.apps6 ON projections.projects4.id = projections.apps6.project_id AND projections.projects4.instance_id = projections.apps6.instance_id` + ` JOIN projections.apps7 ON projections.projects4.id = projections.apps7.project_id AND projections.projects4.instance_id = projections.apps7.instance_id` +
` LEFT JOIN projections.apps6_api_configs ON projections.apps6.id = projections.apps6_api_configs.app_id AND projections.apps6.instance_id = projections.apps6_api_configs.instance_id` + ` LEFT JOIN projections.apps7_api_configs ON projections.apps7.id = projections.apps7_api_configs.app_id AND projections.apps7.instance_id = projections.apps7_api_configs.instance_id` +
` LEFT JOIN projections.apps6_oidc_configs ON projections.apps6.id = projections.apps6_oidc_configs.app_id AND projections.apps6.instance_id = projections.apps6_oidc_configs.instance_id` + ` LEFT JOIN projections.apps7_oidc_configs ON projections.apps7.id = projections.apps7_oidc_configs.app_id AND projections.apps7.instance_id = projections.apps7_oidc_configs.instance_id` +
` LEFT JOIN projections.apps6_saml_configs ON projections.apps6.id = projections.apps6_saml_configs.app_id AND projections.apps6.instance_id = projections.apps6_saml_configs.instance_id` + ` LEFT JOIN projections.apps7_saml_configs ON projections.apps7.id = projections.apps7_saml_configs.app_id AND projections.apps7.instance_id = projections.apps7_saml_configs.instance_id` +
` AS OF SYSTEM TIME '-1 ms'`) ` AS OF SYSTEM TIME '-1 ms'`)
appCols = database.TextArray[string]{ appCols = database.TextArray[string]{

View File

@ -1,11 +1,11 @@
with config as ( with config as (
select app_id, client_id, client_secret select app_id, client_id, client_secret, 'api' as app_type
from projections.apps6_api_configs from projections.apps7_api_configs
where instance_id = $1 where instance_id = $1
and client_id = $2 and client_id = $2
union union
select app_id, client_id, client_secret select app_id, client_id, client_secret, 'oidc' as app_type
from projections.apps6_oidc_configs from projections.apps7_oidc_configs
where instance_id = $1 where instance_id = $1
and client_id = $2 and client_id = $2
), ),
@ -18,6 +18,7 @@ keys as (
and expiration > current_timestamp and expiration > current_timestamp
group by identifier group by identifier
) )
select config.client_id, config.client_secret, apps.project_id, keys.public_keys from config select config.app_id, config.client_id, config.client_secret, config.app_type, apps.project_id, apps.resource_owner, keys.public_keys
join projections.apps6 apps on apps.id = config.app_id from config
join projections.apps7 apps on apps.id = config.app_id
left join keys on keys.client_id = config.client_id; left join keys on keys.client_id = config.client_id;

View File

@ -7,8 +7,8 @@ with client as (
c.application_type, c.auth_method_type, c.post_logout_redirect_uris, c.is_dev_mode, 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.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 c.id_token_userinfo_assertion, c.clock_skew, c.additional_origins, a.project_id, a.state
from projections.apps6_oidc_configs c from projections.apps7_oidc_configs c
join projections.apps6 a on a.id = c.app_id and a.instance_id = c.instance_id join projections.apps7 a on a.id = c.app_id and a.instance_id = c.instance_id
where c.instance_id = $1 where c.instance_id = $1
and c.client_id = $2 and c.client_id = $2
), ),

View File

@ -1,6 +1,6 @@
with usr as ( with usr as (
select u.id, u.creation_date, u.change_date, u.sequence, u.state, u.resource_owner, u.username, n.login_name as preferred_login_name 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.users11 u from projections.users12 u
left join projections.login_names3 n on u.id = n.user_id and u.instance_id = n.instance_id left join projections.login_names3 n on u.id = n.user_id and u.instance_id = n.instance_id
where u.id = $1 where u.id = $1
and u.instance_id = $2 and u.instance_id = $2
@ -9,7 +9,7 @@ with usr as (
human as ( human as (
select $1 as user_id, row_to_json(r) as human from ( 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 select first_name, last_name, nick_name, display_name, avatar_key, preferred_language, gender, email, is_email_verified, phone, is_phone_verified
from projections.users11_humans from projections.users12_humans
where user_id = $1 where user_id = $1
and instance_id = $2 and instance_id = $2
) r ) r
@ -17,7 +17,7 @@ human as (
machine as ( machine as (
select $1 as user_id, row_to_json(r) as machine from ( select $1 as user_id, row_to_json(r) as machine from (
select name, description select name, description
from projections.users11_machines from projections.users12_machines
where user_id = $1 where user_id = $1
and instance_id = $2 and instance_id = $2
) r ) r

View File

@ -21,21 +21,21 @@ var (
", members.user_id" + ", members.user_id" +
", members.roles" + ", members.roles" +
", projections.login_names3.login_name" + ", projections.login_names3.login_name" +
", projections.users11_humans.email" + ", projections.users12_humans.email" +
", projections.users11_humans.first_name" + ", projections.users12_humans.first_name" +
", projections.users11_humans.last_name" + ", projections.users12_humans.last_name" +
", projections.users11_humans.display_name" + ", projections.users12_humans.display_name" +
", projections.users11_machines.name" + ", projections.users12_machines.name" +
", projections.users11_humans.avatar_key" + ", projections.users12_humans.avatar_key" +
", projections.users11.type" + ", projections.users12.type" +
", COUNT(*) OVER () " + ", COUNT(*) OVER () " +
"FROM projections.instance_members4 AS members " + "FROM projections.instance_members4 AS members " +
"LEFT JOIN projections.users11_humans " + "LEFT JOIN projections.users12_humans " +
"ON members.user_id = projections.users11_humans.user_id AND members.instance_id = projections.users11_humans.instance_id " + "ON members.user_id = projections.users12_humans.user_id AND members.instance_id = projections.users12_humans.instance_id " +
"LEFT JOIN projections.users11_machines " + "LEFT JOIN projections.users12_machines " +
"ON members.user_id = projections.users11_machines.user_id AND members.instance_id = projections.users11_machines.instance_id " + "ON members.user_id = projections.users12_machines.user_id AND members.instance_id = projections.users12_machines.instance_id " +
"LEFT JOIN projections.users11 " + "LEFT JOIN projections.users12 " +
"ON members.user_id = projections.users11.id AND members.instance_id = projections.users11.instance_id " + "ON members.user_id = projections.users12.id AND members.instance_id = projections.users12.instance_id " +
"LEFT JOIN projections.login_names3 " + "LEFT JOIN projections.login_names3 " +
"ON members.user_id = projections.login_names3.user_id AND members.instance_id = projections.login_names3.instance_id " + "ON members.user_id = projections.login_names3.user_id AND members.instance_id = projections.login_names3.instance_id " +
"AS OF SYSTEM TIME '-1 ms' " + "AS OF SYSTEM TIME '-1 ms' " +

View File

@ -7,7 +7,6 @@ import (
"sync" "sync"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/eventstore/handler/v2"
"github.com/zitadel/zitadel/internal/query/projection" "github.com/zitadel/zitadel/internal/query/projection"
@ -30,10 +29,20 @@ func TriggerIntrospectionProjections(ctx context.Context) {
triggerBatch(ctx, introspectionTriggerHandlers()...) triggerBatch(ctx, introspectionTriggerHandlers()...)
} }
type AppType string
const (
AppTypeAPI = "api"
AppTypeOIDC = "oidc"
)
type IntrospectionClient struct { type IntrospectionClient struct {
AppID string
ClientID string ClientID string
ClientSecret *crypto.CryptoValue HashedSecret string
AppType AppType
ProjectID string ProjectID string
ResourceOwner string
PublicKeys database.Map[[]byte] PublicKeys database.Map[[]byte]
} }
@ -50,7 +59,15 @@ func (q *Queries) GetIntrospectionClientByID(ctx context.Context, clientID strin
) )
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
return row.Scan(&client.ClientID, &client.ClientSecret, &client.ProjectID, &client.PublicKeys) return row.Scan(
&client.AppID,
&client.ClientID,
&client.HashedSecret,
&client.AppType,
&client.ProjectID,
&client.ResourceOwner,
&client.PublicKeys,
)
}, },
introspectionClientByIDQuery, introspectionClientByIDQuery,
instanceID, clientID, getKeys, instanceID, clientID, getKeys,

View File

@ -4,7 +4,6 @@ import (
"database/sql" "database/sql"
"database/sql/driver" "database/sql/driver"
_ "embed" _ "embed"
"encoding/json"
"regexp" "regexp"
"testing" "testing"
@ -12,20 +11,10 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database"
) )
func TestQueries_GetIntrospectionClientByID(t *testing.T) { func TestQueries_GetIntrospectionClientByID(t *testing.T) {
secret := &crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "alg",
KeyID: "keyID",
Crypted: []byte("secret"),
}
encSecret, err := json.Marshal(secret)
require.NoError(t, err)
pubkeys := database.Map[[]byte]{ pubkeys := database.Map[[]byte]{
"key1": {1, 2, 3}, "key1": {1, 2, 3},
"key2": {4, 5, 6}, "key2": {4, 5, 6},
@ -61,13 +50,16 @@ func TestQueries_GetIntrospectionClientByID(t *testing.T) {
getKeys: false, getKeys: false,
}, },
mock: mockQuery(expQuery, mock: mockQuery(expQuery,
[]string{"client_id", "client_secret", "project_id", "public_keys"}, []string{"app_id", "client_id", "client_secret", "app_type", "project_id", "resource_owner", "public_keys"},
[]driver.Value{"clientID", encSecret, "projectID", nil}, []driver.Value{"appID", "clientID", "secret", "oidc", "projectID", "orgID", nil},
"instanceID", "clientID", false), "instanceID", "clientID", false),
want: &IntrospectionClient{ want: &IntrospectionClient{
AppID: "appID",
ClientID: "clientID", ClientID: "clientID",
ClientSecret: secret, HashedSecret: "secret",
AppType: AppTypeOIDC,
ProjectID: "projectID", ProjectID: "projectID",
ResourceOwner: "orgID",
PublicKeys: nil, PublicKeys: nil,
}, },
}, },
@ -78,13 +70,16 @@ func TestQueries_GetIntrospectionClientByID(t *testing.T) {
getKeys: true, getKeys: true,
}, },
mock: mockQuery(expQuery, mock: mockQuery(expQuery,
[]string{"client_id", "client_secret", "project_id", "public_keys"}, []string{"app_id", "client_id", "client_secret", "app_type", "project_id", "resource_owner", "public_keys"},
[]driver.Value{"clientID", nil, "projectID", encPubkeys}, []driver.Value{"appID", "clientID", "", "oidc", "projectID", "orgID", encPubkeys},
"instanceID", "clientID", true), "instanceID", "clientID", true),
want: &IntrospectionClient{ want: &IntrospectionClient{
AppID: "appID",
ClientID: "clientID", ClientID: "clientID",
ClientSecret: nil, HashedSecret: "",
AppType: AppTypeOIDC,
ProjectID: "projectID", ProjectID: "projectID",
ResourceOwner: "orgID",
PublicKeys: pubkeys, PublicKeys: pubkeys,
}, },
}, },

View File

@ -8,7 +8,6 @@ import (
"time" "time"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/telemetry/tracing"
@ -20,7 +19,7 @@ type OIDCClient struct {
AppID string `json:"app_id,omitempty"` AppID string `json:"app_id,omitempty"`
State domain.AppState `json:"state,omitempty"` State domain.AppState `json:"state,omitempty"`
ClientID string `json:"client_id,omitempty"` ClientID string `json:"client_id,omitempty"`
ClientSecret *crypto.CryptoValue `json:"client_secret,omitempty"` HashedSecret string `json:"client_secret,omitempty"`
RedirectURIs []string `json:"redirect_uris,omitempty"` RedirectURIs []string `json:"redirect_uris,omitempty"`
ResponseTypes []domain.OIDCResponseType `json:"response_types,omitempty"` ResponseTypes []domain.OIDCResponseType `json:"response_types,omitempty"`
GrantTypes []domain.OIDCGrantType `json:"grant_types,omitempty"` GrantTypes []domain.OIDCGrantType `json:"grant_types,omitempty"`

View File

@ -11,7 +11,6 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
@ -66,7 +65,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx
AppID: "236647088211886082", AppID: "236647088211886082",
State: domain.AppStateActive, State: domain.AppStateActive,
ClientID: "236647088211951618@tests", ClientID: "236647088211951618@tests",
ClientSecret: nil, HashedSecret: "",
RedirectURIs: []string{"http://localhost:9999/auth/callback"}, RedirectURIs: []string{"http://localhost:9999/auth/callback"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode, domain.OIDCGrantTypeRefreshToken}, GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode, domain.OIDCGrantTypeRefreshToken},
@ -97,7 +96,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx
AppID: "236646457053020162", AppID: "236646457053020162",
State: domain.AppStateActive, State: domain.AppStateActive,
ClientID: "236646457053085698@tests", ClientID: "236646457053085698@tests",
ClientSecret: nil, HashedSecret: "",
RedirectURIs: []string{"http://localhost:9999/auth/callback"}, RedirectURIs: []string{"http://localhost:9999/auth/callback"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode}, ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}, GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
@ -128,11 +127,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx
AppID: "236646858984783874", AppID: "236646858984783874",
State: domain.AppStateActive, State: domain.AppStateActive,
ClientID: "236646858984849410@tests", ClientID: "236646858984849410@tests",
ClientSecret: &crypto.CryptoValue{ HashedSecret: "$2a$14$OzZ0XEZZEtD13py/EPba2evsS6WcKZ5orVMj9pWHEGEHmLu2h3PFq",
CryptoType: crypto.TypeHash,
Algorithm: "bcrypt",
Crypted: []byte(`$2a$14$OzZ0XEZZEtD13py/EPba2evsS6WcKZ5orVMj9pWHEGEHmLu2h3PFq`),
},
RedirectURIs: []string{"http://localhost:9999/auth/callback"}, RedirectURIs: []string{"http://localhost:9999/auth/callback"},
ResponseTypes: []domain.OIDCResponseType{0}, ResponseTypes: []domain.OIDCResponseType{0},
GrantTypes: []domain.OIDCGrantType{0}, GrantTypes: []domain.OIDCGrantType{0},
@ -163,7 +158,7 @@ low2kyJov38V4Uk2I8kuXpLcnrpw5Tio2ooiUE27b0vHZqBKOei9Uo88qCrn3EKx
AppID: "239520764276441090", AppID: "239520764276441090",
State: domain.AppStateActive, State: domain.AppStateActive,
ClientID: "239520764779364354@zitadel", ClientID: "239520764779364354@zitadel",
ClientSecret: nil, HashedSecret: "",
RedirectURIs: []string{ RedirectURIs: []string{
"http://test2-qucuh5.localhost:9000/ui/console/auth/callback", "http://test2-qucuh5.localhost:9000/ui/console/auth/callback",
"http://test.localhost.com:9000/ui/console/auth/callback"}, "http://test.localhost.com:9000/ui/console/auth/callback"},

View File

@ -21,24 +21,24 @@ var (
", members.user_id" + ", members.user_id" +
", members.roles" + ", members.roles" +
", projections.login_names3.login_name" + ", projections.login_names3.login_name" +
", projections.users11_humans.email" + ", projections.users12_humans.email" +
", projections.users11_humans.first_name" + ", projections.users12_humans.first_name" +
", projections.users11_humans.last_name" + ", projections.users12_humans.last_name" +
", projections.users11_humans.display_name" + ", projections.users12_humans.display_name" +
", projections.users11_machines.name" + ", projections.users12_machines.name" +
", projections.users11_humans.avatar_key" + ", projections.users12_humans.avatar_key" +
", projections.users11.type" + ", projections.users12.type" +
", COUNT(*) OVER () " + ", COUNT(*) OVER () " +
"FROM projections.org_members4 AS members " + "FROM projections.org_members4 AS members " +
"LEFT JOIN projections.users11_humans " + "LEFT JOIN projections.users12_humans " +
"ON members.user_id = projections.users11_humans.user_id " + "ON members.user_id = projections.users12_humans.user_id " +
"AND members.instance_id = projections.users11_humans.instance_id " + "AND members.instance_id = projections.users12_humans.instance_id " +
"LEFT JOIN projections.users11_machines " + "LEFT JOIN projections.users12_machines " +
"ON members.user_id = projections.users11_machines.user_id " + "ON members.user_id = projections.users12_machines.user_id " +
"AND members.instance_id = projections.users11_machines.instance_id " + "AND members.instance_id = projections.users12_machines.instance_id " +
"LEFT JOIN projections.users11 " + "LEFT JOIN projections.users12 " +
"ON members.user_id = projections.users11.id " + "ON members.user_id = projections.users12.id " +
"AND members.instance_id = projections.users11.instance_id " + "AND members.instance_id = projections.users12.instance_id " +
"LEFT JOIN projections.login_names3 " + "LEFT JOIN projections.login_names3 " +
"ON members.user_id = projections.login_names3.user_id " + "ON members.user_id = projections.login_names3.user_id " +
"AND members.instance_id = projections.login_names3.instance_id " + "AND members.instance_id = projections.login_names3.instance_id " +

View File

@ -21,24 +21,24 @@ var (
", members.user_id" + ", members.user_id" +
", members.roles" + ", members.roles" +
", projections.login_names3.login_name" + ", projections.login_names3.login_name" +
", projections.users11_humans.email" + ", projections.users12_humans.email" +
", projections.users11_humans.first_name" + ", projections.users12_humans.first_name" +
", projections.users11_humans.last_name" + ", projections.users12_humans.last_name" +
", projections.users11_humans.display_name" + ", projections.users12_humans.display_name" +
", projections.users11_machines.name" + ", projections.users12_machines.name" +
", projections.users11_humans.avatar_key" + ", projections.users12_humans.avatar_key" +
", projections.users11.type" + ", projections.users12.type" +
", COUNT(*) OVER () " + ", COUNT(*) OVER () " +
"FROM projections.project_grant_members4 AS members " + "FROM projections.project_grant_members4 AS members " +
"LEFT JOIN projections.users11_humans " + "LEFT JOIN projections.users12_humans " +
"ON members.user_id = projections.users11_humans.user_id " + "ON members.user_id = projections.users12_humans.user_id " +
"AND members.instance_id = projections.users11_humans.instance_id " + "AND members.instance_id = projections.users12_humans.instance_id " +
"LEFT JOIN projections.users11_machines " + "LEFT JOIN projections.users12_machines " +
"ON members.user_id = projections.users11_machines.user_id " + "ON members.user_id = projections.users12_machines.user_id " +
"AND members.instance_id = projections.users11_machines.instance_id " + "AND members.instance_id = projections.users12_machines.instance_id " +
"LEFT JOIN projections.users11 " + "LEFT JOIN projections.users12 " +
"ON members.user_id = projections.users11.id " + "ON members.user_id = projections.users12.id " +
"AND members.instance_id = projections.users11.instance_id " + "AND members.instance_id = projections.users12.instance_id " +
"LEFT JOIN projections.login_names3 " + "LEFT JOIN projections.login_names3 " +
"ON members.user_id = projections.login_names3.user_id " + "ON members.user_id = projections.login_names3.user_id " +
"AND members.instance_id = projections.login_names3.instance_id " + "AND members.instance_id = projections.login_names3.instance_id " +

View File

@ -21,24 +21,24 @@ var (
", members.user_id" + ", members.user_id" +
", members.roles" + ", members.roles" +
", projections.login_names3.login_name" + ", projections.login_names3.login_name" +
", projections.users11_humans.email" + ", projections.users12_humans.email" +
", projections.users11_humans.first_name" + ", projections.users12_humans.first_name" +
", projections.users11_humans.last_name" + ", projections.users12_humans.last_name" +
", projections.users11_humans.display_name" + ", projections.users12_humans.display_name" +
", projections.users11_machines.name" + ", projections.users12_machines.name" +
", projections.users11_humans.avatar_key" + ", projections.users12_humans.avatar_key" +
", projections.users11.type" + ", projections.users12.type" +
", COUNT(*) OVER () " + ", COUNT(*) OVER () " +
"FROM projections.project_members4 AS members " + "FROM projections.project_members4 AS members " +
"LEFT JOIN projections.users11_humans " + "LEFT JOIN projections.users12_humans " +
"ON members.user_id = projections.users11_humans.user_id " + "ON members.user_id = projections.users12_humans.user_id " +
"AND members.instance_id = projections.users11_humans.instance_id " + "AND members.instance_id = projections.users12_humans.instance_id " +
"LEFT JOIN projections.users11_machines " + "LEFT JOIN projections.users12_machines " +
"ON members.user_id = projections.users11_machines.user_id " + "ON members.user_id = projections.users12_machines.user_id " +
"AND members.instance_id = projections.users11_machines.instance_id " + "AND members.instance_id = projections.users12_machines.instance_id " +
"LEFT JOIN projections.users11 " + "LEFT JOIN projections.users12 " +
"ON members.user_id = projections.users11.id " + "ON members.user_id = projections.users12.id " +
"AND members.instance_id = projections.users11.instance_id " + "AND members.instance_id = projections.users12.instance_id " +
"LEFT JOIN projections.login_names3 " + "LEFT JOIN projections.login_names3 " +
"ON members.user_id = projections.login_names3.user_id " + "ON members.user_id = projections.login_names3.user_id " +
"AND members.instance_id = projections.login_names3.instance_id " + "AND members.instance_id = projections.login_names3.instance_id " +

View File

@ -3,6 +3,7 @@ package projection
import ( import (
"context" "context"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
@ -15,7 +16,7 @@ import (
) )
const ( const (
AppProjectionTable = "projections.apps6" AppProjectionTable = "projections.apps7"
AppAPITable = AppProjectionTable + "_" + appAPITableSuffix AppAPITable = AppProjectionTable + "_" + appAPITableSuffix
AppOIDCTable = AppProjectionTable + "_" + appOIDCTableSuffix AppOIDCTable = AppProjectionTable + "_" + appOIDCTableSuffix
AppSAMLTable = AppProjectionTable + "_" + appSAMLTableSuffix AppSAMLTable = AppProjectionTable + "_" + appSAMLTableSuffix
@ -96,7 +97,7 @@ func (*appProjection) Init() *old_handler.Check {
handler.NewColumn(AppAPIConfigColumnAppID, handler.ColumnTypeText), handler.NewColumn(AppAPIConfigColumnAppID, handler.ColumnTypeText),
handler.NewColumn(AppAPIConfigColumnInstanceID, handler.ColumnTypeText), handler.NewColumn(AppAPIConfigColumnInstanceID, handler.ColumnTypeText),
handler.NewColumn(AppAPIConfigColumnClientID, handler.ColumnTypeText), handler.NewColumn(AppAPIConfigColumnClientID, handler.ColumnTypeText),
handler.NewColumn(AppAPIConfigColumnClientSecret, handler.ColumnTypeJSONB, handler.Nullable()), handler.NewColumn(AppAPIConfigColumnClientSecret, handler.ColumnTypeText, handler.Nullable()),
handler.NewColumn(AppAPIConfigColumnAuthMethod, handler.ColumnTypeEnum), handler.NewColumn(AppAPIConfigColumnAuthMethod, handler.ColumnTypeEnum),
}, },
handler.NewPrimaryKey(AppAPIConfigColumnInstanceID, AppAPIConfigColumnAppID), handler.NewPrimaryKey(AppAPIConfigColumnInstanceID, AppAPIConfigColumnAppID),
@ -109,7 +110,7 @@ func (*appProjection) Init() *old_handler.Check {
handler.NewColumn(AppOIDCConfigColumnInstanceID, handler.ColumnTypeText), handler.NewColumn(AppOIDCConfigColumnInstanceID, handler.ColumnTypeText),
handler.NewColumn(AppOIDCConfigColumnVersion, handler.ColumnTypeEnum), handler.NewColumn(AppOIDCConfigColumnVersion, handler.ColumnTypeEnum),
handler.NewColumn(AppOIDCConfigColumnClientID, handler.ColumnTypeText), handler.NewColumn(AppOIDCConfigColumnClientID, handler.ColumnTypeText),
handler.NewColumn(AppOIDCConfigColumnClientSecret, handler.ColumnTypeJSONB, handler.Nullable()), handler.NewColumn(AppOIDCConfigColumnClientSecret, handler.ColumnTypeText, handler.Nullable()),
handler.NewColumn(AppOIDCConfigColumnRedirectUris, handler.ColumnTypeTextArray, handler.Nullable()), handler.NewColumn(AppOIDCConfigColumnRedirectUris, handler.ColumnTypeTextArray, handler.Nullable()),
handler.NewColumn(AppOIDCConfigColumnResponseTypes, handler.ColumnTypeEnumArray, handler.Nullable()), handler.NewColumn(AppOIDCConfigColumnResponseTypes, handler.ColumnTypeEnumArray, handler.Nullable()),
handler.NewColumn(AppOIDCConfigColumnGrantTypes, handler.ColumnTypeEnumArray, handler.Nullable()), handler.NewColumn(AppOIDCConfigColumnGrantTypes, handler.ColumnTypeEnumArray, handler.Nullable()),
@ -186,6 +187,10 @@ func (p *appProjection) Reducers() []handler.AggregateReducer {
Event: project.APIConfigSecretChangedType, Event: project.APIConfigSecretChangedType,
Reduce: p.reduceAPIConfigSecretChanged, Reduce: p.reduceAPIConfigSecretChanged,
}, },
{
Event: project.APIConfigSecretHashUpdatedType,
Reduce: p.reduceAPIConfigSecretHashUpdated,
},
{ {
Event: project.OIDCConfigAddedType, Event: project.OIDCConfigAddedType,
Reduce: p.reduceOIDCConfigAdded, Reduce: p.reduceOIDCConfigAdded,
@ -198,6 +203,10 @@ func (p *appProjection) Reducers() []handler.AggregateReducer {
Event: project.OIDCConfigSecretChangedType, Event: project.OIDCConfigSecretChangedType,
Reduce: p.reduceOIDCConfigSecretChanged, Reduce: p.reduceOIDCConfigSecretChanged,
}, },
{
Event: project.OIDCConfigSecretHashUpdatedType,
Reduce: p.reduceOIDCConfigSecretHashUpdated,
},
{ {
Event: project.SAMLConfigAddedType, Event: project.SAMLConfigAddedType,
Reduce: p.reduceSAMLConfigAdded, Reduce: p.reduceSAMLConfigAdded,
@ -350,7 +359,7 @@ func (p *appProjection) reduceAPIConfigAdded(event eventstore.Event) (*handler.S
handler.NewCol(AppAPIConfigColumnAppID, e.AppID), handler.NewCol(AppAPIConfigColumnAppID, e.AppID),
handler.NewCol(AppAPIConfigColumnInstanceID, e.Aggregate().InstanceID), handler.NewCol(AppAPIConfigColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(AppAPIConfigColumnClientID, e.ClientID), handler.NewCol(AppAPIConfigColumnClientID, e.ClientID),
handler.NewCol(AppAPIConfigColumnClientSecret, e.ClientSecret), handler.NewCol(AppAPIConfigColumnClientSecret, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)),
handler.NewCol(AppAPIConfigColumnAuthMethod, e.AuthMethodType), handler.NewCol(AppAPIConfigColumnAuthMethod, e.AuthMethodType),
}, },
handler.WithTableSuffix(appAPITableSuffix), handler.WithTableSuffix(appAPITableSuffix),
@ -374,9 +383,6 @@ func (p *appProjection) reduceAPIConfigChanged(event eventstore.Event) (*handler
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-vnZKi", "reduce.wrong.event.type %s", project.APIConfigChangedType) return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-vnZKi", "reduce.wrong.event.type %s", project.APIConfigChangedType)
} }
cols := make([]handler.Column, 0, 2) cols := make([]handler.Column, 0, 2)
if e.ClientSecret != nil {
cols = append(cols, handler.NewCol(AppAPIConfigColumnClientSecret, e.ClientSecret))
}
if e.AuthMethodType != nil { if e.AuthMethodType != nil {
cols = append(cols, handler.NewCol(AppAPIConfigColumnAuthMethod, *e.AuthMethodType)) cols = append(cols, handler.NewCol(AppAPIConfigColumnAuthMethod, *e.AuthMethodType))
} }
@ -415,7 +421,37 @@ func (p *appProjection) reduceAPIConfigSecretChanged(event eventstore.Event) (*h
e, e,
handler.AddUpdateStatement( handler.AddUpdateStatement(
[]handler.Column{ []handler.Column{
handler.NewCol(AppAPIConfigColumnClientSecret, e.ClientSecret), handler.NewCol(AppAPIConfigColumnClientSecret, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)),
},
[]handler.Condition{
handler.NewCond(AppAPIConfigColumnAppID, e.AppID),
handler.NewCond(AppAPIConfigColumnInstanceID, e.Aggregate().InstanceID),
},
handler.WithTableSuffix(appAPITableSuffix),
),
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(AppColumnChangeDate, e.CreationDate()),
handler.NewCol(AppColumnSequence, e.Sequence()),
},
[]handler.Condition{
handler.NewCond(AppColumnID, e.AppID),
handler.NewCond(AppColumnInstanceID, e.Aggregate().InstanceID),
},
),
), nil
}
func (p *appProjection) reduceAPIConfigSecretHashUpdated(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*project.APIConfigSecretHashUpdatedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-ttb0I", "reduce.wrong.event.type %s", project.APIConfigSecretHashUpdatedType)
}
return handler.NewMultiStatement(
e,
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(AppAPIConfigColumnClientSecret, e.HashedSecret),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond(AppAPIConfigColumnAppID, e.AppID), handler.NewCond(AppAPIConfigColumnAppID, e.AppID),
@ -449,7 +485,7 @@ func (p *appProjection) reduceOIDCConfigAdded(event eventstore.Event) (*handler.
handler.NewCol(AppOIDCConfigColumnInstanceID, e.Aggregate().InstanceID), handler.NewCol(AppOIDCConfigColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(AppOIDCConfigColumnVersion, e.Version), handler.NewCol(AppOIDCConfigColumnVersion, e.Version),
handler.NewCol(AppOIDCConfigColumnClientID, e.ClientID), handler.NewCol(AppOIDCConfigColumnClientID, e.ClientID),
handler.NewCol(AppOIDCConfigColumnClientSecret, e.ClientSecret), handler.NewCol(AppOIDCConfigColumnClientSecret, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)),
handler.NewCol(AppOIDCConfigColumnRedirectUris, database.TextArray[string](e.RedirectUris)), handler.NewCol(AppOIDCConfigColumnRedirectUris, database.TextArray[string](e.RedirectUris)),
handler.NewCol(AppOIDCConfigColumnResponseTypes, database.NumberArray[domain.OIDCResponseType](e.ResponseTypes)), handler.NewCol(AppOIDCConfigColumnResponseTypes, database.NumberArray[domain.OIDCResponseType](e.ResponseTypes)),
handler.NewCol(AppOIDCConfigColumnGrantTypes, database.NumberArray[domain.OIDCGrantType](e.GrantTypes)), handler.NewCol(AppOIDCConfigColumnGrantTypes, database.NumberArray[domain.OIDCGrantType](e.GrantTypes)),
@ -569,7 +605,37 @@ func (p *appProjection) reduceOIDCConfigSecretChanged(event eventstore.Event) (*
e, e,
handler.AddUpdateStatement( handler.AddUpdateStatement(
[]handler.Column{ []handler.Column{
handler.NewCol(AppOIDCConfigColumnClientSecret, e.ClientSecret), handler.NewCol(AppOIDCConfigColumnClientSecret, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)),
},
[]handler.Condition{
handler.NewCond(AppOIDCConfigColumnAppID, e.AppID),
handler.NewCond(AppOIDCConfigColumnInstanceID, e.Aggregate().InstanceID),
},
handler.WithTableSuffix(appOIDCTableSuffix),
),
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(AppColumnChangeDate, e.CreationDate()),
handler.NewCol(AppColumnSequence, e.Sequence()),
},
[]handler.Condition{
handler.NewCond(AppColumnID, e.AppID),
handler.NewCond(AppColumnInstanceID, e.Aggregate().InstanceID),
},
),
), nil
}
func (p *appProjection) reduceOIDCConfigSecretHashUpdated(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*project.OIDCConfigSecretHashUpdatedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-toSh1", "reduce.wrong.event.type %s", project.OIDCConfigSecretHashUpdatedType)
}
return handler.NewMultiStatement(
e,
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(AppOIDCConfigColumnClientSecret, e.HashedSecret),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond(AppOIDCConfigColumnAppID, e.AppID), handler.NewCond(AppOIDCConfigColumnAppID, e.AppID),

View File

@ -46,7 +46,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.apps6 (id, name, project_id, creation_date, change_date, resource_owner, instance_id, state, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedStmt: "INSERT INTO projections.apps7 (id, name, project_id, creation_date, change_date, resource_owner, instance_id, state, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"app-id", "app-id",
"my-app", "my-app",
@ -83,7 +83,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.apps6 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.apps7 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"my-app", "my-app",
anyArg{}, anyArg{},
@ -136,7 +136,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.apps6 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.apps7 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
domain.AppStateInactive, domain.AppStateInactive,
anyArg{}, anyArg{},
@ -168,7 +168,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.apps6 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.apps7 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
domain.AppStateActive, domain.AppStateActive,
anyArg{}, anyArg{},
@ -200,7 +200,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.apps6 WHERE (id = $1) AND (instance_id = $2)", expectedStmt: "DELETE FROM projections.apps7 WHERE (id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"app-id", "app-id",
"instance-id", "instance-id",
@ -227,7 +227,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.apps6 WHERE (project_id = $1) AND (instance_id = $2)", expectedStmt: "DELETE FROM projections.apps7 WHERE (project_id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -254,7 +254,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.apps6 WHERE (instance_id = $1)", expectedStmt: "DELETE FROM projections.apps7 WHERE (instance_id = $1)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
}, },
@ -264,7 +264,7 @@ func TestAppProjection_reduces(t *testing.T) {
}, },
}, },
{ {
name: "project reduceAPIConfigAdded", name: "project reduceAPIConfigAdded, v1 secret",
args: args{ args: args{
event: getEvent( event: getEvent(
testEvent( testEvent(
@ -273,7 +273,7 @@ func TestAppProjection_reduces(t *testing.T) {
[]byte(`{ []byte(`{
"appId": "app-id", "appId": "app-id",
"clientId": "client-id", "clientId": "client-id",
"clientSecret": {}, "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"},
"authMethodType": 1 "authMethodType": 1
}`), }`),
), project.APIConfigAddedEventMapper), ), project.APIConfigAddedEventMapper),
@ -285,17 +285,61 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.apps6_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)", expectedStmt: "INSERT INTO projections.apps7_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"app-id", "app-id",
"instance-id", "instance-id",
"client-id", "client-id",
anyArg{}, "secret",
domain.APIAuthMethodTypePrivateKeyJWT, domain.APIAuthMethodTypePrivateKeyJWT,
}, },
}, },
{ {
expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
"app-id",
"instance-id",
},
},
},
},
},
},
{
name: "project reduceAPIConfigAdded, v2 secret",
args: args{
event: getEvent(
testEvent(
project.APIConfigAddedType,
project.AggregateType,
[]byte(`{
"appId": "app-id",
"clientId": "client-id",
"hashedSecret": "secret",
"authMethodType": 1
}`),
), project.APIConfigAddedEventMapper),
},
reduce: (&appProjection{}).reduceAPIConfigAdded,
want: wantReduce{
aggregateType: eventstore.AggregateType("project"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.apps7_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)",
expectedArgs: []interface{}{
"app-id",
"instance-id",
"client-id",
"secret",
domain.APIAuthMethodTypePrivateKeyJWT,
},
},
{
expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -317,7 +361,6 @@ func TestAppProjection_reduces(t *testing.T) {
[]byte(`{ []byte(`{
"appId": "app-id", "appId": "app-id",
"clientId": "client-id", "clientId": "client-id",
"clientSecret": {},
"authMethodType": 1 "authMethodType": 1
}`), }`),
), project.APIConfigChangedEventMapper), ), project.APIConfigChangedEventMapper),
@ -329,16 +372,15 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.apps6_api_configs SET (client_secret, auth_method) = ($1, $2) WHERE (app_id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.apps7_api_configs SET auth_method = $1 WHERE (app_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{},
domain.APIAuthMethodTypePrivateKeyJWT, domain.APIAuthMethodTypePrivateKeyJWT,
"app-id", "app-id",
"instance-id", "instance-id",
}, },
}, },
{ {
expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -372,7 +414,7 @@ func TestAppProjection_reduces(t *testing.T) {
}, },
}, },
{ {
name: "project reduceAPIConfigSecretChanged", name: "project reduceAPIConfigSecretChanged, v1 secret",
args: args{ args: args{
event: getEvent( event: getEvent(
testEvent( testEvent(
@ -380,7 +422,7 @@ func TestAppProjection_reduces(t *testing.T) {
project.AggregateType, project.AggregateType,
[]byte(`{ []byte(`{
"appId": "app-id", "appId": "app-id",
"client_secret": {} "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"}
}`), }`),
), project.APIConfigSecretChangedEventMapper), ), project.APIConfigSecretChangedEventMapper),
}, },
@ -391,15 +433,15 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.apps6_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.apps7_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, "secret",
"app-id", "app-id",
"instance-id", "instance-id",
}, },
}, },
{ {
expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -412,7 +454,87 @@ func TestAppProjection_reduces(t *testing.T) {
}, },
}, },
{ {
name: "project reduceOIDCConfigAdded", name: "project reduceAPIConfigSecretChanged, v2 secret",
args: args{
event: getEvent(
testEvent(
project.APIConfigSecretChangedType,
project.AggregateType,
[]byte(`{
"appId": "app-id",
"hashedSecret": "secret"
}`),
), project.APIConfigSecretChangedEventMapper),
},
reduce: (&appProjection{}).reduceAPIConfigSecretChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("project"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.apps7_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
"secret",
"app-id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
"app-id",
"instance-id",
},
},
},
},
},
},
{
name: "project reduceAPIConfigSecretHashUpdated",
args: args{
event: getEvent(
testEvent(
project.APIConfigSecretHashUpdatedType,
project.AggregateType,
[]byte(`{
"appId": "app-id",
"hashedSecret": "secret"
}`),
), eventstore.GenericEventMapper[project.APIConfigSecretHashUpdatedEvent]),
},
reduce: (&appProjection{}).reduceAPIConfigSecretHashUpdated,
want: wantReduce{
aggregateType: eventstore.AggregateType("project"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.apps7_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
"secret",
"app-id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
"app-id",
"instance-id",
},
},
},
},
},
},
{
name: "project reduceOIDCConfigAdded, v1 secret",
args: args{ args: args{
event: getEvent( event: getEvent(
testEvent( testEvent(
@ -422,7 +544,7 @@ func TestAppProjection_reduces(t *testing.T) {
"oidcVersion": 0, "oidcVersion": 0,
"appId": "app-id", "appId": "app-id",
"clientId": "client-id", "clientId": "client-id",
"clientSecret": {}, "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"},
"redirectUris": ["redirect.one.ch", "redirect.two.ch"], "redirectUris": ["redirect.one.ch", "redirect.two.ch"],
"responseTypes": [1,2], "responseTypes": [1,2],
"grantTypes": [1,2], "grantTypes": [1,2],
@ -447,13 +569,13 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.apps6_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)", expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"app-id", "app-id",
"instance-id", "instance-id",
domain.OIDCVersionV1, domain.OIDCVersionV1,
"client-id", "client-id",
anyArg{}, "secret",
database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"}, database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"},
database.NumberArray[domain.OIDCResponseType]{1, 2}, database.NumberArray[domain.OIDCResponseType]{1, 2},
database.NumberArray[domain.OIDCGrantType]{1, 2}, database.NumberArray[domain.OIDCGrantType]{1, 2},
@ -471,7 +593,79 @@ func TestAppProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
"app-id",
"instance-id",
},
},
},
},
},
},
{
name: "project reduceOIDCConfigAdded, v2 secret",
args: args{
event: getEvent(
testEvent(
project.OIDCConfigAddedType,
project.AggregateType,
[]byte(`{
"oidcVersion": 0,
"appId": "app-id",
"clientId": "client-id",
"hashedSecret": "secret",
"redirectUris": ["redirect.one.ch", "redirect.two.ch"],
"responseTypes": [1,2],
"grantTypes": [1,2],
"applicationType": 2,
"authMethodType": 2,
"postLogoutRedirectUris": ["logout.one.ch", "logout.two.ch"],
"devMode": true,
"accessTokenType": 1,
"accessTokenRoleAssertion": true,
"idTokenRoleAssertion": true,
"idTokenUserinfoAssertion": true,
"clockSkew": 1000,
"additionalOrigins": ["origin.one.ch", "origin.two.ch"],
"skipNativeAppSuccessPage": true
}`),
), project.OIDCConfigAddedEventMapper),
},
reduce: (&appProjection{}).reduceOIDCConfigAdded,
want: wantReduce{
aggregateType: eventstore.AggregateType("project"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.apps7_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)",
expectedArgs: []interface{}{
"app-id",
"instance-id",
domain.OIDCVersionV1,
"client-id",
"secret",
database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"},
database.NumberArray[domain.OIDCResponseType]{1, 2},
database.NumberArray[domain.OIDCGrantType]{1, 2},
domain.OIDCApplicationTypeNative,
domain.OIDCAuthMethodTypeNone,
database.TextArray[string]{"logout.one.ch", "logout.two.ch"},
true,
domain.OIDCTokenTypeJWT,
true,
true,
true,
1 * time.Microsecond,
database.TextArray[string]{"origin.one.ch", "origin.two.ch"},
true,
},
},
{
expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -518,7 +712,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.apps6_oidc_configs SET (version, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) WHERE (app_id = $16) AND (instance_id = $17)", expectedStmt: "UPDATE projections.apps7_oidc_configs SET (version, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) WHERE (app_id = $16) AND (instance_id = $17)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
domain.OIDCVersionV1, domain.OIDCVersionV1,
database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"}, database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"},
@ -540,7 +734,7 @@ func TestAppProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -574,7 +768,7 @@ func TestAppProjection_reduces(t *testing.T) {
}, },
}, },
{ {
name: "project reduceOIDCConfigSecretChanged", name: "project reduceOIDCConfigSecretChanged, v1 secret",
args: args{ args: args{
event: getEvent( event: getEvent(
testEvent( testEvent(
@ -582,7 +776,7 @@ func TestAppProjection_reduces(t *testing.T) {
project.AggregateType, project.AggregateType,
[]byte(`{ []byte(`{
"appId": "app-id", "appId": "app-id",
"client_secret": {} "clientSecret": {"CryptoType":1,"Algorithm":"bcrypt","Crypted":"c2VjcmV0"}
}`), }`),
), project.OIDCConfigSecretChangedEventMapper), ), project.OIDCConfigSecretChangedEventMapper),
}, },
@ -593,15 +787,95 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.apps6_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.apps7_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, "secret",
"app-id", "app-id",
"instance-id", "instance-id",
}, },
}, },
{ {
expectedStmt: "UPDATE projections.apps6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
"app-id",
"instance-id",
},
},
},
},
},
},
{
name: "project reduceOIDCConfigSecretChanged, v2 secret",
args: args{
event: getEvent(
testEvent(
project.OIDCConfigSecretChangedType,
project.AggregateType,
[]byte(`{
"appId": "app-id",
"hashedSecret": "secret"
}`),
), project.OIDCConfigSecretChangedEventMapper),
},
reduce: (&appProjection{}).reduceOIDCConfigSecretChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("project"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.apps7_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
"secret",
"app-id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
"app-id",
"instance-id",
},
},
},
},
},
},
{
name: "project reduceOIDCConfigSecretHashUpdated",
args: args{
event: getEvent(
testEvent(
project.OIDCConfigSecretHashUpdatedType,
project.AggregateType,
[]byte(`{
"appId": "app-id",
"hashedSecret": "secret"
}`),
), eventstore.GenericEventMapper[project.OIDCConfigSecretHashUpdatedEvent]),
},
reduce: (&appProjection{}).reduceOIDCConfigSecretHashUpdated,
want: wantReduce{
aggregateType: eventstore.AggregateType("project"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.apps7_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
"secret",
"app-id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.apps7 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -630,7 +904,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.apps6 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedStmt: "DELETE FROM projections.apps7 WHERE (instance_id = $1) AND (resource_owner = $2)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"instance-id", "instance-id",
"agg-id", "agg-id",

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
old_handler "github.com/zitadel/zitadel/internal/eventstore/handler" old_handler "github.com/zitadel/zitadel/internal/eventstore/handler"
@ -15,7 +16,7 @@ import (
) )
const ( const (
UserTable = "projections.users11" UserTable = "projections.users12"
UserHumanTable = UserTable + "_" + UserHumanSuffix UserHumanTable = UserTable + "_" + UserHumanSuffix
UserMachineTable = UserTable + "_" + UserMachineSuffix UserMachineTable = UserTable + "_" + UserMachineSuffix
UserNotifyTable = UserTable + "_" + UserNotifySuffix UserNotifyTable = UserTable + "_" + UserNotifySuffix
@ -125,7 +126,7 @@ func (*userProjection) Init() *old_handler.Check {
handler.NewColumn(MachineUserInstanceIDCol, handler.ColumnTypeText), handler.NewColumn(MachineUserInstanceIDCol, handler.ColumnTypeText),
handler.NewColumn(MachineNameCol, handler.ColumnTypeText), handler.NewColumn(MachineNameCol, handler.ColumnTypeText),
handler.NewColumn(MachineDescriptionCol, handler.ColumnTypeText, handler.Nullable()), handler.NewColumn(MachineDescriptionCol, handler.ColumnTypeText, handler.Nullable()),
handler.NewColumn(MachineSecretCol, handler.ColumnTypeJSONB, handler.Nullable()), handler.NewColumn(MachineSecretCol, handler.ColumnTypeText, handler.Nullable()),
handler.NewColumn(MachineAccessTokenTypeCol, handler.ColumnTypeEnum, handler.Default(0)), handler.NewColumn(MachineAccessTokenTypeCol, handler.ColumnTypeEnum, handler.Default(0)),
}, },
handler.NewPrimaryKey(MachineUserInstanceIDCol, MachineUserIDCol), handler.NewPrimaryKey(MachineUserInstanceIDCol, MachineUserIDCol),
@ -285,6 +286,10 @@ func (p *userProjection) Reducers() []handler.AggregateReducer {
Event: user.MachineSecretSetType, Event: user.MachineSecretSetType,
Reduce: p.reduceMachineSecretSet, Reduce: p.reduceMachineSecretSet,
}, },
{
Event: user.MachineSecretHashUpdatedType,
Reduce: p.reduceMachineSecretHashUpdated,
},
{ {
Event: user.MachineSecretRemovedType, Event: user.MachineSecretRemovedType,
Reduce: p.reduceMachineSecretRemoved, Reduce: p.reduceMachineSecretRemoved,
@ -354,7 +359,7 @@ func (p *userProjection) reduceHumanAdded(event eventstore.Event) (*handler.Stat
handler.NewCol(NotifyInstanceIDCol, e.Aggregate().InstanceID), handler.NewCol(NotifyInstanceIDCol, e.Aggregate().InstanceID),
handler.NewCol(NotifyLastEmailCol, e.EmailAddress), handler.NewCol(NotifyLastEmailCol, e.EmailAddress),
handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: string(e.PhoneNumber), Valid: e.PhoneNumber != ""}), handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: string(e.PhoneNumber), Valid: e.PhoneNumber != ""}),
handler.NewCol(NotifyPasswordSetCol, user.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""), handler.NewCol(NotifyPasswordSetCol, crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""),
}, },
handler.WithTableSuffix(UserNotifySuffix), handler.WithTableSuffix(UserNotifySuffix),
), ),
@ -403,7 +408,7 @@ func (p *userProjection) reduceHumanRegistered(event eventstore.Event) (*handler
handler.NewCol(NotifyInstanceIDCol, e.Aggregate().InstanceID), handler.NewCol(NotifyInstanceIDCol, e.Aggregate().InstanceID),
handler.NewCol(NotifyLastEmailCol, e.EmailAddress), handler.NewCol(NotifyLastEmailCol, e.EmailAddress),
handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: string(e.PhoneNumber), Valid: e.PhoneNumber != ""}), handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: string(e.PhoneNumber), Valid: e.PhoneNumber != ""}),
handler.NewCol(NotifyPasswordSetCol, user.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""), handler.NewCol(NotifyPasswordSetCol, crypto.SecretOrEncodedHash(e.Secret, e.EncodedHash) != ""),
}, },
handler.WithTableSuffix(UserNotifySuffix), handler.WithTableSuffix(UserNotifySuffix),
), ),
@ -952,7 +957,37 @@ func (p *userProjection) reduceMachineSecretSet(event eventstore.Event) (*handle
), ),
handler.AddUpdateStatement( handler.AddUpdateStatement(
[]handler.Column{ []handler.Column{
handler.NewCol(MachineSecretCol, e.ClientSecret), handler.NewCol(MachineSecretCol, crypto.SecretOrEncodedHash(e.ClientSecret, e.HashedSecret)),
},
[]handler.Condition{
handler.NewCond(MachineUserIDCol, e.Aggregate().ID),
handler.NewCond(MachineUserInstanceIDCol, e.Aggregate().InstanceID),
},
handler.WithTableSuffix(UserMachineSuffix),
),
), nil
}
func (p *userProjection) reduceMachineSecretHashUpdated(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*user.MachineSecretHashUpdatedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Wieng4u", "reduce.wrong.event.type %s", user.MachineSecretHashUpdatedType)
}
return handler.NewMultiStatement(
e,
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(UserChangeDateCol, e.CreationDate()),
handler.NewCol(UserSequenceCol, e.Sequence()),
},
[]handler.Condition{
handler.NewCond(UserIDCol, e.Aggregate().ID),
handler.NewCond(UserInstanceIDCol, e.Aggregate().InstanceID),
},
),
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(MachineSecretCol, e.HashedSecret),
}, },
[]handler.Condition{ []handler.Condition{
handler.NewCond(MachineUserIDCol, e.Aggregate().ID), handler.NewCond(MachineUserIDCol, e.Aggregate().ID),

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