mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 23:37:23 +00:00
feat: allow to force MFA local only (#6234)
This PR adds an option to the LoginPolicy to "Force MFA for local users", so that users authenticated through an IDP must not configure (and verify) an MFA.
This commit is contained in:
parent
1c3a15ff57
commit
fed15574f6
@ -99,6 +99,28 @@
|
||||
{{ 'POLICY.DATA.FORCEMFA' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div *ngIf="loginData" class="login-policy-row">
|
||||
<mat-checkbox
|
||||
card-actions
|
||||
class="login-policy-toggle"
|
||||
color="primary"
|
||||
ngDefaultControl
|
||||
[(ngModel)]="loginData.forceMfaLocalOnly"
|
||||
[disabled]="
|
||||
([
|
||||
serviceType === PolicyComponentServiceType.ADMIN
|
||||
? 'iam.policy.write'
|
||||
: serviceType === PolicyComponentServiceType.MGMT
|
||||
? 'policy.write'
|
||||
: ''
|
||||
]
|
||||
| hasRole
|
||||
| async) === false
|
||||
"
|
||||
>
|
||||
{{ 'POLICY.DATA.FORCEMFALOCALONLY' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<cnsl-card class="max-card-width" *ngIf="loginData">
|
||||
<cnsl-factor-table
|
||||
[service]="service"
|
||||
|
@ -152,6 +152,7 @@ export class LoginPolicyComponent implements OnInit {
|
||||
mgmtreq.setAllowRegister(this.loginData.allowRegister);
|
||||
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
|
||||
mgmtreq.setForceMfa(this.loginData.forceMfa);
|
||||
mgmtreq.setForceMfaLocalOnly(this.loginData.forceMfaLocalOnly);
|
||||
mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
|
||||
mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset);
|
||||
mgmtreq.setMultiFactorsList(this.loginData.multiFactorsList);
|
||||
@ -185,6 +186,7 @@ export class LoginPolicyComponent implements OnInit {
|
||||
mgmtreq.setAllowRegister(this.loginData.allowRegister);
|
||||
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
|
||||
mgmtreq.setForceMfa(this.loginData.forceMfa);
|
||||
mgmtreq.setForceMfaLocalOnly(this.loginData.forceMfaLocalOnly);
|
||||
mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
|
||||
mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset);
|
||||
mgmtreq.setDisableLoginWithEmail(this.loginData.disableLoginWithEmail);
|
||||
@ -217,6 +219,7 @@ export class LoginPolicyComponent implements OnInit {
|
||||
adminreq.setAllowRegister(this.loginData.allowRegister);
|
||||
adminreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
|
||||
adminreq.setForceMfa(this.loginData.forceMfa);
|
||||
adminreq.setForceMfaLocalOnly(this.loginData.forceMfaLocalOnly);
|
||||
adminreq.setPasswordlessType(this.loginData.passwordlessType);
|
||||
adminreq.setHidePasswordReset(this.loginData.hidePasswordReset);
|
||||
adminreq.setDisableLoginWithEmail(this.loginData.disableLoginWithEmail);
|
||||
|
@ -1335,6 +1335,8 @@
|
||||
"ALLOWREGISTER_DESC": "Ако опцията е избрана, в входа се появява допълнителна стъпка за регистрация на потребител.",
|
||||
"FORCEMFA": "Сила MFA",
|
||||
"FORCEMFA_DESC": "Ако опцията е избрана, потребителите трябва да конфигурират втори фактор за влизане.",
|
||||
"FORCEMFALOCALONLY": "Принудително MFA за локални потребители",
|
||||
"FORCEMFALOCALONLY_DESC": "Ако е избрана опцията, локалните удостоверени потребители трябва да конфигурират втори фактор за влизане.",
|
||||
"HIDEPASSWORDRESET": "Скриване на нулиране на парола",
|
||||
"HIDEPASSWORDRESET_DESC": "Ако опцията е избрана, потребителят не може да нулира паролата си в процеса на влизане.",
|
||||
"HIDELOGINNAMESUFFIX": "Скриване на суфикса на името за влизане",
|
||||
|
@ -1339,8 +1339,10 @@
|
||||
"ALLOWUSERNAMEPASSWORD_DESC": "Der konventionelle Login mit Benutzername und Passwort wird erlaubt.",
|
||||
"ALLOWEXTERNALIDP_DESC": "Der Login wird für die darunter liegenden Identitätsanbieter erlaubt.",
|
||||
"ALLOWREGISTER_DESC": "Ist die Option gewählt, erscheint im Login ein zusätzlicher Schritt zum Registrieren eines Benutzers.",
|
||||
"FORCEMFA": "Mfa erzwingen",
|
||||
"FORCEMFA": "MFA erzwingen",
|
||||
"FORCEMFA_DESC": "Ist die Option gewählt, müssen Benutzer einen zweiten Faktor für den Login verwenden.",
|
||||
"FORCEMFALOCALONLY": "MFA für lokale Users erzwingen",
|
||||
"FORCEMFALOCALONLY_DESC": "Ist die Option gewählt, müssen lokal authentifizierte Benutzer einen zweiten Faktor für den Login verwenden.",
|
||||
"HIDEPASSWORDRESET": "Passwort vergessen ausblenden",
|
||||
"HIDEPASSWORDRESET_DESC": "Ist die Option gewählt, ist es nicht möglich im Login das Passwort zurück zusetzen via Passwort vergessen Link.",
|
||||
"HIDELOGINNAMESUFFIX": "Loginname Suffix ausblenden",
|
||||
|
@ -1342,6 +1342,8 @@
|
||||
"ALLOWREGISTER_DESC": "If the option is selected, an additional step for registering a user appears in the login.",
|
||||
"FORCEMFA": "Force MFA",
|
||||
"FORCEMFA_DESC": "If the option is selected, users have to configure a second factor for login.",
|
||||
"FORCEMFALOCALONLY": "Force MFA for local authenticated users",
|
||||
"FORCEMFALOCALONLY_DESC": "If the option is selected, local authenticated users have to configure a second factor for login.",
|
||||
"HIDEPASSWORDRESET": "Hide Password reset",
|
||||
"HIDEPASSWORDRESET_DESC": "If the option is selected, the user can't reset his password in the login process.",
|
||||
"HIDELOGINNAMESUFFIX": "Hide Loginname suffix",
|
||||
|
@ -1342,6 +1342,8 @@
|
||||
"ALLOWREGISTER_DESC": "Si esta opción es seleccionada, aparece un paso adicional durante el inicio de sesión para registrar un usuario.",
|
||||
"FORCEMFA": "Forzar MFA",
|
||||
"FORCEMFA_DESC": "Si esta opción es seleccionada, los usuarios tendrán que configurar un doble factor para iniciar sesión.",
|
||||
"FORCEMFALOCALONLY": "Forzar MFA para usuarios locales",
|
||||
"FORCEMFALOCALONLY_DESC": "Si esta opción es seleccionada, los usuarios autenticados localmente tendrán que configurar un doble factor para iniciar sesión",
|
||||
"HIDEPASSWORDRESET": "Ocultar restablecer contraseña",
|
||||
"HIDEPASSWORDRESET_DESC": "Si esta opción es seleccionada, el usuario no podrá restablecer su contraseña en el proceso de inicio de sesión.",
|
||||
"HIDELOGINNAMESUFFIX": "Ocultar sufijo del nombre de inicio de sesión",
|
||||
|
@ -1341,6 +1341,8 @@
|
||||
"ALLOWREGISTER_DESC": "Si l'option est sélectionnée, une étape supplémentaire pour l'enregistrement d'un utilisateur apparaît dans la connexion.",
|
||||
"FORCEMFA": "Forcer MFA",
|
||||
"FORCEMFA_DESC": "Si l'option est sélectionnée, les utilisateurs doivent configurer un deuxième facteur pour la connexion.",
|
||||
"FORCEMFALOCALONLY": "Forcer MFA pour les utilisateurs locaux",
|
||||
"FORCEMFALOCALONLY_DESC": "Si l'option est sélectionnée, les utilisateurs locaux authentifiés doivent configurer un deuxième facteur pour la connexion.",
|
||||
"HIDEPASSWORDRESET": "Masquer la réinitialisation du mot de passe",
|
||||
"HIDEPASSWORDRESET_DESC": "Si l'option est sélectionnée, l'utilisateur ne peut pas réinitialiser son mot de passe lors du processus de connexion.",
|
||||
"HIDELOGINNAMESUFFIX": "Masquer le suffixe du nom de connexion",
|
||||
|
@ -1341,6 +1341,8 @@
|
||||
"ALLOWREGISTER_DESC": "Se l'opzione \u00e8 selezionata, nel login apparirà un passo aggiuntivo per la registrazione di un utente.",
|
||||
"FORCEMFA": "Forza MFA",
|
||||
"FORCEMFA_DESC": "Se l'opzione \u00e8 selezionata, gli utenti devono configurare un secondo fattore per il login.",
|
||||
"FORCEMFALOCALONLY": "Forza MFA per gli utenti locali",
|
||||
"FORCEMFALOCALONLY_DESC": "Se l'opzione è selezionata, gli utenti locali autenticati devono configurare un secondo fattore per l'accesso.",
|
||||
"HIDEPASSWORDRESET": "Nascondi ripristino della password",
|
||||
"HIDEPASSWORDRESET_DESC": "Se l'opzione \u00e8 selezionata, l'utente non pu\u00f2 resettare la sua password nel interfaccia login.",
|
||||
"HIDELOGINNAMESUFFIX": "Nascondi il suffisso del nome utente",
|
||||
|
@ -1337,6 +1337,8 @@
|
||||
"ALLOWREGISTER_DESC": "このオプションが選択されている場合、ユーザーを登録するための追加のステップがログインに表示されます。",
|
||||
"FORCEMFA": "MFAを強制する",
|
||||
"FORCEMFA_DESC": "このオプションが選択されている場合、ユーザーはログイン用の二要素認証を構成する必要があります。",
|
||||
"FORCEMFALOCALONLY": "ローカル ユーザーに MFA を強制する",
|
||||
"FORCEMFALOCALONLY_DESC": "オプションが選択されている場合、ローカル認証されたユーザーはログインの 2 番目の要素を構成する必要があります。",
|
||||
"HIDEPASSWORDRESET": "パスワードリセットを非表示にする",
|
||||
"HIDEPASSWORDRESET_DESC": "このオプションが選択されている場合、ユーザーはログイン過程ででパスワードをリセットできません。",
|
||||
"HIDELOGINNAMESUFFIX": "ログイン名の接尾辞を非表示にする",
|
||||
|
@ -1342,6 +1342,8 @@
|
||||
"ALLOWREGISTER_DESC": "Доколку е избрана опцијата, се прикажува дополнителен чекор за регистрирање на корисник во најавата.",
|
||||
"FORCEMFA": "Задолжителна MFA",
|
||||
"FORCEMFA_DESC": "Доколку е избрана опцијата, корисниците мораат да конфигурираат втор фактор за најава.",
|
||||
"FORCEMFALOCALONLY": "Force MFA за локални корисници",
|
||||
"FORCEMFALOCALONLY_DESC": "Ако е избрана опцијата, локалните автентицирани корисници треба да конфигурираат втор фактор за најавување.",
|
||||
"HIDEPASSWORDRESET": "Сокриј го ресетирањето на лозинка",
|
||||
"HIDEPASSWORDRESET_DESC": "Доколку е избрана опцијата, корисникот нема да може да ја ресетира својата лозинка во процесот на најава.",
|
||||
"HIDELOGINNAMESUFFIX": "Сокриј го суфиксот на корисничкото име",
|
||||
|
@ -1341,6 +1341,8 @@
|
||||
"ALLOWREGISTER_DESC": "Jeśli ta opcja jest zaznaczona, pojawi się dodatkowy krok rejestracji użytkownika w procesie logowania.",
|
||||
"FORCEMFA": "Wymuś MFA",
|
||||
"FORCEMFA_DESC": "Jeśli ta opcja jest zaznaczona, użytkownicy muszą skonfigurować drugi czynnik logowania.",
|
||||
"FORCEMFALOCALONLY": "Wymuś MFA dla lokalnych użytkowników",
|
||||
"FORCEMFALOCALONLY_DESC": "Jeśli ta opcja jest zaznaczona, lokalni uwierzytelnieni użytkownicy muszą skonfigurować drugi czynnik logowania.",
|
||||
"HIDEPASSWORDRESET": "Ukryj reset hasła",
|
||||
"HIDEPASSWORDRESET_DESC": "Jeśli ta opcja jest zaznaczona, użytkownik nie może zresetować swojego hasła w procesie logowania.",
|
||||
"HIDELOGINNAMESUFFIX": "Ukryj sufiks nazwy użytkownika",
|
||||
|
@ -1340,6 +1340,8 @@
|
||||
"ALLOWREGISTER_DESC": "如果选择了该选项,登录中会出现一个用于注册用户的附加步骤。",
|
||||
"FORCEMFA": "强制使用 MFA",
|
||||
"FORCEMFA_DESC": "如果选择该选项,用户必须配置第二身份认证登录因素。",
|
||||
"FORCEMFALOCALONLY": "对本地用户强制执行 MFA",
|
||||
"FORCEMFALOCALONLY_DESC": "如果选择该选项,本地经过身份验证的用户必须配置第二个登录因素。",
|
||||
"HIDEPASSWORDRESET": "隐藏密码重置按钮",
|
||||
"HIDEPASSWORDRESET_DESC": "如果选择该选项,则用户无法在登录过程中重置其密码。",
|
||||
"HIDELOGINNAMESUFFIX": "隐藏登录名后缀",
|
||||
|
@ -125,6 +125,7 @@ Secondfactors (2FA):
|
||||
|
||||
Force a user to register and use a multifactor authentication, by checking the option "Force MFA".
|
||||
Ensure that you have added the MFA methods you want to allow.
|
||||
Or you can enable the "Force MFA for local authenticated users", which will enforce this rule only on local authentication, but not on users authenticated through an Identity Provider.
|
||||
|
||||
### Login Lifetimes
|
||||
|
||||
|
@ -430,6 +430,7 @@ func (s *Server) getLoginPolicy(ctx context.Context, orgID string, orgIDPs []str
|
||||
AllowRegister: queriedLogin.AllowRegister,
|
||||
AllowExternalIdp: queriedLogin.AllowExternalIDPs,
|
||||
ForceMfa: queriedLogin.ForceMFA,
|
||||
ForceMfaLocalOnly: queriedLogin.ForceMFALocalOnly,
|
||||
PasswordlessType: policy_pb.PasswordlessType(queriedLogin.PasswordlessType),
|
||||
HidePasswordReset: queriedLogin.HidePasswordReset,
|
||||
IgnoreUnknownUsernames: queriedLogin.IgnoreUnknownUsernames,
|
||||
|
@ -14,6 +14,7 @@ func updateLoginPolicyToCommand(p *admin_pb.UpdateLoginPolicyRequest) *command.C
|
||||
AllowRegister: p.AllowRegister,
|
||||
AllowExternalIDP: p.AllowExternalIdp,
|
||||
ForceMFA: p.ForceMfa,
|
||||
ForceMFALocalOnly: p.ForceMfaLocalOnly,
|
||||
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
|
||||
HidePasswordReset: p.HidePasswordReset,
|
||||
IgnoreUnknownUsernames: p.IgnoreUnknownUsernames,
|
||||
|
@ -15,6 +15,7 @@ func AddLoginPolicyToCommand(p *mgmt_pb.AddCustomLoginPolicyRequest) *command.Ad
|
||||
AllowRegister: p.AllowRegister,
|
||||
AllowExternalIDP: p.AllowExternalIdp,
|
||||
ForceMFA: p.ForceMfa,
|
||||
ForceMFALocalOnly: p.ForceMfaLocalOnly,
|
||||
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
|
||||
HidePasswordReset: p.HidePasswordReset,
|
||||
IgnoreUnknownUsernames: p.IgnoreUnknownUsernames,
|
||||
@ -49,6 +50,7 @@ func updateLoginPolicyToCommand(p *mgmt_pb.UpdateCustomLoginPolicyRequest) *comm
|
||||
AllowRegister: p.AllowRegister,
|
||||
AllowExternalIDP: p.AllowExternalIdp,
|
||||
ForceMFA: p.ForceMfa,
|
||||
ForceMFALocalOnly: p.ForceMfaLocalOnly,
|
||||
PasswordlessType: policy_grpc.PasswordlessTypeToDomain(p.PasswordlessType),
|
||||
HidePasswordReset: p.HidePasswordReset,
|
||||
IgnoreUnknownUsernames: p.IgnoreUnknownUsernames,
|
||||
|
@ -18,6 +18,7 @@ func ModelLoginPolicyToPb(policy *query.LoginPolicy) *policy_pb.LoginPolicy {
|
||||
AllowRegister: policy.AllowRegister,
|
||||
AllowExternalIdp: policy.AllowExternalIDPs,
|
||||
ForceMfa: policy.ForceMFA,
|
||||
ForceMfaLocalOnly: policy.ForceMFALocalOnly,
|
||||
PasswordlessType: ModelPasswordlessTypeToPb(policy.PasswordlessType),
|
||||
HidePasswordReset: policy.HidePasswordReset,
|
||||
IgnoreUnknownUsernames: policy.IgnoreUnknownUsernames,
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2alpha"
|
||||
)
|
||||
|
||||
// TODO: ?
|
||||
func loginSettingsToPb(current *query.LoginPolicy) *settings.LoginSettings {
|
||||
multi := make([]settings.MultiFactorType, len(current.MultiFactors))
|
||||
for i, typ := range current.MultiFactors {
|
||||
@ -24,6 +23,7 @@ func loginSettingsToPb(current *query.LoginPolicy) *settings.LoginSettings {
|
||||
AllowRegister: current.AllowRegister,
|
||||
AllowExternalIdp: current.AllowExternalIDPs,
|
||||
ForceMfa: current.ForceMFA,
|
||||
ForceMfaLocalOnly: current.ForceMFALocalOnly,
|
||||
PasskeysType: passkeysTypeToPb(current.PasswordlessType),
|
||||
HidePasswordReset: current.HidePasswordReset,
|
||||
IgnoreUnknownUsernames: current.IgnoreUnknownUsernames,
|
||||
|
@ -25,6 +25,7 @@ func Test_loginSettingsToPb(t *testing.T) {
|
||||
AllowRegister: true,
|
||||
AllowExternalIDPs: true,
|
||||
ForceMFA: true,
|
||||
ForceMFALocalOnly: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
@ -52,6 +53,7 @@ func Test_loginSettingsToPb(t *testing.T) {
|
||||
AllowRegister: true,
|
||||
AllowExternalIdp: true,
|
||||
ForceMfa: true,
|
||||
ForceMfaLocalOnly: true,
|
||||
PasskeysType: settings.PasskeysType_PASSKEYS_TYPE_ALLOWED,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
|
@ -842,6 +842,7 @@ func queryLoginPolicyToDomain(policy *query.LoginPolicy) *domain.LoginPolicy {
|
||||
AllowRegister: policy.AllowRegister,
|
||||
AllowExternalIDP: policy.AllowExternalIDPs,
|
||||
ForceMFA: policy.ForceMFA,
|
||||
ForceMFALocalOnly: policy.ForceMFALocalOnly,
|
||||
SecondFactors: policy.SecondFactors,
|
||||
MultiFactors: policy.MultiFactors,
|
||||
PasswordlessType: policy.PasswordlessType,
|
||||
@ -975,7 +976,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
|
||||
}
|
||||
}
|
||||
|
||||
step, ok, err := repo.mfaChecked(userSession, request, user)
|
||||
step, ok, err := repo.mfaChecked(userSession, request, user, isInternalLogin && len(request.LinkingUsers) == 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1094,9 +1095,9 @@ func (repo *AuthRequestRepo) firstFactorChecked(request *domain.AuthRequest, use
|
||||
return &domain.PasswordStep{}
|
||||
}
|
||||
|
||||
func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, request *domain.AuthRequest, user *user_model.UserView) (domain.NextStep, bool, error) {
|
||||
func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, request *domain.AuthRequest, user *user_model.UserView, isInternalAuthentication bool) (domain.NextStep, bool, error) {
|
||||
mfaLevel := request.MFALevel()
|
||||
allowedProviders, required := user.MFATypesAllowed(mfaLevel, request.LoginPolicy)
|
||||
allowedProviders, required := user.MFATypesAllowed(mfaLevel, request.LoginPolicy, isInternalAuthentication)
|
||||
promptRequired := (user.MFAMaxSetUp < mfaLevel) || (len(allowedProviders) == 0 && required)
|
||||
if promptRequired || !repo.mfaSkippedOrSetUp(user, request) {
|
||||
types := user.MFATypesSetupPossible(mfaLevel, request.LoginPolicy)
|
||||
|
@ -1439,6 +1439,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
|
||||
userSession *user_model.UserSessionView
|
||||
request *domain.AuthRequest
|
||||
user *user_model.UserView
|
||||
isInternal bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -1472,6 +1473,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
|
||||
MFAMaxSetUp: domain.MFALevelNotSetUp,
|
||||
},
|
||||
},
|
||||
isInternal: true,
|
||||
},
|
||||
nil,
|
||||
false,
|
||||
@ -1490,6 +1492,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
|
||||
MFAMaxSetUp: domain.MFALevelNotSetUp,
|
||||
},
|
||||
},
|
||||
isInternal: true,
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
@ -1509,6 +1512,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
|
||||
MFAMaxSetUp: domain.MFALevelNotSetUp,
|
||||
},
|
||||
},
|
||||
isInternal: true,
|
||||
},
|
||||
&domain.MFAPromptStep{
|
||||
MFAProviders: []domain.MFAType{
|
||||
@ -1533,6 +1537,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
|
||||
MFAMaxSetUp: domain.MFALevelNotSetUp,
|
||||
},
|
||||
},
|
||||
isInternal: true,
|
||||
},
|
||||
&domain.MFAPromptStep{
|
||||
Required: true,
|
||||
@ -1557,6 +1562,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
|
||||
MFAInitSkipped: testNow,
|
||||
},
|
||||
},
|
||||
isInternal: true,
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
@ -1578,6 +1584,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
|
||||
},
|
||||
},
|
||||
userSession: &user_model.UserSessionView{SecondFactorVerification: testNow.Add(-5 * time.Hour)},
|
||||
isInternal: true,
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
@ -1599,6 +1606,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
|
||||
},
|
||||
},
|
||||
userSession: &user_model.UserSessionView{},
|
||||
isInternal: true,
|
||||
},
|
||||
|
||||
&domain.MFAVerificationStep{
|
||||
@ -1607,11 +1615,107 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
|
||||
false,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"external not checked or forced but set up, want step",
|
||||
args{
|
||||
request: &domain.AuthRequest{
|
||||
LoginPolicy: &domain.LoginPolicy{
|
||||
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
|
||||
SecondFactorCheckLifetime: 18 * time.Hour,
|
||||
},
|
||||
},
|
||||
user: &user_model.UserView{
|
||||
HumanView: &user_model.HumanView{
|
||||
MFAMaxSetUp: domain.MFALevelSecondFactor,
|
||||
OTPState: user_model.MFAStateReady,
|
||||
},
|
||||
},
|
||||
userSession: &user_model.UserSessionView{},
|
||||
isInternal: false,
|
||||
},
|
||||
&domain.MFAVerificationStep{
|
||||
MFAProviders: []domain.MFAType{domain.MFATypeOTP},
|
||||
},
|
||||
false,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"external not forced but checked",
|
||||
args{
|
||||
request: &domain.AuthRequest{
|
||||
LoginPolicy: &domain.LoginPolicy{
|
||||
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
|
||||
SecondFactorCheckLifetime: 18 * time.Hour,
|
||||
},
|
||||
},
|
||||
user: &user_model.UserView{
|
||||
HumanView: &user_model.HumanView{
|
||||
MFAMaxSetUp: domain.MFALevelSecondFactor,
|
||||
OTPState: user_model.MFAStateReady,
|
||||
},
|
||||
},
|
||||
userSession: &user_model.UserSessionView{SecondFactorVerification: testNow.Add(-5 * time.Hour)},
|
||||
isInternal: false,
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"external not checked but required, want step",
|
||||
args{
|
||||
request: &domain.AuthRequest{
|
||||
LoginPolicy: &domain.LoginPolicy{
|
||||
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
|
||||
SecondFactorCheckLifetime: 18 * time.Hour,
|
||||
ForceMFA: true,
|
||||
},
|
||||
},
|
||||
user: &user_model.UserView{
|
||||
HumanView: &user_model.HumanView{
|
||||
MFAMaxSetUp: domain.MFALevelNotSetUp,
|
||||
},
|
||||
},
|
||||
userSession: &user_model.UserSessionView{},
|
||||
isInternal: false,
|
||||
},
|
||||
&domain.MFAPromptStep{
|
||||
Required: true,
|
||||
MFAProviders: []domain.MFAType{
|
||||
domain.MFATypeOTP,
|
||||
},
|
||||
},
|
||||
false,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"external not checked but local required",
|
||||
args{
|
||||
request: &domain.AuthRequest{
|
||||
LoginPolicy: &domain.LoginPolicy{
|
||||
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
|
||||
SecondFactorCheckLifetime: 18 * time.Hour,
|
||||
ForceMFA: true,
|
||||
ForceMFALocalOnly: true,
|
||||
},
|
||||
},
|
||||
user: &user_model.UserView{
|
||||
HumanView: &user_model.HumanView{
|
||||
MFAMaxSetUp: domain.MFALevelNotSetUp,
|
||||
},
|
||||
},
|
||||
userSession: &user_model.UserSessionView{},
|
||||
isInternal: false,
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := &AuthRequestRepo{}
|
||||
got, ok, err := repo.mfaChecked(tt.args.userSession, tt.args.request, tt.args.user)
|
||||
got, ok, err := repo.mfaChecked(tt.args.userSession, tt.args.request, tt.args.user, tt.args.isInternal)
|
||||
if (tt.errFunc != nil && !tt.errFunc(err)) || (err != nil && tt.errFunc == nil) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
return
|
||||
|
@ -166,16 +166,25 @@ func (repo *TokenVerifierRepo) checkAuthentication(ctx context.Context, authMeth
|
||||
if domain.HasMFA(authMethods) {
|
||||
return nil
|
||||
}
|
||||
availableAuthMethods, forceMFA, err := repo.Query.ListUserAuthMethodTypesRequired(setCallerCtx(ctx, userID), userID, false)
|
||||
availableAuthMethods, forceMFA, forceMFALocalOnly, err := repo.Query.ListUserAuthMethodTypesRequired(setCallerCtx(ctx, userID), userID, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if forceMFA || domain.HasMFA(availableAuthMethods) {
|
||||
if domain.RequiresMFA(forceMFA, forceMFALocalOnly, hasIDPAuthentication(authMethods)) || domain.HasMFA(availableAuthMethods) {
|
||||
return caos_errs.ThrowPermissionDenied(nil, "AUTHZ-Kl3p0", "mfa required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasIDPAuthentication(authMethods []domain.UserAuthMethodType) bool {
|
||||
for _, method := range authMethods {
|
||||
if method == domain.UserAuthMethodTypeIDP {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func authMethodsFromSession(session *query.Session) []domain.UserAuthMethodType {
|
||||
types := make([]domain.UserAuthMethodType, 0, domain.UserAuthMethodTypeIDP)
|
||||
if !session.PasswordFactor.PasswordCheckedAt.IsZero() {
|
||||
|
@ -70,6 +70,7 @@ type InstanceSetup struct {
|
||||
AllowRegister bool
|
||||
AllowExternalIDP bool
|
||||
ForceMFA bool
|
||||
ForceMFALocalOnly bool
|
||||
HidePasswordReset bool
|
||||
IgnoreUnknownUsername bool
|
||||
AllowDomainDiscovery bool
|
||||
@ -226,6 +227,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
|
||||
setup.LoginPolicy.AllowRegister,
|
||||
setup.LoginPolicy.AllowExternalIDP,
|
||||
setup.LoginPolicy.ForceMFA,
|
||||
setup.LoginPolicy.ForceMFALocalOnly,
|
||||
setup.LoginPolicy.HidePasswordReset,
|
||||
setup.LoginPolicy.IgnoreUnknownUsername,
|
||||
setup.LoginPolicy.AllowDomainDiscovery,
|
||||
|
@ -34,6 +34,7 @@ func writeModelToLoginPolicy(wm *LoginPolicyWriteModel) *domain.LoginPolicy {
|
||||
IgnoreUnknownUsernames: wm.IgnoreUnknownUsernames,
|
||||
AllowDomainDiscovery: wm.AllowDomainDiscovery,
|
||||
ForceMFA: wm.ForceMFA,
|
||||
ForceMFALocalOnly: wm.ForceMFALocalOnly,
|
||||
PasswordlessType: wm.PasswordlessType,
|
||||
DefaultRedirectURI: wm.DefaultRedirectURI,
|
||||
PasswordCheckLifetime: wm.PasswordCheckLifetime,
|
||||
@ -123,10 +124,10 @@ func writeModelToLockoutPolicy(wm *LockoutPolicyWriteModel) *domain.LockoutPolic
|
||||
|
||||
func writeModelToPrivacyPolicy(wm *PrivacyPolicyWriteModel) *domain.PrivacyPolicy {
|
||||
return &domain.PrivacyPolicy{
|
||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||
TOSLink: wm.TOSLink,
|
||||
PrivacyLink: wm.PrivacyLink,
|
||||
HelpLink: wm.HelpLink,
|
||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||
TOSLink: wm.TOSLink,
|
||||
PrivacyLink: wm.PrivacyLink,
|
||||
HelpLink: wm.HelpLink,
|
||||
SupportEmail: wm.SupportEmail,
|
||||
}
|
||||
}
|
||||
|
@ -234,6 +234,7 @@ func prepareChangeDefaultLoginPolicy(a *instance.Aggregate, policy *ChangeLoginP
|
||||
policy.AllowRegister,
|
||||
policy.AllowExternalIDP,
|
||||
policy.ForceMFA,
|
||||
policy.ForceMFALocalOnly,
|
||||
policy.HidePasswordReset,
|
||||
policy.IgnoreUnknownUsernames,
|
||||
policy.AllowDomainDiscovery,
|
||||
@ -260,6 +261,7 @@ func prepareAddDefaultLoginPolicy(
|
||||
allowRegister bool,
|
||||
allowExternalIDP bool,
|
||||
forceMFA bool,
|
||||
forceMFALocalOnly bool,
|
||||
hidePasswordReset bool,
|
||||
ignoreUnknownUsernames bool,
|
||||
allowDomainDiscovery bool,
|
||||
@ -293,6 +295,7 @@ func prepareAddDefaultLoginPolicy(
|
||||
allowRegister,
|
||||
allowExternalIDP,
|
||||
forceMFA,
|
||||
forceMFALocalOnly,
|
||||
hidePasswordReset,
|
||||
ignoreUnknownUsernames,
|
||||
allowDomainDiscovery,
|
||||
|
@ -65,6 +65,7 @@ func (wm *InstanceLoginPolicyWriteModel) NewChangedEvent(
|
||||
allowRegister,
|
||||
allowExternalIDP,
|
||||
forceMFA,
|
||||
forceMFALocalOnly,
|
||||
hidePasswordReset,
|
||||
ignoreUnknownUsernames,
|
||||
allowDomainDiscovery,
|
||||
@ -92,6 +93,9 @@ func (wm *InstanceLoginPolicyWriteModel) NewChangedEvent(
|
||||
if wm.ForceMFA != forceMFA {
|
||||
changes = append(changes, policy.ChangeForceMFA(forceMFA))
|
||||
}
|
||||
if wm.ForceMFALocalOnly != forceMFALocalOnly {
|
||||
changes = append(changes, policy.ChangeForceMFALocalOnly(forceMFALocalOnly))
|
||||
}
|
||||
if passwordlessType.Valid() && wm.PasswordlessType != passwordlessType {
|
||||
changes = append(changes, policy.ChangePasswordlessType(passwordlessType))
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@ -92,6 +93,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
ForceMFALocalOnly: true,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
@ -129,6 +131,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@ -153,6 +156,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*10,
|
||||
@ -172,6 +176,7 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
|
||||
AllowUsernamePassword: false,
|
||||
AllowExternalIDP: false,
|
||||
ForceMFA: false,
|
||||
ForceMFALocalOnly: false,
|
||||
HidePasswordReset: false,
|
||||
IgnoreUnknownUsernames: false,
|
||||
AllowDomainDiscovery: false,
|
||||
@ -281,6 +286,7 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -322,6 +328,7 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -383,6 +390,7 @@ func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -526,6 +534,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -567,6 +576,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -621,6 +631,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -680,6 +691,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -747,6 +759,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -1301,7 +1314,7 @@ func TestCommandSide_RemoveMultiFactorDefaultLoginPolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA,
|
||||
func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA, forceMFALocalOnly,
|
||||
hidePasswordReset, ignoreUnknownUsernames, allowDomainDiscovery, disableLoginWithEmail, disableLoginWithPhone bool,
|
||||
passwordlessType domain.PasswordlessType,
|
||||
redirectURI string,
|
||||
@ -1312,6 +1325,7 @@ func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allow
|
||||
policy.ChangeAllowRegister(allowRegister),
|
||||
policy.ChangeAllowExternalIDP(allowExternalIDP),
|
||||
policy.ChangeForceMFA(forceMFA),
|
||||
policy.ChangeForceMFALocalOnly(forceMFALocalOnly),
|
||||
policy.ChangeAllowUserNamePassword(allowUsernamePassword),
|
||||
policy.ChangeHidePasswordReset(hidePasswordReset),
|
||||
policy.ChangeIgnoreUnknownUsernames(ignoreUnknownUsernames),
|
||||
|
@ -21,6 +21,7 @@ type AddLoginPolicy struct {
|
||||
AllowExternalIDP bool
|
||||
IDPProviders []*AddLoginPolicyIDP
|
||||
ForceMFA bool
|
||||
ForceMFALocalOnly bool
|
||||
SecondFactors []domain.SecondFactorType
|
||||
MultiFactors []domain.MultiFactorType
|
||||
PasswordlessType domain.PasswordlessType
|
||||
@ -47,6 +48,7 @@ type ChangeLoginPolicy struct {
|
||||
AllowRegister bool
|
||||
AllowExternalIDP bool
|
||||
ForceMFA bool
|
||||
ForceMFALocalOnly bool
|
||||
PasswordlessType domain.PasswordlessType
|
||||
HidePasswordReset bool
|
||||
IgnoreUnknownUsernames bool
|
||||
@ -425,6 +427,7 @@ func prepareAddLoginPolicy(a *org.Aggregate, policy *AddLoginPolicy) preparation
|
||||
policy.AllowRegister,
|
||||
policy.AllowExternalIDP,
|
||||
policy.ForceMFA,
|
||||
policy.ForceMFALocalOnly,
|
||||
policy.HidePasswordReset,
|
||||
policy.IgnoreUnknownUsernames,
|
||||
policy.AllowDomainDiscovery,
|
||||
|
@ -61,6 +61,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
@ -83,6 +84,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
ForceMFALocalOnly: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
@ -118,6 +120,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@ -139,6 +142,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
ForceMFALocalOnly: true,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
@ -174,6 +178,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
ForceMFALocalOnly: true,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
@ -213,6 +218,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@ -246,6 +252,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
ForceMFALocalOnly: true,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
@ -285,6 +292,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
ForceMFALocalOnly: true,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
@ -341,6 +349,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@ -369,6 +378,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) {
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
ForceMFALocalOnly: true,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
@ -450,6 +460,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
ForceMFALocalOnly: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
DisableLoginWithEmail: true,
|
||||
@ -480,6 +491,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@ -500,6 +512,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDP: true,
|
||||
ForceMFA: true,
|
||||
ForceMFALocalOnly: true,
|
||||
HidePasswordReset: true,
|
||||
IgnoreUnknownUsernames: true,
|
||||
AllowDomainDiscovery: true,
|
||||
@ -536,6 +549,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"https://example.com/redirect",
|
||||
time.Hour*1,
|
||||
@ -581,6 +595,7 @@ func TestCommandSide_ChangeLoginPolicy(t *testing.T) {
|
||||
AllowUsernamePassword: false,
|
||||
AllowExternalIDP: false,
|
||||
ForceMFA: false,
|
||||
ForceMFALocalOnly: false,
|
||||
IgnoreUnknownUsernames: false,
|
||||
AllowDomainDiscovery: false,
|
||||
DisableLoginWithEmail: false,
|
||||
@ -686,6 +701,7 @@ func TestCommandSide_RemoveLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -829,6 +845,7 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -873,6 +890,7 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -937,6 +955,7 @@ func TestCommandSide_AddIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -1104,6 +1123,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -1148,6 +1168,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -1204,6 +1225,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -1267,6 +1289,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -1338,6 +1361,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
|
@ -15,6 +15,7 @@ type LoginPolicyWriteModel struct {
|
||||
AllowRegister bool
|
||||
AllowExternalIDP bool
|
||||
ForceMFA bool
|
||||
ForceMFALocalOnly bool
|
||||
HidePasswordReset bool
|
||||
IgnoreUnknownUsernames bool
|
||||
AllowDomainDiscovery bool
|
||||
@ -38,6 +39,7 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
|
||||
wm.AllowUserNamePassword = e.AllowUserNamePassword
|
||||
wm.AllowExternalIDP = e.AllowExternalIDP
|
||||
wm.ForceMFA = e.ForceMFA
|
||||
wm.ForceMFALocalOnly = e.ForceMFALocalOnly
|
||||
wm.PasswordlessType = e.PasswordlessType
|
||||
wm.HidePasswordReset = e.HidePasswordReset
|
||||
wm.IgnoreUnknownUsernames = e.IgnoreUnknownUsernames
|
||||
@ -64,6 +66,9 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
|
||||
if e.ForceMFA != nil {
|
||||
wm.ForceMFA = *e.ForceMFA
|
||||
}
|
||||
if e.ForceMFALocalOnly != nil {
|
||||
wm.ForceMFALocalOnly = *e.ForceMFALocalOnly
|
||||
}
|
||||
if e.HidePasswordReset != nil {
|
||||
wm.HidePasswordReset = *e.HidePasswordReset
|
||||
}
|
||||
|
@ -1182,6 +1182,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -1222,6 +1223,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -1263,6 +1265,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -1320,6 +1323,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -1406,6 +1410,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -1499,6 +1504,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -1582,6 +1588,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -1671,6 +1678,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
|
@ -2246,6 +2246,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -2315,6 +2316,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -2384,6 +2386,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -2470,6 +2473,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -2614,6 +2618,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -2726,6 +2731,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -2838,6 +2844,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -2944,6 +2951,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -3072,6 +3080,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
@ -3195,6 +3204,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeNotAllowed,
|
||||
"",
|
||||
time.Hour*1,
|
||||
|
@ -16,6 +16,7 @@ type LoginPolicy struct {
|
||||
AllowExternalIDP bool
|
||||
IDPProviders []*IDPProvider
|
||||
ForceMFA bool
|
||||
ForceMFALocalOnly bool
|
||||
SecondFactors []SecondFactorType
|
||||
MultiFactors []MultiFactorType
|
||||
PasswordlessType PasswordlessType
|
||||
|
@ -85,6 +85,16 @@ func HasMFA(methods []UserAuthMethodType) bool {
|
||||
return factors > 1
|
||||
}
|
||||
|
||||
// RequiresMFA checks whether the user requires to authenticate with multiple auth factors based on the LoginPolicy and the authentication type.
|
||||
// Internal authentication will require MFA if either option is activated.
|
||||
// External authentication will only require MFA if it's forced generally and not local only.
|
||||
func RequiresMFA(forceMFA, forceMFALocalOnly, isInternalLogin bool) bool {
|
||||
if isInternalLogin {
|
||||
return forceMFA || forceMFALocalOnly
|
||||
}
|
||||
return forceMFA && !forceMFALocalOnly
|
||||
}
|
||||
|
||||
type PersonalAccessTokenState int32
|
||||
|
||||
const (
|
||||
|
@ -22,7 +22,7 @@ var (
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.idp_login_policy_links5` +
|
||||
` LEFT JOIN projections.idp_templates5 ON projections.idp_login_policy_links5.idp_id = projections.idp_templates5.id AND projections.idp_login_policy_links5.instance_id = projections.idp_templates5.instance_id` +
|
||||
` RIGHT JOIN (SELECT login_policy_owner.aggregate_id, login_policy_owner.instance_id, login_policy_owner.owner_removed FROM projections.login_policies4 AS login_policy_owner` +
|
||||
` RIGHT JOIN (SELECT login_policy_owner.aggregate_id, login_policy_owner.instance_id, login_policy_owner.owner_removed FROM projections.login_policies5 AS login_policy_owner` +
|
||||
` WHERE (login_policy_owner.instance_id = $1 AND (login_policy_owner.aggregate_id = $2 OR login_policy_owner.aggregate_id = $3)) ORDER BY login_policy_owner.is_default LIMIT 1) AS login_policy_owner` +
|
||||
` ON login_policy_owner.aggregate_id = projections.idp_login_policy_links5.resource_owner AND login_policy_owner.instance_id = projections.idp_login_policy_links5.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`)
|
||||
|
@ -26,6 +26,7 @@ type LoginPolicy struct {
|
||||
AllowUsernamePassword bool
|
||||
AllowExternalIDPs bool
|
||||
ForceMFA bool
|
||||
ForceMFALocalOnly bool
|
||||
SecondFactors database.EnumArray[domain.SecondFactorType]
|
||||
MultiFactors database.EnumArray[domain.MultiFactorType]
|
||||
PasswordlessType domain.PasswordlessType
|
||||
@ -95,6 +96,10 @@ var (
|
||||
name: projection.LoginPolicyForceMFACol,
|
||||
table: loginPolicyTable,
|
||||
}
|
||||
LoginPolicyColumnForceMFALocalOnly = Column{
|
||||
name: projection.LoginPolicyForceMFALocalOnlyCol,
|
||||
table: loginPolicyTable,
|
||||
}
|
||||
LoginPolicyColumnSecondFactors = Column{
|
||||
name: projection.LoginPolicy2FAsCol,
|
||||
table: loginPolicyTable,
|
||||
@ -351,6 +356,7 @@ func prepareLoginPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Select
|
||||
LoginPolicyColumnAllowUsernamePassword.identifier(),
|
||||
LoginPolicyColumnAllowExternalIDPs.identifier(),
|
||||
LoginPolicyColumnForceMFA.identifier(),
|
||||
LoginPolicyColumnForceMFALocalOnly.identifier(),
|
||||
LoginPolicyColumnSecondFactors.identifier(),
|
||||
LoginPolicyColumnMultiFactors.identifier(),
|
||||
LoginPolicyColumnPasswordlessType.identifier(),
|
||||
@ -381,6 +387,7 @@ func prepareLoginPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Select
|
||||
&p.AllowUsernamePassword,
|
||||
&p.AllowExternalIDPs,
|
||||
&p.ForceMFA,
|
||||
&p.ForceMFALocalOnly,
|
||||
&p.SecondFactors,
|
||||
&p.MultiFactors,
|
||||
&p.PasswordlessType,
|
||||
|
@ -15,30 +15,31 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
loginPolicyQuery = `SELECT projections.login_policies4.aggregate_id,` +
|
||||
` projections.login_policies4.creation_date,` +
|
||||
` projections.login_policies4.change_date,` +
|
||||
` projections.login_policies4.sequence,` +
|
||||
` projections.login_policies4.allow_register,` +
|
||||
` projections.login_policies4.allow_username_password,` +
|
||||
` projections.login_policies4.allow_external_idps,` +
|
||||
` projections.login_policies4.force_mfa,` +
|
||||
` projections.login_policies4.second_factors,` +
|
||||
` projections.login_policies4.multi_factors,` +
|
||||
` projections.login_policies4.passwordless_type,` +
|
||||
` projections.login_policies4.is_default,` +
|
||||
` projections.login_policies4.hide_password_reset,` +
|
||||
` projections.login_policies4.ignore_unknown_usernames,` +
|
||||
` projections.login_policies4.allow_domain_discovery,` +
|
||||
` projections.login_policies4.disable_login_with_email,` +
|
||||
` projections.login_policies4.disable_login_with_phone,` +
|
||||
` projections.login_policies4.default_redirect_uri,` +
|
||||
` projections.login_policies4.password_check_lifetime,` +
|
||||
` projections.login_policies4.external_login_check_lifetime,` +
|
||||
` projections.login_policies4.mfa_init_skip_lifetime,` +
|
||||
` projections.login_policies4.second_factor_check_lifetime,` +
|
||||
` projections.login_policies4.multi_factor_check_lifetime` +
|
||||
` FROM projections.login_policies4` +
|
||||
loginPolicyQuery = `SELECT projections.login_policies5.aggregate_id,` +
|
||||
` projections.login_policies5.creation_date,` +
|
||||
` projections.login_policies5.change_date,` +
|
||||
` projections.login_policies5.sequence,` +
|
||||
` projections.login_policies5.allow_register,` +
|
||||
` projections.login_policies5.allow_username_password,` +
|
||||
` projections.login_policies5.allow_external_idps,` +
|
||||
` projections.login_policies5.force_mfa,` +
|
||||
` projections.login_policies5.force_mfa_local_only,` +
|
||||
` projections.login_policies5.second_factors,` +
|
||||
` projections.login_policies5.multi_factors,` +
|
||||
` projections.login_policies5.passwordless_type,` +
|
||||
` projections.login_policies5.is_default,` +
|
||||
` projections.login_policies5.hide_password_reset,` +
|
||||
` projections.login_policies5.ignore_unknown_usernames,` +
|
||||
` projections.login_policies5.allow_domain_discovery,` +
|
||||
` projections.login_policies5.disable_login_with_email,` +
|
||||
` projections.login_policies5.disable_login_with_phone,` +
|
||||
` projections.login_policies5.default_redirect_uri,` +
|
||||
` projections.login_policies5.password_check_lifetime,` +
|
||||
` projections.login_policies5.external_login_check_lifetime,` +
|
||||
` projections.login_policies5.mfa_init_skip_lifetime,` +
|
||||
` projections.login_policies5.second_factor_check_lifetime,` +
|
||||
` projections.login_policies5.multi_factor_check_lifetime` +
|
||||
` FROM projections.login_policies5` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
loginPolicyCols = []string{
|
||||
"aggregate_id",
|
||||
@ -49,6 +50,7 @@ var (
|
||||
"allow_username_password",
|
||||
"allow_external_idps",
|
||||
"force_mfa",
|
||||
"force_mfa_local_only",
|
||||
"second_factors",
|
||||
"multi_factors",
|
||||
"passwordless_type",
|
||||
@ -66,15 +68,15 @@ var (
|
||||
"multi_factor_check_lifetime",
|
||||
}
|
||||
|
||||
prepareLoginPolicy2FAsStmt = `SELECT projections.login_policies4.second_factors` +
|
||||
` FROM projections.login_policies4` +
|
||||
prepareLoginPolicy2FAsStmt = `SELECT projections.login_policies5.second_factors` +
|
||||
` FROM projections.login_policies5` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
prepareLoginPolicy2FAsCols = []string{
|
||||
"second_factors",
|
||||
}
|
||||
|
||||
prepareLoginPolicyMFAsStmt = `SELECT projections.login_policies4.multi_factors` +
|
||||
` FROM projections.login_policies4` +
|
||||
prepareLoginPolicyMFAsStmt = `SELECT projections.login_policies5.multi_factors` +
|
||||
` FROM projections.login_policies5` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
prepareLoginPolicyMFAsCols = []string{
|
||||
"multi_factors",
|
||||
@ -126,6 +128,7 @@ func Test_LoginPolicyPrepares(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
database.EnumArray[domain.SecondFactorType]{domain.SecondFactorTypeOTP},
|
||||
database.EnumArray[domain.MultiFactorType]{domain.MultiFactorTypeU2FWithPIN},
|
||||
domain.PasswordlessTypeAllowed,
|
||||
@ -153,6 +156,7 @@ func Test_LoginPolicyPrepares(t *testing.T) {
|
||||
AllowUsernamePassword: true,
|
||||
AllowExternalIDPs: true,
|
||||
ForceMFA: true,
|
||||
ForceMFALocalOnly: true,
|
||||
SecondFactors: database.EnumArray[domain.SecondFactorType]{domain.SecondFactorTypeOTP},
|
||||
MultiFactors: database.EnumArray[domain.MultiFactorType]{domain.MultiFactorTypeU2FWithPIN},
|
||||
PasswordlessType: domain.PasswordlessTypeAllowed,
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
LoginPolicyTable = "projections.login_policies4"
|
||||
LoginPolicyTable = "projections.login_policies5"
|
||||
|
||||
LoginPolicyIDCol = "aggregate_id"
|
||||
LoginPolicyInstanceIDCol = "instance_id"
|
||||
@ -25,6 +25,7 @@ const (
|
||||
LoginPolicyAllowUsernamePasswordCol = "allow_username_password"
|
||||
LoginPolicyAllowExternalIDPsCol = "allow_external_idps"
|
||||
LoginPolicyForceMFACol = "force_mfa"
|
||||
LoginPolicyForceMFALocalOnlyCol = "force_mfa_local_only"
|
||||
LoginPolicy2FAsCol = "second_factors"
|
||||
LoginPolicyMFAsCol = "multi_factors"
|
||||
LoginPolicyPasswordlessTypeCol = "passwordless_type"
|
||||
@ -62,6 +63,7 @@ func newLoginPolicyProjection(ctx context.Context, config crdb.StatementHandlerC
|
||||
crdb.NewColumn(LoginPolicyAllowUsernamePasswordCol, crdb.ColumnTypeBool),
|
||||
crdb.NewColumn(LoginPolicyAllowExternalIDPsCol, crdb.ColumnTypeBool),
|
||||
crdb.NewColumn(LoginPolicyForceMFACol, crdb.ColumnTypeBool),
|
||||
crdb.NewColumn(LoginPolicyForceMFALocalOnlyCol, crdb.ColumnTypeBool, crdb.Default(false)),
|
||||
crdb.NewColumn(LoginPolicy2FAsCol, crdb.ColumnTypeEnumArray, crdb.Nullable()),
|
||||
crdb.NewColumn(LoginPolicyMFAsCol, crdb.ColumnTypeEnumArray, crdb.Nullable()),
|
||||
crdb.NewColumn(LoginPolicyPasswordlessTypeCol, crdb.ColumnTypeEnum),
|
||||
@ -185,6 +187,7 @@ func (p *loginPolicyProjection) reduceLoginPolicyAdded(event eventstore.Event) (
|
||||
handler.NewCol(LoginPolicyAllowUsernamePasswordCol, policyEvent.AllowUserNamePassword),
|
||||
handler.NewCol(LoginPolicyAllowExternalIDPsCol, policyEvent.AllowExternalIDP),
|
||||
handler.NewCol(LoginPolicyForceMFACol, policyEvent.ForceMFA),
|
||||
handler.NewCol(LoginPolicyForceMFALocalOnlyCol, policyEvent.ForceMFALocalOnly),
|
||||
handler.NewCol(LoginPolicyPasswordlessTypeCol, policyEvent.PasswordlessType),
|
||||
handler.NewCol(LoginPolicyIsDefaultCol, isDefault),
|
||||
handler.NewCol(LoginPolicyHidePWResetCol, policyEvent.HidePasswordReset),
|
||||
@ -228,6 +231,9 @@ func (p *loginPolicyProjection) reduceLoginPolicyChanged(event eventstore.Event)
|
||||
if policyEvent.ForceMFA != nil {
|
||||
cols = append(cols, handler.NewCol(LoginPolicyForceMFACol, *policyEvent.ForceMFA))
|
||||
}
|
||||
if policyEvent.ForceMFALocalOnly != nil {
|
||||
cols = append(cols, handler.NewCol(LoginPolicyForceMFALocalOnlyCol, *policyEvent.ForceMFALocalOnly))
|
||||
}
|
||||
if policyEvent.PasswordlessType != nil {
|
||||
cols = append(cols, handler.NewCol(LoginPolicyPasswordlessTypeCol, *policyEvent.PasswordlessType))
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
want wantReduce
|
||||
}{
|
||||
{
|
||||
name: "org reduceLoginPolicyAdded",
|
||||
name: "org reduceLoginPolicyAdded without forceMFALocalOnly",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(org.LoginPolicyAddedEventType),
|
||||
@ -57,7 +57,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.login_policies4 (aggregate_id, instance_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, disable_login_with_email, disable_login_with_phone, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)",
|
||||
expectedStmt: "INSERT INTO projections.login_policies5 (aggregate_id, instance_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, force_mfa_local_only, passwordless_type, is_default, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, disable_login_with_email, disable_login_with_phone, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -68,6 +68,73 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
"https://example.com/redirect",
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
time.Millisecond * 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org reduceLoginPolicyAdded",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(org.LoginPolicyAddedEventType),
|
||||
org.AggregateType,
|
||||
[]byte(`{
|
||||
"allowUsernamePassword": true,
|
||||
"allowRegister": true,
|
||||
"allowExternalIdp": true,
|
||||
"forceMFA": true,
|
||||
"forceMFALocalOnly": true,
|
||||
"hidePasswordReset": true,
|
||||
"ignoreUnknownUsernames": true,
|
||||
"allowDomainDiscovery": true,
|
||||
"disableLoginWithEmail": true,
|
||||
"disableLoginWithPhone": true,
|
||||
"passwordlessType": 1,
|
||||
"defaultRedirectURI": "https://example.com/redirect",
|
||||
"passwordCheckLifetime": 10000000,
|
||||
"externalLoginCheckLifetime": 10000000,
|
||||
"mfaInitSkipLifetime": 10000000,
|
||||
"secondFactorCheckLifetime": 10000000,
|
||||
"multiFactorCheckLifetime": 10000000
|
||||
}`),
|
||||
), org.LoginPolicyAddedEventMapper),
|
||||
},
|
||||
reduce: (&loginPolicyProjection{}).reduceLoginPolicyAdded,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("org"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.login_policies5 (aggregate_id, instance_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, force_mfa_local_only, passwordless_type, is_default, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, disable_login_with_email, disable_login_with_phone, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
false,
|
||||
true,
|
||||
@ -99,6 +166,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
"allowRegister": true,
|
||||
"allowExternalIdp": true,
|
||||
"forceMFA": true,
|
||||
"forceMFALocalOnly": true,
|
||||
"hidePasswordReset": true,
|
||||
"ignoreUnknownUsernames": true,
|
||||
"allowDomainDiscovery": true,
|
||||
@ -121,7 +189,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.login_policies4 SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, disable_login_with_email, disable_login_with_phone, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) WHERE (aggregate_id = $19) AND (instance_id = $20)",
|
||||
expectedStmt: "UPDATE projections.login_policies5 SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, force_mfa_local_only, passwordless_type, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, disable_login_with_email, disable_login_with_phone, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) WHERE (aggregate_id = $20) AND (instance_id = $21)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -129,6 +197,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
true,
|
||||
true,
|
||||
@ -168,7 +237,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.login_policies4 SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.login_policies5 SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -200,7 +269,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.login_policies4 SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.login_policies5 SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -230,7 +299,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.login_policies4 WHERE (aggregate_id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "DELETE FROM projections.login_policies5 WHERE (aggregate_id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -259,7 +328,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.login_policies4 SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.login_policies5 SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -291,7 +360,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.login_policies4 SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.login_policies5 SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -314,8 +383,9 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
[]byte(`{
|
||||
"allowUsernamePassword": true,
|
||||
"allowRegister": true,
|
||||
"allowExternalIdp": false,
|
||||
"forceMFA": false,
|
||||
"allowExternalIdp": true,
|
||||
"forceMFA": true,
|
||||
"forceMFALocalOnly": true,
|
||||
"hidePasswordReset": true,
|
||||
"ignoreUnknownUsernames": true,
|
||||
"allowDomainDiscovery": true,
|
||||
@ -338,7 +408,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.login_policies4 (aggregate_id, instance_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, disable_login_with_email, disable_login_with_phone, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)",
|
||||
expectedStmt: "INSERT INTO projections.login_policies5 (aggregate_id, instance_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, force_mfa_local_only, passwordless_type, is_default, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, disable_login_with_email, disable_login_with_phone, default_redirect_uri, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -347,8 +417,9 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
uint64(15),
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
true,
|
||||
true,
|
||||
@ -380,6 +451,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
"allowRegister": true,
|
||||
"allowExternalIdp": true,
|
||||
"forceMFA": true,
|
||||
"forceMFALocalOnly": true,
|
||||
"hidePasswordReset": true,
|
||||
"ignoreUnknownUsernames": true,
|
||||
"allowDomainDiscovery": true,
|
||||
@ -397,7 +469,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.login_policies4 SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, disable_login_with_email, disable_login_with_phone, default_redirect_uri) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) WHERE (aggregate_id = $14) AND (instance_id = $15)",
|
||||
expectedStmt: "UPDATE projections.login_policies5 SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, force_mfa_local_only, passwordless_type, hide_password_reset, ignore_unknown_usernames, allow_domain_discovery, disable_login_with_email, disable_login_with_phone, default_redirect_uri) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) WHERE (aggregate_id = $15) AND (instance_id = $16)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -405,6 +477,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.PasswordlessTypeAllowed,
|
||||
true,
|
||||
true,
|
||||
@ -439,7 +512,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.login_policies4 SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.login_policies5 SET (change_date, sequence, multi_factors) = ($1, $2, array_append(multi_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -471,7 +544,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.login_policies4 SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.login_policies5 SET (change_date, sequence, multi_factors) = ($1, $2, array_remove(multi_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -503,7 +576,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.login_policies4 SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.login_policies5 SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -535,7 +608,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.login_policies4 SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.login_policies5 SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -565,7 +638,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.login_policies4 SET (change_date, sequence, owner_removed) = ($1, $2, $3) WHERE (instance_id = $4) AND (aggregate_id = $5)",
|
||||
expectedStmt: "UPDATE projections.login_policies5 SET (change_date, sequence, owner_removed) = ($1, $2, $3) WHERE (instance_id = $4) AND (aggregate_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -595,7 +668,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.login_policies4 WHERE (instance_id = $1)",
|
||||
expectedStmt: "DELETE FROM projections.login_policies5 WHERE (instance_id = $1)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
},
|
||||
|
@ -80,11 +80,12 @@ var (
|
||||
table: userIDPsCountTable,
|
||||
}
|
||||
|
||||
forceMFATable = loginPolicyTable.setAlias("auth_methods_force_mfa")
|
||||
forceMFAInstanceID = LoginPolicyColumnInstanceID.setTable(forceMFATable)
|
||||
forceMFAOrgID = LoginPolicyColumnOrgID.setTable(forceMFATable)
|
||||
forceMFAIsDefault = LoginPolicyColumnIsDefault.setTable(forceMFATable)
|
||||
forceMFAForce = LoginPolicyColumnForceMFA.setTable(forceMFATable)
|
||||
forceMFATable = loginPolicyTable.setAlias("auth_methods_force_mfa")
|
||||
forceMFAInstanceID = LoginPolicyColumnInstanceID.setTable(forceMFATable)
|
||||
forceMFAOrgID = LoginPolicyColumnOrgID.setTable(forceMFATable)
|
||||
forceMFAIsDefault = LoginPolicyColumnIsDefault.setTable(forceMFATable)
|
||||
forceMFAForce = LoginPolicyColumnForceMFA.setTable(forceMFATable)
|
||||
forceMFAForceLocalOnly = LoginPolicyColumnForceMFALocalOnly.setTable(forceMFATable)
|
||||
)
|
||||
|
||||
type AuthMethods struct {
|
||||
@ -176,11 +177,11 @@ func (q *Queries) ListActiveUserAuthMethodTypes(ctx context.Context, userID stri
|
||||
return userAuthMethodTypes, err
|
||||
}
|
||||
|
||||
func (q *Queries) ListUserAuthMethodTypesRequired(ctx context.Context, userID string, withOwnerRemoved bool) (userAuthMethodTypes []domain.UserAuthMethodType, forceMFA bool, err error) {
|
||||
func (q *Queries) ListUserAuthMethodTypesRequired(ctx context.Context, userID string, withOwnerRemoved bool) (userAuthMethodTypes []domain.UserAuthMethodType, forceMFA, forceMFALocalOnly bool, err error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
if ctxData.UserID != userID {
|
||||
if err := q.checkPermission(ctx, domain.PermissionUserRead, ctxData.OrgID, userID); err != nil {
|
||||
return nil, false, err
|
||||
return nil, false, false, err
|
||||
}
|
||||
}
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
@ -196,12 +197,12 @@ func (q *Queries) ListUserAuthMethodTypesRequired(ctx context.Context, userID st
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, false, errors.ThrowInvalidArgument(err, "QUERY-E5ut4", "Errors.Query.InvalidRequest")
|
||||
return nil, false, false, errors.ThrowInvalidArgument(err, "QUERY-E5ut4", "Errors.Query.InvalidRequest")
|
||||
}
|
||||
|
||||
rows, err := q.client.QueryContext(ctx, stmt, args...)
|
||||
if err != nil || rows.Err() != nil {
|
||||
return nil, false, errors.ThrowInternal(err, "QUERY-Dun75", "Errors.Internal")
|
||||
return nil, false, false, errors.ThrowInternal(err, "QUERY-Dun75", "Errors.Internal")
|
||||
}
|
||||
return scan(rows)
|
||||
}
|
||||
@ -408,7 +409,7 @@ func prepareActiveUserAuthMethodTypesQuery(ctx context.Context, db prepareDataba
|
||||
}
|
||||
}
|
||||
|
||||
func prepareUserAuthMethodTypesRequiredQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) ([]domain.UserAuthMethodType, bool, error)) {
|
||||
func prepareUserAuthMethodTypesRequiredQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (_ []domain.UserAuthMethodType, forceMFA, forceMFALocalOnly bool, err error)) {
|
||||
loginPolicyQuery, err := prepareAuthMethodsForceMFAQuery()
|
||||
if err != nil {
|
||||
return sq.SelectBuilder{}, nil
|
||||
@ -425,7 +426,8 @@ func prepareUserAuthMethodTypesRequiredQuery(ctx context.Context, db prepareData
|
||||
NotifyPasswordSetCol.identifier(),
|
||||
authMethodTypeTypes.identifier(),
|
||||
userIDPsCountCount.identifier(),
|
||||
forceMFAForce.identifier()).
|
||||
forceMFAForce.identifier(),
|
||||
forceMFAForceLocalOnly.identifier()).
|
||||
From(userTable.identifier()).
|
||||
LeftJoin(join(NotifyUserIDCol, UserIDCol)).
|
||||
LeftJoin("("+authMethodsQuery+") AS "+authMethodTypeTable.alias+" ON "+
|
||||
@ -439,11 +441,12 @@ func prepareUserAuthMethodTypesRequiredQuery(ctx context.Context, db prepareData
|
||||
"(" + forceMFAOrgID.identifier() + " = " + UserInstanceIDCol.identifier() + " OR " + forceMFAOrgID.identifier() + " = " + UserResourceOwnerCol.identifier() + ") AND " +
|
||||
forceMFAInstanceID.identifier() + " = " + UserInstanceIDCol.identifier() + db.Timetravel(call.Took(ctx))).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(rows *sql.Rows) ([]domain.UserAuthMethodType, bool, error) {
|
||||
func(rows *sql.Rows) ([]domain.UserAuthMethodType, bool, bool, error) {
|
||||
userAuthMethodTypes := make([]domain.UserAuthMethodType, 0)
|
||||
var passwordSet sql.NullBool
|
||||
var idp sql.NullInt64
|
||||
var forceMFA sql.NullBool
|
||||
var forceMFALocalOnly sql.NullBool
|
||||
for rows.Next() {
|
||||
var authMethodType sql.NullInt16
|
||||
err := rows.Scan(
|
||||
@ -451,9 +454,10 @@ func prepareUserAuthMethodTypesRequiredQuery(ctx context.Context, db prepareData
|
||||
&authMethodType,
|
||||
&idp,
|
||||
&forceMFA,
|
||||
&forceMFALocalOnly,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
return nil, false, false, err
|
||||
}
|
||||
if authMethodType.Valid {
|
||||
userAuthMethodTypes = append(userAuthMethodTypes, domain.UserAuthMethodType(authMethodType.Int16))
|
||||
@ -468,10 +472,10 @@ func prepareUserAuthMethodTypesRequiredQuery(ctx context.Context, db prepareData
|
||||
}
|
||||
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, false, errors.ThrowInternal(err, "QUERY-W4zje", "Errors.Query.CloseRows")
|
||||
return nil, false, false, errors.ThrowInternal(err, "QUERY-W4zje", "Errors.Query.CloseRows")
|
||||
}
|
||||
|
||||
return userAuthMethodTypes, forceMFA.Bool, nil
|
||||
return userAuthMethodTypes, forceMFA.Bool, forceMFALocalOnly.Bool, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -502,6 +506,7 @@ func prepareAuthMethodQuery() (string, []interface{}, error) {
|
||||
func prepareAuthMethodsForceMFAQuery() (string, error) {
|
||||
loginPolicyQuery, _, err := sq.Select(
|
||||
forceMFAForce.identifier(),
|
||||
forceMFAForceLocalOnly.identifier(),
|
||||
forceMFAInstanceID.identifier(),
|
||||
forceMFAOrgID.identifier(),
|
||||
).
|
||||
|
@ -59,7 +59,8 @@ var (
|
||||
prepareAuthMethodTypesRequiredStmt = `SELECT projections.users8_notifications.password_set,` +
|
||||
` auth_method_types.method_type,` +
|
||||
` user_idps_count.count,` +
|
||||
` auth_methods_force_mfa.force_mfa` +
|
||||
` auth_methods_force_mfa.force_mfa,` +
|
||||
` auth_methods_force_mfa.force_mfa_local_only` +
|
||||
` FROM projections.users8` +
|
||||
` LEFT JOIN projections.users8_notifications ON projections.users8.id = projections.users8_notifications.user_id AND projections.users8.instance_id = projections.users8_notifications.instance_id` +
|
||||
` LEFT JOIN (SELECT DISTINCT(auth_method_types.method_type), auth_method_types.user_id, auth_method_types.instance_id FROM projections.user_auth_methods4 AS auth_method_types` +
|
||||
@ -68,7 +69,7 @@ var (
|
||||
` LEFT JOIN (SELECT user_idps_count.user_id, user_idps_count.instance_id, COUNT(user_idps_count.user_id) AS count FROM projections.idp_user_links3 AS user_idps_count` +
|
||||
` GROUP BY user_idps_count.user_id, user_idps_count.instance_id) AS user_idps_count` +
|
||||
` ON user_idps_count.user_id = projections.users8.id AND user_idps_count.instance_id = projections.users8.instance_id` +
|
||||
` LEFT JOIN (SELECT auth_methods_force_mfa.force_mfa, auth_methods_force_mfa.instance_id, auth_methods_force_mfa.aggregate_id FROM projections.login_policies4 AS auth_methods_force_mfa ORDER BY auth_methods_force_mfa.is_default) AS auth_methods_force_mfa` +
|
||||
` LEFT JOIN (SELECT auth_methods_force_mfa.force_mfa, auth_methods_force_mfa.force_mfa_local_only, auth_methods_force_mfa.instance_id, auth_methods_force_mfa.aggregate_id FROM projections.login_policies5 AS auth_methods_force_mfa ORDER BY auth_methods_force_mfa.is_default) AS auth_methods_force_mfa` +
|
||||
` ON (auth_methods_force_mfa.aggregate_id = projections.users8.instance_id OR auth_methods_force_mfa.aggregate_id = projections.users8.resource_owner) AND auth_methods_force_mfa.instance_id = projections.users8.instance_id` +
|
||||
` AS OF SYSTEM TIME '-1 ms
|
||||
`
|
||||
@ -77,6 +78,7 @@ var (
|
||||
"method_type",
|
||||
"idps_count",
|
||||
"force_mfa",
|
||||
"force_mfa_local_only",
|
||||
}
|
||||
)
|
||||
|
||||
@ -318,11 +320,11 @@ func Test_UserAuthMethodPrepares(t *testing.T) {
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*testUserAuthMethodTypesRequired, error)) {
|
||||
builder, scan := prepareUserAuthMethodTypesRequiredQuery(ctx, db)
|
||||
return builder, func(rows *sql.Rows) (*testUserAuthMethodTypesRequired, error) {
|
||||
authMethods, forceMFA, err := scan(rows)
|
||||
authMethods, forceMFA, forceMFALocalOnly, err := scan(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &testUserAuthMethodTypesRequired{authMethods: authMethods, forceMFA: forceMFA}, nil
|
||||
return &testUserAuthMethodTypesRequired{authMethods: authMethods, forceMFA: forceMFA, forceMFALocalOnly: forceMFALocalOnly}, nil
|
||||
}
|
||||
},
|
||||
want: want{
|
||||
@ -339,11 +341,11 @@ func Test_UserAuthMethodPrepares(t *testing.T) {
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*testUserAuthMethodTypesRequired, error)) {
|
||||
builder, scan := prepareUserAuthMethodTypesRequiredQuery(ctx, db)
|
||||
return builder, func(rows *sql.Rows) (*testUserAuthMethodTypesRequired, error) {
|
||||
authMethods, forceMFA, err := scan(rows)
|
||||
authMethods, forceMFA, forceMFALocalOnly, err := scan(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &testUserAuthMethodTypesRequired{authMethods: authMethods, forceMFA: forceMFA}, nil
|
||||
return &testUserAuthMethodTypesRequired{authMethods: authMethods, forceMFA: forceMFA, forceMFALocalOnly: forceMFALocalOnly}, nil
|
||||
}
|
||||
},
|
||||
want: want{
|
||||
@ -356,6 +358,7 @@ func Test_UserAuthMethodPrepares(t *testing.T) {
|
||||
domain.UserAuthMethodTypePasswordless,
|
||||
1,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -366,7 +369,8 @@ func Test_UserAuthMethodPrepares(t *testing.T) {
|
||||
domain.UserAuthMethodTypePassword,
|
||||
domain.UserAuthMethodTypeIDP,
|
||||
},
|
||||
forceMFA: true,
|
||||
forceMFA: true,
|
||||
forceMFALocalOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -374,11 +378,11 @@ func Test_UserAuthMethodPrepares(t *testing.T) {
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*testUserAuthMethodTypesRequired, error)) {
|
||||
builder, scan := prepareUserAuthMethodTypesRequiredQuery(ctx, db)
|
||||
return builder, func(rows *sql.Rows) (*testUserAuthMethodTypesRequired, error) {
|
||||
authMethods, forceMFA, err := scan(rows)
|
||||
authMethods, forceMFA, forceMFALocalOnly, err := scan(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &testUserAuthMethodTypesRequired{authMethods: authMethods, forceMFA: forceMFA}, nil
|
||||
return &testUserAuthMethodTypesRequired{authMethods: authMethods, forceMFA: forceMFA, forceMFALocalOnly: forceMFALocalOnly}, nil
|
||||
}
|
||||
},
|
||||
want: want{
|
||||
@ -391,12 +395,14 @@ func Test_UserAuthMethodPrepares(t *testing.T) {
|
||||
domain.UserAuthMethodTypePasswordless,
|
||||
1,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
{
|
||||
true,
|
||||
domain.UserAuthMethodTypeOTP,
|
||||
1,
|
||||
true,
|
||||
true,
|
||||
},
|
||||
},
|
||||
),
|
||||
@ -409,7 +415,8 @@ func Test_UserAuthMethodPrepares(t *testing.T) {
|
||||
domain.UserAuthMethodTypePassword,
|
||||
domain.UserAuthMethodTypeIDP,
|
||||
},
|
||||
forceMFA: true,
|
||||
forceMFA: true,
|
||||
forceMFALocalOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -417,11 +424,11 @@ func Test_UserAuthMethodPrepares(t *testing.T) {
|
||||
prepare: func(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*testUserAuthMethodTypesRequired, error)) {
|
||||
builder, scan := prepareUserAuthMethodTypesRequiredQuery(ctx, db)
|
||||
return builder, func(rows *sql.Rows) (*testUserAuthMethodTypesRequired, error) {
|
||||
authMethods, forceMFA, err := scan(rows)
|
||||
authMethods, forceMFA, forceMFALocalOnly, err := scan(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &testUserAuthMethodTypesRequired{authMethods: authMethods, forceMFA: forceMFA}, nil
|
||||
return &testUserAuthMethodTypesRequired{authMethods: authMethods, forceMFA: forceMFA, forceMFALocalOnly: forceMFALocalOnly}, nil
|
||||
}
|
||||
},
|
||||
want: want{
|
||||
@ -448,6 +455,7 @@ func Test_UserAuthMethodPrepares(t *testing.T) {
|
||||
|
||||
// testUserAuthMethodTypesRequired is required as assetPrepare is only able to return a single object from scan
|
||||
type testUserAuthMethodTypesRequired struct {
|
||||
authMethods []domain.UserAuthMethodType
|
||||
forceMFA bool
|
||||
authMethods []domain.UserAuthMethodType
|
||||
forceMFA bool
|
||||
forceMFALocalOnly bool
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ func NewLoginPolicyAddedEvent(
|
||||
allowRegister,
|
||||
allowExternalIDP,
|
||||
forceMFA,
|
||||
forceMFALocalOnly,
|
||||
hidePasswordReset,
|
||||
ignoreUnknownUsernames,
|
||||
allowDomainDiscovery,
|
||||
@ -50,6 +51,7 @@ func NewLoginPolicyAddedEvent(
|
||||
allowRegister,
|
||||
allowExternalIDP,
|
||||
forceMFA,
|
||||
forceMFALocalOnly,
|
||||
hidePasswordReset,
|
||||
ignoreUnknownUsernames,
|
||||
allowDomainDiscovery,
|
||||
|
@ -28,6 +28,7 @@ func NewLoginPolicyAddedEvent(
|
||||
allowRegister,
|
||||
allowExternalIDP,
|
||||
forceMFA,
|
||||
forceMFALocalOnly,
|
||||
hidePasswordReset,
|
||||
ignoreUnknownUsernames,
|
||||
allowDomainDiscovery,
|
||||
@ -51,6 +52,7 @@ func NewLoginPolicyAddedEvent(
|
||||
allowRegister,
|
||||
allowExternalIDP,
|
||||
forceMFA,
|
||||
forceMFALocalOnly,
|
||||
hidePasswordReset,
|
||||
ignoreUnknownUsernames,
|
||||
allowDomainDiscovery,
|
||||
|
@ -24,6 +24,7 @@ type LoginPolicyAddedEvent struct {
|
||||
AllowRegister bool `json:"allowRegister,omitempty"`
|
||||
AllowExternalIDP bool `json:"allowExternalIdp,omitempty"`
|
||||
ForceMFA bool `json:"forceMFA,omitempty"`
|
||||
ForceMFALocalOnly bool `json:"forceMFALocalOnly,omitempty"`
|
||||
HidePasswordReset bool `json:"hidePasswordReset,omitempty"`
|
||||
IgnoreUnknownUsernames bool `json:"ignoreUnknownUsernames,omitempty"`
|
||||
AllowDomainDiscovery bool `json:"allowDomainDiscovery,omitempty"`
|
||||
@ -52,6 +53,7 @@ func NewLoginPolicyAddedEvent(
|
||||
allowRegister,
|
||||
allowExternalIDP,
|
||||
forceMFA,
|
||||
forceMFALocalOnly,
|
||||
hidePasswordReset,
|
||||
ignoreUnknownUsernames,
|
||||
allowDomainDiscovery,
|
||||
@ -71,6 +73,7 @@ func NewLoginPolicyAddedEvent(
|
||||
AllowRegister: allowRegister,
|
||||
AllowUserNamePassword: allowUserNamePassword,
|
||||
ForceMFA: forceMFA,
|
||||
ForceMFALocalOnly: forceMFALocalOnly,
|
||||
PasswordlessType: passwordlessType,
|
||||
HidePasswordReset: hidePasswordReset,
|
||||
IgnoreUnknownUsernames: ignoreUnknownUsernames,
|
||||
@ -106,6 +109,7 @@ type LoginPolicyChangedEvent struct {
|
||||
AllowRegister *bool `json:"allowRegister,omitempty"`
|
||||
AllowExternalIDP *bool `json:"allowExternalIdp,omitempty"`
|
||||
ForceMFA *bool `json:"forceMFA,omitempty"`
|
||||
ForceMFALocalOnly *bool `json:"forceMFALocalOnly,omitempty"`
|
||||
HidePasswordReset *bool `json:"hidePasswordReset,omitempty"`
|
||||
IgnoreUnknownUsernames *bool `json:"ignoreUnknownUsernames,omitempty"`
|
||||
AllowDomainDiscovery *bool `json:"allowDomainDiscovery,omitempty"`
|
||||
@ -170,6 +174,12 @@ func ChangeForceMFA(forceMFA bool) func(*LoginPolicyChangedEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeForceMFALocalOnly(forceMFALocalOnly bool) func(*LoginPolicyChangedEvent) {
|
||||
return func(e *LoginPolicyChangedEvent) {
|
||||
e.ForceMFALocalOnly = &forceMFALocalOnly
|
||||
}
|
||||
}
|
||||
|
||||
func ChangePasswordlessType(passwordlessType domain.PasswordlessType) func(*LoginPolicyChangedEvent) {
|
||||
return func(e *LoginPolicyChangedEvent) {
|
||||
e.PasswordlessType = &passwordlessType
|
||||
|
@ -170,12 +170,12 @@ func (u *UserView) MFATypesSetupPossible(level domain.MFALevel, policy *domain.L
|
||||
return types
|
||||
}
|
||||
|
||||
func (u *UserView) MFATypesAllowed(level domain.MFALevel, policy *domain.LoginPolicy) ([]domain.MFAType, bool) {
|
||||
func (u *UserView) MFATypesAllowed(level domain.MFALevel, policy *domain.LoginPolicy, isInternalAuthentication bool) ([]domain.MFAType, bool) {
|
||||
types := make([]domain.MFAType, 0)
|
||||
required := true
|
||||
switch level {
|
||||
default:
|
||||
required = policy.ForceMFA
|
||||
required = domain.RequiresMFA(policy.ForceMFA, policy.ForceMFALocalOnly, isInternalAuthentication)
|
||||
fallthrough
|
||||
case domain.MFALevelSecondFactor:
|
||||
if policy.HasSecondFactors() {
|
||||
|
@ -5866,6 +5866,11 @@ message UpdateLoginPolicyRequest {
|
||||
description: "defines if the user can additionally (to the login name) be identified by their verified phone number"
|
||||
}
|
||||
];
|
||||
bool force_mfa_local_only = 17 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "if activated, only local authenticated users are forced to use MFA. Authentication through IDPs won't prompt a MFA step in the login."
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message UpdateLoginPolicyResponse {
|
||||
|
@ -9654,6 +9654,11 @@ message AddCustomLoginPolicyRequest {
|
||||
description: "defines if the user can additionally (to the login name) be identified by their verified phone number"
|
||||
}
|
||||
];
|
||||
bool force_mfa_local_only = 20 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "if activated, only local authenticated users are forced to use MFA. Authentication through IDPs won't prompt a MFA step in the login."
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message AddCustomLoginPolicyResponse {
|
||||
@ -9698,6 +9703,11 @@ message UpdateCustomLoginPolicyRequest {
|
||||
description: "defines if the user can additionally (to the login name) be identified by their verified phone number"
|
||||
}
|
||||
];
|
||||
bool force_mfa_local_only = 17 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "if activated, only local authenticated users are forced to use MFA. Authentication through IDPs won't prompt a MFA step in the login."
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message UpdateCustomLoginPolicyResponse {
|
||||
|
@ -239,6 +239,11 @@ message LoginPolicy {
|
||||
description: "defines if the user can additionally (to the login name) be identified by their verified phone number"
|
||||
}
|
||||
];
|
||||
bool force_mfa_local_only = 22 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "if activated, only local authenticated users are forced to use MFA. Authentication through IDPs won't prompt a MFA step in the login."
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
enum SecondFactorType {
|
||||
|
@ -104,6 +104,11 @@ message LoginSettings {
|
||||
description: "resource_owner_type returns if the settings is managed on the organization or on the instance";
|
||||
}
|
||||
];
|
||||
bool force_mfa_local_only = 22 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "if activated, only local authenticated users are forced to use MFA. Authentication through IDPs won't prompt a MFA step in the login."
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
enum SecondFactorType {
|
||||
|
Loading…
x
Reference in New Issue
Block a user