mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-05 13:52:15 +00:00
Merge branch 'main' into next
# Conflicts: # docs/docs/guides/integrate/login-ui/external-login.mdx # internal/command/idp_model.go # proto/zitadel/user/v2alpha/user_service.proto
This commit is contained in:
@@ -34,8 +34,9 @@ import (
|
||||
type Commands struct {
|
||||
httpClient *http.Client
|
||||
|
||||
checkPermission domain.PermissionCheck
|
||||
newCode cryptoCodeFunc
|
||||
checkPermission domain.PermissionCheck
|
||||
newCode cryptoCodeFunc
|
||||
newCodeWithDefault cryptoCodeWithDefaultFunc
|
||||
|
||||
eventstore *eventstore.Eventstore
|
||||
static static.Storage
|
||||
@@ -122,6 +123,7 @@ func StartCommands(
|
||||
httpClient: httpClient,
|
||||
checkPermission: permissionCheck,
|
||||
newCode: newCryptoCode,
|
||||
newCodeWithDefault: newCryptoCodeWithDefaultConfig,
|
||||
sessionTokenCreator: sessionTokenCreator(idGenerator, sessionAlg),
|
||||
sessionTokenVerifier: sessionTokenVerifier,
|
||||
defaultAccessTokenLifetime: defaultAccessTokenLifetime,
|
||||
|
||||
@@ -12,6 +12,10 @@ import (
|
||||
|
||||
type cryptoCodeFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error)
|
||||
|
||||
type cryptoCodeWithDefaultFunc func(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (*CryptoCode, error)
|
||||
|
||||
var emptyConfig = &crypto.GeneratorConfig{}
|
||||
|
||||
type CryptoCode struct {
|
||||
Crypted *crypto.CryptoValue
|
||||
Plain string
|
||||
@@ -19,7 +23,11 @@ type CryptoCode struct {
|
||||
}
|
||||
|
||||
func newCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (*CryptoCode, error) {
|
||||
gen, config, err := secretGenerator(ctx, filter, typ, alg)
|
||||
return newCryptoCodeWithDefaultConfig(ctx, filter, typ, alg, emptyConfig)
|
||||
}
|
||||
|
||||
func newCryptoCodeWithDefaultConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (*CryptoCode, error) {
|
||||
gen, config, err := secretGenerator(ctx, filter, typ, alg, defaultConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -35,15 +43,15 @@ func newCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer,
|
||||
}
|
||||
|
||||
func verifyCryptoCode(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, creation time.Time, expiry time.Duration, crypted *crypto.CryptoValue, plain string) error {
|
||||
gen, _, err := secretGenerator(ctx, filter, typ, alg)
|
||||
gen, _, err := secretGenerator(ctx, filter, typ, alg, emptyConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return crypto.VerifyCode(creation, expiry, crypted, plain, gen)
|
||||
}
|
||||
|
||||
func secretGenerator(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto) (crypto.Generator, *crypto.GeneratorConfig, error) {
|
||||
config, err := secretGeneratorConfig(ctx, filter, typ)
|
||||
func secretGenerator(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, alg crypto.Crypto, defaultConfig *crypto.GeneratorConfig) (crypto.Generator, *crypto.GeneratorConfig, error) {
|
||||
config, err := secretGeneratorConfigWithDefault(ctx, filter, typ, defaultConfig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -58,6 +66,10 @@ func secretGenerator(ctx context.Context, filter preparation.FilterToQueryReduce
|
||||
}
|
||||
|
||||
func secretGeneratorConfig(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType) (*crypto.GeneratorConfig, error) {
|
||||
return secretGeneratorConfigWithDefault(ctx, filter, typ, emptyConfig)
|
||||
}
|
||||
|
||||
func secretGeneratorConfigWithDefault(ctx context.Context, filter preparation.FilterToQueryReducer, typ domain.SecretGeneratorType, defaultConfig *crypto.GeneratorConfig) (*crypto.GeneratorConfig, error) {
|
||||
wm := NewInstanceSecretGeneratorConfigWriteModel(ctx, typ)
|
||||
events, err := filter(ctx, wm.Query())
|
||||
if err != nil {
|
||||
@@ -67,6 +79,9 @@ func secretGeneratorConfig(ctx context.Context, filter preparation.FilterToQuery
|
||||
if err := wm.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if wm.State != domain.SecretGeneratorStateActive {
|
||||
return defaultConfig, nil
|
||||
}
|
||||
return &crypto.GeneratorConfig{
|
||||
Length: wm.Length,
|
||||
Expiry: wm.Expiry,
|
||||
|
||||
@@ -33,6 +33,21 @@ func mockCode(code string, exp time.Duration) cryptoCodeFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func mockCodeWithDefault(code string, exp time.Duration) cryptoCodeWithDefaultFunc {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer, _ domain.SecretGeneratorType, alg crypto.Crypto, _ *crypto.GeneratorConfig) (*CryptoCode, error) {
|
||||
return &CryptoCode{
|
||||
Crypted: &crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte(code),
|
||||
},
|
||||
Plain: code,
|
||||
Expiry: exp,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testGeneratorConfig = crypto.GeneratorConfig{
|
||||
Length: 12,
|
||||
@@ -175,8 +190,9 @@ func Test_verifyCryptoCode(t *testing.T) {
|
||||
|
||||
func Test_secretGenerator(t *testing.T) {
|
||||
type args struct {
|
||||
typ domain.SecretGeneratorType
|
||||
alg crypto.Crypto
|
||||
typ domain.SecretGeneratorType
|
||||
alg crypto.Crypto
|
||||
defaultConfig *crypto.GeneratorConfig
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -190,8 +206,9 @@ func Test_secretGenerator(t *testing.T) {
|
||||
name: "filter config error",
|
||||
eventsore: eventstoreExpect(t, expectFilterError(io.ErrClosedPipe)),
|
||||
args: args{
|
||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||
alg: crypto.CreateMockHashAlg(gomock.NewController(t)),
|
||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||
alg: crypto.CreateMockHashAlg(gomock.NewController(t)),
|
||||
defaultConfig: emptyConfig,
|
||||
},
|
||||
wantErr: io.ErrClosedPipe,
|
||||
},
|
||||
@@ -201,8 +218,9 @@ func Test_secretGenerator(t *testing.T) {
|
||||
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
|
||||
)),
|
||||
args: args{
|
||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||
alg: crypto.CreateMockHashAlg(gomock.NewController(t)),
|
||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||
alg: crypto.CreateMockHashAlg(gomock.NewController(t)),
|
||||
defaultConfig: emptyConfig,
|
||||
},
|
||||
want: crypto.NewHashGenerator(testGeneratorConfig, crypto.CreateMockHashAlg(gomock.NewController(t))),
|
||||
wantConf: &testGeneratorConfig,
|
||||
@@ -213,8 +231,31 @@ func Test_secretGenerator(t *testing.T) {
|
||||
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
|
||||
)),
|
||||
args: args{
|
||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
defaultConfig: emptyConfig,
|
||||
},
|
||||
want: crypto.NewEncryptionGenerator(testGeneratorConfig, crypto.CreateMockEncryptionAlg(gomock.NewController(t))),
|
||||
wantConf: &testGeneratorConfig,
|
||||
},
|
||||
{
|
||||
name: "hash generator with default config",
|
||||
eventsore: eventstoreExpect(t, expectFilter()),
|
||||
args: args{
|
||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||
alg: crypto.CreateMockHashAlg(gomock.NewController(t)),
|
||||
defaultConfig: &testGeneratorConfig,
|
||||
},
|
||||
want: crypto.NewHashGenerator(testGeneratorConfig, crypto.CreateMockHashAlg(gomock.NewController(t))),
|
||||
wantConf: &testGeneratorConfig,
|
||||
},
|
||||
{
|
||||
name: "encryption generator with default config",
|
||||
eventsore: eventstoreExpect(t, expectFilter()),
|
||||
args: args{
|
||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
defaultConfig: &testGeneratorConfig,
|
||||
},
|
||||
want: crypto.NewEncryptionGenerator(testGeneratorConfig, crypto.CreateMockEncryptionAlg(gomock.NewController(t))),
|
||||
wantConf: &testGeneratorConfig,
|
||||
@@ -225,15 +266,16 @@ func Test_secretGenerator(t *testing.T) {
|
||||
eventFromEventPusher(testSecretGeneratorAddedEvent(domain.SecretGeneratorTypeVerifyEmailCode)),
|
||||
)),
|
||||
args: args{
|
||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||
alg: nil,
|
||||
typ: domain.SecretGeneratorTypeVerifyEmailCode,
|
||||
alg: nil,
|
||||
defaultConfig: emptyConfig,
|
||||
},
|
||||
wantErr: errors.ThrowInternalf(nil, "COMMA-RreV6", "Errors.Internal unsupported crypto algorithm type %T", nil),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, gotConf, err := secretGenerator(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg)
|
||||
got, gotConf, err := secretGenerator(context.Background(), tt.eventsore.Filter, tt.args.typ, tt.args.alg, tt.args.defaultConfig)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.IsType(t, tt.want, got)
|
||||
assert.Equal(t, tt.wantConf, gotConf)
|
||||
|
||||
@@ -110,6 +110,16 @@ type LDAPProvider struct {
|
||||
IDPOptions idp.Options
|
||||
}
|
||||
|
||||
type AppleProvider struct {
|
||||
Name string
|
||||
ClientID string
|
||||
TeamID string
|
||||
KeyID string
|
||||
PrivateKey []byte
|
||||
Scopes []string
|
||||
IDPOptions idp.Options
|
||||
}
|
||||
|
||||
func ExistsIDP(ctx context.Context, filter preparation.FilterToQueryReducer, id, orgID string) (exists bool, err error) {
|
||||
writeModel := NewOrgIDPRemoveWriteModel(orgID, id)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/jwt"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
|
||||
@@ -215,6 +216,8 @@ func tokensForSucceededIDPIntent(session idp.Session, encryptionAlg crypto.Encry
|
||||
tokens = s.Tokens
|
||||
case *azuread.Session:
|
||||
tokens = s.Tokens
|
||||
case *apple.Session:
|
||||
tokens = s.Tokens
|
||||
default:
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package command
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
providers "github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/apple"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/github"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
|
||||
@@ -1587,6 +1589,138 @@ func (wm *LDAPIDPWriteModel) GetProviderOptions() idp.Options {
|
||||
return wm.Options
|
||||
}
|
||||
|
||||
type AppleIDPWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
ID string
|
||||
Name string
|
||||
ClientID string
|
||||
TeamID string
|
||||
KeyID string
|
||||
PrivateKey *crypto.CryptoValue
|
||||
Scopes []string
|
||||
idp.Options
|
||||
|
||||
State domain.IDPState
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *idp.AppleIDPAddedEvent:
|
||||
wm.reduceAddedEvent(e)
|
||||
case *idp.AppleIDPChangedEvent:
|
||||
wm.reduceChangedEvent(e)
|
||||
case *idp.RemovedEvent:
|
||||
wm.State = domain.IDPStateRemoved
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) reduceAddedEvent(e *idp.AppleIDPAddedEvent) {
|
||||
wm.Name = e.Name
|
||||
wm.ClientID = e.ClientID
|
||||
wm.TeamID = e.TeamID
|
||||
wm.KeyID = e.KeyID
|
||||
wm.PrivateKey = e.PrivateKey
|
||||
wm.Scopes = e.Scopes
|
||||
wm.Options = e.Options
|
||||
wm.State = domain.IDPStateActive
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) reduceChangedEvent(e *idp.AppleIDPChangedEvent) {
|
||||
if e.Name != nil {
|
||||
wm.Name = *e.Name
|
||||
}
|
||||
if e.ClientID != nil {
|
||||
wm.ClientID = *e.ClientID
|
||||
}
|
||||
if e.PrivateKey != nil {
|
||||
wm.PrivateKey = e.PrivateKey
|
||||
}
|
||||
if e.Scopes != nil {
|
||||
wm.Scopes = e.Scopes
|
||||
}
|
||||
wm.Options.ReduceChanges(e.OptionChanges)
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) NewChanges(
|
||||
name string,
|
||||
clientID string,
|
||||
teamID string,
|
||||
keyID string,
|
||||
privateKey []byte,
|
||||
secretCrypto crypto.Crypto,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
) ([]idp.AppleIDPChanges, error) {
|
||||
changes := make([]idp.AppleIDPChanges, 0)
|
||||
var encryptedKey *crypto.CryptoValue
|
||||
var err error
|
||||
if len(privateKey) != 0 {
|
||||
encryptedKey, err = crypto.Crypt(privateKey, secretCrypto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changes = append(changes, idp.ChangeApplePrivateKey(encryptedKey))
|
||||
}
|
||||
if wm.Name != name {
|
||||
changes = append(changes, idp.ChangeAppleName(name))
|
||||
}
|
||||
if wm.ClientID != clientID {
|
||||
changes = append(changes, idp.ChangeAppleClientID(clientID))
|
||||
}
|
||||
if wm.TeamID != teamID {
|
||||
changes = append(changes, idp.ChangeAppleTeamID(teamID))
|
||||
}
|
||||
if wm.KeyID != keyID {
|
||||
changes = append(changes, idp.ChangeAppleKeyID(keyID))
|
||||
}
|
||||
if slices.Compare(wm.Scopes, scopes) != 0 {
|
||||
changes = append(changes, idp.ChangeAppleScopes(scopes))
|
||||
}
|
||||
|
||||
opts := wm.Options.Changes(options)
|
||||
if !opts.IsZero() {
|
||||
changes = append(changes, idp.ChangeAppleOptions(opts))
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.EncryptionAlgorithm) (providers.Provider, error) {
|
||||
privateKey, err := crypto.Decrypt(wm.PrivateKey, idpAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts := make([]oidc.ProviderOpts, 0, 4)
|
||||
if wm.IsCreationAllowed {
|
||||
opts = append(opts, oidc.WithCreationAllowed())
|
||||
}
|
||||
if wm.IsLinkingAllowed {
|
||||
opts = append(opts, oidc.WithLinkingAllowed())
|
||||
}
|
||||
if wm.IsAutoCreation {
|
||||
opts = append(opts, oidc.WithAutoCreation())
|
||||
}
|
||||
if wm.IsAutoUpdate {
|
||||
opts = append(opts, oidc.WithAutoUpdate())
|
||||
}
|
||||
return apple.New(
|
||||
wm.ClientID,
|
||||
wm.TeamID,
|
||||
wm.KeyID,
|
||||
callbackURL,
|
||||
privateKey,
|
||||
wm.Scopes,
|
||||
opts...,
|
||||
)
|
||||
}
|
||||
|
||||
func (wm *AppleIDPWriteModel) GetProviderOptions() idp.Options {
|
||||
return wm.Options
|
||||
}
|
||||
|
||||
type IDPRemoveWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
@@ -1617,6 +1751,8 @@ func (wm *IDPRemoveWriteModel) Reduce() error {
|
||||
wm.reduceAdded(e.ID)
|
||||
case *idp.LDAPIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID)
|
||||
case *idp.AppleIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID)
|
||||
case *idp.RemovedEvent:
|
||||
wm.reduceRemoved(e.ID)
|
||||
case *idpconfig.IDPConfigAddedEvent:
|
||||
@@ -1699,6 +1835,10 @@ func (wm *IDPTypeWriteModel) Reduce() error {
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeLDAP, e.Aggregate())
|
||||
case *org.LDAPIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeLDAP, e.Aggregate())
|
||||
case *instance.AppleIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeApple, e.Aggregate())
|
||||
case *org.AppleIDPAddedEvent:
|
||||
wm.reduceAdded(e.ID, domain.IDPTypeApple, e.Aggregate())
|
||||
case *instance.OIDCIDPMigratedAzureADEvent:
|
||||
wm.reduceChanged(e.ID, domain.IDPTypeAzureAD)
|
||||
case *org.OIDCIDPMigratedAzureADEvent:
|
||||
@@ -1774,6 +1914,7 @@ func (wm *IDPTypeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
instance.GitLabSelfHostedIDPAddedEventType,
|
||||
instance.GoogleIDPAddedEventType,
|
||||
instance.LDAPIDPAddedEventType,
|
||||
instance.AppleIDPAddedEventType,
|
||||
instance.OIDCIDPMigratedAzureADEventType,
|
||||
instance.OIDCIDPMigratedGoogleEventType,
|
||||
instance.IDPRemovedEventType,
|
||||
@@ -1792,6 +1933,7 @@ func (wm *IDPTypeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
org.GitLabSelfHostedIDPAddedEventType,
|
||||
org.GoogleIDPAddedEventType,
|
||||
org.LDAPIDPAddedEventType,
|
||||
org.AppleIDPAddedEventType,
|
||||
org.OIDCIDPMigratedAzureADEventType,
|
||||
org.OIDCIDPMigratedGoogleEventType,
|
||||
org.IDPRemovedEventType,
|
||||
@@ -1859,6 +2001,8 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp
|
||||
writeModel.model = NewGitLabSelfHostedInstanceIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeGoogle:
|
||||
writeModel.model = NewGoogleInstanceIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeApple:
|
||||
writeModel.model = NewAppleInstanceIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeUnspecified:
|
||||
fallthrough
|
||||
default:
|
||||
@@ -1886,6 +2030,8 @@ func NewAllIDPWriteModel(resourceOwner string, instanceBool bool, id string, idp
|
||||
writeModel.model = NewGitLabSelfHostedOrgIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeGoogle:
|
||||
writeModel.model = NewGoogleOrgIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeApple:
|
||||
writeModel.model = NewAppleOrgIDPWriteModel(resourceOwner, id)
|
||||
case domain.IDPTypeUnspecified:
|
||||
fallthrough
|
||||
default:
|
||||
|
||||
@@ -414,6 +414,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
|
||||
instanceAgg,
|
||||
setup.SMTPConfiguration.From,
|
||||
setup.SMTPConfiguration.FromName,
|
||||
setup.SMTPConfiguration.ReplyToAddress,
|
||||
setup.SMTPConfiguration.SMTP.Host,
|
||||
setup.SMTPConfiguration.SMTP.User,
|
||||
[]byte(setup.SMTPConfiguration.SMTP.Password),
|
||||
|
||||
@@ -467,6 +467,48 @@ func (c *Commands) UpdateInstanceLDAPProvider(ctx context.Context, id string, pr
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddInstanceAppleProvider(ctx context.Context, provider AppleProvider) (string, *domain.ObjectDetails, error) {
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
instanceAgg := instance.NewAggregate(instanceID)
|
||||
id, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
writeModel := NewAppleInstanceIDPWriteModel(instanceID, id)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddInstanceAppleProvider(instanceAgg, writeModel, provider))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return id, pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) UpdateInstanceAppleProvider(ctx context.Context, id string, provider AppleProvider) (*domain.ObjectDetails, error) {
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
instanceAgg := instance.NewAggregate(instanceID)
|
||||
writeModel := NewAppleInstanceIDPWriteModel(instanceID, id)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateInstanceAppleProvider(instanceAgg, writeModel, provider))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cmds) == 0 {
|
||||
// no change, so return directly
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: writeModel.ProcessedSequence,
|
||||
EventDate: writeModel.ChangeDate,
|
||||
ResourceOwner: writeModel.ResourceOwner,
|
||||
}, nil
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteInstanceProvider(ctx context.Context, id string) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareDeleteInstanceProvider(instanceAgg, id))
|
||||
@@ -1518,6 +1560,98 @@ func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, writ
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareAddInstanceAppleProvider(a *instance.Aggregate, writeModel *InstanceAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-jkn3w", "Errors.IDP.ClientIDMissing")
|
||||
}
|
||||
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Ffg32", "Errors.IDP.TeamIDMissing")
|
||||
}
|
||||
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-GDjm5", "Errors.IDP.KeyIDMissing")
|
||||
}
|
||||
if len(provider.PrivateKey) == 0 {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-GVD4n", "Errors.IDP.PrivateKeyMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
if err = writeModel.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privateKey, err := crypto.Encrypt(provider.PrivateKey, c.idpConfigEncryption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{
|
||||
instance.NewAppleIDPAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.ID,
|
||||
provider.Name,
|
||||
provider.ClientID,
|
||||
provider.TeamID,
|
||||
provider.KeyID,
|
||||
privateKey,
|
||||
provider.Scopes,
|
||||
provider.IDPOptions,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareUpdateInstanceAppleProvider(a *instance.Aggregate, writeModel *InstanceAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-FRHBH", "Errors.IDMissing")
|
||||
}
|
||||
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SFm4l", "Errors.IDP.ClientIDMissing")
|
||||
}
|
||||
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SG34t", "Errors.IDP.TeamIDMissing")
|
||||
}
|
||||
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Gh4z2", "Errors.IDP.KeyIDMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
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.Exists() {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "INST-SG3bh", "Errors.IDPConfig.NotExisting")
|
||||
}
|
||||
event, err := writeModel.NewChangedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.ID,
|
||||
provider.Name,
|
||||
provider.ClientID,
|
||||
provider.TeamID,
|
||||
provider.KeyID,
|
||||
provider.PrivateKey,
|
||||
c.idpConfigEncryption,
|
||||
provider.Scopes,
|
||||
provider.IDPOptions,
|
||||
)
|
||||
if err != nil || event == nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{event}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareDeleteInstanceProvider(a *instance.Aggregate, id string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
|
||||
@@ -793,6 +793,73 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
|
||||
return instance.NewLDAPIDPChangedEvent(ctx, aggregate, id, changes)
|
||||
}
|
||||
|
||||
type InstanceAppleIDPWriteModel struct {
|
||||
AppleIDPWriteModel
|
||||
}
|
||||
|
||||
func NewAppleInstanceIDPWriteModel(instanceID, id string) *InstanceAppleIDPWriteModel {
|
||||
return &InstanceAppleIDPWriteModel{
|
||||
AppleIDPWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: instanceID,
|
||||
ResourceOwner: instanceID,
|
||||
},
|
||||
ID: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *InstanceAppleIDPWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *instance.AppleIDPAddedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||
case *instance.AppleIDPChangedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPChangedEvent)
|
||||
case *instance.IDPRemovedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
default:
|
||||
wm.AppleIDPWriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *InstanceAppleIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(instance.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
instance.AppleIDPAddedEventType,
|
||||
instance.AppleIDPChangedEventType,
|
||||
instance.IDPRemovedEventType,
|
||||
).
|
||||
EventData(map[string]interface{}{"id": wm.ID}).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *InstanceAppleIDPWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID string,
|
||||
privateKey []byte,
|
||||
secretCrypto crypto.Crypto,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
) (*instance.AppleIDPChangedEvent, error) {
|
||||
|
||||
changes, err := wm.AppleIDPWriteModel.NewChanges(name, clientID, teamID, keyID, privateKey, secretCrypto, scopes, options)
|
||||
if err != nil || len(changes) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return instance.NewAppleIDPChangedEvent(ctx, aggregate, id, changes)
|
||||
}
|
||||
|
||||
type InstanceIDPRemoveWriteModel struct {
|
||||
IDPRemoveWriteModel
|
||||
}
|
||||
@@ -832,6 +899,8 @@ func (wm *InstanceIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event)
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
|
||||
case *instance.LDAPIDPAddedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.LDAPIDPAddedEvent)
|
||||
case *instance.AppleIDPAddedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||
case *instance.IDPRemovedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
case *instance.IDPConfigAddedEvent:
|
||||
@@ -861,6 +930,7 @@ func (wm *InstanceIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
instance.GitLabSelfHostedIDPAddedEventType,
|
||||
instance.GoogleIDPAddedEventType,
|
||||
instance.LDAPIDPAddedEventType,
|
||||
instance.AppleIDPAddedEventType,
|
||||
instance.IDPRemovedEventType,
|
||||
).
|
||||
EventData(map[string]interface{}{"id": wm.ID}).
|
||||
|
||||
@@ -4857,3 +4857,464 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddInstanceAppleIDP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
provider AppleProvider
|
||||
}
|
||||
type res struct {
|
||||
id string
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"invalid clientID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-jkn3w", "Errors.IDP.ClientIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid teamID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-Ffg32", "Errors.IDP.TeamIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid keyID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-GDjm5", "Errors.IDP.KeyIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid privateKey",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-GVD4n", "Errors.IDP.PrivateKeyMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
},
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
PrivateKey: []byte("privateKey"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "id1",
|
||||
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok all set",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
[]string{"name", "email"},
|
||||
idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
)),
|
||||
},
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
PrivateKey: []byte("privateKey"),
|
||||
Scopes: []string{"name", "email"},
|
||||
IDPOptions: idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "id1",
|
||||
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
idpConfigEncryption: tt.fields.secretCrypto,
|
||||
}
|
||||
id, got, err := c.AddInstanceAppleProvider(tt.args.ctx, tt.args.provider)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.id, id)
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_UpdateInstanceAppleIDP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
id string
|
||||
provider AppleProvider
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"invalid id",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-FRHBH", "Errors.IDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid clientID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-SFm4l", "Errors.IDP.ClientIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid teamID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-SG34t", "Errors.IDP.TeamIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid keyID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-Gh4z2", "Errors.IDP.KeyIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewAppleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"instance1",
|
||||
func() eventstore.Command {
|
||||
t := true
|
||||
event, _ := instance.NewAppleIDPChangedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
[]idp.AppleIDPChanges{
|
||||
idp.ChangeAppleClientID("clientID2"),
|
||||
idp.ChangeAppleTeamID("teamID2"),
|
||||
idp.ChangeAppleKeyID("keyID2"),
|
||||
idp.ChangeApplePrivateKey(&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("newPrivateKey"),
|
||||
}),
|
||||
idp.ChangeAppleScopes([]string{"name", "email"}),
|
||||
idp.ChangeAppleOptions(idp.OptionChanges{
|
||||
IsCreationAllowed: &t,
|
||||
IsLinkingAllowed: &t,
|
||||
IsAutoCreation: &t,
|
||||
IsAutoUpdate: &t,
|
||||
}),
|
||||
},
|
||||
)
|
||||
return event
|
||||
}(),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID2",
|
||||
TeamID: "teamID2",
|
||||
KeyID: "keyID2",
|
||||
PrivateKey: []byte("newPrivateKey"),
|
||||
Scopes: []string{"name", "email"},
|
||||
IDPOptions: idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idpConfigEncryption: tt.fields.secretCrypto,
|
||||
}
|
||||
got, err := c.UpdateInstanceAppleProvider(tt.args.ctx, tt.args.id, tt.args.provider)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,14 @@ import (
|
||||
type InstanceSMTPConfigWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
SenderAddress string
|
||||
SenderName string
|
||||
TLS bool
|
||||
Host string
|
||||
User string
|
||||
Password *crypto.CryptoValue
|
||||
State domain.SMTPConfigState
|
||||
SenderAddress string
|
||||
SenderName string
|
||||
ReplyToAddress string
|
||||
TLS bool
|
||||
Host string
|
||||
User string
|
||||
Password *crypto.CryptoValue
|
||||
State domain.SMTPConfigState
|
||||
|
||||
domain string
|
||||
domainState domain.InstanceDomainState
|
||||
@@ -62,6 +63,7 @@ func (wm *InstanceSMTPConfigWriteModel) Reduce() error {
|
||||
wm.TLS = e.TLS
|
||||
wm.SenderAddress = e.SenderAddress
|
||||
wm.SenderName = e.SenderName
|
||||
wm.ReplyToAddress = e.ReplyToAddress
|
||||
wm.Host = e.Host
|
||||
wm.User = e.User
|
||||
wm.Password = e.Password
|
||||
@@ -76,6 +78,9 @@ func (wm *InstanceSMTPConfigWriteModel) Reduce() error {
|
||||
if e.FromName != nil {
|
||||
wm.SenderName = *e.FromName
|
||||
}
|
||||
if e.ReplyToAddress != nil {
|
||||
wm.ReplyToAddress = *e.ReplyToAddress
|
||||
}
|
||||
if e.Host != nil {
|
||||
wm.Host = *e.Host
|
||||
}
|
||||
@@ -87,6 +92,7 @@ func (wm *InstanceSMTPConfigWriteModel) Reduce() error {
|
||||
wm.TLS = false
|
||||
wm.SenderName = ""
|
||||
wm.SenderAddress = ""
|
||||
wm.ReplyToAddress = ""
|
||||
wm.Host = ""
|
||||
wm.User = ""
|
||||
wm.Password = nil
|
||||
@@ -122,7 +128,7 @@ func (wm *InstanceSMTPConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, tls bool, fromAddress, fromName, smtpHost, smtpUser string) (*instance.SMTPConfigChangedEvent, bool, error) {
|
||||
func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, tls bool, fromAddress, fromName, replyToAddress, smtpHost, smtpUser string) (*instance.SMTPConfigChangedEvent, bool, error) {
|
||||
changes := make([]instance.SMTPConfigChanges, 0)
|
||||
var err error
|
||||
|
||||
@@ -135,6 +141,9 @@ func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, agg
|
||||
if wm.SenderName != fromName {
|
||||
changes = append(changes, instance.ChangeSMTPConfigFromName(fromName))
|
||||
}
|
||||
if wm.ReplyToAddress != replyToAddress {
|
||||
changes = append(changes, instance.ChangeSMTPConfigReplyToAddress(replyToAddress))
|
||||
}
|
||||
if wm.Host != smtpHost {
|
||||
changes = append(changes, instance.ChangeSMTPConfigSMTPHost(smtpHost))
|
||||
}
|
||||
|
||||
@@ -444,6 +444,46 @@ func (c *Commands) UpdateOrgLDAPProvider(ctx context.Context, resourceOwner, id
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddOrgAppleProvider(ctx context.Context, resourceOwner string, provider AppleProvider) (string, *domain.ObjectDetails, error) {
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
id, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
writeModel := NewAppleOrgIDPWriteModel(resourceOwner, id)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgAppleProvider(orgAgg, writeModel, provider))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return id, pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) UpdateOrgAppleProvider(ctx context.Context, resourceOwner, id string, provider AppleProvider) (*domain.ObjectDetails, error) {
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
writeModel := NewAppleOrgIDPWriteModel(resourceOwner, id)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateOrgAppleProvider(orgAgg, writeModel, provider))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cmds) == 0 {
|
||||
// no change, so return directly
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: writeModel.ProcessedSequence,
|
||||
EventDate: writeModel.ChangeDate,
|
||||
ResourceOwner: writeModel.ResourceOwner,
|
||||
}, nil
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteOrgProvider(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareDeleteOrgProvider(orgAgg, resourceOwner, id))
|
||||
@@ -1507,6 +1547,98 @@ func (c *Commands) prepareUpdateOrgLDAPProvider(a *org.Aggregate, writeModel *Or
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareAddOrgAppleProvider(a *org.Aggregate, writeModel *OrgAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-jkn3w", "Errors.IDP.ClientIDMissing")
|
||||
}
|
||||
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Ffg32", "Errors.IDP.TeamIDMissing")
|
||||
}
|
||||
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-GDjm5", "Errors.IDP.KeyIDMissing")
|
||||
}
|
||||
if len(provider.PrivateKey) == 0 {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-GVD4n", "Errors.IDP.PrivateKeyMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
if err = writeModel.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privateKey, err := crypto.Encrypt(provider.PrivateKey, c.idpConfigEncryption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{
|
||||
org.NewAppleIDPAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.ID,
|
||||
provider.Name,
|
||||
provider.ClientID,
|
||||
provider.TeamID,
|
||||
provider.KeyID,
|
||||
privateKey,
|
||||
provider.Scopes,
|
||||
provider.IDPOptions,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareUpdateOrgAppleProvider(a *org.Aggregate, writeModel *OrgAppleIDPWriteModel, provider AppleProvider) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-FRHBH", "Errors.IDMissing")
|
||||
}
|
||||
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SFm4l", "Errors.IDP.ClientIDMissing")
|
||||
}
|
||||
if provider.TeamID = strings.TrimSpace(provider.TeamID); provider.TeamID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SG34t", "Errors.IDP.TeamIDMissing")
|
||||
}
|
||||
if provider.KeyID = strings.TrimSpace(provider.KeyID); provider.KeyID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Gh4z2", "Errors.IDP.KeyIDMissing")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
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.Exists() {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "ORG-SG3bh", "Errors.IDPConfig.NotExisting")
|
||||
}
|
||||
event, err := writeModel.NewChangedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.ID,
|
||||
provider.Name,
|
||||
provider.ClientID,
|
||||
provider.TeamID,
|
||||
provider.KeyID,
|
||||
provider.PrivateKey,
|
||||
c.idpConfigEncryption,
|
||||
provider.Scopes,
|
||||
provider.IDPOptions,
|
||||
)
|
||||
if err != nil || event == nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{event}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareDeleteOrgProvider(a *org.Aggregate, resourceOwner, id string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
|
||||
@@ -803,6 +803,73 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent(
|
||||
return org.NewLDAPIDPChangedEvent(ctx, aggregate, id, changes)
|
||||
}
|
||||
|
||||
type OrgAppleIDPWriteModel struct {
|
||||
AppleIDPWriteModel
|
||||
}
|
||||
|
||||
func NewAppleOrgIDPWriteModel(orgID, id string) *OrgAppleIDPWriteModel {
|
||||
return &OrgAppleIDPWriteModel{
|
||||
AppleIDPWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: orgID,
|
||||
ResourceOwner: orgID,
|
||||
},
|
||||
ID: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgAppleIDPWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *org.AppleIDPAddedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||
case *org.AppleIDPChangedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.AppleIDPChangedEvent)
|
||||
case *org.IDPRemovedEvent:
|
||||
wm.AppleIDPWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
default:
|
||||
wm.AppleIDPWriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgAppleIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(org.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
org.AppleIDPAddedEventType,
|
||||
org.AppleIDPChangedEventType,
|
||||
org.IDPRemovedEventType,
|
||||
).
|
||||
EventData(map[string]interface{}{"id": wm.ID}).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *OrgAppleIDPWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id,
|
||||
name,
|
||||
clientID,
|
||||
teamID,
|
||||
keyID string,
|
||||
privateKey []byte,
|
||||
secretCrypto crypto.Crypto,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
) (*org.AppleIDPChangedEvent, error) {
|
||||
|
||||
changes, err := wm.AppleIDPWriteModel.NewChanges(name, clientID, teamID, keyID, privateKey, secretCrypto, scopes, options)
|
||||
if err != nil || len(changes) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return org.NewAppleIDPChangedEvent(ctx, aggregate, id, changes)
|
||||
}
|
||||
|
||||
type OrgIDPRemoveWriteModel struct {
|
||||
IDPRemoveWriteModel
|
||||
}
|
||||
@@ -842,6 +909,8 @@ func (wm *OrgIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
|
||||
case *org.LDAPIDPAddedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.LDAPIDPAddedEvent)
|
||||
case *org.AppleIDPAddedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.AppleIDPAddedEvent)
|
||||
case *org.IDPRemovedEvent:
|
||||
wm.IDPRemoveWriteModel.AppendEvents(&e.RemovedEvent)
|
||||
case *org.IDPConfigAddedEvent:
|
||||
@@ -871,6 +940,7 @@ func (wm *OrgIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
org.GitLabSelfHostedIDPAddedEventType,
|
||||
org.GoogleIDPAddedEventType,
|
||||
org.LDAPIDPAddedEventType,
|
||||
org.AppleIDPAddedEventType,
|
||||
org.IDPRemovedEventType,
|
||||
).
|
||||
EventData(map[string]interface{}{"id": wm.ID}).
|
||||
|
||||
@@ -4926,6 +4926,473 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddOrgAppleIDP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
resourceOwner string
|
||||
provider AppleProvider
|
||||
}
|
||||
type res struct {
|
||||
id string
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"invalid clientID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-jkn3w", "Errors.IDP.ClientIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid teamID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-Ffg32", "Errors.IDP.TeamIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid keyID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-GDjm5", "Errors.IDP.KeyIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid privateKey",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-GVD4n", "Errors.IDP.PrivateKeyMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
org.NewAppleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
PrivateKey: []byte("privateKey"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "id1",
|
||||
want: &domain.ObjectDetails{ResourceOwner: "org1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok all set",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
org.NewAppleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
[]string{"name", "email"},
|
||||
idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
PrivateKey: []byte("privateKey"),
|
||||
Scopes: []string{"name", "email"},
|
||||
IDPOptions: idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "id1",
|
||||
want: &domain.ObjectDetails{ResourceOwner: "org1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
idpConfigEncryption: tt.fields.secretCrypto,
|
||||
}
|
||||
id, got, err := c.AddOrgAppleProvider(tt.args.ctx, tt.args.resourceOwner, tt.args.provider)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.id, id)
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_UpdateOrgAppleIDP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
resourceOwner string
|
||||
id string
|
||||
provider AppleProvider
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"invalid id",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-FRHBH", "Errors.IDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid clientID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-SFm4l", "Errors.IDP.ClientIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid teamID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-SG34t", "Errors.IDP.TeamIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid keyID",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-Gh4z2", "Errors.IDP.KeyIDMissing"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewAppleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID",
|
||||
TeamID: "teamID",
|
||||
KeyID: "keyID",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{ResourceOwner: "org1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewAppleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"",
|
||||
"clientID",
|
||||
"teamID",
|
||||
"keyID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("privateKey"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
)),
|
||||
),
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
func() eventstore.Command {
|
||||
t := true
|
||||
event, _ := org.NewAppleIDPChangedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
[]idp.AppleIDPChanges{
|
||||
idp.ChangeAppleClientID("clientID2"),
|
||||
idp.ChangeAppleTeamID("teamID2"),
|
||||
idp.ChangeAppleKeyID("keyID2"),
|
||||
idp.ChangeApplePrivateKey(&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("newPrivateKey"),
|
||||
}),
|
||||
idp.ChangeAppleScopes([]string{"name", "email"}),
|
||||
idp.ChangeAppleOptions(idp.OptionChanges{
|
||||
IsCreationAllowed: &t,
|
||||
IsLinkingAllowed: &t,
|
||||
IsAutoCreation: &t,
|
||||
IsAutoUpdate: &t,
|
||||
}),
|
||||
},
|
||||
)
|
||||
return event
|
||||
}(),
|
||||
),
|
||||
),
|
||||
),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
id: "id1",
|
||||
provider: AppleProvider{
|
||||
ClientID: "clientID2",
|
||||
TeamID: "teamID2",
|
||||
KeyID: "keyID2",
|
||||
PrivateKey: []byte("newPrivateKey"),
|
||||
Scopes: []string{"name", "email"},
|
||||
IDPOptions: idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{ResourceOwner: "org1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idpConfigEncryption: tt.fields.secretCrypto,
|
||||
}
|
||||
got, err := c.UpdateOrgAppleProvider(tt.args.ctx, tt.args.resourceOwner, tt.args.id, tt.args.provider)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func stringPointer(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
@@ -473,6 +473,7 @@ func prepareChangeLoginPolicy(a *org.Aggregate, policy *ChangeLoginPolicy) prepa
|
||||
policy.AllowRegister,
|
||||
policy.AllowExternalIDP,
|
||||
policy.ForceMFA,
|
||||
policy.ForceMFALocalOnly,
|
||||
policy.HidePasswordReset,
|
||||
policy.IgnoreUnknownUsernames,
|
||||
policy.AllowDomainDiscovery,
|
||||
|
||||
@@ -67,6 +67,7 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
|
||||
allowRegister,
|
||||
allowExternalIDP,
|
||||
forceMFA,
|
||||
forceMFALocalOnly,
|
||||
hidePasswordReset,
|
||||
ignoreUnknownUsernames,
|
||||
allowDomainDiscovery,
|
||||
@@ -94,6 +95,9 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
|
||||
if wm.ForceMFA != forceMFA {
|
||||
changes = append(changes, policy.ChangeForceMFA(forceMFA))
|
||||
}
|
||||
if wm.ForceMFALocalOnly != forceMFALocalOnly {
|
||||
changes = append(changes, policy.ChangeForceMFALocalOnly(forceMFALocalOnly))
|
||||
}
|
||||
if wm.HidePasswordReset != hidePasswordReset {
|
||||
changes = append(changes, policy.ChangeHidePasswordReset(hidePasswordReset))
|
||||
}
|
||||
|
||||
@@ -574,6 +574,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
&duration10,
|
||||
@@ -2196,7 +2197,7 @@ func TestCommandSide_RemoveMultiFactorLoginPolicy(t *testing.T) {
|
||||
}
|
||||
|
||||
func newLoginPolicyChangedEvent(ctx context.Context, orgID string,
|
||||
usernamePassword, register, externalIDP, mfa, passwordReset, ignoreUnknownUsernames, allowDomainDiscovery, disableLoginWithEmail, disableLoginWithPhone bool,
|
||||
usernamePassword, register, externalIDP, mfa, mfaLocalOnly, passwordReset, ignoreUnknownUsernames, allowDomainDiscovery, disableLoginWithEmail, disableLoginWithPhone bool,
|
||||
passwordlessType domain.PasswordlessType,
|
||||
redirectURI string,
|
||||
passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime *time.Duration) *org.LoginPolicyChangedEvent {
|
||||
@@ -2205,6 +2206,7 @@ func newLoginPolicyChangedEvent(ctx context.Context, orgID string,
|
||||
policy.ChangeAllowRegister(register),
|
||||
policy.ChangeAllowExternalIDP(externalIDP),
|
||||
policy.ChangeForceMFA(mfa),
|
||||
policy.ChangeForceMFALocalOnly(mfaLocalOnly),
|
||||
policy.ChangeHidePasswordReset(passwordReset),
|
||||
policy.ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames),
|
||||
policy.ChangeAllowDomainDiscovery(allowDomainDiscovery),
|
||||
|
||||
@@ -33,6 +33,8 @@ type SessionCommands struct {
|
||||
hasher *crypto.PasswordHasher
|
||||
intentAlg crypto.EncryptionAlgorithm
|
||||
totpAlg crypto.EncryptionAlgorithm
|
||||
otpAlg crypto.EncryptionAlgorithm
|
||||
createCode cryptoCodeWithDefaultFunc
|
||||
createToken func(sessionID string) (id string, token string, err error)
|
||||
now func() time.Time
|
||||
}
|
||||
@@ -45,6 +47,8 @@ func (c *Commands) NewSessionCommands(cmds []SessionCommand, session *SessionWri
|
||||
hasher: c.userPasswordHasher,
|
||||
intentAlg: c.idpConfigEncryption,
|
||||
totpAlg: c.multifactors.OTP.CryptoMFA,
|
||||
otpAlg: c.userEncryption,
|
||||
createCode: c.newCodeWithDefault,
|
||||
createToken: c.sessionTokenCreator,
|
||||
now: time.Now,
|
||||
}
|
||||
@@ -204,6 +208,22 @@ func (s *SessionCommands) TOTPChecked(ctx context.Context, checkedAt time.Time)
|
||||
s.eventCommands = append(s.eventCommands, session.NewTOTPCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt))
|
||||
}
|
||||
|
||||
func (s *SessionCommands) OTPSMSChallenged(ctx context.Context, code *crypto.CryptoValue, expiry time.Duration, returnCode bool) {
|
||||
s.eventCommands = append(s.eventCommands, session.NewOTPSMSChallengedEvent(ctx, s.sessionWriteModel.aggregate, code, expiry, returnCode))
|
||||
}
|
||||
|
||||
func (s *SessionCommands) OTPSMSChecked(ctx context.Context, checkedAt time.Time) {
|
||||
s.eventCommands = append(s.eventCommands, session.NewOTPSMSCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt))
|
||||
}
|
||||
|
||||
func (s *SessionCommands) OTPEmailChallenged(ctx context.Context, code *crypto.CryptoValue, expiry time.Duration, returnCode bool, urlTmpl string) {
|
||||
s.eventCommands = append(s.eventCommands, session.NewOTPEmailChallengedEvent(ctx, s.sessionWriteModel.aggregate, code, expiry, returnCode, urlTmpl))
|
||||
}
|
||||
|
||||
func (s *SessionCommands) OTPEmailChecked(ctx context.Context, checkedAt time.Time) {
|
||||
s.eventCommands = append(s.eventCommands, session.NewOTPEmailCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt))
|
||||
}
|
||||
|
||||
func (s *SessionCommands) SetToken(ctx context.Context, tokenID string) {
|
||||
s.eventCommands = append(s.eventCommands, session.NewTokenSetEvent(ctx, s.sessionWriteModel.aggregate, tokenID))
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package command
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/session"
|
||||
@@ -15,6 +16,12 @@ type WebAuthNChallengeModel struct {
|
||||
RPID string
|
||||
}
|
||||
|
||||
type OTPCode struct {
|
||||
Code *crypto.CryptoValue
|
||||
Expiry time.Duration
|
||||
CreationDate time.Time
|
||||
}
|
||||
|
||||
func (p *WebAuthNChallengeModel) WebAuthNLogin(human *domain.Human, credentialAssertionData []byte) *domain.WebAuthNLogin {
|
||||
return &domain.WebAuthNLogin{
|
||||
ObjectRoot: human.ObjectRoot,
|
||||
@@ -36,11 +43,15 @@ type SessionWriteModel struct {
|
||||
IntentCheckedAt time.Time
|
||||
WebAuthNCheckedAt time.Time
|
||||
TOTPCheckedAt time.Time
|
||||
OTPSMSCheckedAt time.Time
|
||||
OTPEmailCheckedAt time.Time
|
||||
WebAuthNUserVerified bool
|
||||
Metadata map[string][]byte
|
||||
State domain.SessionState
|
||||
|
||||
WebAuthNChallenge *WebAuthNChallengeModel
|
||||
WebAuthNChallenge *WebAuthNChallengeModel
|
||||
OTPSMSCodeChallenge *OTPCode
|
||||
OTPEmailCodeChallenge *OTPCode
|
||||
|
||||
aggregate *eventstore.Aggregate
|
||||
}
|
||||
@@ -73,6 +84,14 @@ func (wm *SessionWriteModel) Reduce() error {
|
||||
wm.reduceWebAuthNChecked(e)
|
||||
case *session.TOTPCheckedEvent:
|
||||
wm.reduceTOTPChecked(e)
|
||||
case *session.OTPSMSChallengedEvent:
|
||||
wm.reduceOTPSMSChallenged(e)
|
||||
case *session.OTPSMSCheckedEvent:
|
||||
wm.reduceOTPSMSChecked(e)
|
||||
case *session.OTPEmailChallengedEvent:
|
||||
wm.reduceOTPEmailChallenged(e)
|
||||
case *session.OTPEmailCheckedEvent:
|
||||
wm.reduceOTPEmailChecked(e)
|
||||
case *session.TokenSetEvent:
|
||||
wm.reduceTokenSet(e)
|
||||
case *session.TerminateEvent:
|
||||
@@ -95,6 +114,10 @@ func (wm *SessionWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
session.WebAuthNChallengedType,
|
||||
session.WebAuthNCheckedType,
|
||||
session.TOTPCheckedType,
|
||||
session.OTPSMSChallengedType,
|
||||
session.OTPSMSCheckedType,
|
||||
session.OTPEmailChallengedType,
|
||||
session.OTPEmailCheckedType,
|
||||
session.TokenSetType,
|
||||
session.MetadataSetType,
|
||||
session.TerminateType,
|
||||
@@ -143,6 +166,32 @@ func (wm *SessionWriteModel) reduceTOTPChecked(e *session.TOTPCheckedEvent) {
|
||||
wm.TOTPCheckedAt = e.CheckedAt
|
||||
}
|
||||
|
||||
func (wm *SessionWriteModel) reduceOTPSMSChallenged(e *session.OTPSMSChallengedEvent) {
|
||||
wm.OTPSMSCodeChallenge = &OTPCode{
|
||||
Code: e.Code,
|
||||
Expiry: e.Expiry,
|
||||
CreationDate: e.CreationDate(),
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *SessionWriteModel) reduceOTPSMSChecked(e *session.OTPSMSCheckedEvent) {
|
||||
wm.OTPSMSCodeChallenge = nil
|
||||
wm.OTPSMSCheckedAt = e.CheckedAt
|
||||
}
|
||||
|
||||
func (wm *SessionWriteModel) reduceOTPEmailChallenged(e *session.OTPEmailChallengedEvent) {
|
||||
wm.OTPEmailCodeChallenge = &OTPCode{
|
||||
Code: e.Code,
|
||||
Expiry: e.Expiry,
|
||||
CreationDate: e.CreationDate(),
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *SessionWriteModel) reduceOTPEmailChecked(e *session.OTPEmailCheckedEvent) {
|
||||
wm.OTPEmailCodeChallenge = nil
|
||||
wm.OTPEmailCheckedAt = e.CheckedAt
|
||||
}
|
||||
|
||||
func (wm *SessionWriteModel) reduceTokenSet(e *session.TokenSetEvent) {
|
||||
wm.TokenID = e.TokenID
|
||||
}
|
||||
@@ -159,7 +208,8 @@ func (wm *SessionWriteModel) AuthenticationTime() time.Time {
|
||||
wm.WebAuthNCheckedAt,
|
||||
wm.TOTPCheckedAt,
|
||||
wm.IntentCheckedAt,
|
||||
// TODO: add OTP (sms and email) check https://github.com/zitadel/zitadel/issues/6224
|
||||
wm.OTPSMSCheckedAt,
|
||||
wm.OTPEmailCheckedAt,
|
||||
} {
|
||||
if check.After(authTime) {
|
||||
authTime = check
|
||||
@@ -187,14 +237,11 @@ func (wm *SessionWriteModel) AuthMethodTypes() []domain.UserAuthMethodType {
|
||||
if !wm.TOTPCheckedAt.IsZero() {
|
||||
types = append(types, domain.UserAuthMethodTypeTOTP)
|
||||
}
|
||||
// TODO: add checks with https://github.com/zitadel/zitadel/issues/6224
|
||||
/*
|
||||
if !wm.TOTPFactor.OTPSMSCheckedAt.IsZero() {
|
||||
types = append(types, domain.UserAuthMethodTypeOTPSMS)
|
||||
}
|
||||
if !wm.TOTPFactor.OTPEmailCheckedAt.IsZero() {
|
||||
types = append(types, domain.UserAuthMethodTypeOTPEmail)
|
||||
}
|
||||
*/
|
||||
if !wm.OTPSMSCheckedAt.IsZero() {
|
||||
types = append(types, domain.UserAuthMethodTypeOTPSMS)
|
||||
}
|
||||
if !wm.OTPEmailCheckedAt.IsZero() {
|
||||
types = append(types, domain.UserAuthMethodTypeOTPEmail)
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/repository/session"
|
||||
)
|
||||
|
||||
func (c *Commands) CreateOTPSMSChallengeReturnCode(dst *string) SessionCommand {
|
||||
return c.createOTPSMSChallenge(true, dst)
|
||||
}
|
||||
|
||||
func (c *Commands) CreateOTPSMSChallenge() SessionCommand {
|
||||
return c.createOTPSMSChallenge(false, nil)
|
||||
}
|
||||
|
||||
func (c *Commands) createOTPSMSChallenge(returnCode bool, dst *string) SessionCommand {
|
||||
return func(ctx context.Context, cmd *SessionCommands) error {
|
||||
if cmd.sessionWriteModel.UserID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-JKL3g", "Errors.User.UserIDMissing")
|
||||
}
|
||||
writeModel := NewHumanOTPSMSWriteModel(cmd.sessionWriteModel.UserID, "")
|
||||
if err := cmd.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return err
|
||||
}
|
||||
if !writeModel.OTPAdded() {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-BJ2g3", "Errors.User.MFA.OTP.NotReady")
|
||||
}
|
||||
code, err := cmd.createCode(ctx, cmd.eventstore.Filter, domain.SecretGeneratorTypeOTPSMS, cmd.otpAlg, c.defaultSecretGenerators.OTPSMS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if returnCode {
|
||||
*dst = code.Plain
|
||||
}
|
||||
cmd.OTPSMSChallenged(ctx, code.Crypted, code.Expiry, returnCode)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) OTPSMSSent(ctx context.Context, sessionID, resourceOwner string) error {
|
||||
sessionWriteModel := NewSessionWriteModel(sessionID, resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sessionWriteModel.OTPSMSCodeChallenge == nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-G3t31", "Errors.User.Code.NotFound")
|
||||
}
|
||||
return c.pushAppendAndReduce(ctx, sessionWriteModel,
|
||||
session.NewOTPSMSSentEvent(ctx, &session.NewAggregate(sessionID, sessionWriteModel.ResourceOwner).Aggregate),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Commands) CreateOTPEmailChallengeURLTemplate(urlTmpl string) (SessionCommand, error) {
|
||||
if err := domain.RenderOTPEmailURLTemplate(io.Discard, urlTmpl, "code", "userID", "loginName", "displayName", language.English); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.createOTPEmailChallenge(false, urlTmpl, nil), nil
|
||||
}
|
||||
|
||||
func (c *Commands) CreateOTPEmailChallengeReturnCode(dst *string) SessionCommand {
|
||||
return c.createOTPEmailChallenge(true, "", dst)
|
||||
}
|
||||
|
||||
func (c *Commands) CreateOTPEmailChallenge() SessionCommand {
|
||||
return c.createOTPEmailChallenge(false, "", nil)
|
||||
}
|
||||
|
||||
func (c *Commands) createOTPEmailChallenge(returnCode bool, urlTmpl string, dst *string) SessionCommand {
|
||||
return func(ctx context.Context, cmd *SessionCommands) error {
|
||||
if cmd.sessionWriteModel.UserID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-JK3gp", "Errors.User.UserIDMissing")
|
||||
}
|
||||
writeModel := NewHumanOTPEmailWriteModel(cmd.sessionWriteModel.UserID, "")
|
||||
if err := cmd.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return err
|
||||
}
|
||||
if !writeModel.OTPAdded() {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-JKLJ3", "Errors.User.MFA.OTP.NotReady")
|
||||
}
|
||||
code, err := cmd.createCode(ctx, cmd.eventstore.Filter, domain.SecretGeneratorTypeOTPEmail, cmd.otpAlg, c.defaultSecretGenerators.OTPEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if returnCode {
|
||||
*dst = code.Plain
|
||||
}
|
||||
cmd.OTPEmailChallenged(ctx, code.Crypted, code.Expiry, returnCode, urlTmpl)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) OTPEmailSent(ctx context.Context, sessionID, resourceOwner string) error {
|
||||
sessionWriteModel := NewSessionWriteModel(sessionID, resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sessionWriteModel.OTPEmailCodeChallenge == nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-SLr02", "Errors.User.Code.NotFound")
|
||||
}
|
||||
return c.pushAppendAndReduce(ctx, sessionWriteModel,
|
||||
session.NewOTPEmailSentEvent(ctx, &session.NewAggregate(sessionID, sessionWriteModel.ResourceOwner).Aggregate),
|
||||
)
|
||||
}
|
||||
|
||||
func CheckOTPSMS(code string) SessionCommand {
|
||||
return func(ctx context.Context, cmd *SessionCommands) (err error) {
|
||||
if cmd.sessionWriteModel.UserID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-VDrh3", "Errors.User.UserIDMissing")
|
||||
}
|
||||
challenge := cmd.sessionWriteModel.OTPSMSCodeChallenge
|
||||
if challenge == nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-SF3tv", "Errors.User.Code.NotFound")
|
||||
}
|
||||
err = crypto.VerifyCodeWithAlgorithm(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.OTPSMSChecked(ctx, cmd.now())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func CheckOTPEmail(code string) SessionCommand {
|
||||
return func(ctx context.Context, cmd *SessionCommands) (err error) {
|
||||
if cmd.sessionWriteModel.UserID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-ejo2w", "Errors.User.UserIDMissing")
|
||||
}
|
||||
challenge := cmd.sessionWriteModel.OTPEmailCodeChallenge
|
||||
if challenge == nil {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-zF3g3", "Errors.User.Code.NotFound")
|
||||
}
|
||||
err = crypto.VerifyCodeWithAlgorithm(challenge.CreationDate, challenge.Expiry, challenge.Code, code, cmd.otpAlg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.OTPEmailChecked(ctx, cmd.now())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,951 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/session"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestCommands_CreateOTPSMSChallengeReturnCode(t *testing.T) {
|
||||
type fields struct {
|
||||
userID string
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
createCode cryptoCodeWithDefaultFunc
|
||||
}
|
||||
type res struct {
|
||||
err error
|
||||
returnCode string
|
||||
commands []eventstore.Command
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "userID missing, precondition error",
|
||||
fields: fields{
|
||||
userID: "",
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-JKL3g", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "otp not ready, precondition error",
|
||||
fields: fields{
|
||||
userID: "userID",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-BJ2g3", "Errors.User.MFA.OTP.NotReady"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate code",
|
||||
fields: fields{
|
||||
userID: "userID",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPSMSAddedEvent(context.Background(), &user.NewAggregate("userID", "org").Aggregate),
|
||||
),
|
||||
),
|
||||
),
|
||||
createCode: mockCodeWithDefault("1234567", 5*time.Minute),
|
||||
},
|
||||
res: res{
|
||||
returnCode: "1234567",
|
||||
commands: []eventstore.Command{
|
||||
session.NewOTPSMSChallengedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("1234567"),
|
||||
},
|
||||
5*time.Minute,
|
||||
true,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
// config will not be actively used for the test (is only for default),
|
||||
// but not providing it would result in a nil pointer
|
||||
defaultSecretGenerators: &SecretGenerators{
|
||||
OTPSMS: emptyConfig,
|
||||
},
|
||||
}
|
||||
var dst string
|
||||
cmd := c.CreateOTPSMSChallengeReturnCode(&dst)
|
||||
|
||||
sessionModel := &SessionWriteModel{
|
||||
UserID: tt.fields.userID,
|
||||
UserCheckedAt: testNow,
|
||||
State: domain.SessionStateActive,
|
||||
aggregate: &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
}
|
||||
cmds := &SessionCommands{
|
||||
sessionCommands: []SessionCommand{cmd},
|
||||
sessionWriteModel: sessionModel,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
createCode: tt.fields.createCode,
|
||||
now: time.Now,
|
||||
}
|
||||
|
||||
err := cmd(context.Background(), cmds)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.returnCode, dst)
|
||||
assert.Equal(t, tt.res.commands, cmds.eventCommands)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_CreateOTPSMSChallenge(t *testing.T) {
|
||||
type fields struct {
|
||||
userID string
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
createCode cryptoCodeWithDefaultFunc
|
||||
}
|
||||
type res struct {
|
||||
err error
|
||||
commands []eventstore.Command
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "userID missing, precondition error",
|
||||
fields: fields{
|
||||
userID: "",
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-JKL3g", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "otp not ready, precondition error",
|
||||
fields: fields{
|
||||
userID: "userID",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-BJ2g3", "Errors.User.MFA.OTP.NotReady"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate code",
|
||||
fields: fields{
|
||||
userID: "userID",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPSMSAddedEvent(context.Background(), &user.NewAggregate("userID", "org").Aggregate),
|
||||
),
|
||||
),
|
||||
),
|
||||
createCode: mockCodeWithDefault("1234567", 5*time.Minute),
|
||||
},
|
||||
res: res{
|
||||
commands: []eventstore.Command{
|
||||
session.NewOTPSMSChallengedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("1234567"),
|
||||
},
|
||||
5*time.Minute,
|
||||
false,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
// config will not be actively used for the test (is only for default),
|
||||
// but not providing it would result in a nil pointer
|
||||
defaultSecretGenerators: &SecretGenerators{
|
||||
OTPSMS: emptyConfig,
|
||||
},
|
||||
}
|
||||
|
||||
cmd := c.CreateOTPSMSChallenge()
|
||||
|
||||
sessionModel := &SessionWriteModel{
|
||||
UserID: tt.fields.userID,
|
||||
UserCheckedAt: testNow,
|
||||
State: domain.SessionStateActive,
|
||||
aggregate: &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
}
|
||||
cmds := &SessionCommands{
|
||||
sessionCommands: []SessionCommand{cmd},
|
||||
sessionWriteModel: sessionModel,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
createCode: tt.fields.createCode,
|
||||
now: time.Now,
|
||||
}
|
||||
|
||||
err := cmd(context.Background(), cmds)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.commands, cmds.eventCommands)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_OTPSMSSent(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
sessionID string
|
||||
resourceOwner string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "not challenged, precondition error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
sessionID: "sessionID",
|
||||
resourceOwner: "instanceID",
|
||||
},
|
||||
wantErr: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-G3t31", "Errors.User.Code.NotFound"),
|
||||
},
|
||||
{
|
||||
name: "challenged and sent",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
session.NewOTPSMSChallengedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("1234567"),
|
||||
},
|
||||
5*time.Minute,
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
session.NewOTPSMSSentEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
sessionID: "sessionID",
|
||||
resourceOwner: "instanceID",
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
err := c.OTPSMSSent(tt.args.ctx, tt.args.sessionID, tt.args.resourceOwner)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_CreateOTPEmailChallengeURLTemplate(t *testing.T) {
|
||||
type fields struct {
|
||||
userID string
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
createCode cryptoCodeWithDefaultFunc
|
||||
}
|
||||
type args struct {
|
||||
urlTmpl string
|
||||
}
|
||||
type res struct {
|
||||
templateError error
|
||||
err error
|
||||
commands []eventstore.Command
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "invalid template, precondition error",
|
||||
args: args{
|
||||
urlTmpl: "https://example.com/mfa/email?userID={{.UserID}}&code={{.InvalidField}}",
|
||||
},
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
res: res{
|
||||
templateError: caos_errs.ThrowInvalidArgument(nil, "DOMAIN-ieYa7", "Errors.User.InvalidURLTemplate"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "userID missing, precondition error",
|
||||
args: args{
|
||||
urlTmpl: "https://example.com/mfa/email?userID={{.UserID}}&code={{.Code}}&lang={{.PreferredLanguage}}",
|
||||
},
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-JK3gp", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "otp not ready, precondition error",
|
||||
args: args{
|
||||
urlTmpl: "https://example.com/mfa/email?userID={{.UserID}}&code={{.Code}}&lang={{.PreferredLanguage}}",
|
||||
},
|
||||
fields: fields{
|
||||
userID: "userID",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-JKLJ3", "Errors.User.MFA.OTP.NotReady"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate code",
|
||||
args: args{
|
||||
urlTmpl: "https://example.com/mfa/email?userID={{.UserID}}&code={{.Code}}&lang={{.PreferredLanguage}}",
|
||||
},
|
||||
fields: fields{
|
||||
userID: "userID",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPEmailAddedEvent(context.Background(), &user.NewAggregate("userID", "org").Aggregate),
|
||||
),
|
||||
),
|
||||
),
|
||||
createCode: mockCodeWithDefault("1234567", 5*time.Minute),
|
||||
},
|
||||
res: res{
|
||||
commands: []eventstore.Command{
|
||||
session.NewOTPEmailChallengedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("1234567"),
|
||||
},
|
||||
5*time.Minute,
|
||||
false,
|
||||
"https://example.com/mfa/email?userID={{.UserID}}&code={{.Code}}&lang={{.PreferredLanguage}}",
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
// config will not be actively used for the test (is only for default),
|
||||
// but not providing it would result in a nil pointer
|
||||
defaultSecretGenerators: &SecretGenerators{
|
||||
OTPEmail: emptyConfig,
|
||||
},
|
||||
}
|
||||
|
||||
cmd, err := c.CreateOTPEmailChallengeURLTemplate(tt.args.urlTmpl)
|
||||
assert.ErrorIs(t, err, tt.res.templateError)
|
||||
if tt.res.templateError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sessionModel := &SessionWriteModel{
|
||||
UserID: tt.fields.userID,
|
||||
UserCheckedAt: testNow,
|
||||
State: domain.SessionStateActive,
|
||||
aggregate: &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
}
|
||||
cmds := &SessionCommands{
|
||||
sessionCommands: []SessionCommand{cmd},
|
||||
sessionWriteModel: sessionModel,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
createCode: tt.fields.createCode,
|
||||
now: time.Now,
|
||||
}
|
||||
|
||||
err = cmd(context.Background(), cmds)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.commands, cmds.eventCommands)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_CreateOTPEmailChallengeReturnCode(t *testing.T) {
|
||||
type fields struct {
|
||||
userID string
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
createCode cryptoCodeWithDefaultFunc
|
||||
}
|
||||
type res struct {
|
||||
err error
|
||||
returnCode string
|
||||
commands []eventstore.Command
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "userID missing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-JK3gp", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "otp not ready, precondition error",
|
||||
fields: fields{
|
||||
userID: "userID",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-JKLJ3", "Errors.User.MFA.OTP.NotReady"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate code",
|
||||
fields: fields{
|
||||
userID: "userID",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPEmailAddedEvent(context.Background(), &user.NewAggregate("userID", "org").Aggregate),
|
||||
),
|
||||
),
|
||||
),
|
||||
createCode: mockCodeWithDefault("1234567", 5*time.Minute),
|
||||
},
|
||||
res: res{
|
||||
returnCode: "1234567",
|
||||
commands: []eventstore.Command{
|
||||
session.NewOTPEmailChallengedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("1234567"),
|
||||
},
|
||||
5*time.Minute,
|
||||
true,
|
||||
"",
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
// config will not be actively used for the test (is only for default),
|
||||
// but not providing it would result in a nil pointer
|
||||
defaultSecretGenerators: &SecretGenerators{
|
||||
OTPEmail: emptyConfig,
|
||||
},
|
||||
}
|
||||
var dst string
|
||||
cmd := c.CreateOTPEmailChallengeReturnCode(&dst)
|
||||
|
||||
sessionModel := &SessionWriteModel{
|
||||
UserID: tt.fields.userID,
|
||||
UserCheckedAt: testNow,
|
||||
State: domain.SessionStateActive,
|
||||
aggregate: &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
}
|
||||
cmds := &SessionCommands{
|
||||
sessionCommands: []SessionCommand{cmd},
|
||||
sessionWriteModel: sessionModel,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
createCode: tt.fields.createCode,
|
||||
now: time.Now,
|
||||
}
|
||||
|
||||
err := cmd(context.Background(), cmds)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.returnCode, dst)
|
||||
assert.Equal(t, tt.res.commands, cmds.eventCommands)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_CreateOTPEmailChallenge(t *testing.T) {
|
||||
type fields struct {
|
||||
userID string
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
createCode cryptoCodeWithDefaultFunc
|
||||
}
|
||||
type res struct {
|
||||
err error
|
||||
commands []eventstore.Command
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "userID missing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-JK3gp", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "otp not ready, precondition error",
|
||||
fields: fields{
|
||||
userID: "userID",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-JKLJ3", "Errors.User.MFA.OTP.NotReady"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate code",
|
||||
fields: fields{
|
||||
userID: "userID",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPEmailAddedEvent(context.Background(), &user.NewAggregate("userID", "org").Aggregate),
|
||||
),
|
||||
),
|
||||
),
|
||||
createCode: mockCodeWithDefault("1234567", 5*time.Minute),
|
||||
},
|
||||
res: res{
|
||||
commands: []eventstore.Command{
|
||||
session.NewOTPEmailChallengedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("1234567"),
|
||||
},
|
||||
5*time.Minute,
|
||||
false,
|
||||
"",
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
// config will not be actively used for the test (is only for default),
|
||||
// but not providing it would result in a nil pointer
|
||||
defaultSecretGenerators: &SecretGenerators{
|
||||
OTPEmail: emptyConfig,
|
||||
},
|
||||
}
|
||||
|
||||
cmd := c.CreateOTPEmailChallenge()
|
||||
|
||||
sessionModel := &SessionWriteModel{
|
||||
UserID: tt.fields.userID,
|
||||
UserCheckedAt: testNow,
|
||||
State: domain.SessionStateActive,
|
||||
aggregate: &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
}
|
||||
cmds := &SessionCommands{
|
||||
sessionCommands: []SessionCommand{cmd},
|
||||
sessionWriteModel: sessionModel,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
createCode: tt.fields.createCode,
|
||||
now: time.Now,
|
||||
}
|
||||
|
||||
err := cmd(context.Background(), cmds)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.commands, cmds.eventCommands)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_OTPEmailSent(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
sessionID string
|
||||
resourceOwner string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "not challenged, precondition error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
sessionID: "sessionID",
|
||||
resourceOwner: "instanceID",
|
||||
},
|
||||
wantErr: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-SLr02", "Errors.User.Code.NotFound"),
|
||||
},
|
||||
{
|
||||
name: "challenged and sent",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
session.NewOTPEmailChallengedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("1234567"),
|
||||
},
|
||||
5*time.Minute,
|
||||
false,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
eventPusherToEvents(
|
||||
session.NewOTPEmailSentEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
sessionID: "sessionID",
|
||||
resourceOwner: "instanceID",
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
err := c.OTPEmailSent(tt.args.ctx, tt.args.sessionID, tt.args.resourceOwner)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckOTPSMS(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
userID string
|
||||
otpCodeChallenge *OTPCode
|
||||
otpAlg crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
code string
|
||||
}
|
||||
type res struct {
|
||||
err error
|
||||
commands []eventstore.Command
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "missing userID",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
userID: "",
|
||||
},
|
||||
args: args{
|
||||
code: "code",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-VDrh3", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing challenge",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
userID: "userID",
|
||||
otpCodeChallenge: nil,
|
||||
},
|
||||
args: args{
|
||||
code: "code",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-SF3tv", "Errors.User.Code.NotFound"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid code",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
userID: "userID",
|
||||
otpCodeChallenge: &OTPCode{
|
||||
Code: &crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("code"),
|
||||
},
|
||||
Expiry: 5 * time.Minute,
|
||||
CreationDate: testNow.Add(-10 * time.Minute),
|
||||
},
|
||||
otpAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
code: "code",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "CODE-QvUQ4P", "Errors.User.Code.Expired"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "check ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
userID: "userID",
|
||||
otpCodeChallenge: &OTPCode{
|
||||
Code: &crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("code"),
|
||||
},
|
||||
Expiry: 5 * time.Minute,
|
||||
CreationDate: testNow,
|
||||
},
|
||||
otpAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
code: "code",
|
||||
},
|
||||
res: res{
|
||||
commands: []eventstore.Command{
|
||||
session.NewOTPSMSCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
testNow,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := CheckOTPSMS(tt.args.code)
|
||||
|
||||
sessionModel := &SessionWriteModel{
|
||||
UserID: tt.fields.userID,
|
||||
UserCheckedAt: testNow,
|
||||
State: domain.SessionStateActive,
|
||||
OTPSMSCodeChallenge: tt.fields.otpCodeChallenge,
|
||||
aggregate: &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
}
|
||||
cmds := &SessionCommands{
|
||||
sessionCommands: []SessionCommand{cmd},
|
||||
sessionWriteModel: sessionModel,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
otpAlg: tt.fields.otpAlg,
|
||||
now: func() time.Time {
|
||||
return testNow
|
||||
},
|
||||
}
|
||||
|
||||
err := cmd(context.Background(), cmds)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.commands, cmds.eventCommands)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckOTPEmail(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
userID string
|
||||
otpCodeChallenge *OTPCode
|
||||
otpAlg crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
code string
|
||||
}
|
||||
type res struct {
|
||||
err error
|
||||
commands []eventstore.Command
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "missing userID",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
userID: "",
|
||||
},
|
||||
args: args{
|
||||
code: "code",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-ejo2w", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing challenge",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
userID: "userID",
|
||||
otpCodeChallenge: nil,
|
||||
},
|
||||
args: args{
|
||||
code: "code",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-zF3g3", "Errors.User.Code.NotFound"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid code",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
userID: "userID",
|
||||
otpCodeChallenge: &OTPCode{
|
||||
Code: &crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("code"),
|
||||
},
|
||||
Expiry: 5 * time.Minute,
|
||||
CreationDate: testNow.Add(-10 * time.Minute),
|
||||
},
|
||||
otpAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
code: "code",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.ThrowPreconditionFailed(nil, "CODE-QvUQ4P", "Errors.User.Code.Expired"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "check ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
userID: "userID",
|
||||
otpCodeChallenge: &OTPCode{
|
||||
Code: &crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("code"),
|
||||
},
|
||||
Expiry: 5 * time.Minute,
|
||||
CreationDate: testNow,
|
||||
},
|
||||
otpAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
code: "code",
|
||||
},
|
||||
res: res{
|
||||
commands: []eventstore.Command{
|
||||
session.NewOTPEmailCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
testNow,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := CheckOTPEmail(tt.args.code)
|
||||
|
||||
sessionModel := &SessionWriteModel{
|
||||
UserID: tt.fields.userID,
|
||||
UserCheckedAt: testNow,
|
||||
State: domain.SessionStateActive,
|
||||
OTPEmailCodeChallenge: tt.fields.otpCodeChallenge,
|
||||
aggregate: &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||
}
|
||||
cmds := &SessionCommands{
|
||||
sessionCommands: []SessionCommand{cmd},
|
||||
sessionWriteModel: sessionModel,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
otpAlg: tt.fields.otpAlg,
|
||||
now: func() time.Time {
|
||||
return testNow
|
||||
},
|
||||
}
|
||||
|
||||
err := cmd(context.Background(), cmds)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.commands, cmds.eventCommands)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
func (c *Commands) AddSMTPConfig(ctx context.Context, config *smtp.Config) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
validation := c.prepareAddSMTPConfig(instanceAgg, config.From, config.FromName, config.SMTP.Host, config.SMTP.User, []byte(config.SMTP.Password), config.Tls)
|
||||
validation := c.prepareAddSMTPConfig(instanceAgg, config.From, config.FromName, config.ReplyToAddress, config.SMTP.Host, config.SMTP.User, []byte(config.SMTP.Password), config.Tls)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -35,7 +35,7 @@ func (c *Commands) AddSMTPConfig(ctx context.Context, config *smtp.Config) (*dom
|
||||
|
||||
func (c *Commands) ChangeSMTPConfig(ctx context.Context, config *smtp.Config) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
validation := c.prepareChangeSMTPConfig(instanceAgg, config.From, config.FromName, config.SMTP.Host, config.SMTP.User, config.Tls)
|
||||
validation := c.prepareChangeSMTPConfig(instanceAgg, config.From, config.FromName, config.ReplyToAddress, config.SMTP.Host, config.SMTP.User, config.Tls)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -99,11 +99,14 @@ func (c *Commands) RemoveSMTPConfig(ctx context.Context) (*domain.ObjectDetails,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, hostAndPort, user string, password []byte, tls bool) preparation.Validation {
|
||||
func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, replyTo, hostAndPort, user string, password []byte, tls bool) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if from = strings.TrimSpace(from); from == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INST-mruNY", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
replyTo = strings.TrimSpace(replyTo)
|
||||
|
||||
hostAndPort = strings.TrimSpace(hostAndPort)
|
||||
if _, _, err := net.SplitHostPort(hostAndPort); err != nil {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INST-9JdRe", "Errors.Invalid.Argument")
|
||||
@@ -136,6 +139,7 @@ func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, hostA
|
||||
tls,
|
||||
from,
|
||||
name,
|
||||
replyTo,
|
||||
hostAndPort,
|
||||
user,
|
||||
smtpPassword,
|
||||
@@ -145,11 +149,13 @@ func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, hostA
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, hostAndPort, user string, tls bool) preparation.Validation {
|
||||
func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, replyTo, hostAndPort, user string, tls bool) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if from = strings.TrimSpace(from); from == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INST-ASv2d", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
replyTo = strings.TrimSpace(replyTo)
|
||||
hostAndPort = strings.TrimSpace(hostAndPort)
|
||||
if _, _, err := net.SplitHostPort(hostAndPort); err != nil {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INST-Kv875", "Errors.Invalid.Argument")
|
||||
@@ -174,6 +180,7 @@ func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, ho
|
||||
tls,
|
||||
from,
|
||||
name,
|
||||
replyTo,
|
||||
hostAndPort,
|
||||
user,
|
||||
)
|
||||
|
||||
@@ -95,6 +95,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"[email protected]",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@@ -106,9 +107,10 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
smtp: &smtp.Config{
|
||||
Tls: true,
|
||||
From: "[email protected]",
|
||||
FromName: "name",
|
||||
Tls: true,
|
||||
From: "[email protected]",
|
||||
FromName: "name",
|
||||
ReplyToAddress: "",
|
||||
SMTP: smtp.SMTP{
|
||||
Host: "host:587",
|
||||
User: "user",
|
||||
@@ -150,6 +152,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"[email protected]",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{
|
||||
@@ -184,6 +187,72 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add smtp config with reply to address, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewDomainAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
"domain.ch",
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
instance.NewDomainPolicyAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
true, true, false,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"INSTANCE",
|
||||
instance.NewSMTPConfigAddedEvent(
|
||||
context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
true,
|
||||
"[email protected]",
|
||||
"name",
|
||||
"[email protected]",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("password"),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
smtp: &smtp.Config{
|
||||
Tls: true,
|
||||
From: "[email protected]",
|
||||
FromName: "name",
|
||||
ReplyToAddress: "[email protected]",
|
||||
SMTP: smtp.SMTP{
|
||||
Host: "host:587",
|
||||
User: "user",
|
||||
Password: "password",
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "smtp config, port is missing",
|
||||
fields: fields{
|
||||
@@ -258,6 +327,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"[email protected]",
|
||||
"name",
|
||||
"",
|
||||
"[2001:db8::1]:2525",
|
||||
"user",
|
||||
&crypto.CryptoValue{
|
||||
@@ -396,6 +466,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"[email protected]",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@@ -446,6 +517,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"[email protected]",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@@ -496,6 +568,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"[email protected]",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@@ -511,6 +584,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
false,
|
||||
"[email protected]",
|
||||
"name2",
|
||||
"[email protected]",
|
||||
"host2:587",
|
||||
"user2",
|
||||
),
|
||||
@@ -522,9 +596,10 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
smtp: &smtp.Config{
|
||||
Tls: false,
|
||||
From: "[email protected]",
|
||||
FromName: "name2",
|
||||
Tls: false,
|
||||
From: "[email protected]",
|
||||
FromName: "name2",
|
||||
ReplyToAddress: "[email protected]",
|
||||
SMTP: smtp.SMTP{
|
||||
Host: "host2:587",
|
||||
User: "user2",
|
||||
@@ -607,6 +682,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"[email protected]",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@@ -622,6 +698,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
false,
|
||||
"[email protected]",
|
||||
"name2",
|
||||
"[email protected]",
|
||||
"[2001:db8::1]:2525",
|
||||
"user2",
|
||||
),
|
||||
@@ -633,9 +710,10 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
smtp: &smtp.Config{
|
||||
Tls: false,
|
||||
From: "[email protected]",
|
||||
FromName: "name2",
|
||||
Tls: false,
|
||||
From: "[email protected]",
|
||||
FromName: "name2",
|
||||
ReplyToAddress: "[email protected]",
|
||||
SMTP: smtp.SMTP{
|
||||
Host: "[2001:db8::1]:2525",
|
||||
User: "user2",
|
||||
@@ -716,6 +794,7 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) {
|
||||
true,
|
||||
"from",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@@ -819,6 +898,7 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"from",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@@ -868,11 +948,12 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newSMTPConfigChangedEvent(ctx context.Context, tls bool, fromAddress, fromName, host, user string) *instance.SMTPConfigChangedEvent {
|
||||
func newSMTPConfigChangedEvent(ctx context.Context, tls bool, fromAddress, fromName, replyTo, host, user string) *instance.SMTPConfigChangedEvent {
|
||||
changes := []instance.SMTPConfigChanges{
|
||||
instance.ChangeSMTPConfigTLS(tls),
|
||||
instance.ChangeSMTPConfigFromAddress(fromAddress),
|
||||
instance.ChangeSMTPConfigFromName(fromName),
|
||||
instance.ChangeSMTPConfigReplyToAddress(replyTo),
|
||||
instance.ChangeSMTPConfigSMTPHost(host),
|
||||
instance.ChangeSMTPConfigSMTPUser(user),
|
||||
}
|
||||
|
||||
@@ -310,7 +310,6 @@ func (c *Commands) HumanCheckOTPSMS(ctx context.Context, userID, code, resourceO
|
||||
resourceOwner,
|
||||
authRequest,
|
||||
writeModel,
|
||||
domain.SecretGeneratorTypeOTPSMS,
|
||||
succeededEvent,
|
||||
failedEvent,
|
||||
)
|
||||
@@ -431,7 +430,6 @@ func (c *Commands) HumanCheckOTPEmail(ctx context.Context, userID, code, resourc
|
||||
resourceOwner,
|
||||
authRequest,
|
||||
writeModel,
|
||||
domain.SecretGeneratorTypeOTPEmail,
|
||||
succeededEvent,
|
||||
failedEvent,
|
||||
)
|
||||
@@ -497,7 +495,6 @@ func (c *Commands) humanCheckOTP(
|
||||
userID, code, resourceOwner string,
|
||||
authRequest *domain.AuthRequest,
|
||||
writeModelByID func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error),
|
||||
secretGeneratorType domain.SecretGeneratorType,
|
||||
checkSucceededEvent func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command,
|
||||
checkFailedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command,
|
||||
) error {
|
||||
|
||||
Reference in New Issue
Block a user