fix: v2 human command (#3435)

* add/register human command done

* validations

* crypto

* move clientid

* keys

* fix: clientID

* remove v2 package

* tests

* tests running

* revert old code

* instance domain from ctx

* chore: rename zitadel app ids

* comments

* fix: test
This commit is contained in:
Silvan 2022-04-12 16:20:17 +02:00 committed by GitHub
parent 4a0d61d75a
commit cea2567e22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 3524 additions and 2832 deletions

View File

@ -12,14 +12,14 @@ CREATE TABLE eventstore.events (
, editor_user TEXT NOT NULL
, editor_service TEXT NOT NULL
, resource_owner TEXT NOT NULL
, instance_id TEXT
, instance_id TEXT NOT NULL
, PRIMARY KEY (event_sequence DESC) USING HASH WITH BUCKET_COUNT = 10
, INDEX agg_type_agg_id (aggregate_type, aggregate_id)
, INDEX agg_type (aggregate_type)
, INDEX agg_type_seq (aggregate_type, event_sequence DESC)
STORING (id, event_type, aggregate_id, aggregate_version, previous_aggregate_sequence, creation_date, event_data, editor_user, editor_service, resource_owner, instance_id, previous_aggregate_type_sequence)
, INDEX max_sequence (aggregate_type, aggregate_id, event_sequence DESC)
, CONSTRAINT previous_sequence_unique UNIQUE (previous_aggregate_sequence DESC)
, CONSTRAINT prev_agg_type_seq_unique UNIQUE(previous_aggregate_type_sequence)
, PRIMARY KEY (event_sequence DESC, instance_id) USING HASH WITH BUCKET_COUNT = 10
, INDEX agg_type_agg_id (aggregate_type, aggregate_id, instance_id)
, INDEX agg_type (aggregate_type, instance_id)
, INDEX agg_type_seq (aggregate_type, event_sequence DESC, instance_id)
STORING (id, event_type, aggregate_id, aggregate_version, previous_aggregate_sequence, creation_date, event_data, editor_user, editor_service, resource_owner, previous_aggregate_type_sequence)
, INDEX max_sequence (aggregate_type, aggregate_id, event_sequence DESC, instance_id)
, CONSTRAINT previous_sequence_unique UNIQUE (previous_aggregate_sequence DESC, instance_id)
, CONSTRAINT prev_agg_type_seq_unique UNIQUE(previous_aggregate_type_sequence, instance_id)
)

View File

@ -3,7 +3,6 @@ package setup
import (
"context"
"database/sql"
_ "embed"
)
const (

View File

@ -2,21 +2,62 @@ package setup
import (
"context"
"database/sql"
"fmt"
"github.com/caos/zitadel/internal/command/v2"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
crypto_db "github.com/caos/zitadel/internal/crypto/database"
"github.com/caos/zitadel/internal/eventstore"
)
type DefaultInstance struct {
cmd *command.Command
InstanceSetup command.InstanceSetup
userEncryptionKey *crypto.KeyConfig
masterKey string
db *sql.DB
es *eventstore.Eventstore
domain string
defaults systemdefaults.SystemDefaults
zitadelRoles []authz.RoleMapping
}
func (mig *DefaultInstance) Execute(ctx context.Context) error {
_, err := mig.cmd.SetUpInstance(ctx, &mig.InstanceSetup)
keyStorage, err := crypto_db.NewKeyStorage(mig.db, mig.masterKey)
if err != nil {
return fmt.Errorf("cannot start key storage: %w", err)
}
if err = verifyKey(mig.userEncryptionKey, keyStorage); err != nil {
return err
}
userAlg, err := crypto.NewAESCrypto(mig.userEncryptionKey, keyStorage)
if err != nil {
return err
}
cmd := command.NewCommandV2(mig.es, mig.defaults, userAlg, mig.zitadelRoles)
ctx = authz.WithRequestedDomain(ctx, mig.domain)
_, err = cmd.SetUpInstance(ctx, &mig.InstanceSetup)
return err
}
func (mig *DefaultInstance) String() string {
return "03_default_instance"
}
func verifyKey(key *crypto.KeyConfig, storage crypto.KeyStorage) (err error) {
_, err = crypto.LoadKey(key.EncryptionKeyID, storage)
if err == nil {
return nil
}
k, err := crypto.NewKey(key.EncryptionKeyID)
if err != nil {
return err
}
return storage.CreateKeys(k)
}

View File

@ -10,6 +10,7 @@ import (
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/config/hook"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/database"
)
@ -21,6 +22,7 @@ type Config struct {
ExternalDomain string
ExternalSecure bool
Log *logging.Config
EncryptionKeys *encryptionKeyConfig
}
func MustNewConfig(v *viper.Viper) *Config {
@ -40,6 +42,10 @@ type Steps struct {
S3DefaultInstance *DefaultInstance
}
type encryptionKeyConfig struct {
User *crypto.KeyConfig
}
func MustNewSteps(v *viper.Viper) *Steps {
v.SetConfigType("yaml")
err := v.ReadConfig(bytes.NewBuffer(defaultSteps))

View File

@ -8,8 +8,8 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/caos/zitadel/cmd/admin/key"
http_util "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/command/v2"
"github.com/caos/zitadel/internal/database"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/migration"
@ -31,12 +31,15 @@ Requirements:
config := MustNewConfig(viper.GetViper())
steps := MustNewSteps(viper.New())
Setup(config, steps)
masterKey, err := key.MasterKey(cmd)
logging.OnError(err).Panic("No master key provided")
Setup(config, steps, masterKey)
},
}
}
func Setup(config *Config, steps *Steps) {
func Setup(config *Config, steps *Steps, masterKey string) {
dbClient, err := database.Connect(config.Database)
logging.OnError(err).Fatal("unable to connect to database")
@ -44,11 +47,17 @@ func Setup(config *Config, steps *Steps) {
logging.OnError(err).Fatal("unable to start eventstore")
migration.RegisterMappers(eventstoreClient)
cmd := command.New(eventstoreClient, "localhost", config.SystemDefaults)
steps.s1ProjectionTable = &ProjectionTable{dbClient: dbClient}
steps.s2AssetsTable = &AssetTable{dbClient: dbClient}
steps.S3DefaultInstance.cmd = cmd
steps.S3DefaultInstance.es = eventstoreClient
steps.S3DefaultInstance.db = dbClient
steps.S3DefaultInstance.defaults = config.SystemDefaults
steps.S3DefaultInstance.masterKey = masterKey
steps.S3DefaultInstance.domain = config.SystemDefaults.Domain
steps.S3DefaultInstance.zitadelRoles = config.InternalAuthZ.RolePermissionMappings
steps.S3DefaultInstance.userEncryptionKey = config.EncryptionKeys.User
steps.S3DefaultInstance.InstanceSetup.Zitadel.IsDevMode = !config.ExternalSecure
steps.S3DefaultInstance.InstanceSetup.Zitadel.BaseURL = http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
steps.S3DefaultInstance.InstanceSetup.Zitadel.IsDevMode = !config.ExternalSecure
steps.S3DefaultInstance.InstanceSetup.Zitadel.BaseURL = http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)

View File

@ -8,11 +8,64 @@ S3DefaultInstance:
LastName: Admin
NickName:
DisplayName:
Email: admin@zitadel.ch
Email:
Address: admin@zitadel.ch
Verified: true
PreferredLanguage:
Gender:
Phone:
Number:
Verified:
Password: Password1!
SecretGenerators:
PasswordSaltCost: 14
ClientSecret:
Length: 64
IncludeLowerLetters: true
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
InitializeUserCode:
Length: 6
Expiry: '72h'
IncludeLowerLetters: false
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
EmailVerificationCode:
Length: 6
Expiry: '1h'
IncludeLowerLetters: false
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
PhoneVerificationCode:
Length: 6
Expiry: '1h'
IncludeLowerLetters: false
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
PasswordVerificationCode:
Length: 6
Expiry: '1h'
IncludeLowerLetters: false
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
PasswordlessInitCode:
Length: 12
Expiry: '1h'
IncludeLowerLetters: true
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
DomainVerification:
Length: 32
IncludeLowerLetters: true
IncludeUpperLetters: true
IncludeDigits: true
IncludeSymbols: false
Features:
TierName: Default Tier
TierDescription: ""

View File

@ -32,26 +32,20 @@ type encryptionKeys struct {
OIDCKey []byte
}
func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyStorage) (*encryptionKeys, error) {
keys, err := keyStorage.ReadKeys()
func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyStorage) (keys *encryptionKeys, err error) {
if err := verifyDefaultKeys(keyStorage); err != nil {
return nil, err
}
keys = new(encryptionKeys)
keys.DomainVerification, err = crypto.NewAESCrypto(keyConfig.DomainVerification, keyStorage)
if err != nil {
return nil, err
}
if len(keys) == 0 {
if err := createDefaultKeys(keyStorage); err != nil {
return nil, err
}
}
encryptionKeys := new(encryptionKeys)
encryptionKeys.DomainVerification, err = crypto.NewAESCrypto(keyConfig.DomainVerification, keyStorage)
keys.IDPConfig, err = crypto.NewAESCrypto(keyConfig.IDPConfig, keyStorage)
if err != nil {
return nil, err
}
encryptionKeys.IDPConfig, err = crypto.NewAESCrypto(keyConfig.IDPConfig, keyStorage)
if err != nil {
return nil, err
}
encryptionKeys.OIDC, err = crypto.NewAESCrypto(keyConfig.OIDC, keyStorage)
keys.OIDC, err = crypto.NewAESCrypto(keyConfig.OIDC, keyStorage)
if err != nil {
return nil, err
}
@ -59,20 +53,20 @@ func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyS
if err != nil {
return nil, err
}
encryptionKeys.OIDCKey = []byte(key)
encryptionKeys.OTP, err = crypto.NewAESCrypto(keyConfig.OTP, keyStorage)
keys.OIDCKey = []byte(key)
keys.OTP, err = crypto.NewAESCrypto(keyConfig.OTP, keyStorage)
if err != nil {
return nil, err
}
encryptionKeys.SMS, err = crypto.NewAESCrypto(keyConfig.SMS, keyStorage)
keys.SMS, err = crypto.NewAESCrypto(keyConfig.SMS, keyStorage)
if err != nil {
return nil, err
}
encryptionKeys.SMTP, err = crypto.NewAESCrypto(keyConfig.SMTP, keyStorage)
keys.SMTP, err = crypto.NewAESCrypto(keyConfig.SMTP, keyStorage)
if err != nil {
return nil, err
}
encryptionKeys.User, err = crypto.NewAESCrypto(keyConfig.User, keyStorage)
keys.User, err = crypto.NewAESCrypto(keyConfig.User, keyStorage)
if err != nil {
return nil, err
}
@ -80,23 +74,30 @@ func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyS
if err != nil {
return nil, err
}
encryptionKeys.CSRFCookieKey = []byte(key)
keys.CSRFCookieKey = []byte(key)
key, err = crypto.LoadKey(keyConfig.UserAgentCookieKeyID, keyStorage)
if err != nil {
return nil, err
}
encryptionKeys.UserAgentCookieKey = []byte(key)
return encryptionKeys, nil
keys.UserAgentCookieKey = []byte(key)
return keys, nil
}
func createDefaultKeys(keyStorage crypto.KeyStorage) error {
keys := make([]*crypto.Key, len(defaultKeyIDs))
for i, keyID := range defaultKeyIDs {
func verifyDefaultKeys(keyStorage crypto.KeyStorage) (err error) {
keys := make([]*crypto.Key, 0, len(defaultKeyIDs))
for _, keyID := range defaultKeyIDs {
_, err := crypto.LoadKey(keyID, keyStorage)
if err == nil {
continue
}
key, err := crypto.NewKey(keyID)
if err != nil {
return err
}
keys[i] = key
keys = append(keys, key)
}
if len(keys) == 0 {
return nil
}
if err := keyStorage.CreateKeys(keys...); err != nil {
return caos_errs.ThrowInternal(err, "START-aGBq2", "cannot create default keys")

View File

@ -21,7 +21,6 @@ import (
"golang.org/x/net/http2/h2c"
"github.com/caos/zitadel/cmd/admin/key"
admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing"
"github.com/caos/zitadel/internal/api"
"github.com/caos/zitadel/internal/api/assets"
@ -118,7 +117,7 @@ func startZitadel(config *Config, masterKey string) error {
Origin: http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure),
DisplayName: "ZITADEL",
}
commands, err := command.StartCommands(eventstoreClient, config.SystemDefaults, config.InternalAuthZ, storage, authZRepo, webAuthNConfig, keys.IDPConfig, keys.OTP, keys.SMTP, keys.SMS, keys.DomainVerification, keys.OIDC)
commands, err := command.StartCommands(eventstoreClient, config.SystemDefaults, config.InternalAuthZ, storage, authZRepo, webAuthNConfig, keys.IDPConfig, keys.OTP, keys.SMTP, keys.SMS, keys.User, keys.DomainVerification, keys.OIDC)
if err != nil {
return fmt.Errorf("cannot start commands: %w", err)
}

View File

@ -3,6 +3,7 @@ package start
import (
"github.com/caos/logging"
"github.com/caos/zitadel/cmd/admin/initialise"
"github.com/caos/zitadel/cmd/admin/key"
"github.com/caos/zitadel/cmd/admin/setup"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -20,16 +21,18 @@ Last ZITADEL starts.
Requirements:
- cockroachdb`,
Run: func(cmd *cobra.Command, args []string) {
masterKey, err := key.MasterKey(cmd)
logging.OnError(err).Panic("No master key provided")
initialise.InitAll(initialise.MustNewConfig(viper.GetViper()))
setupConfig := setup.MustNewConfig(viper.GetViper())
setupSteps := setup.MustNewSteps(viper.New())
setup.Setup(setupConfig, setupSteps)
setup.Setup(setupConfig, setupSteps, masterKey)
startConfig := MustNewConfig(viper.GetViper())
startMasterKey, _ := cmd.Flags().GetString(flagMasterKey)
err := startZitadel(startConfig, startMasterKey)
err = startZitadel(startConfig, masterKey)
logging.OnError(err).Fatal("unable to start zitadel")
},
}

View File

@ -12,6 +12,7 @@ type Instance interface {
InstanceID() string
ProjectID() string
ConsoleClientID() string
RequestedDomain() string
}
type InstanceVerifier interface {
@ -19,7 +20,8 @@ type InstanceVerifier interface {
}
type instance struct {
ID string
ID string
Domain string
}
func (i *instance) InstanceID() string {
@ -34,6 +36,10 @@ func (i *instance) ConsoleClientID() string {
return ""
}
func (i *instance) RequestedDomain() string {
return i.Domain
}
func GetInstance(ctx context.Context) Instance {
instance, ok := ctx.Value(instanceKey).(Instance)
if !ok {
@ -49,3 +55,13 @@ func WithInstance(ctx context.Context, instance Instance) context.Context {
func WithInstanceID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, instanceKey, &instance{ID: id})
}
func WithRequestedDomain(ctx context.Context, domain string) context.Context {
i, ok := ctx.Value(instanceKey).(*instance)
if !ok {
i = new(instance)
}
i.Domain = domain
return context.WithValue(ctx, instanceKey, i)
}

View File

@ -78,3 +78,7 @@ func (m *mockInstance) ProjectID() string {
func (m *mockInstance) ConsoleClientID() string {
return "consoleID"
}
func (m *mockInstance) RequestedDomain() string {
return "zitadel.cloud"
}

View File

@ -28,7 +28,8 @@ type Server struct {
userCodeAlg crypto.EncryptionAlgorithm
}
func CreateServer(command *command.Commands,
func CreateServer(
command *command.Commands,
query *query.Queries,
sd systemdefaults.SystemDefaults,
assetAPIPrefix string,

View File

@ -4,7 +4,9 @@ import (
"context"
"time"
"github.com/caos/logging"
"github.com/caos/oidc/pkg/oidc"
"golang.org/x/text/language"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/caos/zitadel/internal/api/authz"
@ -13,9 +15,9 @@ import (
idp_grpc "github.com/caos/zitadel/internal/api/grpc/idp"
"github.com/caos/zitadel/internal/api/grpc/metadata"
obj_grpc "github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/api/grpc/user"
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
z_oidc "github.com/caos/zitadel/internal/api/oidc"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/query"
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
@ -192,24 +194,40 @@ func (s *Server) BulkRemoveUserMetadata(ctx context.Context, req *mgmt_pb.BulkRe
}
func (s *Server) AddHumanUser(ctx context.Context, req *mgmt_pb.AddHumanUserRequest) (*mgmt_pb.AddHumanUserResponse, error) {
initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.userCodeAlg)
if err != nil {
return nil, err
}
phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg)
if err != nil {
return nil, err
}
human, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, AddHumanUserRequestToDomain(req), initCodeGenerator, phoneCodeGenerator)
lang, err := language.Parse(req.Profile.PreferredLanguage)
logging.OnError(err).Debug("unable to parse language")
details, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, &command.AddHuman{
Username: req.UserName,
FirstName: req.Profile.FirstName,
LastName: req.Profile.LastName,
NickName: req.Profile.NickName,
DisplayName: req.Profile.DisplayName,
Email: command.Email{
Address: req.Email.Email,
Verified: req.Email.IsEmailVerified,
},
PreferredLang: lang,
Gender: user_grpc.GenderToDomain(req.Profile.Gender),
Phone: command.Phone{
Number: req.Phone.Phone,
Verified: req.Phone.IsPhoneVerified,
},
Password: req.InitialPassword,
PasswordChangeRequired: true,
Passwordless: false,
Register: false,
ExternalIDP: false,
})
if err != nil {
return nil, err
}
return &mgmt_pb.AddHumanUserResponse{
UserId: human.AggregateID,
UserId: details.ID,
Details: obj_grpc.AddToDetailsPb(
human.Sequence,
human.ChangeDate,
human.ResourceOwner,
details.Sequence,
details.EventDate,
details.ResourceOwner,
),
}, nil
}
@ -763,7 +781,7 @@ func (s *Server) GetPersonalAccessTokenByIDs(ctx context.Context, req *mgmt_pb.G
return nil, err
}
return &mgmt_pb.GetPersonalAccessTokenByIDsResponse{
Token: user.PersonalAccessTokenToPb(token),
Token: user_grpc.PersonalAccessTokenToPb(token),
}, nil
}

View File

@ -172,3 +172,7 @@ func (m *mockInstance) ProjectID() string {
func (m *mockInstance) ConsoleClientID() string {
return "consoleClientID"
}
func (m *mockInstance) RequestedDomain() string {
return "localhost"
}

View File

@ -256,3 +256,7 @@ func (m *mockInstance) ProjectID() string {
func (m *mockInstance) ConsoleClientID() string {
return "consoleClientID"
}
func (m *mockInstance) RequestedDomain() string {
return "zitadel.cloud"
}

View File

@ -48,6 +48,18 @@ type Commands struct {
privateKeyLifetime time.Duration
publicKeyLifetime time.Duration
tokenVerifier orgFeatureChecker
v2 *commandNew
}
type commandNew struct {
es *eventstore.Eventstore
userPasswordAlg crypto.HashAlgorithm
phoneAlg crypto.EncryptionAlgorithm
emailAlg crypto.EncryptionAlgorithm
initCodeAlg crypto.EncryptionAlgorithm
zitadelRoles []authz.RoleMapping
id id.Generator
}
type orgFeatureChecker interface {
@ -64,6 +76,7 @@ func StartCommands(es *eventstore.Eventstore,
otpEncryption,
smtpEncryption,
smsEncryption,
userEncryption,
domainVerificationEncryption,
oidcEncryption crypto.EncryptionAlgorithm,
) (repo *Commands, err error) {
@ -81,7 +94,9 @@ func StartCommands(es *eventstore.Eventstore,
smsCrypto: smsEncryption,
domainVerificationAlg: domainVerificationEncryption,
keyAlgorithm: oidcEncryption,
v2: NewCommandV2(es, defaults, userEncryption, authZConfig.RolePermissionMappings),
}
instance_repo.RegisterEventMappers(repo.eventstore)
org.RegisterEventMappers(repo.eventstore)
usr_repo.RegisterEventMappers(repo.eventstore)
@ -113,6 +128,31 @@ func StartCommands(es *eventstore.Eventstore,
return repo, nil
}
func NewCommandV2(
es *eventstore.Eventstore,
defaults sd.SystemDefaults,
userAlg crypto.EncryptionAlgorithm,
zitadelRoles []authz.RoleMapping,
) *commandNew {
instance_repo.RegisterEventMappers(es)
org.RegisterEventMappers(es)
usr_repo.RegisterEventMappers(es)
usr_grant_repo.RegisterEventMappers(es)
proj_repo.RegisterEventMappers(es)
keypair.RegisterEventMappers(es)
action.RegisterEventMappers(es)
return &commandNew{
es: es,
userPasswordAlg: crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost),
initCodeAlg: userAlg,
phoneAlg: userAlg,
emailAlg: userAlg,
zitadelRoles: zitadelRoles,
id: id.SonyFlakeGenerator,
}
}
func AppendAndReduce(object interface {
AppendEvents(...eventstore.Event)
Reduce() error

View File

@ -0,0 +1,67 @@
package command
import (
"context"
"time"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
)
func newCryptoCodeWithExpiry(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (value *crypto.CryptoValue, expiry time.Duration, err error) {
config, err := secretGeneratorConfig(ctx, filter, typ)
if err != nil {
return nil, -1, err
}
switch a := alg.(type) {
case crypto.HashAlgorithm:
value, _, err = crypto.NewCode(crypto.NewHashGenerator(*config, a))
case crypto.EncryptionAlgorithm:
value, _, err = crypto.NewCode(crypto.NewEncryptionGenerator(*config, a))
default:
return nil, -1, errors.ThrowInternal(nil, "COMMA-RreV6", "Errors.Internal")
}
if err != nil {
return nil, -1, err
}
return value, config.Expiry, nil
}
func newCryptoCodeWithPlain(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (value *crypto.CryptoValue, plain string, err error) {
config, err := secretGeneratorConfig(ctx, filter, typ)
if err != nil {
return nil, "", err
}
switch a := alg.(type) {
case crypto.HashAlgorithm:
return crypto.NewCode(crypto.NewHashGenerator(*config, a))
case crypto.EncryptionAlgorithm:
return crypto.NewCode(crypto.NewEncryptionGenerator(*config, a))
}
return nil, "", errors.ThrowInvalidArgument(nil, "V2-NGESt", "Errors.Internal")
}
func secretGeneratorConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType) (*crypto.GeneratorConfig, error) {
wm := NewInstanceSecretGeneratorConfigWriteModel(ctx, typ)
events, err := filter(ctx, wm.Query())
if err != nil {
return nil, err
}
wm.AppendEvents(events...)
if err := wm.Reduce(); err != nil {
return nil, err
}
return &crypto.GeneratorConfig{
Length: wm.Length,
Expiry: wm.Expiry,
IncludeLowerLetters: wm.IncludeLowerLetters,
IncludeUpperLetters: wm.IncludeUpperLetters,
IncludeDigits: wm.IncludeDigits,
IncludeSymbols: wm.IncludeSymbols,
}, nil
}

23
internal/command/email.go Normal file
View File

@ -0,0 +1,23 @@
package command
import (
"context"
"time"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
)
type Email struct {
Address string
Verified bool
}
func (e *Email) Valid() bool {
return e.Address != "" && domain.EmailRegex.MatchString(e.Address)
}
func newEmailCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (value *crypto.CryptoValue, expiry time.Duration, err error) {
return newCryptoCodeWithExpiry(ctx, filter, domain.SecretGeneratorTypeVerifyEmailCode, alg)
}

View File

@ -6,8 +6,10 @@ import (
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/ui/console"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/id"
"github.com/caos/zitadel/internal/repository/instance"
@ -53,6 +55,16 @@ type InstanceSetup struct {
ActionsAllowed domain.ActionsAllowed
MaxActions int
}
SecretGenerators struct {
PasswordSaltCost uint
ClientSecret *crypto.GeneratorConfig
InitializeUserCode *crypto.GeneratorConfig
EmailVerificationCode *crypto.GeneratorConfig
PhoneVerificationCode *crypto.GeneratorConfig
PasswordVerificationCode *crypto.GeneratorConfig
PasswordlessInitCode *crypto.GeneratorConfig
DomainVerification *crypto.GeneratorConfig
}
PasswordComplexityPolicy struct {
MinLength uint64
HasLowercase bool
@ -110,15 +122,11 @@ type ZitadelConfig struct {
IsDevMode bool
BaseURL string
projectID string
mgmtID string
mgmtClientID string
adminID string
adminClientID string
authID string
authClientID string
consoleID string
consoleClientID string
projectID string
mgmtAppID string
adminAppID string
authAppID string
consoleAppID string
}
func (s *InstanceSetup) generateIDs() (err error) {
@ -127,50 +135,35 @@ func (s *InstanceSetup) generateIDs() (err error) {
return err
}
s.Zitadel.mgmtID, err = id.SonyFlakeGenerator.Next()
if err != nil {
return err
}
s.Zitadel.mgmtClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
s.Zitadel.mgmtAppID, err = id.SonyFlakeGenerator.Next()
if err != nil {
return err
}
s.Zitadel.adminID, err = id.SonyFlakeGenerator.Next()
if err != nil {
return err
}
s.Zitadel.adminClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
s.Zitadel.adminAppID, err = id.SonyFlakeGenerator.Next()
if err != nil {
return err
}
s.Zitadel.authID, err = id.SonyFlakeGenerator.Next()
if err != nil {
return err
}
s.Zitadel.authClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
s.Zitadel.authAppID, err = id.SonyFlakeGenerator.Next()
if err != nil {
return err
}
s.Zitadel.consoleID, err = id.SonyFlakeGenerator.Next()
if err != nil {
return err
}
s.Zitadel.consoleClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
s.Zitadel.consoleAppID, err = id.SonyFlakeGenerator.Next()
if err != nil {
return err
}
return nil
}
func (command *Command) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*domain.ObjectDetails, error) {
func (c *commandNew) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*domain.ObjectDetails, error) {
instanceID, err := id.SonyFlakeGenerator.Next()
if err != nil {
return nil, err
}
ctx = authz.SetCtxData(authz.WithInstanceID(ctx, instanceID), authz.CtxData{OrgID: instanceID, ResourceOwner: instanceID})
requestedDomain := authz.GetInstance(ctx).RequestedDomain()
ctx = authz.SetCtxData(authz.WithRequestedDomain(authz.WithInstanceID(ctx, instanceID), requestedDomain), authz.CtxData{OrgID: instanceID, ResourceOwner: instanceID})
orgID, err := id.SonyFlakeGenerator.Next()
if err != nil {
@ -219,6 +212,14 @@ func (command *Command) SetUpInstance(ctx context.Context, setup *InstanceSetup)
setup.Features.ActionsAllowed,
setup.Features.MaxActions,
),
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeAppSecret, setup.SecretGenerators.ClientSecret),
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeInitCode, setup.SecretGenerators.InitializeUserCode),
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeVerifyEmailCode, setup.SecretGenerators.EmailVerificationCode),
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeVerifyPhoneCode, setup.SecretGenerators.PhoneVerificationCode),
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypePasswordResetCode, setup.SecretGenerators.PasswordVerificationCode),
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypePasswordlessInitCode, setup.SecretGenerators.PasswordlessInitCode),
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeVerifyDomain, setup.SecretGenerators.DomainVerification),
AddPasswordComplexityPolicy(
instanceAgg,
setup.PasswordComplexityPolicy.MinLength,
@ -280,72 +281,82 @@ func (command *Command) SetUpInstance(ctx context.Context, setup *InstanceSetup)
validations = append(validations, SetInstanceCustomTexts(instanceAgg, msg))
}
validations = append(validations,
AddOrg(orgAgg, setup.Org.Name, command.iamDomain),
AddHumanCommand(userAgg, &setup.Org.Human, command.userPasswordAlg),
AddOrgMember(orgAgg, userID, domain.RoleOrgOwner),
AddInstanceMember(instanceAgg, userID, domain.RoleIAMOwner),
console := &addOIDCApp{
AddApp: AddApp{
Aggregate: *projectAgg,
ID: setup.Zitadel.consoleAppID,
Name: consoleAppName,
},
Version: domain.OIDCVersionV1,
RedirectUris: []string{setup.Zitadel.BaseURL + consoleRedirectPath},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeUserAgent,
AuthMethodType: domain.OIDCAuthMethodTypeNone,
PostLogoutRedirectUris: []string{setup.Zitadel.BaseURL + consolePostLogoutPath},
DevMode: setup.Zitadel.IsDevMode,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: false,
IDTokenRoleAssertion: false,
IDTokenUserinfoAssertion: false,
ClockSkew: 0,
}
AddProject(projectAgg, zitadelProjectName, userID, false, false, false, domain.PrivateLabelingSettingUnspecified),
validations = append(validations,
AddOrgCommand(ctx, orgAgg, setup.Org.Name),
addHumanCommand(userAgg, &setup.Org.Human, c.userPasswordAlg, c.phoneAlg, c.emailAlg, c.initCodeAlg),
c.AddOrgMember(orgAgg, userID, domain.RoleOrgOwner),
c.AddInstanceMember(instanceAgg, userID, domain.RoleIAMOwner),
AddProjectCommand(projectAgg, zitadelProjectName, userID, false, false, false, domain.PrivateLabelingSettingUnspecified),
SetIAMProject(instanceAgg, projectAgg.ID),
AddAPIApp(
*projectAgg,
setup.Zitadel.mgmtID,
mgmtAppName,
setup.Zitadel.mgmtClientID,
AddAPIAppCommand(
&addAPIApp{
AddApp: AddApp{
Aggregate: *projectAgg,
ID: setup.Zitadel.mgmtAppID,
Name: mgmtAppName,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
nil,
domain.APIAuthMethodTypePrivateKeyJWT,
),
AddAPIApp(
*projectAgg,
setup.Zitadel.adminID,
adminAppName,
setup.Zitadel.adminClientID,
AddAPIAppCommand(
&addAPIApp{
AddApp: AddApp{
Aggregate: *projectAgg,
ID: setup.Zitadel.adminAppID,
Name: adminAppName,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
nil,
domain.APIAuthMethodTypePrivateKeyJWT,
),
AddAPIApp(
*projectAgg,
setup.Zitadel.authID,
authAppName,
setup.Zitadel.authClientID,
AddAPIAppCommand(
&addAPIApp{
AddApp: AddApp{
Aggregate: *projectAgg,
ID: setup.Zitadel.authAppID,
Name: authAppName,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
nil,
domain.APIAuthMethodTypePrivateKeyJWT,
),
AddOIDCApp(
*projectAgg,
domain.OIDCVersionV1,
setup.Zitadel.consoleID,
consoleAppName,
setup.Zitadel.consoleClientID,
nil,
[]string{setup.Zitadel.BaseURL + consoleRedirectPath},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
domain.OIDCApplicationTypeUserAgent,
domain.OIDCAuthMethodTypeNone,
[]string{setup.Zitadel.BaseURL + consolePostLogoutPath},
setup.Zitadel.IsDevMode,
domain.OIDCTokenTypeBearer,
false,
false,
false,
0,
nil,
),
SetIAMConsoleID(instanceAgg, setup.Zitadel.consoleClientID),
AddOIDCAppCommand(console, nil),
SetIAMConsoleID(instanceAgg, &console.ClientID),
)
cmds, err := preparation.PrepareCommands(ctx, command.es.Filter, validations...)
cmds, err := preparation.PrepareCommands(ctx, c.es.Filter, validations...)
if err != nil {
return nil, err
}
events, err := command.es.Push(ctx, cmds...)
events, err := c.es.Push(ctx, cmds...)
if err != nil {
return nil, err
}
@ -368,7 +379,7 @@ func SetIAMProject(a *instance.Aggregate, projectID string) preparation.Validati
}
//SetIAMConsoleID defines the command to set the clientID of the Console App onto the instance
func SetIAMConsoleID(a *instance.Aggregate, clientID string) preparation.Validation {
func SetIAMConsoleID(a *instance.Aggregate, clientID *string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
return []eventstore.Command{
@ -377,3 +388,25 @@ func SetIAMConsoleID(a *instance.Aggregate, clientID string) preparation.Validat
}, nil
}
}
func (c *Commands) setGlobalOrg(ctx context.Context, iamAgg *eventstore.Aggregate, iamWriteModel *InstanceWriteModel, orgID string) (eventstore.Command, error) {
err := c.eventstore.FilterToQueryReducer(ctx, iamWriteModel)
if err != nil {
return nil, err
}
if iamWriteModel.GlobalOrgID != "" {
return nil, errors.ThrowPreconditionFailed(nil, "IAM-HGG24", "Errors.IAM.GlobalOrgAlreadySet")
}
return instance.NewGlobalOrgSetEventEvent(ctx, iamAgg, orgID), nil
}
func (c *Commands) setIAMProject(ctx context.Context, iamAgg *eventstore.Aggregate, iamWriteModel *InstanceWriteModel, projectID string) (eventstore.Command, error) {
err := c.eventstore.FilterToQueryReducer(ctx, iamWriteModel)
if err != nil {
return nil, err
}
if iamWriteModel.ProjectID != "" {
return nil, errors.ThrowPreconditionFailed(nil, "IAM-EGbw2", "Errors.IAM.IAMProjectAlreadySet")
}
return instance.NewIAMProjectSetEvent(ctx, iamAgg, projectID), nil
}

View File

@ -5,12 +5,11 @@ import (
"strings"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/repository/instance"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
)
func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
@ -52,7 +51,7 @@ func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain stri
func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain string, generated bool) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-28nlD", "Errors.Invalid.Argument")
return nil, errors.ThrowInvalidArgument(nil, "INST-28nlD", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
@ -60,7 +59,7 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
return nil, err
}
if domainWriteModel.State == domain.InstanceDomainStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "INST-i2nl", "Errors.Instance.Domain.AlreadyExists")
return nil, errors.ThrowAlreadyExists(nil, "INST-i2nl", "Errors.Instance.Domain.AlreadyExists")
}
return []eventstore.Command{instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated)}, nil
}, nil
@ -70,7 +69,7 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
func (c *Commands) removeInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-39nls", "Errors.Invalid.Argument")
return nil, errors.ThrowInvalidArgument(nil, "INST-39nls", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
@ -78,10 +77,10 @@ func (c *Commands) removeInstanceDomain(a *instance.Aggregate, instanceDomain st
return nil, err
}
if domainWriteModel.State != domain.InstanceDomainStateActive {
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-8ls9f", "Errors.Instance.Domain.NotFound")
return nil, errors.ThrowNotFound(nil, "INSTANCE-8ls9f", "Errors.Instance.Domain.NotFound")
}
if domainWriteModel.Generated {
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-9hn3n", "Errors.Instance.Domain.GeneratedNotRemovable")
return nil, errors.ThrowPreconditionFailed(nil, "INSTANCE-9hn3n", "Errors.Instance.Domain.GeneratedNotRemovable")
}
return []eventstore.Command{instance.NewDomainRemovedEvent(ctx, &a.Aggregate, instanceDomain)}, nil
}, nil

View File

@ -3,7 +3,7 @@ package command
import (
"context"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
)

View File

@ -5,8 +5,7 @@ import (
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
@ -95,8 +94,8 @@ func SetInstanceCustomTexts(
}
}
func existingInstanceCustomMessageText(ctx context.Context, filter preparation.FilterToQueryReducer, textType string, lang language.Tag) (*command.InstanceCustomMessageTextWriteModel, error) {
writeModel := command.NewInstanceCustomMessageTextWriteModel(ctx, textType, lang)
func existingInstanceCustomMessageText(ctx context.Context, filter preparation.FilterToQueryReducer, textType string, lang language.Tag) (*InstanceCustomMessageTextWriteModel, error) {
writeModel := NewInstanceCustomMessageTextWriteModel(ctx, textType, lang)
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err

View File

@ -2,13 +2,97 @@ package command
import (
"context"
"time"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/repository/instance"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
)
func SetDefaultFeatures(
a *instance.Aggregate,
tierName,
tierDescription string,
state domain.FeaturesState,
stateDescription string,
retention time.Duration,
loginPolicyFactors,
loginPolicyIDP,
loginPolicyPasswordless,
loginPolicyRegistration,
loginPolicyUsernameLogin,
loginPolicyPasswordReset,
passwordComplexityPolicy,
labelPolicyPrivateLabel,
labelPolicyWatermark,
customDomain,
privacyPolicy,
metadataUser,
customTextMessage,
customTextLogin,
lockoutPolicy bool,
actionsAllowed domain.ActionsAllowed,
maxActions int,
) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if !state.Valid() || state == domain.FeaturesStateUnspecified || state == domain.FeaturesStateRemoved {
return nil, errors.ThrowInvalidArgument(nil, "INSTA-d3r1s", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
writeModel, err := defaultFeatures(ctx, filter)
if err != nil {
return nil, err
}
event, hasChanged := writeModel.NewSetEvent(ctx, &a.Aggregate,
tierName,
tierDescription,
state,
stateDescription,
retention,
loginPolicyFactors,
loginPolicyIDP,
loginPolicyPasswordless,
loginPolicyRegistration,
loginPolicyUsernameLogin,
loginPolicyPasswordReset,
passwordComplexityPolicy,
labelPolicyPrivateLabel,
labelPolicyWatermark,
customDomain,
privacyPolicy,
metadataUser,
customTextMessage,
customTextLogin,
lockoutPolicy,
actionsAllowed,
maxActions,
)
if !hasChanged {
return nil, errors.ThrowPreconditionFailed(nil, "INSTA-GE4h2", "Errors.Features.NotChanged")
}
return []eventstore.Command{
event,
}, nil
}, nil
}
}
func defaultFeatures(ctx context.Context, filter preparation.FilterToQueryReducer) (*InstanceFeaturesWriteModel, error) {
features := NewInstanceFeaturesWriteModel(ctx)
events, err := filter(ctx, features.Query())
if err != nil {
return nil, err
}
if len(events) == 0 {
return features, nil
}
features.AppendEvents(events...)
err = features.Reduce()
return features, err
}
func (c *Commands) SetDefaultFeatures(ctx context.Context, features *domain.Features) (*domain.ObjectDetails, error) {
existingFeatures := NewInstanceFeaturesWriteModel(ctx)
setEvent, err := c.setDefaultFeatures(ctx, existingFeatures, features)
@ -59,7 +143,7 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *Ins
features.MaxActions,
)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
return nil, errors.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
}
return setEvent, nil
}

View File

@ -3,7 +3,7 @@ package command
import (
"context"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
)

View File

@ -3,7 +3,7 @@ package command
import (
"context"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
)

View File

@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"

View File

@ -4,15 +4,71 @@ import (
"context"
"reflect"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
func (c *commandNew) AddInstanceMember(a *instance.Aggregate, userID string, roles ...string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if userID == "" {
return nil, errors.ThrowInvalidArgument(nil, "INSTA-SDSfs", "Errors.Invalid.Argument")
}
if len(domain.CheckForInvalidRoles(roles, domain.IAMRolePrefix, c.zitadelRoles)) > 0 {
return nil, caos_errs.ThrowInvalidArgument(nil, "INSTANCE-4m0fS", "Errors.IAM.MemberInvalid")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
if exists, err := ExistsUser(ctx, filter, userID, ""); err != nil || !exists {
return nil, errors.ThrowNotFound(err, "INSTA-GSXOn", "Errors.User.NotFound")
}
if isMember, err := IsInstanceMember(ctx, filter, a.ID, userID); err != nil || isMember {
return nil, errors.ThrowAlreadyExists(err, "INSTA-pFDwe", "Errors.Instance.Member.AlreadyExists")
}
return []eventstore.Command{instance.NewMemberAddedEvent(ctx, &a.Aggregate, userID, roles...)}, nil
},
nil
}
}
func IsInstanceMember(ctx context.Context, filter preparation.FilterToQueryReducer, instanceID, userID string) (isMember bool, err error) {
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
OrderAsc().
AddQuery().
AggregateIDs(instanceID).
AggregateTypes(instance.AggregateType).
EventTypes(
instance.MemberAddedEventType,
instance.MemberRemovedEventType,
instance.MemberCascadeRemovedEventType,
).Builder())
if err != nil {
return false, err
}
for _, event := range events {
switch e := event.(type) {
case *instance.MemberAddedEvent:
if e.UserID == userID {
isMember = true
}
case *instance.MemberRemovedEvent:
if e.UserID == userID {
isMember = false
}
case *instance.MemberCascadeRemovedEvent:
if e.UserID == userID {
isMember = false
}
}
}
return isMember, nil
}
func (c *Commands) AddInstanceMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
if member.UserID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INSTANCE-Mf83b", "Errors.IAM.MemberInvalid")

View File

@ -3,7 +3,7 @@ package command
import (
"context"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
)

View File

@ -3,7 +3,7 @@ package command
import (
"context"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
)

View File

@ -3,7 +3,7 @@ package command
import (
"context"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
)

View File

@ -173,7 +173,7 @@ func eventFromEventPusherWithInstanceID(instanceID string, event eventstore.Comm
AggregateID: event.Aggregate().ID,
AggregateType: repository.AggregateType(event.Aggregate().Type),
ResourceOwner: sql.NullString{String: event.Aggregate().ResourceOwner, Valid: event.Aggregate().ResourceOwner != ""},
InstanceID: sql.NullString{String: instanceID, Valid: instanceID != ""},
InstanceID: instanceID,
}
}

View File

@ -2,14 +2,78 @@ package command
import (
"context"
"strings"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/id"
"github.com/caos/zitadel/internal/repository/org"
user_repo "github.com/caos/zitadel/internal/repository/user"
)
type OrgSetup struct {
Name string
Human AddHuman
}
func (c *commandNew) SetUpOrg(ctx context.Context, o *OrgSetup) (*domain.ObjectDetails, error) {
orgID, err := id.SonyFlakeGenerator.Next()
if err != nil {
return nil, err
}
userID, err := id.SonyFlakeGenerator.Next()
if err != nil {
return nil, err
}
orgAgg := org.NewAggregate(orgID, orgID)
userAgg := user_repo.NewAggregate(userID, orgID)
cmds, err := preparation.PrepareCommands(ctx, c.es.Filter,
AddOrgCommand(ctx, orgAgg, o.Name),
addHumanCommand(userAgg, &o.Human, c.userPasswordAlg, c.phoneAlg, c.emailAlg, c.initCodeAlg),
c.AddOrgMember(orgAgg, userID, domain.RoleOrgOwner),
)
if err != nil {
return nil, err
}
events, err := c.es.Push(ctx, cmds...)
if err != nil {
return nil, err
}
return &domain.ObjectDetails{
Sequence: events[len(events)-1].Sequence(),
EventDate: events[len(events)-1].CreationDate(),
ResourceOwner: orgID,
}, nil
}
//AddOrgCommand defines the commands to create a new org,
// this includes the verified default domain
func AddOrgCommand(ctx context.Context, a *org.Aggregate, name string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if name = strings.TrimSpace(name); name == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-mruNY", "Errors.Invalid.Argument")
}
defaultDomain := domain.NewIAMDomainName(name, authz.GetInstance(ctx).RequestedDomain())
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
return []eventstore.Command{
org.NewOrgAddedEvent(ctx, &a.Aggregate, name),
org.NewDomainAddedEvent(ctx, &a.Aggregate, defaultDomain),
org.NewDomainVerifiedEvent(ctx, &a.Aggregate, defaultDomain),
org.NewDomainPrimarySetEvent(ctx, &a.Aggregate, defaultDomain),
}, nil
}, nil
}
}
func (c *Commands) getOrg(ctx context.Context, orgID string) (*domain.Org, error) {
writeModel, err := c.getOrgWriteModelByID(ctx, orgID)
if err != nil {

View File

@ -2,20 +2,92 @@ package command
import (
"context"
"strings"
"github.com/caos/logging"
errs "errors"
http_utils "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
)
func AddOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if domain = strings.TrimSpace(domain); domain == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-r3h4J", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
existing, err := orgDomain(ctx, filter, a.ID, domain)
if err != nil && !errs.Is(err, errors.ThrowNotFound(nil, "", "")) {
return nil, err
}
if existing != nil && existing.Verified {
return nil, errors.ThrowAlreadyExists(nil, "V2-e1wse", "Errors.Already.Exists")
}
return []eventstore.Command{org.NewDomainAddedEvent(ctx, &a.Aggregate, domain)}, nil
}, nil
}
}
func VerifyOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if domain = strings.TrimSpace(domain); domain == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-yqlVQ", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
// no checks required because unique constraints handle it
return []eventstore.Command{org.NewDomainVerifiedEvent(ctx, &a.Aggregate, domain)}, nil
}, nil
}
}
func SetPrimaryOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if domain = strings.TrimSpace(domain); domain == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-gmNqY", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
existing, err := orgDomain(ctx, filter, a.ID, domain)
if err != nil {
return nil, errors.ThrowAlreadyExists(err, "V2-d0Gyw", "Errors.Already.Exists")
}
if existing.Primary {
return nil, errors.ThrowPreconditionFailed(nil, "COMMA-FfoZO", "Errors.Org.DomainAlreadyPrimary")
}
if !existing.Verified {
return nil, errors.ThrowPreconditionFailed(nil, "COMMA-yKA80", "Errors.Org.DomainNotVerified")
}
return []eventstore.Command{org.NewDomainPrimarySetEvent(ctx, &a.Aggregate, domain)}, nil
}, nil
}
}
func orgDomain(ctx context.Context, filter preparation.FilterToQueryReducer, orgID, domain string) (*OrgDomainWriteModel, error) {
wm := NewOrgDomainWriteModel(orgID, domain)
events, err := filter(ctx, wm.Query())
if err != nil {
return nil, err
}
if len(events) == 0 {
return nil, errors.ThrowNotFound(nil, "COMMA-kFHpQ", "Errors.Org.DomainNotFound")
}
wm.AppendEvents(events...)
if err = wm.Reduce(); err != nil {
return nil, err
}
return wm, nil
}
func (c *Commands) AddOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain, claimedUserIDs []string) (*domain.OrgDomain, error) {
if !orgDomain.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
return nil, errors.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
}
domainWriteModel := NewOrgDomainWriteModel(orgDomain.AggregateID, orgDomain.Domain)
orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel)
@ -36,21 +108,21 @@ func (c *Commands) AddOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain
func (c *Commands) GenerateOrgDomainValidation(ctx context.Context, orgDomain *domain.OrgDomain) (token, url string, err error) {
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
return "", "", caos_errs.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
return "", "", errors.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
}
checkType, ok := orgDomain.ValidationType.CheckType()
if !ok {
return "", "", caos_errs.ThrowInvalidArgument(nil, "ORG-Gsw31", "Errors.Org.DomainVerificationTypeInvalid")
return "", "", errors.ThrowInvalidArgument(nil, "ORG-Gsw31", "Errors.Org.DomainVerificationTypeInvalid")
}
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
if err != nil {
return "", "", err
}
if domainWriteModel.State != domain.OrgDomainStateActive {
return "", "", caos_errs.ThrowNotFound(nil, "ORG-AGD31", "Errors.Org.DomainNotOnOrg")
return "", "", errors.ThrowNotFound(nil, "ORG-AGD31", "Errors.Org.DomainNotOnOrg")
}
if domainWriteModel.Verified {
return "", "", caos_errs.ThrowPreconditionFailed(nil, "ORG-HGw21", "Errors.Org.DomainAlreadyVerified")
return "", "", errors.ThrowPreconditionFailed(nil, "ORG-HGw21", "Errors.Org.DomainAlreadyVerified")
}
token, err = orgDomain.GenerateVerificationCode(c.domainVerificationGenerator)
if err != nil {
@ -58,7 +130,7 @@ func (c *Commands) GenerateOrgDomainValidation(ctx context.Context, orgDomain *d
}
url, err = http_utils.TokenUrl(orgDomain.Domain, token, checkType)
if err != nil {
return "", "", caos_errs.ThrowPreconditionFailed(err, "ORG-Bae21", "Errors.Org.DomainVerificationTypeInvalid")
return "", "", errors.ThrowPreconditionFailed(err, "ORG-Bae21", "Errors.Org.DomainVerificationTypeInvalid")
}
orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel)
@ -74,20 +146,20 @@ func (c *Commands) GenerateOrgDomainValidation(ctx context.Context, orgDomain *d
func (c *Commands) ValidateOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain, claimedUserIDs []string) (*domain.ObjectDetails, error) {
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
return nil, errors.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
}
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
if err != nil {
return nil, err
}
if domainWriteModel.State != domain.OrgDomainStateActive {
return nil, caos_errs.ThrowNotFound(nil, "ORG-Sjdi3", "Errors.Org.DomainNotOnOrg")
return nil, errors.ThrowNotFound(nil, "ORG-Sjdi3", "Errors.Org.DomainNotOnOrg")
}
if domainWriteModel.Verified {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-HGw21", "Errors.Org.DomainAlreadyVerified")
return nil, errors.ThrowPreconditionFailed(nil, "ORG-HGw21", "Errors.Org.DomainAlreadyVerified")
}
if domainWriteModel.ValidationCode == nil || domainWriteModel.ValidationType == domain.OrgDomainValidationTypeUnspecified {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-SFBB3", "Errors.Org.DomainVerificationMissing")
return nil, errors.ThrowPreconditionFailed(nil, "ORG-SFBB3", "Errors.Org.DomainVerificationMissing")
}
validationCode, err := crypto.DecryptString(domainWriteModel.ValidationCode, c.domainVerificationAlg)
@ -122,22 +194,22 @@ func (c *Commands) ValidateOrgDomain(ctx context.Context, orgDomain *domain.OrgD
events = append(events, org.NewDomainVerificationFailedEvent(ctx, orgAgg, orgDomain.Domain))
_, err = c.eventstore.Push(ctx, events...)
logging.LogWithFields("ORG-dhTE", "orgID", orgAgg.ID, "domain", orgDomain.Domain).OnError(err).Error("NewDomainVerificationFailedEvent push failed")
return nil, caos_errs.ThrowInvalidArgument(err, "ORG-GH3s", "Errors.Org.DomainVerificationFailed")
return nil, errors.ThrowInvalidArgument(err, "ORG-GH3s", "Errors.Org.DomainVerificationFailed")
}
func (c *Commands) SetPrimaryOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain) (*domain.ObjectDetails, error) {
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SsDG2", "Errors.Org.InvalidDomain")
return nil, errors.ThrowInvalidArgument(nil, "ORG-SsDG2", "Errors.Org.InvalidDomain")
}
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
if err != nil {
return nil, err
}
if domainWriteModel.State != domain.OrgDomainStateActive {
return nil, caos_errs.ThrowNotFound(nil, "ORG-GDfA3", "Errors.Org.DomainNotOnOrg")
return nil, errors.ThrowNotFound(nil, "ORG-GDfA3", "Errors.Org.DomainNotOnOrg")
}
if !domainWriteModel.Verified {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-Ggd32", "Errors.Org.DomainNotVerified")
return nil, errors.ThrowPreconditionFailed(nil, "ORG-Ggd32", "Errors.Org.DomainNotVerified")
}
orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, org.NewDomainPrimarySetEvent(ctx, orgAgg, orgDomain.Domain))
@ -153,17 +225,17 @@ func (c *Commands) SetPrimaryOrgDomain(ctx context.Context, orgDomain *domain.Or
func (c *Commands) RemoveOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain) (*domain.ObjectDetails, error) {
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SJsK3", "Errors.Org.InvalidDomain")
return nil, errors.ThrowInvalidArgument(nil, "ORG-SJsK3", "Errors.Org.InvalidDomain")
}
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
if err != nil {
return nil, err
}
if domainWriteModel.State != domain.OrgDomainStateActive {
return nil, caos_errs.ThrowNotFound(nil, "ORG-GDfA3", "Errors.Org.DomainNotOnOrg")
return nil, errors.ThrowNotFound(nil, "ORG-GDfA3", "Errors.Org.DomainNotOnOrg")
}
if domainWriteModel.Primary {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-Sjdi3", "Errors.Org.PrimaryDomainNotDeletable")
return nil, errors.ThrowPreconditionFailed(nil, "ORG-Sjdi3", "Errors.Org.PrimaryDomainNotDeletable")
}
orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, org.NewDomainRemovedEvent(ctx, orgAgg, orgDomain.Domain, domainWriteModel.Verified))
@ -183,7 +255,7 @@ func (c *Commands) addOrgDomain(ctx context.Context, orgAgg *eventstore.Aggregat
return nil, err
}
if addedDomain.State == domain.OrgDomainStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMA-Bd2jj", "Errors.Org.Domain.AlreadyExists")
return nil, errors.ThrowAlreadyExists(nil, "COMMA-Bd2jj", "Errors.Org.Domain.AlreadyExists")
}
events := []eventstore.Command{

View File

@ -9,9 +9,10 @@ import (
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
@ -21,6 +22,206 @@ import (
"github.com/caos/zitadel/internal/repository/user"
)
func TestAddDomain(t *testing.T) {
type args struct {
a *org.Aggregate
domain string
filter preparation.FilterToQueryReducer
}
agg := org.NewAggregate("test", "test")
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid domain",
args: args{
a: agg,
domain: "",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-r3h4J", "Errors.Invalid.Argument"),
},
},
{
name: "correct",
args: args{
a: agg,
domain: "domain",
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, nil
},
},
want: Want{
Commands: []eventstore.Command{
org.NewDomainAddedEvent(context.Background(), &agg.Aggregate, "domain"),
},
},
},
{
name: "already verified",
args: args{
a: agg,
domain: "domain",
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain"),
org.NewDomainVerificationAddedEvent(ctx, &agg.Aggregate, "domain", domain.OrgDomainValidationTypeHTTP, nil),
org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "domain"),
}, nil
},
},
want: Want{
CreateErr: errors.ThrowAlreadyExists(nil, "", ""),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t, AddOrgDomain(tt.args.a, tt.args.domain), tt.args.filter, tt.want)
})
}
}
func TestVerifyDomain(t *testing.T) {
type args struct {
a *org.Aggregate
domain string
}
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid domain",
args: args{
a: org.NewAggregate("test", "test"),
domain: "",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-yqlVQ", "Errors.Invalid.Argument"),
},
},
{
name: "correct",
args: args{
a: org.NewAggregate("test", "test"),
domain: "domain",
},
want: Want{
Commands: []eventstore.Command{
org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("test", "test").Aggregate, "domain"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t, VerifyOrgDomain(tt.args.a, tt.args.domain), nil, tt.want)
})
}
}
func TestSetDomainPrimary(t *testing.T) {
type args struct {
a *org.Aggregate
domain string
filter preparation.FilterToQueryReducer
}
agg := org.NewAggregate("test", "test")
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid domain",
args: args{
a: agg,
domain: "",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-gmNqY", "Errors.Invalid.Argument"),
},
},
{
name: "not exists",
args: args{
a: agg,
domain: "domain",
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, nil
},
},
want: Want{
CreateErr: errors.ThrowNotFound(nil, "", ""),
},
},
{
name: "not verified",
args: args{
a: agg,
domain: "domain",
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain")}, nil
},
},
want: Want{
CreateErr: errors.ThrowPreconditionFailed(nil, "", ""),
},
},
{
name: "already primary",
args: args{
a: agg,
domain: "domain",
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain"),
org.NewDomainVerificationAddedEvent(ctx, &agg.Aggregate, "domain", domain.OrgDomainValidationTypeHTTP, nil),
org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "domain"),
org.NewDomainPrimarySetEvent(ctx, &agg.Aggregate, "domain"),
}, nil
},
},
want: Want{
CreateErr: errors.ThrowPreconditionFailed(nil, "", ""),
},
},
{
name: "correct",
args: args{
a: agg,
domain: "domain",
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain"),
org.NewDomainVerificationAddedEvent(ctx, &agg.Aggregate, "domain", domain.OrgDomainValidationTypeHTTP, nil),
org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "domain"),
}, nil
},
},
want: Want{
Commands: []eventstore.Command{
org.NewDomainPrimarySetEvent(context.Background(), &agg.Aggregate, "domain"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t, SetPrimaryOrgDomain(tt.args.a, tt.args.domain), tt.args.filter, tt.want)
})
}
}
func TestCommandSide_AddOrgDomain(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
@ -52,7 +253,7 @@ func TestCommandSide_AddOrgDomain(t *testing.T) {
domain: &domain.OrgDomain{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -86,7 +287,7 @@ func TestCommandSide_AddOrgDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
err: errors.IsErrorAlreadyExists,
},
},
{
@ -187,7 +388,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -204,7 +405,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -224,7 +425,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -253,7 +454,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
},
},
res: res{
err: caos_errs.IsNotFound,
err: errors.IsNotFound,
},
},
{
@ -294,7 +495,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -462,7 +663,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -479,7 +680,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -508,7 +709,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsNotFound,
err: errors.IsNotFound,
},
},
{
@ -549,7 +750,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -584,7 +785,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -642,7 +843,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -920,7 +1121,7 @@ func TestCommandSide_SetPrimaryDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -937,7 +1138,7 @@ func TestCommandSide_SetPrimaryDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -966,7 +1167,7 @@ func TestCommandSide_SetPrimaryDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsNotFound,
err: errors.IsNotFound,
},
},
{
@ -1000,7 +1201,7 @@ func TestCommandSide_SetPrimaryDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -1107,7 +1308,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -1124,7 +1325,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -1153,7 +1354,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsNotFound,
err: errors.IsNotFound,
},
},
{
@ -1199,7 +1400,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -1318,7 +1519,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
}
func invalidDomainVerification(domain, token, verifier string, checkType http.CheckType) error {
return caos_errs.ThrowInvalidArgument(nil, "HTTP-GH422", "Errors.Internal")
return errors.ThrowInvalidArgument(nil, "HTTP-GH422", "Errors.Internal")
}
func validDomainVerification(domain, token, verifier string, checkType http.CheckType) error {

View File

@ -4,24 +4,84 @@ import (
"context"
"reflect"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
func (c *commandNew) AddOrgMember(a *org.Aggregate, userID string, roles ...string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if userID == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument")
}
if len(roles) == 0 {
return nil, errors.ThrowInvalidArgument(nil, "V2-PfYhb", "Errors.Invalid.Argument")
}
if len(domain.CheckForInvalidRoles(roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 && len(domain.CheckForInvalidRoles(roles, domain.RoleSelfManagementGlobal, c.zitadelRoles)) > 0 {
return nil, errors.ThrowInvalidArgument(nil, "Org-4N8es", "Errors.Org.MemberInvalid")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
if exists, err := ExistsUser(ctx, filter, userID, a.ID); err != nil || !exists {
return nil, errors.ThrowNotFound(err, "ORG-GoXOn", "Errors.User.NotFound")
}
if isMember, err := IsOrgMember(ctx, filter, a.ID, userID); err != nil || isMember {
return nil, errors.ThrowAlreadyExists(err, "ORG-poWwe", "Errors.Org.Member.AlreadyExists")
}
return []eventstore.Command{org.NewMemberAddedEvent(ctx, &a.Aggregate, userID, roles...)}, nil
},
nil
}
}
func IsOrgMember(ctx context.Context, filter preparation.FilterToQueryReducer, orgID, userID string) (isMember bool, err error) {
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(orgID).
OrderAsc().
AddQuery().
AggregateIDs(orgID).
AggregateTypes(org.AggregateType).
EventTypes(
org.MemberAddedEventType,
org.MemberRemovedEventType,
org.MemberCascadeRemovedEventType,
).Builder())
if err != nil {
return false, err
}
for _, event := range events {
switch e := event.(type) {
case *org.MemberAddedEvent:
if e.UserID == userID {
isMember = true
}
case *org.MemberRemovedEvent:
if e.UserID == userID {
isMember = false
}
case *org.MemberCascadeRemovedEvent:
if e.UserID == userID {
isMember = false
}
}
}
return isMember, nil
}
func (c *Commands) AddOrgMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
if member.UserID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-u8fkf", "Errors.Org.MemberInvalid")
return nil, errors.ThrowInvalidArgument(nil, "Org-u8fkf", "Errors.Org.MemberInvalid")
}
addedMember := NewOrgMemberWriteModel(member.AggregateID, member.UserID)
orgAgg := OrgAggregateFromWriteModel(&addedMember.WriteModel)
err := c.checkUserExists(ctx, addedMember.UserID, "")
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "Org-2H8ds", "Errors.User.NotFound")
return nil, errors.ThrowPreconditionFailed(err, "Org-2H8ds", "Errors.User.NotFound")
}
event, err := c.addOrgMember(ctx, orgAgg, addedMember, member)
if err != nil {
@ -40,10 +100,10 @@ func (c *Commands) AddOrgMember(ctx context.Context, member *domain.Member) (*do
func (c *Commands) addOrgMember(ctx context.Context, orgAgg *eventstore.Aggregate, addedMember *OrgMemberWriteModel, member *domain.Member) (eventstore.Command, error) {
if !member.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-W8m4l", "Errors.Org.MemberInvalid")
return nil, errors.ThrowInvalidArgument(nil, "Org-W8m4l", "Errors.Org.MemberInvalid")
}
if len(domain.CheckForInvalidRoles(member.Roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 && len(domain.CheckForInvalidRoles(member.Roles, domain.RoleSelfManagementGlobal, c.zitadelRoles)) > 0 {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-4N8es", "Errors.Org.MemberInvalid")
return nil, errors.ThrowInvalidArgument(nil, "Org-4N8es", "Errors.Org.MemberInvalid")
}
err := c.eventstore.FilterToQueryReducer(ctx, addedMember)
if err != nil {
@ -59,10 +119,10 @@ func (c *Commands) addOrgMember(ctx context.Context, orgAgg *eventstore.Aggregat
//ChangeOrgMember updates an existing member
func (c *Commands) ChangeOrgMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
if !member.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-LiaZi", "Errors.Org.MemberInvalid")
return nil, errors.ThrowInvalidArgument(nil, "Org-LiaZi", "Errors.Org.MemberInvalid")
}
if len(domain.CheckForInvalidRoles(member.Roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-m9fG8", "Errors.Org.MemberInvalid")
return nil, errors.ThrowInvalidArgument(nil, "IAM-m9fG8", "Errors.Org.MemberInvalid")
}
existingMember, err := c.orgMemberWriteModelByID(ctx, member.AggregateID, member.UserID)
@ -71,7 +131,7 @@ func (c *Commands) ChangeOrgMember(ctx context.Context, member *domain.Member) (
}
if reflect.DeepEqual(existingMember.Roles, member.Roles) {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-LiaZi", "Errors.Org.Member.RolesNotChanged")
return nil, errors.ThrowPreconditionFailed(nil, "Org-LiaZi", "Errors.Org.Member.RolesNotChanged")
}
orgAgg := OrgAggregateFromWriteModel(&existingMember.MemberWriteModel.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, org.NewMemberChangedEvent(ctx, orgAgg, member.UserID, member.Roles...))

View File

@ -8,8 +8,9 @@ import (
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
@ -19,6 +20,271 @@ import (
"github.com/caos/zitadel/internal/repository/user"
)
func TestAddMember(t *testing.T) {
type args struct {
a *org.Aggregate
userID string
roles []string
zitadelRoles []authz.RoleMapping
filter preparation.FilterToQueryReducer
}
ctx := context.Background()
agg := org.NewAggregate("test", "test")
tests := []struct {
name string
args args
want Want
}{
{
name: "no user id",
args: args{
a: agg,
userID: "",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument"),
},
},
{
name: "no roles",
args: args{
a: agg,
userID: "12342",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "V2-PfYhb", "Errors.Invalid.Argument"),
},
},
{
name: "TODO: invalid roles",
args: args{
a: agg,
userID: "123",
roles: []string{"ORG_OWNER"},
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "Org-4N8es", ""),
},
},
{
name: "user not exists",
args: args{
a: agg,
userID: "userID",
roles: []string{"ORG_OWNER"},
zitadelRoles: []authz.RoleMapping{
{
Role: "ORG_OWNER",
},
},
filter: NewMultiFilter().Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, nil
}).Filter(),
},
want: Want{
CreateErr: errors.ThrowNotFound(nil, "ORG-GoXOn", "Errors.User.NotFound"),
},
},
{
name: "already member",
args: args{
a: agg,
userID: "userID",
roles: []string{"ORG_OWNER"},
zitadelRoles: []authz.RoleMapping{
{
Role: "ORG_OWNER",
},
},
filter: NewMultiFilter().
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
user.NewMachineAddedEvent(
ctx,
&user.NewAggregate("id", "ro").Aggregate,
"userName",
"name",
"description",
true,
),
}, nil
}).
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewMemberAddedEvent(
ctx,
&org.NewAggregate("id", "ro").Aggregate,
"userID",
),
}, nil
}).
Filter(),
},
want: Want{
CreateErr: errors.ThrowAlreadyExists(nil, "ORG-poWwe", "Errors.Org.Member.AlreadyExists"),
},
},
{
name: "correct",
args: args{
a: agg,
userID: "userID",
roles: []string{"ORG_OWNER"},
zitadelRoles: []authz.RoleMapping{
{
Role: "ORG_OWNER",
},
},
filter: NewMultiFilter().
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
user.NewMachineAddedEvent(
ctx,
&user.NewAggregate("id", "ro").Aggregate,
"userName",
"name",
"description",
true,
),
}, nil
}).
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, nil
}).
Filter(),
},
want: Want{
Commands: []eventstore.Command{
org.NewMemberAddedEvent(ctx, &agg.Aggregate, "userID", "ORG_OWNER"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t, (&commandNew{zitadelRoles: tt.args.zitadelRoles}).AddOrgMember(tt.args.a, tt.args.userID, tt.args.roles...), tt.args.filter, tt.want)
})
}
}
func TestIsMember(t *testing.T) {
type args struct {
filter preparation.FilterToQueryReducer
orgID string
userID string
}
tests := []struct {
name string
args args
wantExists bool
wantErr bool
}{
{
name: "no events",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{}, nil
},
orgID: "orgID",
userID: "userID",
},
wantExists: false,
wantErr: false,
},
{
name: "member added",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewMemberAddedEvent(
context.Background(),
&org.NewAggregate("orgID", "ro").Aggregate,
"userID",
),
}, nil
},
orgID: "orgID",
userID: "userID",
},
wantExists: true,
wantErr: false,
},
{
name: "member removed",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewMemberAddedEvent(
context.Background(),
&org.NewAggregate("orgID", "ro").Aggregate,
"userID",
),
org.NewMemberRemovedEvent(
context.Background(),
&org.NewAggregate("orgID", "ro").Aggregate,
"userID",
),
}, nil
},
orgID: "orgID",
userID: "userID",
},
wantExists: false,
wantErr: false,
},
{
name: "member cascade removed",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewMemberAddedEvent(
context.Background(),
&org.NewAggregate("orgID", "ro").Aggregate,
"userID",
),
org.NewMemberCascadeRemovedEvent(
context.Background(),
&org.NewAggregate("orgID", "ro").Aggregate,
"userID",
),
}, nil
},
orgID: "orgID",
userID: "userID",
},
wantExists: false,
wantErr: false,
},
{
name: "error durring filter",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, errors.ThrowInternal(nil, "PROJE-Op26p", "Errors.Internal")
},
orgID: "orgID",
userID: "userID",
},
wantExists: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotExists, err := IsOrgMember(context.Background(), tt.args.filter, tt.args.orgID, tt.args.userID)
if (err != nil) != tt.wantErr {
t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotExists != tt.wantExists {
t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
}
})
}
}
func TestCommandSide_AddOrgMember(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
@ -54,7 +320,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -76,7 +342,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -113,7 +379,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -163,7 +429,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
err: errors.IsErrorAlreadyExists,
},
},
{
@ -188,7 +454,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
),
),
expectFilter(),
expectPushFailed(caos_errs.ThrowAlreadyExists(nil, "ERROR", "internal"),
expectPushFailed(errors.ThrowAlreadyExists(nil, "ERROR", "internal"),
[]*repository.Event{
eventFromEventPusher(org.NewMemberAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
@ -216,7 +482,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
err: errors.IsErrorAlreadyExists,
},
},
{
@ -335,7 +601,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -356,7 +622,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -383,7 +649,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
},
},
res: res{
err: caos_errs.IsNotFound,
err: errors.IsNotFound,
},
},
{
@ -418,7 +684,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -530,7 +796,7 @@ func TestCommandSide_RemoveOrgMember(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -547,7 +813,7 @@ func TestCommandSide_RemoveOrgMember(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{

View File

@ -9,7 +9,7 @@ import (
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
@ -20,6 +20,53 @@ import (
"github.com/caos/zitadel/internal/repository/user"
)
func TestAddOrg(t *testing.T) {
type args struct {
a *org.Aggregate
name string
}
ctx := context.Background()
agg := org.NewAggregate("test", "test")
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid domain",
args: args{
a: agg,
name: "",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-mruNY", "Errors.Invalid.Argument"),
},
},
{
name: "correct",
args: args{
a: agg,
name: "caos ag",
},
want: Want{
Commands: []eventstore.Command{
org.NewOrgAddedEvent(ctx, &agg.Aggregate, "caos ag"),
org.NewDomainAddedEvent(ctx, &agg.Aggregate, "caos-ag.localhost"),
org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "caos-ag.localhost"),
org.NewDomainPrimarySetEvent(ctx, &agg.Aggregate, "caos-ag.localhost"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t, AddOrgCommand(authz.WithRequestedDomain(context.Background(), "localhost"), tt.args.a, tt.args.name), nil, tt.want)
})
}
}
func TestCommandSide_AddOrg(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
@ -57,7 +104,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -101,7 +148,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -127,7 +174,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
),
),
expectFilterOrgMemberNotFound(),
expectPushFailed(caos_errs.ThrowAlreadyExists(nil, "id", "internal"),
expectPushFailed(errors.ThrowAlreadyExists(nil, "id", "internal"),
[]*repository.Event{
eventFromEventPusher(org.NewOrgAddedEvent(
context.Background(),
@ -170,7 +217,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
err: errors.IsErrorAlreadyExists,
},
},
{
@ -196,7 +243,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
),
),
expectFilterOrgMemberNotFound(),
expectPushFailed(caos_errs.ThrowInternal(nil, "id", "internal"),
expectPushFailed(errors.ThrowInternal(nil, "id", "internal"),
[]*repository.Event{
eventFromEventPusher(org.NewOrgAddedEvent(
context.Background(),
@ -239,7 +286,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsInternal,
err: errors.IsInternal,
},
},
{
@ -374,7 +421,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
orgID: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -391,7 +438,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
name: "org",
},
res: res{
err: caos_errs.IsNotFound,
err: errors.IsNotFound,
},
},
{
@ -409,7 +456,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
),
expectFilter(),
expectPushFailed(
caos_errs.ThrowInternal(nil, "id", "message"),
errors.ThrowInternal(nil, "id", "message"),
[]*repository.Event{
eventFromEventPusher(org.NewOrgChangedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate, "org", "neworg")),
@ -425,7 +472,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
name: "neworg",
},
res: res{
err: caos_errs.IsInternal,
err: errors.IsInternal,
},
},
{
@ -596,7 +643,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
orgID: "org1",
},
res: res{
err: caos_errs.IsNotFound,
err: errors.IsNotFound,
},
},
{
@ -622,7 +669,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
orgID: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -638,7 +685,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
),
),
expectPushFailed(
caos_errs.ThrowInternal(nil, "id", "message"),
errors.ThrowInternal(nil, "id", "message"),
[]*repository.Event{
eventFromEventPusher(org.NewOrgDeactivatedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate)),
@ -651,7 +698,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
orgID: "org1",
},
res: res{
err: caos_errs.IsInternal,
err: errors.IsInternal,
},
},
{
@ -731,7 +778,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
orgID: "org1",
},
res: res{
err: caos_errs.IsNotFound,
err: errors.IsNotFound,
},
},
{
@ -753,7 +800,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
orgID: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -774,7 +821,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
),
),
expectPushFailed(
caos_errs.ThrowInternal(nil, "id", "message"),
errors.ThrowInternal(nil, "id", "message"),
[]*repository.Event{
eventFromEventPusher(org.NewOrgReactivatedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
@ -788,7 +835,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
orgID: "org1",
},
res: res{
err: caos_errs.IsInternal,
err: errors.IsInternal,
},
},
{

33
internal/command/phone.go Normal file
View File

@ -0,0 +1,33 @@
package command
import (
"context"
"time"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/ttacon/libphonenumber"
)
type Phone struct {
Number string
Verified bool
}
func FormatPhoneNumber(number string) (string, error) {
if number == "" {
return "", nil
}
phoneNr, err := libphonenumber.Parse(number, libphonenumber.UNKNOWN_REGION)
if err != nil {
return "", errors.ThrowInvalidArgument(nil, "EVENT-so0wa", "Errors.User.Phone.Invalid")
}
number = libphonenumber.Format(phoneNr, libphonenumber.E164)
return number, nil
}
func newPhoneCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (value *crypto.CryptoValue, expiry time.Duration, err error) {
return newCryptoCodeWithExpiry(ctx, filter, domain.SecretGeneratorTypeVerifyPhoneCode, alg)
}

View File

@ -0,0 +1,57 @@
package command
import (
"testing"
"github.com/caos/zitadel/internal/errors"
)
func TestFormatPhoneNumber(t *testing.T) {
type args struct {
number string
}
tests := []struct {
name string
args args
result *Phone
errFunc func(err error) bool
}{
{
name: "invalid phone number",
args: args{
number: "PhoneNumber",
},
errFunc: errors.IsErrorInvalidArgument,
},
{
name: "format phone +4171 xxx xx xx",
args: args{
number: "+4171 123 45 67",
},
result: &Phone{
Number: "+41711234567",
},
},
{
name: "format non swiss phone +4371 xxx xx xx",
args: args{
number: "+4371 123 45 67",
},
result: &Phone{
Number: "+43711234567",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
formatted, err := FormatPhoneNumber(tt.args.number)
if tt.errFunc == nil && tt.result.Number != formatted {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.args.number, formatted)
}
if tt.errFunc != nil && !tt.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@ -7,7 +7,7 @@ import (
"reflect"
"testing"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/eventstore"
)
@ -18,13 +18,17 @@ type Want struct {
Commands []eventstore.Command
}
type CommandVerifier interface {
Validate(eventstore.Command) bool
}
//AssertValidation checks if the validation works as inteded
func AssertValidation(t *testing.T, validation preparation.Validation, filter preparation.FilterToQueryReducer, want Want) {
t.Helper()
creates, err := validation()
if !errors.Is(err, want.ValidationErr) {
t.Errorf("wrong validation err = %v, want %v", err, want.ValidationErr)
t.Errorf("wrong validation err = (%[1]T): %[1]v, want (%[2]T): %[2]v", err, want.ValidationErr)
return
}
if err != nil {
@ -32,7 +36,7 @@ func AssertValidation(t *testing.T, validation preparation.Validation, filter pr
}
cmds, err := creates(context.Background(), filter)
if !errors.Is(err, want.CreateErr) {
t.Errorf("wrong create err = %v, want %v", err, want.CreateErr)
t.Errorf("wrong create err = (%[1]T): %[1]v, want (%[2]T): %[2]v", err, want.CreateErr)
return
}
if err != nil {
@ -45,6 +49,12 @@ func AssertValidation(t *testing.T, validation preparation.Validation, filter pr
}
for i, cmd := range want.Commands {
if v, ok := cmd.(CommandVerifier); ok {
if verified := v.Validate(cmds[i]); !verified {
t.Errorf("verification failed on command: = %v, want %v", cmds[i], cmd)
}
continue
}
if !reflect.DeepEqual(cmd, cmds[i]) {
t.Errorf("unexpected command: = %v, want %v", cmds[i], cmd)
}

View File

@ -2,9 +2,12 @@ package command
import (
"context"
"strings"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/project"
@ -52,6 +55,57 @@ func (c *Commands) addProject(ctx context.Context, projectAdd *domain.Project, r
return events, addedProject, nil
}
func AddProjectCommand(
a *project.Aggregate,
name string,
owner string,
projectRoleAssertion bool,
projectRoleCheck bool,
hasProjectCheck bool,
privateLabelingSetting domain.PrivateLabelingSetting,
) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if name = strings.TrimSpace(name); name == "" {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-C01yo", "Errors.Invalid.Argument")
}
if !privateLabelingSetting.Valid() {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-AO52V", "Errors.Invalid.Argument")
}
if owner == "" {
return nil, errors.ThrowPreconditionFailed(nil, "PROJE-hzxwo", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
return []eventstore.Command{
project.NewProjectAddedEvent(ctx, &a.Aggregate,
name,
projectRoleAssertion,
projectRoleCheck,
hasProjectCheck,
privateLabelingSetting,
),
project.NewProjectMemberAddedEvent(ctx, &a.Aggregate,
owner,
domain.RoleProjectOwner),
}, nil
}, nil
}
}
func projectWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, projectID, resourceOwner string) (project *ProjectWriteModel, err error) {
project = NewProjectWriteModel(projectID, resourceOwner)
events, err := filter(ctx, project.Query())
if err != nil {
return nil, err
}
project.AppendEvents(events...)
if err := project.Reduce(); err != nil {
return nil, err
}
return project, nil
}
func (c *Commands) getProjectByID(ctx context.Context, projectID, resourceOwner string) (*domain.Project, error) {
projectWriteModel, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
if err != nil {

View File

@ -3,11 +3,23 @@ package command
import (
"context"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/repository/project"
)
type AddApp struct {
Aggregate project.Aggregate
ID string
Name string
}
func newAppClientSecret(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.HashAlgorithm) (value *crypto.CryptoValue, plain string, err error) {
return newCryptoCodeWithPlain(ctx, filter, domain.SecretGeneratorTypeAppSecret, alg)
}
func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appChange domain.Application, resourceOwner string) (*domain.ObjectDetails, error) {
if projectID == "" || appChange.GetAppID() == "" || appChange.GetApplicationName() == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.App.Invalid")

View File

@ -2,24 +2,84 @@ package command
import (
"context"
"strings"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/project"
"github.com/caos/zitadel/internal/id"
project_repo "github.com/caos/zitadel/internal/repository/project"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
type addAPIApp struct {
AddApp
AuthMethodType domain.APIAuthMethodType
ClientID string
ClientSecret *crypto.CryptoValue
ClientSecretPlain string
}
func AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashAlgorithm) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if app.ID == "" {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument")
}
if app.Name = strings.TrimSpace(app.Name); app.Name == "" {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-F7g21", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
project, err := projectWriteModel(ctx, filter, app.Aggregate.ID, app.Aggregate.ResourceOwner)
if err != nil || !project.State.Valid() {
return nil, errors.ThrowNotFound(err, "PROJE-Sf2gb", "Errors.Project.NotFound")
}
app.ClientID, err = domain.NewClientID(id.SonyFlakeGenerator, project.Name)
if err != nil {
return nil, errors.ThrowInternal(err, "V2-f0pgP", "Errors.Internal")
}
//requires client secret
// TODO(release blocking):we have to return the secret
if app.AuthMethodType == domain.APIAuthMethodTypeBasic {
app.ClientSecret, app.ClientSecretPlain, err = newAppClientSecret(ctx, filter, clientSecretAlg)
if err != nil {
return nil, err
}
}
return []eventstore.Command{
project_repo.NewApplicationAddedEvent(
ctx,
&app.Aggregate.Aggregate,
app.ID,
app.Name,
),
project_repo.NewAPIConfigAddedEvent(
ctx,
&app.Aggregate.Aggregate,
app.ID,
app.ClientID,
app.ClientSecret,
app.AuthMethodType,
),
}, nil
}, nil
}
}
func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.APIApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) {
if application == nil || application.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Application.Invalid")
return nil, errors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Application.Invalid")
}
project, err := c.getProjectByID(ctx, application.AggregateID, resourceOwner)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "PROJECT-9fnsf", "Errors.Project.NotFound")
return nil, errors.ThrowPreconditionFailed(err, "PROJECT-9fnsf", "Errors.Project.NotFound")
}
addedApplication := NewAPIApplicationWriteModel(application.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
@ -43,7 +103,7 @@ func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.AP
func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore.Aggregate, proj *domain.Project, apiAppApp *domain.APIApp, resourceOwner string, appSecretGenerator crypto.Generator) (events []eventstore.Command, stringPW string, err error) {
if !apiAppApp.IsValid() {
return nil, "", caos_errs.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Application.Invalid")
return nil, "", errors.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Application.Invalid")
}
apiAppApp.AppID, err = c.idGenerator.Next()
if err != nil {
@ -51,7 +111,7 @@ func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore
}
events = []eventstore.Command{
project.NewApplicationAddedEvent(ctx, projectAgg, apiAppApp.AppID, apiAppApp.AppName),
project_repo.NewApplicationAddedEvent(ctx, projectAgg, apiAppApp.AppID, apiAppApp.AppName),
}
var stringPw string
@ -63,7 +123,7 @@ func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore
if err != nil {
return nil, "", err
}
events = append(events, project.NewAPIConfigAddedEvent(ctx,
events = append(events, project_repo.NewAPIConfigAddedEvent(ctx,
projectAgg,
apiAppApp.AppID,
apiAppApp.ClientID,
@ -75,7 +135,7 @@ func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore
func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (*domain.APIApp, error) {
if apiApp.AppID == "" || apiApp.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-1m900", "Errors.Project.App.APIConfigInvalid")
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-1m900", "Errors.Project.App.APIConfigInvalid")
}
existingAPI, err := c.getAPIAppWriteModel(ctx, apiApp.AggregateID, apiApp.AppID, resourceOwner)
@ -83,10 +143,10 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA
return nil, err
}
if existingAPI.State == domain.AppStateUnspecified || existingAPI.State == domain.AppStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-2n8uU", "Errors.Project.App.NotExisting")
return nil, errors.ThrowNotFound(nil, "COMMAND-2n8uU", "Errors.Project.App.NotExisting")
}
if !existingAPI.IsAPI() {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Gnwt3", "Errors.Project.App.IsNotAPI")
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-Gnwt3", "Errors.Project.App.IsNotAPI")
}
projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel)
changedEvent, hasChanged, err := existingAPI.NewChangedEvent(
@ -98,7 +158,7 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA
return nil, err
}
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-1m88i", "Errors.NoChangesFound")
return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-1m88i", "Errors.NoChangesFound")
}
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
@ -115,7 +175,7 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA
func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string, appSecretGenerator crypto.Generator) (*domain.APIApp, error) {
if projectID == "" || appID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing")
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing")
}
existingAPI, err := c.getAPIAppWriteModel(ctx, projectID, appID, resourceOwner)
@ -123,10 +183,10 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap
return nil, err
}
if existingAPI.State == domain.AppStateUnspecified || existingAPI.State == domain.AppStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-2g66f", "Errors.Project.App.NotExisting")
return nil, errors.ThrowNotFound(nil, "COMMAND-2g66f", "Errors.Project.App.NotExisting")
}
if !existingAPI.IsAPI() {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-aeH4", "Errors.Project.App.IsNotAPI")
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-aeH4", "Errors.Project.App.IsNotAPI")
}
cryptoSecret, stringPW, err := domain.NewClientSecret(appSecretGenerator)
if err != nil {
@ -135,7 +195,7 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap
projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, project.NewAPIConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret))
pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret))
if err != nil {
return nil, err
}
@ -158,13 +218,13 @@ func (c *Commands) VerifyAPIClientSecret(ctx context.Context, projectID, appID,
return err
}
if !app.State.Exists() {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NoExisting")
return errors.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NoExisting")
}
if !app.IsAPI() {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Bf3fw", "Errors.Project.App.IsNotAPI")
return errors.ThrowInvalidArgument(nil, "COMMAND-Bf3fw", "Errors.Project.App.IsNotAPI")
}
if app.ClientSecret == nil {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-D3t5g", "Errors.Project.App.APIConfigInvalid")
return errors.ThrowPreconditionFailed(nil, "COMMAND-D3t5g", "Errors.Project.App.APIConfigInvalid")
}
projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel)
@ -172,12 +232,12 @@ func (c *Commands) VerifyAPIClientSecret(ctx context.Context, projectID, appID,
err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.userPasswordAlg)
spanPasswordComparison.EndWithError(err)
if err == nil {
_, err = c.eventstore.Push(ctx, project.NewAPIConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID))
_, err = c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID))
return err
}
_, err = c.eventstore.Push(ctx, project.NewAPIConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID))
_, err = c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID))
logging.Log("COMMAND-g3f12").OnError(err).Error("could not push event APIClientSecretCheckFailed")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid")
return errors.ThrowInvalidArgument(nil, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid")
}
func (c *Commands) getAPIAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (*APIApplicationWriteModel, error) {

View File

@ -4,9 +4,10 @@ import (
"context"
"testing"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
@ -16,6 +17,118 @@ import (
"github.com/stretchr/testify/assert"
)
func TestAddAPIConfig(t *testing.T) {
type args struct {
a *project.Aggregate
appID string
name string
filter preparation.FilterToQueryReducer
}
ctx := context.Background()
agg := project.NewAggregate("test", "test")
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid appID",
args: args{
a: agg,
appID: "",
name: "name",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument"),
},
},
{
name: "invalid name",
args: args{
a: agg,
appID: "appID",
name: "",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-F7g21", "Errors.Invalid.Argument"),
},
},
{
name: "project not exists",
args: args{
a: agg,
appID: "id",
name: "name",
filter: NewMultiFilter().
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, nil
}).
Filter(),
},
want: Want{
CreateErr: errors.ThrowNotFound(nil, "PROJE-Sf2gb", "Errors.Project.NotFound"),
},
},
{
name: "correct without client secret",
args: args{
a: agg,
appID: "appID",
name: "name",
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,
"appID",
"name",
),
project.NewAPIConfigAddedEvent(ctx, &agg.Aggregate,
"appID",
"",
nil,
domain.APIAuthMethodTypePrivateKeyJWT,
),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t,
AddAPIAppCommand(
&addAPIApp{
AddApp: AddApp{
Aggregate: *tt.args.a,
ID: tt.args.appID,
Name: tt.args.name,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
nil,
), tt.args.filter, tt.want)
})
}
}
func TestCommandSide_AddAPIApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
@ -50,7 +163,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -73,7 +186,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -103,7 +216,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -295,7 +408,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -318,7 +431,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -341,7 +454,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
err: errors.IsNotFound,
},
},
{
@ -381,7 +494,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -504,7 +617,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -521,7 +634,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -539,7 +652,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
err: errors.IsNotFound,
},
},
{

View File

@ -2,17 +2,121 @@ package command
import (
"context"
"strings"
"time"
"github.com/caos/logging"
http_util "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/project"
"github.com/caos/zitadel/internal/id"
project_repo "github.com/caos/zitadel/internal/repository/project"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
type addOIDCApp struct {
AddApp
Version domain.OIDCVersion
RedirectUris []string
ResponseTypes []domain.OIDCResponseType
GrantTypes []domain.OIDCGrantType
ApplicationType domain.OIDCApplicationType
AuthMethodType domain.OIDCAuthMethodType
PostLogoutRedirectUris []string
DevMode bool
AccessTokenType domain.OIDCTokenType
AccessTokenRoleAssertion bool
IDTokenRoleAssertion bool
IDTokenUserinfoAssertion bool
ClockSkew time.Duration
AdditionalOrigins []string
ClientID string
ClientSecret *crypto.CryptoValue
ClientSecretPlain string
}
//AddOIDCAppCommand prepares the commands to add an oidc app. The ClientID will be set during the CreateCommands
func AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.HashAlgorithm) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if app.ID == "" {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument")
}
if app.Name = strings.TrimSpace(app.Name); app.Name == "" {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-Fef31", "Errors.Invalid.Argument")
}
if app.ClockSkew > time.Second*5 || app.ClockSkew < 0 {
return nil, errors.ThrowInvalidArgument(nil, "V2-PnCMS", "Errors.Invalid.Argument")
}
for _, origin := range app.AdditionalOrigins {
if !http_util.IsOrigin(origin) {
return nil, errors.ThrowInvalidArgument(nil, "V2-DqWPX", "Errors.Invalid.Argument")
}
}
if !domain.ContainsRequiredGrantTypes(app.ResponseTypes, app.GrantTypes) {
return nil, errors.ThrowInvalidArgument(nil, "V2-sLpW1", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) (_ []eventstore.Command, err error) {
project, err := projectWriteModel(ctx, filter, app.Aggregate.ID, app.Aggregate.ResourceOwner)
if err != nil || !project.State.Valid() {
return nil, errors.ThrowNotFound(err, "PROJE-6swVG", "Errors.Project.NotFound")
}
app.ClientID, err = domain.NewClientID(id.SonyFlakeGenerator, project.Name)
if err != nil {
return nil, errors.ThrowInternal(err, "V2-VMSQ1", "Errors.Internal")
}
if app.AuthMethodType == domain.OIDCAuthMethodTypeBasic || app.AuthMethodType == domain.OIDCAuthMethodTypePost {
app.ClientSecret, app.ClientSecretPlain, err = newAppClientSecret(ctx, filter, clientSecretAlg)
if err != nil {
return nil, err
}
}
return []eventstore.Command{
project_repo.NewApplicationAddedEvent(
ctx,
&app.Aggregate.Aggregate,
app.ID,
app.Name,
),
project_repo.NewOIDCConfigAddedEvent(
ctx,
&app.Aggregate.Aggregate,
app.Version,
app.ID,
app.ClientID,
app.ClientSecret,
app.RedirectUris,
app.ResponseTypes,
app.GrantTypes,
app.ApplicationType,
app.AuthMethodType,
app.PostLogoutRedirectUris,
app.DevMode,
app.AccessTokenType,
app.AccessTokenRoleAssertion,
app.IDTokenRoleAssertion,
app.IDTokenUserinfoAssertion,
app.ClockSkew,
app.AdditionalOrigins,
),
}, nil
}, nil
}
}
func (c *Commands) AddOIDCApplication(ctx context.Context, application *domain.OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) {
if application == nil || application.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Application.Invalid")
@ -52,7 +156,7 @@ func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstor
}
events = []eventstore.Command{
project.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName),
project_repo.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName),
}
var stringPw string
@ -64,7 +168,7 @@ func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstor
if err != nil {
return nil, "", err
}
events = append(events, project.NewOIDCConfigAddedEvent(ctx,
events = append(events, project_repo.NewOIDCConfigAddedEvent(ctx,
projectAgg,
oidcApp.OIDCVersion,
oidcApp.AppID,
@ -164,7 +268,7 @@ func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, a
projectAgg := ProjectAggregateFromWriteModel(&existingOIDC.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, project.NewOIDCConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret))
pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret))
if err != nil {
return nil, err
}
@ -201,10 +305,10 @@ func (c *Commands) VerifyOIDCClientSecret(ctx context.Context, projectID, appID,
err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.userPasswordAlg)
spanPasswordComparison.EndWithError(err)
if err == nil {
_, err = c.eventstore.Push(ctx, project.NewOIDCConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID))
_, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID))
return err
}
_, err = c.eventstore.Push(ctx, project.NewOIDCConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID))
_, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID))
logging.Log("COMMAND-ADfhz").OnError(err).Error("could not push event OIDCClientSecretCheckFailed")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid")
}

View File

@ -7,9 +7,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
@ -18,6 +19,162 @@ import (
"github.com/caos/zitadel/internal/repository/project"
)
func TestAddOIDCApp(t *testing.T) {
type args struct {
app *addOIDCApp
clientSecretAlg crypto.HashAlgorithm
filter preparation.FilterToQueryReducer
}
ctx := context.Background()
agg := project.NewAggregate("test", "test")
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid appID",
args: args{
app: &addOIDCApp{
AddApp: AddApp{
Aggregate: *agg,
ID: "",
Name: "name",
},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
Version: domain.OIDCVersionV1,
ApplicationType: domain.OIDCApplicationTypeWeb,
AuthMethodType: domain.OIDCAuthMethodTypeNone,
AccessTokenType: domain.OIDCTokenTypeBearer,
},
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument"),
},
},
{
name: "invalid name",
args: args{
app: &addOIDCApp{
AddApp: AddApp{
Aggregate: *agg,
ID: "id",
Name: "",
},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
Version: domain.OIDCVersionV1,
ApplicationType: domain.OIDCApplicationTypeWeb,
AuthMethodType: domain.OIDCAuthMethodTypeNone,
AccessTokenType: domain.OIDCTokenTypeBearer,
},
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-Fef31", "Errors.Invalid.Argument"),
},
},
{
name: "project not exists",
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.OIDCAuthMethodTypeNone,
AccessTokenType: domain.OIDCTokenTypeBearer,
},
filter: NewMultiFilter().
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, nil
}).
Filter(),
},
want: Want{
CreateErr: errors.ThrowNotFound(nil, "PROJE-6swVG", ""),
},
},
{
name: "correct",
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.OIDCAuthMethodTypeNone,
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",
"",
nil,
nil,
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypeNone,
nil,
false,
domain.OIDCTokenTypeBearer,
false,
false,
false,
0,
nil,
),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t,
AddOIDCAppCommand(
tt.args.app,
tt.args.clientSecretAlg,
), tt.args.filter, tt.want)
})
}
}
func TestCommandSide_AddOIDCApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
@ -52,7 +209,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -75,7 +232,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -105,7 +262,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -274,7 +431,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -298,7 +455,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -322,7 +479,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -347,7 +504,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
err: errors.IsNotFound,
},
},
{
@ -418,7 +575,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: errors.IsPreconditionFailed,
},
},
{
@ -580,7 +737,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -597,7 +754,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: errors.IsErrorInvalidArgument,
},
},
{
@ -615,7 +772,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
err: errors.IsNotFound,
},
},
{

View File

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
@ -985,3 +986,186 @@ func newProjectChangedEvent(ctx context.Context, projectID, resourceOwner, oldNa
)
return event
}
func TestAddProject(t *testing.T) {
type args struct {
a *project.Aggregate
name string
owner string
privateLabelingSetting domain.PrivateLabelingSetting
}
ctx := context.Background()
agg := project.NewAggregate("test", "test")
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid name",
args: args{
a: agg,
name: "",
owner: "owner",
privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-C01yo", "Errors.Invalid.Argument"),
},
},
{
name: "invalid private labeling setting",
args: args{
a: agg,
name: "name",
owner: "owner",
privateLabelingSetting: -1,
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-AO52V", "Errors.Invalid.Argument"),
},
},
{
name: "invalid owner",
args: args{
a: agg,
name: "name",
owner: "",
privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
},
want: Want{
ValidationErr: errors.ThrowPreconditionFailed(nil, "PROJE-hzxwo", "Errors.Invalid.Argument"),
},
},
{
name: "correct",
args: args{
a: agg,
name: "ZITADEL",
owner: "CAOS AG",
privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
},
want: Want{
Commands: []eventstore.Command{
project.NewProjectAddedEvent(ctx, &agg.Aggregate,
"ZITADEL",
false,
false,
false,
domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
),
project.NewProjectMemberAddedEvent(ctx, &agg.Aggregate,
"CAOS AG",
domain.RoleProjectOwner),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t, AddProjectCommand(tt.args.a, tt.args.name, tt.args.owner, false, false, false, tt.args.privateLabelingSetting), nil, tt.want)
})
}
}
// func TestExistsProject(t *testing.T) {
// type args struct {
// filter preparation.FilterToQueryReducer
// id string
// resourceOwner string
// }
// tests := []struct {
// name string
// args args
// wantExists bool
// wantErr bool
// }{
// {
// name: "no events",
// args: args{
// filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
// return []eventstore.Event{}, nil
// },
// id: "id",
// resourceOwner: "ro",
// },
// wantExists: false,
// wantErr: false,
// },
// {
// name: "project added",
// args: args{
// filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
// return []eventstore.Event{
// project.NewProjectAddedEvent(
// context.Background(),
// &project.NewAggregate("id", "ro").Aggregate,
// "name",
// false,
// false,
// false,
// domain.PrivateLabelingSettingEnforceProjectResourceOwnerPolicy,
// ),
// }, nil
// },
// id: "id",
// resourceOwner: "ro",
// },
// wantExists: true,
// wantErr: false,
// },
// {
// name: "project removed",
// args: args{
// filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
// return []eventstore.Event{
// project.NewProjectAddedEvent(
// context.Background(),
// &project.NewAggregate("id", "ro").Aggregate,
// "name",
// false,
// false,
// false,
// domain.PrivateLabelingSettingEnforceProjectResourceOwnerPolicy,
// ),
// project.NewProjectRemovedEvent(
// context.Background(),
// &project.NewAggregate("id", "ro").Aggregate,
// "name",
// ),
// }, nil
// },
// id: "id",
// resourceOwner: "ro",
// },
// wantExists: false,
// wantErr: false,
// },
// {
// name: "error durring filter",
// args: args{
// filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
// return nil, errors.ThrowInternal(nil, "PROJE-Op26p", "Errors.Internal")
// },
// id: "id",
// resourceOwner: "ro",
// },
// wantExists: false,
// wantErr: true,
// },
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// gotExists, err := projectWriteModel(context.Background(), tt.args.filter, tt.args.id, tt.args.resourceOwner)
// if (err != nil) != tt.wantErr {
// t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
// return
// }
// if gotExists != tt.wantExists {
// t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
// }
// })
// }
// }

View File

@ -0,0 +1,73 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
)
func (c *commandNew) AddSecretGeneratorConfig(ctx context.Context, typ domain.SecretGeneratorType, config *crypto.GeneratorConfig) (*domain.ObjectDetails, error) {
agg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
cmds, err := preparation.PrepareCommands(ctx, c.es.Filter, addSecretGeneratorConfig(agg, typ, config))
if err != nil {
return nil, err
}
events, err := c.es.Push(ctx, cmds...)
if err != nil {
return nil, err
}
return &domain.ObjectDetails{
Sequence: events[len(events)-1].Sequence(),
EventDate: events[len(events)-1].CreationDate(),
ResourceOwner: agg.ResourceOwner,
}, nil
}
//AddOrg defines the commands to create a new org,
// this includes the verified default domain
func addSecretGeneratorConfig(a *instance.Aggregate, typ domain.SecretGeneratorType, config *crypto.GeneratorConfig) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if !typ.Valid() {
return nil, errors.ThrowInvalidArgument(nil, "V2-FGqVj", "Errors.InvalidArgument")
}
if config.Length < 1 {
return nil, errors.ThrowInvalidArgument(nil, "V2-jEqCt", "Errors.InvalidArgument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
writeModel := NewInstanceSecretGeneratorConfigWriteModel(ctx, typ)
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
}
writeModel.AppendEvents(events...)
if err = writeModel.Reduce(); err != nil {
return nil, err
}
if writeModel.State == domain.SecretGeneratorStateActive {
return nil, errors.ThrowAlreadyExists(nil, "V2-6CqKo", "Errors.SecretGenerator.AlreadyExists")
}
return []eventstore.Command{
instance.NewSecretGeneratorAddedEvent(
ctx,
&a.Aggregate,
typ,
config.Length,
config.Expiry,
config.IncludeLowerLetters,
config.IncludeUpperLetters,
config.IncludeDigits,
config.IncludeSymbols,
),
}, nil
}, nil
}
}

View File

@ -5,15 +5,15 @@ import (
"fmt"
"time"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/internal/repository/user"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
@ -378,3 +378,38 @@ func (c *Commands) userWriteModelByID(ctx context.Context, userID, resourceOwner
}
return writeModel, nil
}
func ExistsUser(ctx context.Context, filter preparation.FilterToQueryReducer, id, resourceOwner string) (exists bool, err error) {
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(resourceOwner).
OrderAsc().
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(id).
EventTypes(
user.HumanRegisteredType,
user.UserV1RegisteredType,
user.HumanAddedType,
user.UserV1AddedType,
user.MachineAddedEventType,
user.UserRemovedType,
).Builder())
if err != nil {
return false, err
}
for _, event := range events {
switch event.(type) {
case *user.HumanRegisteredEvent, *user.HumanAddedEvent, *user.MachineAddedEvent:
exists = true
case *user.UserRemovedEvent:
exists = false
}
}
return exists, nil
}
func newUserInitCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (value *crypto.CryptoValue, expiry time.Duration, err error) {
return newCryptoCodeWithExpiry(ctx, filter, domain.SecretGeneratorTypeInitCode, alg)
}

View File

@ -4,12 +4,11 @@ import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/errors"
)
func domainPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) {
func domainPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (*PolicyDomainWriteModel, error) {
wm, err := orgDomainPolicy(ctx, filter)
if err != nil || wm != nil && wm.State.Exists() {
return wm, err
@ -21,8 +20,8 @@ func domainPolicyWriteModel(ctx context.Context, filter preparation.FilterToQuer
return nil, errors.ThrowInternal(nil, "USER-Ggk9n", "Errors.Internal")
}
func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) {
policy := command.NewOrgDomainPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*PolicyDomainWriteModel, error) {
policy := NewOrgDomainPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
events, err := filter(ctx, policy.Query())
if err != nil {
return nil, err
@ -35,8 +34,8 @@ func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReduce
return &policy.PolicyDomainWriteModel, err
}
func instanceDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) {
policy := command.NewInstanceDomainPolicyWriteModel(ctx)
func instanceDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*PolicyDomainWriteModel, error) {
policy := NewInstanceDomainPolicyWriteModel(ctx)
events, err := filter(ctx, policy.Query())
if err != nil {
return nil, err

View File

@ -6,8 +6,7 @@ import (
"testing"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
@ -22,7 +21,7 @@ func Test_customDomainPolicy(t *testing.T) {
tests := []struct {
name string
args args
want *command.PolicyDomainWriteModel
want *PolicyDomainWriteModel
wantErr bool
}{
{
@ -58,7 +57,7 @@ func Test_customDomainPolicy(t *testing.T) {
}, nil
},
},
want: &command.PolicyDomainWriteModel{
want: &PolicyDomainWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: "id",
ResourceOwner: "ro",
@ -91,7 +90,7 @@ func Test_defaultDomainPolicy(t *testing.T) {
tests := []struct {
name string
args args
want *command.PolicyDomainWriteModel
want *PolicyDomainWriteModel
wantErr bool
}{
{
@ -127,7 +126,7 @@ func Test_defaultDomainPolicy(t *testing.T) {
}, nil
},
},
want: &command.PolicyDomainWriteModel{
want: &PolicyDomainWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: "INSTANCE",
ResourceOwner: "INSTANCE",
@ -160,7 +159,7 @@ func Test_DomainPolicy(t *testing.T) {
tests := []struct {
name string
args args
want *command.PolicyDomainWriteModel
want *PolicyDomainWriteModel
wantErr bool
}{
{
@ -186,7 +185,7 @@ func Test_DomainPolicy(t *testing.T) {
}, nil
},
},
want: &command.PolicyDomainWriteModel{
want: &PolicyDomainWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: "id",
ResourceOwner: "ro",
@ -230,7 +229,7 @@ func Test_DomainPolicy(t *testing.T) {
}).
Filter(),
},
want: &command.PolicyDomainWriteModel{
want: &PolicyDomainWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: "INSTANCE",
ResourceOwner: "INSTANCE",

View File

@ -4,13 +4,14 @@ import (
"context"
"strings"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/user"
"golang.org/x/text/language"
)
func (c *Commands) getHuman(ctx context.Context, userID, resourceowner string) (*domain.Human, error) {
@ -19,51 +20,258 @@ func (c *Commands) getHuman(ctx context.Context, userID, resourceowner string) (
return nil, err
}
if !isUserStateExists(human.UserState) {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-M9dsd", "Errors.User.NotFound")
return nil, errors.ThrowNotFound(nil, "COMMAND-M9dsd", "Errors.User.NotFound")
}
return writeModelToHuman(human), nil
}
func (c *Commands) AddHuman(ctx context.Context, orgID string, human *domain.Human, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) (*domain.Human, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-XYFk9", "Errors.ResourceOwnerMissing")
type AddHuman struct {
// Username is required
Username string
// FirstName is required
FirstName string
// LastName is required
LastName string
// NickName is required
NickName string
// DisplayName is required
DisplayName string
// Email is required
Email Email
// PreferredLang is required
PreferredLang language.Tag
// Gender is required
Gender domain.Gender
//Phone represents an international phone number
Phone Phone
//Password is optional
Password string
//PasswordChangeRequired is used if the `Password`-field is set
PasswordChangeRequired bool
Passwordless bool
ExternalIDP bool
Register bool
}
func (c *Commands) AddHuman(ctx context.Context, resourceOwner string, human *AddHuman) (*domain.HumanDetails, error) {
return c.v2.AddHuman(ctx, resourceOwner, human)
}
func (c *commandNew) AddHuman(ctx context.Context, resourceOwner string, human *AddHuman) (*domain.HumanDetails, error) {
if resourceOwner == "" {
return nil, errors.ThrowInvalidArgument(nil, "COMMA-5Ky74", "Errors.Internal")
}
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Org.DomainPolicy.NotFound")
}
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexityPolicy.NotFound")
}
events, addedHuman, err := c.addHuman(ctx, orgID, human, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
userID, err := c.id.Next()
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, events...)
agg := user.NewAggregate(userID, resourceOwner)
cmds, err := preparation.PrepareCommands(ctx, c.es.Filter, addHumanCommand(agg, human, c.userPasswordAlg, c.phoneAlg, c.emailAlg, c.initCodeAlg))
if err != nil {
return nil, err
}
err = AppendAndReduce(addedHuman, pushedEvents...)
events, err := c.es.Push(ctx, cmds...)
if err != nil {
return nil, err
}
return writeModelToHuman(addedHuman), nil
return &domain.HumanDetails{
ID: userID,
ObjectDetails: domain.ObjectDetails{
Sequence: events[len(events)-1].Sequence(),
EventDate: events[len(events)-1].CreationDate(),
ResourceOwner: events[len(events)-1].Aggregate().ResourceOwner,
},
}, nil
}
type humanCreationCommand interface {
eventstore.Command
AddPhoneData(phoneNumber string)
AddPasswordData(secret *crypto.CryptoValue, changeRequired bool)
}
func addHumanCommand(a *user.Aggregate, human *AddHuman, passwordAlg crypto.HashAlgorithm, phoneAlg, emailAlg, initCodeAlg crypto.EncryptionAlgorithm) preparation.Validation {
return func() (_ preparation.CreateCommands, err error) {
if !human.Email.Valid() {
return nil, errors.ThrowInvalidArgument(nil, "USER-Ec7dM", "Errors.Invalid.Argument")
}
if human.Username = strings.TrimSpace(human.Username); human.Username == "" {
return nil, errors.ThrowInvalidArgument(nil, "V2-zzad3", "Errors.Invalid.Argument")
}
if human.FirstName = strings.TrimSpace(human.FirstName); human.FirstName == "" {
return nil, errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.Invalid.Argument")
}
if human.LastName = strings.TrimSpace(human.LastName); human.LastName == "" {
return nil, errors.ThrowInvalidArgument(nil, "USER-DiAq8", "Errors.Invalid.Argument")
}
human.ensureDisplayName()
if human.Phone.Number, err = FormatPhoneNumber(human.Phone.Number); err != nil {
return nil, errors.ThrowInvalidArgument(nil, "USER-tD6ax", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
domainPolicy, err := domainPolicyWriteModel(ctx, filter)
if err != nil {
return nil, err
}
if err = userValidateDomain(ctx, a, human.Username, domainPolicy.UserLoginMustBeDomain, filter); err != nil {
return nil, err
}
var createCmd humanCreationCommand
if human.Register {
createCmd = user.NewHumanRegisteredEvent(
ctx,
&a.Aggregate,
human.Username,
human.FirstName,
human.LastName,
human.NickName,
human.DisplayName,
human.PreferredLang,
human.Gender,
human.Email.Address,
domainPolicy.UserLoginMustBeDomain,
)
} else {
createCmd = user.NewHumanAddedEvent(
ctx,
&a.Aggregate,
human.Username,
human.FirstName,
human.LastName,
human.NickName,
human.DisplayName,
human.PreferredLang,
human.Gender,
human.Email.Address,
domainPolicy.UserLoginMustBeDomain,
)
}
if human.Phone.Number != "" {
createCmd.AddPhoneData(human.Phone.Number)
}
if human.Password != "" {
if err = humanValidatePassword(ctx, filter, human.Password); err != nil {
return nil, err
}
secret, err := crypto.Hash([]byte(human.Password), passwordAlg)
if err != nil {
return nil, err
}
createCmd.AddPasswordData(secret, human.PasswordChangeRequired)
}
cmds := make([]eventstore.Command, 0, 3)
cmds = append(cmds, createCmd)
//add init code if
// email not verified or
// user not registered and password set
if human.shouldAddInitCode() {
value, expiry, err := newUserInitCode(ctx, filter, initCodeAlg)
if err != nil {
return nil, err
}
cmds = append(cmds, user.NewHumanInitialCodeAddedEvent(ctx, &a.Aggregate, value, expiry))
}
if human.Email.Verified {
cmds = append(cmds, user.NewHumanEmailVerifiedEvent(ctx, &a.Aggregate))
} else {
value, expiry, err := newEmailCode(ctx, filter, emailAlg)
if err != nil {
return nil, err
}
cmds = append(cmds, user.NewHumanEmailCodeAddedEvent(ctx, &a.Aggregate, value, expiry))
}
if human.Phone.Verified {
cmds = append(cmds, user.NewHumanPhoneVerifiedEvent(ctx, &a.Aggregate))
} else if human.Phone.Number != "" {
value, expiry, err := newPhoneCode(ctx, filter, phoneAlg)
if err != nil {
return nil, err
}
cmds = append(cmds, user.NewHumanPhoneCodeAddedEvent(ctx, &a.Aggregate, value, expiry))
}
return cmds, nil
}, nil
}
}
func userValidateDomain(ctx context.Context, a *user.Aggregate, username string, mustBeDomain bool, filter preparation.FilterToQueryReducer) error {
if mustBeDomain {
return nil
}
usernameSplit := strings.Split(username, "@")
if len(usernameSplit) != 2 {
return errors.ThrowInvalidArgument(nil, "COMMAND-Dfd21", "Errors.User.Invalid")
}
domainCheck := NewOrgDomainVerifiedWriteModel(usernameSplit[1])
events, err := filter(ctx, domainCheck.Query())
if err != nil {
return err
}
domainCheck.AppendEvents(events...)
if err = domainCheck.Reduce(); err != nil {
return err
}
if domainCheck.Verified && domainCheck.ResourceOwner != a.ResourceOwner {
return errors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
}
return nil
}
func humanValidatePassword(ctx context.Context, filter preparation.FilterToQueryReducer, password string) error {
passwordComplexity, err := passwordComplexityPolicyWriteModel(ctx, filter)
if err != nil {
return err
}
return passwordComplexity.Validate(password)
}
func (h *AddHuman) ensureDisplayName() {
if strings.TrimSpace(h.DisplayName) != "" {
return
}
h.DisplayName = h.FirstName + " " + h.LastName
}
func (h *AddHuman) shouldAddInitCode() bool {
//user without idp
return !h.Email.Verified ||
//user with idp
!h.ExternalIDP &&
!h.Passwordless &&
h.Password != ""
}
func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, passwordlessCodeGenerator crypto.Generator) (_ *domain.Human, passwordlessCode *domain.PasswordlessInitCode, err error) {
if orgID == "" {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5N8fs", "Errors.ResourceOwnerMissing")
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-5N8fs", "Errors.ResourceOwnerMissing")
}
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
if err != nil {
return nil, nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-2N9fs", "Errors.Org.DomainPolicy.NotFound")
return nil, nil, errors.ThrowPreconditionFailed(err, "COMMAND-2N9fs", "Errors.Org.DomainPolicy.NotFound")
}
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
if err != nil {
return nil, nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-4N8gs", "Errors.Org.PasswordComplexityPolicy.NotFound")
return nil, nil, errors.ThrowPreconditionFailed(err, "COMMAND-4N8gs", "Errors.Org.PasswordComplexityPolicy.NotFound")
}
events, addedHuman, addedCode, code, err := c.importHuman(ctx, orgID, human, passwordless, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator)
if err != nil {
@ -89,53 +297,24 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
return writeModelToHuman(addedHuman), passwordlessCode, nil
}
func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
if orgID == "" || !human.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-67Ms8", "Errors.User.Invalid")
}
if human.Password != nil && human.SecretString != "" {
human.ChangeRequired = true
}
return c.createHuman(ctx, orgID, human, nil, false, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
}
func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) {
if orgID == "" || !human.IsValid() {
return nil, nil, nil, "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.User.Invalid")
}
events, humanWriteModel, err = c.createHuman(ctx, orgID, human, nil, false, passwordless, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
if err != nil {
return nil, nil, nil, "", err
}
if passwordless {
var codeEvent eventstore.Command
codeEvent, passwordlessCodeWriteModel, code, err = c.humanAddPasswordlessInitCode(ctx, human.AggregateID, orgID, true, passwordlessCodeGenerator)
if err != nil {
return nil, nil, nil, "", err
}
events = append(events, codeEvent)
}
return events, humanWriteModel, passwordlessCodeWriteModel, code, nil
}
func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgMemberRoles []string, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) (*domain.Human, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-GEdf2", "Errors.ResourceOwnerMissing")
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-GEdf2", "Errors.ResourceOwnerMissing")
}
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Org.DomainPolicy.NotFound")
return nil, errors.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Org.DomainPolicy.NotFound")
}
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexityPolicy.NotFound")
return nil, errors.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexityPolicy.NotFound")
}
loginPolicy, err := c.getOrgLoginPolicy(ctx, orgID)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-Dfg3g", "Errors.Org.LoginPolicy.NotFound")
return nil, errors.ThrowPreconditionFailed(err, "COMMAND-Dfg3g", "Errors.Org.LoginPolicy.NotFound")
}
if !loginPolicy.AllowRegister {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-SAbr3", "Errors.Org.LoginPolicy.RegistrationNotAllowed")
return nil, errors.ThrowPreconditionFailed(err, "COMMAND-SAbr3", "Errors.Org.LoginPolicy.RegistrationNotAllowed")
}
userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, link, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
if err != nil {
@ -171,12 +350,41 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai
return writeModelToHuman(registeredHuman), nil
}
func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
if orgID == "" || !human.IsValid() {
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-67Ms8", "Errors.User.Invalid")
}
if human.Password != nil && human.SecretString != "" {
human.ChangeRequired = true
}
return c.createHuman(ctx, orgID, human, nil, false, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
}
func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) {
if orgID == "" || !human.IsValid() {
return nil, nil, nil, "", errors.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.User.Invalid")
}
events, humanWriteModel, err = c.createHuman(ctx, orgID, human, nil, false, passwordless, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
if err != nil {
return nil, nil, nil, "", err
}
if passwordless {
var codeEvent eventstore.Command
codeEvent, passwordlessCodeWriteModel, code, err = c.humanAddPasswordlessInitCode(ctx, human.AggregateID, orgID, true, passwordlessCodeGenerator)
if err != nil {
return nil, nil, nil, "", err
}
events = append(events, codeEvent)
}
return events, humanWriteModel, passwordlessCodeWriteModel, code, nil
}
func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
if human != nil && human.Username == "" {
human.Username = human.EmailAddress
}
if orgID == "" || !human.IsValid() || link == nil && (human.Password == nil || human.SecretString == "") {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-9dk45", "Errors.User.Invalid")
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-9dk45", "Errors.User.Invalid")
}
if human.Password != nil && human.SecretString != "" {
human.ChangeRequired = false
@ -184,21 +392,21 @@ func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domai
return c.createHuman(ctx, orgID, human, link, true, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
}
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, selfregister, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, selfregister, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) (events []eventstore.Command, addedHuman *HumanWriteModel, err error) {
if err := human.CheckDomainPolicy(domainPolicy); err != nil {
return nil, nil, err
}
if !domainPolicy.UserLoginMustBeDomain {
usernameSplit := strings.Split(human.Username, "@")
if len(usernameSplit) != 2 {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Dfd21", "Errors.User.Invalid")
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-Dfd21", "Errors.User.Invalid")
}
domainCheck := NewOrgDomainVerifiedWriteModel(usernameSplit[1])
if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil {
return nil, nil, err
}
if domainCheck.Verified && domainCheck.ResourceOwner != orgID {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
}
}
userID, err := c.idGenerator.Next()
@ -213,10 +421,9 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
}
}
addedHuman := NewHumanWriteModel(human.AggregateID, orgID)
addedHuman = NewHumanWriteModel(human.AggregateID, orgID)
//TODO: adlerhurst maybe we could simplify the code below
userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel)
var events []eventstore.Command
if selfregister {
events = append(events, createRegisterHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
@ -259,7 +466,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
func (c *Commands) HumanSkipMFAInit(ctx context.Context, userID, resourceowner string) (err error) {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-2xpX9", "Errors.User.UserIDMissing")
return errors.ThrowInvalidArgument(nil, "COMMAND-2xpX9", "Errors.User.UserIDMissing")
}
existingHuman, err := c.getHumanWriteModelByID(ctx, userID, resourceowner)
@ -267,7 +474,7 @@ func (c *Commands) HumanSkipMFAInit(ctx context.Context, userID, resourceowner s
return err
}
if !isUserStateExists(existingHuman.UserState) {
return caos_errs.ThrowNotFound(nil, "COMMAND-m9cV8", "Errors.User.NotFound")
return errors.ThrowNotFound(nil, "COMMAND-m9cV8", "Errors.User.NotFound")
}
_, err = c.eventstore.Push(ctx,
@ -340,10 +547,10 @@ func createRegisterHumanEvent(ctx context.Context, aggregate *eventstore.Aggrega
func (c *Commands) HumansSignOut(ctx context.Context, agentID string, userIDs []string) error {
if agentID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
return errors.ThrowInvalidArgument(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
}
if len(userIDs) == 0 {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-M0od3", "Errors.User.UserIDMissing")
return errors.ThrowInvalidArgument(nil, "COMMAND-M0od3", "Errors.User.UserIDMissing")
}
events := make([]eventstore.Command, 0)
for _, userID := range userIDs {

View File

@ -71,7 +71,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
PhoneNumber: "0711234567",
PhoneNumber: "+41711234567",
},
resourceOwner: "org1",
},
@ -114,7 +114,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
PhoneNumber: "0711234567",
PhoneNumber: "+41711234567",
},
resourceOwner: "org1",
},
@ -172,7 +172,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
PhoneNumber: "0719876543",
PhoneNumber: "+41719876543",
IsPhoneVerified: true,
},
resourceOwner: "org1",
@ -239,7 +239,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
PhoneNumber: "0711234567",
PhoneNumber: "+41711234567",
},
resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t),

File diff suppressed because it is too large Load Diff

View File

@ -4,12 +4,11 @@ import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/errors"
)
func passwordComplexityPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) {
func passwordComplexityPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (*PasswordComplexityPolicyWriteModel, error) {
wm, err := customPasswordComplexityPolicy(ctx, filter)
if err != nil || wm != nil && wm.State.Exists() {
return wm, err
@ -21,8 +20,8 @@ func passwordComplexityPolicyWriteModel(ctx context.Context, filter preparation.
return nil, errors.ThrowInternal(nil, "USER-uQ96e", "Errors.Internal")
}
func customPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) {
policy := command.NewOrgPasswordComplexityPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
func customPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*PasswordComplexityPolicyWriteModel, error) {
policy := NewOrgPasswordComplexityPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
events, err := filter(ctx, policy.Query())
if err != nil {
return nil, err
@ -35,8 +34,8 @@ func customPasswordComplexityPolicy(ctx context.Context, filter preparation.Filt
return &policy.PasswordComplexityPolicyWriteModel, err
}
func defaultPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) {
policy := command.NewInstancePasswordComplexityPolicyWriteModel(ctx)
func defaultPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*PasswordComplexityPolicyWriteModel, error) {
policy := NewInstancePasswordComplexityPolicyWriteModel(ctx)
events, err := filter(ctx, policy.Query())
if err != nil {
return nil, err

View File

@ -6,8 +6,7 @@ import (
"testing"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
@ -22,7 +21,7 @@ func Test_customPasswordComplexityPolicy(t *testing.T) {
tests := []struct {
name string
args args
want *command.PasswordComplexityPolicyWriteModel
want *PasswordComplexityPolicyWriteModel
wantErr bool
}{
{
@ -62,7 +61,7 @@ func Test_customPasswordComplexityPolicy(t *testing.T) {
}, nil
},
},
want: &command.PasswordComplexityPolicyWriteModel{
want: &PasswordComplexityPolicyWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: "id",
ResourceOwner: "ro",
@ -99,7 +98,7 @@ func Test_defaultPasswordComplexityPolicy(t *testing.T) {
tests := []struct {
name string
args args
want *command.PasswordComplexityPolicyWriteModel
want *PasswordComplexityPolicyWriteModel
wantErr bool
}{
{
@ -139,7 +138,7 @@ func Test_defaultPasswordComplexityPolicy(t *testing.T) {
}, nil
},
},
want: &command.PasswordComplexityPolicyWriteModel{
want: &PasswordComplexityPolicyWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: "INSTANCE",
ResourceOwner: "INSTANCE",
@ -176,7 +175,7 @@ func Test_passwordComplexityPolicy(t *testing.T) {
tests := []struct {
name string
args args
want *command.PasswordComplexityPolicyWriteModel
want *PasswordComplexityPolicyWriteModel
wantErr bool
}{
{
@ -206,7 +205,7 @@ func Test_passwordComplexityPolicy(t *testing.T) {
}, nil
},
},
want: &command.PasswordComplexityPolicyWriteModel{
want: &PasswordComplexityPolicyWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: "id",
ResourceOwner: "ro",
@ -258,7 +257,7 @@ func Test_passwordComplexityPolicy(t *testing.T) {
}).
Filter(),
},
want: &command.PasswordComplexityPolicyWriteModel{
want: &PasswordComplexityPolicyWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: "INSTANCE",
ResourceOwner: "INSTANCE",

View File

@ -11,7 +11,9 @@ import (
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/command/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
@ -1577,3 +1579,155 @@ func TestCommandSide_UserDomainClaimedSent(t *testing.T) {
})
}
}
func TestExistsUser(t *testing.T) {
type args struct {
filter preparation.FilterToQueryReducer
id string
resourceOwner string
}
tests := []struct {
name string
args args
wantExists bool
wantErr bool
}{
{
name: "no events",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{}, nil
},
id: "id",
resourceOwner: "ro",
},
wantExists: false,
wantErr: false,
},
{
name: "human registered",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
user.NewHumanRegisteredEvent(
context.Background(),
&user.NewAggregate("id", "ro").Aggregate,
"userName",
"firstName",
"lastName",
"nickName",
"displayName",
language.German,
domain.GenderFemale,
"support@zitadel.ch",
true,
),
}, nil
},
id: "id",
resourceOwner: "ro",
},
wantExists: true,
wantErr: false,
},
{
name: "human added",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("id", "ro").Aggregate,
"userName",
"firstName",
"lastName",
"nickName",
"displayName",
language.German,
domain.GenderFemale,
"support@zitadel.ch",
true,
),
}, nil
},
id: "id",
resourceOwner: "ro",
},
wantExists: true,
wantErr: false,
},
{
name: "machine added",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
user.NewMachineAddedEvent(
context.Background(),
&user.NewAggregate("id", "ro").Aggregate,
"userName",
"name",
"description",
true,
),
}, nil
},
id: "id",
resourceOwner: "ro",
},
wantExists: true,
wantErr: false,
},
{
name: "user removed",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
user.NewMachineAddedEvent(
context.Background(),
&user.NewAggregate("removed", "ro").Aggregate,
"userName",
"name",
"description",
true,
),
user.NewUserRemovedEvent(
context.Background(),
&user.NewAggregate("removed", "ro").Aggregate,
"userName",
nil,
true,
),
}, nil
},
id: "id",
resourceOwner: "ro",
},
wantExists: false,
wantErr: false,
},
{
name: "error durring filter",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, errors.ThrowInternal(nil, "USER-Drebn", "Errors.Internal")
},
id: "id",
resourceOwner: "ro",
},
wantExists: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotExists, err := ExistsUser(context.Background(), tt.args.filter, tt.args.id, tt.args.resourceOwner)
if (err != nil) != tt.wantErr {
t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotExists != tt.wantExists {
t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
}
})
}
}

View File

@ -1,36 +0,0 @@
package command
import (
sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/action"
iam_repo "github.com/caos/zitadel/internal/repository/instance"
"github.com/caos/zitadel/internal/repository/keypair"
"github.com/caos/zitadel/internal/repository/org"
proj_repo "github.com/caos/zitadel/internal/repository/project"
usr_repo "github.com/caos/zitadel/internal/repository/user"
usr_grant_repo "github.com/caos/zitadel/internal/repository/usergrant"
)
type Command struct {
es *eventstore.Eventstore
userPasswordAlg crypto.HashAlgorithm
iamDomain string
}
func New(es *eventstore.Eventstore, iamDomain string, defaults sd.SystemDefaults) *Command {
iam_repo.RegisterEventMappers(es)
org.RegisterEventMappers(es)
usr_repo.RegisterEventMappers(es)
usr_grant_repo.RegisterEventMappers(es)
proj_repo.RegisterEventMappers(es)
keypair.RegisterEventMappers(es)
action.RegisterEventMappers(es)
return &Command{
es: es,
iamDomain: iamDomain,
userPasswordAlg: crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost),
}
}

View File

@ -1,95 +0,0 @@
package command
import (
"context"
"time"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
)
func SetDefaultFeatures(
a *instance.Aggregate,
tierName,
tierDescription string,
state domain.FeaturesState,
stateDescription string,
retention time.Duration,
loginPolicyFactors,
loginPolicyIDP,
loginPolicyPasswordless,
loginPolicyRegistration,
loginPolicyUsernameLogin,
loginPolicyPasswordReset,
passwordComplexityPolicy,
labelPolicyPrivateLabel,
labelPolicyWatermark,
customDomain,
privacyPolicy,
metadataUser,
customTextMessage,
customTextLogin,
lockoutPolicy bool,
actionsAllowed domain.ActionsAllowed,
maxActions int,
) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if !state.Valid() || state == domain.FeaturesStateUnspecified || state == domain.FeaturesStateRemoved {
return nil, errors.ThrowInvalidArgument(nil, "INSTA-d3r1s", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
writeModel, err := defaultFeatures(ctx, filter)
if err != nil {
return nil, err
}
event, hasChanged := writeModel.NewSetEvent(ctx, &a.Aggregate,
tierName,
tierDescription,
state,
stateDescription,
retention,
loginPolicyFactors,
loginPolicyIDP,
loginPolicyPasswordless,
loginPolicyRegistration,
loginPolicyUsernameLogin,
loginPolicyPasswordReset,
passwordComplexityPolicy,
labelPolicyPrivateLabel,
labelPolicyWatermark,
customDomain,
privacyPolicy,
metadataUser,
customTextMessage,
customTextLogin,
lockoutPolicy,
actionsAllowed,
maxActions,
)
if !hasChanged {
return nil, errors.ThrowPreconditionFailed(nil, "INSTA-GE4h2", "Errors.Features.NotChanged")
}
return []eventstore.Command{
event,
}, nil
}, nil
}
}
func defaultFeatures(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.InstanceFeaturesWriteModel, error) {
features := command.NewInstanceFeaturesWriteModel(ctx)
events, err := filter(ctx, features.Query())
if err != nil {
return nil, err
}
if len(events) == 0 {
return features, nil
}
features.AppendEvents(events...)
err = features.Reduce()
return features, err
}

View File

@ -1,64 +0,0 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
)
func AddInstanceMember(a *instance.Aggregate, userID string, roles ...string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if userID == "" {
return nil, errors.ThrowInvalidArgument(nil, "INSTA-SDSfs", "Errors.Invalid.Argument")
}
// TODO: check roles
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
if exists, err := ExistsUser(ctx, filter, userID, ""); err != nil || !exists {
return nil, errors.ThrowNotFound(err, "INSTA-GSXOn", "Errors.User.NotFound")
}
if isMember, err := IsInstanceMember(ctx, filter, a.ID, userID); err != nil || isMember {
return nil, errors.ThrowAlreadyExists(err, "INSTA-pFDwe", "Errors.Instance.Member.AlreadyExists")
}
return []eventstore.Command{instance.NewMemberAddedEvent(ctx, &a.Aggregate, userID, roles...)}, nil
},
nil
}
}
func IsInstanceMember(ctx context.Context, filter preparation.FilterToQueryReducer, instanceID, userID string) (isMember bool, err error) {
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
OrderAsc().
AddQuery().
AggregateIDs(instanceID).
AggregateTypes(instance.AggregateType).
EventTypes(
instance.MemberAddedEventType,
instance.MemberRemovedEventType,
instance.MemberCascadeRemovedEventType,
).Builder())
if err != nil {
return false, err
}
for _, event := range events {
switch e := event.(type) {
case *instance.MemberAddedEvent:
if e.UserID == userID {
isMember = true
}
case *instance.MemberRemovedEvent:
if e.UserID == userID {
isMember = false
}
case *instance.MemberCascadeRemovedEvent:
if e.UserID == userID {
isMember = false
}
}
}
return isMember, nil
}

View File

@ -1,72 +0,0 @@
package command
import (
"context"
"strings"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/id"
"github.com/caos/zitadel/internal/repository/org"
user_repo "github.com/caos/zitadel/internal/repository/user"
)
type OrgSetup struct {
Name string
Human AddHuman
}
func (command *Command) SetUpOrg(ctx context.Context, o *OrgSetup) (*domain.ObjectDetails, error) {
orgID, err := id.SonyFlakeGenerator.Next()
if err != nil {
return nil, err
}
userID, err := id.SonyFlakeGenerator.Next()
if err != nil {
return nil, err
}
orgAgg := org.NewAggregate(orgID, orgID)
userAgg := user_repo.NewAggregate(userID, orgID)
cmds, err := preparation.PrepareCommands(ctx, command.es.Filter,
AddOrg(orgAgg, o.Name, command.iamDomain),
AddHumanCommand(userAgg, &o.Human, command.userPasswordAlg),
AddOrgMember(orgAgg, userID, domain.RoleOrgOwner),
)
if err != nil {
return nil, err
}
events, err := command.es.Push(ctx, cmds...)
if err != nil {
return nil, err
}
return &domain.ObjectDetails{
Sequence: events[len(events)-1].Sequence(),
EventDate: events[len(events)-1].CreationDate(),
ResourceOwner: orgID,
}, nil
}
//AddOrg defines the commands to create a new org,
// this includes the verified default domain
func AddOrg(a *org.Aggregate, name, iamDomain string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if name = strings.TrimSpace(name); name == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-mruNY", "Errors.Invalid.Argument")
}
defaultDomain := domain.NewIAMDomainName(name, iamDomain)
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
return []eventstore.Command{
org.NewOrgAddedEvent(ctx, &a.Aggregate, name),
org.NewDomainAddedEvent(ctx, &a.Aggregate, defaultDomain),
org.NewDomainVerifiedEvent(ctx, &a.Aggregate, defaultDomain),
org.NewDomainPrimarySetEvent(ctx, &a.Aggregate, defaultDomain),
}, nil
}, nil
}
}

View File

@ -1,46 +0,0 @@
package command
import (
"context"
"strings"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
)
func AddOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if domain = strings.TrimSpace(domain); domain == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-r3h4J", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
return []eventstore.Command{org.NewDomainAddedEvent(ctx, &a.Aggregate, domain)}, nil
}, nil
}
}
func VerifyOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if domain = strings.TrimSpace(domain); domain == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-yqlVQ", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
//TODO: check if already exists
return []eventstore.Command{org.NewDomainVerifiedEvent(ctx, &a.Aggregate, domain)}, nil
}, nil
}
}
func SetPrimaryOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if domain = strings.TrimSpace(domain); domain == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-gmNqY", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
//TODO: check if already exists and verified
return []eventstore.Command{org.NewDomainPrimarySetEvent(ctx, &a.Aggregate, domain)}, nil
}, nil
}
}

View File

@ -1,133 +0,0 @@
package command
import (
"context"
"testing"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
)
func TestAddDomain(t *testing.T) {
type args struct {
a *org.Aggregate
domain string
}
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid domain",
args: args{
a: org.NewAggregate("test", "test"),
domain: "",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-r3h4J", "Errors.Invalid.Argument"),
},
},
{
name: "correct",
args: args{
a: org.NewAggregate("test", "test"),
domain: "domain",
},
want: Want{
Commands: []eventstore.Command{
org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("test", "test").Aggregate, "domain"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t, AddOrgDomain(tt.args.a, tt.args.domain), nil, tt.want)
})
}
}
func TestVerifyDomain(t *testing.T) {
type args struct {
a *org.Aggregate
domain string
}
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid domain",
args: args{
a: org.NewAggregate("test", "test"),
domain: "",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-yqlVQ", "Errors.Invalid.Argument"),
},
},
{
name: "correct",
args: args{
a: org.NewAggregate("test", "test"),
domain: "domain",
},
want: Want{
Commands: []eventstore.Command{
org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("test", "test").Aggregate, "domain"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t, VerifyOrgDomain(tt.args.a, tt.args.domain), nil, tt.want)
})
}
}
func TestSetDomainPrimary(t *testing.T) {
type args struct {
a *org.Aggregate
domain string
}
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid domain",
args: args{
a: org.NewAggregate("test", "test"),
domain: "",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-gmNqY", "Errors.Invalid.Argument"),
},
},
{
name: "correct",
args: args{
a: org.NewAggregate("test", "test"),
domain: "domain",
},
want: Want{
Commands: []eventstore.Command{
org.NewDomainPrimarySetEvent(context.Background(), &org.NewAggregate("test", "test").Aggregate, "domain"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t, SetPrimaryOrgDomain(tt.args.a, tt.args.domain), nil, tt.want)
})
}
}

View File

@ -1,65 +0,0 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
)
func AddOrgMember(a *org.Aggregate, userID string, roles ...string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if userID == "" {
return nil, errors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument")
}
// TODO: check roles
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
if exists, err := ExistsUser(ctx, filter, userID, a.ID); err != nil || !exists {
return nil, errors.ThrowNotFound(err, "ORG-GoXOn", "Errors.User.NotFound")
}
if isMember, err := IsOrgMember(ctx, filter, a.ID, userID); err != nil || isMember {
return nil, errors.ThrowAlreadyExists(err, "ORG-poWwe", "Errors.Org.Member.AlreadyExists")
}
return []eventstore.Command{org.NewMemberAddedEvent(ctx, &a.Aggregate, userID, roles...)}, nil
},
nil
}
}
func IsOrgMember(ctx context.Context, filter preparation.FilterToQueryReducer, orgID, userID string) (isMember bool, err error) {
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(orgID).
OrderAsc().
AddQuery().
AggregateIDs(orgID).
AggregateTypes(org.AggregateType).
EventTypes(
org.MemberAddedEventType,
org.MemberRemovedEventType,
org.MemberCascadeRemovedEventType,
).Builder())
if err != nil {
return false, err
}
for _, event := range events {
switch e := event.(type) {
case *org.MemberAddedEvent:
if e.UserID == userID {
isMember = true
}
case *org.MemberRemovedEvent:
if e.UserID == userID {
isMember = false
}
case *org.MemberCascadeRemovedEvent:
if e.UserID == userID {
isMember = false
}
}
}
return isMember, nil
}

View File

@ -1,249 +0,0 @@
package command
import (
"context"
"testing"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/user"
)
func TestAddMember(t *testing.T) {
type args struct {
a *org.Aggregate
userID string
roles []string
filter preparation.FilterToQueryReducer
}
ctx := context.Background()
agg := org.NewAggregate("test", "test")
tests := []struct {
name string
args args
want Want
}{
{
name: "no user id",
args: args{
a: agg,
userID: "",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument"),
},
},
// {
// name: "TODO: invalid roles",
// args: args{
// a: agg,
// userID: "",
// roles: []string{""},
// },
// want: preparation.Want{
// ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument"),
// },
// },
{
name: "user not exists",
args: args{
a: agg,
userID: "userID",
filter: NewMultiFilter().
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, nil
}).
Filter(),
},
want: Want{
CreateErr: errors.ThrowNotFound(nil, "ORG-GoXOn", "Errors.User.NotFound"),
},
},
{
name: "already member",
args: args{
a: agg,
userID: "userID",
filter: NewMultiFilter().
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
user.NewMachineAddedEvent(
ctx,
&user.NewAggregate("id", "ro").Aggregate,
"userName",
"name",
"description",
true,
),
}, nil
}).
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewMemberAddedEvent(
ctx,
&org.NewAggregate("id", "ro").Aggregate,
"userID",
),
}, nil
}).
Filter(),
},
want: Want{
CreateErr: errors.ThrowAlreadyExists(nil, "ORG-poWwe", "Errors.Org.Member.AlreadyExists"),
},
},
{
name: "correct",
args: args{
a: agg,
userID: "userID",
filter: NewMultiFilter().
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
user.NewMachineAddedEvent(
ctx,
&user.NewAggregate("id", "ro").Aggregate,
"userName",
"name",
"description",
true,
),
}, nil
}).
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, nil
}).
Filter(),
},
want: Want{
Commands: []eventstore.Command{
org.NewMemberAddedEvent(ctx, &agg.Aggregate, "userID"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t, AddOrgMember(tt.args.a, tt.args.userID, tt.args.roles...), tt.args.filter, tt.want)
})
}
}
func TestIsMember(t *testing.T) {
type args struct {
filter preparation.FilterToQueryReducer
orgID string
userID string
}
tests := []struct {
name string
args args
wantExists bool
wantErr bool
}{
{
name: "no events",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{}, nil
},
orgID: "orgID",
userID: "userID",
},
wantExists: false,
wantErr: false,
},
{
name: "member added",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewMemberAddedEvent(
context.Background(),
&org.NewAggregate("orgID", "ro").Aggregate,
"userID",
),
}, nil
},
orgID: "orgID",
userID: "userID",
},
wantExists: true,
wantErr: false,
},
{
name: "member removed",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewMemberAddedEvent(
context.Background(),
&org.NewAggregate("orgID", "ro").Aggregate,
"userID",
),
org.NewMemberRemovedEvent(
context.Background(),
&org.NewAggregate("orgID", "ro").Aggregate,
"userID",
),
}, nil
},
orgID: "orgID",
userID: "userID",
},
wantExists: false,
wantErr: false,
},
{
name: "member cascade removed",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewMemberAddedEvent(
context.Background(),
&org.NewAggregate("orgID", "ro").Aggregate,
"userID",
),
org.NewMemberCascadeRemovedEvent(
context.Background(),
&org.NewAggregate("orgID", "ro").Aggregate,
"userID",
),
}, nil
},
orgID: "orgID",
userID: "userID",
},
wantExists: false,
wantErr: false,
},
{
name: "error durring filter",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, errors.ThrowInternal(nil, "PROJE-Op26p", "Errors.Internal")
},
orgID: "orgID",
userID: "userID",
},
wantExists: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotExists, err := IsOrgMember(context.Background(), tt.args.filter, tt.args.orgID, tt.args.userID)
if (err != nil) != tt.wantErr {
t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotExists != tt.wantExists {
t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
}
})
}
}

View File

@ -1,57 +0,0 @@
package command
import (
"context"
"testing"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
)
func TestAddOrg(t *testing.T) {
type args struct {
a *org.Aggregate
name string
}
ctx := context.Background()
agg := org.NewAggregate("test", "test")
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid domain",
args: args{
a: agg,
name: "",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-mruNY", "Errors.Invalid.Argument"),
},
},
{
name: "correct",
args: args{
a: agg,
name: "caos ag",
},
want: Want{
Commands: []eventstore.Command{
org.NewOrgAddedEvent(ctx, &agg.Aggregate, "caos ag"),
org.NewDomainAddedEvent(ctx, &agg.Aggregate, "caos-ag.localhost"),
org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "caos-ag.localhost"),
org.NewDomainPrimarySetEvent(ctx, &agg.Aggregate, "caos-ag.localhost"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t, AddOrg(tt.args.a, tt.args.name, "localhost"), nil, tt.want)
})
}
}

View File

@ -1,75 +0,0 @@
package command
import (
"context"
"strings"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/project"
)
func AddProject(
a *project.Aggregate,
name string,
owner string,
projectRoleAssertion bool,
projectRoleCheck bool,
hasProjectCheck bool,
privateLabelingSetting domain.PrivateLabelingSetting,
) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if name = strings.TrimSpace(name); name == "" {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-C01yo", "Errors.Invalid.Argument")
}
if !privateLabelingSetting.Valid() {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-AO52V", "Errors.Invalid.Argument")
}
if owner == "" {
return nil, errors.ThrowPreconditionFailed(nil, "PROJE-hzxwo", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
return []eventstore.Command{
project.NewProjectAddedEvent(ctx, &a.Aggregate,
name,
projectRoleAssertion,
projectRoleCheck,
hasProjectCheck,
privateLabelingSetting,
),
project.NewProjectMemberAddedEvent(ctx, &a.Aggregate,
owner,
domain.RoleProjectOwner),
}, nil
}, nil
}
}
func ExistsProject(ctx context.Context, filter preparation.FilterToQueryReducer, projectID, resourceOwner string) (exists bool, err error) {
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(resourceOwner).
OrderAsc().
AddQuery().
AggregateTypes(project.AggregateType).
AggregateIDs(projectID).
EventTypes(
project.ProjectAddedType,
project.ProjectRemovedType,
).Builder())
if err != nil {
return false, err
}
for _, event := range events {
switch event.(type) {
case *project.ProjectAddedEvent:
exists = true
case *project.ProjectRemovedEvent:
exists = false
}
}
return exists, nil
}

View File

@ -1,155 +0,0 @@
package command
import (
"context"
"strings"
"time"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/project"
)
func AddOIDCApp(
a project.Aggregate,
version domain.OIDCVersion,
appID,
name,
clientID string,
clientSecret *crypto.CryptoValue,
redirectUris []string,
responseTypes []domain.OIDCResponseType,
grantTypes []domain.OIDCGrantType,
applicationType domain.OIDCApplicationType,
authMethodType domain.OIDCAuthMethodType,
postLogoutRedirectUris []string,
devMode bool,
accessTokenType domain.OIDCTokenType,
accessTokenRoleAssertion bool,
idTokenRoleAssertion bool,
idTokenUserinfoAssertion bool,
clockSkew time.Duration,
additionalOrigins []string,
) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if appID == "" {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument")
}
if name = strings.TrimSpace(name); name == "" {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-Fef31", "Errors.Invalid.Argument")
}
if clientID == "" {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-ghTsJ", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
if exists, err := ExistsProject(ctx, filter, a.ID, a.ResourceOwner); !exists || err != nil {
return nil, errors.ThrowNotFound(err, "PROJE-5LQ0U", "Errors.Project.NotFound")
}
return []eventstore.Command{
project.NewApplicationAddedEvent(
ctx,
&a.Aggregate,
appID,
name,
),
project.NewOIDCConfigAddedEvent(
ctx,
&a.Aggregate,
version,
appID,
clientID,
clientSecret,
redirectUris,
responseTypes,
grantTypes,
applicationType,
authMethodType,
postLogoutRedirectUris,
devMode,
accessTokenType,
accessTokenRoleAssertion,
idTokenRoleAssertion,
idTokenUserinfoAssertion,
clockSkew,
additionalOrigins,
),
}, nil
}, nil
}
}
func AddAPIApp(
a project.Aggregate,
appID,
name,
clientID string,
clientSecret *crypto.CryptoValue,
authMethodType domain.APIAuthMethodType,
) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if appID == "" {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument")
}
if name = strings.TrimSpace(name); name == "" {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-F7g21", "Errors.Invalid.Argument")
}
if clientID == "" {
return nil, errors.ThrowInvalidArgument(nil, "PROJE-XXED5", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
if exists, err := ExistsProject(ctx, filter, a.ID, a.ResourceOwner); !exists || err != nil {
return nil, errors.ThrowNotFound(err, "PROJE-Sf2gb", "Errors.Project.NotFound")
}
return []eventstore.Command{
project.NewApplicationAddedEvent(
ctx,
&a.Aggregate,
appID,
name,
),
project.NewAPIConfigAddedEvent(
ctx,
&a.Aggregate,
appID,
clientID,
clientSecret,
authMethodType,
),
}, nil
}, nil
}
}
func ExistsApp(ctx context.Context, filter preparation.FilterToQueryReducer, projectID, appID, resourceOwner string) (exists bool, err error) {
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(resourceOwner).
OrderAsc().
AddQuery().
AggregateTypes(project.AggregateType).
AggregateIDs(projectID).
EventTypes(
project.ApplicationAddedType,
project.ApplicationRemovedType,
).Builder())
if err != nil {
return false, err
}
for _, event := range events {
switch e := event.(type) {
case *project.ApplicationAddedEvent:
if e.AppID == appID {
exists = true
}
case *project.ApplicationRemovedEvent:
if e.AppID == appID {
exists = false
}
}
}
return exists, nil
}

View File

@ -1,386 +0,0 @@
package command
import (
"context"
"testing"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/project"
)
func TestAddOIDCApp(t *testing.T) {
type args struct {
a *project.Aggregate
appID string
name string
clientID string
filter preparation.FilterToQueryReducer
}
ctx := context.Background()
agg := project.NewAggregate("test", "test")
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid appID",
args: args{
a: agg,
appID: "",
name: "name",
clientID: "clientID",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument"),
},
},
{
name: "invalid name",
args: args{
a: agg,
appID: "appID",
name: "",
clientID: "clientID",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-Fef31", "Errors.Invalid.Argument"),
},
},
{
name: "invalid clientID",
args: args{
a: agg,
appID: "appID",
name: "name",
clientID: "",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-ghTsJ", "Errors.Invalid.Argument"),
},
},
{
name: "project not exists",
args: args{
a: agg,
appID: "id",
name: "name",
clientID: "clientID",
filter: NewMultiFilter().
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, nil
}).
Filter(),
},
want: Want{
CreateErr: errors.ThrowNotFound(nil, "PROJE-5LQ0U", "Errors.Project.NotFound"),
},
},
{
name: "correct",
args: args{
a: agg,
appID: "appID",
name: "name",
clientID: "clientID",
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,
"appID",
"name",
),
project.NewOIDCConfigAddedEvent(ctx, &agg.Aggregate,
domain.OIDCVersionV1,
"appID",
"clientID",
nil,
nil,
nil,
nil,
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypeBasic,
nil,
false,
domain.OIDCTokenTypeBearer,
false,
false,
false,
0,
nil,
),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t,
AddOIDCApp(*tt.args.a,
domain.OIDCVersionV1,
tt.args.appID,
tt.args.name,
tt.args.clientID,
nil,
nil,
nil,
nil,
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypeBasic,
nil,
false,
domain.OIDCTokenTypeBearer,
false,
false,
false,
0,
nil,
), tt.args.filter, tt.want)
})
}
}
func TestAddAPIConfig(t *testing.T) {
type args struct {
a *project.Aggregate
appID string
name string
clientID string
filter preparation.FilterToQueryReducer
}
ctx := context.Background()
agg := project.NewAggregate("test", "test")
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid appID",
args: args{
a: agg,
appID: "",
name: "name",
clientID: "clientID",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument"),
},
},
{
name: "invalid name",
args: args{
a: agg,
appID: "appID",
name: "",
clientID: "clientID",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-F7g21", "Errors.Invalid.Argument"),
},
},
{
name: "invalid clientID",
args: args{
a: agg,
appID: "appID",
name: "name",
clientID: "",
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-XXED5", "Errors.Invalid.Argument"),
},
},
{
name: "project not exists",
args: args{
a: agg,
appID: "id",
name: "name",
clientID: "clientID",
filter: NewMultiFilter().
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, nil
}).
Filter(),
},
want: Want{
CreateErr: errors.ThrowNotFound(nil, "PROJE-Sf2gb", "Errors.Project.NotFound"),
},
},
{
name: "correct",
args: args{
a: agg,
appID: "appID",
name: "name",
clientID: "clientID",
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,
"appID",
"name",
),
project.NewAPIConfigAddedEvent(ctx, &agg.Aggregate,
"appID",
"clientID",
nil,
domain.APIAuthMethodTypeBasic,
),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t,
AddAPIApp(*tt.args.a,
tt.args.appID,
tt.args.name,
tt.args.clientID,
nil,
domain.APIAuthMethodTypeBasic,
), tt.args.filter, tt.want)
})
}
}
func TestExistsApp(t *testing.T) {
type args struct {
filter preparation.FilterToQueryReducer
appID string
projectID string
resourceOwner string
}
tests := []struct {
name string
args args
wantExists bool
wantErr bool
}{
{
name: "no events",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{}, nil
},
appID: "appID",
projectID: "projectID",
resourceOwner: "ro",
},
wantExists: false,
wantErr: false,
},
{
name: "app added",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
project.NewApplicationAddedEvent(
context.Background(),
&project.NewAggregate("id", "ro").Aggregate,
"appID",
"name",
),
}, nil
},
appID: "appID",
projectID: "projectID",
resourceOwner: "ro",
},
wantExists: true,
wantErr: false,
},
{
name: "app removed",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
project.NewApplicationAddedEvent(
context.Background(),
&project.NewAggregate("id", "ro").Aggregate,
"appID",
"name",
),
project.NewApplicationRemovedEvent(
context.Background(),
&project.NewAggregate("id", "ro").Aggregate,
"appID",
"name",
),
}, nil
},
appID: "appID",
projectID: "projectID",
resourceOwner: "ro",
},
wantExists: false,
wantErr: false,
},
{
name: "error durring filter",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, errors.ThrowInternal(nil, "PROJE-Op26p", "Errors.Internal")
},
appID: "appID",
projectID: "projectID",
resourceOwner: "ro",
},
wantExists: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotExists, err := ExistsApp(context.Background(), tt.args.filter, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
if (err != nil) != tt.wantErr {
t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotExists != tt.wantExists {
t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
}
})
}
}

View File

@ -1,195 +0,0 @@
package command
import (
"context"
"testing"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/project"
)
func TestAddProject(t *testing.T) {
type args struct {
a *project.Aggregate
name string
owner string
privateLabelingSetting domain.PrivateLabelingSetting
}
ctx := context.Background()
agg := project.NewAggregate("test", "test")
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid name",
args: args{
a: agg,
name: "",
owner: "owner",
privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-C01yo", "Errors.Invalid.Argument"),
},
},
{
name: "invalid private labeling setting",
args: args{
a: agg,
name: "name",
owner: "owner",
privateLabelingSetting: -1,
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-AO52V", "Errors.Invalid.Argument"),
},
},
{
name: "invalid owner",
args: args{
a: agg,
name: "name",
owner: "",
privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
},
want: Want{
ValidationErr: errors.ThrowPreconditionFailed(nil, "PROJE-hzxwo", "Errors.Invalid.Argument"),
},
},
{
name: "correct",
args: args{
a: agg,
name: "ZITADEL",
owner: "CAOS AG",
privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
},
want: Want{
Commands: []eventstore.Command{
project.NewProjectAddedEvent(ctx, &agg.Aggregate,
"ZITADEL",
false,
false,
false,
domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
),
project.NewProjectMemberAddedEvent(ctx, &agg.Aggregate,
"CAOS AG",
domain.RoleProjectOwner),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t, AddProject(tt.args.a, tt.args.name, tt.args.owner, false, false, false, tt.args.privateLabelingSetting), nil, tt.want)
})
}
}
func TestExistsProject(t *testing.T) {
type args struct {
filter preparation.FilterToQueryReducer
id string
resourceOwner string
}
tests := []struct {
name string
args args
wantExists bool
wantErr bool
}{
{
name: "no events",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{}, nil
},
id: "id",
resourceOwner: "ro",
},
wantExists: false,
wantErr: false,
},
{
name: "project added",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
project.NewProjectAddedEvent(
context.Background(),
&project.NewAggregate("id", "ro").Aggregate,
"name",
false,
false,
false,
domain.PrivateLabelingSettingEnforceProjectResourceOwnerPolicy,
),
}, nil
},
id: "id",
resourceOwner: "ro",
},
wantExists: true,
wantErr: false,
},
{
name: "project removed",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
project.NewProjectAddedEvent(
context.Background(),
&project.NewAggregate("id", "ro").Aggregate,
"name",
false,
false,
false,
domain.PrivateLabelingSettingEnforceProjectResourceOwnerPolicy,
),
project.NewProjectRemovedEvent(
context.Background(),
&project.NewAggregate("id", "ro").Aggregate,
"name",
),
}, nil
},
id: "id",
resourceOwner: "ro",
},
wantExists: false,
wantErr: false,
},
{
name: "error durring filter",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, errors.ThrowInternal(nil, "PROJE-Op26p", "Errors.Internal")
},
id: "id",
resourceOwner: "ro",
},
wantExists: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotExists, err := ExistsProject(context.Background(), tt.args.filter, tt.args.id, tt.args.resourceOwner)
if (err != nil) != tt.wantErr {
t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotExists != tt.wantExists {
t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
}
})
}
}

View File

@ -1,40 +0,0 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/user"
)
func ExistsUser(ctx context.Context, filter preparation.FilterToQueryReducer, id, resourceOwner string) (exists bool, err error) {
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(resourceOwner).
OrderAsc().
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(id).
EventTypes(
user.HumanRegisteredType,
user.UserV1RegisteredType,
user.HumanAddedType,
user.UserV1AddedType,
user.MachineAddedEventType,
user.UserRemovedType,
).Builder())
if err != nil {
return false, err
}
for _, event := range events {
switch event.(type) {
case *user.HumanRegisteredEvent, *user.HumanAddedEvent, *user.MachineAddedEvent:
exists = true
case *user.UserRemovedEvent:
exists = false
}
}
return exists, nil
}

View File

@ -1,97 +0,0 @@
package command
import (
"context"
"strings"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/user"
)
type AddHuman struct {
// Username is required
Username string
// FirstName is required
FirstName string
// LastName is required
LastName string
// NickName is required
NickName string
// DisplayName is required
DisplayName string
// Email is required
Email string
// PreferredLang is required
PreferredLang language.Tag
// Gender is required
Gender domain.Gender
//TODO: can it also be verified?
Phone string
//Password is optional
Password string
//PasswordChangeRequired is used if the `Password`-field is set
PasswordChangeRequired bool
}
func AddHumanCommand(a *user.Aggregate, human *AddHuman, passwordAlg crypto.HashAlgorithm) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if !domain.EmailRegex.MatchString(human.Email) {
return nil, errors.ThrowInvalidArgument(nil, "USER-Ec7dM", "Errors.Invalid.Argument")
}
if human.FirstName = strings.TrimSpace(human.FirstName); human.FirstName == "" {
return nil, errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.Invalid.Argument")
}
if human.LastName = strings.TrimSpace(human.LastName); human.LastName == "" {
return nil, errors.ThrowInvalidArgument(nil, "USER-DiAq8", "Errors.Invalid.Argument")
}
human.Phone = strings.TrimSpace(human.Phone)
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
domainPolicy, err := domainPolicyWriteModel(ctx, filter)
if err != nil {
return nil, err
}
cmd := user.NewHumanAddedEvent(
ctx,
&a.Aggregate,
human.Username,
human.FirstName,
human.LastName,
human.NickName,
human.DisplayName,
human.PreferredLang,
human.Gender,
human.Email, //TODO: pass if verified
domainPolicy.UserLoginMustBeDomain,
)
if human.Phone != "" {
cmd.AddPhoneData(human.Phone) //TODO: pass if verified
}
if human.Password != "" {
passwordComplexity, err := passwordComplexityPolicyWriteModel(ctx, filter)
if err != nil {
return nil, err
}
if err = passwordComplexity.Validate(human.Password); err != nil {
return nil, err
}
secret, err := crypto.Hash([]byte(human.Password), passwordAlg)
if err != nil {
return nil, err
}
cmd.AddPasswordData(secret, human.PasswordChangeRequired)
}
return []eventstore.Command{cmd}, nil
}, nil
}
}

View File

@ -1,169 +0,0 @@
package command
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/user"
)
func TestAddHumanCommand(t *testing.T) {
type args struct {
a *user.Aggregate
human *AddHuman
passwordAlg crypto.HashAlgorithm
filter preparation.FilterToQueryReducer
}
agg := user.NewAggregate("id", "ro")
tests := []struct {
name string
args args
want Want
}{
{
name: "invalid email",
args: args{
a: agg,
human: &AddHuman{
Email: "invalid",
},
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-Ec7dM", "Errors.Invalid.Argument"),
},
},
{
name: "invalid first name",
args: args{
a: agg,
human: &AddHuman{
Email: "support@zitadel.ch",
},
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.Invalid.Argument"),
},
},
{
name: "invalid last name",
args: args{
a: agg,
human: &AddHuman{
Email: "support@zitadel.ch",
FirstName: "hurst",
},
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-DiAq8", "Errors.Invalid.Argument"),
},
},
{
name: "invalid password",
args: args{
a: agg,
human: &AddHuman{
Email: "support@zitadel.ch",
FirstName: "gigi",
LastName: "giraffe",
Password: "short",
},
filter: NewMultiFilter().Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewDomainPolicyAddedEvent(
context.Background(),
&org.NewAggregate("id", "ro").Aggregate,
true,
),
}, nil
}).
Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewPasswordComplexityPolicyAddedEvent(
context.Background(),
&org.NewAggregate("id", "ro").Aggregate,
8,
true,
true,
true,
true,
),
}, nil
}).
Filter(),
},
want: Want{
CreateErr: errors.ThrowInvalidArgument(nil, "COMMA-HuJf6", "Errors.User.PasswordComplexityPolicy.MinLength"),
},
},
{
name: "correct",
args: args{
a: agg,
human: &AddHuman{
Email: "support@zitadel.ch",
FirstName: "gigi",
LastName: "giraffe",
Password: "",
},
passwordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
filter: NewMultiFilter().Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewDomainPolicyAddedEvent(
context.Background(),
&org.NewAggregate("id", "ro").Aggregate,
true,
),
}, nil
}).
Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewPasswordComplexityPolicyAddedEvent(
context.Background(),
&org.NewAggregate("id", "ro").Aggregate,
2,
false,
false,
false,
false,
),
}, nil
}).
Filter(),
},
want: Want{
Commands: []eventstore.Command{
user.NewHumanAddedEvent(
context.Background(),
&agg.Aggregate,
"",
"gigi",
"giraffe",
"",
"",
language.Und,
0,
"support@zitadel.ch",
true,
),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AssertValidation(t, AddHumanCommand(tt.args.a, tt.args.human, tt.args.passwordAlg), tt.args.filter, tt.want)
})
}
}

View File

@ -1,166 +0,0 @@
package command
import (
"context"
"testing"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/command/v2/preparation"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/user"
)
func TestExistsUser(t *testing.T) {
type args struct {
filter preparation.FilterToQueryReducer
id string
resourceOwner string
}
tests := []struct {
name string
args args
wantExists bool
wantErr bool
}{
{
name: "no events",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{}, nil
},
id: "id",
resourceOwner: "ro",
},
wantExists: false,
wantErr: false,
},
{
name: "human registered",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
user.NewHumanRegisteredEvent(
context.Background(),
&user.NewAggregate("id", "ro").Aggregate,
"userName",
"firstName",
"lastName",
"nickName",
"displayName",
language.German,
domain.GenderFemale,
"support@zitadel.ch",
true,
),
}, nil
},
id: "id",
resourceOwner: "ro",
},
wantExists: true,
wantErr: false,
},
{
name: "human added",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("id", "ro").Aggregate,
"userName",
"firstName",
"lastName",
"nickName",
"displayName",
language.German,
domain.GenderFemale,
"support@zitadel.ch",
true,
),
}, nil
},
id: "id",
resourceOwner: "ro",
},
wantExists: true,
wantErr: false,
},
{
name: "machine added",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
user.NewMachineAddedEvent(
context.Background(),
&user.NewAggregate("id", "ro").Aggregate,
"userName",
"name",
"description",
true,
),
}, nil
},
id: "id",
resourceOwner: "ro",
},
wantExists: true,
wantErr: false,
},
{
name: "user removed",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
user.NewMachineAddedEvent(
context.Background(),
&user.NewAggregate("removed", "ro").Aggregate,
"userName",
"name",
"description",
true,
),
user.NewUserRemovedEvent(
context.Background(),
&user.NewAggregate("removed", "ro").Aggregate,
"userName",
nil,
true,
),
}, nil
},
id: "id",
resourceOwner: "ro",
},
wantExists: false,
wantErr: false,
},
{
name: "error durring filter",
args: args{
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, errors.ThrowInternal(nil, "USER-Drebn", "Errors.Internal")
},
id: "id",
resourceOwner: "ro",
},
wantExists: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotExists, err := ExistsUser(context.Background(), tt.args.filter, tt.args.id, tt.args.resourceOwner)
if (err != nil) != tt.wantErr {
t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotExists != tt.wantExists {
t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
}
})
}
}

View File

@ -39,7 +39,7 @@ func LoadKey(id string, keyStorage KeyStorage) (string, error) {
return key.Value, nil
}
func LoadKeys(config *KeyConfig, keyStorage KeyStorage) (map[string]string, []string, error) {
func LoadKeys(config *KeyConfig, keyStorage KeyStorage) (Keys, []string, error) {
if config == nil {
return nil, nil, errors.ThrowInvalidArgument(nil, "CRYPT-dJK8s", "config must not be nil")
}
@ -47,7 +47,7 @@ func LoadKeys(config *KeyConfig, keyStorage KeyStorage) (map[string]string, []st
if err != nil {
return nil, nil, err
}
keys := make(map[string]string)
keys := make(Keys)
ids := make([]string, 0, len(config.DecryptionKeyIDs)+1)
if config.EncryptionKeyID != "" {
key, ok := readKeys[config.EncryptionKeyID]

View File

@ -146,10 +146,15 @@ func (a *OIDCApp) OriginsValid() bool {
return true
}
func (a *OIDCApp) getRequiredGrantTypes() []OIDCGrantType {
grantTypes := make([]OIDCGrantType, 0)
implicit := false
for _, r := range a.ResponseTypes {
func ContainsRequiredGrantTypes(responseTypes []OIDCResponseType, grantTypes []OIDCGrantType) bool {
required := RequiredOIDCGrantTypes(responseTypes)
return ContainsOIDCGrantTypes(required, grantTypes)
}
func RequiredOIDCGrantTypes(responseTypes []OIDCResponseType) (grantTypes []OIDCGrantType) {
var implicit bool
for _, r := range responseTypes {
switch r {
case OIDCResponseTypeCode:
grantTypes = append(grantTypes, OIDCGrantTypeAuthorizationCode)
@ -160,9 +165,23 @@ func (a *OIDCApp) getRequiredGrantTypes() []OIDCGrantType {
}
}
}
return grantTypes
}
func (a *OIDCApp) getRequiredGrantTypes() []OIDCGrantType {
return RequiredOIDCGrantTypes(a.ResponseTypes)
}
func ContainsOIDCGrantTypes(shouldContain, list []OIDCGrantType) bool {
for _, should := range shouldContain {
if !containsOIDCGrantType(list, should) {
return false
}
}
return true
}
func containsOIDCGrantType(grantTypes []OIDCGrantType, grantType OIDCGrantType) bool {
for _, gt := range grantTypes {
if gt == grantType {

View File

@ -8,6 +8,11 @@ import (
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
)
type HumanDetails struct {
ID string
ObjectDetails
}
type Human struct {
es_models.ObjectRoot

View File

@ -8,7 +8,9 @@ import (
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
)
var EmailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
var (
EmailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
)
type Email struct {
es_models.ObjectRoot

View File

@ -1,11 +1,12 @@
package domain
import (
"time"
"github.com/caos/zitadel/internal/crypto"
caos_errs "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/ttacon/libphonenumber"
"time"
)
const (

View File

@ -22,8 +22,14 @@ const (
ProjectStateActive
ProjectStateInactive
ProjectStateRemoved
projectStateMax
)
func (s ProjectState) Valid() bool {
return s > ProjectStateUnspecified && s < projectStateMax
}
type PrivateLabelingSetting int32
const (

View File

@ -7,6 +7,7 @@ const (
SecretGeneratorTypeInitCode
SecretGeneratorTypeVerifyEmailCode
SecretGeneratorTypeVerifyPhoneCode
SecretGeneratorTypeVerifyDomain
SecretGeneratorTypePasswordResetCode
SecretGeneratorTypePasswordlessInitCode
SecretGeneratorTypeAppSecret
@ -14,6 +15,10 @@ const (
secretGeneratorTypeCount
)
func (t SecretGeneratorType) Valid() bool {
return t > SecretGeneratorTypeUnspecified && t < secretGeneratorTypeCount
}
type SecretGeneratorState int32
const (

View File

@ -79,7 +79,7 @@ func BaseEventFromRepo(event *repository.Event) *BaseEvent {
ID: event.AggregateID,
Type: AggregateType(event.AggregateType),
ResourceOwner: event.ResourceOwner.String,
InstanceID: event.InstanceID.String,
InstanceID: event.InstanceID,
Version: Version(event.Version),
},
EventType: EventType(event.Type),

View File

@ -82,7 +82,7 @@ func commandsToRepository(instanceID string, cmds []Command) (events []*reposito
AggregateID: cmd.Aggregate().ID,
AggregateType: repository.AggregateType(cmd.Aggregate().Type),
ResourceOwner: sql.NullString{String: cmd.Aggregate().ResourceOwner, Valid: cmd.Aggregate().ResourceOwner != ""},
InstanceID: sql.NullString{String: instanceID, Valid: instanceID != ""},
InstanceID: instanceID,
EditorService: cmd.EditorService(),
EditorUser: cmd.EditorUser(),
Type: repository.EventType(cmd.Type()),
@ -178,7 +178,7 @@ func (es *Eventstore) LatestSequence(ctx context.Context, queryFactory *SearchQu
return es.repo.LatestSequence(ctx, query)
}
type queryReducer interface {
type QueryReducer interface {
reducer
//Query returns the SearchQueryFactory for the events needed in reducer
Query() *SearchQueryBuilder
@ -186,7 +186,7 @@ type queryReducer interface {
//FilterToQueryReducer filters the events based on the search query of the query function,
// appends all events to the reducer and calls it's reduce function
func (es *Eventstore) FilterToQueryReducer(ctx context.Context, r queryReducer) error {
func (es *Eventstore) FilterToQueryReducer(ctx context.Context, r QueryReducer) error {
events, err := es.Filter(ctx, r.Query())
if err != nil {
return err

View File

@ -380,7 +380,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
EditorService: "editorService",
EditorUser: "editorUser",
ResourceOwner: sql.NullString{String: "caos", Valid: true},
InstanceID: sql.NullString{String: "instanceID", Valid: true},
InstanceID: "instanceID",
Type: "test.event",
Version: "v1",
},
@ -418,7 +418,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
EditorService: "editorService",
EditorUser: "editorUser",
ResourceOwner: sql.NullString{String: "caos", Valid: true},
InstanceID: sql.NullString{String: "instanceID", Valid: true},
InstanceID: "instanceID",
Type: "test.event",
Version: "v1",
},
@ -429,7 +429,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
EditorService: "editorService",
EditorUser: "editorUser",
ResourceOwner: sql.NullString{String: "caos", Valid: true},
InstanceID: sql.NullString{String: "instanceID", Valid: true},
InstanceID: "instanceID",
Type: "test.event",
Version: "v1",
},
@ -585,7 +585,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
EditorService: "editorService",
EditorUser: "editorUser",
ResourceOwner: sql.NullString{String: "", Valid: false},
InstanceID: sql.NullString{String: "zitadel"},
InstanceID: "zitadel",
Type: "test.event",
Version: "v1",
},
@ -630,7 +630,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
EditorService: "editorService",
EditorUser: "editorUser",
ResourceOwner: sql.NullString{String: "caos", Valid: true},
InstanceID: sql.NullString{String: "zitadel"},
InstanceID: "zitadel",
Type: "test.event",
Version: "v1",
},
@ -641,7 +641,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
EditorService: "editorService",
EditorUser: "editorUser",
ResourceOwner: sql.NullString{String: "caos", Valid: true},
InstanceID: sql.NullString{String: "zitadel"},
InstanceID: "zitadel",
Type: "test.event",
Version: "v1",
},
@ -654,7 +654,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
EditorService: "editorService",
EditorUser: "editorUser",
ResourceOwner: sql.NullString{String: "caos", Valid: true},
InstanceID: sql.NullString{String: "zitadel"},
InstanceID: "zitadel",
Type: "test.event",
Version: "v1",
},
@ -772,7 +772,7 @@ func TestEventstore_Push(t *testing.T) {
EditorService: "editorService",
EditorUser: "editorUser",
ResourceOwner: sql.NullString{String: "caos", Valid: true},
InstanceID: sql.NullString{String: "zitadel"},
InstanceID: "zitadel",
Type: "test.event",
Version: "v1",
},
@ -816,7 +816,7 @@ func TestEventstore_Push(t *testing.T) {
EditorService: "editorService",
EditorUser: "editorUser",
ResourceOwner: sql.NullString{String: "caos", Valid: true},
InstanceID: sql.NullString{String: "zitadel"},
InstanceID: "zitadel",
Type: "test.event",
Version: "v1",
},
@ -827,7 +827,7 @@ func TestEventstore_Push(t *testing.T) {
EditorService: "editorService",
EditorUser: "editorUser",
ResourceOwner: sql.NullString{String: "caos", Valid: true},
InstanceID: sql.NullString{String: "zitadel"},
InstanceID: "zitadel",
Type: "test.event",
Version: "v1",
},
@ -882,7 +882,7 @@ func TestEventstore_Push(t *testing.T) {
EditorService: "editorService",
EditorUser: "editorUser",
ResourceOwner: sql.NullString{String: "caos", Valid: true},
InstanceID: sql.NullString{String: "zitadel"},
InstanceID: "zitadel",
Type: "test.event",
Version: "v1",
},
@ -893,7 +893,7 @@ func TestEventstore_Push(t *testing.T) {
EditorService: "editorService",
EditorUser: "editorUser",
ResourceOwner: sql.NullString{String: "caos", Valid: true},
InstanceID: sql.NullString{String: "zitadel"},
InstanceID: "zitadel",
Type: "test.event",
Version: "v1",
},
@ -906,7 +906,7 @@ func TestEventstore_Push(t *testing.T) {
EditorService: "editorService",
EditorUser: "editorUser",
ResourceOwner: sql.NullString{String: "caos", Valid: true},
InstanceID: sql.NullString{String: "zitadel"},
InstanceID: "zitadel",
Type: "test.event",
Version: "v1",
},

View File

@ -58,7 +58,7 @@ type Event struct {
ResourceOwner sql.NullString
//InstanceID is the instance where this event belongs to
// use the ID of the instance
InstanceID sql.NullString
InstanceID string
}
//EventType is the description of the change

View File

@ -71,7 +71,7 @@ const (
" $7::VARCHAR AS editor_service," +
" IFNULL((resource_owner), $8::VARCHAR) AS resource_owner," +
" $9::VARCHAR AS instance_id," +
" NEXTVAL(CONCAT('eventstore.', IFNULL($9, 'system'), '_seq'))," +
" NEXTVAL(CONCAT('eventstore.', IF($9 <> '', CONCAT('i_', $9), 'system'), '_seq'))," +
" aggregate_sequence AS previous_aggregate_sequence," +
" aggregate_type_sequence AS previous_aggregate_type_sequence " +
"FROM previous_data " +

View File

@ -136,7 +136,7 @@ func Test_prepareColumns(t *testing.T) {
},
},
fields: fields{
dbRow: []interface{}{time.Time{}, repository.EventType(""), uint64(5), Sequence(0), Sequence(0), Data(nil), "", "", sql.NullString{String: ""}, sql.NullString{String: ""}, repository.AggregateType("user"), "hodor", repository.Version("")},
dbRow: []interface{}{time.Time{}, repository.EventType(""), uint64(5), Sequence(0), Sequence(0), Data(nil), "", "", sql.NullString{String: ""}, "", repository.AggregateType("user"), "hodor", repository.Version("")},
},
},
{

View File

@ -68,6 +68,7 @@ type Instance struct {
DefaultLanguage language.Tag
SetupStarted domain.Step
SetupDone domain.Step
Host string
}
func (i *Instance) InstanceID() string {
@ -82,6 +83,10 @@ func (i *Instance) ConsoleClientID() string {
return i.ConsoleID
}
func (i *Instance) RequestedDomain() string {
return i.Host
}
type InstanceSearchQueries struct {
SearchRequest
Queries []SearchQuery
@ -96,7 +101,7 @@ func (q *InstanceSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder
}
func (q *Queries) Instance(ctx context.Context) (*Instance, error) {
stmt, scan := prepareIAMQuery()
stmt, scan := prepareInstanceQuery(authz.GetInstance(ctx).RequestedDomain())
query, args, err := stmt.Where(sq.Eq{
InstanceColumnID.identifier(): authz.GetInstance(ctx).InstanceID(),
}).ToSql()
@ -109,7 +114,7 @@ func (q *Queries) Instance(ctx context.Context) (*Instance, error) {
}
func (q *Queries) InstanceByHost(ctx context.Context, host string) (authz.Instance, error) {
stmt, scan := prepareIAMQuery()
stmt, scan := prepareInstanceQuery(host)
query, args, err := stmt.Where(sq.Eq{
InstanceColumnID.identifier(): "system", //TODO: change column to domain when available
}).ToSql()
@ -129,7 +134,7 @@ func (q *Queries) GetDefaultLanguage(ctx context.Context) language.Tag {
return iam.DefaultLanguage
}
func prepareIAMQuery() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
return sq.Select(
InstanceColumnID.identifier(),
InstanceColumnChangeDate.identifier(),
@ -143,17 +148,17 @@ func prepareIAMQuery() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
).
From(instanceTable.identifier()).PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*Instance, error) {
iam := new(Instance)
instance := &Instance{Host: host}
lang := ""
err := row.Scan(
&iam.ID,
&iam.ChangeDate,
&iam.Sequence,
&iam.GlobalOrgID,
&iam.IAMProjectID,
&iam.ConsoleID,
&iam.SetupStarted,
&iam.SetupDone,
&instance.ID,
&instance.ChangeDate,
&instance.Sequence,
&instance.GlobalOrgID,
&instance.IAMProjectID,
&instance.ConsoleID,
&instance.SetupStarted,
&instance.SetupDone,
&lang,
)
if err != nil {
@ -162,7 +167,7 @@ func prepareIAMQuery() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
}
return nil, errors.ThrowInternal(err, "QUERY-d9nw", "Errors.Internal")
}
iam.DefaultLanguage = language.Make(lang)
return iam, nil
instance.DefaultLanguage = language.Make(lang)
return instance, nil
}
}

View File

@ -10,6 +10,7 @@ import (
"golang.org/x/text/language"
sq "github.com/Masterminds/squirrel"
"github.com/caos/zitadel/internal/domain"
errs "github.com/caos/zitadel/internal/errors"
)
@ -26,8 +27,10 @@ func Test_InstancePrepares(t *testing.T) {
object interface{}
}{
{
name: "prepareInstanceQuery no result",
prepare: prepareIAMQuery,
name: "prepareInstanceQuery no result",
prepare: func() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
return prepareInstanceQuery("")
},
want: want{
sqlExpectations: mockQueries(
regexp.QuoteMeta(`SELECT projections.instances.id,`+
@ -53,8 +56,10 @@ func Test_InstancePrepares(t *testing.T) {
object: (*Instance)(nil),
},
{
name: "prepareInstanceQuery found",
prepare: prepareIAMQuery,
name: "prepareInstanceQuery found",
prepare: func() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
return prepareInstanceQuery("")
},
want: want{
sqlExpectations: mockQuery(
regexp.QuoteMeta(`SELECT projections.instances.id,`+
@ -104,8 +109,10 @@ func Test_InstancePrepares(t *testing.T) {
},
},
{
name: "prepareInstanceQuery sql err",
prepare: prepareIAMQuery,
name: "prepareInstanceQuery sql err",
prepare: func() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
return prepareInstanceQuery("")
},
want: want{
sqlExpectations: mockQueryErr(
regexp.QuoteMeta(`SELECT projections.instances.id,`+

View File

@ -26,7 +26,7 @@ func testEvent(
Version: "v1",
AggregateID: "agg-id",
ResourceOwner: sql.NullString{String: "ro-id", Valid: true},
InstanceID: sql.NullString{String: "instance-id", Valid: true},
InstanceID: "instance-id",
ID: "event-id",
EditorService: "editor-svc",
EditorUser: "editor-user",

View File

@ -8,7 +8,6 @@ import (
"github.com/caos/zitadel/internal/eventstore/handler"
"github.com/caos/zitadel/internal/eventstore/handler/crdb"
"github.com/caos/zitadel/internal/repository/instance"
"github.com/caos/zitadel/internal/repository/project"
)
const (
@ -63,7 +62,7 @@ func NewSecretGeneratorProjection(ctx context.Context, config crdb.StatementHand
func (p *SecretGeneratorProjection) reducers() []handler.AggregateReducer {
return []handler.AggregateReducer{
{
Aggregate: project.AggregateType,
Aggregate: instance.AggregateType,
EventRedusers: []handler.EventReducer{
{
Event: instance.SecretGeneratorAddedEventType,

View File

@ -73,7 +73,7 @@ func (e *ConsoleSetEvent) UniqueConstraints() []*eventstore.EventUniqueConstrain
func NewIAMConsoleSetEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
clientID string,
clientID *string,
) *ConsoleSetEvent {
return &ConsoleSetEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@ -81,7 +81,7 @@ func NewIAMConsoleSetEvent(
aggregate,
ConsoleSetEventType,
),
ClientID: clientID,
ClientID: *clientID,
}
}

View File

@ -58,6 +58,22 @@ func NewAPIConfigAddedEvent(
}
}
func (e *APIConfigAddedEvent) Validate(cmd eventstore.Command) bool {
c, ok := cmd.(*APIConfigAddedEvent)
if !ok {
return false
}
if e.AppID != c.AppID {
return false
}
if e.AuthMethodType != c.AuthMethodType {
return false
}
return true
}
func APIConfigAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
e := &APIConfigAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),

View File

@ -97,6 +97,92 @@ func NewOIDCConfigAddedEvent(
}
}
func (e *OIDCConfigAddedEvent) Validate(cmd eventstore.Command) bool {
c, ok := cmd.(*OIDCConfigAddedEvent)
if !ok {
return false
}
if e.Version != c.Version {
return false
}
if e.AppID != c.AppID {
return false
}
if e.ClientID != "" && e.ClientID != c.ClientID {
return false
}
if e.ClientSecret != c.ClientSecret {
return false
}
if len(e.RedirectUris) != len(c.RedirectUris) {
return false
}
for i, uri := range e.RedirectUris {
if uri != c.RedirectUris[i] {
return false
}
}
if len(e.ResponseTypes) != len(c.ResponseTypes) {
return false
}
for i, typ := range e.ResponseTypes {
if typ != c.ResponseTypes[i] {
return false
}
}
if len(e.GrantTypes) != len(c.GrantTypes) {
return false
}
for i, typ := range e.GrantTypes {
if typ != c.GrantTypes[i] {
return false
}
}
if e.ApplicationType != c.ApplicationType {
return false
}
if e.AuthMethodType != c.AuthMethodType {
return false
}
if len(e.PostLogoutRedirectUris) != len(c.PostLogoutRedirectUris) {
return false
}
for i, uri := range e.PostLogoutRedirectUris {
if uri != c.PostLogoutRedirectUris[i] {
return false
}
}
if e.DevMode != c.DevMode {
return false
}
if e.AccessTokenType != c.AccessTokenType {
return false
}
if e.AccessTokenRoleAssertion != c.AccessTokenRoleAssertion {
return false
}
if e.IDTokenRoleAssertion != c.IDTokenRoleAssertion {
return false
}
if e.IDTokenUserinfoAssertion != c.IDTokenUserinfoAssertion {
return false
}
if e.ClockSkew != c.ClockSkew {
return false
}
if len(e.AdditionalOrigins) != len(c.AdditionalOrigins) {
return false
}
for i, origin := range e.AdditionalOrigins {
if origin != c.AdditionalOrigins[i] {
return false
}
}
return true
}
func OIDCConfigAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
e := &OIDCConfigAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),