mirror of
https://github.com/zitadel/zitadel.git
synced 2025-04-24 06:21:31 +00:00
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:
parent
4a0d61d75a
commit
cea2567e22
@ -12,14 +12,14 @@ CREATE TABLE eventstore.events (
|
|||||||
, editor_user TEXT NOT NULL
|
, editor_user TEXT NOT NULL
|
||||||
, editor_service TEXT NOT NULL
|
, editor_service TEXT NOT NULL
|
||||||
, resource_owner 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
|
, PRIMARY KEY (event_sequence DESC, instance_id) USING HASH WITH BUCKET_COUNT = 10
|
||||||
, INDEX agg_type_agg_id (aggregate_type, aggregate_id)
|
, INDEX agg_type_agg_id (aggregate_type, aggregate_id, instance_id)
|
||||||
, INDEX agg_type (aggregate_type)
|
, INDEX agg_type (aggregate_type, instance_id)
|
||||||
, INDEX agg_type_seq (aggregate_type, event_sequence DESC)
|
, 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, instance_id, previous_aggregate_type_sequence)
|
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)
|
, INDEX max_sequence (aggregate_type, aggregate_id, event_sequence DESC, instance_id)
|
||||||
, CONSTRAINT previous_sequence_unique UNIQUE (previous_aggregate_sequence DESC)
|
, CONSTRAINT previous_sequence_unique UNIQUE (previous_aggregate_sequence DESC, instance_id)
|
||||||
, CONSTRAINT prev_agg_type_seq_unique UNIQUE(previous_aggregate_type_sequence)
|
, CONSTRAINT prev_agg_type_seq_unique UNIQUE(previous_aggregate_type_sequence, instance_id)
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,6 @@ package setup
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
_ "embed"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -2,21 +2,62 @@ package setup
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"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 {
|
type DefaultInstance struct {
|
||||||
cmd *command.Command
|
|
||||||
InstanceSetup command.InstanceSetup
|
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 {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mig *DefaultInstance) String() string {
|
func (mig *DefaultInstance) String() string {
|
||||||
return "03_default_instance"
|
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)
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/config/hook"
|
"github.com/caos/zitadel/internal/config/hook"
|
||||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
"github.com/caos/zitadel/internal/database"
|
"github.com/caos/zitadel/internal/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ type Config struct {
|
|||||||
ExternalDomain string
|
ExternalDomain string
|
||||||
ExternalSecure bool
|
ExternalSecure bool
|
||||||
Log *logging.Config
|
Log *logging.Config
|
||||||
|
EncryptionKeys *encryptionKeyConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustNewConfig(v *viper.Viper) *Config {
|
func MustNewConfig(v *viper.Viper) *Config {
|
||||||
@ -40,6 +42,10 @@ type Steps struct {
|
|||||||
S3DefaultInstance *DefaultInstance
|
S3DefaultInstance *DefaultInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type encryptionKeyConfig struct {
|
||||||
|
User *crypto.KeyConfig
|
||||||
|
}
|
||||||
|
|
||||||
func MustNewSteps(v *viper.Viper) *Steps {
|
func MustNewSteps(v *viper.Viper) *Steps {
|
||||||
v.SetConfigType("yaml")
|
v.SetConfigType("yaml")
|
||||||
err := v.ReadConfig(bytes.NewBuffer(defaultSteps))
|
err := v.ReadConfig(bytes.NewBuffer(defaultSteps))
|
||||||
|
@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/cmd/admin/key"
|
||||||
http_util "github.com/caos/zitadel/internal/api/http"
|
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/database"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
"github.com/caos/zitadel/internal/migration"
|
"github.com/caos/zitadel/internal/migration"
|
||||||
@ -31,12 +31,15 @@ Requirements:
|
|||||||
config := MustNewConfig(viper.GetViper())
|
config := MustNewConfig(viper.GetViper())
|
||||||
steps := MustNewSteps(viper.New())
|
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)
|
dbClient, err := database.Connect(config.Database)
|
||||||
logging.OnError(err).Fatal("unable to connect to 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")
|
logging.OnError(err).Fatal("unable to start eventstore")
|
||||||
migration.RegisterMappers(eventstoreClient)
|
migration.RegisterMappers(eventstoreClient)
|
||||||
|
|
||||||
cmd := command.New(eventstoreClient, "localhost", config.SystemDefaults)
|
|
||||||
|
|
||||||
steps.s1ProjectionTable = &ProjectionTable{dbClient: dbClient}
|
steps.s1ProjectionTable = &ProjectionTable{dbClient: dbClient}
|
||||||
steps.s2AssetsTable = &AssetTable{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.IsDevMode = !config.ExternalSecure
|
||||||
steps.S3DefaultInstance.InstanceSetup.Zitadel.BaseURL = http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
|
steps.S3DefaultInstance.InstanceSetup.Zitadel.BaseURL = http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
|
||||||
|
|
||||||
|
@ -8,11 +8,64 @@ S3DefaultInstance:
|
|||||||
LastName: Admin
|
LastName: Admin
|
||||||
NickName:
|
NickName:
|
||||||
DisplayName:
|
DisplayName:
|
||||||
Email: admin@zitadel.ch
|
Email:
|
||||||
|
Address: admin@zitadel.ch
|
||||||
|
Verified: true
|
||||||
PreferredLanguage:
|
PreferredLanguage:
|
||||||
Gender:
|
Gender:
|
||||||
Phone:
|
Phone:
|
||||||
|
Number:
|
||||||
|
Verified:
|
||||||
Password: Password1!
|
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:
|
Features:
|
||||||
TierName: Default Tier
|
TierName: Default Tier
|
||||||
TierDescription: ""
|
TierDescription: ""
|
||||||
|
@ -32,26 +32,20 @@ type encryptionKeys struct {
|
|||||||
OIDCKey []byte
|
OIDCKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyStorage) (*encryptionKeys, error) {
|
func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyStorage) (keys *encryptionKeys, err error) {
|
||||||
keys, err := keyStorage.ReadKeys()
|
if err := verifyDefaultKeys(keyStorage); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
keys = new(encryptionKeys)
|
||||||
|
keys.DomainVerification, err = crypto.NewAESCrypto(keyConfig.DomainVerification, keyStorage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(keys) == 0 {
|
keys.IDPConfig, err = crypto.NewAESCrypto(keyConfig.IDPConfig, keyStorage)
|
||||||
if err := createDefaultKeys(keyStorage); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
encryptionKeys := new(encryptionKeys)
|
|
||||||
encryptionKeys.DomainVerification, err = crypto.NewAESCrypto(keyConfig.DomainVerification, keyStorage)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
encryptionKeys.IDPConfig, err = crypto.NewAESCrypto(keyConfig.IDPConfig, keyStorage)
|
keys.OIDC, err = crypto.NewAESCrypto(keyConfig.OIDC, keyStorage)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
encryptionKeys.OIDC, err = crypto.NewAESCrypto(keyConfig.OIDC, keyStorage)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -59,20 +53,20 @@ func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyS
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
encryptionKeys.OIDCKey = []byte(key)
|
keys.OIDCKey = []byte(key)
|
||||||
encryptionKeys.OTP, err = crypto.NewAESCrypto(keyConfig.OTP, keyStorage)
|
keys.OTP, err = crypto.NewAESCrypto(keyConfig.OTP, keyStorage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
encryptionKeys.SMS, err = crypto.NewAESCrypto(keyConfig.SMS, keyStorage)
|
keys.SMS, err = crypto.NewAESCrypto(keyConfig.SMS, keyStorage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
encryptionKeys.SMTP, err = crypto.NewAESCrypto(keyConfig.SMTP, keyStorage)
|
keys.SMTP, err = crypto.NewAESCrypto(keyConfig.SMTP, keyStorage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
encryptionKeys.User, err = crypto.NewAESCrypto(keyConfig.User, keyStorage)
|
keys.User, err = crypto.NewAESCrypto(keyConfig.User, keyStorage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -80,23 +74,30 @@ func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyS
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
encryptionKeys.CSRFCookieKey = []byte(key)
|
keys.CSRFCookieKey = []byte(key)
|
||||||
key, err = crypto.LoadKey(keyConfig.UserAgentCookieKeyID, keyStorage)
|
key, err = crypto.LoadKey(keyConfig.UserAgentCookieKeyID, keyStorage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
encryptionKeys.UserAgentCookieKey = []byte(key)
|
keys.UserAgentCookieKey = []byte(key)
|
||||||
return encryptionKeys, nil
|
return keys, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDefaultKeys(keyStorage crypto.KeyStorage) error {
|
func verifyDefaultKeys(keyStorage crypto.KeyStorage) (err error) {
|
||||||
keys := make([]*crypto.Key, len(defaultKeyIDs))
|
keys := make([]*crypto.Key, 0, len(defaultKeyIDs))
|
||||||
for i, keyID := range defaultKeyIDs {
|
for _, keyID := range defaultKeyIDs {
|
||||||
|
_, err := crypto.LoadKey(keyID, keyStorage)
|
||||||
|
if err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
key, err := crypto.NewKey(keyID)
|
key, err := crypto.NewKey(keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
keys[i] = key
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if err := keyStorage.CreateKeys(keys...); err != nil {
|
if err := keyStorage.CreateKeys(keys...); err != nil {
|
||||||
return caos_errs.ThrowInternal(err, "START-aGBq2", "cannot create default keys")
|
return caos_errs.ThrowInternal(err, "START-aGBq2", "cannot create default keys")
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"golang.org/x/net/http2/h2c"
|
"golang.org/x/net/http2/h2c"
|
||||||
|
|
||||||
"github.com/caos/zitadel/cmd/admin/key"
|
"github.com/caos/zitadel/cmd/admin/key"
|
||||||
|
|
||||||
admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing"
|
admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing"
|
||||||
"github.com/caos/zitadel/internal/api"
|
"github.com/caos/zitadel/internal/api"
|
||||||
"github.com/caos/zitadel/internal/api/assets"
|
"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),
|
Origin: http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure),
|
||||||
DisplayName: "ZITADEL",
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot start commands: %w", err)
|
return fmt.Errorf("cannot start commands: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package start
|
|||||||
import (
|
import (
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/caos/zitadel/cmd/admin/initialise"
|
"github.com/caos/zitadel/cmd/admin/initialise"
|
||||||
|
"github.com/caos/zitadel/cmd/admin/key"
|
||||||
"github.com/caos/zitadel/cmd/admin/setup"
|
"github.com/caos/zitadel/cmd/admin/setup"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -20,16 +21,18 @@ Last ZITADEL starts.
|
|||||||
Requirements:
|
Requirements:
|
||||||
- cockroachdb`,
|
- cockroachdb`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
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()))
|
initialise.InitAll(initialise.MustNewConfig(viper.GetViper()))
|
||||||
|
|
||||||
setupConfig := setup.MustNewConfig(viper.GetViper())
|
setupConfig := setup.MustNewConfig(viper.GetViper())
|
||||||
setupSteps := setup.MustNewSteps(viper.New())
|
setupSteps := setup.MustNewSteps(viper.New())
|
||||||
setup.Setup(setupConfig, setupSteps)
|
setup.Setup(setupConfig, setupSteps, masterKey)
|
||||||
|
|
||||||
startConfig := MustNewConfig(viper.GetViper())
|
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")
|
logging.OnError(err).Fatal("unable to start zitadel")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ type Instance interface {
|
|||||||
InstanceID() string
|
InstanceID() string
|
||||||
ProjectID() string
|
ProjectID() string
|
||||||
ConsoleClientID() string
|
ConsoleClientID() string
|
||||||
|
RequestedDomain() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstanceVerifier interface {
|
type InstanceVerifier interface {
|
||||||
@ -20,6 +21,7 @@ type InstanceVerifier interface {
|
|||||||
|
|
||||||
type instance struct {
|
type instance struct {
|
||||||
ID string
|
ID string
|
||||||
|
Domain string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *instance) InstanceID() string {
|
func (i *instance) InstanceID() string {
|
||||||
@ -34,6 +36,10 @@ func (i *instance) ConsoleClientID() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *instance) RequestedDomain() string {
|
||||||
|
return i.Domain
|
||||||
|
}
|
||||||
|
|
||||||
func GetInstance(ctx context.Context) Instance {
|
func GetInstance(ctx context.Context) Instance {
|
||||||
instance, ok := ctx.Value(instanceKey).(Instance)
|
instance, ok := ctx.Value(instanceKey).(Instance)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -49,3 +55,13 @@ func WithInstance(ctx context.Context, instance Instance) context.Context {
|
|||||||
func WithInstanceID(ctx context.Context, id string) context.Context {
|
func WithInstanceID(ctx context.Context, id string) context.Context {
|
||||||
return context.WithValue(ctx, instanceKey, &instance{ID: id})
|
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)
|
||||||
|
}
|
||||||
|
@ -78,3 +78,7 @@ func (m *mockInstance) ProjectID() string {
|
|||||||
func (m *mockInstance) ConsoleClientID() string {
|
func (m *mockInstance) ConsoleClientID() string {
|
||||||
return "consoleID"
|
return "consoleID"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockInstance) RequestedDomain() string {
|
||||||
|
return "zitadel.cloud"
|
||||||
|
}
|
||||||
|
@ -28,7 +28,8 @@ type Server struct {
|
|||||||
userCodeAlg crypto.EncryptionAlgorithm
|
userCodeAlg crypto.EncryptionAlgorithm
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateServer(command *command.Commands,
|
func CreateServer(
|
||||||
|
command *command.Commands,
|
||||||
query *query.Queries,
|
query *query.Queries,
|
||||||
sd systemdefaults.SystemDefaults,
|
sd systemdefaults.SystemDefaults,
|
||||||
assetAPIPrefix string,
|
assetAPIPrefix string,
|
||||||
|
@ -4,7 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/logging"
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/pkg/oidc"
|
||||||
|
"golang.org/x/text/language"
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
@ -13,9 +15,9 @@ import (
|
|||||||
idp_grpc "github.com/caos/zitadel/internal/api/grpc/idp"
|
idp_grpc "github.com/caos/zitadel/internal/api/grpc/idp"
|
||||||
"github.com/caos/zitadel/internal/api/grpc/metadata"
|
"github.com/caos/zitadel/internal/api/grpc/metadata"
|
||||||
obj_grpc "github.com/caos/zitadel/internal/api/grpc/object"
|
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"
|
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
|
||||||
z_oidc "github.com/caos/zitadel/internal/api/oidc"
|
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/domain"
|
||||||
"github.com/caos/zitadel/internal/query"
|
"github.com/caos/zitadel/internal/query"
|
||||||
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
|
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) {
|
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)
|
lang, err := language.Parse(req.Profile.PreferredLanguage)
|
||||||
if err != nil {
|
logging.OnError(err).Debug("unable to parse language")
|
||||||
return nil, err
|
|
||||||
}
|
details, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, &command.AddHuman{
|
||||||
phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg)
|
Username: req.UserName,
|
||||||
if err != nil {
|
FirstName: req.Profile.FirstName,
|
||||||
return nil, err
|
LastName: req.Profile.LastName,
|
||||||
}
|
NickName: req.Profile.NickName,
|
||||||
human, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, AddHumanUserRequestToDomain(req), initCodeGenerator, phoneCodeGenerator)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &mgmt_pb.AddHumanUserResponse{
|
return &mgmt_pb.AddHumanUserResponse{
|
||||||
UserId: human.AggregateID,
|
UserId: details.ID,
|
||||||
Details: obj_grpc.AddToDetailsPb(
|
Details: obj_grpc.AddToDetailsPb(
|
||||||
human.Sequence,
|
details.Sequence,
|
||||||
human.ChangeDate,
|
details.EventDate,
|
||||||
human.ResourceOwner,
|
details.ResourceOwner,
|
||||||
),
|
),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -763,7 +781,7 @@ func (s *Server) GetPersonalAccessTokenByIDs(ctx context.Context, req *mgmt_pb.G
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &mgmt_pb.GetPersonalAccessTokenByIDsResponse{
|
return &mgmt_pb.GetPersonalAccessTokenByIDsResponse{
|
||||||
Token: user.PersonalAccessTokenToPb(token),
|
Token: user_grpc.PersonalAccessTokenToPb(token),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,3 +172,7 @@ func (m *mockInstance) ProjectID() string {
|
|||||||
func (m *mockInstance) ConsoleClientID() string {
|
func (m *mockInstance) ConsoleClientID() string {
|
||||||
return "consoleClientID"
|
return "consoleClientID"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockInstance) RequestedDomain() string {
|
||||||
|
return "localhost"
|
||||||
|
}
|
||||||
|
@ -256,3 +256,7 @@ func (m *mockInstance) ProjectID() string {
|
|||||||
func (m *mockInstance) ConsoleClientID() string {
|
func (m *mockInstance) ConsoleClientID() string {
|
||||||
return "consoleClientID"
|
return "consoleClientID"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockInstance) RequestedDomain() string {
|
||||||
|
return "zitadel.cloud"
|
||||||
|
}
|
||||||
|
@ -48,6 +48,18 @@ type Commands struct {
|
|||||||
privateKeyLifetime time.Duration
|
privateKeyLifetime time.Duration
|
||||||
publicKeyLifetime time.Duration
|
publicKeyLifetime time.Duration
|
||||||
tokenVerifier orgFeatureChecker
|
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 {
|
type orgFeatureChecker interface {
|
||||||
@ -64,6 +76,7 @@ func StartCommands(es *eventstore.Eventstore,
|
|||||||
otpEncryption,
|
otpEncryption,
|
||||||
smtpEncryption,
|
smtpEncryption,
|
||||||
smsEncryption,
|
smsEncryption,
|
||||||
|
userEncryption,
|
||||||
domainVerificationEncryption,
|
domainVerificationEncryption,
|
||||||
oidcEncryption crypto.EncryptionAlgorithm,
|
oidcEncryption crypto.EncryptionAlgorithm,
|
||||||
) (repo *Commands, err error) {
|
) (repo *Commands, err error) {
|
||||||
@ -81,7 +94,9 @@ func StartCommands(es *eventstore.Eventstore,
|
|||||||
smsCrypto: smsEncryption,
|
smsCrypto: smsEncryption,
|
||||||
domainVerificationAlg: domainVerificationEncryption,
|
domainVerificationAlg: domainVerificationEncryption,
|
||||||
keyAlgorithm: oidcEncryption,
|
keyAlgorithm: oidcEncryption,
|
||||||
|
v2: NewCommandV2(es, defaults, userEncryption, authZConfig.RolePermissionMappings),
|
||||||
}
|
}
|
||||||
|
|
||||||
instance_repo.RegisterEventMappers(repo.eventstore)
|
instance_repo.RegisterEventMappers(repo.eventstore)
|
||||||
org.RegisterEventMappers(repo.eventstore)
|
org.RegisterEventMappers(repo.eventstore)
|
||||||
usr_repo.RegisterEventMappers(repo.eventstore)
|
usr_repo.RegisterEventMappers(repo.eventstore)
|
||||||
@ -113,6 +128,31 @@ func StartCommands(es *eventstore.Eventstore,
|
|||||||
return repo, nil
|
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 {
|
func AppendAndReduce(object interface {
|
||||||
AppendEvents(...eventstore.Event)
|
AppendEvents(...eventstore.Event)
|
||||||
Reduce() error
|
Reduce() error
|
||||||
|
67
internal/command/crypto.go
Normal file
67
internal/command/crypto.go
Normal 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
23
internal/command/email.go
Normal 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)
|
||||||
|
}
|
@ -6,8 +6,10 @@ import (
|
|||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/api/ui/console"
|
"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/domain"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
"github.com/caos/zitadel/internal/id"
|
"github.com/caos/zitadel/internal/id"
|
||||||
"github.com/caos/zitadel/internal/repository/instance"
|
"github.com/caos/zitadel/internal/repository/instance"
|
||||||
@ -53,6 +55,16 @@ type InstanceSetup struct {
|
|||||||
ActionsAllowed domain.ActionsAllowed
|
ActionsAllowed domain.ActionsAllowed
|
||||||
MaxActions int
|
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 {
|
PasswordComplexityPolicy struct {
|
||||||
MinLength uint64
|
MinLength uint64
|
||||||
HasLowercase bool
|
HasLowercase bool
|
||||||
@ -111,14 +123,10 @@ type ZitadelConfig struct {
|
|||||||
BaseURL string
|
BaseURL string
|
||||||
|
|
||||||
projectID string
|
projectID string
|
||||||
mgmtID string
|
mgmtAppID string
|
||||||
mgmtClientID string
|
adminAppID string
|
||||||
adminID string
|
authAppID string
|
||||||
adminClientID string
|
consoleAppID string
|
||||||
authID string
|
|
||||||
authClientID string
|
|
||||||
consoleID string
|
|
||||||
consoleClientID string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InstanceSetup) generateIDs() (err error) {
|
func (s *InstanceSetup) generateIDs() (err error) {
|
||||||
@ -127,50 +135,35 @@ func (s *InstanceSetup) generateIDs() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Zitadel.mgmtID, err = id.SonyFlakeGenerator.Next()
|
s.Zitadel.mgmtAppID, err = id.SonyFlakeGenerator.Next()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Zitadel.mgmtClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Zitadel.adminID, err = id.SonyFlakeGenerator.Next()
|
s.Zitadel.adminAppID, err = id.SonyFlakeGenerator.Next()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Zitadel.adminClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Zitadel.authID, err = id.SonyFlakeGenerator.Next()
|
s.Zitadel.authAppID, err = id.SonyFlakeGenerator.Next()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Zitadel.authClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Zitadel.consoleID, err = id.SonyFlakeGenerator.Next()
|
s.Zitadel.consoleAppID, err = id.SonyFlakeGenerator.Next()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Zitadel.consoleClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
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()
|
instanceID, err := id.SonyFlakeGenerator.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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()
|
orgID, err := id.SonyFlakeGenerator.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -219,6 +212,14 @@ func (command *Command) SetUpInstance(ctx context.Context, setup *InstanceSetup)
|
|||||||
setup.Features.ActionsAllowed,
|
setup.Features.ActionsAllowed,
|
||||||
setup.Features.MaxActions,
|
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(
|
AddPasswordComplexityPolicy(
|
||||||
instanceAgg,
|
instanceAgg,
|
||||||
setup.PasswordComplexityPolicy.MinLength,
|
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, SetInstanceCustomTexts(instanceAgg, msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
validations = append(validations,
|
console := &addOIDCApp{
|
||||||
AddOrg(orgAgg, setup.Org.Name, command.iamDomain),
|
AddApp: AddApp{
|
||||||
AddHumanCommand(userAgg, &setup.Org.Human, command.userPasswordAlg),
|
Aggregate: *projectAgg,
|
||||||
AddOrgMember(orgAgg, userID, domain.RoleOrgOwner),
|
ID: setup.Zitadel.consoleAppID,
|
||||||
AddInstanceMember(instanceAgg, userID, domain.RoleIAMOwner),
|
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),
|
SetIAMProject(instanceAgg, projectAgg.ID),
|
||||||
|
|
||||||
AddAPIApp(
|
AddAPIAppCommand(
|
||||||
*projectAgg,
|
&addAPIApp{
|
||||||
setup.Zitadel.mgmtID,
|
AddApp: AddApp{
|
||||||
mgmtAppName,
|
Aggregate: *projectAgg,
|
||||||
setup.Zitadel.mgmtClientID,
|
ID: setup.Zitadel.mgmtAppID,
|
||||||
|
Name: mgmtAppName,
|
||||||
|
},
|
||||||
|
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
|
||||||
|
},
|
||||||
nil,
|
nil,
|
||||||
domain.APIAuthMethodTypePrivateKeyJWT,
|
|
||||||
),
|
),
|
||||||
|
|
||||||
AddAPIApp(
|
AddAPIAppCommand(
|
||||||
*projectAgg,
|
&addAPIApp{
|
||||||
setup.Zitadel.adminID,
|
AddApp: AddApp{
|
||||||
adminAppName,
|
Aggregate: *projectAgg,
|
||||||
setup.Zitadel.adminClientID,
|
ID: setup.Zitadel.adminAppID,
|
||||||
|
Name: adminAppName,
|
||||||
|
},
|
||||||
|
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
|
||||||
|
},
|
||||||
nil,
|
nil,
|
||||||
domain.APIAuthMethodTypePrivateKeyJWT,
|
|
||||||
),
|
),
|
||||||
|
|
||||||
AddAPIApp(
|
AddAPIAppCommand(
|
||||||
*projectAgg,
|
&addAPIApp{
|
||||||
setup.Zitadel.authID,
|
AddApp: AddApp{
|
||||||
authAppName,
|
Aggregate: *projectAgg,
|
||||||
setup.Zitadel.authClientID,
|
ID: setup.Zitadel.authAppID,
|
||||||
|
Name: authAppName,
|
||||||
|
},
|
||||||
|
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
|
||||||
|
},
|
||||||
nil,
|
nil,
|
||||||
domain.APIAuthMethodTypePrivateKeyJWT,
|
|
||||||
),
|
),
|
||||||
|
|
||||||
AddOIDCApp(
|
AddOIDCAppCommand(console, nil),
|
||||||
*projectAgg,
|
SetIAMConsoleID(instanceAgg, &console.ClientID),
|
||||||
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),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
cmds, err := preparation.PrepareCommands(ctx, command.es.Filter, validations...)
|
cmds, err := preparation.PrepareCommands(ctx, c.es.Filter, validations...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
events, err := command.es.Push(ctx, cmds...)
|
events, err := c.es.Push(ctx, cmds...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
//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() (preparation.CreateCommands, error) {
|
||||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||||
return []eventstore.Command{
|
return []eventstore.Command{
|
||||||
@ -377,3 +388,25 @@ func SetIAMConsoleID(a *instance.Aggregate, clientID string) preparation.Validat
|
|||||||
}, nil
|
}, 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
|
||||||
|
}
|
@ -5,12 +5,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/repository/instance"
|
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"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"
|
||||||
|
"github.com/caos/zitadel/internal/repository/instance"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
|
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 {
|
func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain string, generated bool) preparation.Validation {
|
||||||
return func() (preparation.CreateCommands, error) {
|
return func() (preparation.CreateCommands, error) {
|
||||||
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
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) {
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||||
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
|
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
|
||||||
@ -60,7 +59,7 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if domainWriteModel.State == domain.InstanceDomainStateActive {
|
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
|
return []eventstore.Command{instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated)}, nil
|
||||||
}, 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 {
|
func (c *Commands) removeInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
|
||||||
return func() (preparation.CreateCommands, error) {
|
return func() (preparation.CreateCommands, error) {
|
||||||
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
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) {
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||||
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
|
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
|
||||||
@ -78,10 +77,10 @@ func (c *Commands) removeInstanceDomain(a *instance.Aggregate, instanceDomain st
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if domainWriteModel.State != domain.InstanceDomainStateActive {
|
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 {
|
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
|
return []eventstore.Command{instance.NewDomainRemovedEvent(ctx, &a.Aggregate, instanceDomain)}, nil
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -3,7 +3,7 @@ package command
|
|||||||
import (
|
import (
|
||||||
"context"
|
"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/eventstore"
|
||||||
"github.com/caos/zitadel/internal/repository/instance"
|
"github.com/caos/zitadel/internal/repository/instance"
|
||||||
)
|
)
|
@ -5,8 +5,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/command"
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
"github.com/caos/zitadel/internal/repository/instance"
|
"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) {
|
func existingInstanceCustomMessageText(ctx context.Context, filter preparation.FilterToQueryReducer, textType string, lang language.Tag) (*InstanceCustomMessageTextWriteModel, error) {
|
||||||
writeModel := command.NewInstanceCustomMessageTextWriteModel(ctx, textType, lang)
|
writeModel := NewInstanceCustomMessageTextWriteModel(ctx, textType, lang)
|
||||||
events, err := filter(ctx, writeModel.Query())
|
events, err := filter(ctx, writeModel.Query())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
@ -2,13 +2,97 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/repository/instance"
|
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"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) {
|
func (c *Commands) SetDefaultFeatures(ctx context.Context, features *domain.Features) (*domain.ObjectDetails, error) {
|
||||||
existingFeatures := NewInstanceFeaturesWriteModel(ctx)
|
existingFeatures := NewInstanceFeaturesWriteModel(ctx)
|
||||||
setEvent, err := c.setDefaultFeatures(ctx, existingFeatures, features)
|
setEvent, err := c.setDefaultFeatures(ctx, existingFeatures, features)
|
||||||
@ -59,7 +143,7 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *Ins
|
|||||||
features.MaxActions,
|
features.MaxActions,
|
||||||
)
|
)
|
||||||
if !hasChanged {
|
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
|
return setEvent, nil
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package command
|
|||||||
import (
|
import (
|
||||||
"context"
|
"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/eventstore"
|
||||||
"github.com/caos/zitadel/internal/repository/instance"
|
"github.com/caos/zitadel/internal/repository/instance"
|
||||||
)
|
)
|
@ -3,7 +3,7 @@ package command
|
|||||||
import (
|
import (
|
||||||
"context"
|
"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/eventstore"
|
||||||
"github.com/caos/zitadel/internal/repository/instance"
|
"github.com/caos/zitadel/internal/repository/instance"
|
||||||
)
|
)
|
@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"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/domain"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
"github.com/caos/zitadel/internal/repository/instance"
|
"github.com/caos/zitadel/internal/repository/instance"
|
@ -4,15 +4,71 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"reflect"
|
"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/domain"
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
caos_errs "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/repository/instance"
|
||||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
"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) {
|
func (c *Commands) AddInstanceMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
|
||||||
if member.UserID == "" {
|
if member.UserID == "" {
|
||||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INSTANCE-Mf83b", "Errors.IAM.MemberInvalid")
|
return nil, caos_errs.ThrowInvalidArgument(nil, "INSTANCE-Mf83b", "Errors.IAM.MemberInvalid")
|
||||||
|
@ -3,7 +3,7 @@ package command
|
|||||||
import (
|
import (
|
||||||
"context"
|
"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/eventstore"
|
||||||
"github.com/caos/zitadel/internal/repository/instance"
|
"github.com/caos/zitadel/internal/repository/instance"
|
||||||
)
|
)
|
@ -3,7 +3,7 @@ package command
|
|||||||
import (
|
import (
|
||||||
"context"
|
"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/eventstore"
|
||||||
"github.com/caos/zitadel/internal/repository/instance"
|
"github.com/caos/zitadel/internal/repository/instance"
|
||||||
)
|
)
|
@ -3,7 +3,7 @@ package command
|
|||||||
import (
|
import (
|
||||||
"context"
|
"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/eventstore"
|
||||||
"github.com/caos/zitadel/internal/repository/instance"
|
"github.com/caos/zitadel/internal/repository/instance"
|
||||||
)
|
)
|
@ -173,7 +173,7 @@ func eventFromEventPusherWithInstanceID(instanceID string, event eventstore.Comm
|
|||||||
AggregateID: event.Aggregate().ID,
|
AggregateID: event.Aggregate().ID,
|
||||||
AggregateType: repository.AggregateType(event.Aggregate().Type),
|
AggregateType: repository.AggregateType(event.Aggregate().Type),
|
||||||
ResourceOwner: sql.NullString{String: event.Aggregate().ResourceOwner, Valid: event.Aggregate().ResourceOwner != ""},
|
ResourceOwner: sql.NullString{String: event.Aggregate().ResourceOwner, Valid: event.Aggregate().ResourceOwner != ""},
|
||||||
InstanceID: sql.NullString{String: instanceID, Valid: instanceID != ""},
|
InstanceID: instanceID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,14 +2,78 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"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/crypto"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
caos_errs "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"
|
||||||
|
"github.com/caos/zitadel/internal/id"
|
||||||
"github.com/caos/zitadel/internal/repository/org"
|
"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) {
|
func (c *Commands) getOrg(ctx context.Context, orgID string) (*domain.Org, error) {
|
||||||
writeModel, err := c.getOrgWriteModelByID(ctx, orgID)
|
writeModel, err := c.getOrgWriteModelByID(ctx, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2,20 +2,92 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
|
|
||||||
|
errs "errors"
|
||||||
|
|
||||||
http_utils "github.com/caos/zitadel/internal/api/http"
|
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/crypto"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"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"
|
||||||
"github.com/caos/zitadel/internal/repository/org"
|
"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) {
|
func (c *Commands) AddOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain, claimedUserIDs []string) (*domain.OrgDomain, error) {
|
||||||
if !orgDomain.IsValid() {
|
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)
|
domainWriteModel := NewOrgDomainWriteModel(orgDomain.AggregateID, orgDomain.Domain)
|
||||||
orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel)
|
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) {
|
func (c *Commands) GenerateOrgDomainValidation(ctx context.Context, orgDomain *domain.OrgDomain) (token, url string, err error) {
|
||||||
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
|
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()
|
checkType, ok := orgDomain.ValidationType.CheckType()
|
||||||
if !ok {
|
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)
|
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
if domainWriteModel.State != domain.OrgDomainStateActive {
|
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 {
|
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)
|
token, err = orgDomain.GenerateVerificationCode(c.domainVerificationGenerator)
|
||||||
if err != nil {
|
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)
|
url, err = http_utils.TokenUrl(orgDomain.Domain, token, checkType)
|
||||||
if err != nil {
|
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)
|
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) {
|
func (c *Commands) ValidateOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain, claimedUserIDs []string) (*domain.ObjectDetails, error) {
|
||||||
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
|
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)
|
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if domainWriteModel.State != domain.OrgDomainStateActive {
|
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 {
|
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 {
|
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)
|
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))
|
events = append(events, org.NewDomainVerificationFailedEvent(ctx, orgAgg, orgDomain.Domain))
|
||||||
_, err = c.eventstore.Push(ctx, events...)
|
_, err = c.eventstore.Push(ctx, events...)
|
||||||
logging.LogWithFields("ORG-dhTE", "orgID", orgAgg.ID, "domain", orgDomain.Domain).OnError(err).Error("NewDomainVerificationFailedEvent push failed")
|
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) {
|
func (c *Commands) SetPrimaryOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain) (*domain.ObjectDetails, error) {
|
||||||
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
|
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)
|
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if domainWriteModel.State != domain.OrgDomainStateActive {
|
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 {
|
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)
|
orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel)
|
||||||
pushedEvents, err := c.eventstore.Push(ctx, org.NewDomainPrimarySetEvent(ctx, orgAgg, orgDomain.Domain))
|
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) {
|
func (c *Commands) RemoveOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain) (*domain.ObjectDetails, error) {
|
||||||
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
|
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)
|
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if domainWriteModel.State != domain.OrgDomainStateActive {
|
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 {
|
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)
|
orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel)
|
||||||
pushedEvents, err := c.eventstore.Push(ctx, org.NewDomainRemovedEvent(ctx, orgAgg, orgDomain.Domain, domainWriteModel.Verified))
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
if addedDomain.State == domain.OrgDomainStateActive {
|
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{
|
events := []eventstore.Command{
|
||||||
|
@ -9,9 +9,10 @@ import (
|
|||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/http"
|
"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/crypto"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"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"
|
||||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||||
@ -21,6 +22,206 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/repository/user"
|
"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) {
|
func TestCommandSide_AddOrgDomain(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore *eventstore.Eventstore
|
||||||
@ -52,7 +253,7 @@ func TestCommandSide_AddOrgDomain(t *testing.T) {
|
|||||||
domain: &domain.OrgDomain{},
|
domain: &domain.OrgDomain{},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -86,7 +287,7 @@ func TestCommandSide_AddOrgDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorAlreadyExists,
|
err: errors.IsErrorAlreadyExists,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -187,7 +388,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -204,7 +405,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -224,7 +425,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -253,7 +454,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsNotFound,
|
err: errors.IsNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -294,7 +495,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsPreconditionFailed,
|
err: errors.IsPreconditionFailed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -462,7 +663,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -479,7 +680,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -508,7 +709,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsNotFound,
|
err: errors.IsNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -549,7 +750,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsPreconditionFailed,
|
err: errors.IsPreconditionFailed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -584,7 +785,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsPreconditionFailed,
|
err: errors.IsPreconditionFailed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -642,7 +843,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -920,7 +1121,7 @@ func TestCommandSide_SetPrimaryDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -937,7 +1138,7 @@ func TestCommandSide_SetPrimaryDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -966,7 +1167,7 @@ func TestCommandSide_SetPrimaryDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsNotFound,
|
err: errors.IsNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1000,7 +1201,7 @@ func TestCommandSide_SetPrimaryDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsPreconditionFailed,
|
err: errors.IsPreconditionFailed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1107,7 +1308,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1124,7 +1325,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1153,7 +1354,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsNotFound,
|
err: errors.IsNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1199,7 +1400,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
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 {
|
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 {
|
func validDomainVerification(domain, token, verifier string, checkType http.CheckType) error {
|
||||||
|
@ -4,24 +4,84 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"reflect"
|
"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/domain"
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"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/repository/org"
|
||||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
"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) {
|
func (c *Commands) AddOrgMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
|
||||||
if member.UserID == "" {
|
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)
|
addedMember := NewOrgMemberWriteModel(member.AggregateID, member.UserID)
|
||||||
orgAgg := OrgAggregateFromWriteModel(&addedMember.WriteModel)
|
orgAgg := OrgAggregateFromWriteModel(&addedMember.WriteModel)
|
||||||
err := c.checkUserExists(ctx, addedMember.UserID, "")
|
err := c.checkUserExists(ctx, addedMember.UserID, "")
|
||||||
if err != nil {
|
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)
|
event, err := c.addOrgMember(ctx, orgAgg, addedMember, member)
|
||||||
if err != nil {
|
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) {
|
func (c *Commands) addOrgMember(ctx context.Context, orgAgg *eventstore.Aggregate, addedMember *OrgMemberWriteModel, member *domain.Member) (eventstore.Command, error) {
|
||||||
if !member.IsValid() {
|
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 {
|
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)
|
err := c.eventstore.FilterToQueryReducer(ctx, addedMember)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -59,10 +119,10 @@ func (c *Commands) addOrgMember(ctx context.Context, orgAgg *eventstore.Aggregat
|
|||||||
//ChangeOrgMember updates an existing member
|
//ChangeOrgMember updates an existing member
|
||||||
func (c *Commands) ChangeOrgMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
|
func (c *Commands) ChangeOrgMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
|
||||||
if !member.IsValid() {
|
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 {
|
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)
|
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) {
|
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)
|
orgAgg := OrgAggregateFromWriteModel(&existingMember.MemberWriteModel.WriteModel)
|
||||||
pushedEvents, err := c.eventstore.Push(ctx, org.NewMemberChangedEvent(ctx, orgAgg, member.UserID, member.Roles...))
|
pushedEvents, err := c.eventstore.Push(ctx, org.NewMemberChangedEvent(ctx, orgAgg, member.UserID, member.Roles...))
|
||||||
|
@ -8,8 +8,9 @@ import (
|
|||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"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"
|
||||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||||
@ -19,6 +20,271 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/repository/user"
|
"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) {
|
func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore *eventstore.Eventstore
|
||||||
@ -54,7 +320,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -76,7 +342,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsPreconditionFailed,
|
err: errors.IsPreconditionFailed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -113,7 +379,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -163,7 +429,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorAlreadyExists,
|
err: errors.IsErrorAlreadyExists,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -188,7 +454,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
expectPushFailed(caos_errs.ThrowAlreadyExists(nil, "ERROR", "internal"),
|
expectPushFailed(errors.ThrowAlreadyExists(nil, "ERROR", "internal"),
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(org.NewMemberAddedEvent(context.Background(),
|
eventFromEventPusher(org.NewMemberAddedEvent(context.Background(),
|
||||||
&org.NewAggregate("org1", "org1").Aggregate,
|
&org.NewAggregate("org1", "org1").Aggregate,
|
||||||
@ -216,7 +482,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorAlreadyExists,
|
err: errors.IsErrorAlreadyExists,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -335,7 +601,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -356,7 +622,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -383,7 +649,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsNotFound,
|
err: errors.IsNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -418,7 +684,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsPreconditionFailed,
|
err: errors.IsPreconditionFailed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -530,7 +796,7 @@ func TestCommandSide_RemoveOrgMember(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -547,7 +813,7 @@ func TestCommandSide_RemoveOrgMember(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"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"
|
||||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||||
@ -20,6 +20,53 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/repository/user"
|
"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) {
|
func TestCommandSide_AddOrg(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore *eventstore.Eventstore
|
||||||
@ -57,7 +104,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -101,7 +148,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsPreconditionFailed,
|
err: errors.IsPreconditionFailed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -127,7 +174,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilterOrgMemberNotFound(),
|
expectFilterOrgMemberNotFound(),
|
||||||
expectPushFailed(caos_errs.ThrowAlreadyExists(nil, "id", "internal"),
|
expectPushFailed(errors.ThrowAlreadyExists(nil, "id", "internal"),
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(org.NewOrgAddedEvent(
|
eventFromEventPusher(org.NewOrgAddedEvent(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
@ -170,7 +217,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorAlreadyExists,
|
err: errors.IsErrorAlreadyExists,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -196,7 +243,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilterOrgMemberNotFound(),
|
expectFilterOrgMemberNotFound(),
|
||||||
expectPushFailed(caos_errs.ThrowInternal(nil, "id", "internal"),
|
expectPushFailed(errors.ThrowInternal(nil, "id", "internal"),
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(org.NewOrgAddedEvent(
|
eventFromEventPusher(org.NewOrgAddedEvent(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
@ -239,7 +286,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsInternal,
|
err: errors.IsInternal,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -374,7 +421,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
|||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -391,7 +438,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
|||||||
name: "org",
|
name: "org",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsNotFound,
|
err: errors.IsNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -409,7 +456,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
|||||||
),
|
),
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
expectPushFailed(
|
expectPushFailed(
|
||||||
caos_errs.ThrowInternal(nil, "id", "message"),
|
errors.ThrowInternal(nil, "id", "message"),
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(org.NewOrgChangedEvent(context.Background(),
|
eventFromEventPusher(org.NewOrgChangedEvent(context.Background(),
|
||||||
&org.NewAggregate("org1", "org1").Aggregate, "org", "neworg")),
|
&org.NewAggregate("org1", "org1").Aggregate, "org", "neworg")),
|
||||||
@ -425,7 +472,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
|||||||
name: "neworg",
|
name: "neworg",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsInternal,
|
err: errors.IsInternal,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -596,7 +643,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
|||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsNotFound,
|
err: errors.IsNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -622,7 +669,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
|||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsPreconditionFailed,
|
err: errors.IsPreconditionFailed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -638,7 +685,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectPushFailed(
|
expectPushFailed(
|
||||||
caos_errs.ThrowInternal(nil, "id", "message"),
|
errors.ThrowInternal(nil, "id", "message"),
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(org.NewOrgDeactivatedEvent(context.Background(),
|
eventFromEventPusher(org.NewOrgDeactivatedEvent(context.Background(),
|
||||||
&org.NewAggregate("org1", "org1").Aggregate)),
|
&org.NewAggregate("org1", "org1").Aggregate)),
|
||||||
@ -651,7 +698,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
|||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsInternal,
|
err: errors.IsInternal,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -731,7 +778,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
|||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsNotFound,
|
err: errors.IsNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -753,7 +800,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
|||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsPreconditionFailed,
|
err: errors.IsPreconditionFailed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -774,7 +821,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectPushFailed(
|
expectPushFailed(
|
||||||
caos_errs.ThrowInternal(nil, "id", "message"),
|
errors.ThrowInternal(nil, "id", "message"),
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(org.NewOrgReactivatedEvent(context.Background(),
|
eventFromEventPusher(org.NewOrgReactivatedEvent(context.Background(),
|
||||||
&org.NewAggregate("org1", "org1").Aggregate,
|
&org.NewAggregate("org1", "org1").Aggregate,
|
||||||
@ -788,7 +835,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
|||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsInternal,
|
err: errors.IsInternal,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
33
internal/command/phone.go
Normal file
33
internal/command/phone.go
Normal 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)
|
||||||
|
}
|
57
internal/command/phone_test.go
Normal file
57
internal/command/phone_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"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/eventstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,13 +18,17 @@ type Want struct {
|
|||||||
Commands []eventstore.Command
|
Commands []eventstore.Command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommandVerifier interface {
|
||||||
|
Validate(eventstore.Command) bool
|
||||||
|
}
|
||||||
|
|
||||||
//AssertValidation checks if the validation works as inteded
|
//AssertValidation checks if the validation works as inteded
|
||||||
func AssertValidation(t *testing.T, validation preparation.Validation, filter preparation.FilterToQueryReducer, want Want) {
|
func AssertValidation(t *testing.T, validation preparation.Validation, filter preparation.FilterToQueryReducer, want Want) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
creates, err := validation()
|
creates, err := validation()
|
||||||
if !errors.Is(err, want.ValidationErr) {
|
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
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -32,7 +36,7 @@ func AssertValidation(t *testing.T, validation preparation.Validation, filter pr
|
|||||||
}
|
}
|
||||||
cmds, err := creates(context.Background(), filter)
|
cmds, err := creates(context.Background(), filter)
|
||||||
if !errors.Is(err, want.CreateErr) {
|
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
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -45,6 +49,12 @@ func AssertValidation(t *testing.T, validation preparation.Validation, filter pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, cmd := range want.Commands {
|
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]) {
|
if !reflect.DeepEqual(cmd, cmds[i]) {
|
||||||
t.Errorf("unexpected command: = %v, want %v", cmds[i], cmd)
|
t.Errorf("unexpected command: = %v, want %v", cmds[i], cmd)
|
||||||
}
|
}
|
@ -2,9 +2,12 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
caos_errs "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"
|
||||||
"github.com/caos/zitadel/internal/repository/project"
|
"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
|
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) {
|
func (c *Commands) getProjectByID(ctx context.Context, projectID, resourceOwner string) (*domain.Project, error) {
|
||||||
projectWriteModel, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
|
projectWriteModel, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3,11 +3,23 @@ package command
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
"github.com/caos/zitadel/internal/repository/project"
|
"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) {
|
func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appChange domain.Application, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||||
if projectID == "" || appChange.GetAppID() == "" || appChange.GetApplicationName() == "" {
|
if projectID == "" || appChange.GetAppID() == "" || appChange.GetApplicationName() == "" {
|
||||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.App.Invalid")
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.App.Invalid")
|
||||||
|
@ -2,24 +2,84 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/crypto"
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"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"
|
||||||
"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"
|
"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) {
|
func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.APIApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) {
|
||||||
if application == nil || application.AggregateID == "" {
|
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)
|
project, err := c.getProjectByID(ctx, application.AggregateID, resourceOwner)
|
||||||
if err != nil {
|
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)
|
addedApplication := NewAPIApplicationWriteModel(application.AggregateID, resourceOwner)
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
|
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) {
|
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() {
|
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()
|
apiAppApp.AppID, err = c.idGenerator.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -51,7 +111,7 @@ func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore
|
|||||||
}
|
}
|
||||||
|
|
||||||
events = []eventstore.Command{
|
events = []eventstore.Command{
|
||||||
project.NewApplicationAddedEvent(ctx, projectAgg, apiAppApp.AppID, apiAppApp.AppName),
|
project_repo.NewApplicationAddedEvent(ctx, projectAgg, apiAppApp.AppID, apiAppApp.AppName),
|
||||||
}
|
}
|
||||||
|
|
||||||
var stringPw string
|
var stringPw string
|
||||||
@ -63,7 +123,7 @@ func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
events = append(events, project.NewAPIConfigAddedEvent(ctx,
|
events = append(events, project_repo.NewAPIConfigAddedEvent(ctx,
|
||||||
projectAgg,
|
projectAgg,
|
||||||
apiAppApp.AppID,
|
apiAppApp.AppID,
|
||||||
apiAppApp.ClientID,
|
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) {
|
func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (*domain.APIApp, error) {
|
||||||
if apiApp.AppID == "" || apiApp.AggregateID == "" {
|
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)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
if existingAPI.State == domain.AppStateUnspecified || existingAPI.State == domain.AppStateRemoved {
|
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() {
|
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)
|
projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel)
|
||||||
changedEvent, hasChanged, err := existingAPI.NewChangedEvent(
|
changedEvent, hasChanged, err := existingAPI.NewChangedEvent(
|
||||||
@ -98,7 +158,7 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !hasChanged {
|
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)
|
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) {
|
func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string, appSecretGenerator crypto.Generator) (*domain.APIApp, error) {
|
||||||
if projectID == "" || appID == "" {
|
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)
|
existingAPI, err := c.getAPIAppWriteModel(ctx, projectID, appID, resourceOwner)
|
||||||
@ -123,10 +183,10 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if existingAPI.State == domain.AppStateUnspecified || existingAPI.State == domain.AppStateRemoved {
|
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() {
|
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)
|
cryptoSecret, stringPW, err := domain.NewClientSecret(appSecretGenerator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -135,7 +195,7 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap
|
|||||||
|
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -158,13 +218,13 @@ func (c *Commands) VerifyAPIClientSecret(ctx context.Context, projectID, appID,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !app.State.Exists() {
|
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() {
|
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 {
|
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)
|
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)
|
err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.userPasswordAlg)
|
||||||
spanPasswordComparison.EndWithError(err)
|
spanPasswordComparison.EndWithError(err)
|
||||||
if err == nil {
|
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
|
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")
|
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) {
|
func (c *Commands) getAPIAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (*APIApplicationWriteModel, error) {
|
||||||
|
@ -4,9 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/crypto"
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"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"
|
||||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||||
@ -16,6 +17,118 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"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) {
|
func TestCommandSide_AddAPIApplication(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore *eventstore.Eventstore
|
||||||
@ -50,7 +163,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -73,7 +186,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsPreconditionFailed,
|
err: errors.IsPreconditionFailed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -103,7 +216,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -295,7 +408,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -318,7 +431,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -341,7 +454,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsNotFound,
|
err: errors.IsNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -381,7 +494,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsPreconditionFailed,
|
err: errors.IsPreconditionFailed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -504,7 +617,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -521,7 +634,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -539,7 +652,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsNotFound,
|
err: errors.IsNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -2,17 +2,121 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"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/crypto"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
caos_errs "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"
|
||||||
"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"
|
"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) {
|
func (c *Commands) AddOIDCApplication(ctx context.Context, application *domain.OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) {
|
||||||
if application == nil || application.AggregateID == "" {
|
if application == nil || application.AggregateID == "" {
|
||||||
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Application.Invalid")
|
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{
|
events = []eventstore.Command{
|
||||||
project.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName),
|
project_repo.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName),
|
||||||
}
|
}
|
||||||
|
|
||||||
var stringPw string
|
var stringPw string
|
||||||
@ -64,7 +168,7 @@ func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstor
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
events = append(events, project.NewOIDCConfigAddedEvent(ctx,
|
events = append(events, project_repo.NewOIDCConfigAddedEvent(ctx,
|
||||||
projectAgg,
|
projectAgg,
|
||||||
oidcApp.OIDCVersion,
|
oidcApp.OIDCVersion,
|
||||||
oidcApp.AppID,
|
oidcApp.AppID,
|
||||||
@ -164,7 +268,7 @@ func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, a
|
|||||||
|
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&existingOIDC.WriteModel)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.userPasswordAlg)
|
||||||
spanPasswordComparison.EndWithError(err)
|
spanPasswordComparison.EndWithError(err)
|
||||||
if err == nil {
|
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
|
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")
|
logging.Log("COMMAND-ADfhz").OnError(err).Error("could not push event OIDCClientSecretCheckFailed")
|
||||||
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid")
|
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid")
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,10 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/crypto"
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"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"
|
||||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||||
@ -18,6 +19,162 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/repository/project"
|
"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) {
|
func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore *eventstore.Eventstore
|
||||||
@ -52,7 +209,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -75,7 +232,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsPreconditionFailed,
|
err: errors.IsPreconditionFailed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -105,7 +262,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -274,7 +431,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -298,7 +455,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -322,7 +479,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -347,7 +504,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsNotFound,
|
err: errors.IsNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -418,7 +575,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsPreconditionFailed,
|
err: errors.IsPreconditionFailed,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -580,7 +737,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -597,7 +754,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsErrorInvalidArgument,
|
err: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -615,7 +772,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
err: caos_errs.IsNotFound,
|
err: errors.IsNotFound,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
caos_errs "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"
|
||||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||||
@ -985,3 +986,186 @@ func newProjectChangedEvent(ctx context.Context, projectID, resourceOwner, oldNa
|
|||||||
)
|
)
|
||||||
return event
|
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)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
73
internal/command/secret_generator.go
Normal file
73
internal/command/secret_generator.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -5,15 +5,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"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/logging"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
caos_errs "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/v1/models"
|
||||||
|
"github.com/caos/zitadel/internal/query"
|
||||||
"github.com/caos/zitadel/internal/repository/user"
|
"github.com/caos/zitadel/internal/repository/user"
|
||||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||||
)
|
)
|
||||||
@ -378,3 +378,38 @@ func (c *Commands) userWriteModelByID(ctx context.Context, userID, resourceOwner
|
|||||||
}
|
}
|
||||||
return writeModel, nil
|
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)
|
||||||
|
}
|
||||||
|
@ -4,12 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/command"
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"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)
|
wm, err := orgDomainPolicy(ctx, filter)
|
||||||
if err != nil || wm != nil && wm.State.Exists() {
|
if err != nil || wm != nil && wm.State.Exists() {
|
||||||
return wm, err
|
return wm, err
|
||||||
@ -21,8 +20,8 @@ func domainPolicyWriteModel(ctx context.Context, filter preparation.FilterToQuer
|
|||||||
return nil, errors.ThrowInternal(nil, "USER-Ggk9n", "Errors.Internal")
|
return nil, errors.ThrowInternal(nil, "USER-Ggk9n", "Errors.Internal")
|
||||||
}
|
}
|
||||||
|
|
||||||
func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) {
|
func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*PolicyDomainWriteModel, error) {
|
||||||
policy := command.NewOrgDomainPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
|
policy := NewOrgDomainPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
|
||||||
events, err := filter(ctx, policy.Query())
|
events, err := filter(ctx, policy.Query())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -35,8 +34,8 @@ func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReduce
|
|||||||
return &policy.PolicyDomainWriteModel, err
|
return &policy.PolicyDomainWriteModel, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func instanceDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) {
|
func instanceDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*PolicyDomainWriteModel, error) {
|
||||||
policy := command.NewInstanceDomainPolicyWriteModel(ctx)
|
policy := NewInstanceDomainPolicyWriteModel(ctx)
|
||||||
events, err := filter(ctx, policy.Query())
|
events, err := filter(ctx, policy.Query())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
@ -6,8 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/command"
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
@ -22,7 +21,7 @@ func Test_customDomainPolicy(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want *command.PolicyDomainWriteModel
|
want *PolicyDomainWriteModel
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -58,7 +57,7 @@ func Test_customDomainPolicy(t *testing.T) {
|
|||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &command.PolicyDomainWriteModel{
|
want: &PolicyDomainWriteModel{
|
||||||
WriteModel: eventstore.WriteModel{
|
WriteModel: eventstore.WriteModel{
|
||||||
AggregateID: "id",
|
AggregateID: "id",
|
||||||
ResourceOwner: "ro",
|
ResourceOwner: "ro",
|
||||||
@ -91,7 +90,7 @@ func Test_defaultDomainPolicy(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want *command.PolicyDomainWriteModel
|
want *PolicyDomainWriteModel
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -127,7 +126,7 @@ func Test_defaultDomainPolicy(t *testing.T) {
|
|||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &command.PolicyDomainWriteModel{
|
want: &PolicyDomainWriteModel{
|
||||||
WriteModel: eventstore.WriteModel{
|
WriteModel: eventstore.WriteModel{
|
||||||
AggregateID: "INSTANCE",
|
AggregateID: "INSTANCE",
|
||||||
ResourceOwner: "INSTANCE",
|
ResourceOwner: "INSTANCE",
|
||||||
@ -160,7 +159,7 @@ func Test_DomainPolicy(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want *command.PolicyDomainWriteModel
|
want *PolicyDomainWriteModel
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -186,7 +185,7 @@ func Test_DomainPolicy(t *testing.T) {
|
|||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &command.PolicyDomainWriteModel{
|
want: &PolicyDomainWriteModel{
|
||||||
WriteModel: eventstore.WriteModel{
|
WriteModel: eventstore.WriteModel{
|
||||||
AggregateID: "id",
|
AggregateID: "id",
|
||||||
ResourceOwner: "ro",
|
ResourceOwner: "ro",
|
||||||
@ -230,7 +229,7 @@ func Test_DomainPolicy(t *testing.T) {
|
|||||||
}).
|
}).
|
||||||
Filter(),
|
Filter(),
|
||||||
},
|
},
|
||||||
want: &command.PolicyDomainWriteModel{
|
want: &PolicyDomainWriteModel{
|
||||||
WriteModel: eventstore.WriteModel{
|
WriteModel: eventstore.WriteModel{
|
||||||
AggregateID: "INSTANCE",
|
AggregateID: "INSTANCE",
|
||||||
ResourceOwner: "INSTANCE",
|
ResourceOwner: "INSTANCE",
|
@ -4,13 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/crypto"
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"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/eventstore/v1/models"
|
||||||
"github.com/caos/zitadel/internal/repository/user"
|
"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) {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
if !isUserStateExists(human.UserState) {
|
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
|
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) {
|
type AddHuman struct {
|
||||||
if orgID == "" {
|
// Username is required
|
||||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-XYFk9", "Errors.ResourceOwnerMissing")
|
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)
|
userID, err := c.id.Next()
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = AppendAndReduce(addedHuman, pushedEvents...)
|
events, err := c.es.Push(ctx, cmds...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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 == "" {
|
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)
|
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
|
||||||
if err != nil {
|
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)
|
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
|
||||||
if err != nil {
|
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)
|
events, addedHuman, addedCode, code, err := c.importHuman(ctx, orgID, human, passwordless, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -89,53 +297,24 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
|
|||||||
return writeModelToHuman(addedHuman), passwordlessCode, nil
|
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) {
|
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 == "" {
|
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)
|
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
|
||||||
if err != nil {
|
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)
|
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
|
||||||
if err != nil {
|
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)
|
loginPolicy, err := c.getOrgLoginPolicy(ctx, orgID)
|
||||||
if err != nil {
|
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 {
|
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)
|
userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, link, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -171,12 +350,41 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai
|
|||||||
return writeModelToHuman(registeredHuman), nil
|
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) {
|
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 == "" {
|
if human != nil && human.Username == "" {
|
||||||
human.Username = human.EmailAddress
|
human.Username = human.EmailAddress
|
||||||
}
|
}
|
||||||
if orgID == "" || !human.IsValid() || link == nil && (human.Password == nil || human.SecretString == "") {
|
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 != "" {
|
if human.Password != nil && human.SecretString != "" {
|
||||||
human.ChangeRequired = false
|
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)
|
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 {
|
if err := human.CheckDomainPolicy(domainPolicy); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if !domainPolicy.UserLoginMustBeDomain {
|
if !domainPolicy.UserLoginMustBeDomain {
|
||||||
usernameSplit := strings.Split(human.Username, "@")
|
usernameSplit := strings.Split(human.Username, "@")
|
||||||
if len(usernameSplit) != 2 {
|
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])
|
domainCheck := NewOrgDomainVerifiedWriteModel(usernameSplit[1])
|
||||||
if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil {
|
if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if domainCheck.Verified && domainCheck.ResourceOwner != orgID {
|
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()
|
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
|
//TODO: adlerhurst maybe we could simplify the code below
|
||||||
userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel)
|
userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel)
|
||||||
var events []eventstore.Command
|
|
||||||
|
|
||||||
if selfregister {
|
if selfregister {
|
||||||
events = append(events, createRegisterHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
|
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) {
|
func (c *Commands) HumanSkipMFAInit(ctx context.Context, userID, resourceowner string) (err error) {
|
||||||
if userID == "" {
|
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)
|
existingHuman, err := c.getHumanWriteModelByID(ctx, userID, resourceowner)
|
||||||
@ -267,7 +474,7 @@ func (c *Commands) HumanSkipMFAInit(ctx context.Context, userID, resourceowner s
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !isUserStateExists(existingHuman.UserState) {
|
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,
|
_, 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 {
|
func (c *Commands) HumansSignOut(ctx context.Context, agentID string, userIDs []string) error {
|
||||||
if agentID == "" {
|
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 {
|
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)
|
events := make([]eventstore.Command, 0)
|
||||||
for _, userID := range userIDs {
|
for _, userID := range userIDs {
|
||||||
|
@ -71,7 +71,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
|
|||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "user1",
|
AggregateID: "user1",
|
||||||
},
|
},
|
||||||
PhoneNumber: "0711234567",
|
PhoneNumber: "+41711234567",
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -114,7 +114,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
|
|||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "user1",
|
AggregateID: "user1",
|
||||||
},
|
},
|
||||||
PhoneNumber: "0711234567",
|
PhoneNumber: "+41711234567",
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -172,7 +172,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
|
|||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "user1",
|
AggregateID: "user1",
|
||||||
},
|
},
|
||||||
PhoneNumber: "0719876543",
|
PhoneNumber: "+41719876543",
|
||||||
IsPhoneVerified: true,
|
IsPhoneVerified: true,
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
@ -239,7 +239,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
|
|||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "user1",
|
AggregateID: "user1",
|
||||||
},
|
},
|
||||||
PhoneNumber: "0711234567",
|
PhoneNumber: "+41711234567",
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
secretGenerator: GetMockSecretGenerator(t),
|
secretGenerator: GetMockSecretGenerator(t),
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -4,12 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/command"
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"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)
|
wm, err := customPasswordComplexityPolicy(ctx, filter)
|
||||||
if err != nil || wm != nil && wm.State.Exists() {
|
if err != nil || wm != nil && wm.State.Exists() {
|
||||||
return wm, err
|
return wm, err
|
||||||
@ -21,8 +20,8 @@ func passwordComplexityPolicyWriteModel(ctx context.Context, filter preparation.
|
|||||||
return nil, errors.ThrowInternal(nil, "USER-uQ96e", "Errors.Internal")
|
return nil, errors.ThrowInternal(nil, "USER-uQ96e", "Errors.Internal")
|
||||||
}
|
}
|
||||||
|
|
||||||
func customPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) {
|
func customPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*PasswordComplexityPolicyWriteModel, error) {
|
||||||
policy := command.NewOrgPasswordComplexityPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
|
policy := NewOrgPasswordComplexityPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
|
||||||
events, err := filter(ctx, policy.Query())
|
events, err := filter(ctx, policy.Query())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -35,8 +34,8 @@ func customPasswordComplexityPolicy(ctx context.Context, filter preparation.Filt
|
|||||||
return &policy.PasswordComplexityPolicyWriteModel, err
|
return &policy.PasswordComplexityPolicyWriteModel, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) {
|
func defaultPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*PasswordComplexityPolicyWriteModel, error) {
|
||||||
policy := command.NewInstancePasswordComplexityPolicyWriteModel(ctx)
|
policy := NewInstancePasswordComplexityPolicyWriteModel(ctx)
|
||||||
events, err := filter(ctx, policy.Query())
|
events, err := filter(ctx, policy.Query())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
@ -6,8 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/command"
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
@ -22,7 +21,7 @@ func Test_customPasswordComplexityPolicy(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want *command.PasswordComplexityPolicyWriteModel
|
want *PasswordComplexityPolicyWriteModel
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -62,7 +61,7 @@ func Test_customPasswordComplexityPolicy(t *testing.T) {
|
|||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &command.PasswordComplexityPolicyWriteModel{
|
want: &PasswordComplexityPolicyWriteModel{
|
||||||
WriteModel: eventstore.WriteModel{
|
WriteModel: eventstore.WriteModel{
|
||||||
AggregateID: "id",
|
AggregateID: "id",
|
||||||
ResourceOwner: "ro",
|
ResourceOwner: "ro",
|
||||||
@ -99,7 +98,7 @@ func Test_defaultPasswordComplexityPolicy(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want *command.PasswordComplexityPolicyWriteModel
|
want *PasswordComplexityPolicyWriteModel
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -139,7 +138,7 @@ func Test_defaultPasswordComplexityPolicy(t *testing.T) {
|
|||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &command.PasswordComplexityPolicyWriteModel{
|
want: &PasswordComplexityPolicyWriteModel{
|
||||||
WriteModel: eventstore.WriteModel{
|
WriteModel: eventstore.WriteModel{
|
||||||
AggregateID: "INSTANCE",
|
AggregateID: "INSTANCE",
|
||||||
ResourceOwner: "INSTANCE",
|
ResourceOwner: "INSTANCE",
|
||||||
@ -176,7 +175,7 @@ func Test_passwordComplexityPolicy(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want *command.PasswordComplexityPolicyWriteModel
|
want *PasswordComplexityPolicyWriteModel
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -206,7 +205,7 @@ func Test_passwordComplexityPolicy(t *testing.T) {
|
|||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &command.PasswordComplexityPolicyWriteModel{
|
want: &PasswordComplexityPolicyWriteModel{
|
||||||
WriteModel: eventstore.WriteModel{
|
WriteModel: eventstore.WriteModel{
|
||||||
AggregateID: "id",
|
AggregateID: "id",
|
||||||
ResourceOwner: "ro",
|
ResourceOwner: "ro",
|
||||||
@ -258,7 +257,7 @@ func Test_passwordComplexityPolicy(t *testing.T) {
|
|||||||
}).
|
}).
|
||||||
Filter(),
|
Filter(),
|
||||||
},
|
},
|
||||||
want: &command.PasswordComplexityPolicyWriteModel{
|
want: &PasswordComplexityPolicyWriteModel{
|
||||||
WriteModel: eventstore.WriteModel{
|
WriteModel: eventstore.WriteModel{
|
||||||
AggregateID: "INSTANCE",
|
AggregateID: "INSTANCE",
|
||||||
ResourceOwner: "INSTANCE",
|
ResourceOwner: "INSTANCE",
|
@ -11,7 +11,9 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/command/preparation"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
caos_errs "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"
|
||||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
"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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -39,7 +39,7 @@ func LoadKey(id string, keyStorage KeyStorage) (string, error) {
|
|||||||
return key.Value, nil
|
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 {
|
if config == nil {
|
||||||
return nil, nil, errors.ThrowInvalidArgument(nil, "CRYPT-dJK8s", "config must not be 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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
keys := make(map[string]string)
|
keys := make(Keys)
|
||||||
ids := make([]string, 0, len(config.DecryptionKeyIDs)+1)
|
ids := make([]string, 0, len(config.DecryptionKeyIDs)+1)
|
||||||
if config.EncryptionKeyID != "" {
|
if config.EncryptionKeyID != "" {
|
||||||
key, ok := readKeys[config.EncryptionKeyID]
|
key, ok := readKeys[config.EncryptionKeyID]
|
||||||
|
@ -146,10 +146,15 @@ func (a *OIDCApp) OriginsValid() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *OIDCApp) getRequiredGrantTypes() []OIDCGrantType {
|
func ContainsRequiredGrantTypes(responseTypes []OIDCResponseType, grantTypes []OIDCGrantType) bool {
|
||||||
grantTypes := make([]OIDCGrantType, 0)
|
required := RequiredOIDCGrantTypes(responseTypes)
|
||||||
implicit := false
|
return ContainsOIDCGrantTypes(required, grantTypes)
|
||||||
for _, r := range a.ResponseTypes {
|
}
|
||||||
|
|
||||||
|
func RequiredOIDCGrantTypes(responseTypes []OIDCResponseType) (grantTypes []OIDCGrantType) {
|
||||||
|
var implicit bool
|
||||||
|
|
||||||
|
for _, r := range responseTypes {
|
||||||
switch r {
|
switch r {
|
||||||
case OIDCResponseTypeCode:
|
case OIDCResponseTypeCode:
|
||||||
grantTypes = append(grantTypes, OIDCGrantTypeAuthorizationCode)
|
grantTypes = append(grantTypes, OIDCGrantTypeAuthorizationCode)
|
||||||
@ -160,9 +165,23 @@ func (a *OIDCApp) getRequiredGrantTypes() []OIDCGrantType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return grantTypes
|
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 {
|
func containsOIDCGrantType(grantTypes []OIDCGrantType, grantType OIDCGrantType) bool {
|
||||||
for _, gt := range grantTypes {
|
for _, gt := range grantTypes {
|
||||||
if gt == grantType {
|
if gt == grantType {
|
||||||
|
@ -8,6 +8,11 @@ import (
|
|||||||
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type HumanDetails struct {
|
||||||
|
ID string
|
||||||
|
ObjectDetails
|
||||||
|
}
|
||||||
|
|
||||||
type Human struct {
|
type Human struct {
|
||||||
es_models.ObjectRoot
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
@ -8,7 +8,9 @@ import (
|
|||||||
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
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 {
|
type Email struct {
|
||||||
es_models.ObjectRoot
|
es_models.ObjectRoot
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/crypto"
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||||
"github.com/ttacon/libphonenumber"
|
"github.com/ttacon/libphonenumber"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -22,8 +22,14 @@ const (
|
|||||||
ProjectStateActive
|
ProjectStateActive
|
||||||
ProjectStateInactive
|
ProjectStateInactive
|
||||||
ProjectStateRemoved
|
ProjectStateRemoved
|
||||||
|
|
||||||
|
projectStateMax
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s ProjectState) Valid() bool {
|
||||||
|
return s > ProjectStateUnspecified && s < projectStateMax
|
||||||
|
}
|
||||||
|
|
||||||
type PrivateLabelingSetting int32
|
type PrivateLabelingSetting int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -7,6 +7,7 @@ const (
|
|||||||
SecretGeneratorTypeInitCode
|
SecretGeneratorTypeInitCode
|
||||||
SecretGeneratorTypeVerifyEmailCode
|
SecretGeneratorTypeVerifyEmailCode
|
||||||
SecretGeneratorTypeVerifyPhoneCode
|
SecretGeneratorTypeVerifyPhoneCode
|
||||||
|
SecretGeneratorTypeVerifyDomain
|
||||||
SecretGeneratorTypePasswordResetCode
|
SecretGeneratorTypePasswordResetCode
|
||||||
SecretGeneratorTypePasswordlessInitCode
|
SecretGeneratorTypePasswordlessInitCode
|
||||||
SecretGeneratorTypeAppSecret
|
SecretGeneratorTypeAppSecret
|
||||||
@ -14,6 +15,10 @@ const (
|
|||||||
secretGeneratorTypeCount
|
secretGeneratorTypeCount
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (t SecretGeneratorType) Valid() bool {
|
||||||
|
return t > SecretGeneratorTypeUnspecified && t < secretGeneratorTypeCount
|
||||||
|
}
|
||||||
|
|
||||||
type SecretGeneratorState int32
|
type SecretGeneratorState int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -79,7 +79,7 @@ func BaseEventFromRepo(event *repository.Event) *BaseEvent {
|
|||||||
ID: event.AggregateID,
|
ID: event.AggregateID,
|
||||||
Type: AggregateType(event.AggregateType),
|
Type: AggregateType(event.AggregateType),
|
||||||
ResourceOwner: event.ResourceOwner.String,
|
ResourceOwner: event.ResourceOwner.String,
|
||||||
InstanceID: event.InstanceID.String,
|
InstanceID: event.InstanceID,
|
||||||
Version: Version(event.Version),
|
Version: Version(event.Version),
|
||||||
},
|
},
|
||||||
EventType: EventType(event.Type),
|
EventType: EventType(event.Type),
|
||||||
|
@ -82,7 +82,7 @@ func commandsToRepository(instanceID string, cmds []Command) (events []*reposito
|
|||||||
AggregateID: cmd.Aggregate().ID,
|
AggregateID: cmd.Aggregate().ID,
|
||||||
AggregateType: repository.AggregateType(cmd.Aggregate().Type),
|
AggregateType: repository.AggregateType(cmd.Aggregate().Type),
|
||||||
ResourceOwner: sql.NullString{String: cmd.Aggregate().ResourceOwner, Valid: cmd.Aggregate().ResourceOwner != ""},
|
ResourceOwner: sql.NullString{String: cmd.Aggregate().ResourceOwner, Valid: cmd.Aggregate().ResourceOwner != ""},
|
||||||
InstanceID: sql.NullString{String: instanceID, Valid: instanceID != ""},
|
InstanceID: instanceID,
|
||||||
EditorService: cmd.EditorService(),
|
EditorService: cmd.EditorService(),
|
||||||
EditorUser: cmd.EditorUser(),
|
EditorUser: cmd.EditorUser(),
|
||||||
Type: repository.EventType(cmd.Type()),
|
Type: repository.EventType(cmd.Type()),
|
||||||
@ -178,7 +178,7 @@ func (es *Eventstore) LatestSequence(ctx context.Context, queryFactory *SearchQu
|
|||||||
return es.repo.LatestSequence(ctx, query)
|
return es.repo.LatestSequence(ctx, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
type queryReducer interface {
|
type QueryReducer interface {
|
||||||
reducer
|
reducer
|
||||||
//Query returns the SearchQueryFactory for the events needed in reducer
|
//Query returns the SearchQueryFactory for the events needed in reducer
|
||||||
Query() *SearchQueryBuilder
|
Query() *SearchQueryBuilder
|
||||||
@ -186,7 +186,7 @@ type queryReducer interface {
|
|||||||
|
|
||||||
//FilterToQueryReducer filters the events based on the search query of the query function,
|
//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
|
// 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())
|
events, err := es.Filter(ctx, r.Query())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -380,7 +380,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
InstanceID: sql.NullString{String: "instanceID", Valid: true},
|
InstanceID: "instanceID",
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -418,7 +418,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
InstanceID: sql.NullString{String: "instanceID", Valid: true},
|
InstanceID: "instanceID",
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -429,7 +429,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
InstanceID: sql.NullString{String: "instanceID", Valid: true},
|
InstanceID: "instanceID",
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -585,7 +585,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "", Valid: false},
|
ResourceOwner: sql.NullString{String: "", Valid: false},
|
||||||
InstanceID: sql.NullString{String: "zitadel"},
|
InstanceID: "zitadel",
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -630,7 +630,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
InstanceID: sql.NullString{String: "zitadel"},
|
InstanceID: "zitadel",
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -641,7 +641,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
InstanceID: sql.NullString{String: "zitadel"},
|
InstanceID: "zitadel",
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -654,7 +654,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
InstanceID: sql.NullString{String: "zitadel"},
|
InstanceID: "zitadel",
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -772,7 +772,7 @@ func TestEventstore_Push(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
InstanceID: sql.NullString{String: "zitadel"},
|
InstanceID: "zitadel",
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -816,7 +816,7 @@ func TestEventstore_Push(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
InstanceID: sql.NullString{String: "zitadel"},
|
InstanceID: "zitadel",
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -827,7 +827,7 @@ func TestEventstore_Push(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
InstanceID: sql.NullString{String: "zitadel"},
|
InstanceID: "zitadel",
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -882,7 +882,7 @@ func TestEventstore_Push(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
InstanceID: sql.NullString{String: "zitadel"},
|
InstanceID: "zitadel",
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -893,7 +893,7 @@ func TestEventstore_Push(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
InstanceID: sql.NullString{String: "zitadel"},
|
InstanceID: "zitadel",
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -906,7 +906,7 @@ func TestEventstore_Push(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
InstanceID: sql.NullString{String: "zitadel"},
|
InstanceID: "zitadel",
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
|
@ -58,7 +58,7 @@ type Event struct {
|
|||||||
ResourceOwner sql.NullString
|
ResourceOwner sql.NullString
|
||||||
//InstanceID is the instance where this event belongs to
|
//InstanceID is the instance where this event belongs to
|
||||||
// use the ID of the instance
|
// use the ID of the instance
|
||||||
InstanceID sql.NullString
|
InstanceID string
|
||||||
}
|
}
|
||||||
|
|
||||||
//EventType is the description of the change
|
//EventType is the description of the change
|
||||||
|
@ -71,7 +71,7 @@ const (
|
|||||||
" $7::VARCHAR AS editor_service," +
|
" $7::VARCHAR AS editor_service," +
|
||||||
" IFNULL((resource_owner), $8::VARCHAR) AS resource_owner," +
|
" IFNULL((resource_owner), $8::VARCHAR) AS resource_owner," +
|
||||||
" $9::VARCHAR AS instance_id," +
|
" $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_sequence AS previous_aggregate_sequence," +
|
||||||
" aggregate_type_sequence AS previous_aggregate_type_sequence " +
|
" aggregate_type_sequence AS previous_aggregate_type_sequence " +
|
||||||
"FROM previous_data " +
|
"FROM previous_data " +
|
||||||
|
@ -136,7 +136,7 @@ func Test_prepareColumns(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fields: fields{
|
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("")},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -68,6 +68,7 @@ type Instance struct {
|
|||||||
DefaultLanguage language.Tag
|
DefaultLanguage language.Tag
|
||||||
SetupStarted domain.Step
|
SetupStarted domain.Step
|
||||||
SetupDone domain.Step
|
SetupDone domain.Step
|
||||||
|
Host string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) InstanceID() string {
|
func (i *Instance) InstanceID() string {
|
||||||
@ -82,6 +83,10 @@ func (i *Instance) ConsoleClientID() string {
|
|||||||
return i.ConsoleID
|
return i.ConsoleID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Instance) RequestedDomain() string {
|
||||||
|
return i.Host
|
||||||
|
}
|
||||||
|
|
||||||
type InstanceSearchQueries struct {
|
type InstanceSearchQueries struct {
|
||||||
SearchRequest
|
SearchRequest
|
||||||
Queries []SearchQuery
|
Queries []SearchQuery
|
||||||
@ -96,7 +101,7 @@ func (q *InstanceSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) Instance(ctx context.Context) (*Instance, error) {
|
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{
|
query, args, err := stmt.Where(sq.Eq{
|
||||||
InstanceColumnID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
InstanceColumnID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||||
}).ToSql()
|
}).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) {
|
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{
|
query, args, err := stmt.Where(sq.Eq{
|
||||||
InstanceColumnID.identifier(): "system", //TODO: change column to domain when available
|
InstanceColumnID.identifier(): "system", //TODO: change column to domain when available
|
||||||
}).ToSql()
|
}).ToSql()
|
||||||
@ -129,7 +134,7 @@ func (q *Queries) GetDefaultLanguage(ctx context.Context) language.Tag {
|
|||||||
return iam.DefaultLanguage
|
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(
|
return sq.Select(
|
||||||
InstanceColumnID.identifier(),
|
InstanceColumnID.identifier(),
|
||||||
InstanceColumnChangeDate.identifier(),
|
InstanceColumnChangeDate.identifier(),
|
||||||
@ -143,17 +148,17 @@ func prepareIAMQuery() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
|
|||||||
).
|
).
|
||||||
From(instanceTable.identifier()).PlaceholderFormat(sq.Dollar),
|
From(instanceTable.identifier()).PlaceholderFormat(sq.Dollar),
|
||||||
func(row *sql.Row) (*Instance, error) {
|
func(row *sql.Row) (*Instance, error) {
|
||||||
iam := new(Instance)
|
instance := &Instance{Host: host}
|
||||||
lang := ""
|
lang := ""
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&iam.ID,
|
&instance.ID,
|
||||||
&iam.ChangeDate,
|
&instance.ChangeDate,
|
||||||
&iam.Sequence,
|
&instance.Sequence,
|
||||||
&iam.GlobalOrgID,
|
&instance.GlobalOrgID,
|
||||||
&iam.IAMProjectID,
|
&instance.IAMProjectID,
|
||||||
&iam.ConsoleID,
|
&instance.ConsoleID,
|
||||||
&iam.SetupStarted,
|
&instance.SetupStarted,
|
||||||
&iam.SetupDone,
|
&instance.SetupDone,
|
||||||
&lang,
|
&lang,
|
||||||
)
|
)
|
||||||
if err != nil {
|
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")
|
return nil, errors.ThrowInternal(err, "QUERY-d9nw", "Errors.Internal")
|
||||||
}
|
}
|
||||||
iam.DefaultLanguage = language.Make(lang)
|
instance.DefaultLanguage = language.Make(lang)
|
||||||
return iam, nil
|
return instance, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
|
sq "github.com/Masterminds/squirrel"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
errs "github.com/caos/zitadel/internal/errors"
|
errs "github.com/caos/zitadel/internal/errors"
|
||||||
)
|
)
|
||||||
@ -27,7 +28,9 @@ func Test_InstancePrepares(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "prepareInstanceQuery no result",
|
name: "prepareInstanceQuery no result",
|
||||||
prepare: prepareIAMQuery,
|
prepare: func() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
|
||||||
|
return prepareInstanceQuery("")
|
||||||
|
},
|
||||||
want: want{
|
want: want{
|
||||||
sqlExpectations: mockQueries(
|
sqlExpectations: mockQueries(
|
||||||
regexp.QuoteMeta(`SELECT projections.instances.id,`+
|
regexp.QuoteMeta(`SELECT projections.instances.id,`+
|
||||||
@ -54,7 +57,9 @@ func Test_InstancePrepares(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "prepareInstanceQuery found",
|
name: "prepareInstanceQuery found",
|
||||||
prepare: prepareIAMQuery,
|
prepare: func() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
|
||||||
|
return prepareInstanceQuery("")
|
||||||
|
},
|
||||||
want: want{
|
want: want{
|
||||||
sqlExpectations: mockQuery(
|
sqlExpectations: mockQuery(
|
||||||
regexp.QuoteMeta(`SELECT projections.instances.id,`+
|
regexp.QuoteMeta(`SELECT projections.instances.id,`+
|
||||||
@ -105,7 +110,9 @@ func Test_InstancePrepares(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "prepareInstanceQuery sql err",
|
name: "prepareInstanceQuery sql err",
|
||||||
prepare: prepareIAMQuery,
|
prepare: func() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
|
||||||
|
return prepareInstanceQuery("")
|
||||||
|
},
|
||||||
want: want{
|
want: want{
|
||||||
sqlExpectations: mockQueryErr(
|
sqlExpectations: mockQueryErr(
|
||||||
regexp.QuoteMeta(`SELECT projections.instances.id,`+
|
regexp.QuoteMeta(`SELECT projections.instances.id,`+
|
||||||
|
@ -26,7 +26,7 @@ func testEvent(
|
|||||||
Version: "v1",
|
Version: "v1",
|
||||||
AggregateID: "agg-id",
|
AggregateID: "agg-id",
|
||||||
ResourceOwner: sql.NullString{String: "ro-id", Valid: true},
|
ResourceOwner: sql.NullString{String: "ro-id", Valid: true},
|
||||||
InstanceID: sql.NullString{String: "instance-id", Valid: true},
|
InstanceID: "instance-id",
|
||||||
ID: "event-id",
|
ID: "event-id",
|
||||||
EditorService: "editor-svc",
|
EditorService: "editor-svc",
|
||||||
EditorUser: "editor-user",
|
EditorUser: "editor-user",
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/eventstore/handler"
|
"github.com/caos/zitadel/internal/eventstore/handler"
|
||||||
"github.com/caos/zitadel/internal/eventstore/handler/crdb"
|
"github.com/caos/zitadel/internal/eventstore/handler/crdb"
|
||||||
"github.com/caos/zitadel/internal/repository/instance"
|
"github.com/caos/zitadel/internal/repository/instance"
|
||||||
"github.com/caos/zitadel/internal/repository/project"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -63,7 +62,7 @@ func NewSecretGeneratorProjection(ctx context.Context, config crdb.StatementHand
|
|||||||
func (p *SecretGeneratorProjection) reducers() []handler.AggregateReducer {
|
func (p *SecretGeneratorProjection) reducers() []handler.AggregateReducer {
|
||||||
return []handler.AggregateReducer{
|
return []handler.AggregateReducer{
|
||||||
{
|
{
|
||||||
Aggregate: project.AggregateType,
|
Aggregate: instance.AggregateType,
|
||||||
EventRedusers: []handler.EventReducer{
|
EventRedusers: []handler.EventReducer{
|
||||||
{
|
{
|
||||||
Event: instance.SecretGeneratorAddedEventType,
|
Event: instance.SecretGeneratorAddedEventType,
|
||||||
|
@ -73,7 +73,7 @@ func (e *ConsoleSetEvent) UniqueConstraints() []*eventstore.EventUniqueConstrain
|
|||||||
func NewIAMConsoleSetEvent(
|
func NewIAMConsoleSetEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
aggregate *eventstore.Aggregate,
|
aggregate *eventstore.Aggregate,
|
||||||
clientID string,
|
clientID *string,
|
||||||
) *ConsoleSetEvent {
|
) *ConsoleSetEvent {
|
||||||
return &ConsoleSetEvent{
|
return &ConsoleSetEvent{
|
||||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
@ -81,7 +81,7 @@ func NewIAMConsoleSetEvent(
|
|||||||
aggregate,
|
aggregate,
|
||||||
ConsoleSetEventType,
|
ConsoleSetEventType,
|
||||||
),
|
),
|
||||||
ClientID: clientID,
|
ClientID: *clientID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
func APIConfigAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||||
e := &APIConfigAddedEvent{
|
e := &APIConfigAddedEvent{
|
||||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
@ -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) {
|
func OIDCConfigAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||||
e := &OIDCConfigAddedEvent{
|
e := &OIDCConfigAddedEvent{
|
||||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user