feat(idp): provide auto only options (#8420)

# Which Problems Are Solved

As of now, **automatic creation** and **automatic linking options** were
only considered if the corresponding **allowed option** (account
creation / linking allowed) was enabled.

With this PR, this is no longer needed and allows administrators to
address cases, where only an **automatic creation** is allowed, but
users themselves should not be allowed to **manually** create new
accounts using an identity provider or edit the information during the
process.
Also, allowing users to only link to the proposed existing account is
now possible with an enabled **automatic linking option**, while
disabling **account linking allowed**.

# How the Problems Are Solved

- Check for **automatic** options without the corresponding **allowed**
option.
- added technical advisory to notify about the possible behavior change

# Additional Changes

- display the error message on the IdP linking step in the login UI (in
case there is one)
- display an error in case no option is possible
- exchanged deprecated `eventstoreExpect` with `expectEventstore` in
touched test files

# Additional Context

closes https://github.com/zitadel/zitadel/issues/7393

---------

Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
This commit is contained in:
Livio Spring 2024-08-14 15:04:26 +02:00 committed by GitHub
parent d32e22734f
commit e2e1100124
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 776 additions and 180 deletions

View File

@ -2058,10 +2058,10 @@
"ISAUTOCREATION_DESC": "Ако е избрано, ще бъде създаден акаунт, ако все още не съществува.",
"ISAUTOUPDATE": "Автоматична актуализация",
"ISAUTOUPDATE_DESC": "Ако е избрано, акаунтите се актуализират при повторно удостоверяване.",
"ISCREATIONALLOWED": "Създаването на акаунт е разрешено",
"ISCREATIONALLOWED_DESC": "Определя дали могат да се създават акаунти.",
"ISLINKINGALLOWED": "Свързването на акаунти е разрешено",
"ISLINKINGALLOWED_DESC": "Определя дали дадена самоличност може да бъде свързана със съществуващ акаунт.",
"ISCREATIONALLOWED": "Създаването на акаунт е разрешено (ръчно)",
"ISCREATIONALLOWED_DESC": "Определя дали могат да се създават акаунти с помощта на външен акаунт. Деактивирай, ако потребителите не трябва да могат да редактират информация за акаунта, когато автоматичното създаване е активирано.",
"ISLINKINGALLOWED": "Свързването на акаунти е разрешено (ръчно)",
"ISLINKINGALLOWED_DESC": "Определя дали дадена самоличност може да бъде свързана със съществуващ акаунт. Деактивирай, ако потребителите трябва да могат да свързват само предлагания акаунт в случай на активно автоматично свързване.",
"AUTOLINKING_DESC": "Определя дали идентичността ще бъде подканена да бъде свързана със съществуващ профил.",
"AUTOLINKINGTYPE": {
"0": "Изключено",

View File

@ -2063,10 +2063,10 @@
"ISAUTOCREATION_DESC": "Pokud je vybráno, účet bude vytvořen, pokud ještě neexistuje.",
"ISAUTOUPDATE": "Automatická aktualizace",
"ISAUTOUPDATE_DESC": "Pokud je vybráno, účty jsou aktualizovány při opětovné autentizaci.",
"ISCREATIONALLOWED": "Je povoleno vytváření účtu",
"ISCREATIONALLOWED_DESC": "Určuje, zda lze vytvářet účty.",
"ISLINKINGALLOWED": "Je povoleno propojení účtů",
"ISLINKINGALLOWED_DESC": "Určuje, zda lze identitu propojit s existujícím účtem.",
"ISCREATIONALLOWED": "Vytvoření účtu povoleno (ručně)",
"ISCREATIONALLOWED_DESC": "Určuje, zda mohou být účty vytvořeny pomocí externího účtu. Zakázat, pokud uživatelé by neměli být schopni upravovat informace o účtu, když je automatické vytváření povoleno.",
"ISLINKINGALLOWED": "Propojení účtu povoleno (ručně)",
"ISLINKINGALLOWED_DESC": "Určuje, zda identita může být ručně propojena s existujícím účtem. Zakázat, pokud by uživatelé měli být povoleni pouze propojit navrhovaný účet v případě aktivního automatického propojení.",
"AUTOLINKING_DESC": "Určuje, zda se bude identita vyzývat k propojení se stávajícím účtem.",
"AUTOLINKINGTYPE": {
"0": "Vypnuto",

View File

@ -2054,10 +2054,10 @@
"ISAUTOCREATION_DESC": "Legt fest, ob ein Konto erstellt wird, falls es noch nicht existiert.",
"ISAUTOUPDATE": "Automatisches Update",
"ISAUTOUPDATE_DESC": "Legt fest, ob Konten bei der erneuten Authentifizierung aktualisiert werden.",
"ISCREATIONALLOWED": "Account erstellen erlaubt",
"ISCREATIONALLOWED_DESC": "Legt fest, ob Konten erstellt werden können.",
"ISLINKINGALLOWED": "Account linking erlaubt",
"ISLINKINGALLOWED_DESC": "Legt fest, ob eine Identität mit einem bestehenden Konto verknüpft werden kann.",
"ISCREATIONALLOWED": "Kontoerstellung erlaubt (manuell)",
"ISCREATIONALLOWED_DESC": "Bestimmt, ob Konten mit einem externen Konto erstellt werden können. Deaktiviere, wenn Benutzer Kontoinformationen nicht bearbeiten können sollen, wenn die automatische Erstellung aktiviert ist.",
"ISLINKINGALLOWED": "Kontoverknüpfung erlaubt (manuell)",
"ISLINKINGALLOWED_DESC": "Bestimmt, ob eine Identität manuell mit einem bestehenden Konto verknüpft werden kann. Deaktiviere, wenn Benutzer nur das vorgeschlagene Konto im Falle einer aktiven automatischen Verknüpfung verknüpfen dürfen.",
"AUTOLINKING_DESC": "Legt fest, ob eine Identität aufgefordert wird, mit einem vorhandenen Konto verknüpft zu werden.",
"AUTOLINKINGTYPE": {
"0": "Deaktiviert",

View File

@ -2066,10 +2066,10 @@
"ISAUTOCREATION_DESC": "If selected, an account will be created if it does not exist yet.",
"ISAUTOUPDATE": "Automatic update",
"ISAUTOUPDATE_DESC": "If selected, accounts are updated on reauthentication.",
"ISCREATIONALLOWED": "Account creation allowed",
"ISCREATIONALLOWED_DESC": "Determines whether accounts can be created.",
"ISLINKINGALLOWED": "Account linking allowed",
"ISLINKINGALLOWED_DESC": "Determines whether an identity can be linked to an existing account.",
"ISCREATIONALLOWED": "Account creation allowed (manually)",
"ISCREATIONALLOWED_DESC": "Determines whether accounts can be created using an external account. Disable if users should not be able to edit account information when auto_creation is enabled.",
"ISLINKINGALLOWED": "Account linking allowed (manually)",
"ISLINKINGALLOWED_DESC": "Determines whether an identity can be manually linked to an existing account. Disable if users should only be allowed to link the proposed account in case of active auto_linking.",
"AUTOLINKING_DESC": "Determines whether an identity will be prompted to be linked to an existing account.",
"AUTOLINKINGTYPE": {
"0": "Disabled",

View File

@ -2059,10 +2059,10 @@
"ISAUTOCREATION_DESC": "Si se selecciona, una cuenta se creará si aún no existiera.",
"ISAUTOUPDATE": "Actualización automática",
"ISAUTOUPDATE_DESC": "Si se selecciona, las cuentas se actualizarán en la reautenticación.",
"ISCREATIONALLOWED": "Creación de cuentas permitida",
"ISCREATIONALLOWED_DESC": "Determina si se pueden crear cuentas.",
"ISLINKINGALLOWED": "Permitida la vinculación de cuentas",
"ISLINKINGALLOWED_DESC": "Determina si una identidad puede vincularse a una cuenta existente.",
"ISCREATIONALLOWED": "Creación de cuenta permitida (manualmente)",
"ISCREATIONALLOWED_DESC": "Determina si las cuentas se pueden crear usando una cuenta externa. Deshabilita si los usuarios no deberían poder editar la información de la cuenta cuando la creación automática está habilitada.",
"ISLINKINGALLOWED": "Enlace de cuenta permitido (manualmente)",
"ISLINKINGALLOWED_DESC": "Determina si una identidad se puede vincular manualmente a una cuenta existente. Deshabilita si los usuarios solo deberían poder vincular la cuenta propuesta en caso de vinculación automática activa.",
"AUTOLINKING_DESC": "Determina si se pedirá a una identidad que se vincule a una cuenta existente.",
"AUTOLINKINGTYPE": {
"0": "Desactivado",

View File

@ -2058,10 +2058,10 @@
"ISAUTOCREATION_DESC": "Détermine si un compte sera créé s'il n'existe pas déjà.",
"ISAUTOUPDATE": "Mise à jour automatique",
"ISAUTOUPDATE_DESC": "Si cette option est sélectionnée, les comptes sont mis à jour lors de la réauthentification.",
"ISCREATIONALLOWED": "Création de comptes autorisée",
"ISCREATIONALLOWED_DESC": "Détermine si des comptes peuvent être créés.",
"ISLINKINGALLOWED": "Liaison de comptes autorisée",
"ISLINKINGALLOWED_DESC": "Détermine si une identité peut être liée à un compte existant.",
"ISCREATIONALLOWED": "Création de compte autorisée (manuellement)",
"ISCREATIONALLOWED_DESC": "Détermine si les comptes peuvent être créés à l'aide d'un compte externe. Désactivez si les utilisateurs ne doivent pas pouvoir modifier les informations du compte lorsque la création automatique est activée.",
"ISLINKINGALLOWED": "Liaison de compte autorisée (manuellement)",
"ISLINKINGALLOWED_DESC": "Détermine si une identité peut être liée manuellement à un compte existant. Désactivez si les utilisateurs ne doivent pouvoir lier que le compte proposé en cas de liaison automatique active.",
"AUTOLINKING_DESC": "Détermine si une identité sera invitée à être liée à un compte existant.",
"AUTOLINKINGTYPE": {
"0": "Désactivé",

View File

@ -2058,10 +2058,10 @@
"ISAUTOCREATION_DESC": "Se selezionato, verrà creato un account se non esiste ancora.",
"ISAUTOUPDATE": "Aggiornamento automatico",
"ISAUTOUPDATE_DESC": "Se selezionato, gli account vengono aggiornati alla riautenticazione.",
"ISCREATIONALLOWED": "Creazione consentita",
"ISCREATIONALLOWED_DESC": "Determina se i conti possono essere creati.",
"ISLINKINGALLOWED": "Collegamento consentito",
"ISLINKINGALLOWED_DESC": "Determina se un'identità può essere collegata a un account esistente.",
"ISCREATIONALLOWED": "Creazione account consentita (manualmente)",
"ISCREATIONALLOWED_DESC": "Determina se gli account possono essere creati utilizzando un account esterno. Disabilita se gli utenti non dovrebbero essere in grado di modificare le informazioni dell'account quando la creazione automatica è abilitata.",
"ISLINKINGALLOWED": "Collegamento account consentito (manualmente)",
"ISLINKINGALLOWED_DESC": "Determina se un'identità può essere collegata manualmente a un account esistente. Disabilita se gli utenti dovrebbero essere autorizzati solo a collegare l'account proposto in caso di collegamento automatico attivo.",
"AUTOLINKING_DESC": "Determina se un'identità verrà invitata a essere collegata a un account esistente.",
"AUTOLINKINGTYPE": {
"0": "Disabilitato",

View File

@ -2054,10 +2054,10 @@
"ISAUTOCREATION_DESC": "選択した場合、アカウントがまだ存在しない場合はアカウントが作成されます。",
"ISAUTOUPDATE": "自動更新",
"ISAUTOUPDATE_DESC": "選択した場合、アカウントは再認証時に更新されます。",
"ISCREATIONALLOWED": "アカウント作成を許可",
"ISCREATIONALLOWED_DESC": "アカウントを作成できるかどうかを決ます。",
"ISLINKINGALLOWED": "アカウントリンクを許可",
"ISLINKINGALLOWED_DESC": "IDを既存のアカウントにリンクできるかどうかを決めます。",
"ISCREATIONALLOWED": "アカウント作成を許可 (手動)",
"ISCREATIONALLOWED_DESC": "外部アカウントを使用してアカウントを作成できるかどうかを決定します。 自動作成が有効になっている場合、ユーザーがアカウント情報を編集できないようにするには、無効にします。",
"ISLINKINGALLOWED": "アカウントリンクを許可 (手動)",
"ISLINKINGALLOWED_DESC": "アイデンティティを手動で既存のアカウントにリンクできるかどうかを決定します。 自動リンクがアクティブな場合は、ユーザーが提案されたアカウントのみをリンクできるようにするには、無効にします。",
"AUTOLINKING_DESC": "アイデンティティが既存のアカウントにリンクされるように促されるかどうかを決定します。",
"AUTOLINKINGTYPE": {
"0": "無効",

View File

@ -2061,10 +2061,10 @@
"ISAUTOCREATION_DESC": "Доколку е избрано, корисничка сметка ќе биде креиран ако сè уште не постои.",
"ISAUTOUPDATE": "Автоматско ажурирање",
"ISAUTOUPDATE_DESC": "Доколку е избрано, корисничките сметки се ажурираат при повторно најавување.",
"ISCREATIONALLOWED": "Дозволено креирање на кориснички сметки",
"ISCREATIONALLOWED_DESC": "Одредува дали може да се креираат кориснички сметки.",
"ISLINKINGALLOWED": "Дозволено поврзување на кориснички сметки",
"ISLINKINGALLOWED_DESC": "Одредува дали може да се поврзе идентитет со постоечка корисничка сметка.",
"ISCREATIONALLOWED": "Создавање на сметка дозволено (рачно)",
"ISCREATIONALLOWED_DESC": "Одредува дали сметките можат да се создадат со користење на надворешна сметка. Деактивирај ако корисниците не треба да можат да уредуваат информации за сметката кога автоматското создавање е овозможено.",
"ISLINKINGALLOWED": "Поврзување на сметка дозволено (рачно)",
"ISLINKINGALLOWED_DESC": "Одредува дали идентитет може да биде рачно поврзан со постоечка сметка. Деактивирај ако корисниците треба да можат да поврзуваат само предложената сметка во случај на активно автоматско поврзување.",
"AUTOLINKING_DESC": "Одредува дали ќе се бара идентитетот да биде поврзан со постоечки профил.",
"AUTOLINKINGTYPE": {
"0": "Исклучено",

View File

@ -2066,10 +2066,10 @@
"ISAUTOCREATION_DESC": "Als geselecteerd, wordt een account aangemaakt als het nog niet bestaat.",
"ISAUTOUPDATE": "Automatische update",
"ISAUTOUPDATE_DESC": "Als geselecteerd, worden accounts bijgewerkt bij opnieuw authenticeren.",
"ISCREATIONALLOWED": "Account creatie toegestaan",
"ISCREATIONALLOWED_DESC": "Bepaalt of accounts kunnen worden aangemaakt.",
"ISLINKINGALLOWED": "Account koppeling toegestaan",
"ISLINKINGALLOWED_DESC": "Bepaalt of een identiteit kan worden gekoppeld aan een bestaand account.",
"ISCREATIONALLOWED": "Accountaanmaak toegestaan (handmatig)",
"ISCREATIONALLOWED_DESC": "Bepaalt of accounts kunnen worden aangemaakt met behulp van een extern account. Schakel uit als gebruikers accountinformatie niet mogen kunnen bewerken wanneer auto-aanmaak is ingeschakeld.",
"ISLINKINGALLOWED": "Accountkoppeling toegestaan (handmatig)",
"ISLINKINGALLOWED_DESC": "Bepaalt of een identiteit handmatig kan worden gekoppeld aan een bestaand account. Schakel uit als gebruikers alleen het voorgestelde account mogen kunnen koppelen in geval van actieve auto-koppeling.",
"AUTOLINKING_DESC": "Bepaalt of een identiteit wordt gevraagd om te worden gekoppeld aan een bestaand account.",
"AUTOLINKINGTYPE": {
"0": "Uitgeschakeld",

View File

@ -2057,10 +2057,10 @@
"ISAUTOCREATION_DESC": "Jeśli zostanie wybrana, konto zostanie utworzone, jeśli jeszcze nie istnieje.",
"ISAUTOUPDATE": "Automatyczna aktualizacja",
"ISAUTOUPDATE_DESC": "Jeśli zaznaczone, konta są aktualizowane przy ponownym uwierzytelnianiu.",
"ISCREATIONALLOWED": "tworzenie dozwolone",
"ISCREATIONALLOWED_DESC": "Określa, czy można tworzyć konta.",
"ISLINKINGALLOWED": "dozwolone łączenie rachunków",
"ISLINKINGALLOWED_DESC": "Określa, czy tożsamość może być powiązana z istniejącym kontem.",
"ISCREATIONALLOWED": "Tworzenie konta dozwolone (ręcznie)",
"ISCREATIONALLOWED_DESC": "Określa, czy konta mogą być tworzone przy użyciu konta zewnętrznego. Wyłącz, jeśli użytkownicy nie powinni mieć możliwości edytowania informacji o koncie, gdy automatyczne tworzenie jest włączone.",
"ISLINKINGALLOWED": "Łączenie kont dozwolone (ręcznie)",
"ISLINKINGALLOWED_DESC": "Określa, czy tożsamość może być ręcznie połączona z istniejącym kontem. Wyłącz, jeśli użytkownicy powinni mieć możliwość łączenia tylko proponowanego konta w przypadku aktywnego automatycznego łączenia.",
"AUTOLINKING_DESC": "Określa, czy tożsamość będzie proszona o połączenie z istniejącym kontem.",
"AUTOLINKINGTYPE": {
"0": "Wyłączone",

View File

@ -2059,10 +2059,10 @@
"ISAUTOCREATION_DESC": "Se selecionado, uma conta será criada se ainda não existir.",
"ISAUTOUPDATE": "Atualização Automática",
"ISAUTOUPDATE_DESC": "Se selecionado, as contas são atualizadas ao serem reautenticadas.",
"ISCREATIONALLOWED": "Criação de Conta Permitida",
"ISCREATIONALLOWED_DESC": "Determina se as contas podem ser criadas.",
"ISLINKINGALLOWED": "Vinculação de Conta Permitida",
"ISLINKINGALLOWED_DESC": "Determina se uma identidade pode ser vinculada a uma conta existente.",
"ISCREATIONALLOWED": "Criação de conta permitida (manualmente)",
"ISCREATIONALLOWED_DESC": "Determina se contas podem ser criadas usando uma conta externa. Desative se os usuários não devem poder editar informações da conta quando a criação automática está ativada.",
"ISLINKINGALLOWED": "Vinculação de conta permitida (manualmente)",
"ISLINKINGALLOWED_DESC": "Determina se uma identidade pode ser vinculada manualmente a uma conta existente. Desative se os usuários só devem poder vincular a conta proposta em caso de vinculação automática ativa.",
"AUTOLINKING_DESC": "Determina se uma identidade será solicitada a ser vinculada a uma conta existente.",
"AUTOLINKINGTYPE": {
"0": "Desativado",

View File

@ -2148,10 +2148,10 @@
"ISAUTOCREATION_DESC": "Если этот флажок установлен, учетная запись будет создана, если она еще не существует.",
"ISAUTOUPDATE": "Автоматическое обновление",
"ISAUTOUPDATE_DESC": "Если этот флажок установлен, учетные записи обновляются при повторной аутентификации.",
"ISCREATIONALLOWED": "Создание учетной записи разрешено",
"ISCREATIONALLOWED_DESC": "Определяет, можно ли создавать учетные записи.",
"ISLINKINGALLOWED": "Привязка аккаунтов разрешена",
"ISLINKINGALLOWED_DESC": "Определяет, можно ли связать личность с существующей учетной записью.",
"ISCREATIONALLOWED": "Создание аккаунта разрешено (вручную)",
"ISCREATIONALLOWED_DESC": "Определяет, можно ли создавать учетные записи с использованием внешней учетной записи. Отключите, если пользователи не должны иметь возможность редактировать информацию об аккаунте при включенном автоматическом создании.",
"ISLINKINGALLOWED": "Связывание аккаунтов разрешено (вручную)",
"ISLINKINGALLOWED_DESC": "Определяет, можно ли вручную связать идентификатор с существующим аккаунтом. Отключите, если пользователям разрешено связывать только предлагаемый аккаунт в случае активного автоматического связывания.",
"AUTOLINKING_DESC": "Определяет, будет ли запрошено связать идентификацию с существующим аккаунтом.",
"AUTOLINKINGTYPE": {
"0": "Отключено",

View File

@ -2070,10 +2070,10 @@
"ISAUTOCREATION_DESC": "Om valt, skapas ett konto om det inte redan finns.",
"ISAUTOUPDATE": "Automatisk uppdatering",
"ISAUTOUPDATE_DESC": "Om valt, uppdateras konton vid återautentisering.",
"ISCREATIONALLOWED": "Kontoskapande tillåtet",
"ISCREATIONALLOWED_DESC": "Avgör om konton kan skapas.",
"ISLINKINGALLOWED": "Kontolänkning tillåten",
"ISLINKINGALLOWED_DESC": "Avgör om en identitet kan länkas till ett befintligt konto.",
"ISCREATIONALLOWED": "Kontoinrättning tillåten (manuellt)",
"ISCREATIONALLOWED_DESC": "Bestämmer om konton kan skapas med hjälp av ett externt konto. Avaktivera om användare inte ska kunna redigera kontoinformation när automatisk skapelse är aktiverad.",
"ISLINKINGALLOWED": "Kontolänkning tillåten (manuellt)",
"ISLINKINGALLOWED_DESC": "Bestämmer om en identitet kan länkas manuellt till ett befintligt konto. Avaktivera om användare bara ska kunna länka det föreslagna kontot vid aktiv automatisk länkning.",
"AUTOLINKING_DESC": "Avgör om en identitet kommer att uppmanas att länkas till ett befintligt konto.",
"AUTOLINKINGTYPE": {
"0": "Inaktiverad",

View File

@ -2057,10 +2057,10 @@
"ISAUTOCREATION_DESC": "如果选择了,如果账户还不存在,就会创建一个账户。",
"ISAUTOUPDATE": "正在自动更新",
"ISAUTOUPDATE_DESC": "如果选择,账户将在重新认证时更新。",
"ISCREATIONALLOWED": "是否允许创作",
"ISCREATIONALLOWED_DESC": "确定是否可以创建账户。",
"ISLINKINGALLOWED": "是否允许连接",
"ISLINKINGALLOWED_DESC": "确定一个身份是否可以与一个现有的账户相联系。",
"ISCREATIONALLOWED": "允许创建账户(手动)",
"ISCREATIONALLOWED_DESC": "确定是否可以使用外部账户创建账户。如果用户在启用自动创建时不应该能够编辑账户信息,请禁用。",
"ISLINKINGALLOWED": "允许链接账户(手动)",
"ISLINKINGALLOWED_DESC": "确定是否可以手动将身份链接到现有账户。如果用户只能在激活自动链接的情况下链接建议的账户,请禁用。",
"AUTOLINKING_DESC": "确定是否提示将身份链接到现有帐户。",
"AUTOLINKINGTYPE": {
"0": "已禁用",

View File

@ -66,14 +66,14 @@ enum AutoLinkingOption {
}
message Options {
bool is_linking_allowed = 1 [
bool is_manual_linking_allowed = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Enable if users should be able to link an existing ZITADEL user with an external account.";
description: "Enable if users should be able to link an existing ZITADEL user with an external account. Disable if users should only be allowed to link the proposed account in case of active auto_linking.";
}
];
bool is_creation_allowed = 2 [
bool is_manual_creation_allowed = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Enable if users should be able to create a new account in ZITADEL when using an external account.";
description: "Enable if users should be able to create a new account in ZITADEL when using an external account. Disable if users should not be able to edit account information when auto_creation is enabled.";
}
];
bool is_auto_creation = 3 [

View File

@ -147,9 +147,11 @@ When configuring external IdP templates in ZITADEL, several common settings enab
- **Automatic update**: This feature, when activated, allows ZITADEL to automatically update a user's profile information whenever changes are detected in the user's account on the external IdP. For example, if a user changes their last name in their Google or Microsoft account, ZITADEL will reflect this update in the user's account upon their next login.
- **Account creation allowed**: Determines whether new user accounts can be created in ZITADEL through the external IdP authentication process. Enabling this setting is crucial for allowing users who are new to your application to register and create accounts seamlessly via their existing external IdP accounts.
- **Account creation allowed (manually)**: Determines whether new user accounts can be created in ZITADEL through the external IdP authentication process. Enabling this setting is crucial for allowing users who are new to your application to register and create accounts seamlessly via their existing external IdP accounts. However, if you rely on the **automatic creation** and want to prevent users to manually create their accounts or edit information during the automatic process, you need to disable this option.
- **Account linking allowed**: Enables existing ZITADEL accounts to be linked with identities from external IdPs. It requires that a linkable ZITADEL account already exists for the user attempting to log in with an external IdP. Account linking is beneficial for users who wish to associate multiple login methods with their ZITADEL account, providing flexibility and convenience in how they access your application.
- **Account linking allowed (manually)**: Enables existing ZITADEL accounts to be linked with identities from external IdPs. It requires that a linkable ZITADEL account already exists for the user attempting to log in with an external IdP. Account linking is beneficial for users who wish to associate multiple login methods with their ZITADEL account, providing flexibility and convenience in how they access your application. However, if you rely on an **automatic linking option** and want to prevent users to manually link their accounts, you need to disable this option.
- **Automatic linking options**: Enables existing ZITADEL accounts to be linked with identities from external IdPs. If not disabled, ZITADEL will check for an existing account with the configured criteria (username or email) and prompt the user to link the account.

View File

@ -0,0 +1,29 @@
---
title: Technical Advisory 10011
---
## Date and Version
Version: 2.60.0
Date: TBD
## Description
Version 2.60.0 allows more combinations in the identity provider options. As of now, **automatic creation** and **automatic linking options** were only considered if the corresponding **allowed option** (account creation / linking allowed) was enabled.
Starting with this release, this is no longer needed and allows administrators to address cases, where only an **automatic creation** is allowed, but users themselves should not be allowed to **manually** create new accounts using an identity provider or edit the information during the process.
Also, allowing users to only link to the proposed existing account is now possible with an enabled **automatic linking option**, while disabling **account linking allowed**.
## Statement
This change was tracked in the following PR:
[feat(idp): provide auto only options](https://github.com/zitadel/zitadel/pull/8420), which was released in Version [2.60.0](https://github.com/zitadel/zitadel/releases/tag/v2.60.0)
## Mitigation
If you previously enabled one of the **automatic** options with the corresponding **allowed** option, be sure that this is the intended behavior.
## Impact
Once this update has been released and deployed, the **automatic** options can be activated with the corresponding **allowed** option.

View File

@ -178,6 +178,18 @@ We understand that these advisories may include breaking changes, and we aim to
<td>2.53.0</td>
<td>2024-05-28</td>
</tr>
<tr>
<td>
<a href="./advisory/a10011">A-10011</a>
</td>
<td>Identity Provider options: allow "auto" only</td>
<td>Breaking Behavior Change</td>
<td>
Version 2.60.0 allows more combinations in the identity provider options. Due to this there might be unexpected behavior changes.
</td>
<td>2.53.0</td>
<td>2024-05-28</td>
</tr>
</table>
## Subscribe to our Mailing List

View File

@ -533,9 +533,10 @@ func (l *Login) externalUserNotExisting(w http.ResponseWriter, r *http.Request,
}
}
// if auto creation or creation itself is disabled, send the user to the notFoundOption
if !provider.IsCreationAllowed || !provider.IsAutoCreation {
l.renderExternalNotFoundOption(w, r, authReq, orgIAMPolicy, human, idpLink, err)
// if auto creation is disabled, send the user to the notFoundOption
// where they can either link or create an account (based on the available options)
if !provider.IsAutoCreation {
l.renderExternalNotFoundOption(w, r, authReq, orgIAMPolicy, human, idpLink, nil)
return
}
@ -614,6 +615,10 @@ func (l *Login) renderExternalNotFoundOption(w http.ResponseWriter, r *http.Requ
l.renderError(w, r, authReq, err)
return
}
if !idpTemplate.IsCreationAllowed && !idpTemplate.IsLinkingAllowed {
l.renderError(w, r, authReq, zerrors.ThrowPreconditionFailed(nil, "LOGIN-3kl44", "Errors.User.ExternalIDP.NoOptionAllowed"))
return
}
translator := l.getTranslator(r.Context(), authReq)
data := externalNotFoundOptionData{

View File

@ -19,6 +19,9 @@ func (l *Login) linkUsers(w http.ResponseWriter, r *http.Request, authReq *domai
func (l *Login) renderLinkUsersDone(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) {
var errType, errMessage string
if err != nil {
errType, errMessage = l.getErrorMessage(r, err)
}
translator := l.getTranslator(r.Context(), authReq)
data := l.getUserData(r, authReq, translator, "LinkingUsersDone.Title", "LinkingUsersDone.Description", errType, errMessage)
l.renderer.RenderTemplate(w, r, translator, l.renderer.Templates[tmplLinkUsersDone], data, nil)

View File

@ -475,6 +475,7 @@ Errors:
NoExternalUserData: Не са получени външни потребителски данни
CreationNotAllowed: Създаването на нов потребител не е разрешено на този доставчик
LinkingNotAllowed: Свързването на потребител не е разрешено на този доставчик
NoOptionAllowed: Нито създаване, нито свързване е разрешено за този доставчик. Моля, свържете се с администратора.
GrantRequired: 'Влизането не е възможно. '
ProjectRequired: 'Влизането не е възможно. '
IdentityProvider:

View File

@ -486,6 +486,7 @@ Errors:
NoExternalUserData: Nebyla přijata žádná externí uživatelská data
CreationNotAllowed: Vytvoření nového uživatele není na tomto poskytovateli povoleno
LinkingNotAllowed: Propojení uživatele není na tomto poskytovateli povoleno
NoOptionAllowed: Ani vytvoření, ani propojení není povoleno pro tohoto poskytovatele. Obraťte se na svého správce.
GrantRequired: Přihlášení není možné. Uživatel musí mít alespoň jeden oprávnění na aplikaci. Prosím, kontaktujte svého správce.
ProjectRequired: Přihlášení není možné. Organizace uživatele musí být přidělena k projektu. Prosím, kontaktujte svého správce.
IdentityProvider:

View File

@ -485,6 +485,7 @@ Errors:
NoExternalUserData: Keine externen User-Daten erhalten
CreationNotAllowed: Erstellen eines neuen Benutzers mit diesem Provider ist nicht erlaubt
LinkingNotAllowed: Verknüpfen eines Benutzers mit diesem Provider ist nicht erlaubt
NoOptionAllowed: Weder Erstellung noch Verknüpfung ist für diesen Provider erlaubt. Bitte wenden Sie sich an Ihren Administrator.
GrantRequired: Die Anmeldung an diese Applikation ist nicht möglich. Der Benutzer benötigt mindestens eine Berechtigung an der Applikation. Bitte wende dich an deinen Administrator.
ProjectRequired: Die Anmeldung an dieser Applikation ist nicht möglich. Die Organisation des Benutzer benötigt Berechtigung auf das Projekt. Bitte wende dich an deinen Administrator.
IdentityProvider:

View File

@ -484,8 +484,9 @@ Errors:
ExternalUserIDEmpty: External User ID is empty
UserDisplayNameEmpty: User Display Name is empty
NoExternalUserData: No external User Data received
CreationNotAllowed: Creation of a new user is not allowed on this Provider
LinkingNotAllowed: Linking of a user is not allowed on this Provider
CreationNotAllowed: Creation of a new user is not allowed on this provider
LinkingNotAllowed: Linking of a user is not allowed on this provider
NoOptionAllowed: Neither creation of linking is allowed on this provider. Please contact your administrator.
GrantRequired: Login not possible. The user is required to have at least one grant on the application. Please contact your administrator.
ProjectRequired: Login not possible. The organization of the user must be granted to the project. Please contact your administrator.
IdentityProvider:

View File

@ -469,6 +469,7 @@ Errors:
NoExternalUserData: No se recibieron datos del usuario externo
CreationNotAllowed: La creación de un nuevo usuario no está permitida para este proveedor
LinkingNotAllowed: La vinculación de un usuario no está permitida para este proveedor
NoOptionAllowed: Ni la creación ni la vinculación están permitidas en este proveedor. Póngase en contacto con su administrador.
GrantRequired: El inicio de sesión no es posible. Se requiere que el usuario tenga al menos una concesión sobre la aplicación. Por favor contacta con tu administrador.
ProjectRequired: El inicio de sesión no es posible. La organización del usuario debe tener el acceso concedido para el proyecto. Por favor contacta con tu administrador.
IdentityProvider:

View File

@ -487,6 +487,7 @@ Errors:
NoExternalUserData: Aucune donnée d'utilisateur externe reçue
CreationNotAllowed: La création d'un nouvel utilisateur n'est pas autorisée sur ce fournisseur.
LinkingNotAllowed: La création d'un lien vers un utilisateur n'est pas autorisée pour ce fournisseur.
NoOptionAllowed: Ni la création ni la liaison sont autorisées pour ce fournisseur. Veuillez contacter votre administrateur.
GrantRequired: Connexion impossible. L'utilisateur doit avoir au moins une subvention sur l'application. Veuillez contacter votre administrateur.
ProjectRequired: Connexion impossible. L'organisation de l'utilisateur doit être accordée au projet. Veuillez contacter votre administrateur.
IdentityProvider:

View File

@ -487,6 +487,7 @@ Errors:
NoExternalUserData: Nessun dato utente esterno ricevuto
CreationNotAllowed: La creazione di un nuovo utente non è consentita su questo provider.
LinkingNotAllowed: Il collegamento di un utente non è consentito su questo provider.
NoOptionAllowed: Né la creazione né il collegamento sono consentiti per questo provider. Contattare l'amministratore.
GrantRequired: Accesso non possibile. L'utente deve avere almeno una sovvenzione sull'applicazione. Contatta il tuo amministratore.
ProjectRequired: Accesso non possibile. L'organizzazione dell'utente deve essere concessa al progetto. Contatta il tuo amministratore.
IdentityProvider:

View File

@ -450,6 +450,7 @@ Errors:
NoExternalUserData: 外部ユーザー情報を取得できません
CreationNotAllowed: このプロバイダーでは、新しいユーザーの作成は許可されていません
LinkingNotAllowed: このプロバイダーでは、ユーザーのリンクが許可されていません
NoOptionAllowed: このプロバイダーでは作成もリンクも許可されていません。 管理者にお問い合わせください。
GrantRequired: ログインできません。このユーザーは、アプリケーションに少なくとも1つの権限を付与されていることが必要です。管理者にお問い合わせください。
ProjectRequired: ログインできません。ユーザーの組織がプロジェクトに権限を付与されている必要があります。管理者にお問い合わせください。
IdentityProvider:

View File

@ -487,6 +487,7 @@ Errors:
NoExternalUserData: Нема преземени податоци за надворешен корисник
CreationNotAllowed: Креирањето на нов корисник не е дозволено на овој провајдер
LinkingNotAllowed: Поврзувањето на корисник не е дозволено на овој провајдер
NoOptionAllowed: NНиту создавање, ниту поврзување е дозволено за овој провајдер. Ве молиме контактирајте го вашиот администратор.
GrantRequired: Не е можно најавување. Корисникот мора да има барем едно овластување за апликацијата. Ве молиме контактирајте го вашиот администратор.
ProjectRequired: Не е можно најавување. Организацијата на корисникот мора да биде доделена на проектот. Ве молиме контактирајте го вашиот администратор.
IdentityProvider:

View File

@ -486,6 +486,7 @@ Errors:
NoExternalUserData: Geen externe Gebruiker Data ontvangen
CreationNotAllowed: Creatie van een nieuwe gebruiker is niet toegestaan op deze Provider
LinkingNotAllowed: Koppeling van een gebruiker is niet toegestaan op deze Provider
NoOptionAllowed: Noch aanmaak noch koppeling is toegestaan voor deze provider. Neem contact op met uw beheerder.
GrantRequired: Inloggen niet mogelijk. De gebruiker moet minimaal één grant hebben op de applicatie. Neem contact op met uw beheerder.
ProjectRequired: Inloggen niet mogelijk. De organisatie van de gebruiker moet toegekend zijn aan het project. Neem contact op met uw beheerder.
IdentityProvider:

View File

@ -487,6 +487,7 @@ Errors:
NoExternalUserData: Nie otrzymano danych użytkownika zewnętrznego
CreationNotAllowed: Tworzenie nowego użytkownika nie jest dozwolone w tym Providencie
LinkingNotAllowed: Linkowanie użytkownika nie jest dozwolone na tym Providencie
NoOptionAllowed: Ani tworzenie, ani łączenie nie jest dozwolone dla tego dostawcy. Skontaktuj się z administratorem.
GrantRequired: Logowanie nie jest możliwe. Użytkownik musi posiadać przynajmniej jedno uprawnienie w aplikacji. Skontaktuj się z administratorem.
ProjectRequired: Logowanie nie jest możliwe. Organizacja użytkownika musi zostać udzielona projektowi. Skontaktuj się z administratorem.
IdentityProvider:

View File

@ -483,6 +483,7 @@ Errors:
NoExternalUserData: Nenhum dado de usuário externo recebido
CreationNotAllowed: A criação de um novo usuário não é permitida neste provedor
LinkingNotAllowed: A vinculação de um usuário não é permitida neste provedor
NoOptionAllowed: Nem criação nem vinculação são permitidas neste fornecedor. Contate o seu administrador.
GrantRequired: Login não é possível. O usuário precisa ter pelo menos uma permissão no aplicativo. Entre em contato com o administrador.
ProjectRequired: Login não é possível. A organização do usuário precisa ser concedida ao projeto. Entre em contato com o administrador.
IdentityProvider:

View File

@ -486,6 +486,7 @@ Errors:
NoExternalUserData: Данные внешнего пользователя не получены
CreationNotAllowed: Создание нового пользователя для данного провайдера не разрешено
LinkingNotAllowed: Привязка пользователя с данным провайдером запрещена
NoOptionAllowed: Ни создание, ни связывание не разрешены для этого провайдера. Пожалуйста, обратитесь к администратору.
GrantRequired: Вход невозможен. Пользователь должен иметь хотя бы один допуск в приложении. Пожалуйста, свяжитесь с вашим администратором.
ProjectRequired: Вход невозможен. Организация пользователя должна иметь допуск к проекту. Пожалуйста, свяжитесь с вашим администратором.
IdentityProvider:

View File

@ -486,6 +486,7 @@ Errors:
NoExternalUserData: Det kom ingen användarinformation från det externa kontot
CreationNotAllowed: Det är inte tillåtet att skapa nya konton från den här externa leverantören
LinkingNotAllowed: Det är inte tillåtet att koppla ihop konton från den här externa leverantören
NoOptionAllowed: Varken skapande eller länkande är tillåtet för denna leverantör. Kontakta administratören.
GrantRequired: Det går inte att logga in just nu. Användarkontot har inte tillgång till någonting i tjänsten. Ta kontakt med systemansvarig.
ProjectRequired: Det går inte att logga in just nu. Användarkontots organisation har inte tillgång till tjänsten. Ta kontakt med systemansvarig.
IdentityProvider:

View File

@ -486,6 +486,7 @@ Errors:
NoExternalUserData: 未收到外部用户数据
CreationNotAllowed: 不允许在该供应商上创建新用户
LinkingNotAllowed: 在此提供者上不允许链接一个用户
NoOptionAllowed: 此提供商不允许创建或链接。请联系您的管理员。
GrantRequired: 无法登录,用户需要在应用程序上拥有至少一项授权,请联系您的管理员。
ProjectRequired: 无法登录,用户的组织必须授予项目,请联系您的管理员。
IdentityProvider:

View File

@ -4,7 +4,9 @@
<h1>{{t "LinkingUsersDone.Title"}}</h1>
{{ template "user-profile" . }}
{{if not .ErrID}}
<p>{{t "LinkingUsersDone.Description"}}</p>
{{end}}
</div>
<form action="{{ loginUrl }}" method="POST">
@ -12,6 +14,8 @@
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
{{template "error-message" .}}
<div class="lgn-actions">
<a class="lgn-stroked-button" href="{{ loginUrl }}">
{{t "LinkingUsersDone.CancelButtonText"}}

View File

@ -1272,7 +1272,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
func TestCommandSide_ImportHuman(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
idGenerator id.Generator
userPasswordHasher *crypto.Hasher
}
@ -1299,9 +1299,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
name: "orgid missing, invalid argument error",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args{
ctx: context.Background(),
@ -1327,8 +1325,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
name: "org policy not found, precondition error",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
expectFilter(),
),
@ -1357,8 +1354,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
name: "password policy not found, precondition error",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@ -1397,8 +1393,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
name: "user invalid, invalid argument error",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@ -1442,8 +1437,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
name: "add human (with password and initial code), ok",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@ -1529,8 +1523,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
name: "add human email verified password change not required, ok",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@ -1610,8 +1603,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
name: "add human email verified passwordless only, ok",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@ -1710,8 +1702,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
name: "add human email verified passwordless and password change not required, ok",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@ -1814,8 +1805,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
name: "add human (with phone), ok",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@ -1916,8 +1906,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
name: "add human (with verified phone), ok",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@ -2012,8 +2001,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
name: "add human (with undefined preferred language), ok",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@ -2098,8 +2086,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
name: "add human (with unsupported preferred language), ok",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@ -2185,8 +2172,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
name: "add human (with idp), ok",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@ -2328,11 +2314,153 @@ func TestCommandSide_ImportHuman(t *testing.T) {
},
},
{
name: "add human (with idp, creation not allowed), precondition error",
name: "add human (with idp, no creation allowed), precondition error",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
true,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"idpID",
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
false,
),
),
eventFromEventPusher(
org.NewIDPOIDCConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"clientID",
"idpID",
"issuer",
"authEndpoint",
"tokenEndpoint",
nil,
domain.OIDCMappingFieldUnspecified,
domain.OIDCMappingFieldUnspecified,
),
),
eventFromEventPusher(
func() eventstore.Command {
e, _ := org.NewOIDCIDPChangedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"config1",
[]idp.OIDCIDPChanges{
idp.ChangeOIDCOptions(idp.OptionChanges{
IsCreationAllowed: gu.Ptr(false),
IsAutoCreation: gu.Ptr(false),
}),
},
)
return e
}(),
),
),
expectFilter(
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"idpID",
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
false,
),
),
eventFromEventPusher(
org.NewIDPOIDCConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"clientID",
"idpID",
"issuer",
"authEndpoint",
"tokenEndpoint",
nil,
domain.OIDCMappingFieldUnspecified,
domain.OIDCMappingFieldUnspecified,
),
),
eventFromEventPusher(
func() eventstore.Command {
e, _ := org.NewOIDCIDPChangedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"config1",
[]idp.OIDCIDPChanges{
idp.ChangeOIDCOptions(idp.OptionChanges{IsCreationAllowed: gu.Ptr(false)}),
},
)
return e
}(),
),
eventFromEventPusher(
org.NewIdentityProviderAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"idpID",
domain.IdentityProviderTypeOrg,
),
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args{
ctx: context.Background(),
orgID: "org1",
human: &domain.Human{
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
IsEmailVerified: true,
},
},
links: []*domain.UserIDPLink{
{
IDPConfigID: "idpID",
ExternalUserID: "externalID",
DisplayName: "name",
},
},
secretGenerator: GetMockSecretGenerator(t),
}
},
res: res{
err: zerrors.IsPreconditionFailed,
},
},
{
name: "add human (with idp, manual creation not allowed), ok",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@ -2422,7 +2550,10 @@ func TestCommandSide_ImportHuman(t *testing.T) {
&org.NewAggregate("org1").Aggregate,
"config1",
[]idp.OIDCIDPChanges{
idp.ChangeOIDCOptions(idp.OptionChanges{IsCreationAllowed: gu.Ptr(false)}),
idp.ChangeOIDCOptions(idp.OptionChanges{
IsCreationAllowed: gu.Ptr(false),
IsAutoCreation: gu.Ptr(true),
}),
},
)
return e
@ -2436,6 +2567,17 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
expectPush(
newAddHumanEvent("", false, true, "", AllowedLanguage),
user.NewUserIDPLinkAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"idpID",
"name",
"externalID",
),
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
@ -2446,8 +2588,9 @@ func TestCommandSide_ImportHuman(t *testing.T) {
human: &domain.Human{
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
FirstName: "firstname",
LastName: "lastname",
PreferredLanguage: AllowedLanguage,
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
@ -2465,7 +2608,196 @@ func TestCommandSide_ImportHuman(t *testing.T) {
}
},
res: res{
err: zerrors.IsPreconditionFailed,
wantHuman: &domain.Human{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
DisplayName: "firstname lastname",
PreferredLanguage: AllowedLanguage,
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
IsEmailVerified: true,
},
State: domain.UserStateActive,
},
},
},
{
name: "add human (with idp, auto creation not allowed), ok",
given: func(t *testing.T) (fields, args) {
return fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
true,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"idpID",
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
false,
),
),
eventFromEventPusher(
org.NewIDPOIDCConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"clientID",
"idpID",
"issuer",
"authEndpoint",
"tokenEndpoint",
nil,
domain.OIDCMappingFieldUnspecified,
domain.OIDCMappingFieldUnspecified,
),
),
eventFromEventPusher(
func() eventstore.Command {
e, _ := org.NewOIDCIDPChangedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"config1",
[]idp.OIDCIDPChanges{
idp.ChangeOIDCOptions(idp.OptionChanges{IsCreationAllowed: gu.Ptr(false)}),
},
)
return e
}(),
),
),
expectFilter(
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"idpID",
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
false,
),
),
eventFromEventPusher(
org.NewIDPOIDCConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"clientID",
"idpID",
"issuer",
"authEndpoint",
"tokenEndpoint",
nil,
domain.OIDCMappingFieldUnspecified,
domain.OIDCMappingFieldUnspecified,
),
),
eventFromEventPusher(
func() eventstore.Command {
e, _ := org.NewOIDCIDPChangedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"config1",
[]idp.OIDCIDPChanges{
idp.ChangeOIDCOptions(idp.OptionChanges{
IsCreationAllowed: gu.Ptr(true),
IsAutoCreation: gu.Ptr(false),
}),
},
)
return e
}(),
),
eventFromEventPusher(
org.NewIdentityProviderAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"idpID",
domain.IdentityProviderTypeOrg,
),
),
),
expectPush(
newAddHumanEvent("", false, true, "", AllowedLanguage),
user.NewUserIDPLinkAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"idpID",
"name",
"externalID",
),
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args{
ctx: context.Background(),
orgID: "org1",
human: &domain.Human{
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
PreferredLanguage: AllowedLanguage,
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
IsEmailVerified: true,
},
},
links: []*domain.UserIDPLink{
{
IDPConfigID: "idpID",
ExternalUserID: "externalID",
DisplayName: "name",
},
},
secretGenerator: GetMockSecretGenerator(t),
}
},
res: res{
wantHuman: &domain.Human{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
DisplayName: "firstname lastname",
PreferredLanguage: AllowedLanguage,
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
IsEmailVerified: true,
},
State: domain.UserStateActive,
},
},
},
}
@ -2473,7 +2805,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
f, a := tt.given(t)
r := &Commands{
eventstore: f.eventstore,
eventstore: f.eventstore(t),
idGenerator: f.idGenerator,
userPasswordHasher: f.userPasswordHasher,
}
@ -2494,7 +2826,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
func TestCommandSide_HumanMFASkip(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type (
args struct {
@ -2516,9 +2848,7 @@ func TestCommandSide_HumanMFASkip(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -2532,8 +2862,7 @@ func TestCommandSide_HumanMFASkip(t *testing.T) {
{
name: "user not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -2549,8 +2878,7 @@ func TestCommandSide_HumanMFASkip(t *testing.T) {
{
name: "skip mfa init, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -2589,7 +2917,7 @@ func TestCommandSide_HumanMFASkip(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
err := r.HumanSkipMFAInit(tt.args.ctx, tt.args.userID, tt.args.orgID)
if tt.res.err == nil {
@ -2604,7 +2932,7 @@ func TestCommandSide_HumanMFASkip(t *testing.T) {
func TestCommandSide_HumanSignOut(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type (
args struct {
@ -2626,9 +2954,7 @@ func TestCommandSide_HumanSignOut(t *testing.T) {
{
name: "agentid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -2642,9 +2968,7 @@ func TestCommandSide_HumanSignOut(t *testing.T) {
{
name: "userids missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -2658,8 +2982,7 @@ func TestCommandSide_HumanSignOut(t *testing.T) {
{
name: "user not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@ -2673,8 +2996,7 @@ func TestCommandSide_HumanSignOut(t *testing.T) {
{
name: "human sign out, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -2713,8 +3035,7 @@ func TestCommandSide_HumanSignOut(t *testing.T) {
{
name: "human sign out multiple users, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -2774,7 +3095,7 @@ func TestCommandSide_HumanSignOut(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
err := r.HumansSignOut(tt.args.ctx, tt.args.agentID, tt.args.userIDs)
if tt.res.err == nil {

View File

@ -86,12 +86,13 @@ func (c *Commands) addUserIDPLink(ctx context.Context, human *eventstore.Aggrega
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-39nfs", "Errors.IDPConfig.NotExisting")
}
options := idpWriteModel.GetProviderOptions()
// IDP user will either be linked or created on a new user
// Therefore we need to either check if linking is allowed or creation:
if linkToExistingUser && !idpWriteModel.GetProviderOptions().IsLinkingAllowed {
if linkToExistingUser && !options.IsLinkingAllowed && options.AutoLinkingOption == domain.AutoLinkingOptionUnspecified {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-Sfee2", "Errors.ExternalIDP.LinkingNotAllowed")
}
if !linkToExistingUser && !idpWriteModel.GetProviderOptions().IsCreationAllowed {
if !linkToExistingUser && !options.IsCreationAllowed && !options.IsAutoCreation {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-SJI3g", "Errors.ExternalIDP.CreationNotAllowed")
}
return user.NewUserIDPLinkAddedEvent(ctx, human, link.IDPConfigID, link.DisplayName, link.ExternalUserID), nil

View File

@ -20,7 +20,7 @@ import (
func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@ -40,9 +40,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) {
{
name: "missing userid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -62,9 +60,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) {
{
name: "no external idps, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -78,8 +74,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) {
{
name: "userID doesnt match aggregate id, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(
@ -120,8 +115,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) {
{
name: "invalid external idp, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(
@ -162,8 +156,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) {
{
name: "config not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(
@ -203,10 +196,9 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) {
},
},
{
name: "linking not allowed, precondition error",
name: "no linking not allowed, precondition failed",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(
@ -307,11 +299,236 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) {
err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-Sfee2", "Errors.ExternalIDP.LinkingNotAllowed"),
},
},
{
name: "auto linking not allowed (manual linking allowed), ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"userName",
"firstName",
"lastName",
"nickName",
"displayName",
language.German,
domain.GenderFemale,
"email@Address.ch",
false,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"config1",
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
true,
),
),
eventFromEventPusher(
org.NewIDPOIDCConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"clientID",
"config1",
"issuer",
"authEndpoint",
"tokenEndpoint",
nil,
domain.OIDCMappingFieldUnspecified,
domain.OIDCMappingFieldUnspecified,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"config1",
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
true,
),
),
eventFromEventPusher(
org.NewIDPOIDCConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"clientID",
"config1",
"issuer",
"authEndpoint",
"tokenEndpoint",
nil,
domain.OIDCMappingFieldUnspecified,
domain.OIDCMappingFieldUnspecified,
),
),
eventFromEventPusher(
func() eventstore.Command {
e, _ := org.NewOIDCIDPChangedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"config1",
[]idp.OIDCIDPChanges{
idp.ChangeOIDCOptions(idp.OptionChanges{
IsLinkingAllowed: gu.Ptr(true),
AutoLinkingOption: gu.Ptr(domain.AutoLinkingOptionUnspecified),
}),
},
)
return e
}(),
),
),
expectPush(
user.NewUserIDPLinkAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"name",
"externaluser1",
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
links: []*domain.UserIDPLink{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
IDPConfigID: "config1",
DisplayName: "name",
ExternalUserID: "externaluser1",
},
},
},
res: res{},
},
{
name: "manual linking not allowed (auto linking allowed), ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(
context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"userName",
"firstName",
"lastName",
"nickName",
"displayName",
language.German,
domain.GenderFemale,
"email@Address.ch",
false,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"config1",
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
true,
),
),
eventFromEventPusher(
org.NewIDPOIDCConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"clientID",
"config1",
"issuer",
"authEndpoint",
"tokenEndpoint",
nil,
domain.OIDCMappingFieldUnspecified,
domain.OIDCMappingFieldUnspecified,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"config1",
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
true,
),
),
eventFromEventPusher(
org.NewIDPOIDCConfigAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"clientID",
"config1",
"issuer",
"authEndpoint",
"tokenEndpoint",
nil,
domain.OIDCMappingFieldUnspecified,
domain.OIDCMappingFieldUnspecified,
),
),
eventFromEventPusher(
func() eventstore.Command {
e, _ := org.NewOIDCIDPChangedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"config1",
[]idp.OIDCIDPChanges{
idp.ChangeOIDCOptions(idp.OptionChanges{
IsLinkingAllowed: gu.Ptr(false),
AutoLinkingOption: gu.Ptr(domain.AutoLinkingOptionEmail),
}),
},
)
return e
}(),
),
),
expectPush(
user.NewUserIDPLinkAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"name",
"externaluser1",
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
links: []*domain.UserIDPLink{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
IDPConfigID: "config1",
DisplayName: "name",
ExternalUserID: "externaluser1",
},
},
},
res: res{},
},
{
name: "add external idp org config, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(
@ -409,8 +626,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) {
{
name: "add external idp iam config, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(
@ -509,7 +725,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
err := r.BulkAddedUserIDPLinks(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.links)
assert.ErrorIs(t, err, tt.res.err)
@ -519,7 +735,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) {
func TestCommandSide_RemoveUserIDPLink(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
}
type args struct {
@ -539,9 +755,7 @@ func TestCommandSide_RemoveUserIDPLink(t *testing.T) {
{
name: "invalid idp, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
@ -561,9 +775,7 @@ func TestCommandSide_RemoveUserIDPLink(t *testing.T) {
{
name: "aggregate id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
@ -580,8 +792,7 @@ func TestCommandSide_RemoveUserIDPLink(t *testing.T) {
{
name: "user removed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewUserIDPLinkAddedEvent(context.Background(),
@ -620,8 +831,7 @@ func TestCommandSide_RemoveUserIDPLink(t *testing.T) {
{
name: "external idp not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
checkPermission: newMockPermissionCheckAllowed(),
@ -643,8 +853,7 @@ func TestCommandSide_RemoveUserIDPLink(t *testing.T) {
{
name: "remove external idp, permission error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewUserIDPLinkAddedEvent(context.Background(),
@ -675,8 +884,7 @@ func TestCommandSide_RemoveUserIDPLink(t *testing.T) {
{
name: "remove external idp, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewUserIDPLinkAddedEvent(context.Background(),
@ -717,7 +925,7 @@ func TestCommandSide_RemoveUserIDPLink(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
}
got, err := r.RemoveUserIDPLink(tt.args.ctx, tt.args.link)
@ -736,7 +944,7 @@ func TestCommandSide_RemoveUserIDPLink(t *testing.T) {
func TestCommandSide_ExternalLoginCheck(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@ -756,9 +964,7 @@ func TestCommandSide_ExternalLoginCheck(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@ -772,8 +978,7 @@ func TestCommandSide_ExternalLoginCheck(t *testing.T) {
{
name: "user removed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewUserIDPLinkAddedEvent(context.Background(),
@ -806,8 +1011,7 @@ func TestCommandSide_ExternalLoginCheck(t *testing.T) {
{
name: "external login check, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@ -852,7 +1056,7 @@ func TestCommandSide_ExternalLoginCheck(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
}
err := r.UserIDPLoginChecked(tt.args.ctx, tt.args.orgID, tt.args.userID, tt.args.authRequest)
if tt.res.err == nil {

View File

@ -500,12 +500,12 @@ message AzureADConfig {
message Options {
bool is_linking_allowed = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Enable if users should be able to link an existing ZITADEL user with an external account.";
description: "Enable if users should be able to manually link an existing ZITADEL user with an external account. Disable if users should only be allowed to link the proposed account in case of active auto_linking.";
}
];
bool is_creation_allowed = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Enable if users should be able to create a new account in ZITADEL when using an external account.";
description: "Enable if users should be able to manually create a new account in ZITADEL when using an external account. Disable if users should not be able to edit account information when auto_creation is enabled.";
}
];
bool is_auto_creation = 3 [