mirror of
https://github.com/zitadel/zitadel.git
synced 2025-04-23 22: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_service TEXT NOT NULL
|
||||
, resource_owner TEXT NOT NULL
|
||||
, instance_id TEXT
|
||||
, instance_id TEXT NOT NULL
|
||||
|
||||
, PRIMARY KEY (event_sequence DESC) USING HASH WITH BUCKET_COUNT = 10
|
||||
, INDEX agg_type_agg_id (aggregate_type, aggregate_id)
|
||||
, INDEX agg_type (aggregate_type)
|
||||
, INDEX agg_type_seq (aggregate_type, event_sequence DESC)
|
||||
STORING (id, event_type, aggregate_id, aggregate_version, previous_aggregate_sequence, creation_date, event_data, editor_user, editor_service, resource_owner, instance_id, previous_aggregate_type_sequence)
|
||||
, INDEX max_sequence (aggregate_type, aggregate_id, event_sequence DESC)
|
||||
, CONSTRAINT previous_sequence_unique UNIQUE (previous_aggregate_sequence DESC)
|
||||
, CONSTRAINT prev_agg_type_seq_unique UNIQUE(previous_aggregate_type_sequence)
|
||||
, PRIMARY KEY (event_sequence DESC, instance_id) USING HASH WITH BUCKET_COUNT = 10
|
||||
, INDEX agg_type_agg_id (aggregate_type, aggregate_id, instance_id)
|
||||
, INDEX agg_type (aggregate_type, instance_id)
|
||||
, INDEX agg_type_seq (aggregate_type, event_sequence DESC, instance_id)
|
||||
STORING (id, event_type, aggregate_id, aggregate_version, previous_aggregate_sequence, creation_date, event_data, editor_user, editor_service, resource_owner, previous_aggregate_type_sequence)
|
||||
, INDEX max_sequence (aggregate_type, aggregate_id, event_sequence DESC, instance_id)
|
||||
, CONSTRAINT previous_sequence_unique UNIQUE (previous_aggregate_sequence DESC, instance_id)
|
||||
, CONSTRAINT prev_agg_type_seq_unique UNIQUE(previous_aggregate_type_sequence, instance_id)
|
||||
)
|
||||
|
@ -3,7 +3,6 @@ package setup
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -2,21 +2,62 @@ package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2"
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
crypto_db "github.com/caos/zitadel/internal/crypto/database"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
type DefaultInstance struct {
|
||||
cmd *command.Command
|
||||
InstanceSetup command.InstanceSetup
|
||||
|
||||
userEncryptionKey *crypto.KeyConfig
|
||||
masterKey string
|
||||
db *sql.DB
|
||||
es *eventstore.Eventstore
|
||||
domain string
|
||||
defaults systemdefaults.SystemDefaults
|
||||
zitadelRoles []authz.RoleMapping
|
||||
}
|
||||
|
||||
func (mig *DefaultInstance) Execute(ctx context.Context) error {
|
||||
_, err := mig.cmd.SetUpInstance(ctx, &mig.InstanceSetup)
|
||||
keyStorage, err := crypto_db.NewKeyStorage(mig.db, mig.masterKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot start key storage: %w", err)
|
||||
}
|
||||
if err = verifyKey(mig.userEncryptionKey, keyStorage); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userAlg, err := crypto.NewAESCrypto(mig.userEncryptionKey, keyStorage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := command.NewCommandV2(mig.es, mig.defaults, userAlg, mig.zitadelRoles)
|
||||
|
||||
ctx = authz.WithRequestedDomain(ctx, mig.domain)
|
||||
_, err = cmd.SetUpInstance(ctx, &mig.InstanceSetup)
|
||||
return err
|
||||
}
|
||||
|
||||
func (mig *DefaultInstance) String() string {
|
||||
return "03_default_instance"
|
||||
}
|
||||
|
||||
func verifyKey(key *crypto.KeyConfig, storage crypto.KeyStorage) (err error) {
|
||||
_, err = crypto.LoadKey(key.EncryptionKeyID, storage)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
k, err := crypto.NewKey(key.EncryptionKeyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.CreateKeys(k)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/config/hook"
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/database"
|
||||
)
|
||||
|
||||
@ -21,6 +22,7 @@ type Config struct {
|
||||
ExternalDomain string
|
||||
ExternalSecure bool
|
||||
Log *logging.Config
|
||||
EncryptionKeys *encryptionKeyConfig
|
||||
}
|
||||
|
||||
func MustNewConfig(v *viper.Viper) *Config {
|
||||
@ -40,6 +42,10 @@ type Steps struct {
|
||||
S3DefaultInstance *DefaultInstance
|
||||
}
|
||||
|
||||
type encryptionKeyConfig struct {
|
||||
User *crypto.KeyConfig
|
||||
}
|
||||
|
||||
func MustNewSteps(v *viper.Viper) *Steps {
|
||||
v.SetConfigType("yaml")
|
||||
err := v.ReadConfig(bytes.NewBuffer(defaultSteps))
|
||||
|
@ -8,8 +8,8 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/caos/zitadel/cmd/admin/key"
|
||||
http_util "github.com/caos/zitadel/internal/api/http"
|
||||
"github.com/caos/zitadel/internal/command/v2"
|
||||
"github.com/caos/zitadel/internal/database"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/migration"
|
||||
@ -31,12 +31,15 @@ Requirements:
|
||||
config := MustNewConfig(viper.GetViper())
|
||||
steps := MustNewSteps(viper.New())
|
||||
|
||||
Setup(config, steps)
|
||||
masterKey, err := key.MasterKey(cmd)
|
||||
logging.OnError(err).Panic("No master key provided")
|
||||
|
||||
Setup(config, steps, masterKey)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Setup(config *Config, steps *Steps) {
|
||||
func Setup(config *Config, steps *Steps, masterKey string) {
|
||||
dbClient, err := database.Connect(config.Database)
|
||||
logging.OnError(err).Fatal("unable to connect to database")
|
||||
|
||||
@ -44,11 +47,17 @@ func Setup(config *Config, steps *Steps) {
|
||||
logging.OnError(err).Fatal("unable to start eventstore")
|
||||
migration.RegisterMappers(eventstoreClient)
|
||||
|
||||
cmd := command.New(eventstoreClient, "localhost", config.SystemDefaults)
|
||||
|
||||
steps.s1ProjectionTable = &ProjectionTable{dbClient: dbClient}
|
||||
steps.s2AssetsTable = &AssetTable{dbClient: dbClient}
|
||||
steps.S3DefaultInstance.cmd = cmd
|
||||
steps.S3DefaultInstance.es = eventstoreClient
|
||||
steps.S3DefaultInstance.db = dbClient
|
||||
steps.S3DefaultInstance.defaults = config.SystemDefaults
|
||||
steps.S3DefaultInstance.masterKey = masterKey
|
||||
steps.S3DefaultInstance.domain = config.SystemDefaults.Domain
|
||||
steps.S3DefaultInstance.zitadelRoles = config.InternalAuthZ.RolePermissionMappings
|
||||
steps.S3DefaultInstance.userEncryptionKey = config.EncryptionKeys.User
|
||||
steps.S3DefaultInstance.InstanceSetup.Zitadel.IsDevMode = !config.ExternalSecure
|
||||
steps.S3DefaultInstance.InstanceSetup.Zitadel.BaseURL = http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
|
||||
steps.S3DefaultInstance.InstanceSetup.Zitadel.IsDevMode = !config.ExternalSecure
|
||||
steps.S3DefaultInstance.InstanceSetup.Zitadel.BaseURL = http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
|
||||
|
||||
|
@ -8,11 +8,64 @@ S3DefaultInstance:
|
||||
LastName: Admin
|
||||
NickName:
|
||||
DisplayName:
|
||||
Email: admin@zitadel.ch
|
||||
Email:
|
||||
Address: admin@zitadel.ch
|
||||
Verified: true
|
||||
PreferredLanguage:
|
||||
Gender:
|
||||
Phone:
|
||||
Number:
|
||||
Verified:
|
||||
Password: Password1!
|
||||
SecretGenerators:
|
||||
PasswordSaltCost: 14
|
||||
ClientSecret:
|
||||
Length: 64
|
||||
IncludeLowerLetters: true
|
||||
IncludeUpperLetters: true
|
||||
IncludeDigits: true
|
||||
IncludeSymbols: false
|
||||
InitializeUserCode:
|
||||
Length: 6
|
||||
Expiry: '72h'
|
||||
IncludeLowerLetters: false
|
||||
IncludeUpperLetters: true
|
||||
IncludeDigits: true
|
||||
IncludeSymbols: false
|
||||
EmailVerificationCode:
|
||||
Length: 6
|
||||
Expiry: '1h'
|
||||
IncludeLowerLetters: false
|
||||
IncludeUpperLetters: true
|
||||
IncludeDigits: true
|
||||
IncludeSymbols: false
|
||||
PhoneVerificationCode:
|
||||
Length: 6
|
||||
Expiry: '1h'
|
||||
IncludeLowerLetters: false
|
||||
IncludeUpperLetters: true
|
||||
IncludeDigits: true
|
||||
IncludeSymbols: false
|
||||
PasswordVerificationCode:
|
||||
Length: 6
|
||||
Expiry: '1h'
|
||||
IncludeLowerLetters: false
|
||||
IncludeUpperLetters: true
|
||||
IncludeDigits: true
|
||||
IncludeSymbols: false
|
||||
PasswordlessInitCode:
|
||||
Length: 12
|
||||
Expiry: '1h'
|
||||
IncludeLowerLetters: true
|
||||
IncludeUpperLetters: true
|
||||
IncludeDigits: true
|
||||
IncludeSymbols: false
|
||||
DomainVerification:
|
||||
Length: 32
|
||||
IncludeLowerLetters: true
|
||||
IncludeUpperLetters: true
|
||||
IncludeDigits: true
|
||||
IncludeSymbols: false
|
||||
Features:
|
||||
TierName: Default Tier
|
||||
TierDescription: ""
|
||||
|
@ -32,26 +32,20 @@ type encryptionKeys struct {
|
||||
OIDCKey []byte
|
||||
}
|
||||
|
||||
func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyStorage) (*encryptionKeys, error) {
|
||||
keys, err := keyStorage.ReadKeys()
|
||||
func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyStorage) (keys *encryptionKeys, err error) {
|
||||
if err := verifyDefaultKeys(keyStorage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys = new(encryptionKeys)
|
||||
keys.DomainVerification, err = crypto.NewAESCrypto(keyConfig.DomainVerification, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
if err := createDefaultKeys(keyStorage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
encryptionKeys := new(encryptionKeys)
|
||||
encryptionKeys.DomainVerification, err = crypto.NewAESCrypto(keyConfig.DomainVerification, keyStorage)
|
||||
keys.IDPConfig, err = crypto.NewAESCrypto(keyConfig.IDPConfig, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptionKeys.IDPConfig, err = crypto.NewAESCrypto(keyConfig.IDPConfig, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptionKeys.OIDC, err = crypto.NewAESCrypto(keyConfig.OIDC, keyStorage)
|
||||
keys.OIDC, err = crypto.NewAESCrypto(keyConfig.OIDC, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -59,20 +53,20 @@ func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyS
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptionKeys.OIDCKey = []byte(key)
|
||||
encryptionKeys.OTP, err = crypto.NewAESCrypto(keyConfig.OTP, keyStorage)
|
||||
keys.OIDCKey = []byte(key)
|
||||
keys.OTP, err = crypto.NewAESCrypto(keyConfig.OTP, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptionKeys.SMS, err = crypto.NewAESCrypto(keyConfig.SMS, keyStorage)
|
||||
keys.SMS, err = crypto.NewAESCrypto(keyConfig.SMS, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptionKeys.SMTP, err = crypto.NewAESCrypto(keyConfig.SMTP, keyStorage)
|
||||
keys.SMTP, err = crypto.NewAESCrypto(keyConfig.SMTP, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptionKeys.User, err = crypto.NewAESCrypto(keyConfig.User, keyStorage)
|
||||
keys.User, err = crypto.NewAESCrypto(keyConfig.User, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -80,23 +74,30 @@ func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyS
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptionKeys.CSRFCookieKey = []byte(key)
|
||||
keys.CSRFCookieKey = []byte(key)
|
||||
key, err = crypto.LoadKey(keyConfig.UserAgentCookieKeyID, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptionKeys.UserAgentCookieKey = []byte(key)
|
||||
return encryptionKeys, nil
|
||||
keys.UserAgentCookieKey = []byte(key)
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func createDefaultKeys(keyStorage crypto.KeyStorage) error {
|
||||
keys := make([]*crypto.Key, len(defaultKeyIDs))
|
||||
for i, keyID := range defaultKeyIDs {
|
||||
func verifyDefaultKeys(keyStorage crypto.KeyStorage) (err error) {
|
||||
keys := make([]*crypto.Key, 0, len(defaultKeyIDs))
|
||||
for _, keyID := range defaultKeyIDs {
|
||||
_, err := crypto.LoadKey(keyID, keyStorage)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
key, err := crypto.NewKey(keyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys[i] = key
|
||||
keys = append(keys, key)
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := keyStorage.CreateKeys(keys...); err != nil {
|
||||
return caos_errs.ThrowInternal(err, "START-aGBq2", "cannot create default keys")
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"golang.org/x/net/http2/h2c"
|
||||
|
||||
"github.com/caos/zitadel/cmd/admin/key"
|
||||
|
||||
admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing"
|
||||
"github.com/caos/zitadel/internal/api"
|
||||
"github.com/caos/zitadel/internal/api/assets"
|
||||
@ -118,7 +117,7 @@ func startZitadel(config *Config, masterKey string) error {
|
||||
Origin: http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure),
|
||||
DisplayName: "ZITADEL",
|
||||
}
|
||||
commands, err := command.StartCommands(eventstoreClient, config.SystemDefaults, config.InternalAuthZ, storage, authZRepo, webAuthNConfig, keys.IDPConfig, keys.OTP, keys.SMTP, keys.SMS, keys.DomainVerification, keys.OIDC)
|
||||
commands, err := command.StartCommands(eventstoreClient, config.SystemDefaults, config.InternalAuthZ, storage, authZRepo, webAuthNConfig, keys.IDPConfig, keys.OTP, keys.SMTP, keys.SMS, keys.User, keys.DomainVerification, keys.OIDC)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot start commands: %w", err)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package start
|
||||
import (
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/cmd/admin/initialise"
|
||||
"github.com/caos/zitadel/cmd/admin/key"
|
||||
"github.com/caos/zitadel/cmd/admin/setup"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
@ -20,16 +21,18 @@ Last ZITADEL starts.
|
||||
Requirements:
|
||||
- cockroachdb`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
masterKey, err := key.MasterKey(cmd)
|
||||
logging.OnError(err).Panic("No master key provided")
|
||||
|
||||
initialise.InitAll(initialise.MustNewConfig(viper.GetViper()))
|
||||
|
||||
setupConfig := setup.MustNewConfig(viper.GetViper())
|
||||
setupSteps := setup.MustNewSteps(viper.New())
|
||||
setup.Setup(setupConfig, setupSteps)
|
||||
setup.Setup(setupConfig, setupSteps, masterKey)
|
||||
|
||||
startConfig := MustNewConfig(viper.GetViper())
|
||||
startMasterKey, _ := cmd.Flags().GetString(flagMasterKey)
|
||||
|
||||
err := startZitadel(startConfig, startMasterKey)
|
||||
err = startZitadel(startConfig, masterKey)
|
||||
logging.OnError(err).Fatal("unable to start zitadel")
|
||||
},
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ type Instance interface {
|
||||
InstanceID() string
|
||||
ProjectID() string
|
||||
ConsoleClientID() string
|
||||
RequestedDomain() string
|
||||
}
|
||||
|
||||
type InstanceVerifier interface {
|
||||
@ -19,7 +20,8 @@ type InstanceVerifier interface {
|
||||
}
|
||||
|
||||
type instance struct {
|
||||
ID string
|
||||
ID string
|
||||
Domain string
|
||||
}
|
||||
|
||||
func (i *instance) InstanceID() string {
|
||||
@ -34,6 +36,10 @@ func (i *instance) ConsoleClientID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (i *instance) RequestedDomain() string {
|
||||
return i.Domain
|
||||
}
|
||||
|
||||
func GetInstance(ctx context.Context) Instance {
|
||||
instance, ok := ctx.Value(instanceKey).(Instance)
|
||||
if !ok {
|
||||
@ -49,3 +55,13 @@ func WithInstance(ctx context.Context, instance Instance) context.Context {
|
||||
func WithInstanceID(ctx context.Context, id string) context.Context {
|
||||
return context.WithValue(ctx, instanceKey, &instance{ID: id})
|
||||
}
|
||||
|
||||
func WithRequestedDomain(ctx context.Context, domain string) context.Context {
|
||||
i, ok := ctx.Value(instanceKey).(*instance)
|
||||
if !ok {
|
||||
i = new(instance)
|
||||
}
|
||||
|
||||
i.Domain = domain
|
||||
return context.WithValue(ctx, instanceKey, i)
|
||||
}
|
||||
|
@ -78,3 +78,7 @@ func (m *mockInstance) ProjectID() string {
|
||||
func (m *mockInstance) ConsoleClientID() string {
|
||||
return "consoleID"
|
||||
}
|
||||
|
||||
func (m *mockInstance) RequestedDomain() string {
|
||||
return "zitadel.cloud"
|
||||
}
|
||||
|
@ -28,7 +28,8 @@ type Server struct {
|
||||
userCodeAlg crypto.EncryptionAlgorithm
|
||||
}
|
||||
|
||||
func CreateServer(command *command.Commands,
|
||||
func CreateServer(
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
sd systemdefaults.SystemDefaults,
|
||||
assetAPIPrefix string,
|
||||
|
@ -4,7 +4,9 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
@ -13,9 +15,9 @@ import (
|
||||
idp_grpc "github.com/caos/zitadel/internal/api/grpc/idp"
|
||||
"github.com/caos/zitadel/internal/api/grpc/metadata"
|
||||
obj_grpc "github.com/caos/zitadel/internal/api/grpc/object"
|
||||
"github.com/caos/zitadel/internal/api/grpc/user"
|
||||
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
|
||||
z_oidc "github.com/caos/zitadel/internal/api/oidc"
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
|
||||
@ -192,24 +194,40 @@ func (s *Server) BulkRemoveUserMetadata(ctx context.Context, req *mgmt_pb.BulkRe
|
||||
}
|
||||
|
||||
func (s *Server) AddHumanUser(ctx context.Context, req *mgmt_pb.AddHumanUserRequest) (*mgmt_pb.AddHumanUserResponse, error) {
|
||||
initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.userCodeAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
human, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, AddHumanUserRequestToDomain(req), initCodeGenerator, phoneCodeGenerator)
|
||||
lang, err := language.Parse(req.Profile.PreferredLanguage)
|
||||
logging.OnError(err).Debug("unable to parse language")
|
||||
|
||||
details, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, &command.AddHuman{
|
||||
Username: req.UserName,
|
||||
FirstName: req.Profile.FirstName,
|
||||
LastName: req.Profile.LastName,
|
||||
NickName: req.Profile.NickName,
|
||||
DisplayName: req.Profile.DisplayName,
|
||||
Email: command.Email{
|
||||
Address: req.Email.Email,
|
||||
Verified: req.Email.IsEmailVerified,
|
||||
},
|
||||
PreferredLang: lang,
|
||||
Gender: user_grpc.GenderToDomain(req.Profile.Gender),
|
||||
Phone: command.Phone{
|
||||
Number: req.Phone.Phone,
|
||||
Verified: req.Phone.IsPhoneVerified,
|
||||
},
|
||||
Password: req.InitialPassword,
|
||||
PasswordChangeRequired: true,
|
||||
Passwordless: false,
|
||||
Register: false,
|
||||
ExternalIDP: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.AddHumanUserResponse{
|
||||
UserId: human.AggregateID,
|
||||
UserId: details.ID,
|
||||
Details: obj_grpc.AddToDetailsPb(
|
||||
human.Sequence,
|
||||
human.ChangeDate,
|
||||
human.ResourceOwner,
|
||||
details.Sequence,
|
||||
details.EventDate,
|
||||
details.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
@ -763,7 +781,7 @@ func (s *Server) GetPersonalAccessTokenByIDs(ctx context.Context, req *mgmt_pb.G
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetPersonalAccessTokenByIDsResponse{
|
||||
Token: user.PersonalAccessTokenToPb(token),
|
||||
Token: user_grpc.PersonalAccessTokenToPb(token),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -172,3 +172,7 @@ func (m *mockInstance) ProjectID() string {
|
||||
func (m *mockInstance) ConsoleClientID() string {
|
||||
return "consoleClientID"
|
||||
}
|
||||
|
||||
func (m *mockInstance) RequestedDomain() string {
|
||||
return "localhost"
|
||||
}
|
||||
|
@ -256,3 +256,7 @@ func (m *mockInstance) ProjectID() string {
|
||||
func (m *mockInstance) ConsoleClientID() string {
|
||||
return "consoleClientID"
|
||||
}
|
||||
|
||||
func (m *mockInstance) RequestedDomain() string {
|
||||
return "zitadel.cloud"
|
||||
}
|
||||
|
@ -48,6 +48,18 @@ type Commands struct {
|
||||
privateKeyLifetime time.Duration
|
||||
publicKeyLifetime time.Duration
|
||||
tokenVerifier orgFeatureChecker
|
||||
|
||||
v2 *commandNew
|
||||
}
|
||||
|
||||
type commandNew struct {
|
||||
es *eventstore.Eventstore
|
||||
userPasswordAlg crypto.HashAlgorithm
|
||||
phoneAlg crypto.EncryptionAlgorithm
|
||||
emailAlg crypto.EncryptionAlgorithm
|
||||
initCodeAlg crypto.EncryptionAlgorithm
|
||||
zitadelRoles []authz.RoleMapping
|
||||
id id.Generator
|
||||
}
|
||||
|
||||
type orgFeatureChecker interface {
|
||||
@ -64,6 +76,7 @@ func StartCommands(es *eventstore.Eventstore,
|
||||
otpEncryption,
|
||||
smtpEncryption,
|
||||
smsEncryption,
|
||||
userEncryption,
|
||||
domainVerificationEncryption,
|
||||
oidcEncryption crypto.EncryptionAlgorithm,
|
||||
) (repo *Commands, err error) {
|
||||
@ -81,7 +94,9 @@ func StartCommands(es *eventstore.Eventstore,
|
||||
smsCrypto: smsEncryption,
|
||||
domainVerificationAlg: domainVerificationEncryption,
|
||||
keyAlgorithm: oidcEncryption,
|
||||
v2: NewCommandV2(es, defaults, userEncryption, authZConfig.RolePermissionMappings),
|
||||
}
|
||||
|
||||
instance_repo.RegisterEventMappers(repo.eventstore)
|
||||
org.RegisterEventMappers(repo.eventstore)
|
||||
usr_repo.RegisterEventMappers(repo.eventstore)
|
||||
@ -113,6 +128,31 @@ func StartCommands(es *eventstore.Eventstore,
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
func NewCommandV2(
|
||||
es *eventstore.Eventstore,
|
||||
defaults sd.SystemDefaults,
|
||||
userAlg crypto.EncryptionAlgorithm,
|
||||
zitadelRoles []authz.RoleMapping,
|
||||
) *commandNew {
|
||||
instance_repo.RegisterEventMappers(es)
|
||||
org.RegisterEventMappers(es)
|
||||
usr_repo.RegisterEventMappers(es)
|
||||
usr_grant_repo.RegisterEventMappers(es)
|
||||
proj_repo.RegisterEventMappers(es)
|
||||
keypair.RegisterEventMappers(es)
|
||||
action.RegisterEventMappers(es)
|
||||
|
||||
return &commandNew{
|
||||
es: es,
|
||||
userPasswordAlg: crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost),
|
||||
initCodeAlg: userAlg,
|
||||
phoneAlg: userAlg,
|
||||
emailAlg: userAlg,
|
||||
zitadelRoles: zitadelRoles,
|
||||
id: id.SonyFlakeGenerator,
|
||||
}
|
||||
}
|
||||
|
||||
func AppendAndReduce(object interface {
|
||||
AppendEvents(...eventstore.Event)
|
||||
Reduce() error
|
||||
|
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/ui/console"
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
@ -53,6 +55,16 @@ type InstanceSetup struct {
|
||||
ActionsAllowed domain.ActionsAllowed
|
||||
MaxActions int
|
||||
}
|
||||
SecretGenerators struct {
|
||||
PasswordSaltCost uint
|
||||
ClientSecret *crypto.GeneratorConfig
|
||||
InitializeUserCode *crypto.GeneratorConfig
|
||||
EmailVerificationCode *crypto.GeneratorConfig
|
||||
PhoneVerificationCode *crypto.GeneratorConfig
|
||||
PasswordVerificationCode *crypto.GeneratorConfig
|
||||
PasswordlessInitCode *crypto.GeneratorConfig
|
||||
DomainVerification *crypto.GeneratorConfig
|
||||
}
|
||||
PasswordComplexityPolicy struct {
|
||||
MinLength uint64
|
||||
HasLowercase bool
|
||||
@ -110,15 +122,11 @@ type ZitadelConfig struct {
|
||||
IsDevMode bool
|
||||
BaseURL string
|
||||
|
||||
projectID string
|
||||
mgmtID string
|
||||
mgmtClientID string
|
||||
adminID string
|
||||
adminClientID string
|
||||
authID string
|
||||
authClientID string
|
||||
consoleID string
|
||||
consoleClientID string
|
||||
projectID string
|
||||
mgmtAppID string
|
||||
adminAppID string
|
||||
authAppID string
|
||||
consoleAppID string
|
||||
}
|
||||
|
||||
func (s *InstanceSetup) generateIDs() (err error) {
|
||||
@ -127,50 +135,35 @@ func (s *InstanceSetup) generateIDs() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Zitadel.mgmtID, err = id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Zitadel.mgmtClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
|
||||
s.Zitadel.mgmtAppID, err = id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Zitadel.adminID, err = id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Zitadel.adminClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
|
||||
s.Zitadel.adminAppID, err = id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Zitadel.authID, err = id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Zitadel.authClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
|
||||
s.Zitadel.authAppID, err = id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Zitadel.consoleID, err = id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Zitadel.consoleClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
|
||||
s.Zitadel.consoleAppID, err = id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (command *Command) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*domain.ObjectDetails, error) {
|
||||
func (c *commandNew) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*domain.ObjectDetails, error) {
|
||||
instanceID, err := id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx = authz.SetCtxData(authz.WithInstanceID(ctx, instanceID), authz.CtxData{OrgID: instanceID, ResourceOwner: instanceID})
|
||||
requestedDomain := authz.GetInstance(ctx).RequestedDomain()
|
||||
ctx = authz.SetCtxData(authz.WithRequestedDomain(authz.WithInstanceID(ctx, instanceID), requestedDomain), authz.CtxData{OrgID: instanceID, ResourceOwner: instanceID})
|
||||
|
||||
orgID, err := id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
@ -219,6 +212,14 @@ func (command *Command) SetUpInstance(ctx context.Context, setup *InstanceSetup)
|
||||
setup.Features.ActionsAllowed,
|
||||
setup.Features.MaxActions,
|
||||
),
|
||||
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeAppSecret, setup.SecretGenerators.ClientSecret),
|
||||
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeInitCode, setup.SecretGenerators.InitializeUserCode),
|
||||
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeVerifyEmailCode, setup.SecretGenerators.EmailVerificationCode),
|
||||
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeVerifyPhoneCode, setup.SecretGenerators.PhoneVerificationCode),
|
||||
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypePasswordResetCode, setup.SecretGenerators.PasswordVerificationCode),
|
||||
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypePasswordlessInitCode, setup.SecretGenerators.PasswordlessInitCode),
|
||||
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeVerifyDomain, setup.SecretGenerators.DomainVerification),
|
||||
|
||||
AddPasswordComplexityPolicy(
|
||||
instanceAgg,
|
||||
setup.PasswordComplexityPolicy.MinLength,
|
||||
@ -280,72 +281,82 @@ func (command *Command) SetUpInstance(ctx context.Context, setup *InstanceSetup)
|
||||
validations = append(validations, SetInstanceCustomTexts(instanceAgg, msg))
|
||||
}
|
||||
|
||||
validations = append(validations,
|
||||
AddOrg(orgAgg, setup.Org.Name, command.iamDomain),
|
||||
AddHumanCommand(userAgg, &setup.Org.Human, command.userPasswordAlg),
|
||||
AddOrgMember(orgAgg, userID, domain.RoleOrgOwner),
|
||||
AddInstanceMember(instanceAgg, userID, domain.RoleIAMOwner),
|
||||
console := &addOIDCApp{
|
||||
AddApp: AddApp{
|
||||
Aggregate: *projectAgg,
|
||||
ID: setup.Zitadel.consoleAppID,
|
||||
Name: consoleAppName,
|
||||
},
|
||||
Version: domain.OIDCVersionV1,
|
||||
RedirectUris: []string{setup.Zitadel.BaseURL + consoleRedirectPath},
|
||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||
ApplicationType: domain.OIDCApplicationTypeUserAgent,
|
||||
AuthMethodType: domain.OIDCAuthMethodTypeNone,
|
||||
PostLogoutRedirectUris: []string{setup.Zitadel.BaseURL + consolePostLogoutPath},
|
||||
DevMode: setup.Zitadel.IsDevMode,
|
||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||
AccessTokenRoleAssertion: false,
|
||||
IDTokenRoleAssertion: false,
|
||||
IDTokenUserinfoAssertion: false,
|
||||
ClockSkew: 0,
|
||||
}
|
||||
|
||||
AddProject(projectAgg, zitadelProjectName, userID, false, false, false, domain.PrivateLabelingSettingUnspecified),
|
||||
validations = append(validations,
|
||||
AddOrgCommand(ctx, orgAgg, setup.Org.Name),
|
||||
addHumanCommand(userAgg, &setup.Org.Human, c.userPasswordAlg, c.phoneAlg, c.emailAlg, c.initCodeAlg),
|
||||
c.AddOrgMember(orgAgg, userID, domain.RoleOrgOwner),
|
||||
c.AddInstanceMember(instanceAgg, userID, domain.RoleIAMOwner),
|
||||
|
||||
AddProjectCommand(projectAgg, zitadelProjectName, userID, false, false, false, domain.PrivateLabelingSettingUnspecified),
|
||||
SetIAMProject(instanceAgg, projectAgg.ID),
|
||||
|
||||
AddAPIApp(
|
||||
*projectAgg,
|
||||
setup.Zitadel.mgmtID,
|
||||
mgmtAppName,
|
||||
setup.Zitadel.mgmtClientID,
|
||||
AddAPIAppCommand(
|
||||
&addAPIApp{
|
||||
AddApp: AddApp{
|
||||
Aggregate: *projectAgg,
|
||||
ID: setup.Zitadel.mgmtAppID,
|
||||
Name: mgmtAppName,
|
||||
},
|
||||
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
|
||||
},
|
||||
nil,
|
||||
domain.APIAuthMethodTypePrivateKeyJWT,
|
||||
),
|
||||
|
||||
AddAPIApp(
|
||||
*projectAgg,
|
||||
setup.Zitadel.adminID,
|
||||
adminAppName,
|
||||
setup.Zitadel.adminClientID,
|
||||
AddAPIAppCommand(
|
||||
&addAPIApp{
|
||||
AddApp: AddApp{
|
||||
Aggregate: *projectAgg,
|
||||
ID: setup.Zitadel.adminAppID,
|
||||
Name: adminAppName,
|
||||
},
|
||||
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
|
||||
},
|
||||
nil,
|
||||
domain.APIAuthMethodTypePrivateKeyJWT,
|
||||
),
|
||||
|
||||
AddAPIApp(
|
||||
*projectAgg,
|
||||
setup.Zitadel.authID,
|
||||
authAppName,
|
||||
setup.Zitadel.authClientID,
|
||||
AddAPIAppCommand(
|
||||
&addAPIApp{
|
||||
AddApp: AddApp{
|
||||
Aggregate: *projectAgg,
|
||||
ID: setup.Zitadel.authAppID,
|
||||
Name: authAppName,
|
||||
},
|
||||
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
|
||||
},
|
||||
nil,
|
||||
domain.APIAuthMethodTypePrivateKeyJWT,
|
||||
),
|
||||
|
||||
AddOIDCApp(
|
||||
*projectAgg,
|
||||
domain.OIDCVersionV1,
|
||||
setup.Zitadel.consoleID,
|
||||
consoleAppName,
|
||||
setup.Zitadel.consoleClientID,
|
||||
nil,
|
||||
[]string{setup.Zitadel.BaseURL + consoleRedirectPath},
|
||||
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||
domain.OIDCApplicationTypeUserAgent,
|
||||
domain.OIDCAuthMethodTypeNone,
|
||||
[]string{setup.Zitadel.BaseURL + consolePostLogoutPath},
|
||||
setup.Zitadel.IsDevMode,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
nil,
|
||||
),
|
||||
SetIAMConsoleID(instanceAgg, setup.Zitadel.consoleClientID),
|
||||
AddOIDCAppCommand(console, nil),
|
||||
SetIAMConsoleID(instanceAgg, &console.ClientID),
|
||||
)
|
||||
|
||||
cmds, err := preparation.PrepareCommands(ctx, command.es.Filter, validations...)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.es.Filter, validations...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := command.es.Push(ctx, cmds...)
|
||||
events, err := c.es.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -368,7 +379,7 @@ func SetIAMProject(a *instance.Aggregate, projectID string) preparation.Validati
|
||||
}
|
||||
|
||||
//SetIAMConsoleID defines the command to set the clientID of the Console App onto the instance
|
||||
func SetIAMConsoleID(a *instance.Aggregate, clientID string) preparation.Validation {
|
||||
func SetIAMConsoleID(a *instance.Aggregate, clientID *string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
return []eventstore.Command{
|
||||
@ -377,3 +388,25 @@ func SetIAMConsoleID(a *instance.Aggregate, clientID string) preparation.Validat
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) setGlobalOrg(ctx context.Context, iamAgg *eventstore.Aggregate, iamWriteModel *InstanceWriteModel, orgID string) (eventstore.Command, error) {
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, iamWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if iamWriteModel.GlobalOrgID != "" {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "IAM-HGG24", "Errors.IAM.GlobalOrgAlreadySet")
|
||||
}
|
||||
return instance.NewGlobalOrgSetEventEvent(ctx, iamAgg, orgID), nil
|
||||
}
|
||||
|
||||
func (c *Commands) setIAMProject(ctx context.Context, iamAgg *eventstore.Aggregate, iamWriteModel *InstanceWriteModel, projectID string) (eventstore.Command, error) {
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, iamWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if iamWriteModel.ProjectID != "" {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "IAM-EGbw2", "Errors.IAM.IAMProjectAlreadySet")
|
||||
}
|
||||
return instance.NewIAMProjectSetEvent(ctx, iamAgg, projectID), nil
|
||||
}
|
@ -5,12 +5,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
|
||||
@ -52,7 +51,7 @@ func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain stri
|
||||
func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain string, generated bool) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-28nlD", "Errors.Invalid.Argument")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INST-28nlD", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
|
||||
@ -60,7 +59,7 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
|
||||
return nil, err
|
||||
}
|
||||
if domainWriteModel.State == domain.InstanceDomainStateActive {
|
||||
return nil, caos_errs.ThrowAlreadyExists(nil, "INST-i2nl", "Errors.Instance.Domain.AlreadyExists")
|
||||
return nil, errors.ThrowAlreadyExists(nil, "INST-i2nl", "Errors.Instance.Domain.AlreadyExists")
|
||||
}
|
||||
return []eventstore.Command{instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated)}, nil
|
||||
}, nil
|
||||
@ -70,7 +69,7 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
|
||||
func (c *Commands) removeInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-39nls", "Errors.Invalid.Argument")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INST-39nls", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
|
||||
@ -78,10 +77,10 @@ func (c *Commands) removeInstanceDomain(a *instance.Aggregate, instanceDomain st
|
||||
return nil, err
|
||||
}
|
||||
if domainWriteModel.State != domain.InstanceDomainStateActive {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-8ls9f", "Errors.Instance.Domain.NotFound")
|
||||
return nil, errors.ThrowNotFound(nil, "INSTANCE-8ls9f", "Errors.Instance.Domain.NotFound")
|
||||
}
|
||||
if domainWriteModel.Generated {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-9hn3n", "Errors.Instance.Domain.GeneratedNotRemovable")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "INSTANCE-9hn3n", "Errors.Instance.Domain.GeneratedNotRemovable")
|
||||
}
|
||||
return []eventstore.Command{instance.NewDomainRemovedEvent(ctx, &a.Aggregate, instanceDomain)}, nil
|
||||
}, nil
|
||||
|
@ -3,7 +3,7 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
@ -5,8 +5,7 @@ import (
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
@ -95,8 +94,8 @@ func SetInstanceCustomTexts(
|
||||
}
|
||||
}
|
||||
|
||||
func existingInstanceCustomMessageText(ctx context.Context, filter preparation.FilterToQueryReducer, textType string, lang language.Tag) (*command.InstanceCustomMessageTextWriteModel, error) {
|
||||
writeModel := command.NewInstanceCustomMessageTextWriteModel(ctx, textType, lang)
|
||||
func existingInstanceCustomMessageText(ctx context.Context, filter preparation.FilterToQueryReducer, textType string, lang language.Tag) (*InstanceCustomMessageTextWriteModel, error) {
|
||||
writeModel := NewInstanceCustomMessageTextWriteModel(ctx, textType, lang)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
@ -2,13 +2,97 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
func SetDefaultFeatures(
|
||||
a *instance.Aggregate,
|
||||
tierName,
|
||||
tierDescription string,
|
||||
state domain.FeaturesState,
|
||||
stateDescription string,
|
||||
retention time.Duration,
|
||||
loginPolicyFactors,
|
||||
loginPolicyIDP,
|
||||
loginPolicyPasswordless,
|
||||
loginPolicyRegistration,
|
||||
loginPolicyUsernameLogin,
|
||||
loginPolicyPasswordReset,
|
||||
passwordComplexityPolicy,
|
||||
labelPolicyPrivateLabel,
|
||||
labelPolicyWatermark,
|
||||
customDomain,
|
||||
privacyPolicy,
|
||||
metadataUser,
|
||||
customTextMessage,
|
||||
customTextLogin,
|
||||
lockoutPolicy bool,
|
||||
actionsAllowed domain.ActionsAllowed,
|
||||
maxActions int,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if !state.Valid() || state == domain.FeaturesStateUnspecified || state == domain.FeaturesStateRemoved {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INSTA-d3r1s", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel, err := defaultFeatures(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
event, hasChanged := writeModel.NewSetEvent(ctx, &a.Aggregate,
|
||||
tierName,
|
||||
tierDescription,
|
||||
state,
|
||||
stateDescription,
|
||||
retention,
|
||||
loginPolicyFactors,
|
||||
loginPolicyIDP,
|
||||
loginPolicyPasswordless,
|
||||
loginPolicyRegistration,
|
||||
loginPolicyUsernameLogin,
|
||||
loginPolicyPasswordReset,
|
||||
passwordComplexityPolicy,
|
||||
labelPolicyPrivateLabel,
|
||||
labelPolicyWatermark,
|
||||
customDomain,
|
||||
privacyPolicy,
|
||||
metadataUser,
|
||||
customTextMessage,
|
||||
customTextLogin,
|
||||
lockoutPolicy,
|
||||
actionsAllowed,
|
||||
maxActions,
|
||||
)
|
||||
if !hasChanged {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "INSTA-GE4h2", "Errors.Features.NotChanged")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
event,
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func defaultFeatures(ctx context.Context, filter preparation.FilterToQueryReducer) (*InstanceFeaturesWriteModel, error) {
|
||||
features := NewInstanceFeaturesWriteModel(ctx)
|
||||
events, err := filter(ctx, features.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return features, nil
|
||||
}
|
||||
features.AppendEvents(events...)
|
||||
err = features.Reduce()
|
||||
return features, err
|
||||
}
|
||||
|
||||
func (c *Commands) SetDefaultFeatures(ctx context.Context, features *domain.Features) (*domain.ObjectDetails, error) {
|
||||
existingFeatures := NewInstanceFeaturesWriteModel(ctx)
|
||||
setEvent, err := c.setDefaultFeatures(ctx, existingFeatures, features)
|
||||
@ -59,7 +143,7 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *Ins
|
||||
features.MaxActions,
|
||||
)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
|
||||
}
|
||||
return setEvent, nil
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
@ -3,7 +3,7 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
@ -4,15 +4,71 @@ import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
func (c *commandNew) AddInstanceMember(a *instance.Aggregate, userID string, roles ...string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if userID == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INSTA-SDSfs", "Errors.Invalid.Argument")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(roles, domain.IAMRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INSTANCE-4m0fS", "Errors.IAM.MemberInvalid")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
if exists, err := ExistsUser(ctx, filter, userID, ""); err != nil || !exists {
|
||||
return nil, errors.ThrowNotFound(err, "INSTA-GSXOn", "Errors.User.NotFound")
|
||||
}
|
||||
if isMember, err := IsInstanceMember(ctx, filter, a.ID, userID); err != nil || isMember {
|
||||
return nil, errors.ThrowAlreadyExists(err, "INSTA-pFDwe", "Errors.Instance.Member.AlreadyExists")
|
||||
}
|
||||
return []eventstore.Command{instance.NewMemberAddedEvent(ctx, &a.Aggregate, userID, roles...)}, nil
|
||||
},
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
func IsInstanceMember(ctx context.Context, filter preparation.FilterToQueryReducer, instanceID, userID string) (isMember bool, err error) {
|
||||
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
OrderAsc().
|
||||
AddQuery().
|
||||
AggregateIDs(instanceID).
|
||||
AggregateTypes(instance.AggregateType).
|
||||
EventTypes(
|
||||
instance.MemberAddedEventType,
|
||||
instance.MemberRemovedEventType,
|
||||
instance.MemberCascadeRemovedEventType,
|
||||
).Builder())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *instance.MemberAddedEvent:
|
||||
if e.UserID == userID {
|
||||
isMember = true
|
||||
}
|
||||
case *instance.MemberRemovedEvent:
|
||||
if e.UserID == userID {
|
||||
isMember = false
|
||||
}
|
||||
case *instance.MemberCascadeRemovedEvent:
|
||||
if e.UserID == userID {
|
||||
isMember = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isMember, nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddInstanceMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
|
||||
if member.UserID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INSTANCE-Mf83b", "Errors.IAM.MemberInvalid")
|
||||
|
@ -3,7 +3,7 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
@ -3,7 +3,7 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
@ -3,7 +3,7 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
@ -173,7 +173,7 @@ func eventFromEventPusherWithInstanceID(instanceID string, event eventstore.Comm
|
||||
AggregateID: event.Aggregate().ID,
|
||||
AggregateType: repository.AggregateType(event.Aggregate().Type),
|
||||
ResourceOwner: sql.NullString{String: event.Aggregate().ResourceOwner, Valid: event.Aggregate().ResourceOwner != ""},
|
||||
InstanceID: sql.NullString{String: instanceID, Valid: instanceID != ""},
|
||||
InstanceID: instanceID,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,14 +2,78 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
user_repo "github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
type OrgSetup struct {
|
||||
Name string
|
||||
Human AddHuman
|
||||
}
|
||||
|
||||
func (c *commandNew) SetUpOrg(ctx context.Context, o *OrgSetup) (*domain.ObjectDetails, error) {
|
||||
orgID, err := id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userID, err := id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgAgg := org.NewAggregate(orgID, orgID)
|
||||
userAgg := user_repo.NewAggregate(userID, orgID)
|
||||
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.es.Filter,
|
||||
AddOrgCommand(ctx, orgAgg, o.Name),
|
||||
addHumanCommand(userAgg, &o.Human, c.userPasswordAlg, c.phoneAlg, c.emailAlg, c.initCodeAlg),
|
||||
c.AddOrgMember(orgAgg, userID, domain.RoleOrgOwner),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := c.es.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: orgID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//AddOrgCommand defines the commands to create a new org,
|
||||
// this includes the verified default domain
|
||||
func AddOrgCommand(ctx context.Context, a *org.Aggregate, name string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if name = strings.TrimSpace(name); name == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-mruNY", "Errors.Invalid.Argument")
|
||||
}
|
||||
defaultDomain := domain.NewIAMDomainName(name, authz.GetInstance(ctx).RequestedDomain())
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
return []eventstore.Command{
|
||||
org.NewOrgAddedEvent(ctx, &a.Aggregate, name),
|
||||
org.NewDomainAddedEvent(ctx, &a.Aggregate, defaultDomain),
|
||||
org.NewDomainVerifiedEvent(ctx, &a.Aggregate, defaultDomain),
|
||||
org.NewDomainPrimarySetEvent(ctx, &a.Aggregate, defaultDomain),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) getOrg(ctx context.Context, orgID string) (*domain.Org, error) {
|
||||
writeModel, err := c.getOrgWriteModelByID(ctx, orgID)
|
||||
if err != nil {
|
||||
|
@ -2,20 +2,92 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
errs "errors"
|
||||
|
||||
http_utils "github.com/caos/zitadel/internal/api/http"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func AddOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if domain = strings.TrimSpace(domain); domain == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-r3h4J", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
existing, err := orgDomain(ctx, filter, a.ID, domain)
|
||||
if err != nil && !errs.Is(err, errors.ThrowNotFound(nil, "", "")) {
|
||||
return nil, err
|
||||
}
|
||||
if existing != nil && existing.Verified {
|
||||
return nil, errors.ThrowAlreadyExists(nil, "V2-e1wse", "Errors.Already.Exists")
|
||||
}
|
||||
return []eventstore.Command{org.NewDomainAddedEvent(ctx, &a.Aggregate, domain)}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func VerifyOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if domain = strings.TrimSpace(domain); domain == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-yqlVQ", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
// no checks required because unique constraints handle it
|
||||
return []eventstore.Command{org.NewDomainVerifiedEvent(ctx, &a.Aggregate, domain)}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func SetPrimaryOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if domain = strings.TrimSpace(domain); domain == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-gmNqY", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
existing, err := orgDomain(ctx, filter, a.ID, domain)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowAlreadyExists(err, "V2-d0Gyw", "Errors.Already.Exists")
|
||||
}
|
||||
if existing.Primary {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "COMMA-FfoZO", "Errors.Org.DomainAlreadyPrimary")
|
||||
}
|
||||
if !existing.Verified {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "COMMA-yKA80", "Errors.Org.DomainNotVerified")
|
||||
}
|
||||
return []eventstore.Command{org.NewDomainPrimarySetEvent(ctx, &a.Aggregate, domain)}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func orgDomain(ctx context.Context, filter preparation.FilterToQueryReducer, orgID, domain string) (*OrgDomainWriteModel, error) {
|
||||
wm := NewOrgDomainWriteModel(orgID, domain)
|
||||
events, err := filter(ctx, wm.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return nil, errors.ThrowNotFound(nil, "COMMA-kFHpQ", "Errors.Org.DomainNotFound")
|
||||
}
|
||||
wm.AppendEvents(events...)
|
||||
if err = wm.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain, claimedUserIDs []string) (*domain.OrgDomain, error) {
|
||||
if !orgDomain.IsValid() {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
|
||||
}
|
||||
domainWriteModel := NewOrgDomainWriteModel(orgDomain.AggregateID, orgDomain.Domain)
|
||||
orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel)
|
||||
@ -36,21 +108,21 @@ func (c *Commands) AddOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain
|
||||
|
||||
func (c *Commands) GenerateOrgDomainValidation(ctx context.Context, orgDomain *domain.OrgDomain) (token, url string, err error) {
|
||||
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
|
||||
return "", "", caos_errs.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
|
||||
return "", "", errors.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
|
||||
}
|
||||
checkType, ok := orgDomain.ValidationType.CheckType()
|
||||
if !ok {
|
||||
return "", "", caos_errs.ThrowInvalidArgument(nil, "ORG-Gsw31", "Errors.Org.DomainVerificationTypeInvalid")
|
||||
return "", "", errors.ThrowInvalidArgument(nil, "ORG-Gsw31", "Errors.Org.DomainVerificationTypeInvalid")
|
||||
}
|
||||
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if domainWriteModel.State != domain.OrgDomainStateActive {
|
||||
return "", "", caos_errs.ThrowNotFound(nil, "ORG-AGD31", "Errors.Org.DomainNotOnOrg")
|
||||
return "", "", errors.ThrowNotFound(nil, "ORG-AGD31", "Errors.Org.DomainNotOnOrg")
|
||||
}
|
||||
if domainWriteModel.Verified {
|
||||
return "", "", caos_errs.ThrowPreconditionFailed(nil, "ORG-HGw21", "Errors.Org.DomainAlreadyVerified")
|
||||
return "", "", errors.ThrowPreconditionFailed(nil, "ORG-HGw21", "Errors.Org.DomainAlreadyVerified")
|
||||
}
|
||||
token, err = orgDomain.GenerateVerificationCode(c.domainVerificationGenerator)
|
||||
if err != nil {
|
||||
@ -58,7 +130,7 @@ func (c *Commands) GenerateOrgDomainValidation(ctx context.Context, orgDomain *d
|
||||
}
|
||||
url, err = http_utils.TokenUrl(orgDomain.Domain, token, checkType)
|
||||
if err != nil {
|
||||
return "", "", caos_errs.ThrowPreconditionFailed(err, "ORG-Bae21", "Errors.Org.DomainVerificationTypeInvalid")
|
||||
return "", "", errors.ThrowPreconditionFailed(err, "ORG-Bae21", "Errors.Org.DomainVerificationTypeInvalid")
|
||||
}
|
||||
|
||||
orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel)
|
||||
@ -74,20 +146,20 @@ func (c *Commands) GenerateOrgDomainValidation(ctx context.Context, orgDomain *d
|
||||
|
||||
func (c *Commands) ValidateOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain, claimedUserIDs []string) (*domain.ObjectDetails, error) {
|
||||
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
|
||||
}
|
||||
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if domainWriteModel.State != domain.OrgDomainStateActive {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "ORG-Sjdi3", "Errors.Org.DomainNotOnOrg")
|
||||
return nil, errors.ThrowNotFound(nil, "ORG-Sjdi3", "Errors.Org.DomainNotOnOrg")
|
||||
}
|
||||
if domainWriteModel.Verified {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-HGw21", "Errors.Org.DomainAlreadyVerified")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "ORG-HGw21", "Errors.Org.DomainAlreadyVerified")
|
||||
}
|
||||
if domainWriteModel.ValidationCode == nil || domainWriteModel.ValidationType == domain.OrgDomainValidationTypeUnspecified {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-SFBB3", "Errors.Org.DomainVerificationMissing")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "ORG-SFBB3", "Errors.Org.DomainVerificationMissing")
|
||||
}
|
||||
|
||||
validationCode, err := crypto.DecryptString(domainWriteModel.ValidationCode, c.domainVerificationAlg)
|
||||
@ -122,22 +194,22 @@ func (c *Commands) ValidateOrgDomain(ctx context.Context, orgDomain *domain.OrgD
|
||||
events = append(events, org.NewDomainVerificationFailedEvent(ctx, orgAgg, orgDomain.Domain))
|
||||
_, err = c.eventstore.Push(ctx, events...)
|
||||
logging.LogWithFields("ORG-dhTE", "orgID", orgAgg.ID, "domain", orgDomain.Domain).OnError(err).Error("NewDomainVerificationFailedEvent push failed")
|
||||
return nil, caos_errs.ThrowInvalidArgument(err, "ORG-GH3s", "Errors.Org.DomainVerificationFailed")
|
||||
return nil, errors.ThrowInvalidArgument(err, "ORG-GH3s", "Errors.Org.DomainVerificationFailed")
|
||||
}
|
||||
|
||||
func (c *Commands) SetPrimaryOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain) (*domain.ObjectDetails, error) {
|
||||
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SsDG2", "Errors.Org.InvalidDomain")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-SsDG2", "Errors.Org.InvalidDomain")
|
||||
}
|
||||
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if domainWriteModel.State != domain.OrgDomainStateActive {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "ORG-GDfA3", "Errors.Org.DomainNotOnOrg")
|
||||
return nil, errors.ThrowNotFound(nil, "ORG-GDfA3", "Errors.Org.DomainNotOnOrg")
|
||||
}
|
||||
if !domainWriteModel.Verified {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-Ggd32", "Errors.Org.DomainNotVerified")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "ORG-Ggd32", "Errors.Org.DomainNotVerified")
|
||||
}
|
||||
orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, org.NewDomainPrimarySetEvent(ctx, orgAgg, orgDomain.Domain))
|
||||
@ -153,17 +225,17 @@ func (c *Commands) SetPrimaryOrgDomain(ctx context.Context, orgDomain *domain.Or
|
||||
|
||||
func (c *Commands) RemoveOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain) (*domain.ObjectDetails, error) {
|
||||
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SJsK3", "Errors.Org.InvalidDomain")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-SJsK3", "Errors.Org.InvalidDomain")
|
||||
}
|
||||
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if domainWriteModel.State != domain.OrgDomainStateActive {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "ORG-GDfA3", "Errors.Org.DomainNotOnOrg")
|
||||
return nil, errors.ThrowNotFound(nil, "ORG-GDfA3", "Errors.Org.DomainNotOnOrg")
|
||||
}
|
||||
if domainWriteModel.Primary {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-Sjdi3", "Errors.Org.PrimaryDomainNotDeletable")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "ORG-Sjdi3", "Errors.Org.PrimaryDomainNotDeletable")
|
||||
}
|
||||
orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, org.NewDomainRemovedEvent(ctx, orgAgg, orgDomain.Domain, domainWriteModel.Verified))
|
||||
@ -183,7 +255,7 @@ func (c *Commands) addOrgDomain(ctx context.Context, orgAgg *eventstore.Aggregat
|
||||
return nil, err
|
||||
}
|
||||
if addedDomain.State == domain.OrgDomainStateActive {
|
||||
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMA-Bd2jj", "Errors.Org.Domain.AlreadyExists")
|
||||
return nil, errors.ThrowAlreadyExists(nil, "COMMA-Bd2jj", "Errors.Org.Domain.AlreadyExists")
|
||||
}
|
||||
|
||||
events := []eventstore.Command{
|
||||
|
@ -9,9 +9,10 @@ import (
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/http"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
@ -21,6 +22,206 @@ import (
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestAddDomain(t *testing.T) {
|
||||
type args struct {
|
||||
a *org.Aggregate
|
||||
domain string
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
|
||||
agg := org.NewAggregate("test", "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid domain",
|
||||
args: args{
|
||||
a: agg,
|
||||
domain: "",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-r3h4J", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: agg,
|
||||
domain: "domain",
|
||||
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
org.NewDomainAddedEvent(context.Background(), &agg.Aggregate, "domain"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "already verified",
|
||||
args: args{
|
||||
a: agg,
|
||||
domain: "domain",
|
||||
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain"),
|
||||
org.NewDomainVerificationAddedEvent(ctx, &agg.Aggregate, "domain", domain.OrgDomainValidationTypeHTTP, nil),
|
||||
org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "domain"),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
CreateErr: errors.ThrowAlreadyExists(nil, "", ""),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, AddOrgDomain(tt.args.a, tt.args.domain), tt.args.filter, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyDomain(t *testing.T) {
|
||||
type args struct {
|
||||
a *org.Aggregate
|
||||
domain string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid domain",
|
||||
args: args{
|
||||
a: org.NewAggregate("test", "test"),
|
||||
domain: "",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-yqlVQ", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: org.NewAggregate("test", "test"),
|
||||
domain: "domain",
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("test", "test").Aggregate, "domain"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, VerifyOrgDomain(tt.args.a, tt.args.domain), nil, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDomainPrimary(t *testing.T) {
|
||||
type args struct {
|
||||
a *org.Aggregate
|
||||
domain string
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
|
||||
agg := org.NewAggregate("test", "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid domain",
|
||||
args: args{
|
||||
a: agg,
|
||||
domain: "",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-gmNqY", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not exists",
|
||||
args: args{
|
||||
a: agg,
|
||||
domain: "domain",
|
||||
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
CreateErr: errors.ThrowNotFound(nil, "", ""),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not verified",
|
||||
args: args{
|
||||
a: agg,
|
||||
domain: "domain",
|
||||
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain")}, nil
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
CreateErr: errors.ThrowPreconditionFailed(nil, "", ""),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "already primary",
|
||||
args: args{
|
||||
a: agg,
|
||||
domain: "domain",
|
||||
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain"),
|
||||
org.NewDomainVerificationAddedEvent(ctx, &agg.Aggregate, "domain", domain.OrgDomainValidationTypeHTTP, nil),
|
||||
org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "domain"),
|
||||
org.NewDomainPrimarySetEvent(ctx, &agg.Aggregate, "domain"),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
CreateErr: errors.ThrowPreconditionFailed(nil, "", ""),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: agg,
|
||||
domain: "domain",
|
||||
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain"),
|
||||
org.NewDomainVerificationAddedEvent(ctx, &agg.Aggregate, "domain", domain.OrgDomainValidationTypeHTTP, nil),
|
||||
org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "domain"),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
org.NewDomainPrimarySetEvent(context.Background(), &agg.Aggregate, "domain"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, SetPrimaryOrgDomain(tt.args.a, tt.args.domain), tt.args.filter, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddOrgDomain(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
@ -52,7 +253,7 @@ func TestCommandSide_AddOrgDomain(t *testing.T) {
|
||||
domain: &domain.OrgDomain{},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -86,7 +287,7 @@ func TestCommandSide_AddOrgDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorAlreadyExists,
|
||||
err: errors.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -187,7 +388,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -204,7 +405,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -224,7 +425,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -253,7 +454,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -294,7 +495,7 @@ func TestCommandSide_GenerateOrgDomainValidation(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -462,7 +663,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -479,7 +680,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -508,7 +709,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -549,7 +750,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -584,7 +785,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -642,7 +843,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -920,7 +1121,7 @@ func TestCommandSide_SetPrimaryDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -937,7 +1138,7 @@ func TestCommandSide_SetPrimaryDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -966,7 +1167,7 @@ func TestCommandSide_SetPrimaryDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1000,7 +1201,7 @@ func TestCommandSide_SetPrimaryDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1107,7 +1308,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1124,7 +1325,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1153,7 +1354,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1199,7 +1400,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1318,7 +1519,7 @@ func TestCommandSide_RemoveOrgDomain(t *testing.T) {
|
||||
}
|
||||
|
||||
func invalidDomainVerification(domain, token, verifier string, checkType http.CheckType) error {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "HTTP-GH422", "Errors.Internal")
|
||||
return errors.ThrowInvalidArgument(nil, "HTTP-GH422", "Errors.Internal")
|
||||
}
|
||||
|
||||
func validDomainVerification(domain, token, verifier string, checkType http.CheckType) error {
|
||||
|
@ -4,24 +4,84 @@ import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
func (c *commandNew) AddOrgMember(a *org.Aggregate, userID string, roles ...string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if userID == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument")
|
||||
}
|
||||
if len(roles) == 0 {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "V2-PfYhb", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
if len(domain.CheckForInvalidRoles(roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 && len(domain.CheckForInvalidRoles(roles, domain.RoleSelfManagementGlobal, c.zitadelRoles)) > 0 {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "Org-4N8es", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
if exists, err := ExistsUser(ctx, filter, userID, a.ID); err != nil || !exists {
|
||||
return nil, errors.ThrowNotFound(err, "ORG-GoXOn", "Errors.User.NotFound")
|
||||
}
|
||||
if isMember, err := IsOrgMember(ctx, filter, a.ID, userID); err != nil || isMember {
|
||||
return nil, errors.ThrowAlreadyExists(err, "ORG-poWwe", "Errors.Org.Member.AlreadyExists")
|
||||
}
|
||||
return []eventstore.Command{org.NewMemberAddedEvent(ctx, &a.Aggregate, userID, roles...)}, nil
|
||||
},
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
func IsOrgMember(ctx context.Context, filter preparation.FilterToQueryReducer, orgID, userID string) (isMember bool, err error) {
|
||||
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(orgID).
|
||||
OrderAsc().
|
||||
AddQuery().
|
||||
AggregateIDs(orgID).
|
||||
AggregateTypes(org.AggregateType).
|
||||
EventTypes(
|
||||
org.MemberAddedEventType,
|
||||
org.MemberRemovedEventType,
|
||||
org.MemberCascadeRemovedEventType,
|
||||
).Builder())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *org.MemberAddedEvent:
|
||||
if e.UserID == userID {
|
||||
isMember = true
|
||||
}
|
||||
case *org.MemberRemovedEvent:
|
||||
if e.UserID == userID {
|
||||
isMember = false
|
||||
}
|
||||
case *org.MemberCascadeRemovedEvent:
|
||||
if e.UserID == userID {
|
||||
isMember = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isMember, nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddOrgMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
|
||||
if member.UserID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-u8fkf", "Errors.Org.MemberInvalid")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "Org-u8fkf", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
addedMember := NewOrgMemberWriteModel(member.AggregateID, member.UserID)
|
||||
orgAgg := OrgAggregateFromWriteModel(&addedMember.WriteModel)
|
||||
err := c.checkUserExists(ctx, addedMember.UserID, "")
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "Org-2H8ds", "Errors.User.NotFound")
|
||||
return nil, errors.ThrowPreconditionFailed(err, "Org-2H8ds", "Errors.User.NotFound")
|
||||
}
|
||||
event, err := c.addOrgMember(ctx, orgAgg, addedMember, member)
|
||||
if err != nil {
|
||||
@ -40,10 +100,10 @@ func (c *Commands) AddOrgMember(ctx context.Context, member *domain.Member) (*do
|
||||
|
||||
func (c *Commands) addOrgMember(ctx context.Context, orgAgg *eventstore.Aggregate, addedMember *OrgMemberWriteModel, member *domain.Member) (eventstore.Command, error) {
|
||||
if !member.IsValid() {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-W8m4l", "Errors.Org.MemberInvalid")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "Org-W8m4l", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 && len(domain.CheckForInvalidRoles(member.Roles, domain.RoleSelfManagementGlobal, c.zitadelRoles)) > 0 {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-4N8es", "Errors.Org.MemberInvalid")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "Org-4N8es", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, addedMember)
|
||||
if err != nil {
|
||||
@ -59,10 +119,10 @@ func (c *Commands) addOrgMember(ctx context.Context, orgAgg *eventstore.Aggregat
|
||||
//ChangeOrgMember updates an existing member
|
||||
func (c *Commands) ChangeOrgMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
|
||||
if !member.IsValid() {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-LiaZi", "Errors.Org.MemberInvalid")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "Org-LiaZi", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-m9fG8", "Errors.Org.MemberInvalid")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "IAM-m9fG8", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
|
||||
existingMember, err := c.orgMemberWriteModelByID(ctx, member.AggregateID, member.UserID)
|
||||
@ -71,7 +131,7 @@ func (c *Commands) ChangeOrgMember(ctx context.Context, member *domain.Member) (
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(existingMember.Roles, member.Roles) {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-LiaZi", "Errors.Org.Member.RolesNotChanged")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "Org-LiaZi", "Errors.Org.Member.RolesNotChanged")
|
||||
}
|
||||
orgAgg := OrgAggregateFromWriteModel(&existingMember.MemberWriteModel.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, org.NewMemberChangedEvent(ctx, orgAgg, member.UserID, member.Roles...))
|
||||
|
@ -8,8 +8,9 @@ import (
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
@ -19,6 +20,271 @@ import (
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestAddMember(t *testing.T) {
|
||||
type args struct {
|
||||
a *org.Aggregate
|
||||
userID string
|
||||
roles []string
|
||||
zitadelRoles []authz.RoleMapping
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
agg := org.NewAggregate("test", "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "no user id",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no roles",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "12342",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "V2-PfYhb", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TODO: invalid roles",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "123",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "Org-4N8es", ""),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user not exists",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "userID",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "ORG_OWNER",
|
||||
},
|
||||
},
|
||||
filter: NewMultiFilter().Append(
|
||||
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
}).Filter(),
|
||||
},
|
||||
want: Want{
|
||||
CreateErr: errors.ThrowNotFound(nil, "ORG-GoXOn", "Errors.User.NotFound"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "already member",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "userID",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "ORG_OWNER",
|
||||
},
|
||||
},
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
user.NewMachineAddedEvent(
|
||||
ctx,
|
||||
&user.NewAggregate("id", "ro").Aggregate,
|
||||
"userName",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewMemberAddedEvent(
|
||||
ctx,
|
||||
&org.NewAggregate("id", "ro").Aggregate,
|
||||
"userID",
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
CreateErr: errors.ThrowAlreadyExists(nil, "ORG-poWwe", "Errors.Org.Member.AlreadyExists"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "userID",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "ORG_OWNER",
|
||||
},
|
||||
},
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
user.NewMachineAddedEvent(
|
||||
ctx,
|
||||
&user.NewAggregate("id", "ro").Aggregate,
|
||||
"userName",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
org.NewMemberAddedEvent(ctx, &agg.Aggregate, "userID", "ORG_OWNER"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, (&commandNew{zitadelRoles: tt.args.zitadelRoles}).AddOrgMember(tt.args.a, tt.args.userID, tt.args.roles...), tt.args.filter, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMember(t *testing.T) {
|
||||
type args struct {
|
||||
filter preparation.FilterToQueryReducer
|
||||
orgID string
|
||||
userID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantExists bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no events",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{}, nil
|
||||
},
|
||||
orgID: "orgID",
|
||||
userID: "userID",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "member added",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewMemberAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("orgID", "ro").Aggregate,
|
||||
"userID",
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
orgID: "orgID",
|
||||
userID: "userID",
|
||||
},
|
||||
wantExists: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "member removed",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewMemberAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("orgID", "ro").Aggregate,
|
||||
"userID",
|
||||
),
|
||||
org.NewMemberRemovedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("orgID", "ro").Aggregate,
|
||||
"userID",
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
orgID: "orgID",
|
||||
userID: "userID",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "member cascade removed",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewMemberAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("orgID", "ro").Aggregate,
|
||||
"userID",
|
||||
),
|
||||
org.NewMemberCascadeRemovedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("orgID", "ro").Aggregate,
|
||||
"userID",
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
orgID: "orgID",
|
||||
userID: "userID",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "error durring filter",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "PROJE-Op26p", "Errors.Internal")
|
||||
},
|
||||
orgID: "orgID",
|
||||
userID: "userID",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotExists, err := IsOrgMember(context.Background(), tt.args.filter, tt.args.orgID, tt.args.userID)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotExists != tt.wantExists {
|
||||
t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
@ -54,7 +320,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -76,7 +342,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -113,7 +379,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -163,7 +429,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorAlreadyExists,
|
||||
err: errors.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -188,7 +454,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPushFailed(caos_errs.ThrowAlreadyExists(nil, "ERROR", "internal"),
|
||||
expectPushFailed(errors.ThrowAlreadyExists(nil, "ERROR", "internal"),
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(org.NewMemberAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
@ -216,7 +482,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorAlreadyExists,
|
||||
err: errors.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -335,7 +601,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -356,7 +622,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -383,7 +649,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -418,7 +684,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -530,7 +796,7 @@ func TestCommandSide_RemoveOrgMember(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -547,7 +813,7 @@ func TestCommandSide_RemoveOrgMember(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
@ -20,6 +20,53 @@ import (
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestAddOrg(t *testing.T) {
|
||||
type args struct {
|
||||
a *org.Aggregate
|
||||
name string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
agg := org.NewAggregate("test", "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid domain",
|
||||
args: args{
|
||||
a: agg,
|
||||
name: "",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-mruNY", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: agg,
|
||||
name: "caos ag",
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
org.NewOrgAddedEvent(ctx, &agg.Aggregate, "caos ag"),
|
||||
org.NewDomainAddedEvent(ctx, &agg.Aggregate, "caos-ag.localhost"),
|
||||
org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "caos-ag.localhost"),
|
||||
org.NewDomainPrimarySetEvent(ctx, &agg.Aggregate, "caos-ag.localhost"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, AddOrgCommand(authz.WithRequestedDomain(context.Background(), "localhost"), tt.args.a, tt.args.name), nil, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddOrg(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
@ -57,7 +104,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -101,7 +148,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -127,7 +174,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
),
|
||||
),
|
||||
expectFilterOrgMemberNotFound(),
|
||||
expectPushFailed(caos_errs.ThrowAlreadyExists(nil, "id", "internal"),
|
||||
expectPushFailed(errors.ThrowAlreadyExists(nil, "id", "internal"),
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(org.NewOrgAddedEvent(
|
||||
context.Background(),
|
||||
@ -170,7 +217,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorAlreadyExists,
|
||||
err: errors.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -196,7 +243,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
),
|
||||
),
|
||||
expectFilterOrgMemberNotFound(),
|
||||
expectPushFailed(caos_errs.ThrowInternal(nil, "id", "internal"),
|
||||
expectPushFailed(errors.ThrowInternal(nil, "id", "internal"),
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(org.NewOrgAddedEvent(
|
||||
context.Background(),
|
||||
@ -239,7 +286,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsInternal,
|
||||
err: errors.IsInternal,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -374,7 +421,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
orgID: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -391,7 +438,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
name: "org",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -409,7 +456,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
),
|
||||
expectFilter(),
|
||||
expectPushFailed(
|
||||
caos_errs.ThrowInternal(nil, "id", "message"),
|
||||
errors.ThrowInternal(nil, "id", "message"),
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(org.NewOrgChangedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate, "org", "neworg")),
|
||||
@ -425,7 +472,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
name: "neworg",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsInternal,
|
||||
err: errors.IsInternal,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -596,7 +643,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
orgID: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -622,7 +669,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
orgID: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -638,7 +685,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
),
|
||||
),
|
||||
expectPushFailed(
|
||||
caos_errs.ThrowInternal(nil, "id", "message"),
|
||||
errors.ThrowInternal(nil, "id", "message"),
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(org.NewOrgDeactivatedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate)),
|
||||
@ -651,7 +698,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
orgID: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsInternal,
|
||||
err: errors.IsInternal,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -731,7 +778,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
orgID: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -753,7 +800,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
orgID: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -774,7 +821,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
),
|
||||
),
|
||||
expectPushFailed(
|
||||
caos_errs.ThrowInternal(nil, "id", "message"),
|
||||
errors.ThrowInternal(nil, "id", "message"),
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(org.NewOrgReactivatedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
@ -788,7 +835,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
orgID: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsInternal,
|
||||
err: errors.IsInternal,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
33
internal/command/phone.go
Normal file
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"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
@ -18,13 +18,17 @@ type Want struct {
|
||||
Commands []eventstore.Command
|
||||
}
|
||||
|
||||
type CommandVerifier interface {
|
||||
Validate(eventstore.Command) bool
|
||||
}
|
||||
|
||||
//AssertValidation checks if the validation works as inteded
|
||||
func AssertValidation(t *testing.T, validation preparation.Validation, filter preparation.FilterToQueryReducer, want Want) {
|
||||
t.Helper()
|
||||
|
||||
creates, err := validation()
|
||||
if !errors.Is(err, want.ValidationErr) {
|
||||
t.Errorf("wrong validation err = %v, want %v", err, want.ValidationErr)
|
||||
t.Errorf("wrong validation err = (%[1]T): %[1]v, want (%[2]T): %[2]v", err, want.ValidationErr)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
@ -32,7 +36,7 @@ func AssertValidation(t *testing.T, validation preparation.Validation, filter pr
|
||||
}
|
||||
cmds, err := creates(context.Background(), filter)
|
||||
if !errors.Is(err, want.CreateErr) {
|
||||
t.Errorf("wrong create err = %v, want %v", err, want.CreateErr)
|
||||
t.Errorf("wrong create err = (%[1]T): %[1]v, want (%[2]T): %[2]v", err, want.CreateErr)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
@ -45,6 +49,12 @@ func AssertValidation(t *testing.T, validation preparation.Validation, filter pr
|
||||
}
|
||||
|
||||
for i, cmd := range want.Commands {
|
||||
if v, ok := cmd.(CommandVerifier); ok {
|
||||
if verified := v.Validate(cmds[i]); !verified {
|
||||
t.Errorf("verification failed on command: = %v, want %v", cmds[i], cmd)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(cmd, cmds[i]) {
|
||||
t.Errorf("unexpected command: = %v, want %v", cmds[i], cmd)
|
||||
}
|
@ -2,9 +2,12 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/project"
|
||||
@ -52,6 +55,57 @@ func (c *Commands) addProject(ctx context.Context, projectAdd *domain.Project, r
|
||||
return events, addedProject, nil
|
||||
}
|
||||
|
||||
func AddProjectCommand(
|
||||
a *project.Aggregate,
|
||||
name string,
|
||||
owner string,
|
||||
projectRoleAssertion bool,
|
||||
projectRoleCheck bool,
|
||||
hasProjectCheck bool,
|
||||
privateLabelingSetting domain.PrivateLabelingSetting,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if name = strings.TrimSpace(name); name == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-C01yo", "Errors.Invalid.Argument")
|
||||
}
|
||||
if !privateLabelingSetting.Valid() {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-AO52V", "Errors.Invalid.Argument")
|
||||
}
|
||||
if owner == "" {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "PROJE-hzxwo", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
return []eventstore.Command{
|
||||
project.NewProjectAddedEvent(ctx, &a.Aggregate,
|
||||
name,
|
||||
projectRoleAssertion,
|
||||
projectRoleCheck,
|
||||
hasProjectCheck,
|
||||
privateLabelingSetting,
|
||||
),
|
||||
project.NewProjectMemberAddedEvent(ctx, &a.Aggregate,
|
||||
owner,
|
||||
domain.RoleProjectOwner),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func projectWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, projectID, resourceOwner string) (project *ProjectWriteModel, err error) {
|
||||
project = NewProjectWriteModel(projectID, resourceOwner)
|
||||
events, err := filter(ctx, project.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
project.AppendEvents(events...)
|
||||
if err := project.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getProjectByID(ctx context.Context, projectID, resourceOwner string) (*domain.Project, error) {
|
||||
projectWriteModel, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
|
||||
if err != nil {
|
||||
|
@ -3,11 +3,23 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/repository/project"
|
||||
)
|
||||
|
||||
type AddApp struct {
|
||||
Aggregate project.Aggregate
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
|
||||
func newAppClientSecret(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.HashAlgorithm) (value *crypto.CryptoValue, plain string, err error) {
|
||||
return newCryptoCodeWithPlain(ctx, filter, domain.SecretGeneratorTypeAppSecret, alg)
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appChange domain.Application, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if projectID == "" || appChange.GetAppID() == "" || appChange.GetApplicationName() == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.App.Invalid")
|
||||
|
@ -2,24 +2,84 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/project"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
project_repo "github.com/caos/zitadel/internal/repository/project"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
type addAPIApp struct {
|
||||
AddApp
|
||||
AuthMethodType domain.APIAuthMethodType
|
||||
|
||||
ClientID string
|
||||
ClientSecret *crypto.CryptoValue
|
||||
ClientSecretPlain string
|
||||
}
|
||||
|
||||
func AddAPIAppCommand(app *addAPIApp, clientSecretAlg crypto.HashAlgorithm) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if app.ID == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument")
|
||||
}
|
||||
if app.Name = strings.TrimSpace(app.Name); app.Name == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-F7g21", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
project, err := projectWriteModel(ctx, filter, app.Aggregate.ID, app.Aggregate.ResourceOwner)
|
||||
if err != nil || !project.State.Valid() {
|
||||
return nil, errors.ThrowNotFound(err, "PROJE-Sf2gb", "Errors.Project.NotFound")
|
||||
}
|
||||
|
||||
app.ClientID, err = domain.NewClientID(id.SonyFlakeGenerator, project.Name)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "V2-f0pgP", "Errors.Internal")
|
||||
}
|
||||
|
||||
//requires client secret
|
||||
// TODO(release blocking):we have to return the secret
|
||||
if app.AuthMethodType == domain.APIAuthMethodTypeBasic {
|
||||
app.ClientSecret, app.ClientSecretPlain, err = newAppClientSecret(ctx, filter, clientSecretAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return []eventstore.Command{
|
||||
project_repo.NewApplicationAddedEvent(
|
||||
ctx,
|
||||
&app.Aggregate.Aggregate,
|
||||
app.ID,
|
||||
app.Name,
|
||||
),
|
||||
project_repo.NewAPIConfigAddedEvent(
|
||||
ctx,
|
||||
&app.Aggregate.Aggregate,
|
||||
app.ID,
|
||||
app.ClientID,
|
||||
app.ClientSecret,
|
||||
app.AuthMethodType,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.APIApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.APIApp, err error) {
|
||||
if application == nil || application.AggregateID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Application.Invalid")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Application.Invalid")
|
||||
}
|
||||
project, err := c.getProjectByID(ctx, application.AggregateID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "PROJECT-9fnsf", "Errors.Project.NotFound")
|
||||
return nil, errors.ThrowPreconditionFailed(err, "PROJECT-9fnsf", "Errors.Project.NotFound")
|
||||
}
|
||||
addedApplication := NewAPIApplicationWriteModel(application.AggregateID, resourceOwner)
|
||||
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
|
||||
@ -43,7 +103,7 @@ func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.AP
|
||||
|
||||
func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore.Aggregate, proj *domain.Project, apiAppApp *domain.APIApp, resourceOwner string, appSecretGenerator crypto.Generator) (events []eventstore.Command, stringPW string, err error) {
|
||||
if !apiAppApp.IsValid() {
|
||||
return nil, "", caos_errs.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Application.Invalid")
|
||||
return nil, "", errors.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Application.Invalid")
|
||||
}
|
||||
apiAppApp.AppID, err = c.idGenerator.Next()
|
||||
if err != nil {
|
||||
@ -51,7 +111,7 @@ func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore
|
||||
}
|
||||
|
||||
events = []eventstore.Command{
|
||||
project.NewApplicationAddedEvent(ctx, projectAgg, apiAppApp.AppID, apiAppApp.AppName),
|
||||
project_repo.NewApplicationAddedEvent(ctx, projectAgg, apiAppApp.AppID, apiAppApp.AppName),
|
||||
}
|
||||
|
||||
var stringPw string
|
||||
@ -63,7 +123,7 @@ func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
events = append(events, project.NewAPIConfigAddedEvent(ctx,
|
||||
events = append(events, project_repo.NewAPIConfigAddedEvent(ctx,
|
||||
projectAgg,
|
||||
apiAppApp.AppID,
|
||||
apiAppApp.ClientID,
|
||||
@ -75,7 +135,7 @@ func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore
|
||||
|
||||
func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (*domain.APIApp, error) {
|
||||
if apiApp.AppID == "" || apiApp.AggregateID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-1m900", "Errors.Project.App.APIConfigInvalid")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-1m900", "Errors.Project.App.APIConfigInvalid")
|
||||
}
|
||||
|
||||
existingAPI, err := c.getAPIAppWriteModel(ctx, apiApp.AggregateID, apiApp.AppID, resourceOwner)
|
||||
@ -83,10 +143,10 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA
|
||||
return nil, err
|
||||
}
|
||||
if existingAPI.State == domain.AppStateUnspecified || existingAPI.State == domain.AppStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-2n8uU", "Errors.Project.App.NotExisting")
|
||||
return nil, errors.ThrowNotFound(nil, "COMMAND-2n8uU", "Errors.Project.App.NotExisting")
|
||||
}
|
||||
if !existingAPI.IsAPI() {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Gnwt3", "Errors.Project.App.IsNotAPI")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-Gnwt3", "Errors.Project.App.IsNotAPI")
|
||||
}
|
||||
projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel)
|
||||
changedEvent, hasChanged, err := existingAPI.NewChangedEvent(
|
||||
@ -98,7 +158,7 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA
|
||||
return nil, err
|
||||
}
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-1m88i", "Errors.NoChangesFound")
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-1m88i", "Errors.NoChangesFound")
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
|
||||
@ -115,7 +175,7 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA
|
||||
|
||||
func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string, appSecretGenerator crypto.Generator) (*domain.APIApp, error) {
|
||||
if projectID == "" || appID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing")
|
||||
}
|
||||
|
||||
existingAPI, err := c.getAPIAppWriteModel(ctx, projectID, appID, resourceOwner)
|
||||
@ -123,10 +183,10 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap
|
||||
return nil, err
|
||||
}
|
||||
if existingAPI.State == domain.AppStateUnspecified || existingAPI.State == domain.AppStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-2g66f", "Errors.Project.App.NotExisting")
|
||||
return nil, errors.ThrowNotFound(nil, "COMMAND-2g66f", "Errors.Project.App.NotExisting")
|
||||
}
|
||||
if !existingAPI.IsAPI() {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-aeH4", "Errors.Project.App.IsNotAPI")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-aeH4", "Errors.Project.App.IsNotAPI")
|
||||
}
|
||||
cryptoSecret, stringPW, err := domain.NewClientSecret(appSecretGenerator)
|
||||
if err != nil {
|
||||
@ -135,7 +195,7 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap
|
||||
|
||||
projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel)
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, project.NewAPIConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret))
|
||||
pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -158,13 +218,13 @@ func (c *Commands) VerifyAPIClientSecret(ctx context.Context, projectID, appID,
|
||||
return err
|
||||
}
|
||||
if !app.State.Exists() {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NoExisting")
|
||||
return errors.ThrowPreconditionFailed(nil, "COMMAND-DFnbf", "Errors.Project.App.NoExisting")
|
||||
}
|
||||
if !app.IsAPI() {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Bf3fw", "Errors.Project.App.IsNotAPI")
|
||||
return errors.ThrowInvalidArgument(nil, "COMMAND-Bf3fw", "Errors.Project.App.IsNotAPI")
|
||||
}
|
||||
if app.ClientSecret == nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-D3t5g", "Errors.Project.App.APIConfigInvalid")
|
||||
return errors.ThrowPreconditionFailed(nil, "COMMAND-D3t5g", "Errors.Project.App.APIConfigInvalid")
|
||||
}
|
||||
|
||||
projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel)
|
||||
@ -172,12 +232,12 @@ func (c *Commands) VerifyAPIClientSecret(ctx context.Context, projectID, appID,
|
||||
err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.userPasswordAlg)
|
||||
spanPasswordComparison.EndWithError(err)
|
||||
if err == nil {
|
||||
_, err = c.eventstore.Push(ctx, project.NewAPIConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID))
|
||||
_, err = c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID))
|
||||
return err
|
||||
}
|
||||
_, err = c.eventstore.Push(ctx, project.NewAPIConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID))
|
||||
_, err = c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID))
|
||||
logging.Log("COMMAND-g3f12").OnError(err).Error("could not push event APIClientSecretCheckFailed")
|
||||
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid")
|
||||
return errors.ThrowInvalidArgument(nil, "COMMAND-SADfg", "Errors.Project.App.ClientSecretInvalid")
|
||||
}
|
||||
|
||||
func (c *Commands) getAPIAppWriteModel(ctx context.Context, projectID, appID, resourceOwner string) (*APIApplicationWriteModel, error) {
|
||||
|
@ -4,9 +4,10 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
@ -16,6 +17,118 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAddAPIConfig(t *testing.T) {
|
||||
type args struct {
|
||||
a *project.Aggregate
|
||||
appID string
|
||||
name string
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
agg := project.NewAggregate("test", "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid appID",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "",
|
||||
name: "name",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid name",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "appID",
|
||||
name: "",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-F7g21", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "project not exists",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "id",
|
||||
name: "name",
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
CreateErr: errors.ThrowNotFound(nil, "PROJE-Sf2gb", "Errors.Project.NotFound"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct without client secret",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "appID",
|
||||
name: "name",
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
project.NewProjectAddedEvent(
|
||||
ctx,
|
||||
&agg.Aggregate,
|
||||
"project",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingUnspecified,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
project.NewApplicationAddedEvent(
|
||||
ctx,
|
||||
&agg.Aggregate,
|
||||
"appID",
|
||||
"name",
|
||||
),
|
||||
project.NewAPIConfigAddedEvent(ctx, &agg.Aggregate,
|
||||
"appID",
|
||||
"",
|
||||
nil,
|
||||
domain.APIAuthMethodTypePrivateKeyJWT,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t,
|
||||
AddAPIAppCommand(
|
||||
&addAPIApp{
|
||||
AddApp: AddApp{
|
||||
Aggregate: *tt.args.a,
|
||||
ID: tt.args.appID,
|
||||
Name: tt.args.name,
|
||||
},
|
||||
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
|
||||
},
|
||||
nil,
|
||||
), tt.args.filter, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddAPIApplication(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
@ -50,7 +163,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -73,7 +186,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -103,7 +216,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -295,7 +408,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -318,7 +431,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -341,7 +454,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -381,7 +494,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -504,7 +617,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -521,7 +634,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -539,7 +652,7 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -2,17 +2,121 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
http_util "github.com/caos/zitadel/internal/api/http"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/project"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
project_repo "github.com/caos/zitadel/internal/repository/project"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
type addOIDCApp struct {
|
||||
AddApp
|
||||
Version domain.OIDCVersion
|
||||
RedirectUris []string
|
||||
ResponseTypes []domain.OIDCResponseType
|
||||
GrantTypes []domain.OIDCGrantType
|
||||
ApplicationType domain.OIDCApplicationType
|
||||
AuthMethodType domain.OIDCAuthMethodType
|
||||
PostLogoutRedirectUris []string
|
||||
DevMode bool
|
||||
AccessTokenType domain.OIDCTokenType
|
||||
AccessTokenRoleAssertion bool
|
||||
IDTokenRoleAssertion bool
|
||||
IDTokenUserinfoAssertion bool
|
||||
ClockSkew time.Duration
|
||||
AdditionalOrigins []string
|
||||
|
||||
ClientID string
|
||||
ClientSecret *crypto.CryptoValue
|
||||
ClientSecretPlain string
|
||||
}
|
||||
|
||||
//AddOIDCAppCommand prepares the commands to add an oidc app. The ClientID will be set during the CreateCommands
|
||||
func AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.HashAlgorithm) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if app.ID == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
if app.Name = strings.TrimSpace(app.Name); app.Name == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-Fef31", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
if app.ClockSkew > time.Second*5 || app.ClockSkew < 0 {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "V2-PnCMS", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
for _, origin := range app.AdditionalOrigins {
|
||||
if !http_util.IsOrigin(origin) {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "V2-DqWPX", "Errors.Invalid.Argument")
|
||||
}
|
||||
}
|
||||
|
||||
if !domain.ContainsRequiredGrantTypes(app.ResponseTypes, app.GrantTypes) {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "V2-sLpW1", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) (_ []eventstore.Command, err error) {
|
||||
project, err := projectWriteModel(ctx, filter, app.Aggregate.ID, app.Aggregate.ResourceOwner)
|
||||
if err != nil || !project.State.Valid() {
|
||||
return nil, errors.ThrowNotFound(err, "PROJE-6swVG", "Errors.Project.NotFound")
|
||||
}
|
||||
|
||||
app.ClientID, err = domain.NewClientID(id.SonyFlakeGenerator, project.Name)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "V2-VMSQ1", "Errors.Internal")
|
||||
}
|
||||
|
||||
if app.AuthMethodType == domain.OIDCAuthMethodTypeBasic || app.AuthMethodType == domain.OIDCAuthMethodTypePost {
|
||||
app.ClientSecret, app.ClientSecretPlain, err = newAppClientSecret(ctx, filter, clientSecretAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return []eventstore.Command{
|
||||
project_repo.NewApplicationAddedEvent(
|
||||
ctx,
|
||||
&app.Aggregate.Aggregate,
|
||||
app.ID,
|
||||
app.Name,
|
||||
),
|
||||
project_repo.NewOIDCConfigAddedEvent(
|
||||
ctx,
|
||||
&app.Aggregate.Aggregate,
|
||||
app.Version,
|
||||
app.ID,
|
||||
app.ClientID,
|
||||
app.ClientSecret,
|
||||
app.RedirectUris,
|
||||
app.ResponseTypes,
|
||||
app.GrantTypes,
|
||||
app.ApplicationType,
|
||||
app.AuthMethodType,
|
||||
app.PostLogoutRedirectUris,
|
||||
app.DevMode,
|
||||
app.AccessTokenType,
|
||||
app.AccessTokenRoleAssertion,
|
||||
app.IDTokenRoleAssertion,
|
||||
app.IDTokenUserinfoAssertion,
|
||||
app.ClockSkew,
|
||||
app.AdditionalOrigins,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) AddOIDCApplication(ctx context.Context, application *domain.OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) (_ *domain.OIDCApp, err error) {
|
||||
if application == nil || application.AggregateID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Application.Invalid")
|
||||
@ -52,7 +156,7 @@ func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstor
|
||||
}
|
||||
|
||||
events = []eventstore.Command{
|
||||
project.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName),
|
||||
project_repo.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName),
|
||||
}
|
||||
|
||||
var stringPw string
|
||||
@ -64,7 +168,7 @@ func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstor
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
events = append(events, project.NewOIDCConfigAddedEvent(ctx,
|
||||
events = append(events, project_repo.NewOIDCConfigAddedEvent(ctx,
|
||||
projectAgg,
|
||||
oidcApp.OIDCVersion,
|
||||
oidcApp.AppID,
|
||||
@ -164,7 +268,7 @@ func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, a
|
||||
|
||||
projectAgg := ProjectAggregateFromWriteModel(&existingOIDC.WriteModel)
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, project.NewOIDCConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret))
|
||||
pushedEvents, err := c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretChangedEvent(ctx, projectAgg, appID, cryptoSecret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -201,10 +305,10 @@ func (c *Commands) VerifyOIDCClientSecret(ctx context.Context, projectID, appID,
|
||||
err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.userPasswordAlg)
|
||||
spanPasswordComparison.EndWithError(err)
|
||||
if err == nil {
|
||||
_, err = c.eventstore.Push(ctx, project.NewOIDCConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID))
|
||||
_, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID))
|
||||
return err
|
||||
}
|
||||
_, err = c.eventstore.Push(ctx, project.NewOIDCConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID))
|
||||
_, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID))
|
||||
logging.Log("COMMAND-ADfhz").OnError(err).Error("could not push event OIDCClientSecretCheckFailed")
|
||||
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid")
|
||||
}
|
||||
|
@ -7,9 +7,10 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
@ -18,6 +19,162 @@ import (
|
||||
"github.com/caos/zitadel/internal/repository/project"
|
||||
)
|
||||
|
||||
func TestAddOIDCApp(t *testing.T) {
|
||||
type args struct {
|
||||
app *addOIDCApp
|
||||
clientSecretAlg crypto.HashAlgorithm
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
agg := project.NewAggregate("test", "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid appID",
|
||||
args: args{
|
||||
app: &addOIDCApp{
|
||||
AddApp: AddApp{
|
||||
Aggregate: *agg,
|
||||
ID: "",
|
||||
Name: "name",
|
||||
},
|
||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||
Version: domain.OIDCVersionV1,
|
||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
||||
AuthMethodType: domain.OIDCAuthMethodTypeNone,
|
||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid name",
|
||||
args: args{
|
||||
app: &addOIDCApp{
|
||||
AddApp: AddApp{
|
||||
Aggregate: *agg,
|
||||
ID: "id",
|
||||
Name: "",
|
||||
},
|
||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||
Version: domain.OIDCVersionV1,
|
||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
||||
AuthMethodType: domain.OIDCAuthMethodTypeNone,
|
||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-Fef31", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "project not exists",
|
||||
args: args{
|
||||
app: &addOIDCApp{
|
||||
AddApp: AddApp{
|
||||
Aggregate: *agg,
|
||||
ID: "id",
|
||||
Name: "name",
|
||||
},
|
||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||
Version: domain.OIDCVersionV1,
|
||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
||||
AuthMethodType: domain.OIDCAuthMethodTypeNone,
|
||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||
},
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
CreateErr: errors.ThrowNotFound(nil, "PROJE-6swVG", ""),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
app: &addOIDCApp{
|
||||
AddApp: AddApp{
|
||||
Aggregate: *agg,
|
||||
ID: "id",
|
||||
Name: "name",
|
||||
},
|
||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||
Version: domain.OIDCVersionV1,
|
||||
|
||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
||||
AuthMethodType: domain.OIDCAuthMethodTypeNone,
|
||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||
},
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
project.NewProjectAddedEvent(
|
||||
ctx,
|
||||
&agg.Aggregate,
|
||||
"project",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingUnspecified,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
project.NewApplicationAddedEvent(ctx, &agg.Aggregate,
|
||||
"id",
|
||||
"name",
|
||||
),
|
||||
project.NewOIDCConfigAddedEvent(ctx, &agg.Aggregate,
|
||||
domain.OIDCVersionV1,
|
||||
"id",
|
||||
"",
|
||||
nil,
|
||||
nil,
|
||||
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||
domain.OIDCApplicationTypeWeb,
|
||||
domain.OIDCAuthMethodTypeNone,
|
||||
nil,
|
||||
false,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
nil,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t,
|
||||
AddOIDCAppCommand(
|
||||
tt.args.app,
|
||||
tt.args.clientSecretAlg,
|
||||
), tt.args.filter, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
@ -52,7 +209,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -75,7 +232,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -105,7 +262,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -274,7 +431,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -298,7 +455,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -322,7 +479,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -347,7 +504,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -418,7 +575,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
err: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -580,7 +737,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -597,7 +754,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
err: errors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -615,7 +772,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
err: errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
@ -985,3 +986,186 @@ func newProjectChangedEvent(ctx context.Context, projectID, resourceOwner, oldNa
|
||||
)
|
||||
return event
|
||||
}
|
||||
|
||||
func TestAddProject(t *testing.T) {
|
||||
type args struct {
|
||||
a *project.Aggregate
|
||||
name string
|
||||
owner string
|
||||
privateLabelingSetting domain.PrivateLabelingSetting
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
agg := project.NewAggregate("test", "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid name",
|
||||
args: args{
|
||||
a: agg,
|
||||
name: "",
|
||||
owner: "owner",
|
||||
privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-C01yo", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid private labeling setting",
|
||||
args: args{
|
||||
a: agg,
|
||||
name: "name",
|
||||
owner: "owner",
|
||||
privateLabelingSetting: -1,
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-AO52V", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid owner",
|
||||
args: args{
|
||||
a: agg,
|
||||
name: "name",
|
||||
owner: "",
|
||||
privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowPreconditionFailed(nil, "PROJE-hzxwo", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: agg,
|
||||
name: "ZITADEL",
|
||||
owner: "CAOS AG",
|
||||
privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
project.NewProjectAddedEvent(ctx, &agg.Aggregate,
|
||||
"ZITADEL",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
),
|
||||
project.NewProjectMemberAddedEvent(ctx, &agg.Aggregate,
|
||||
"CAOS AG",
|
||||
domain.RoleProjectOwner),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, AddProjectCommand(tt.args.a, tt.args.name, tt.args.owner, false, false, false, tt.args.privateLabelingSetting), nil, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// func TestExistsProject(t *testing.T) {
|
||||
// type args struct {
|
||||
// filter preparation.FilterToQueryReducer
|
||||
// id string
|
||||
// resourceOwner string
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// args args
|
||||
// wantExists bool
|
||||
// wantErr bool
|
||||
// }{
|
||||
// {
|
||||
// name: "no events",
|
||||
// args: args{
|
||||
// filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
// return []eventstore.Event{}, nil
|
||||
// },
|
||||
// id: "id",
|
||||
// resourceOwner: "ro",
|
||||
// },
|
||||
// wantExists: false,
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "project added",
|
||||
// args: args{
|
||||
// filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
// return []eventstore.Event{
|
||||
// project.NewProjectAddedEvent(
|
||||
// context.Background(),
|
||||
// &project.NewAggregate("id", "ro").Aggregate,
|
||||
// "name",
|
||||
// false,
|
||||
// false,
|
||||
// false,
|
||||
// domain.PrivateLabelingSettingEnforceProjectResourceOwnerPolicy,
|
||||
// ),
|
||||
// }, nil
|
||||
// },
|
||||
// id: "id",
|
||||
// resourceOwner: "ro",
|
||||
// },
|
||||
// wantExists: true,
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "project removed",
|
||||
// args: args{
|
||||
// filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
// return []eventstore.Event{
|
||||
// project.NewProjectAddedEvent(
|
||||
// context.Background(),
|
||||
// &project.NewAggregate("id", "ro").Aggregate,
|
||||
// "name",
|
||||
// false,
|
||||
// false,
|
||||
// false,
|
||||
// domain.PrivateLabelingSettingEnforceProjectResourceOwnerPolicy,
|
||||
// ),
|
||||
// project.NewProjectRemovedEvent(
|
||||
// context.Background(),
|
||||
// &project.NewAggregate("id", "ro").Aggregate,
|
||||
// "name",
|
||||
// ),
|
||||
// }, nil
|
||||
// },
|
||||
// id: "id",
|
||||
// resourceOwner: "ro",
|
||||
// },
|
||||
// wantExists: false,
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "error durring filter",
|
||||
// args: args{
|
||||
// filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
// return nil, errors.ThrowInternal(nil, "PROJE-Op26p", "Errors.Internal")
|
||||
// },
|
||||
// id: "id",
|
||||
// resourceOwner: "ro",
|
||||
// },
|
||||
// wantExists: false,
|
||||
// wantErr: true,
|
||||
// },
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// gotExists, err := projectWriteModel(context.Background(), tt.args.filter, tt.args.id, tt.args.resourceOwner)
|
||||
// if (err != nil) != tt.wantErr {
|
||||
// t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
|
||||
// return
|
||||
// }
|
||||
// if gotExists != tt.wantExists {
|
||||
// t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
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"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
@ -378,3 +378,38 @@ func (c *Commands) userWriteModelByID(ctx context.Context, userID, resourceOwner
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func ExistsUser(ctx context.Context, filter preparation.FilterToQueryReducer, id, resourceOwner string) (exists bool, err error) {
|
||||
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(resourceOwner).
|
||||
OrderAsc().
|
||||
AddQuery().
|
||||
AggregateTypes(user.AggregateType).
|
||||
AggregateIDs(id).
|
||||
EventTypes(
|
||||
user.HumanRegisteredType,
|
||||
user.UserV1RegisteredType,
|
||||
user.HumanAddedType,
|
||||
user.UserV1AddedType,
|
||||
user.MachineAddedEventType,
|
||||
user.UserRemovedType,
|
||||
).Builder())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
switch event.(type) {
|
||||
case *user.HumanRegisteredEvent, *user.HumanAddedEvent, *user.MachineAddedEvent:
|
||||
exists = true
|
||||
case *user.UserRemovedEvent:
|
||||
exists = false
|
||||
}
|
||||
}
|
||||
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
func newUserInitCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (value *crypto.CryptoValue, expiry time.Duration, err error) {
|
||||
return newCryptoCodeWithExpiry(ctx, filter, domain.SecretGeneratorTypeInitCode, alg)
|
||||
}
|
||||
|
@ -4,12 +4,11 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func domainPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) {
|
||||
func domainPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (*PolicyDomainWriteModel, error) {
|
||||
wm, err := orgDomainPolicy(ctx, filter)
|
||||
if err != nil || wm != nil && wm.State.Exists() {
|
||||
return wm, err
|
||||
@ -21,8 +20,8 @@ func domainPolicyWriteModel(ctx context.Context, filter preparation.FilterToQuer
|
||||
return nil, errors.ThrowInternal(nil, "USER-Ggk9n", "Errors.Internal")
|
||||
}
|
||||
|
||||
func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) {
|
||||
policy := command.NewOrgDomainPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
|
||||
func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*PolicyDomainWriteModel, error) {
|
||||
policy := NewOrgDomainPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
|
||||
events, err := filter(ctx, policy.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -35,8 +34,8 @@ func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReduce
|
||||
return &policy.PolicyDomainWriteModel, err
|
||||
}
|
||||
|
||||
func instanceDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) {
|
||||
policy := command.NewInstanceDomainPolicyWriteModel(ctx)
|
||||
func instanceDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*PolicyDomainWriteModel, error) {
|
||||
policy := NewInstanceDomainPolicyWriteModel(ctx)
|
||||
events, err := filter(ctx, policy.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
@ -6,8 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
@ -22,7 +21,7 @@ func Test_customDomainPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.PolicyDomainWriteModel
|
||||
want *PolicyDomainWriteModel
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
@ -58,7 +57,7 @@ func Test_customDomainPolicy(t *testing.T) {
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: &command.PolicyDomainWriteModel{
|
||||
want: &PolicyDomainWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "id",
|
||||
ResourceOwner: "ro",
|
||||
@ -91,7 +90,7 @@ func Test_defaultDomainPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.PolicyDomainWriteModel
|
||||
want *PolicyDomainWriteModel
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
@ -127,7 +126,7 @@ func Test_defaultDomainPolicy(t *testing.T) {
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: &command.PolicyDomainWriteModel{
|
||||
want: &PolicyDomainWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "INSTANCE",
|
||||
ResourceOwner: "INSTANCE",
|
||||
@ -160,7 +159,7 @@ func Test_DomainPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.PolicyDomainWriteModel
|
||||
want *PolicyDomainWriteModel
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
@ -186,7 +185,7 @@ func Test_DomainPolicy(t *testing.T) {
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: &command.PolicyDomainWriteModel{
|
||||
want: &PolicyDomainWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "id",
|
||||
ResourceOwner: "ro",
|
||||
@ -230,7 +229,7 @@ func Test_DomainPolicy(t *testing.T) {
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: &command.PolicyDomainWriteModel{
|
||||
want: &PolicyDomainWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "INSTANCE",
|
||||
ResourceOwner: "INSTANCE",
|
@ -4,13 +4,14 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func (c *Commands) getHuman(ctx context.Context, userID, resourceowner string) (*domain.Human, error) {
|
||||
@ -19,51 +20,258 @@ func (c *Commands) getHuman(ctx context.Context, userID, resourceowner string) (
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(human.UserState) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-M9dsd", "Errors.User.NotFound")
|
||||
return nil, errors.ThrowNotFound(nil, "COMMAND-M9dsd", "Errors.User.NotFound")
|
||||
}
|
||||
return writeModelToHuman(human), nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddHuman(ctx context.Context, orgID string, human *domain.Human, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) (*domain.Human, error) {
|
||||
if orgID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-XYFk9", "Errors.ResourceOwnerMissing")
|
||||
type AddHuman struct {
|
||||
// Username is required
|
||||
Username string
|
||||
// FirstName is required
|
||||
FirstName string
|
||||
// LastName is required
|
||||
LastName string
|
||||
// NickName is required
|
||||
NickName string
|
||||
// DisplayName is required
|
||||
DisplayName string
|
||||
// Email is required
|
||||
Email Email
|
||||
// PreferredLang is required
|
||||
PreferredLang language.Tag
|
||||
// Gender is required
|
||||
Gender domain.Gender
|
||||
//Phone represents an international phone number
|
||||
Phone Phone
|
||||
//Password is optional
|
||||
Password string
|
||||
//PasswordChangeRequired is used if the `Password`-field is set
|
||||
PasswordChangeRequired bool
|
||||
Passwordless bool
|
||||
ExternalIDP bool
|
||||
Register bool
|
||||
}
|
||||
|
||||
func (c *Commands) AddHuman(ctx context.Context, resourceOwner string, human *AddHuman) (*domain.HumanDetails, error) {
|
||||
return c.v2.AddHuman(ctx, resourceOwner, human)
|
||||
}
|
||||
|
||||
func (c *commandNew) AddHuman(ctx context.Context, resourceOwner string, human *AddHuman) (*domain.HumanDetails, error) {
|
||||
if resourceOwner == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "COMMA-5Ky74", "Errors.Internal")
|
||||
}
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Org.DomainPolicy.NotFound")
|
||||
}
|
||||
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexityPolicy.NotFound")
|
||||
}
|
||||
events, addedHuman, err := c.addHuman(ctx, orgID, human, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
|
||||
userID, err := c.id.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
||||
agg := user.NewAggregate(userID, resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.es.Filter, addHumanCommand(agg, human, c.userPasswordAlg, c.phoneAlg, c.emailAlg, c.initCodeAlg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = AppendAndReduce(addedHuman, pushedEvents...)
|
||||
events, err := c.es.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return writeModelToHuman(addedHuman), nil
|
||||
return &domain.HumanDetails{
|
||||
ID: userID,
|
||||
ObjectDetails: domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: events[len(events)-1].Aggregate().ResourceOwner,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type humanCreationCommand interface {
|
||||
eventstore.Command
|
||||
AddPhoneData(phoneNumber string)
|
||||
AddPasswordData(secret *crypto.CryptoValue, changeRequired bool)
|
||||
}
|
||||
|
||||
func addHumanCommand(a *user.Aggregate, human *AddHuman, passwordAlg crypto.HashAlgorithm, phoneAlg, emailAlg, initCodeAlg crypto.EncryptionAlgorithm) preparation.Validation {
|
||||
return func() (_ preparation.CreateCommands, err error) {
|
||||
if !human.Email.Valid() {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "USER-Ec7dM", "Errors.Invalid.Argument")
|
||||
}
|
||||
if human.Username = strings.TrimSpace(human.Username); human.Username == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "V2-zzad3", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
if human.FirstName = strings.TrimSpace(human.FirstName); human.FirstName == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.Invalid.Argument")
|
||||
}
|
||||
if human.LastName = strings.TrimSpace(human.LastName); human.LastName == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "USER-DiAq8", "Errors.Invalid.Argument")
|
||||
}
|
||||
human.ensureDisplayName()
|
||||
|
||||
if human.Phone.Number, err = FormatPhoneNumber(human.Phone.Number); err != nil {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "USER-tD6ax", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
domainPolicy, err := domainPolicyWriteModel(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = userValidateDomain(ctx, a, human.Username, domainPolicy.UserLoginMustBeDomain, filter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var createCmd humanCreationCommand
|
||||
if human.Register {
|
||||
createCmd = user.NewHumanRegisteredEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
human.Username,
|
||||
human.FirstName,
|
||||
human.LastName,
|
||||
human.NickName,
|
||||
human.DisplayName,
|
||||
human.PreferredLang,
|
||||
human.Gender,
|
||||
human.Email.Address,
|
||||
domainPolicy.UserLoginMustBeDomain,
|
||||
)
|
||||
} else {
|
||||
createCmd = user.NewHumanAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
human.Username,
|
||||
human.FirstName,
|
||||
human.LastName,
|
||||
human.NickName,
|
||||
human.DisplayName,
|
||||
human.PreferredLang,
|
||||
human.Gender,
|
||||
human.Email.Address,
|
||||
domainPolicy.UserLoginMustBeDomain,
|
||||
)
|
||||
}
|
||||
|
||||
if human.Phone.Number != "" {
|
||||
createCmd.AddPhoneData(human.Phone.Number)
|
||||
}
|
||||
|
||||
if human.Password != "" {
|
||||
if err = humanValidatePassword(ctx, filter, human.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret, err := crypto.Hash([]byte(human.Password), passwordAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createCmd.AddPasswordData(secret, human.PasswordChangeRequired)
|
||||
}
|
||||
|
||||
cmds := make([]eventstore.Command, 0, 3)
|
||||
cmds = append(cmds, createCmd)
|
||||
|
||||
//add init code if
|
||||
// email not verified or
|
||||
// user not registered and password set
|
||||
if human.shouldAddInitCode() {
|
||||
value, expiry, err := newUserInitCode(ctx, filter, initCodeAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmds = append(cmds, user.NewHumanInitialCodeAddedEvent(ctx, &a.Aggregate, value, expiry))
|
||||
}
|
||||
|
||||
if human.Email.Verified {
|
||||
cmds = append(cmds, user.NewHumanEmailVerifiedEvent(ctx, &a.Aggregate))
|
||||
} else {
|
||||
value, expiry, err := newEmailCode(ctx, filter, emailAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmds = append(cmds, user.NewHumanEmailCodeAddedEvent(ctx, &a.Aggregate, value, expiry))
|
||||
}
|
||||
|
||||
if human.Phone.Verified {
|
||||
cmds = append(cmds, user.NewHumanPhoneVerifiedEvent(ctx, &a.Aggregate))
|
||||
} else if human.Phone.Number != "" {
|
||||
value, expiry, err := newPhoneCode(ctx, filter, phoneAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmds = append(cmds, user.NewHumanPhoneCodeAddedEvent(ctx, &a.Aggregate, value, expiry))
|
||||
}
|
||||
|
||||
return cmds, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func userValidateDomain(ctx context.Context, a *user.Aggregate, username string, mustBeDomain bool, filter preparation.FilterToQueryReducer) error {
|
||||
if mustBeDomain {
|
||||
return nil
|
||||
}
|
||||
|
||||
usernameSplit := strings.Split(username, "@")
|
||||
if len(usernameSplit) != 2 {
|
||||
return errors.ThrowInvalidArgument(nil, "COMMAND-Dfd21", "Errors.User.Invalid")
|
||||
}
|
||||
|
||||
domainCheck := NewOrgDomainVerifiedWriteModel(usernameSplit[1])
|
||||
events, err := filter(ctx, domainCheck.Query())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainCheck.AppendEvents(events...)
|
||||
if err = domainCheck.Reduce(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if domainCheck.Verified && domainCheck.ResourceOwner != a.ResourceOwner {
|
||||
return errors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func humanValidatePassword(ctx context.Context, filter preparation.FilterToQueryReducer, password string) error {
|
||||
passwordComplexity, err := passwordComplexityPolicyWriteModel(ctx, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return passwordComplexity.Validate(password)
|
||||
}
|
||||
|
||||
func (h *AddHuman) ensureDisplayName() {
|
||||
if strings.TrimSpace(h.DisplayName) != "" {
|
||||
return
|
||||
}
|
||||
h.DisplayName = h.FirstName + " " + h.LastName
|
||||
}
|
||||
|
||||
func (h *AddHuman) shouldAddInitCode() bool {
|
||||
//user without idp
|
||||
return !h.Email.Verified ||
|
||||
//user with idp
|
||||
!h.ExternalIDP &&
|
||||
!h.Passwordless &&
|
||||
h.Password != ""
|
||||
}
|
||||
|
||||
func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, passwordlessCodeGenerator crypto.Generator) (_ *domain.Human, passwordlessCode *domain.PasswordlessInitCode, err error) {
|
||||
if orgID == "" {
|
||||
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5N8fs", "Errors.ResourceOwnerMissing")
|
||||
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-5N8fs", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-2N9fs", "Errors.Org.DomainPolicy.NotFound")
|
||||
return nil, nil, errors.ThrowPreconditionFailed(err, "COMMAND-2N9fs", "Errors.Org.DomainPolicy.NotFound")
|
||||
}
|
||||
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-4N8gs", "Errors.Org.PasswordComplexityPolicy.NotFound")
|
||||
return nil, nil, errors.ThrowPreconditionFailed(err, "COMMAND-4N8gs", "Errors.Org.PasswordComplexityPolicy.NotFound")
|
||||
}
|
||||
events, addedHuman, addedCode, code, err := c.importHuman(ctx, orgID, human, passwordless, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator)
|
||||
if err != nil {
|
||||
@ -89,53 +297,24 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
|
||||
return writeModelToHuman(addedHuman), passwordlessCode, nil
|
||||
}
|
||||
|
||||
func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
|
||||
if orgID == "" || !human.IsValid() {
|
||||
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-67Ms8", "Errors.User.Invalid")
|
||||
}
|
||||
if human.Password != nil && human.SecretString != "" {
|
||||
human.ChangeRequired = true
|
||||
}
|
||||
return c.createHuman(ctx, orgID, human, nil, false, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
|
||||
}
|
||||
|
||||
func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) {
|
||||
if orgID == "" || !human.IsValid() {
|
||||
return nil, nil, nil, "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.User.Invalid")
|
||||
}
|
||||
events, humanWriteModel, err = c.createHuman(ctx, orgID, human, nil, false, passwordless, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
|
||||
if err != nil {
|
||||
return nil, nil, nil, "", err
|
||||
}
|
||||
if passwordless {
|
||||
var codeEvent eventstore.Command
|
||||
codeEvent, passwordlessCodeWriteModel, code, err = c.humanAddPasswordlessInitCode(ctx, human.AggregateID, orgID, true, passwordlessCodeGenerator)
|
||||
if err != nil {
|
||||
return nil, nil, nil, "", err
|
||||
}
|
||||
events = append(events, codeEvent)
|
||||
}
|
||||
return events, humanWriteModel, passwordlessCodeWriteModel, code, nil
|
||||
}
|
||||
|
||||
func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgMemberRoles []string, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) (*domain.Human, error) {
|
||||
if orgID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-GEdf2", "Errors.ResourceOwnerMissing")
|
||||
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-GEdf2", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Org.DomainPolicy.NotFound")
|
||||
return nil, errors.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Org.DomainPolicy.NotFound")
|
||||
}
|
||||
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexityPolicy.NotFound")
|
||||
return nil, errors.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexityPolicy.NotFound")
|
||||
}
|
||||
loginPolicy, err := c.getOrgLoginPolicy(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-Dfg3g", "Errors.Org.LoginPolicy.NotFound")
|
||||
return nil, errors.ThrowPreconditionFailed(err, "COMMAND-Dfg3g", "Errors.Org.LoginPolicy.NotFound")
|
||||
}
|
||||
if !loginPolicy.AllowRegister {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-SAbr3", "Errors.Org.LoginPolicy.RegistrationNotAllowed")
|
||||
return nil, errors.ThrowPreconditionFailed(err, "COMMAND-SAbr3", "Errors.Org.LoginPolicy.RegistrationNotAllowed")
|
||||
}
|
||||
userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, link, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
|
||||
if err != nil {
|
||||
@ -171,12 +350,41 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai
|
||||
return writeModelToHuman(registeredHuman), nil
|
||||
}
|
||||
|
||||
func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
|
||||
if orgID == "" || !human.IsValid() {
|
||||
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-67Ms8", "Errors.User.Invalid")
|
||||
}
|
||||
if human.Password != nil && human.SecretString != "" {
|
||||
human.ChangeRequired = true
|
||||
}
|
||||
return c.createHuman(ctx, orgID, human, nil, false, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
|
||||
}
|
||||
|
||||
func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) {
|
||||
if orgID == "" || !human.IsValid() {
|
||||
return nil, nil, nil, "", errors.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.User.Invalid")
|
||||
}
|
||||
events, humanWriteModel, err = c.createHuman(ctx, orgID, human, nil, false, passwordless, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
|
||||
if err != nil {
|
||||
return nil, nil, nil, "", err
|
||||
}
|
||||
if passwordless {
|
||||
var codeEvent eventstore.Command
|
||||
codeEvent, passwordlessCodeWriteModel, code, err = c.humanAddPasswordlessInitCode(ctx, human.AggregateID, orgID, true, passwordlessCodeGenerator)
|
||||
if err != nil {
|
||||
return nil, nil, nil, "", err
|
||||
}
|
||||
events = append(events, codeEvent)
|
||||
}
|
||||
return events, humanWriteModel, passwordlessCodeWriteModel, code, nil
|
||||
}
|
||||
|
||||
func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
|
||||
if human != nil && human.Username == "" {
|
||||
human.Username = human.EmailAddress
|
||||
}
|
||||
if orgID == "" || !human.IsValid() || link == nil && (human.Password == nil || human.SecretString == "") {
|
||||
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-9dk45", "Errors.User.Invalid")
|
||||
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-9dk45", "Errors.User.Invalid")
|
||||
}
|
||||
if human.Password != nil && human.SecretString != "" {
|
||||
human.ChangeRequired = false
|
||||
@ -184,21 +392,21 @@ func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domai
|
||||
return c.createHuman(ctx, orgID, human, link, true, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
|
||||
}
|
||||
|
||||
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, selfregister, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
|
||||
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, selfregister, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) (events []eventstore.Command, addedHuman *HumanWriteModel, err error) {
|
||||
if err := human.CheckDomainPolicy(domainPolicy); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !domainPolicy.UserLoginMustBeDomain {
|
||||
usernameSplit := strings.Split(human.Username, "@")
|
||||
if len(usernameSplit) != 2 {
|
||||
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Dfd21", "Errors.User.Invalid")
|
||||
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-Dfd21", "Errors.User.Invalid")
|
||||
}
|
||||
domainCheck := NewOrgDomainVerifiedWriteModel(usernameSplit[1])
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if domainCheck.Verified && domainCheck.ResourceOwner != orgID {
|
||||
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
|
||||
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
|
||||
}
|
||||
}
|
||||
userID, err := c.idGenerator.Next()
|
||||
@ -213,10 +421,9 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
|
||||
}
|
||||
}
|
||||
|
||||
addedHuman := NewHumanWriteModel(human.AggregateID, orgID)
|
||||
addedHuman = NewHumanWriteModel(human.AggregateID, orgID)
|
||||
//TODO: adlerhurst maybe we could simplify the code below
|
||||
userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel)
|
||||
var events []eventstore.Command
|
||||
|
||||
if selfregister {
|
||||
events = append(events, createRegisterHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
|
||||
@ -259,7 +466,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
|
||||
|
||||
func (c *Commands) HumanSkipMFAInit(ctx context.Context, userID, resourceowner string) (err error) {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-2xpX9", "Errors.User.UserIDMissing")
|
||||
return errors.ThrowInvalidArgument(nil, "COMMAND-2xpX9", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingHuman, err := c.getHumanWriteModelByID(ctx, userID, resourceowner)
|
||||
@ -267,7 +474,7 @@ func (c *Commands) HumanSkipMFAInit(ctx context.Context, userID, resourceowner s
|
||||
return err
|
||||
}
|
||||
if !isUserStateExists(existingHuman.UserState) {
|
||||
return caos_errs.ThrowNotFound(nil, "COMMAND-m9cV8", "Errors.User.NotFound")
|
||||
return errors.ThrowNotFound(nil, "COMMAND-m9cV8", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
_, err = c.eventstore.Push(ctx,
|
||||
@ -340,10 +547,10 @@ func createRegisterHumanEvent(ctx context.Context, aggregate *eventstore.Aggrega
|
||||
|
||||
func (c *Commands) HumansSignOut(ctx context.Context, agentID string, userIDs []string) error {
|
||||
if agentID == "" {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
|
||||
return errors.ThrowInvalidArgument(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
|
||||
}
|
||||
if len(userIDs) == 0 {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-M0od3", "Errors.User.UserIDMissing")
|
||||
return errors.ThrowInvalidArgument(nil, "COMMAND-M0od3", "Errors.User.UserIDMissing")
|
||||
}
|
||||
events := make([]eventstore.Command, 0)
|
||||
for _, userID := range userIDs {
|
||||
|
@ -71,7 +71,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
},
|
||||
PhoneNumber: "0711234567",
|
||||
PhoneNumber: "+41711234567",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
@ -114,7 +114,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
},
|
||||
PhoneNumber: "0711234567",
|
||||
PhoneNumber: "+41711234567",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
@ -172,7 +172,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
},
|
||||
PhoneNumber: "0719876543",
|
||||
PhoneNumber: "+41719876543",
|
||||
IsPhoneVerified: true,
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
@ -239,7 +239,7 @@ func TestCommandSide_ChangeHumanPhone(t *testing.T) {
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
},
|
||||
PhoneNumber: "0711234567",
|
||||
PhoneNumber: "+41711234567",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
secretGenerator: GetMockSecretGenerator(t),
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,12 +4,11 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func passwordComplexityPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) {
|
||||
func passwordComplexityPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (*PasswordComplexityPolicyWriteModel, error) {
|
||||
wm, err := customPasswordComplexityPolicy(ctx, filter)
|
||||
if err != nil || wm != nil && wm.State.Exists() {
|
||||
return wm, err
|
||||
@ -21,8 +20,8 @@ func passwordComplexityPolicyWriteModel(ctx context.Context, filter preparation.
|
||||
return nil, errors.ThrowInternal(nil, "USER-uQ96e", "Errors.Internal")
|
||||
}
|
||||
|
||||
func customPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) {
|
||||
policy := command.NewOrgPasswordComplexityPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
|
||||
func customPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*PasswordComplexityPolicyWriteModel, error) {
|
||||
policy := NewOrgPasswordComplexityPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
|
||||
events, err := filter(ctx, policy.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -35,8 +34,8 @@ func customPasswordComplexityPolicy(ctx context.Context, filter preparation.Filt
|
||||
return &policy.PasswordComplexityPolicyWriteModel, err
|
||||
}
|
||||
|
||||
func defaultPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) {
|
||||
policy := command.NewInstancePasswordComplexityPolicyWriteModel(ctx)
|
||||
func defaultPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*PasswordComplexityPolicyWriteModel, error) {
|
||||
policy := NewInstancePasswordComplexityPolicyWriteModel(ctx)
|
||||
events, err := filter(ctx, policy.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
@ -6,8 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
@ -22,7 +21,7 @@ func Test_customPasswordComplexityPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.PasswordComplexityPolicyWriteModel
|
||||
want *PasswordComplexityPolicyWriteModel
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
@ -62,7 +61,7 @@ func Test_customPasswordComplexityPolicy(t *testing.T) {
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: &command.PasswordComplexityPolicyWriteModel{
|
||||
want: &PasswordComplexityPolicyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "id",
|
||||
ResourceOwner: "ro",
|
||||
@ -99,7 +98,7 @@ func Test_defaultPasswordComplexityPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.PasswordComplexityPolicyWriteModel
|
||||
want *PasswordComplexityPolicyWriteModel
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
@ -139,7 +138,7 @@ func Test_defaultPasswordComplexityPolicy(t *testing.T) {
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: &command.PasswordComplexityPolicyWriteModel{
|
||||
want: &PasswordComplexityPolicyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "INSTANCE",
|
||||
ResourceOwner: "INSTANCE",
|
||||
@ -176,7 +175,7 @@ func Test_passwordComplexityPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.PasswordComplexityPolicyWriteModel
|
||||
want *PasswordComplexityPolicyWriteModel
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
@ -206,7 +205,7 @@ func Test_passwordComplexityPolicy(t *testing.T) {
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: &command.PasswordComplexityPolicyWriteModel{
|
||||
want: &PasswordComplexityPolicyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "id",
|
||||
ResourceOwner: "ro",
|
||||
@ -258,7 +257,7 @@ func Test_passwordComplexityPolicy(t *testing.T) {
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: &command.PasswordComplexityPolicyWriteModel{
|
||||
want: &PasswordComplexityPolicyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "INSTANCE",
|
||||
ResourceOwner: "INSTANCE",
|
@ -11,7 +11,9 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
@ -1577,3 +1579,155 @@ func TestCommandSide_UserDomainClaimedSent(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExistsUser(t *testing.T) {
|
||||
type args struct {
|
||||
filter preparation.FilterToQueryReducer
|
||||
id string
|
||||
resourceOwner string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantExists bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no events",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{}, nil
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "human registered",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
user.NewHumanRegisteredEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("id", "ro").Aggregate,
|
||||
"userName",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"nickName",
|
||||
"displayName",
|
||||
language.German,
|
||||
domain.GenderFemale,
|
||||
"support@zitadel.ch",
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "human added",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("id", "ro").Aggregate,
|
||||
"userName",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"nickName",
|
||||
"displayName",
|
||||
language.German,
|
||||
domain.GenderFemale,
|
||||
"support@zitadel.ch",
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "machine added",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
user.NewMachineAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("id", "ro").Aggregate,
|
||||
"userName",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "user removed",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
user.NewMachineAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("removed", "ro").Aggregate,
|
||||
"userName",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
),
|
||||
user.NewUserRemovedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("removed", "ro").Aggregate,
|
||||
"userName",
|
||||
nil,
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "error durring filter",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "USER-Drebn", "Errors.Internal")
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotExists, err := ExistsUser(context.Background(), tt.args.filter, tt.args.id, tt.args.resourceOwner)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotExists != tt.wantExists {
|
||||
t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
func LoadKeys(config *KeyConfig, keyStorage KeyStorage) (map[string]string, []string, error) {
|
||||
func LoadKeys(config *KeyConfig, keyStorage KeyStorage) (Keys, []string, error) {
|
||||
if config == nil {
|
||||
return nil, nil, errors.ThrowInvalidArgument(nil, "CRYPT-dJK8s", "config must not be nil")
|
||||
}
|
||||
@ -47,7 +47,7 @@ func LoadKeys(config *KeyConfig, keyStorage KeyStorage) (map[string]string, []st
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
keys := make(map[string]string)
|
||||
keys := make(Keys)
|
||||
ids := make([]string, 0, len(config.DecryptionKeyIDs)+1)
|
||||
if config.EncryptionKeyID != "" {
|
||||
key, ok := readKeys[config.EncryptionKeyID]
|
||||
|
@ -146,10 +146,15 @@ func (a *OIDCApp) OriginsValid() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *OIDCApp) getRequiredGrantTypes() []OIDCGrantType {
|
||||
grantTypes := make([]OIDCGrantType, 0)
|
||||
implicit := false
|
||||
for _, r := range a.ResponseTypes {
|
||||
func ContainsRequiredGrantTypes(responseTypes []OIDCResponseType, grantTypes []OIDCGrantType) bool {
|
||||
required := RequiredOIDCGrantTypes(responseTypes)
|
||||
return ContainsOIDCGrantTypes(required, grantTypes)
|
||||
}
|
||||
|
||||
func RequiredOIDCGrantTypes(responseTypes []OIDCResponseType) (grantTypes []OIDCGrantType) {
|
||||
var implicit bool
|
||||
|
||||
for _, r := range responseTypes {
|
||||
switch r {
|
||||
case OIDCResponseTypeCode:
|
||||
grantTypes = append(grantTypes, OIDCGrantTypeAuthorizationCode)
|
||||
@ -160,9 +165,23 @@ func (a *OIDCApp) getRequiredGrantTypes() []OIDCGrantType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return grantTypes
|
||||
}
|
||||
|
||||
func (a *OIDCApp) getRequiredGrantTypes() []OIDCGrantType {
|
||||
return RequiredOIDCGrantTypes(a.ResponseTypes)
|
||||
}
|
||||
|
||||
func ContainsOIDCGrantTypes(shouldContain, list []OIDCGrantType) bool {
|
||||
for _, should := range shouldContain {
|
||||
if !containsOIDCGrantType(list, should) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func containsOIDCGrantType(grantTypes []OIDCGrantType, grantType OIDCGrantType) bool {
|
||||
for _, gt := range grantTypes {
|
||||
if gt == grantType {
|
||||
|
@ -8,6 +8,11 @@ import (
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
)
|
||||
|
||||
type HumanDetails struct {
|
||||
ID string
|
||||
ObjectDetails
|
||||
}
|
||||
|
||||
type Human struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
|
@ -8,7 +8,9 @@ import (
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
)
|
||||
|
||||
var EmailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||
var (
|
||||
EmailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||
)
|
||||
|
||||
type Email struct {
|
||||
es_models.ObjectRoot
|
||||
|
@ -1,11 +1,12 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/ttacon/libphonenumber"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -22,8 +22,14 @@ const (
|
||||
ProjectStateActive
|
||||
ProjectStateInactive
|
||||
ProjectStateRemoved
|
||||
|
||||
projectStateMax
|
||||
)
|
||||
|
||||
func (s ProjectState) Valid() bool {
|
||||
return s > ProjectStateUnspecified && s < projectStateMax
|
||||
}
|
||||
|
||||
type PrivateLabelingSetting int32
|
||||
|
||||
const (
|
||||
|
@ -7,6 +7,7 @@ const (
|
||||
SecretGeneratorTypeInitCode
|
||||
SecretGeneratorTypeVerifyEmailCode
|
||||
SecretGeneratorTypeVerifyPhoneCode
|
||||
SecretGeneratorTypeVerifyDomain
|
||||
SecretGeneratorTypePasswordResetCode
|
||||
SecretGeneratorTypePasswordlessInitCode
|
||||
SecretGeneratorTypeAppSecret
|
||||
@ -14,6 +15,10 @@ const (
|
||||
secretGeneratorTypeCount
|
||||
)
|
||||
|
||||
func (t SecretGeneratorType) Valid() bool {
|
||||
return t > SecretGeneratorTypeUnspecified && t < secretGeneratorTypeCount
|
||||
}
|
||||
|
||||
type SecretGeneratorState int32
|
||||
|
||||
const (
|
||||
|
@ -79,7 +79,7 @@ func BaseEventFromRepo(event *repository.Event) *BaseEvent {
|
||||
ID: event.AggregateID,
|
||||
Type: AggregateType(event.AggregateType),
|
||||
ResourceOwner: event.ResourceOwner.String,
|
||||
InstanceID: event.InstanceID.String,
|
||||
InstanceID: event.InstanceID,
|
||||
Version: Version(event.Version),
|
||||
},
|
||||
EventType: EventType(event.Type),
|
||||
|
@ -82,7 +82,7 @@ func commandsToRepository(instanceID string, cmds []Command) (events []*reposito
|
||||
AggregateID: cmd.Aggregate().ID,
|
||||
AggregateType: repository.AggregateType(cmd.Aggregate().Type),
|
||||
ResourceOwner: sql.NullString{String: cmd.Aggregate().ResourceOwner, Valid: cmd.Aggregate().ResourceOwner != ""},
|
||||
InstanceID: sql.NullString{String: instanceID, Valid: instanceID != ""},
|
||||
InstanceID: instanceID,
|
||||
EditorService: cmd.EditorService(),
|
||||
EditorUser: cmd.EditorUser(),
|
||||
Type: repository.EventType(cmd.Type()),
|
||||
@ -178,7 +178,7 @@ func (es *Eventstore) LatestSequence(ctx context.Context, queryFactory *SearchQu
|
||||
return es.repo.LatestSequence(ctx, query)
|
||||
}
|
||||
|
||||
type queryReducer interface {
|
||||
type QueryReducer interface {
|
||||
reducer
|
||||
//Query returns the SearchQueryFactory for the events needed in reducer
|
||||
Query() *SearchQueryBuilder
|
||||
@ -186,7 +186,7 @@ type queryReducer interface {
|
||||
|
||||
//FilterToQueryReducer filters the events based on the search query of the query function,
|
||||
// appends all events to the reducer and calls it's reduce function
|
||||
func (es *Eventstore) FilterToQueryReducer(ctx context.Context, r queryReducer) error {
|
||||
func (es *Eventstore) FilterToQueryReducer(ctx context.Context, r QueryReducer) error {
|
||||
events, err := es.Filter(ctx, r.Query())
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -380,7 +380,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||
EditorService: "editorService",
|
||||
EditorUser: "editorUser",
|
||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||
InstanceID: sql.NullString{String: "instanceID", Valid: true},
|
||||
InstanceID: "instanceID",
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
@ -418,7 +418,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||
EditorService: "editorService",
|
||||
EditorUser: "editorUser",
|
||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||
InstanceID: sql.NullString{String: "instanceID", Valid: true},
|
||||
InstanceID: "instanceID",
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
@ -429,7 +429,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||
EditorService: "editorService",
|
||||
EditorUser: "editorUser",
|
||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||
InstanceID: sql.NullString{String: "instanceID", Valid: true},
|
||||
InstanceID: "instanceID",
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
@ -585,7 +585,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||
EditorService: "editorService",
|
||||
EditorUser: "editorUser",
|
||||
ResourceOwner: sql.NullString{String: "", Valid: false},
|
||||
InstanceID: sql.NullString{String: "zitadel"},
|
||||
InstanceID: "zitadel",
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
@ -630,7 +630,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||
EditorService: "editorService",
|
||||
EditorUser: "editorUser",
|
||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||
InstanceID: sql.NullString{String: "zitadel"},
|
||||
InstanceID: "zitadel",
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
@ -641,7 +641,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||
EditorService: "editorService",
|
||||
EditorUser: "editorUser",
|
||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||
InstanceID: sql.NullString{String: "zitadel"},
|
||||
InstanceID: "zitadel",
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
@ -654,7 +654,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||
EditorService: "editorService",
|
||||
EditorUser: "editorUser",
|
||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||
InstanceID: sql.NullString{String: "zitadel"},
|
||||
InstanceID: "zitadel",
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
@ -772,7 +772,7 @@ func TestEventstore_Push(t *testing.T) {
|
||||
EditorService: "editorService",
|
||||
EditorUser: "editorUser",
|
||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||
InstanceID: sql.NullString{String: "zitadel"},
|
||||
InstanceID: "zitadel",
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
@ -816,7 +816,7 @@ func TestEventstore_Push(t *testing.T) {
|
||||
EditorService: "editorService",
|
||||
EditorUser: "editorUser",
|
||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||
InstanceID: sql.NullString{String: "zitadel"},
|
||||
InstanceID: "zitadel",
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
@ -827,7 +827,7 @@ func TestEventstore_Push(t *testing.T) {
|
||||
EditorService: "editorService",
|
||||
EditorUser: "editorUser",
|
||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||
InstanceID: sql.NullString{String: "zitadel"},
|
||||
InstanceID: "zitadel",
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
@ -882,7 +882,7 @@ func TestEventstore_Push(t *testing.T) {
|
||||
EditorService: "editorService",
|
||||
EditorUser: "editorUser",
|
||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||
InstanceID: sql.NullString{String: "zitadel"},
|
||||
InstanceID: "zitadel",
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
@ -893,7 +893,7 @@ func TestEventstore_Push(t *testing.T) {
|
||||
EditorService: "editorService",
|
||||
EditorUser: "editorUser",
|
||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||
InstanceID: sql.NullString{String: "zitadel"},
|
||||
InstanceID: "zitadel",
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
@ -906,7 +906,7 @@ func TestEventstore_Push(t *testing.T) {
|
||||
EditorService: "editorService",
|
||||
EditorUser: "editorUser",
|
||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||
InstanceID: sql.NullString{String: "zitadel"},
|
||||
InstanceID: "zitadel",
|
||||
Type: "test.event",
|
||||
Version: "v1",
|
||||
},
|
||||
|
@ -58,7 +58,7 @@ type Event struct {
|
||||
ResourceOwner sql.NullString
|
||||
//InstanceID is the instance where this event belongs to
|
||||
// use the ID of the instance
|
||||
InstanceID sql.NullString
|
||||
InstanceID string
|
||||
}
|
||||
|
||||
//EventType is the description of the change
|
||||
|
@ -71,7 +71,7 @@ const (
|
||||
" $7::VARCHAR AS editor_service," +
|
||||
" IFNULL((resource_owner), $8::VARCHAR) AS resource_owner," +
|
||||
" $9::VARCHAR AS instance_id," +
|
||||
" NEXTVAL(CONCAT('eventstore.', IFNULL($9, 'system'), '_seq'))," +
|
||||
" NEXTVAL(CONCAT('eventstore.', IF($9 <> '', CONCAT('i_', $9), 'system'), '_seq'))," +
|
||||
" aggregate_sequence AS previous_aggregate_sequence," +
|
||||
" aggregate_type_sequence AS previous_aggregate_type_sequence " +
|
||||
"FROM previous_data " +
|
||||
|
@ -136,7 +136,7 @@ func Test_prepareColumns(t *testing.T) {
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
dbRow: []interface{}{time.Time{}, repository.EventType(""), uint64(5), Sequence(0), Sequence(0), Data(nil), "", "", sql.NullString{String: ""}, sql.NullString{String: ""}, repository.AggregateType("user"), "hodor", repository.Version("")},
|
||||
dbRow: []interface{}{time.Time{}, repository.EventType(""), uint64(5), Sequence(0), Sequence(0), Data(nil), "", "", sql.NullString{String: ""}, "", repository.AggregateType("user"), "hodor", repository.Version("")},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -68,6 +68,7 @@ type Instance struct {
|
||||
DefaultLanguage language.Tag
|
||||
SetupStarted domain.Step
|
||||
SetupDone domain.Step
|
||||
Host string
|
||||
}
|
||||
|
||||
func (i *Instance) InstanceID() string {
|
||||
@ -82,6 +83,10 @@ func (i *Instance) ConsoleClientID() string {
|
||||
return i.ConsoleID
|
||||
}
|
||||
|
||||
func (i *Instance) RequestedDomain() string {
|
||||
return i.Host
|
||||
}
|
||||
|
||||
type InstanceSearchQueries struct {
|
||||
SearchRequest
|
||||
Queries []SearchQuery
|
||||
@ -96,7 +101,7 @@ func (q *InstanceSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder
|
||||
}
|
||||
|
||||
func (q *Queries) Instance(ctx context.Context) (*Instance, error) {
|
||||
stmt, scan := prepareIAMQuery()
|
||||
stmt, scan := prepareInstanceQuery(authz.GetInstance(ctx).RequestedDomain())
|
||||
query, args, err := stmt.Where(sq.Eq{
|
||||
InstanceColumnID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}).ToSql()
|
||||
@ -109,7 +114,7 @@ func (q *Queries) Instance(ctx context.Context) (*Instance, error) {
|
||||
}
|
||||
|
||||
func (q *Queries) InstanceByHost(ctx context.Context, host string) (authz.Instance, error) {
|
||||
stmt, scan := prepareIAMQuery()
|
||||
stmt, scan := prepareInstanceQuery(host)
|
||||
query, args, err := stmt.Where(sq.Eq{
|
||||
InstanceColumnID.identifier(): "system", //TODO: change column to domain when available
|
||||
}).ToSql()
|
||||
@ -129,7 +134,7 @@ func (q *Queries) GetDefaultLanguage(ctx context.Context) language.Tag {
|
||||
return iam.DefaultLanguage
|
||||
}
|
||||
|
||||
func prepareIAMQuery() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
|
||||
func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
|
||||
return sq.Select(
|
||||
InstanceColumnID.identifier(),
|
||||
InstanceColumnChangeDate.identifier(),
|
||||
@ -143,17 +148,17 @@ func prepareIAMQuery() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
|
||||
).
|
||||
From(instanceTable.identifier()).PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*Instance, error) {
|
||||
iam := new(Instance)
|
||||
instance := &Instance{Host: host}
|
||||
lang := ""
|
||||
err := row.Scan(
|
||||
&iam.ID,
|
||||
&iam.ChangeDate,
|
||||
&iam.Sequence,
|
||||
&iam.GlobalOrgID,
|
||||
&iam.IAMProjectID,
|
||||
&iam.ConsoleID,
|
||||
&iam.SetupStarted,
|
||||
&iam.SetupDone,
|
||||
&instance.ID,
|
||||
&instance.ChangeDate,
|
||||
&instance.Sequence,
|
||||
&instance.GlobalOrgID,
|
||||
&instance.IAMProjectID,
|
||||
&instance.ConsoleID,
|
||||
&instance.SetupStarted,
|
||||
&instance.SetupDone,
|
||||
&lang,
|
||||
)
|
||||
if err != nil {
|
||||
@ -162,7 +167,7 @@ func prepareIAMQuery() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
|
||||
}
|
||||
return nil, errors.ThrowInternal(err, "QUERY-d9nw", "Errors.Internal")
|
||||
}
|
||||
iam.DefaultLanguage = language.Make(lang)
|
||||
return iam, nil
|
||||
instance.DefaultLanguage = language.Make(lang)
|
||||
return instance, nil
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
@ -26,8 +27,10 @@ func Test_InstancePrepares(t *testing.T) {
|
||||
object interface{}
|
||||
}{
|
||||
{
|
||||
name: "prepareInstanceQuery no result",
|
||||
prepare: prepareIAMQuery,
|
||||
name: "prepareInstanceQuery no result",
|
||||
prepare: func() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
|
||||
return prepareInstanceQuery("")
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
regexp.QuoteMeta(`SELECT projections.instances.id,`+
|
||||
@ -53,8 +56,10 @@ func Test_InstancePrepares(t *testing.T) {
|
||||
object: (*Instance)(nil),
|
||||
},
|
||||
{
|
||||
name: "prepareInstanceQuery found",
|
||||
prepare: prepareIAMQuery,
|
||||
name: "prepareInstanceQuery found",
|
||||
prepare: func() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
|
||||
return prepareInstanceQuery("")
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQuery(
|
||||
regexp.QuoteMeta(`SELECT projections.instances.id,`+
|
||||
@ -104,8 +109,10 @@ func Test_InstancePrepares(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareInstanceQuery sql err",
|
||||
prepare: prepareIAMQuery,
|
||||
name: "prepareInstanceQuery sql err",
|
||||
prepare: func() (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
|
||||
return prepareInstanceQuery("")
|
||||
},
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
regexp.QuoteMeta(`SELECT projections.instances.id,`+
|
||||
|
@ -26,7 +26,7 @@ func testEvent(
|
||||
Version: "v1",
|
||||
AggregateID: "agg-id",
|
||||
ResourceOwner: sql.NullString{String: "ro-id", Valid: true},
|
||||
InstanceID: sql.NullString{String: "instance-id", Valid: true},
|
||||
InstanceID: "instance-id",
|
||||
ID: "event-id",
|
||||
EditorService: "editor-svc",
|
||||
EditorUser: "editor-user",
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"github.com/caos/zitadel/internal/eventstore/handler"
|
||||
"github.com/caos/zitadel/internal/eventstore/handler/crdb"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
"github.com/caos/zitadel/internal/repository/project"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -63,7 +62,7 @@ func NewSecretGeneratorProjection(ctx context.Context, config crdb.StatementHand
|
||||
func (p *SecretGeneratorProjection) reducers() []handler.AggregateReducer {
|
||||
return []handler.AggregateReducer{
|
||||
{
|
||||
Aggregate: project.AggregateType,
|
||||
Aggregate: instance.AggregateType,
|
||||
EventRedusers: []handler.EventReducer{
|
||||
{
|
||||
Event: instance.SecretGeneratorAddedEventType,
|
||||
|
@ -73,7 +73,7 @@ func (e *ConsoleSetEvent) UniqueConstraints() []*eventstore.EventUniqueConstrain
|
||||
func NewIAMConsoleSetEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
clientID string,
|
||||
clientID *string,
|
||||
) *ConsoleSetEvent {
|
||||
return &ConsoleSetEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
@ -81,7 +81,7 @@ func NewIAMConsoleSetEvent(
|
||||
aggregate,
|
||||
ConsoleSetEventType,
|
||||
),
|
||||
ClientID: clientID,
|
||||
ClientID: *clientID,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,6 +58,22 @@ func NewAPIConfigAddedEvent(
|
||||
}
|
||||
}
|
||||
|
||||
func (e *APIConfigAddedEvent) Validate(cmd eventstore.Command) bool {
|
||||
c, ok := cmd.(*APIConfigAddedEvent)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if e.AppID != c.AppID {
|
||||
return false
|
||||
}
|
||||
if e.AuthMethodType != c.AuthMethodType {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func APIConfigAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e := &APIConfigAddedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
|
@ -97,6 +97,92 @@ func NewOIDCConfigAddedEvent(
|
||||
}
|
||||
}
|
||||
|
||||
func (e *OIDCConfigAddedEvent) Validate(cmd eventstore.Command) bool {
|
||||
c, ok := cmd.(*OIDCConfigAddedEvent)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if e.Version != c.Version {
|
||||
return false
|
||||
}
|
||||
if e.AppID != c.AppID {
|
||||
return false
|
||||
}
|
||||
if e.ClientID != "" && e.ClientID != c.ClientID {
|
||||
return false
|
||||
}
|
||||
if e.ClientSecret != c.ClientSecret {
|
||||
return false
|
||||
}
|
||||
if len(e.RedirectUris) != len(c.RedirectUris) {
|
||||
return false
|
||||
}
|
||||
for i, uri := range e.RedirectUris {
|
||||
if uri != c.RedirectUris[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(e.ResponseTypes) != len(c.ResponseTypes) {
|
||||
return false
|
||||
}
|
||||
for i, typ := range e.ResponseTypes {
|
||||
if typ != c.ResponseTypes[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(e.GrantTypes) != len(c.GrantTypes) {
|
||||
return false
|
||||
}
|
||||
for i, typ := range e.GrantTypes {
|
||||
if typ != c.GrantTypes[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if e.ApplicationType != c.ApplicationType {
|
||||
return false
|
||||
}
|
||||
if e.AuthMethodType != c.AuthMethodType {
|
||||
return false
|
||||
}
|
||||
if len(e.PostLogoutRedirectUris) != len(c.PostLogoutRedirectUris) {
|
||||
return false
|
||||
}
|
||||
for i, uri := range e.PostLogoutRedirectUris {
|
||||
if uri != c.PostLogoutRedirectUris[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if e.DevMode != c.DevMode {
|
||||
return false
|
||||
}
|
||||
if e.AccessTokenType != c.AccessTokenType {
|
||||
return false
|
||||
}
|
||||
if e.AccessTokenRoleAssertion != c.AccessTokenRoleAssertion {
|
||||
return false
|
||||
}
|
||||
if e.IDTokenRoleAssertion != c.IDTokenRoleAssertion {
|
||||
return false
|
||||
}
|
||||
if e.IDTokenUserinfoAssertion != c.IDTokenUserinfoAssertion {
|
||||
return false
|
||||
}
|
||||
if e.ClockSkew != c.ClockSkew {
|
||||
return false
|
||||
}
|
||||
if len(e.AdditionalOrigins) != len(c.AdditionalOrigins) {
|
||||
return false
|
||||
}
|
||||
for i, origin := range e.AdditionalOrigins {
|
||||
if origin != c.AdditionalOrigins[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func OIDCConfigAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e := &OIDCConfigAddedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
|
Loading…
x
Reference in New Issue
Block a user