mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 04:57:33 +00:00
feat(login): additionally use email/phone for authentication (#4563)
* feat: add ability to disable login by email and phone * feat: check login by email and phone * fix: set verified email / phone correctly on notify users * update projection version * fix merge * fix email/phone verified reduce tests * fix user tests * loginname check * cleanup * fix: update user projection version to handle fixed statement
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
api_http "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -132,3 +134,28 @@ func AppendAndReduce(object interface {
|
||||
object.AppendEvents(events...)
|
||||
return object.Reduce()
|
||||
}
|
||||
|
||||
func queryAndReduce(ctx context.Context, filter preparation.FilterToQueryReducer, wm eventstore.QueryReducer) error {
|
||||
events, err := filter(ctx, wm.Query())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return nil
|
||||
}
|
||||
wm.AppendEvents(events...)
|
||||
return wm.Reduce()
|
||||
}
|
||||
|
||||
type existsWriteModel interface {
|
||||
Exists() bool
|
||||
eventstore.QueryReducer
|
||||
}
|
||||
|
||||
func exists(ctx context.Context, filter preparation.FilterToQueryReducer, wm existsWriteModel) (bool, error) {
|
||||
err := queryAndReduce(ctx, filter, wm)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return wm.Exists(), nil
|
||||
}
|
||||
|
@@ -62,3 +62,7 @@ func (rm *IDPConfigWriteModel) reduceConfigChangedEvent(e *idpconfig.IDPConfigCh
|
||||
func (rm *IDPConfigWriteModel) reduceConfigStateChanged(configID string, state domain.IDPConfigState) {
|
||||
rm.State = state
|
||||
}
|
||||
|
||||
func (rm *IDPConfigWriteModel) Exists() bool {
|
||||
return rm.State.Exists()
|
||||
}
|
||||
|
@@ -72,6 +72,8 @@ type InstanceSetup struct {
|
||||
HidePasswordReset bool
|
||||
IgnoreUnknownUsername bool
|
||||
AllowDomainDiscovery bool
|
||||
DisableLoginWithEmail bool
|
||||
DisableLoginWithPhone bool
|
||||
PasswordlessType domain.PasswordlessType
|
||||
DefaultRedirectURI string
|
||||
PasswordCheckLifetime time.Duration
|
||||
@@ -219,6 +221,8 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
|
||||
setup.LoginPolicy.HidePasswordReset,
|
||||
setup.LoginPolicy.IgnoreUnknownUsername,
|
||||
setup.LoginPolicy.AllowDomainDiscovery,
|
||||
setup.LoginPolicy.DisableLoginWithEmail,
|
||||
setup.LoginPolicy.DisableLoginWithPhone,
|
||||
setup.LoginPolicy.PasswordlessType,
|
||||
setup.LoginPolicy.DefaultRedirectURI,
|
||||
setup.LoginPolicy.PasswordCheckLifetime,
|
||||
|
@@ -15,56 +15,17 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
func (c *Commands) ChangeDefaultLoginPolicy(ctx context.Context, policy *domain.LoginPolicy) (*domain.LoginPolicy, error) {
|
||||
existingPolicy := NewInstanceLoginPolicyWriteModel(ctx)
|
||||
instanceAgg := InstanceAggregateFromWriteModel(&existingPolicy.LoginPolicyWriteModel.WriteModel)
|
||||
event, err := c.changeDefaultLoginPolicy(ctx, instanceAgg, existingPolicy, policy)
|
||||
func (c *Commands) ChangeDefaultLoginPolicy(ctx context.Context, policy *ChangeLoginPolicy) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareChangeDefaultLoginPolicy(instanceAgg, policy))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, event)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingPolicy, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToLoginPolicy(&existingPolicy.LoginPolicyWriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) changeDefaultLoginPolicy(ctx context.Context, instanceAgg *eventstore.Aggregate, existingPolicy *InstanceLoginPolicyWriteModel, policy *domain.LoginPolicy) (eventstore.Command, error) {
|
||||
if ok := domain.ValidateDefaultRedirectURI(policy.DefaultRedirectURI); !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-SFdqd", "Errors.IAM.LoginPolicy.RedirectURIInvalid")
|
||||
}
|
||||
err := c.defaultLoginPolicyWriteModelByID(ctx, existingPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-M0sif", "Errors.IAM.LoginPolicy.NotFound")
|
||||
}
|
||||
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx,
|
||||
instanceAgg,
|
||||
policy.AllowUsernamePassword,
|
||||
policy.AllowRegister,
|
||||
policy.AllowExternalIDP,
|
||||
policy.ForceMFA,
|
||||
policy.HidePasswordReset,
|
||||
policy.IgnoreUnknownUsernames,
|
||||
policy.AllowDomainDiscovery,
|
||||
policy.PasswordlessType,
|
||||
policy.DefaultRedirectURI,
|
||||
policy.PasswordCheckLifetime,
|
||||
policy.ExternalLoginCheckLifetime,
|
||||
policy.MFAInitSkipLifetime,
|
||||
policy.SecondFactorCheckLifetime,
|
||||
policy.MultiFactorCheckLifetime,
|
||||
)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged")
|
||||
}
|
||||
return changedEvent, nil
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddIDPProviderToDefaultLoginPolicy(ctx context.Context, idpProvider *domain.IDPProvider) (*domain.IDPProvider, error) {
|
||||
@@ -255,6 +216,44 @@ func (c *Commands) getDefaultLoginPolicy(ctx context.Context) (*domain.LoginPoli
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func prepareChangeDefaultLoginPolicy(a *instance.Aggregate, policy *ChangeLoginPolicy) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if ok := domain.ValidateDefaultRedirectURI(policy.DefaultRedirectURI); !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-SFdqd", "Errors.IAM.LoginPolicy.RedirectURIInvalid")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
wm := NewInstanceLoginPolicyWriteModel(ctx)
|
||||
if err := queryAndReduce(ctx, filter, wm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !wm.State.Exists() {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-M0sif", "Errors.IAM.LoginPolicy.NotFound")
|
||||
}
|
||||
changedEvent, hasChanged := wm.NewChangedEvent(ctx, &a.Aggregate,
|
||||
policy.AllowUsernamePassword,
|
||||
policy.AllowRegister,
|
||||
policy.AllowExternalIDP,
|
||||
policy.ForceMFA,
|
||||
policy.HidePasswordReset,
|
||||
policy.IgnoreUnknownUsernames,
|
||||
policy.AllowDomainDiscovery,
|
||||
policy.DisableLoginWithEmail,
|
||||
policy.DisableLoginWithPhone,
|
||||
policy.PasswordlessType,
|
||||
policy.DefaultRedirectURI,
|
||||
policy.PasswordCheckLifetime,
|
||||
policy.ExternalLoginCheckLifetime,
|
||||
policy.MFAInitSkipLifetime,
|
||||
policy.SecondFactorCheckLifetime,
|
||||
policy.MultiFactorCheckLifetime)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged")
|
||||
}
|
||||
return []eventstore.Command{changedEvent}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareAddDefaultLoginPolicy(
|
||||
a *instance.Aggregate,
|
||||
allowUsernamePassword bool,
|
||||
@@ -264,6 +263,8 @@ func prepareAddDefaultLoginPolicy(
|
||||
hidePasswordReset bool,
|
||||
ignoreUnknownUsernames bool,
|
||||
allowDomainDiscovery bool,
|
||||
disableLoginWithEmail bool,
|
||||
disableLoginWithPhone bool,
|
||||
passwordlessType domain.PasswordlessType,
|
||||
defaultRedirectURI string,
|
||||
passwordCheckLifetime time.Duration,
|
||||
@@ -295,6 +296,8 @@ func prepareAddDefaultLoginPolicy(
|
||||
hidePasswordReset,
|
||||
ignoreUnknownUsernames,
|
||||
allowDomainDiscovery,
|
||||
disableLoginWithEmail,
|
||||
disableLoginWithPhone,
|
||||
passwordlessType,
|
||||
defaultRedirectURI,
|
||||
passwordCheckLifetime,
|
||||
|
@@ -67,7 +67,9 @@ func (wm *InstanceLoginPolicyWriteModel) NewChangedEvent(
|
||||
forceMFA,
|
||||
hidePasswordReset,
|
||||
ignoreUnknownUsernames,
|
||||
allowDomainDiscovery bool,
|
||||
allowDomainDiscovery,
|
||||
disableLoginWithEmail,
|
||||
disableLoginWithPhone bool,
|
||||
passwordlessType domain.PasswordlessType,
|
||||
defaultRedirectURI string,
|
||||
passwordCheckLifetime,
|
||||
@@ -120,6 +122,12 @@ func (wm *InstanceLoginPolicyWriteModel) NewChangedEvent(
|
||||
if wm.MultiFactorCheckLifetime != multiFactorCheckLifetime {
|
||||
changes = append(changes, policy.ChangeMultiFactorCheckLifetime(multiFactorCheckLifetime))
|
||||
}
|
||||
if wm.DisableLoginWithEmail != disableLoginWithEmail {
|
||||
changes = append(changes, policy.ChangeDisableLoginWithEmail(disableLoginWithEmail))
|
||||
}
|
||||
if wm.DisableLoginWithPhone != disableLoginWithPhone {
|
||||
changes = append(changes, policy.ChangeDisableLoginWithPhone(disableLoginWithPhone))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
@@ -24,10 +24,10 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
policy *domain.LoginPolicy
|
||||
policy *ChangeLoginPolicy
|
||||
}
|
||||
type res struct {
|
||||
want *domain.LoginPolicy
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -46,7 +46,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
policy: &domain.LoginPolicy{
|
||||
policy: &ChangeLoginPolicy{
|
||||
AllowRegister: true,
|
||||
AllowExternalIDP: true,
|
||||
},
|
||||
@@ -71,6 +71,8 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@@ -85,7 +87,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
policy: &domain.LoginPolicy{
|
||||
policy: &ChangeLoginPolicy{
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
@@ -93,6 +95,8 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
DisableLoginWithEmail: true,
|
||||
DisableLoginWithPhone: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
DefaultRedirectURI: "https://example.com/redirect",
|
||||
PasswordCheckLifetime: time.Hour * 1,
|
||||
@@ -123,6 +127,8 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@@ -145,6 +151,8 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*10,
|
||||
@@ -159,7 +167,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
policy: &domain.LoginPolicy{
|
||||
policy: &ChangeLoginPolicy{
|
||||
AllowRegister: false,
|
||||
AllowUsernamePassword: false,
|
||||
AllowExternalIDP: false,
|
||||
@@ -167,6 +175,8 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
HidePasswordReset: false,
|
||||
IgnoreUnknownUsernames: false,
|
||||
AllowDomainDiscovery: false,
|
||||
DisableLoginWithEmail: false,
|
||||
DisableLoginWithPhone: false,
|
||||
PasswordlessType: domain.PasswordlessTypeNotAllowed,
|
||||
DefaultRedirectURI: "",
|
||||
PasswordCheckLifetime: time.Hour * 10,
|
||||
@@ -177,26 +187,8 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.LoginPolicy{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
InstanceID: "INSTANCE",
|
||||
AggregateID: "INSTANCE",
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
AllowRegister: false,
|
||||
AllowUsernamePassword: false,
|
||||
AllowExternalIDP: false,
|
||||
ForceMFA: false,
|
||||
HidePasswordReset: false,
|
||||
IgnoreUnknownUsernames: false,
|
||||
AllowDomainDiscovery: false,
|
||||
PasswordlessType: domain.PasswordlessTypeNotAllowed,
|
||||
DefaultRedirectURI: "",
|
||||
PasswordCheckLifetime: time.Hour * 10,
|
||||
ExternalLoginCheckLifetime: time.Hour * 20,
|
||||
MFAInitSkipLifetime: time.Hour * 30,
|
||||
SecondFactorCheckLifetime: time.Hour * 40,
|
||||
MultiFactorCheckLifetime: time.Hour * 50,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -287,6 +279,8 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -326,6 +320,8 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -385,6 +381,8 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -526,6 +524,8 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -565,6 +565,8 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -617,6 +619,8 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -674,6 +678,8 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -739,6 +745,8 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1293,7 +1301,8 @@ func TestCommandSide_RemoveMultiFactorDefaultLoginPolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA, hidePasswordReset, ignoreUnknownUsernames, allowDomainDiscovery bool,
|
||||
func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA,
|
||||
hidePasswordReset, ignoreUnknownUsernames, allowDomainDiscovery, disableLoginWithEmail, disableLoginWithPhone bool,
|
||||
passwordlessType domain.PasswordlessType,
|
||||
redirectURI string,
|
||||
passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime time.Duration) *instance.LoginPolicyChangedEvent {
|
||||
@@ -1307,6 +1316,8 @@ func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allow
|
||||
policy.ChangeHidePasswordReset(hidePasswordReset),
|
||||
policy.ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames),
|
||||
policy.ChangeAllowDomainDiscovery(allowDomainDiscovery),
|
||||
policy.ChangeDisableLoginWithEmail(disableLoginWithEmail),
|
||||
policy.ChangeDisableLoginWithPhone(disableLoginWithPhone),
|
||||
policy.ChangePasswordlessType(passwordlessType),
|
||||
policy.ChangeDefaultRedirectURI(redirectURI),
|
||||
policy.ChangePasswordCheckLifetime(passwordLifetime),
|
||||
|
@@ -2,9 +2,12 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
@@ -12,74 +15,63 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
func (c *Commands) AddLoginPolicy(ctx context.Context, resourceOwner string, policy *domain.LoginPolicy) (*domain.LoginPolicy, error) {
|
||||
if resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Fn8ds", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if ok := domain.ValidateDefaultRedirectURI(policy.DefaultRedirectURI); !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-WSfdq", "Errors.Org.LoginPolicy.RedirectURIInvalid")
|
||||
}
|
||||
addedPolicy := NewOrgLoginPolicyWriteModel(resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
|
||||
type AddLoginPolicy struct {
|
||||
AllowUsernamePassword bool
|
||||
AllowRegister bool
|
||||
AllowExternalIDP bool
|
||||
IDPProviders []*AddLoginPolicyIDP
|
||||
ForceMFA bool
|
||||
SecondFactors []domain.SecondFactorType
|
||||
MultiFactors []domain.MultiFactorType
|
||||
PasswordlessType domain.PasswordlessType
|
||||
HidePasswordReset bool
|
||||
IgnoreUnknownUsernames bool
|
||||
AllowDomainDiscovery bool
|
||||
DefaultRedirectURI string
|
||||
PasswordCheckLifetime time.Duration
|
||||
ExternalLoginCheckLifetime time.Duration
|
||||
MFAInitSkipLifetime time.Duration
|
||||
SecondFactorCheckLifetime time.Duration
|
||||
MultiFactorCheckLifetime time.Duration
|
||||
DisableLoginWithEmail bool
|
||||
DisableLoginWithPhone bool
|
||||
}
|
||||
|
||||
type AddLoginPolicyIDP struct {
|
||||
ConfigID string
|
||||
Type domain.IdentityProviderType
|
||||
}
|
||||
|
||||
type ChangeLoginPolicy struct {
|
||||
AllowUsernamePassword bool
|
||||
AllowRegister bool
|
||||
AllowExternalIDP bool
|
||||
ForceMFA bool
|
||||
PasswordlessType domain.PasswordlessType
|
||||
HidePasswordReset bool
|
||||
IgnoreUnknownUsernames bool
|
||||
AllowDomainDiscovery bool
|
||||
DefaultRedirectURI string
|
||||
PasswordCheckLifetime time.Duration
|
||||
ExternalLoginCheckLifetime time.Duration
|
||||
MFAInitSkipLifetime time.Duration
|
||||
SecondFactorCheckLifetime time.Duration
|
||||
MultiFactorCheckLifetime time.Duration
|
||||
DisableLoginWithEmail bool
|
||||
DisableLoginWithPhone bool
|
||||
}
|
||||
|
||||
func (c *Commands) AddLoginPolicy(ctx context.Context, resourceOwner string, policy *AddLoginPolicy) (*domain.ObjectDetails, error) {
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareAddLoginPolicy(orgAgg, policy))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if addedPolicy.State == domain.PolicyStateActive {
|
||||
return nil, caos_errs.ThrowAlreadyExists(nil, "Org-Dgfb2", "Errors.Org.LoginPolicy.AlreadyExists")
|
||||
}
|
||||
|
||||
orgAgg := OrgAggregateFromWriteModel(&addedPolicy.WriteModel)
|
||||
cmds := []eventstore.Command{
|
||||
org.NewLoginPolicyAddedEvent(
|
||||
ctx,
|
||||
orgAgg,
|
||||
policy.AllowUsernamePassword,
|
||||
policy.AllowRegister,
|
||||
policy.AllowExternalIDP,
|
||||
policy.ForceMFA,
|
||||
policy.HidePasswordReset,
|
||||
policy.IgnoreUnknownUsernames,
|
||||
policy.AllowDomainDiscovery,
|
||||
policy.PasswordlessType,
|
||||
policy.DefaultRedirectURI,
|
||||
policy.PasswordCheckLifetime,
|
||||
policy.ExternalLoginCheckLifetime,
|
||||
policy.MFAInitSkipLifetime,
|
||||
policy.SecondFactorCheckLifetime,
|
||||
policy.MultiFactorCheckLifetime),
|
||||
}
|
||||
for _, factor := range policy.SecondFactors {
|
||||
if !factor.Valid() {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-SFeea", "Errors.Org.LoginPolicy.MFA.Unspecified")
|
||||
}
|
||||
cmds = append(cmds, org.NewLoginPolicySecondFactorAddedEvent(ctx, orgAgg, factor))
|
||||
}
|
||||
for _, factor := range policy.MultiFactors {
|
||||
if !factor.Valid() {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-WSfrg", "Errors.Org.LoginPolicy.MFA.Unspecified")
|
||||
}
|
||||
cmds = append(cmds, org.NewLoginPolicyMultiFactorAddedEvent(ctx, orgAgg, factor))
|
||||
}
|
||||
for _, provider := range policy.IDPProviders {
|
||||
if provider.Type == domain.IdentityProviderTypeOrg {
|
||||
_, err = c.getOrgIDPConfigByID(ctx, provider.IDPConfigID, resourceOwner)
|
||||
} else {
|
||||
_, err = c.getInstanceIDPConfigByID(ctx, provider.IDPConfigID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "Org-FEd32", "Errors.IDPConfig.NotExisting")
|
||||
}
|
||||
cmds = append(cmds, org.NewIdentityProviderAddedEvent(ctx, orgAgg, provider.IDPConfigID, provider.Type))
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(addedPolicy, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToLoginPolicy(&addedPolicy.LoginPolicyWriteModel), nil
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) orgLoginPolicyWriteModelByID(ctx context.Context, orgID string) (*OrgLoginPolicyWriteModel, error) {
|
||||
@@ -102,54 +94,17 @@ func (c *Commands) getOrgLoginPolicy(ctx context.Context, orgID string) (*domain
|
||||
return c.getDefaultLoginPolicy(ctx)
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeLoginPolicy(ctx context.Context, resourceOwner string, policy *domain.LoginPolicy) (*domain.LoginPolicy, error) {
|
||||
if resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Mf9sf", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if ok := domain.ValidateDefaultRedirectURI(policy.DefaultRedirectURI); !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Sfd21", "Errors.Org.LoginPolicy.RedirectURIInvalid")
|
||||
}
|
||||
existingPolicy := NewOrgLoginPolicyWriteModel(resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
|
||||
func (c *Commands) ChangeLoginPolicy(ctx context.Context, resourceOwner string, policy *ChangeLoginPolicy) (*domain.ObjectDetails, error) {
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareChangeLoginPolicy(orgAgg, policy))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "Org-M0sif", "Errors.Org.LoginPolicy.NotFound")
|
||||
}
|
||||
|
||||
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LoginPolicyWriteModel.WriteModel)
|
||||
changedEvent, hasChanged := existingPolicy.NewChangedEvent(
|
||||
ctx,
|
||||
orgAgg,
|
||||
policy.AllowUsernamePassword,
|
||||
policy.AllowRegister,
|
||||
policy.AllowExternalIDP,
|
||||
policy.ForceMFA,
|
||||
policy.HidePasswordReset,
|
||||
policy.IgnoreUnknownUsernames,
|
||||
policy.AllowDomainDiscovery,
|
||||
policy.PasswordlessType,
|
||||
policy.DefaultRedirectURI,
|
||||
policy.PasswordCheckLifetime,
|
||||
policy.ExternalLoginCheckLifetime,
|
||||
policy.MFAInitSkipLifetime,
|
||||
policy.SecondFactorCheckLifetime,
|
||||
policy.MultiFactorCheckLifetime)
|
||||
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-5M9vdd", "Errors.Org.LoginPolicy.NotChanged")
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingPolicy, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToLoginPolicy(&existingPolicy.LoginPolicyWriteModel), nil
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveLoginPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
|
||||
@@ -437,3 +392,106 @@ func (c *Commands) orgLoginPolicyAuthFactorsWriteModel(ctx context.Context, orgI
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func prepareAddLoginPolicy(a *org.Aggregate, policy *AddLoginPolicy) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if ok := domain.ValidateDefaultRedirectURI(policy.DefaultRedirectURI); !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-WSfdq", "Errors.Org.LoginPolicy.RedirectURIInvalid")
|
||||
}
|
||||
for _, factor := range policy.SecondFactors {
|
||||
if !factor.Valid() {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-SFeea", "Errors.Org.LoginPolicy.MFA.Unspecified")
|
||||
}
|
||||
}
|
||||
for _, factor := range policy.MultiFactors {
|
||||
if !factor.Valid() {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-WSfrg", "Errors.Org.LoginPolicy.MFA.Unspecified")
|
||||
}
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
if exists, err := exists(ctx, filter, NewOrgLoginPolicyWriteModel(a.ID)); exists || err != nil {
|
||||
return nil, caos_errs.ThrowAlreadyExists(nil, "Org-Dgfb2", "Errors.Org.LoginPolicy.AlreadyExists")
|
||||
}
|
||||
for _, idp := range policy.IDPProviders {
|
||||
exists, err := idpExists(ctx, filter, idp)
|
||||
if !exists || err != nil {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(err, "Org-FEd32", "Errors.IDPConfig.NotExisting")
|
||||
}
|
||||
}
|
||||
cmds := make([]eventstore.Command, 0, len(policy.SecondFactors)+len(policy.MultiFactors)+len(policy.IDPProviders)+1)
|
||||
cmds = append(cmds, org.NewLoginPolicyAddedEvent(ctx, &a.Aggregate,
|
||||
policy.AllowUsernamePassword,
|
||||
policy.AllowRegister,
|
||||
policy.AllowExternalIDP,
|
||||
policy.ForceMFA,
|
||||
policy.HidePasswordReset,
|
||||
policy.IgnoreUnknownUsernames,
|
||||
policy.AllowDomainDiscovery,
|
||||
policy.DisableLoginWithEmail,
|
||||
policy.DisableLoginWithPhone,
|
||||
policy.PasswordlessType,
|
||||
policy.DefaultRedirectURI,
|
||||
policy.PasswordCheckLifetime,
|
||||
policy.ExternalLoginCheckLifetime,
|
||||
policy.MFAInitSkipLifetime,
|
||||
policy.SecondFactorCheckLifetime,
|
||||
policy.MultiFactorCheckLifetime,
|
||||
))
|
||||
for _, factor := range policy.SecondFactors {
|
||||
cmds = append(cmds, org.NewLoginPolicySecondFactorAddedEvent(ctx, &a.Aggregate, factor))
|
||||
}
|
||||
for _, factor := range policy.MultiFactors {
|
||||
cmds = append(cmds, org.NewLoginPolicyMultiFactorAddedEvent(ctx, &a.Aggregate, factor))
|
||||
}
|
||||
for _, idp := range policy.IDPProviders {
|
||||
cmds = append(cmds, org.NewIdentityProviderAddedEvent(ctx, &a.Aggregate, idp.ConfigID, idp.Type))
|
||||
}
|
||||
return cmds, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareChangeLoginPolicy(a *org.Aggregate, policy *ChangeLoginPolicy) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if ok := domain.ValidateDefaultRedirectURI(policy.DefaultRedirectURI); !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Sfd21", "Errors.Org.LoginPolicy.RedirectURIInvalid")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
wm := NewOrgLoginPolicyWriteModel(a.ID)
|
||||
if err := queryAndReduce(ctx, filter, wm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !wm.State.Exists() {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "Org-M0sif", "Errors.Org.LoginPolicy.NotFound")
|
||||
}
|
||||
changedEvent, hasChanged := wm.NewChangedEvent(ctx, &a.Aggregate,
|
||||
policy.AllowUsernamePassword,
|
||||
policy.AllowRegister,
|
||||
policy.AllowExternalIDP,
|
||||
policy.ForceMFA,
|
||||
policy.HidePasswordReset,
|
||||
policy.IgnoreUnknownUsernames,
|
||||
policy.AllowDomainDiscovery,
|
||||
policy.DisableLoginWithEmail,
|
||||
policy.DisableLoginWithPhone,
|
||||
policy.PasswordlessType,
|
||||
policy.DefaultRedirectURI,
|
||||
policy.PasswordCheckLifetime,
|
||||
policy.ExternalLoginCheckLifetime,
|
||||
policy.MFAInitSkipLifetime,
|
||||
policy.SecondFactorCheckLifetime,
|
||||
policy.MultiFactorCheckLifetime)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-5M9vdd", "Errors.Org.LoginPolicy.NotChanged")
|
||||
}
|
||||
return []eventstore.Command{changedEvent}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func idpExists(ctx context.Context, filter preparation.FilterToQueryReducer, idp *AddLoginPolicyIDP) (bool, error) {
|
||||
if idp.Type == domain.IdentityProviderTypeSystem {
|
||||
return exists(ctx, filter, NewInstanceIDPConfigWriteModel(ctx, idp.ConfigID))
|
||||
}
|
||||
return exists(ctx, filter, NewOrgIDPConfigWriteModel(idp.ConfigID, authz.GetCtxData(ctx).ResourceOwner))
|
||||
}
|
||||
|
@@ -69,7 +69,9 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
|
||||
forceMFA,
|
||||
hidePasswordReset,
|
||||
ignoreUnknownUsernames,
|
||||
allowDomainDiscovery bool,
|
||||
allowDomainDiscovery,
|
||||
disableLoginWithEmail,
|
||||
disableLoginWithPhone bool,
|
||||
passwordlessType domain.PasswordlessType,
|
||||
defaultRedirectURI string,
|
||||
passwordCheckLifetime,
|
||||
@@ -122,6 +124,12 @@ func (wm *OrgLoginPolicyWriteModel) NewChangedEvent(
|
||||
if wm.DefaultRedirectURI != defaultRedirectURI {
|
||||
changes = append(changes, policy.ChangeDefaultRedirectURI(defaultRedirectURI))
|
||||
}
|
||||
if wm.DisableLoginWithEmail != disableLoginWithEmail {
|
||||
changes = append(changes, policy.ChangeDisableLoginWithEmail(disableLoginWithEmail))
|
||||
}
|
||||
if wm.DisableLoginWithPhone != disableLoginWithPhone {
|
||||
changes = append(changes, policy.ChangeDisableLoginWithPhone(disableLoginWithPhone))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
@@ -33,10 +33,10 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
policy *domain.LoginPolicy
|
||||
policy *AddLoginPolicy
|
||||
}
|
||||
type res struct {
|
||||
want *domain.LoginPolicy
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -45,25 +45,6 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "org id missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
policy: &domain.LoginPolicy{
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "loginpolicy already existing, already exists error",
|
||||
fields: fields{
|
||||
@@ -80,6 +61,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@@ -95,7 +78,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.LoginPolicy{
|
||||
policy: &AddLoginPolicy{
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
@@ -133,6 +116,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@@ -149,7 +134,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.LoginPolicy{
|
||||
policy: &AddLoginPolicy{
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
@@ -157,6 +142,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
DisableLoginWithEmail: true,
|
||||
DisableLoginWithPhone: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
DefaultRedirectURI: "https://example.com/redirect",
|
||||
PasswordCheckLifetime: time.Hour * 1,
|
||||
@@ -167,25 +154,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.LoginPolicy{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
DefaultRedirectURI: "https://example.com/redirect",
|
||||
PasswordCheckLifetime: time.Hour * 1,
|
||||
ExternalLoginCheckLifetime: time.Hour * 2,
|
||||
MFAInitSkipLifetime: time.Hour * 3,
|
||||
SecondFactorCheckLifetime: time.Hour * 4,
|
||||
MultiFactorCheckLifetime: time.Hour * 5,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -194,13 +164,12 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.LoginPolicy{
|
||||
policy: &AddLoginPolicy{
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
@@ -208,6 +177,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
DisableLoginWithEmail: true,
|
||||
DisableLoginWithPhone: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
DefaultRedirectURI: "https://example.com/redirect",
|
||||
PasswordCheckLifetime: time.Hour * 1,
|
||||
@@ -240,6 +211,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@@ -268,7 +241,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.LoginPolicy{
|
||||
policy: &AddLoginPolicy{
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
@@ -276,6 +249,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
DisableLoginWithEmail: true,
|
||||
DisableLoginWithPhone: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
DefaultRedirectURI: "https://example.com/redirect",
|
||||
PasswordCheckLifetime: time.Hour * 1,
|
||||
@@ -288,25 +263,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.LoginPolicy{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
DefaultRedirectURI: "https://example.com/redirect",
|
||||
PasswordCheckLifetime: time.Hour * 1,
|
||||
ExternalLoginCheckLifetime: time.Hour * 2,
|
||||
MFAInitSkipLifetime: time.Hour * 3,
|
||||
SecondFactorCheckLifetime: time.Hour * 4,
|
||||
MultiFactorCheckLifetime: time.Hour * 5,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -322,7 +280,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.LoginPolicy{
|
||||
policy: &AddLoginPolicy{
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
@@ -330,6 +288,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
DisableLoginWithEmail: true,
|
||||
DisableLoginWithPhone: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
DefaultRedirectURI: "https://example.com/redirect",
|
||||
PasswordCheckLifetime: time.Hour * 1,
|
||||
@@ -337,10 +297,10 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
MFAInitSkipLifetime: time.Hour * 3,
|
||||
SecondFactorCheckLifetime: time.Hour * 4,
|
||||
MultiFactorCheckLifetime: time.Hour * 5,
|
||||
IDPProviders: []*domain.IDPProvider{
|
||||
IDPProviders: []*AddLoginPolicyIDP{
|
||||
{
|
||||
Type: domain.IdentityProviderTypeSystem,
|
||||
IDPConfigID: "invalid",
|
||||
Type: domain.IdentityProviderTypeSystem,
|
||||
ConfigID: "invalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -379,6 +339,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@@ -402,7 +364,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.LoginPolicy{
|
||||
policy: &AddLoginPolicy{
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
@@ -410,6 +372,8 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
DisableLoginWithEmail: true,
|
||||
DisableLoginWithPhone: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
DefaultRedirectURI: "https://example.com/redirect",
|
||||
PasswordCheckLifetime: time.Hour * 1,
|
||||
@@ -417,34 +381,17 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
MFAInitSkipLifetime: time.Hour * 3,
|
||||
SecondFactorCheckLifetime: time.Hour * 4,
|
||||
MultiFactorCheckLifetime: time.Hour * 5,
|
||||
IDPProviders: []*domain.IDPProvider{
|
||||
IDPProviders: []*AddLoginPolicyIDP{
|
||||
{
|
||||
Type: domain.IdentityProviderTypeSystem,
|
||||
IDPConfigID: "config1",
|
||||
Type: domain.IdentityProviderTypeSystem,
|
||||
ConfigID: "config1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.LoginPolicy{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
DefaultRedirectURI: "https://example.com/redirect",
|
||||
PasswordCheckLifetime: time.Hour * 1,
|
||||
ExternalLoginCheckLifetime: time.Hour * 2,
|
||||
MFAInitSkipLifetime: time.Hour * 3,
|
||||
SecondFactorCheckLifetime: time.Hour * 4,
|
||||
MultiFactorCheckLifetime: time.Hour * 5,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -475,10 +422,10 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
policy *domain.LoginPolicy
|
||||
policy *ChangeLoginPolicy
|
||||
}
|
||||
type res struct {
|
||||
want *domain.LoginPolicy
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -487,30 +434,6 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "org id missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
policy: &domain.LoginPolicy{
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
DefaultRedirectURI: "https://example.com/redirect",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "loginpolicy not existing, not found error",
|
||||
fields: fields{
|
||||
@@ -522,13 +445,15 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.LoginPolicy{
|
||||
policy: &ChangeLoginPolicy{
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
DisableLoginWithEmail: true,
|
||||
DisableLoginWithPhone: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
DefaultRedirectURI: "https://example.com/redirect",
|
||||
},
|
||||
@@ -553,6 +478,8 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@@ -568,7 +495,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.LoginPolicy{
|
||||
policy: &ChangeLoginPolicy{
|
||||
AllowRegister: true,
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
@@ -576,6 +503,8 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
DisableLoginWithEmail: true,
|
||||
DisableLoginWithPhone: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
DefaultRedirectURI: "https://example.com/redirect",
|
||||
PasswordCheckLifetime: time.Hour * 1,
|
||||
@@ -605,6 +534,8 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@@ -627,6 +558,8 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
&duration10,
|
||||
@@ -643,13 +576,15 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
policy: &domain.LoginPolicy{
|
||||
policy: &ChangeLoginPolicy{
|
||||
AllowRegister: false,
|
||||
AllowUsernamePassword: false,
|
||||
AllowExternalIDP: false,
|
||||
ForceMFA: false,
|
||||
IgnoreUnknownUsernames: false,
|
||||
AllowDomainDiscovery: false,
|
||||
DisableLoginWithEmail: false,
|
||||
DisableLoginWithPhone: false,
|
||||
PasswordlessType: domain.PasswordlessTypeNotAllowed,
|
||||
DefaultRedirectURI: "",
|
||||
PasswordCheckLifetime: time.Hour * 10,
|
||||
@@ -660,25 +595,8 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.LoginPolicy{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
AllowRegister: false,
|
||||
AllowUsernamePassword: false,
|
||||
AllowExternalIDP: false,
|
||||
ForceMFA: false,
|
||||
HidePasswordReset: false,
|
||||
IgnoreUnknownUsernames: false,
|
||||
AllowDomainDiscovery: false,
|
||||
PasswordlessType: domain.PasswordlessTypeNotAllowed,
|
||||
DefaultRedirectURI: "",
|
||||
PasswordCheckLifetime: time.Hour * 10,
|
||||
ExternalLoginCheckLifetime: time.Hour * 20,
|
||||
MFAInitSkipLifetime: time.Hour * 30,
|
||||
SecondFactorCheckLifetime: time.Hour * 40,
|
||||
MultiFactorCheckLifetime: time.Hour * 50,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -766,6 +684,8 @@ func TestCommandSide_RemoveLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -907,6 +827,8 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -949,6 +871,8 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1011,6 +935,8 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1176,6 +1102,8 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1218,6 +1146,8 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1272,6 +1202,8 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1333,6 +1265,8 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1402,6 +1336,8 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -2020,7 +1956,8 @@ func TestCommandSide_RemoveMultiFactorLoginPolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassword, register, externalIDP, mfa, passwordReset, ignoreUnknownUsernames, allowDomainDiscovery bool,
|
||||
func newLoginPolicyChangedEvent(ctx context.Context, orgID string,
|
||||
usernamePassword, register, externalIDP, mfa, passwordReset, ignoreUnknownUsernames, allowDomainDiscovery, disableLoginWithEmail, disableLoginWithPhone bool,
|
||||
passwordlessType domain.PasswordlessType,
|
||||
redirectURI string,
|
||||
passwordLifetime, externalLoginLifetime, mfaInitSkipLifetime, secondFactorLifetime, multiFactorLifetime *time.Duration) *org.LoginPolicyChangedEvent {
|
||||
@@ -2034,6 +1971,8 @@ func newLoginPolicyChangedEvent(ctx context.Context, orgID string, usernamePassw
|
||||
policy.ChangeAllowDomainDiscovery(allowDomainDiscovery),
|
||||
policy.ChangePasswordlessType(passwordlessType),
|
||||
policy.ChangeDefaultRedirectURI(redirectURI),
|
||||
policy.ChangeDisableLoginWithEmail(disableLoginWithEmail),
|
||||
policy.ChangeDisableLoginWithPhone(disableLoginWithPhone),
|
||||
}
|
||||
if passwordLifetime != nil {
|
||||
changes = append(changes, policy.ChangePasswordCheckLifetime(*passwordLifetime))
|
||||
|
@@ -18,6 +18,8 @@ type LoginPolicyWriteModel struct {
|
||||
HidePasswordReset bool
|
||||
IgnoreUnknownUsernames bool
|
||||
AllowDomainDiscovery bool
|
||||
DisableLoginWithEmail bool
|
||||
DisableLoginWithPhone bool
|
||||
PasswordlessType domain.PasswordlessType
|
||||
DefaultRedirectURI string
|
||||
PasswordCheckLifetime time.Duration
|
||||
@@ -40,6 +42,8 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
|
||||
wm.HidePasswordReset = e.HidePasswordReset
|
||||
wm.IgnoreUnknownUsernames = e.IgnoreUnknownUsernames
|
||||
wm.AllowDomainDiscovery = e.AllowDomainDiscovery
|
||||
wm.DisableLoginWithEmail = e.DisableLoginWithEmail
|
||||
wm.DisableLoginWithPhone = e.DisableLoginWithPhone
|
||||
wm.DefaultRedirectURI = e.DefaultRedirectURI
|
||||
wm.PasswordCheckLifetime = e.PasswordCheckLifetime
|
||||
wm.ExternalLoginCheckLifetime = e.ExternalLoginCheckLifetime
|
||||
@@ -90,9 +94,19 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
|
||||
if e.MultiFactorCheckLifetime != nil {
|
||||
wm.MultiFactorCheckLifetime = *e.MultiFactorCheckLifetime
|
||||
}
|
||||
if e.DisableLoginWithEmail != nil {
|
||||
wm.DisableLoginWithEmail = *e.DisableLoginWithEmail
|
||||
}
|
||||
if e.DisableLoginWithPhone != nil {
|
||||
wm.DisableLoginWithPhone = *e.DisableLoginWithPhone
|
||||
}
|
||||
case *policy.LoginPolicyRemovedEvent:
|
||||
wm.State = domain.PolicyStateRemoved
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *LoginPolicyWriteModel) Exists() bool {
|
||||
return wm.State.Exists()
|
||||
}
|
||||
|
@@ -1158,6 +1158,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1196,6 +1198,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1235,6 +1239,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1290,6 +1296,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1379,6 +1387,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1475,6 +1485,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
|
@@ -1680,6 +1680,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1747,6 +1749,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1814,6 +1818,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -1898,6 +1904,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -2040,6 +2048,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -2150,6 +2160,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -2254,6 +2266,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@@ -2380,6 +2394,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
|
Reference in New Issue
Block a user