diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index 90c9bd74cee..42b5fa40247 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -1126,6 +1126,13 @@ InternalAuthZ: - "project.grant.delete" - "project.grant.member.read" - "session.delete" + - Role: "IAM_ADMIN_IMPERSONATOR" + Permissions: + - "admin.impersonation" + - "impersonation" + - Role: "IAM_END_USER_IMPERSONATOR" + Permissions: + - "impersonation" - Role: "ORG_OWNER" Permissions: - "org.read" @@ -1275,6 +1282,13 @@ InternalAuthZ: - "policy.read" - "project.read:self" - "project.create" + - Role: "ORG_ADMIN_IMPERSONATOR" + Permissions: + - "admin.impersonation" + - "impersonation" + - Role: "ORG_END_USER_IMPERSONATOR" + Permissions: + - "impersonation" - Role: "PROJECT_OWNER" Permissions: - "org.global.read" diff --git a/console/src/app/modules/policies/security-policy/security-policy.component.html b/console/src/app/modules/policies/security-policy/security-policy.component.html index 29ae3d02d78..6d89b70c114 100644 --- a/console/src/app/modules/policies/security-policy/security-policy.component.html +++ b/console/src/app/modules/policies/security-policy/security-policy.component.html @@ -5,19 +5,22 @@
- {{ 'SETTING.SECURITY.DESCRIPTION' | translate }} +

{{ 'SETTING.SECURITY.IFRAMETITLE' | translate }}

{{ 'SETTING.SECURITY.IFRAMEENABLED' | translate }} + {{ + 'SETTING.SECURITY.IFRAMEDESCRIPTION' | translate + }}
@@ -29,14 +32,14 @@ matTooltip="{{ 'ACTIONS.ADD' | translate }}" type="submit" mat-icon-button - [disabled]="!enabled || originsControl.invalid || (['iam.policy.write'] | hasRole | async) === false" + [disabled]="!iframeEnabled || originsControl.invalid || (['iam.policy.write'] | hasRole | async) === false" > add
-
+
{{ uri }} @@ -45,6 +48,21 @@
+ +

{{ 'SETTING.SECURITY.IMPERSONATIONTITLE' | translate }}

+ + {{ 'SETTING.SECURITY.IMPERSONATIONENABLED' | translate }} + + {{ + 'SETTING.SECURITY.IMPERSONATIONDESCRIPTION' | translate + }}
diff --git a/console/src/app/modules/policies/security-policy/security-policy.component.ts b/console/src/app/modules/policies/security-policy/security-policy.component.ts index ae809c4d438..6698c16f120 100644 --- a/console/src/app/modules/policies/security-policy/security-policy.component.ts +++ b/console/src/app/modules/policies/security-policy/security-policy.component.ts @@ -13,7 +13,8 @@ import { InfoSectionType } from '../../info-section/info-section.component'; }) export class SecurityPolicyComponent implements OnInit { public originsList: string[] = []; - public enabled: boolean = false; + public iframeEnabled: boolean = false; + public impersonationEnabled: boolean = false; public loading: boolean = false; public InfoSectionType: any = InfoSectionType; @@ -32,7 +33,8 @@ export class SecurityPolicyComponent implements OnInit { private fetchData(): void { this.service.getSecurityPolicy().then((securityPolicy) => { if (securityPolicy.policy) { - this.enabled = securityPolicy.policy?.enableIframeEmbedding; + this.impersonationEnabled = securityPolicy.policy?.enableImpersonation; + this.iframeEnabled = securityPolicy.policy?.enableIframeEmbedding; this.originsList = securityPolicy.policy?.allowedOriginsList; if (securityPolicy.policy.enableIframeEmbedding) { this.originsControl.enable(); @@ -46,7 +48,8 @@ export class SecurityPolicyComponent implements OnInit { private updateData(): Promise { const req = new SetSecurityPolicyRequest(); req.setAllowedOriginsList(this.originsList); - req.setEnableIframeEmbedding(this.enabled); + req.setEnableIframeEmbedding(this.iframeEnabled); + req.setEnableImpersonation(this.impersonationEnabled); return (this.service as AdminService).setSecurityPolicy(req); } @@ -88,7 +91,7 @@ export class SecurityPolicyComponent implements OnInit { } } - public enabledChanged(event: MatCheckboxChange) { + public iframeEnabledChanged(event: MatCheckboxChange) { if (event.checked) { this.originsControl.enable(); } else { diff --git a/console/src/app/utils/color.ts b/console/src/app/utils/color.ts index b76982d4fd7..fef8da25a73 100644 --- a/console/src/app/utils/color.ts +++ b/console/src/app/utils/color.ts @@ -87,6 +87,12 @@ export function getMembershipColor(role: string): Color { case 'IAM_USER_MANAGER': color = COLORS[8]; break; + case 'IAM_ADMIN_IMPERSONATOR': + color = COLORS[17]; + break; + case 'IAM_END_USER_IMPERSONATOR': + color = COLORS[9]; + break; case 'ORG_OWNER': color = COLORS[16]; @@ -106,6 +112,12 @@ export function getMembershipColor(role: string): Color { case 'ORG_PROJECT_CREATOR': color = COLORS[12]; break; + case 'ORG_ADMIN_IMPERSONATOR': + color = COLORS[17]; + break; + case 'ORG_END_USER_IMPERSONATOR': + color = COLORS[9]; + break; case 'PROJECT_OWNER': color = COLORS[9]; diff --git a/console/src/assets/i18n/bg.json b/console/src/assets/i18n/bg.json index 38dbcb0ebd9..c1f99d1ed26 100644 --- a/console/src/assets/i18n/bg.json +++ b/console/src/assets/i18n/bg.json @@ -181,12 +181,16 @@ "IAM_OWNER_VIEWER": "Има разрешение да прегледа целия екземпляр, включително всички организации", "IAM_ORG_MANAGER": "Има разрешение за създаване и управление на организации", "IAM_USER_MANAGER": "Има разрешение за създаване и управление на потребители", + "IAM_ADMIN_IMPERSONATOR": "Има разрешение да се представя за администратор и крайни потребители от всички организации", + "IAM_END_USER_IMPERSONATOR": "Има разрешение да се представя за крайни потребители от всички организации", "ORG_OWNER": "Има разрешение за цялата организация", "ORG_USER_MANAGER": "Има разрешение да създава и управлява потребители на организацията", "ORG_OWNER_VIEWER": "Има разрешение за преглед на цялата организация", "ORG_USER_PERMISSION_EDITOR": "Има разрешение за управление на потребителски безвъзмездни средства", "ORG_PROJECT_PERMISSION_EDITOR": "Има разрешение за управление на грантове по проекти", "ORG_PROJECT_CREATOR": "Има разрешение да създава свои собствени проекти и основни настройки", + "ORG_ADMIN_IMPERSONATOR": "Има разрешение да се представя за администратор и крайни потребители от организацията", + "ORG_END_USER_IMPERSONATOR": "Има разрешение да се представя за крайни потребители от организацията", "PROJECT_OWNER": "Има разрешение върху целия проект", "PROJECT_OWNER_VIEWER": "Има разрешение за преглед на целия проект", "PROJECT_OWNER_GLOBAL": "Има разрешение върху целия проект", @@ -1153,9 +1157,13 @@ "UPDATED": "Настройките обновени." }, "SECURITY": { - "DESCRIPTION": "Тази настройка настройва CSP да позволява рамкиране от набор от разрешени домейни. ", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "Тази настройка настройва CSP да позволява рамкиране от набор от разрешени домейни. ", "IFRAMEENABLED": "Разрешаване на iFrame", - "ALLOWEDORIGINS": "Разрешени URL адреси" + "ALLOWEDORIGINS": "Разрешени URL адреси", + "IMPERSONATIONTITLE": "Имитиране", + "IMPERSONATIONENABLED": "Разрешаване на имитация", + "IMPERSONATIONDESCRIPTION": "Тази настройка позволява да се използва имитация по принцип. Обърнете внимание, че имитаторът също се нуждае от присвоени подходящи роли `*_IMPERSONATOR`." }, "DIALOG": { "RESET": { diff --git a/console/src/assets/i18n/cs.json b/console/src/assets/i18n/cs.json index 71baadfc45a..54be4f2f99c 100644 --- a/console/src/assets/i18n/cs.json +++ b/console/src/assets/i18n/cs.json @@ -188,12 +188,16 @@ "IAM_OWNER_VIEWER": "Má oprávnění prohlížet celou instanci, včetně všech organizací", "IAM_ORG_MANAGER": "Má oprávnění vytvářet a spravovat organizace", "IAM_USER_MANAGER": "Má oprávnění vytvářet a spravovat uživatele", + "IAM_ADMIN_IMPERSONATOR": "Má oprávnění vydávat se za správce a koncové uživatele ze všech organizací", + "IAM_END_USER_IMPERSONATOR": "Má oprávnění vydávat se za koncové uživatele ze všech organizací", "ORG_OWNER": "Má oprávnění nad celou organizací", "ORG_USER_MANAGER": "Má oprávnění vytvářet a spravovat uživatele organizace", "ORG_OWNER_VIEWER": "Má oprávnění prohlížet celou organizaci", "ORG_USER_PERMISSION_EDITOR": "Má oprávnění spravovat uživatelská pověření", "ORG_PROJECT_PERMISSION_EDITOR": "Má oprávnění spravovat pověření projektu", "ORG_PROJECT_CREATOR": "Má oprávnění vytvářet své vlastní projekty a podřízená nastavení", + "ORG_ADMIN_IMPERSONATOR": "Má oprávnění vydávat se za správce a koncové uživatele z organizace", + "ORG_END_USER_IMPERSONATOR": "Má oprávnění vydávat se za koncové uživatele z organizace", "PROJECT_OWNER": "Má oprávnění nad celým projektem", "PROJECT_OWNER_VIEWER": "Má oprávnění prohlížet celý projekt", "PROJECT_OWNER_GLOBAL": "Má oprávnění nad celým projektem", @@ -1160,9 +1164,13 @@ "UPDATED": "Nastavení aktualizováno." }, "SECURITY": { - "DESCRIPTION": "Toto nastavení nastaví CSP tak, aby povolovalo vkládání ze sady povolených domén. Všimněte si, že povolením použití iFrame riskujete umožnění clickjackingu.", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "Toto nastavení nastaví CSP tak, aby povolovalo vkládání ze sady povolených domén. Všimněte si, že povolením použití iFrame riskujete umožnění clickjackingu.", "IFRAMEENABLED": "Povolit iFrame", - "ALLOWEDORIGINS": "Povolené URL" + "ALLOWEDORIGINS": "Povolené URL", + "IMPERSONATIONTITLE": "Předstírání jiné identity", + "IMPERSONATIONENABLED": "Povolit předstírání jiné identity", + "IMPERSONATIONDESCRIPTION": "Toto nastavení v zásadě umožňuje používat zosobnění. Všimněte si, že imitátor potřebuje také přiřazené příslušné role `*_IMPERSONATOR`." }, "DIALOG": { "RESET": { diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 508db351b65..56ecafe8ba6 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -187,12 +187,16 @@ "IAM_OWNER_VIEWER": "Hat die Leseberechtigung, die gesamte Instanz einschließlich aller Organisationen zu überprüfen", "IAM_ORG_MANAGER": "Hat die Berechtigung zum Erstellen und Verwalten von Organisationen", "IAM_USER_MANAGER": "Hat die Berechtigung zum Erstellen und Verwalten von Benutzern", + "IAM_ADMIN_IMPERSONATOR": "Hat die Berechtigung, sich als Administrator und Endbenutzer aller Organisationen auszugeben", + "IAM_END_USER_IMPERSONATOR": "Hat die Berechtigung, sich als Endbenutzer aller Organisationen auszugeben", "ORG_OWNER": "Hat die Berechtigung für die gesamte Organisation", "ORG_USER_MANAGER": "Hat die Berechtigung, Benutzer der Organisation zu erstellen und zu verwalten", "ORG_OWNER_VIEWER": "Hat die Leseberechtigung, die gesamte Organisation zu überprüfen", "ORG_USER_PERMISSION_EDITOR": "Verfügt über die Berechtigung zum Verwalten von User grants", "ORG_PROJECT_PERMISSION_EDITOR": "Hat die Berechtigung, Projektberechtigungen für externe Organisationen zu verwalten", "ORG_PROJECT_CREATOR": "Hat die Berechtigung, seine eigenen Projekte und zugrunde liegenden Einstellungen zu erstellen", + "ORG_ADMIN_IMPERSONATOR": "Hat die Berechtigung, sich als Administrator und Endbenutzer der Organisation auszugeben", + "ORG_END_USER_IMPERSONATOR": "Hat die Berechtigung, sich als Endbenutzer der Organisation auszugeben", "PROJECT_OWNER": "Hat die Berechtigung für das gesamte Projekt", "PROJECT_OWNER_VIEWER": "Hat die Leseberechtigung, das gesamte Projekt zu überprüfen", "PROJECT_OWNER_GLOBAL": "Hat die Berechtigung für das gesamte Projekt", @@ -1159,9 +1163,13 @@ "UPDATED": "Einstellungen geändert" }, "SECURITY": { - "DESCRIPTION": "Mit dieser Einstellung wird die CSP so eingestellt, dass Framing von einer Reihe zulässiger Domänen zugelassen wird. Beachten Sie, dass Sie durch die Aktivierung der Verwendung von iFrames das Risiko eingehen, Clickjacking zu ermöglichen.", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "Mit dieser Einstellung wird die CSP so eingestellt, dass Framing von einer Reihe zulässiger Domänen zugelassen wird. Beachten Sie, dass Sie durch die Aktivierung der Verwendung von iFrames das Risiko eingehen, Clickjacking zu ermöglichen.", "IFRAMEENABLED": "iFrame zulassen", - "ALLOWEDORIGINS": "Zulässige URLs" + "ALLOWEDORIGINS": "Zulässige URLs", + "IMPERSONATIONTITLE": "Identitätswechsel", + "IMPERSONATIONENABLED": "Identitätswechsel zulassen", + "IMPERSONATIONDESCRIPTION": "Diese Einstellung ermöglicht grundsätzlich die Verwendung von Identitätswechseln. Beachten Sie, dass dem Imitator auch die entsprechenden `*_IMPERSONATOR`-Rollen zugewiesen werden müssen." }, "DIALOG": { "RESET": { diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index ec475db3b8a..0e611c23dec 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -188,12 +188,16 @@ "IAM_OWNER_VIEWER": "Has permission to review the whole instance, including all organizations", "IAM_ORG_MANAGER": "Has permission to create and manage organizations", "IAM_USER_MANAGER": "Has permission to create and manage users", + "IAM_ADMIN_IMPERSONATOR": "Has permission to impersonate admin and end users from all organizations", + "IAM_END_USER_IMPERSONATOR": "Has permission to impersonate end users from all organizations", "ORG_OWNER": "Has permission over the whole organization", "ORG_USER_MANAGER": "Has permission to create and manage users of the organization", "ORG_OWNER_VIEWER": "Has permission to review the whole organization", "ORG_USER_PERMISSION_EDITOR": "Has permission to manage user grants", "ORG_PROJECT_PERMISSION_EDITOR": "Has permission to manage project grants", "ORG_PROJECT_CREATOR": "Has permission to create his own projects and underlying settings", + "ORG_ADMIN_IMPERSONATOR": "Has permission to impersonate admin and end users from the organization", + "ORG_END_USER_IMPERSONATOR": "Has permission to impersonate end users from the organization", "PROJECT_OWNER": "Has permission over the whole project", "PROJECT_OWNER_VIEWER": "Has permission to review the whole project", "PROJECT_OWNER_GLOBAL": "Has permission over the whole project", @@ -1160,9 +1164,13 @@ "UPDATED": "Settings updated." }, "SECURITY": { - "DESCRIPTION": "This setting sets the CSP to allow framing from a set of allowed domains. Note that by enabling the use of iFrames, you run the risk of allowing clickjacking.", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "This setting sets the CSP to allow framing from a set of allowed domains. Note that by enabling the use of iFrames, you run the risk of allowing clickjacking.", "IFRAMEENABLED": "Allow iFrame", - "ALLOWEDORIGINS": "Allowed URLs" + "ALLOWEDORIGINS": "Allowed URLs", + "IMPERSONATIONTITLE": "Impersonation", + "IMPERSONATIONENABLED": "Allow Impersonation", + "IMPERSONATIONDESCRIPTION": "This setting allows to use impersonation in principle. Note that the impersonator needs the appropriate `*_IMPERSONATOR` roles assigned as well." }, "DIALOG": { "RESET": { diff --git a/console/src/assets/i18n/es.json b/console/src/assets/i18n/es.json index 84d71a3a95c..2fe15802c7e 100644 --- a/console/src/assets/i18n/es.json +++ b/console/src/assets/i18n/es.json @@ -188,12 +188,16 @@ "IAM_OWNER_VIEWER": "Tiene permiso para revisar toda la instancia, incluyendo todas las organizaciones", "IAM_ORG_MANAGER": "Tiene permiso para crear y gestionar organizaciones", "IAM_USER_MANAGER": "Tiene permiso para crear y gestionar usuarios", + "IAM_ADMIN_IMPERSONATOR": "Tiene permiso para hacerse pasar por administradores y usuarios finales de todas las organizaciones", + "IAM_END_USER_IMPERSONATOR": "Tiene permiso para hacerse pasar por usuarios finales de todas las organizaciones", "ORG_OWNER": "Tiene permisos sobre toda la organización", "ORG_USER_MANAGER": "Tiene permiso para crear y gestionar usuarios de la organización", "ORG_OWNER_VIEWER": "TIene permiso para revisar toda la organización", "ORG_USER_PERMISSION_EDITOR": "Tiene permiso para gestionar concesiones de usuario", "ORG_PROJECT_PERMISSION_EDITOR": "Tiene permiso para gestionar concesiones de proyecto", "ORG_PROJECT_CREATOR": "Tiene permiso para crear sus propios proyectos y ajustes subyacentes", + "ORG_ADMIN_IMPERSONATOR": "Tiene permiso para hacerse pasar por administradores y usuarios finales de la organización", + "ORG_END_USER_IMPERSONATOR": "Tiene permiso para hacerse pasar por usuarios finales de la organización", "PROJECT_OWNER": "Tiene permiso sobre todo el proyecto", "PROJECT_OWNER_VIEWER": "Tiene permiso para revisar todo el proyecto", "PROJECT_OWNER_GLOBAL": "Tiene permiso sobre todo el proyecto", @@ -1161,9 +1165,13 @@ "UPDATED": "Ajustes actualizados." }, "SECURITY": { - "DESCRIPTION": "Este ajuste establece el CSP para permitir el uso de frames para un grupo de dominios permitidos. Ten en cuenta que habilitando el uso de iFrames, corres el riesgo de permitir ataques de clickjacking.", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "Este ajuste establece el CSP para permitir el uso de frames para un grupo de dominios permitidos. Ten en cuenta que habilitando el uso de iFrames, corres el riesgo de permitir ataques de clickjacking.", "IFRAMEENABLED": "Permitir iFrame", - "ALLOWEDORIGINS": "URLs permitidas" + "ALLOWEDORIGINS": "URLs permitidas", + "IMPERSONATIONTITLE": "Suplantación", + "IMPERSONATIONENABLED": "Permitir suplantación", + "IMPERSONATIONDESCRIPTION": "Esta configuración permite utilizar la suplantación en principio. Tenga en cuenta que el imitador también necesita que se le asignen los roles `*_IMPERSONATOR` apropiados." }, "DIALOG": { "RESET": { diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index 4e61fdf75a0..14e09e8784c 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -187,12 +187,16 @@ "IAM_OWNER_VIEWER": "A le droit de passer en revue l'ensemble de l'instance, y compris toutes les organisations.", "IAM_ORG_MANAGER": "A le droit de créer et de gérer des organisations", "IAM_USER_MANAGER": "A le droit de créer et de gérer les utilisateurs", + "IAM_ADMIN_IMPERSONATOR": "A l'autorisation de se faire passer pour l'administrateur et les utilisateurs finaux de toutes les organisations", + "IAM_END_USER_IMPERSONATOR": "Est autorisé à usurper l'identité des utilisateurs finaux de toutes les organisations", "ORG_OWNER": "A le droit de contrôler l'ensemble de l'organisation", "ORG_USER_MANAGER": "A le droit de créer et de gérer les utilisateurs de l'organisation", "ORG_OWNER_VIEWER": "A le droit de passer en revue l'ensemble de l'organisation", "ORG_USER_PERMISSION_EDITOR": "A le droit de gérer les subventions aux utilisateurs", "ORG_PROJECT_PERMISSION_EDITOR": "A le droit de gérer les subventions aux projets", "ORG_PROJECT_CREATOR": "A le droit de créer ses propres projets et leurs paramètres sous-jacents.", + "ORG_ADMIN_IMPERSONATOR": "A l'autorisation de se faire passer pour l'administrateur et les utilisateurs finaux de l'organisation", + "ORG_END_USER_IMPERSONATOR": "Est autorisé à usurper l'identité des utilisateurs finaux de l'organisation", "PROJECT_OWNER": "A le droit de gérer l'ensemble du projet", "PROJECT_OWNER_VIEWER": "A le droit de passer en revue l'ensemble du projet", "PROJECT_OWNER_GLOBAL": "A le droit d'accéder à l'ensemble du projet", @@ -1159,9 +1163,13 @@ "UPDATED": "Paramètres mis à jour." }, "SECURITY": { - "DESCRIPTION": "Ce paramètre permet au CSP d'autoriser les iFrames à partir d'un ensemble de domaines autorisés. Notez qu'en autorisant l'utilisation des iFrames, vous courez le risque d'autoriser le clickjacking.", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "Ce paramètre permet au CSP d'autoriser les iFrames à partir d'un ensemble de domaines autorisés. Notez qu'en autorisant l'utilisation des iFrames, vous courez le risque d'autoriser le clickjacking.", "IFRAMEENABLED": "Autoriser iFrame", - "ALLOWEDORIGINS": "URL d'origine autorisées" + "ALLOWEDORIGINS": "URL d'origine autorisées", + "IMPERSONATIONTITLE": "Usuration d'identité", + "IMPERSONATIONENABLED": "Autoriser l'usurpation d'identité", + "IMPERSONATIONDESCRIPTION": "Ce paramètre permet en principe d'utiliser l'usurpation d'identité. Notez que l'usurpateur d'identité doit également recevoir les rôles `*_IMPERSONATOR` appropriés." }, "DIALOG": { "RESET": { diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index 9650749548e..97ee129f72d 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -186,12 +186,16 @@ "IAM_OWNER_VIEWER": "Ha l'autorizzazione per esaminare l'intera istanza, comprese tutte le organizzazioni", "IAM_ORG_MANAGER": "Ha il permesso di creare e gestire organizzazioni", "IAM_USER_MANAGER": "Ha l'autorizzazione per creare e gestire utenti", + "IAM_ADMIN_IMPERSONATOR": "Dispone dell'autorizzazione per rappresentare l'amministratore e gli utenti finali di tutte le organizzazioni", + "IAM_END_USER_IMPERSONATOR": "Dispone dell'autorizzazione per rappresentare gli utenti finali di tutte le organizzazioni", "ORG_OWNER": "Ha il permesso su tutta l'organizzazione", "ORG_USER_MANAGER": "Ha l'autorizzazione per creare e gestire gli utenti dell'organizzazione", "ORG_OWNER_VIEWER": "Ha il permesso di esaminare l'intera organizzazione", "ORG_USER_PERMISSION_EDITOR": "Ha l'autorizzazione per gestire le autorizzazioni degli utenti", "ORG_PROJECT_PERMISSION_EDITOR": "Ha il permesso di gestire le sovvenzioni di progetto (Project Grant)", "ORG_PROJECT_CREATOR": "Ha il permesso di creare propri progetti e le impostazioni sottostanti", + "ORG_ADMIN_IMPERSONATOR": "Ha il permesso per rappresentare l'amministratore e gli utenti finali dell'organizzazione", + "ORG_END_USER_IMPERSONATOR": "Ha il permesso per rappresentare gli utenti finali dell'organizzazione", "PROJECT_OWNER": "Ha il permesso per l'intero progetto", "PROJECT_OWNER_VIEWER": "Ha il permesso di esaminare l'intero progetto", "PROJECT_OWNER_GLOBAL": "Ha il permesso per l'intero progetto", @@ -1159,9 +1163,13 @@ "UPDATED": "Impostazioni aggiornati" }, "SECURITY": { - "DESCRIPTION": "Questa impostazione consente al CSP di consentire il framing da un insieme di domini consentiti. Si noti che abilitando l'uso di iFrames, si corre il rischio di consentire il clickjacking.", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "Questa impostazione consente al CSP di consentire il framing da un insieme di domini consentiti. Si noti che abilitando l'uso di iFrames, si corre il rischio di consentire il clickjacking.", "IFRAMEENABLED": "I Frame enabled", - "ALLOWEDORIGINS": "URL consentiti" + "ALLOWEDORIGINS": "URL consentiti", + "IMPERSONATIONTITLE": "Impersonificazione", + "IMPERSONATIONENABLED": "Consenti la rappresentazione", + "IMPERSONATIONDESCRIPTION": "Questa impostazione consente in linea di principio di utilizzare la rappresentazione. Tieni presente che il sosia ha bisogno anche dei ruoli `*_IMPERSONATOR` appropriati assegnati." }, "DIALOG": { "RESET": { diff --git a/console/src/assets/i18n/ja.json b/console/src/assets/i18n/ja.json index 5de4521a106..838ed748571 100644 --- a/console/src/assets/i18n/ja.json +++ b/console/src/assets/i18n/ja.json @@ -188,12 +188,16 @@ "IAM_OWNER_VIEWER": "すべての組織を含むインスタンス全体を閲覧する権限を持ちます", "IAM_ORG_MANAGER": "組織の作成および管理する権限を持ちます", "IAM_USER_MANAGER": "ユーザーの作成および管理する権限を持ちます", + "IAM_ADMIN_IMPERSONATOR": "すべての組織の管理者およびエンドユーザーになりすます権限を持っています", + "IAM_END_USER_IMPERSONATOR": "すべての組織のエンドユーザーになりすます権限を持っています", "ORG_OWNER": "組織全体に対する権限を持ちます", "ORG_USER_MANAGER": "組織のユーザーを作成および管理する権限を持ちます", "ORG_OWNER_VIEWER": "組織全体を閲覧する権限を持ちます", "ORG_USER_PERMISSION_EDITOR": "ユーザーグラントを管理する権限を持ちます", "ORG_PROJECT_PERMISSION_EDITOR": "プロジェクトグラントを管理する権限を持ちます", "ORG_PROJECT_CREATOR": "所有するプロジェクトと配下の設定を作成する権限を持ちます", + "ORG_ADMIN_IMPERSONATOR": "組織の管理者およびエンドユーザーになりすます権限がある", + "ORG_END_USER_IMPERSONATOR": "組織のエンドユーザーになりすます権限がある", "PROJECT_OWNER": "特定のプロジェクト全体を管理する権限を持ちます", "PROJECT_OWNER_VIEWER": "特定のプロジェクト全体を閲覧する権限を持ちます", "PROJECT_OWNER_GLOBAL": "全てのプロジェクトを管理する権限を持ちます", @@ -1160,9 +1164,13 @@ "UPDATED": "設定が更新されました。" }, "SECURITY": { - "DESCRIPTION": "この設定は、許可されたドメインのセットからのフレーミングを許可するように CSP を設定します。iFrameの使用を有効にすると、クリックジャッキングが許可される危険性があることに注意してください。", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "この設定は、許可されたドメインのセットからのフレーミングを許可するように CSP を設定します。iFrameの使用を有効にすると、クリックジャッキングが許可される危険性があることに注意してください。", "IFRAMEENABLED": "iFrameを許可する", - "ALLOWEDORIGINS": "許可されたURL" + "ALLOWEDORIGINS": "許可されたURL", + "IMPERSONATIONTITLE": "偽装", + "IMPERSONATIONENABLED": "偽装を許可します", + "IMPERSONATIONDESCRIPTION": "この設定では、原則として偽装を使用できます。偽装者には、適切な `*_IMPERSONATOR` ロールも割り当てられている必要があることに注意してください。" }, "DIALOG": { "RESET": { diff --git a/console/src/assets/i18n/mk.json b/console/src/assets/i18n/mk.json index 91f08126bcd..fd8ace082ca 100644 --- a/console/src/assets/i18n/mk.json +++ b/console/src/assets/i18n/mk.json @@ -188,12 +188,16 @@ "IAM_OWNER_VIEWER": "Има дозвола за преглед на целата инстанца, вклучувајќи ги сите организации", "IAM_ORG_MANAGER": "Има дозвола за креирање и менаџирање на организации", "IAM_USER_MANAGER": "Има дозвола за креирање и менаџирање на корисници", + "IAM_ADMIN_IMPERSONATOR": "Има дозвола да се претставува како администратор и крајни корисници од сите организации", + "IAM_END_USER_IMPERSONATOR": "Има дозвола да ги имитира крајните корисници од сите организации", "ORG_OWNER": "Има дозвола врз целата организација", "ORG_USER_MANAGER": "Има дозвола за креирање и менаџирање на корисници во организацијата", "ORG_OWNER_VIEWER": "Има дозвола за преглед на целата организација", "ORG_USER_PERMISSION_EDITOR": "Има дозвола за менаџирање на овластувања на корисници", "ORG_PROJECT_PERMISSION_EDITOR": "Има дозвола за менаџирање на овластувања на проекти", "ORG_PROJECT_CREATOR": "Има дозвола за креирање на сопствени проекти и нивни подесувања", + "ORG_ADMIN_IMPERSONATOR": "Има дозвола да имитира администратор и крајни корисници од организацијата", + "ORG_END_USER_IMPERSONATOR": "Има дозвола да ги имитира крајните корисници од организацијата", "PROJECT_OWNER": "Има дозвола врз целиот проект", "PROJECT_OWNER_VIEWER": "Има дозвола за преглед на целиот проект", "PROJECT_OWNER_GLOBAL": "Има дозвола врз целиот проект", @@ -1161,9 +1165,13 @@ "UPDATED": "Подесувањата се успешно ажурирани." }, "SECURITY": { - "DESCRIPTION": "Ова подесување поставува CSP за дозволување на фрејминг од одредени дозволени домени. Имајте предвид дека овозможувањето на употребата на iFrame-ови може да ви изложи на ризик од clickjacking.", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "Ова подесување поставува CSP за дозволување на фрејминг од одредени дозволени домени. Имајте предвид дека овозможувањето на употребата на iFrame-ови може да ви изложи на ризик од clickjacking.", "IFRAMEENABLED": "Овозможи iFrame", - "ALLOWEDORIGINS": "Дозволени URLs" + "ALLOWEDORIGINS": "Дозволени URLs", + "IMPERSONATIONTITLE": "Имитирање", + "IMPERSONATIONENABLED": "Дозволи имитирање", + "IMPERSONATIONDESCRIPTION": "Оваа поставка овозможува да се користи имитирање во принцип. Имајте предвид дека на имитаторот му требаат доделени соодветни улоги `*_IMPERSONATOR`" }, "DIALOG": { "RESET": { diff --git a/console/src/assets/i18n/nl.json b/console/src/assets/i18n/nl.json index 55e4acd9da5..615e88d6f61 100644 --- a/console/src/assets/i18n/nl.json +++ b/console/src/assets/i18n/nl.json @@ -188,12 +188,16 @@ "IAM_OWNER_VIEWER": "Heeft toestemming om de hele instantie te bekijken, inclusief alle organisaties", "IAM_ORG_MANAGER": "Heeft toestemming om organisaties aan te maken en te beheren", "IAM_USER_MANAGER": "Heeft toestemming om gebruikers aan te maken en te beheren", + "IAM_ADMIN_IMPERSONATOR": "Heeft toestemming om zich voor te doen als beheerder en eindgebruikers van alle organisaties", + "IAM_END_USER_IMPERSONATOR": "Heeft toestemming om eindgebruikers van alle organisaties na te bootsen", "ORG_OWNER": "Heeft toestemming over de hele organisatie", "ORG_USER_MANAGER": "Heeft toestemming om gebruikers van de organisatie aan te maken en te beheren", "ORG_OWNER_VIEWER": "Heeft toestemming om de hele organisatie te bekijken", "ORG_USER_PERMISSION_EDITOR": "Heeft toestemming om gebruikerstoegang te beheren", "ORG_PROJECT_PERMISSION_EDITOR": "Heeft toestemming om projecttoegang te beheren", "ORG_PROJECT_CREATOR": "Heeft toestemming om zijn eigen projecten en onderliggende instellingen aan te maken", + "ORG_ADMIN_IMPERSONATOR": "Heeft toestemming om de beheerder en eindgebruikers van de organisatie na te bootsen", + "ORG_END_USER_IMPERSONATOR": "Heeft toestemming om eindgebruikers van de organisatie na te bootsen", "PROJECT_OWNER": "Heeft toestemming over het hele project", "PROJECT_OWNER_VIEWER": "Heeft toestemming om het hele project te bekijken", "PROJECT_OWNER_GLOBAL": "Heeft toestemming over het hele project", @@ -1160,9 +1164,13 @@ "UPDATED": "Instellingen bijgewerkt." }, "SECURITY": { - "DESCRIPTION": "Deze instelling stelt de CSP in om framing van een reeks toegestane domeinen toe te staan. Let op: door het gebruik van iFrames toe te staan, loopt u het risico om clickjacking toe te staan.", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "Deze instelling stelt de CSP in om framing van een reeks toegestane domeinen toe te staan. Let op: door het gebruik van iFrames toe te staan, loopt u het risico om clickjacking toe te staan.", "IFRAMEENABLED": "Sta iFrame toe", - "ALLOWEDORIGINS": "Toegestane URLs" + "ALLOWEDORIGINS": "Toegestane URLs", + "IMPERSONATIONTITLE": "Imitatie", + "IMPERSONATIONENABLED": "Imitatie toestaan", + "IMPERSONATIONDESCRIPTION": "Deze instelling maakt het in principe mogelijk om imitatie te gebruiken. Houd er rekening mee dat aan de imitator ook de juiste `*_IMPERSONATOR` rollen moeten worden toegewezen." }, "DIALOG": { "RESET": { diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json index d01d279f7ad..fd20e13efc8 100644 --- a/console/src/assets/i18n/pl.json +++ b/console/src/assets/i18n/pl.json @@ -187,12 +187,16 @@ "IAM_OWNER_VIEWER": "Ma uprawnienie do przeglądania całej instancji, włącznie z wszystkimi organizacjami", "IAM_ORG_MANAGER": "Ma uprawnienie do tworzenia i zarządzania organizacjami", "IAM_USER_MANAGER": "Ma uprawnienie do tworzenia i zarządzania użytkownikami", + "IAM_ADMIN_IMPERSONATOR": "Ma uprawnienia do podszywania się pod administratora i użytkowników końcowych ze wszystkich organizacji", + "IAM_END_USER_IMPERSONATOR": "Ma uprawnienia do podszywania się pod użytkowników końcowych ze wszystkich organizacji", "ORG_OWNER": "Ma uprawnienie nad całą organizacją", "ORG_USER_MANAGER": "Ma uprawnienie do tworzenia i zarządzania użytkownikami organizacji", "ORG_OWNER_VIEWER": "Ma uprawnienie do przeglądania całej organizacji", "ORG_USER_PERMISSION_EDITOR": "Ma uprawnienie do zarządzania uprawnieniami użytkowników", "ORG_PROJECT_PERMISSION_EDITOR": "Ma uprawnienie do zarządzania uprawnieniami projektu", "ORG_PROJECT_CREATOR": "Ma uprawnienie do tworzenia własnych projektów i podstawowych ustawień", + "ORG_ADMIN_IMPERSONATOR": "Ma uprawnienia do podszywania się pod administratora i użytkowników końcowych z organizacji", + "ORG_END_USER_IMPERSONATOR": "Ma uprawnienia do podszywania się pod użytkowników końcowych z organizacji", "PROJECT_OWNER": "Ma uprawnienie nad całym projektem", "PROJECT_OWNER_VIEWER": "Ma uprawnienie do przeglądania całego projektu", "PROJECT_OWNER_GLOBAL": "Ma uprawnienia do całego projektu", @@ -1159,9 +1163,13 @@ "UPDATED": "Ustawienia zaktualizowane." }, "SECURITY": { - "DESCRIPTION": "To ustawienie ustawia CSP, aby pozwalało na osadzanie ramki z zestawu dozwolonych domen. Należy pamiętać, że włączenie używania iFrame oznacza ryzyko pozwolenia na clickjacking.", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "To ustawienie ustawia CSP, aby pozwalało na osadzanie ramki z zestawu dozwolonych domen. Należy pamiętać, że włączenie używania iFrame oznacza ryzyko pozwolenia na clickjacking.", "IFRAMEENABLED": "Zezwól na iFrame", - "ALLOWEDORIGINS": "Dozwolone adresy URL" + "ALLOWEDORIGINS": "Dozwolone adresy URL", + "IMPERSONATIONTITLE": "Podszywanie się", + "IMPERSONATIONENABLED": "Zezwalaj na podszywanie się", + "IMPERSONATIONDESCRIPTION": "To ustawienie pozwala w zasadzie na użycie personifikacji. Należy pamiętać, że osoba personifikująca potrzebuje również przypisanych odpowiednich ról `*_IMPERSONATOR`." }, "DIALOG": { "RESET": { diff --git a/console/src/assets/i18n/pt.json b/console/src/assets/i18n/pt.json index 3528222c413..52764d4b95e 100644 --- a/console/src/assets/i18n/pt.json +++ b/console/src/assets/i18n/pt.json @@ -188,12 +188,16 @@ "IAM_OWNER_VIEWER": "Tem permissão para revisar toda a instância, incluindo todas as organizações", "IAM_ORG_MANAGER": "Tem permissão para criar e gerenciar organizações", "IAM_USER_MANAGER": "Tem permissão para criar e gerenciar usuários", + "IAM_ADMIN_IMPERSONATOR": "Tem permissão para se passar por administradores e usuários finais de todas as organizações", + "IAM_END_USER_IMPERSONATOR": "Tem permissão para se passar por usuários finais de todas as organizações", "ORG_OWNER": "Tem permissão sobre toda a organização", "ORG_USER_MANAGER": "Tem permissão para criar e gerenciar usuários da organização", "ORG_OWNER_VIEWER": "Tem permissão para revisar toda a organização", "ORG_USER_PERMISSION_EDITOR": "Tem permissão para gerenciar concessões de usuários", "ORG_PROJECT_PERMISSION_EDITOR": "Tem permissão para gerenciar concessões de projetos", "ORG_PROJECT_CREATOR": "Tem permissão para criar seus próprios projetos e configurações subjacentes", + "ORG_ADMIN_IMPERSONATOR": "Tem permissão para se passar por administradores e usuários finais da organização", + "ORG_END_USER_IMPERSONATOR": "Tem permissão para se passar por usuários finais da organização", "PROJECT_OWNER": "Tem permissão sobre todo o projeto", "PROJECT_OWNER_VIEWER": "Tem permissão para revisar todo o projeto", "PROJECT_OWNER_GLOBAL": "Tem permissão sobre todo o projeto", @@ -1161,9 +1165,13 @@ "UPDATED": "Configurações atualizadas." }, "SECURITY": { - "DESCRIPTION": "Essa configuração define o CSP para permitir o enquadramento de um conjunto de domínios permitidos. Observe que, ao permitir o uso de iFrames, você corre o risco de permitir ataques de clickjacking.", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "Essa configuração define o CSP para permitir o enquadramento de um conjunto de domínios permitidos. Observe que, ao permitir o uso de iFrames, você corre o risco de permitir ataques de clickjacking.", "IFRAMEENABLED": "Permitir iFrame", - "ALLOWEDORIGINS": "URLs permitidos" + "ALLOWEDORIGINS": "URLs permitidos", + "IMPERSONATIONTITLE": "Personificação", + "IMPERSONATIONENABLED": "Permitir representação", + "IMPERSONATIONDESCRIPTION": "Esta configuração permite usar a representação em princípio. Observe que o imitador também precisa das funções `*_IMPERSONATOR` apropriadas atribuídas." }, "DIALOG": { "RESET": { diff --git a/console/src/assets/i18n/ru.json b/console/src/assets/i18n/ru.json index 3c82142cd76..c3f1d76f64f 100644 --- a/console/src/assets/i18n/ru.json +++ b/console/src/assets/i18n/ru.json @@ -184,12 +184,16 @@ "IAM_OWNER_VIEWER": "Имеет разрешение на проверку всего экземпляра, включая все организации.", "IAM_ORG_MANAGER": "Имеет разрешение на создание и управление организациями", "IAM_USER_MANAGER": "Имеет разрешение на создание пользователей и управление ими.", + "IAM_ADMIN_IMPERSONATOR": "Имеет разрешение выдавать себя за администратора и конечных пользователей из всех организаций", + "IAM_END_USER_IMPERSONATOR": "Имеет разрешение выдавать себя за конечных пользователей из всех организаций", "ORG_OWNER": "Имеет разрешение на всю организацию", "ORG_USER_MANAGER": "Имеет разрешение на создание пользователей организации и управление ими.", "ORG_OWNER_VIEWER": "Имеет разрешение на проверку всей организации", "ORG_USER_PERMISSION_EDITOR": "Имеет разрешение на управление разрешениями пользователей.", "ORG_PROJECT_PERMISSION_EDITOR": "Имеет разрешение на управление разрешениями проекта", "ORG_PROJECT_CREATOR": "Имеет разрешение на создание собственных проектов и базовых настроек.", + "ORG_ADMIN_IMPERSONATOR": "Имеет разрешение выдавать себя за администратора и конечных пользователей организации", + "ORG_END_USER_IMPERSONATOR": "Имеет разрешение выдавать себя за конечных пользователей организации", "PROJECT_OWNER": "Имеет разрешение на весь проект", "PROJECT_OWNER_VIEWER": "Имеет разрешение на проверку всего проекта", "PROJECT_OWNER_GLOBAL": "Имеет разрешение на весь проект", @@ -1151,9 +1155,13 @@ "UPDATED": "Настройки обновлены." }, "SECURITY": { - "DESCRIPTION": "Этот параметр разрешает встраивание окон через iframe для списка разрешенных доменов. Обратите внимание: разрешив встраивание окон, вы рискуете подвергнуть прилужение атакам тима clickjacking.", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "Этот параметр разрешает встраивание окон через iframe для списка разрешенных доменов. Обратите внимание: разрешив встраивание окон, вы рискуете подвергнуть прилужение атакам тима clickjacking.", "IFRAMEENABLED": "Разрешить iframe", - "ALLOWEDORIGINS": "Разрешенные URL-адреса" + "ALLOWEDORIGINS": "Разрешенные URL-адреса", + "IMPERSONATIONTITLE": "Олицетворение", + "IMPERSONATIONENABLED": "Разрешить олицетворение", + "IMPERSONATIONDESCRIPTION": "Этот параметр позволяет в принципе использовать олицетворение. Обратите внимание, что имитатору также необходимо назначить соответствующие роли `*_IMPERSONATOR`." }, "DIALOG": { "RESET": { diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index 04f1a8d0531..f7742d9bfe9 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -187,12 +187,16 @@ "IAM_OWNER_VIEWER": "有权审查整个实例,包括所有组织", "IAM_ORG_MANAGER": "有权创建和管理组织", "IAM_USER_MANAGER": "有权创建和管理用户", + "IAM_ADMIN_IMPERSONATOR": "有权模拟所有组织的管理员和最终用户", + "IAM_END_USER_IMPERSONATOR": "有权模拟所有组织的最终用户", "ORG_OWNER": "拥有整个组织的权限", "ORG_USER_MANAGER": "有权创建和管理组织的用户", "ORG_OWNER_VIEWER": "有权审查整个组织", "ORG_USER_PERMISSION_EDITOR": "有权管理用户授权", "ORG_PROJECT_PERMISSION_EDITOR": "有权管理项目授权", "ORG_PROJECT_CREATOR": "有权创建自己的项目和基础设置", + "ORG_ADMIN_IMPERSONATOR": "有权模拟组织的管理员和最终用户", + "ORG_END_USER_IMPERSONATOR": "有权模拟组织的最终用户", "PROJECT_OWNER": "拥有整个项目的权限", "PROJECT_OWNER_VIEWER": "有权审查整个项目", "PROJECT_OWNER_GLOBAL": "拥有整个项目的权限", @@ -1159,9 +1163,13 @@ "UPDATED": "设置已更新。" }, "SECURITY": { - "DESCRIPTION": "此设置将CSP设置为允许来自一组允许的域的框架。请注意,通过启用iFrames的使用,你会有允许点击劫持的风险。", + "IFRAMETITLE": "iFrame", + "IFRAMEDESCRIPTION": "此设置将CSP设置为允许来自一组允许的域的框架。请注意,通过启用iFrames的使用,你会有允许点击劫持的风险。", "IFRAMEENABLED": "允许 iFrame", - "ALLOWEDORIGINS": "允许的来源 URL" + "ALLOWEDORIGINS": "允许的来源 URL", + "IMPERSONATIONTITLE": "冒充", + "IMPERSONATIONENABLED": "允许模拟", + "IMPERSONATIONDESCRIPTION": "此设置原则上允许使用模拟。请注意,模拟者还需要分配适当的 `*_IMPERSONATOR` 角色。" }, "DIALOG": { "RESET": { diff --git a/docs/docs/guides/manage/console/managers.mdx b/docs/docs/guides/manage/console/managers.mdx index f71bc5698db..59d4a02bfbe 100644 --- a/docs/docs/guides/manage/console/managers.mdx +++ b/docs/docs/guides/manage/console/managers.mdx @@ -16,7 +16,6 @@ import AddManager from "./_add_manager.mdx"; - ## Roles | Name | Role | Description | @@ -25,25 +24,29 @@ import AddManager from "./_add_manager.mdx"; | IAM Owner Viewer | IAM_OWNER_VIEWER | View the IAM and view all organizations with their content | | IAM Org Manager | IAM_ORG_MANAGER | Manage all organizations including their policies, projects and users | | IAM User Manager | IAM_USER_MANAGER | Manage all users and their authorizations over all organizations | +| IAM Admin Impersonator | IAM_ADMIN_IMPERSONATOR | Allow impersonation of admin and end users from all organizations | +| IAM Impersonator | IAM_END_USER_IMPERSONATOR | Allow impersonation of end users from all organizations | | Org Owner | ORG_OWNER | Manage everything within an organization | | Org Owner Viewer | ORG_OWNER_VIEWER | View everything within an organization | | Org User Manager | ORG_USER_MANAGER | Manage users and their authorizations within an organization | | Org User Permission Editor | ORG_USER_PERMISSION_EDITOR | Manage user grants and view everything needed for this | | Org Project Permission Editor | ORG_PROJECT_PERMISSION_EDITOR | Grant Projects to other organizations and view everything needed for this | | Org Project Creator | ORG_PROJECT_CREATOR | This role is used for users in the global organization. They are allowed to create projects and manage them. | +| Org Admin Impersonator | ORG_ADMIN_IMPERSONATOR | Allow impersonation of admin and end users from the organization | +| Org Impersonator | ORG_END_USER_IMPERSONATOR | Allow impersonation of end users from the organization | | Project Owner | PROJECT_OWNER | Manage everything within a project. This includes to grant users for the project. | | Project Owner Viewer | PROJECT_OWNER_VIEWER | View everything within a project. | | Project Owner Global | PROJECT_OWNER_GLOBAL | Same as PROJECT_OWNER, but in the global organization. | | Project Owner Viewer Global | PROJECT_OWNER_VIEWER_GLOBAL | Same as PROJECT_OWNER_VIEWER, but in the global organization. | | Project Grant Owner | PROJECT_GRANT_OWNER | Same as PROJECT_OWNER but for a granted proejct. | - ## Configure roles If you run a self hosted ZITADEL instance you can define your custom roles by overwriting the defaults.yaml In the InternalAuthZ section you will find all the roles and which permissions they have. Example: + ```bash InternalAuthZ: RolePermissionMappings: diff --git a/internal/api/authz/instance.go b/internal/api/authz/instance.go index 3dfd88e8927..18767a3151e 100644 --- a/internal/api/authz/instance.go +++ b/internal/api/authz/instance.go @@ -23,6 +23,7 @@ type Instance interface { DefaultLanguage() language.Tag DefaultOrganisationID() string SecurityPolicyAllowedOrigins() []string + EnableImpersonation() bool Block() *bool AuditLogRetention() *time.Duration Features() feature.Features @@ -87,6 +88,10 @@ func (i *instance) SecurityPolicyAllowedOrigins() []string { return nil } +func (i *instance) EnableImpersonation() bool { + return false +} + func (i *instance) Features() feature.Features { return i.features } diff --git a/internal/api/authz/instance_test.go b/internal/api/authz/instance_test.go index 7057a18293a..c6cac09d4b7 100644 --- a/internal/api/authz/instance_test.go +++ b/internal/api/authz/instance_test.go @@ -130,6 +130,10 @@ func (m *mockInstance) SecurityPolicyAllowedOrigins() []string { return nil } +func (m *mockInstance) EnableImpersonation() bool { + return false +} + func (m *mockInstance) Features() feature.Features { return feature.Features{} } diff --git a/internal/api/grpc/admin/iam_member_integration_test.go b/internal/api/grpc/admin/iam_member_integration_test.go new file mode 100644 index 00000000000..12f4f321c00 --- /dev/null +++ b/internal/api/grpc/admin/iam_member_integration_test.go @@ -0,0 +1,322 @@ +//go:build integration + +package admin_test + +import ( + "context" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zitadel/zitadel/internal/integration" + admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" + "github.com/zitadel/zitadel/pkg/grpc/member" + "github.com/zitadel/zitadel/pkg/grpc/object" +) + +var iamRoles = []string{ + "IAM_OWNER", + "IAM_OWNER_VIEWER", + "IAM_ORG_MANAGER", + "IAM_USER_MANAGER", + "IAM_ADMIN_IMPERSONATOR", + "IAM_END_USER_IMPERSONATOR", +} + +func TestServer_ListIAMMemberRoles(t *testing.T) { + got, err := Client.ListIAMMemberRoles(AdminCTX, &admin_pb.ListIAMMemberRolesRequest{}) + require.NoError(t, err) + assert.ElementsMatch(t, iamRoles, got.GetRoles()) +} + +func TestServer_ListIAMMembers(t *testing.T) { + user := Tester.CreateHumanUserVerified(AdminCTX, Tester.Organisation.ID, gofakeit.Email()) + _, err := Client.AddIAMMember(AdminCTX, &admin_pb.AddIAMMemberRequest{ + UserId: user.GetUserId(), + Roles: iamRoles, + }) + require.NoError(t, err) + type args struct { + ctx context.Context + req *admin_pb.ListIAMMembersRequest + } + tests := []struct { + name string + args args + want *admin_pb.ListIAMMembersResponse + wantErr bool + }{ + { + name: "permission error", + args: args{ + ctx: Tester.WithAuthorization(CTX, integration.OrgOwner), + req: &admin_pb.ListIAMMembersRequest{ + Query: &object.ListQuery{}, + Queries: []*member.SearchQuery{{ + Query: &member.SearchQuery_UserIdQuery{ + UserIdQuery: &member.UserIDQuery{ + UserId: user.GetUserId(), + }, + }, + }}, + }, + }, + wantErr: true, + }, + { + name: "success", + args: args{ + ctx: AdminCTX, + req: &admin_pb.ListIAMMembersRequest{ + Query: &object.ListQuery{}, + Queries: []*member.SearchQuery{{ + Query: &member.SearchQuery_UserIdQuery{ + UserIdQuery: &member.UserIDQuery{ + UserId: user.GetUserId(), + }, + }, + }}, + }, + }, + want: &admin_pb.ListIAMMembersResponse{ + Result: []*member.Member{{ + UserId: user.GetUserId(), + Roles: iamRoles, + }}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Client.ListIAMMembers(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + wantResult := tt.want.GetResult() + gotResult := got.GetResult() + + require.Len(t, gotResult, len(wantResult)) + for i, want := range wantResult { + assert.Equal(t, want.GetUserId(), gotResult[i].GetUserId()) + assert.ElementsMatch(t, want.GetRoles(), gotResult[i].GetRoles()) + } + }) + } +} + +func TestServer_AddIAMMember(t *testing.T) { + user := Tester.CreateHumanUserVerified(AdminCTX, Tester.Organisation.ID, gofakeit.Email()) + type args struct { + ctx context.Context + req *admin_pb.AddIAMMemberRequest + } + tests := []struct { + name string + args args + want *admin_pb.AddIAMMemberResponse + wantErr bool + }{ + { + name: "permission error", + args: args{ + ctx: Tester.WithAuthorization(CTX, integration.OrgOwner), + req: &admin_pb.AddIAMMemberRequest{ + UserId: user.GetUserId(), + Roles: iamRoles, + }, + }, + wantErr: true, + }, + { + name: "success", + args: args{ + ctx: AdminCTX, + req: &admin_pb.AddIAMMemberRequest{ + UserId: user.GetUserId(), + Roles: iamRoles, + }, + }, + want: &admin_pb.AddIAMMemberResponse{ + Details: &object.ObjectDetails{ + ResourceOwner: Tester.Instance.InstanceID(), + }, + }, + }, + { + name: "unknown roles error", + args: args{ + ctx: AdminCTX, + req: &admin_pb.AddIAMMemberRequest{ + UserId: user.GetUserId(), + Roles: []string{"FOO", "BAR"}, + }, + }, + wantErr: true, + }, + { + name: "org role error", + args: args{ + ctx: AdminCTX, + req: &admin_pb.AddIAMMemberRequest{ + UserId: user.GetUserId(), + Roles: []string{"ORG_OWNER"}, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Client.AddIAMMember(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + integration.AssertDetails(t, tt.want, got) + }) + } +} + +func TestServer_UpdateIAMMember(t *testing.T) { + user := Tester.CreateHumanUserVerified(AdminCTX, Tester.Organisation.ID, gofakeit.Email()) + _, err := Client.AddIAMMember(AdminCTX, &admin_pb.AddIAMMemberRequest{ + UserId: user.GetUserId(), + Roles: []string{"IAM_OWNER"}, + }) + require.NoError(t, err) + + type args struct { + ctx context.Context + req *admin_pb.UpdateIAMMemberRequest + } + tests := []struct { + name string + args args + want *admin_pb.UpdateIAMMemberResponse + wantErr bool + }{ + { + name: "permission error", + args: args{ + ctx: Tester.WithAuthorization(CTX, integration.OrgOwner), + req: &admin_pb.UpdateIAMMemberRequest{ + UserId: user.GetUserId(), + Roles: iamRoles, + }, + }, + wantErr: true, + }, + { + name: "success", + args: args{ + ctx: AdminCTX, + req: &admin_pb.UpdateIAMMemberRequest{ + UserId: user.GetUserId(), + Roles: iamRoles, + }, + }, + want: &admin_pb.UpdateIAMMemberResponse{ + Details: &object.ObjectDetails{ + ResourceOwner: Tester.Instance.InstanceID(), + ChangeDate: timestamppb.Now(), + }, + }, + }, + { + name: "unknown roles error", + args: args{ + ctx: AdminCTX, + req: &admin_pb.UpdateIAMMemberRequest{ + UserId: user.GetUserId(), + Roles: []string{"FOO", "BAR"}, + }, + }, + wantErr: true, + }, + { + name: "org role error", + args: args{ + ctx: AdminCTX, + req: &admin_pb.UpdateIAMMemberRequest{ + UserId: user.GetUserId(), + Roles: []string{"ORG_OWNER"}, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Client.UpdateIAMMember(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + integration.AssertDetails(t, tt.want, got) + }) + } +} + +func TestServer_RemoveIAMMember(t *testing.T) { + user := Tester.CreateHumanUserVerified(AdminCTX, Tester.Organisation.ID, gofakeit.Email()) + _, err := Client.AddIAMMember(AdminCTX, &admin_pb.AddIAMMemberRequest{ + UserId: user.GetUserId(), + Roles: []string{"IAM_OWNER"}, + }) + require.NoError(t, err) + + type args struct { + ctx context.Context + req *admin_pb.RemoveIAMMemberRequest + } + tests := []struct { + name string + args args + want *admin_pb.RemoveIAMMemberResponse + wantErr bool + }{ + { + name: "permission error", + args: args{ + ctx: Tester.WithAuthorization(CTX, integration.OrgOwner), + req: &admin_pb.RemoveIAMMemberRequest{ + UserId: user.GetUserId(), + }, + }, + wantErr: true, + }, + { + name: "success", + args: args{ + ctx: AdminCTX, + req: &admin_pb.RemoveIAMMemberRequest{ + UserId: user.GetUserId(), + }, + }, + want: &admin_pb.RemoveIAMMemberResponse{ + Details: &object.ObjectDetails{ + ResourceOwner: Tester.Instance.InstanceID(), + ChangeDate: timestamppb.Now(), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Client.RemoveIAMMember(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + integration.AssertDetails(t, tt.want, got) + }) + } +} diff --git a/internal/api/grpc/admin/iam_settings.go b/internal/api/grpc/admin/iam_settings.go index 53a5969d142..dde4100c321 100644 --- a/internal/api/grpc/admin/iam_settings.go +++ b/internal/api/grpc/admin/iam_settings.go @@ -119,7 +119,7 @@ func (s *Server) GetSecurityPolicy(ctx context.Context, req *admin_pb.GetSecurit } func (s *Server) SetSecurityPolicy(ctx context.Context, req *admin_pb.SetSecurityPolicyRequest) (*admin_pb.SetSecurityPolicyResponse, error) { - details, err := s.command.SetSecurityPolicy(ctx, req.EnableIframeEmbedding, req.AllowedOrigins) + details, err := s.command.SetSecurityPolicy(ctx, securityPolicyToCommand(req)) if err != nil { return nil, err } diff --git a/internal/api/grpc/admin/iam_settings_converter.go b/internal/api/grpc/admin/iam_settings_converter.go index 575fe75b355..8fd1a26f068 100644 --- a/internal/api/grpc/admin/iam_settings_converter.go +++ b/internal/api/grpc/admin/iam_settings_converter.go @@ -5,6 +5,7 @@ import ( "github.com/zitadel/zitadel/internal/api/grpc/object" obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object" + "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/notification/channels/smtp" @@ -173,7 +174,16 @@ func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig { func SecurityPolicyToPb(policy *query.SecurityPolicy) *settings_pb.SecurityPolicy { return &settings_pb.SecurityPolicy{ Details: obj_grpc.ToViewDetailsPb(policy.Sequence, policy.CreationDate, policy.ChangeDate, policy.AggregateID), - EnableIframeEmbedding: policy.Enabled, + EnableIframeEmbedding: policy.EnableIframeEmbedding, AllowedOrigins: policy.AllowedOrigins, + EnableImpersonation: policy.EnableImpersonation, + } +} + +func securityPolicyToCommand(req *admin_pb.SetSecurityPolicyRequest) *command.SecurityPolicy { + return &command.SecurityPolicy{ + EnableIframeEmbedding: req.GetEnableIframeEmbedding(), + AllowedOrigins: req.GetAllowedOrigins(), + EnableImpersonation: req.GetEnableImpersonation(), } } diff --git a/internal/api/grpc/admin/iam_settings_integration_test.go b/internal/api/grpc/admin/iam_settings_integration_test.go new file mode 100644 index 00000000000..10e24ef8a19 --- /dev/null +++ b/internal/api/grpc/admin/iam_settings_integration_test.go @@ -0,0 +1,163 @@ +//go:build integration + +package admin_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/zitadel/zitadel/internal/integration" + admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" + "github.com/zitadel/zitadel/pkg/grpc/object" + "github.com/zitadel/zitadel/pkg/grpc/settings" +) + +func TestServer_GetSecurityPolicy(t *testing.T) { + _, err := Client.SetSecurityPolicy(AdminCTX, &admin_pb.SetSecurityPolicyRequest{ + EnableIframeEmbedding: true, + AllowedOrigins: []string{"foo.com", "bar.com"}, + EnableImpersonation: true, + }) + require.NoError(t, err) + + tests := []struct { + name string + ctx context.Context + want *admin_pb.GetSecurityPolicyResponse + wantErr bool + }{ + { + name: "permission error", + ctx: Tester.WithAuthorization(CTX, integration.OrgOwner), + wantErr: true, + }, + { + name: "success", + ctx: AdminCTX, + want: &admin_pb.GetSecurityPolicyResponse{ + Policy: &settings.SecurityPolicy{ + EnableIframeEmbedding: true, + AllowedOrigins: []string{"foo.com", "bar.com"}, + EnableImpersonation: true, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp, err := Client.GetSecurityPolicy(tt.ctx, &admin_pb.GetSecurityPolicyRequest{}) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + got, want := resp.GetPolicy(), tt.want.GetPolicy() + assert.Equal(t, want.GetEnableIframeEmbedding(), got.GetEnableIframeEmbedding(), "enable iframe embedding") + assert.Equal(t, want.GetAllowedOrigins(), got.GetAllowedOrigins(), "allowed origins") + assert.Equal(t, want.GetEnableImpersonation(), got.GetEnableImpersonation(), "enable impersonation") + }) + } +} + +func TestServer_SetSecurityPolicy(t *testing.T) { + type args struct { + ctx context.Context + req *admin_pb.SetSecurityPolicyRequest + } + tests := []struct { + name string + args args + want *admin_pb.SetSecurityPolicyResponse + wantErr bool + }{ + { + name: "permission error", + args: args{ + ctx: Tester.WithAuthorization(CTX, integration.OrgOwner), + req: &admin_pb.SetSecurityPolicyRequest{ + EnableIframeEmbedding: true, + AllowedOrigins: []string{"foo.com", "bar.com"}, + EnableImpersonation: true, + }, + }, + wantErr: true, + }, + { + name: "success allowed origins", + args: args{ + ctx: AdminCTX, + req: &admin_pb.SetSecurityPolicyRequest{ + AllowedOrigins: []string{"foo.com", "bar.com"}, + }, + }, + want: &admin_pb.SetSecurityPolicyResponse{ + Details: &object.ObjectDetails{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Tester.Instance.InstanceID(), + }, + }, + }, + { + name: "success iframe embedding", + args: args{ + ctx: AdminCTX, + req: &admin_pb.SetSecurityPolicyRequest{ + EnableIframeEmbedding: true, + }, + }, + want: &admin_pb.SetSecurityPolicyResponse{ + Details: &object.ObjectDetails{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Tester.Instance.InstanceID(), + }, + }, + }, + { + name: "success impersonation", + args: args{ + ctx: AdminCTX, + req: &admin_pb.SetSecurityPolicyRequest{ + EnableImpersonation: true, + }, + }, + want: &admin_pb.SetSecurityPolicyResponse{ + Details: &object.ObjectDetails{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Tester.Instance.InstanceID(), + }, + }, + }, + { + name: "success all", + args: args{ + ctx: AdminCTX, + req: &admin_pb.SetSecurityPolicyRequest{ + EnableIframeEmbedding: true, + AllowedOrigins: []string{"foo.com", "bar.com"}, + EnableImpersonation: true, + }, + }, + want: &admin_pb.SetSecurityPolicyResponse{ + Details: &object.ObjectDetails{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Tester.Instance.InstanceID(), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Client.SetSecurityPolicy(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + integration.AssertDetails(t, tt.want, got) + }) + } +} diff --git a/internal/api/grpc/admin/server_integration_test.go b/internal/api/grpc/admin/server_integration_test.go index f3907dddb03..c1739d46b3a 100644 --- a/internal/api/grpc/admin/server_integration_test.go +++ b/internal/api/grpc/admin/server_integration_test.go @@ -12,11 +12,13 @@ import ( "github.com/stretchr/testify/require" "github.com/zitadel/zitadel/internal/integration" + admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" ) var ( - AdminCTX, SystemCTX context.Context - Tester *integration.Tester + CTX, AdminCTX, SystemCTX context.Context + Tester *integration.Tester + Client admin_pb.AdminServiceClient ) func TestMain(m *testing.M) { @@ -27,9 +29,10 @@ func TestMain(m *testing.M) { Tester = integration.NewTester(ctx) defer Tester.Done() + CTX = ctx AdminCTX = Tester.WithAuthorization(ctx, integration.IAMOwner) SystemCTX = Tester.WithAuthorization(ctx, integration.SystemUser) - + Client = Tester.Client.Admin return m.Run() }()) } diff --git a/internal/api/grpc/management/org_integration_test.go b/internal/api/grpc/management/org_integration_test.go new file mode 100644 index 00000000000..de8f2f215a6 --- /dev/null +++ b/internal/api/grpc/management/org_integration_test.go @@ -0,0 +1,327 @@ +//go:build integration + +package management_test + +import ( + "context" + "testing" + + "github.com/brianvoe/gofakeit/v6" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/zitadel/zitadel/internal/integration" + mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management" + "github.com/zitadel/zitadel/pkg/grpc/member" + "github.com/zitadel/zitadel/pkg/grpc/object" +) + +var iamRoles = []string{ + "SELF_MANAGEMENT_GLOBAL", + "ORG_OWNER", + "ORG_USER_MANAGER", + "ORG_OWNER_VIEWER", + "ORG_SETTINGS_MANAGER", + "ORG_USER_PERMISSION_EDITOR", + "ORG_PROJECT_PERMISSION_EDITOR", + "ORG_PROJECT_CREATOR", + "ORG_USER_SELF_MANAGER", + "ORG_ADMIN_IMPERSONATOR", + "ORG_END_USER_IMPERSONATOR", +} + +func TestServer_ListOrgMemberRoles(t *testing.T) { + got, err := Client.ListOrgMemberRoles(OrgCTX, &mgmt_pb.ListOrgMemberRolesRequest{}) + require.NoError(t, err) + assert.ElementsMatch(t, iamRoles, got.GetResult()) +} + +func TestServer_ListOrgMembers(t *testing.T) { + user := Tester.CreateHumanUserVerified(OrgCTX, Tester.Organisation.ID, gofakeit.Email()) + _, err := Client.AddOrgMember(OrgCTX, &mgmt_pb.AddOrgMemberRequest{ + UserId: user.GetUserId(), + Roles: iamRoles[1:], + }) + require.NoError(t, err) + type args struct { + ctx context.Context + req *mgmt_pb.ListOrgMembersRequest + } + tests := []struct { + name string + args args + want *mgmt_pb.ListOrgMembersResponse + wantErr bool + }{ + { + name: "permission error", + args: args{ + ctx: CTX, + req: &mgmt_pb.ListOrgMembersRequest{ + Query: &object.ListQuery{}, + Queries: []*member.SearchQuery{{ + Query: &member.SearchQuery_UserIdQuery{ + UserIdQuery: &member.UserIDQuery{ + UserId: user.GetUserId(), + }, + }, + }}, + }, + }, + wantErr: true, + }, + { + name: "success", + args: args{ + ctx: OrgCTX, + req: &mgmt_pb.ListOrgMembersRequest{ + Query: &object.ListQuery{}, + Queries: []*member.SearchQuery{{ + Query: &member.SearchQuery_UserIdQuery{ + UserIdQuery: &member.UserIDQuery{ + UserId: user.GetUserId(), + }, + }, + }}, + }, + }, + want: &mgmt_pb.ListOrgMembersResponse{ + Result: []*member.Member{{ + UserId: user.GetUserId(), + Roles: iamRoles[1:], + }}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Client.ListOrgMembers(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + wantResult := tt.want.GetResult() + gotResult := got.GetResult() + + require.Len(t, gotResult, len(wantResult)) + for i, want := range wantResult { + assert.Equal(t, want.GetUserId(), gotResult[i].GetUserId()) + assert.ElementsMatch(t, want.GetRoles(), gotResult[i].GetRoles()) + } + }) + } +} + +func TestServer_AddOrgMember(t *testing.T) { + user := Tester.CreateHumanUserVerified(OrgCTX, Tester.Organisation.ID, gofakeit.Email()) + type args struct { + ctx context.Context + req *mgmt_pb.AddOrgMemberRequest + } + tests := []struct { + name string + args args + want *mgmt_pb.AddOrgMemberResponse + wantErr bool + }{ + { + name: "permission error", + args: args{ + ctx: CTX, + req: &mgmt_pb.AddOrgMemberRequest{ + UserId: user.GetUserId(), + Roles: iamRoles, + }, + }, + wantErr: true, + }, + { + name: "success", + args: args{ + ctx: OrgCTX, + req: &mgmt_pb.AddOrgMemberRequest{ + UserId: user.GetUserId(), + Roles: iamRoles[1:], + }, + }, + want: &mgmt_pb.AddOrgMemberResponse{ + Details: &object.ObjectDetails{ + ResourceOwner: Tester.Organisation.ID, + }, + }, + }, + { + name: "unknown roles error", + args: args{ + ctx: OrgCTX, + req: &mgmt_pb.AddOrgMemberRequest{ + UserId: user.GetUserId(), + Roles: []string{"FOO", "BAR"}, + }, + }, + wantErr: true, + }, + { + name: "iam role error", + args: args{ + ctx: OrgCTX, + req: &mgmt_pb.AddOrgMemberRequest{ + UserId: user.GetUserId(), + Roles: []string{"IAM_OWNER"}, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Client.AddOrgMember(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + integration.AssertDetails(t, tt.want, got) + }) + } +} + +func TestServer_UpdateOrgMember(t *testing.T) { + user := Tester.CreateHumanUserVerified(OrgCTX, Tester.Organisation.ID, gofakeit.Email()) + _, err := Client.AddOrgMember(OrgCTX, &mgmt_pb.AddOrgMemberRequest{ + UserId: user.GetUserId(), + Roles: []string{"ORG_OWNER"}, + }) + require.NoError(t, err) + + type args struct { + ctx context.Context + req *mgmt_pb.UpdateOrgMemberRequest + } + tests := []struct { + name string + args args + want *mgmt_pb.UpdateOrgMemberResponse + wantErr bool + }{ + { + name: "permission error", + args: args{ + ctx: CTX, + req: &mgmt_pb.UpdateOrgMemberRequest{ + UserId: user.GetUserId(), + Roles: iamRoles, + }, + }, + wantErr: true, + }, + { + name: "success", + args: args{ + ctx: OrgCTX, + req: &mgmt_pb.UpdateOrgMemberRequest{ + UserId: user.GetUserId(), + Roles: iamRoles[1:], + }, + }, + want: &mgmt_pb.UpdateOrgMemberResponse{ + Details: &object.ObjectDetails{ + ResourceOwner: Tester.Organisation.ID, + ChangeDate: timestamppb.Now(), + }, + }, + }, + { + name: "unknown roles error", + args: args{ + ctx: OrgCTX, + req: &mgmt_pb.UpdateOrgMemberRequest{ + UserId: user.GetUserId(), + Roles: []string{"FOO", "BAR"}, + }, + }, + wantErr: true, + }, + { + name: "iam role error", + args: args{ + ctx: OrgCTX, + req: &mgmt_pb.UpdateOrgMemberRequest{ + UserId: user.GetUserId(), + Roles: []string{"IAM_OWNER"}, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Client.UpdateOrgMember(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + integration.AssertDetails(t, tt.want, got) + }) + } +} + +func TestServer_RemoveIAMMember(t *testing.T) { + user := Tester.CreateHumanUserVerified(OrgCTX, Tester.Organisation.ID, gofakeit.Email()) + _, err := Client.AddOrgMember(OrgCTX, &mgmt_pb.AddOrgMemberRequest{ + UserId: user.GetUserId(), + Roles: []string{"ORG_OWNER"}, + }) + require.NoError(t, err) + + type args struct { + ctx context.Context + req *mgmt_pb.RemoveOrgMemberRequest + } + tests := []struct { + name string + args args + want *mgmt_pb.RemoveOrgMemberResponse + wantErr bool + }{ + { + name: "permission error", + args: args{ + ctx: CTX, + req: &mgmt_pb.RemoveOrgMemberRequest{ + UserId: user.GetUserId(), + }, + }, + wantErr: true, + }, + { + name: "success", + args: args{ + ctx: OrgCTX, + req: &mgmt_pb.RemoveOrgMemberRequest{ + UserId: user.GetUserId(), + }, + }, + want: &mgmt_pb.RemoveOrgMemberResponse{ + Details: &object.ObjectDetails{ + ResourceOwner: Tester.Organisation.ID, + ChangeDate: timestamppb.Now(), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Client.RemoveOrgMember(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + integration.AssertDetails(t, tt.want, got) + }) + } +} diff --git a/internal/api/grpc/management/server_integration_test.go b/internal/api/grpc/management/server_integration_test.go new file mode 100644 index 00000000000..2269e905e1f --- /dev/null +++ b/internal/api/grpc/management/server_integration_test.go @@ -0,0 +1,34 @@ +//go:build integration + +package management_test + +import ( + "context" + "os" + "testing" + "time" + + "github.com/zitadel/zitadel/internal/integration" + mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management" +) + +var ( + CTX, OrgCTX context.Context + Tester *integration.Tester + Client mgmt_pb.ManagementServiceClient +) + +func TestMain(m *testing.M) { + os.Exit(func() int { + ctx, _, cancel := integration.Contexts(3 * time.Minute) + defer cancel() + + Tester = integration.NewTester(ctx) + defer Tester.Done() + + CTX = ctx + OrgCTX = Tester.WithAuthorization(ctx, integration.OrgOwner) + Client = Tester.Client.Mgmt + return m.Run() + }()) +} diff --git a/internal/api/grpc/management/user_integration_test.go b/internal/api/grpc/management/user_integration_test.go index 5d612b158fc..bc89c5c7722 100644 --- a/internal/api/grpc/management/user_integration_test.go +++ b/internal/api/grpc/management/user_integration_test.go @@ -3,8 +3,6 @@ package management_test import ( - "context" - "os" "strconv" "strings" "testing" @@ -20,26 +18,6 @@ import ( "github.com/zitadel/zitadel/pkg/grpc/user" ) -var ( - CTX context.Context - Tester *integration.Tester - Client management.ManagementServiceClient -) - -func TestMain(m *testing.M) { - os.Exit(func() int { - ctx, errCtx, cancel := integration.Contexts(3 * time.Minute) - defer cancel() - - Tester = integration.NewTester(ctx) - defer Tester.Done() - - CTX, _ = Tester.WithAuthorization(ctx, integration.OrgOwner), errCtx - Client = Tester.Client.Mgmt - return m.Run() - }()) -} - // TestImport_and_Get reproduces https://github.com/zitadel/zitadel/issues/5808 // which led to consistency issues due the call timestamp not being // updated after a bulk Trigger. @@ -57,7 +35,7 @@ func TestImport_and_Get(t *testing.T) { userName := strings.Join([]string{firstName, lastName}, "_") email := strings.Join([]string{userName, "example.com"}, "@") - res, err := Client.ImportHumanUser(CTX, &management.ImportHumanUserRequest{ + res, err := Client.ImportHumanUser(OrgCTX, &management.ImportHumanUserRequest{ UserName: userName, Profile: &management.ImportHumanUserRequest_Profile{ FirstName: firstName, @@ -72,7 +50,7 @@ func TestImport_and_Get(t *testing.T) { }) require.NoError(t, err) - _, err = Client.GetUserByID(CTX, &management.GetUserByIDRequest{Id: res.GetUserId()}) + _, err = Client.GetUserByID(OrgCTX, &management.GetUserByIDRequest{Id: res.GetUserId()}) s, ok := status.FromError(err) if ok && s != nil && s.Code() == codes.NotFound { @@ -85,7 +63,7 @@ func TestImport_and_Get(t *testing.T) { func TestImport_UnparsablePreferredLanguage(t *testing.T) { random := integration.RandString(5) - _, err := Client.ImportHumanUser(CTX, &management.ImportHumanUserRequest{ + _, err := Client.ImportHumanUser(OrgCTX, &management.ImportHumanUserRequest{ UserName: random, Profile: &management.ImportHumanUserRequest_Profile{ FirstName: random, diff --git a/internal/api/grpc/oidc/v2/oidc_integration_test.go b/internal/api/grpc/oidc/v2/oidc_integration_test.go index f3e7e0a75db..27884e80a58 100644 --- a/internal/api/grpc/oidc/v2/oidc_integration_test.go +++ b/internal/api/grpc/oidc/v2/oidc_integration_test.go @@ -13,6 +13,7 @@ import ( "github.com/muhlemmer/gu" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/internal/integration" object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" @@ -183,6 +184,7 @@ func TestServer_CreateCallback(t *testing.T) { want: &oidc_pb.CreateCallbackResponse{ CallbackUrl: regexp.QuoteMeta(`oidcintegrationtest://callback?error=access_denied&error_description=nope&error_uri=https%3A%2F%2Fexample.com%2Fdocs&state=state`), Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), }, }, @@ -206,6 +208,7 @@ func TestServer_CreateCallback(t *testing.T) { want: &oidc_pb.CreateCallbackResponse{ CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`, Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), }, }, @@ -231,6 +234,7 @@ func TestServer_CreateCallback(t *testing.T) { want: &oidc_pb.CreateCallbackResponse{ CallbackUrl: `http:\/\/localhost:9999\/callback#access_token=(.*)&expires_in=(.*)&id_token=(.*)&state=state&token_type=Bearer`, Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), }, }, diff --git a/internal/api/grpc/server/middleware/instance_interceptor_test.go b/internal/api/grpc/server/middleware/instance_interceptor_test.go index 2025970d993..5de348c8dc6 100644 --- a/internal/api/grpc/server/middleware/instance_interceptor_test.go +++ b/internal/api/grpc/server/middleware/instance_interceptor_test.go @@ -210,6 +210,10 @@ func (m *mockInstance) SecurityPolicyAllowedOrigins() []string { return nil } +func (m *mockInstance) EnableImpersonation() bool { + return false +} + func (m *mockInstance) Features() feature.Features { return feature.Features{} } diff --git a/internal/api/grpc/session/v2/session_integration_test.go b/internal/api/grpc/session/v2/session_integration_test.go index 5cdc350f88f..c2893c1b594 100644 --- a/internal/api/grpc/session/v2/session_integration_test.go +++ b/internal/api/grpc/session/v2/session_integration_test.go @@ -16,6 +16,7 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/internal/integration" object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" @@ -164,6 +165,7 @@ func TestServer_CreateSession(t *testing.T) { }, want: &session.CreateSessionResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), }, }, @@ -183,6 +185,7 @@ func TestServer_CreateSession(t *testing.T) { }, want: &session.CreateSessionResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), }, }, @@ -211,6 +214,7 @@ func TestServer_CreateSession(t *testing.T) { }, want: &session.CreateSessionResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), }, }, @@ -230,6 +234,7 @@ func TestServer_CreateSession(t *testing.T) { }, want: &session.CreateSessionResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Instance.InstanceID(), }, }, diff --git a/internal/api/grpc/settings/v2/server_integration_test.go b/internal/api/grpc/settings/v2/server_integration_test.go new file mode 100644 index 00000000000..703e8cb971f --- /dev/null +++ b/internal/api/grpc/settings/v2/server_integration_test.go @@ -0,0 +1,34 @@ +//go:build integration + +package settings_test + +import ( + "context" + "os" + "testing" + "time" + + "github.com/zitadel/zitadel/internal/integration" + settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta" +) + +var ( + CTX, AdminCTX context.Context + Tester *integration.Tester + Client settings.SettingsServiceClient +) + +func TestMain(m *testing.M) { + os.Exit(func() int { + ctx, _, cancel := integration.Contexts(3 * time.Minute) + defer cancel() + + Tester = integration.NewTester(ctx) + defer Tester.Done() + + CTX = ctx + AdminCTX = Tester.WithAuthorization(ctx, integration.IAMOwner) + Client = Tester.Client.SettingsV2 + return m.Run() + }()) +} diff --git a/internal/api/grpc/settings/v2/settings.go b/internal/api/grpc/settings/v2/settings.go index 5e09f8e89af..c16178c370c 100644 --- a/internal/api/grpc/settings/v2/settings.go +++ b/internal/api/grpc/settings/v2/settings.go @@ -11,7 +11,7 @@ import ( "github.com/zitadel/zitadel/internal/i18n" "github.com/zitadel/zitadel/internal/query" object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" - "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta" + settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta" ) func (s *Server) GetLoginSettings(ctx context.Context, req *settings.GetLoginSettingsRequest) (*settings.GetLoginSettingsResponse, error) { @@ -124,3 +124,23 @@ func (s *Server) GetGeneralSettings(ctx context.Context, _ *settings.GetGeneralS DefaultLanguage: instance.DefaultLanguage().String(), }, nil } + +func (s *Server) GetSecuritySettings(ctx context.Context, req *settings.GetSecuritySettingsRequest) (*settings.GetSecuritySettingsResponse, error) { + policy, err := s.query.SecurityPolicy(ctx) + if err != nil { + return nil, err + } + return &settings.GetSecuritySettingsResponse{ + Settings: securityPolicyToSettingsPb(policy), + }, nil +} + +func (s *Server) SetSecuritySettings(ctx context.Context, req *settings.SetSecuritySettingsRequest) (*settings.SetSecuritySettingsResponse, error) { + details, err := s.command.SetSecurityPolicy(ctx, securitySettingsToCommand(req)) + if err != nil { + return nil, err + } + return &settings.SetSecuritySettingsResponse{ + Details: object.DomainToDetailsPb(details), + }, nil +} diff --git a/internal/api/grpc/settings/v2/settings_converter.go b/internal/api/grpc/settings/v2/settings_converter.go index 69a494d027a..9c2e002da24 100644 --- a/internal/api/grpc/settings/v2/settings_converter.go +++ b/internal/api/grpc/settings/v2/settings_converter.go @@ -3,6 +3,7 @@ package settings import ( "google.golang.org/protobuf/types/known/durationpb" + "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta" @@ -205,3 +206,21 @@ func idpTypeToPb(idpType domain.IDPType) settings.IdentityProviderType { return settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_UNSPECIFIED } } + +func securityPolicyToSettingsPb(policy *query.SecurityPolicy) *settings.SecuritySettings { + return &settings.SecuritySettings{ + EmbeddedIframe: &settings.EmbeddedIframeSettings{ + Enabled: policy.EnableIframeEmbedding, + AllowedOrigins: policy.AllowedOrigins, + }, + EnableImpersonation: policy.EnableImpersonation, + } +} + +func securitySettingsToCommand(req *settings.SetSecuritySettingsRequest) *command.SecurityPolicy { + return &command.SecurityPolicy{ + EnableIframeEmbedding: req.GetEmbeddedIframe().GetEnabled(), + AllowedOrigins: req.GetEmbeddedIframe().GetAllowedOrigins(), + EnableImpersonation: req.GetEnableImpersonation(), + } +} diff --git a/internal/api/grpc/settings/v2/settings_converter_test.go b/internal/api/grpc/settings/v2/settings_converter_test.go index 6e441a33baf..4fb2107fb2b 100644 --- a/internal/api/grpc/settings/v2/settings_converter_test.go +++ b/internal/api/grpc/settings/v2/settings_converter_test.go @@ -12,6 +12,7 @@ import ( "google.golang.org/protobuf/types/known/durationpb" "github.com/zitadel/zitadel/internal/api/grpc" + "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta" @@ -450,3 +451,35 @@ func Test_idpTypeToPb(t *testing.T) { }) } } + +func Test_securityPolicyToSettingsPb(t *testing.T) { + want := &settings.SecuritySettings{ + EmbeddedIframe: &settings.EmbeddedIframeSettings{ + Enabled: true, + AllowedOrigins: []string{"foo", "bar"}, + }, + EnableImpersonation: true, + } + got := securityPolicyToSettingsPb(&query.SecurityPolicy{ + EnableIframeEmbedding: true, + AllowedOrigins: []string{"foo", "bar"}, + EnableImpersonation: true, + }) + assert.Equal(t, want, got) +} + +func Test_securitySettingsToCommand(t *testing.T) { + want := &command.SecurityPolicy{ + EnableIframeEmbedding: true, + AllowedOrigins: []string{"foo", "bar"}, + EnableImpersonation: true, + } + got := securitySettingsToCommand(&settings.SetSecuritySettingsRequest{ + EmbeddedIframe: &settings.EmbeddedIframeSettings{ + Enabled: true, + AllowedOrigins: []string{"foo", "bar"}, + }, + EnableImpersonation: true, + }) + assert.Equal(t, want, got) +} diff --git a/internal/api/grpc/settings/v2/settings_integration_test.go b/internal/api/grpc/settings/v2/settings_integration_test.go new file mode 100644 index 00000000000..3accc0d63f4 --- /dev/null +++ b/internal/api/grpc/settings/v2/settings_integration_test.go @@ -0,0 +1,174 @@ +//go:build integration + +package settings_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/zitadel/zitadel/internal/integration" + object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" + settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta" +) + +func TestServer_GetSecuritySettings(t *testing.T) { + _, err := Client.SetSecuritySettings(AdminCTX, &settings.SetSecuritySettingsRequest{ + EmbeddedIframe: &settings.EmbeddedIframeSettings{ + Enabled: true, + AllowedOrigins: []string{"foo", "bar"}, + }, + EnableImpersonation: true, + }) + require.NoError(t, err) + + tests := []struct { + name string + ctx context.Context + want *settings.GetSecuritySettingsResponse + wantErr bool + }{ + { + name: "permission error", + ctx: Tester.WithAuthorization(CTX, integration.OrgOwner), + wantErr: true, + }, + { + name: "success", + ctx: AdminCTX, + want: &settings.GetSecuritySettingsResponse{ + Settings: &settings.SecuritySettings{ + EmbeddedIframe: &settings.EmbeddedIframeSettings{ + Enabled: true, + AllowedOrigins: []string{"foo", "bar"}, + }, + EnableImpersonation: true, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp, err := Client.GetSecuritySettings(tt.ctx, &settings.GetSecuritySettingsRequest{}) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + got, want := resp.GetSettings(), tt.want.GetSettings() + assert.Equal(t, want.GetEmbeddedIframe().GetEnabled(), got.GetEmbeddedIframe().GetEnabled(), "enable iframe embedding") + assert.Equal(t, want.GetEmbeddedIframe().GetAllowedOrigins(), got.GetEmbeddedIframe().GetAllowedOrigins(), "allowed origins") + assert.Equal(t, want.GetEnableImpersonation(), got.GetEnableImpersonation(), "enable impersonation") + }) + } +} + +func TestServer_SetSecuritySettings(t *testing.T) { + type args struct { + ctx context.Context + req *settings.SetSecuritySettingsRequest + } + tests := []struct { + name string + args args + want *settings.SetSecuritySettingsResponse + wantErr bool + }{ + { + name: "permission error", + args: args{ + ctx: Tester.WithAuthorization(CTX, integration.OrgOwner), + req: &settings.SetSecuritySettingsRequest{ + EmbeddedIframe: &settings.EmbeddedIframeSettings{ + Enabled: true, + AllowedOrigins: []string{"foo.com", "bar.com"}, + }, + EnableImpersonation: true, + }, + }, + wantErr: true, + }, + { + name: "success allowed origins", + args: args{ + ctx: AdminCTX, + req: &settings.SetSecuritySettingsRequest{ + EmbeddedIframe: &settings.EmbeddedIframeSettings{ + AllowedOrigins: []string{"foo.com", "bar.com"}, + }, + }, + }, + want: &settings.SetSecuritySettingsResponse{ + Details: &object_pb.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Tester.Instance.InstanceID(), + }, + }, + }, + { + name: "success enable iframe embedding", + args: args{ + ctx: AdminCTX, + req: &settings.SetSecuritySettingsRequest{ + EmbeddedIframe: &settings.EmbeddedIframeSettings{ + Enabled: true, + }, + }, + }, + want: &settings.SetSecuritySettingsResponse{ + Details: &object_pb.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Tester.Instance.InstanceID(), + }, + }, + }, + { + name: "success impersonation", + args: args{ + ctx: AdminCTX, + req: &settings.SetSecuritySettingsRequest{ + EnableImpersonation: true, + }, + }, + want: &settings.SetSecuritySettingsResponse{ + Details: &object_pb.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Tester.Instance.InstanceID(), + }, + }, + }, + { + name: "success all", + args: args{ + ctx: AdminCTX, + req: &settings.SetSecuritySettingsRequest{ + EmbeddedIframe: &settings.EmbeddedIframeSettings{ + Enabled: true, + AllowedOrigins: []string{"foo.com", "bar.com"}, + }, + EnableImpersonation: true, + }, + }, + want: &settings.SetSecuritySettingsResponse{ + Details: &object_pb.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Tester.Instance.InstanceID(), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Client.SetSecuritySettings(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + integration.AssertDetails(t, tt.want, got) + }) + } +} diff --git a/internal/api/grpc/user/v2/otp_integration_test.go b/internal/api/grpc/user/v2/otp_integration_test.go index cad0a2924d6..7471675c7e9 100644 --- a/internal/api/grpc/user/v2/otp_integration_test.go +++ b/internal/api/grpc/user/v2/otp_integration_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/internal/integration" object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" @@ -216,6 +217,7 @@ func TestServer_AddOTPEmail(t *testing.T) { }, want: &user.AddOTPEmailResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, }, @@ -282,6 +284,7 @@ func TestServer_RemoveOTPEmail(t *testing.T) { }, want: &user.RemoveOTPEmailResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ResourceOwner, }, }, diff --git a/internal/api/grpc/user/v2/passkey_integration_test.go b/internal/api/grpc/user/v2/passkey_integration_test.go index 383eeb0c82c..230a744a64c 100644 --- a/internal/api/grpc/user/v2/passkey_integration_test.go +++ b/internal/api/grpc/user/v2/passkey_integration_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/internal/integration" object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" @@ -58,6 +59,7 @@ func TestServer_RegisterPasskey(t *testing.T) { }, want: &user.RegisterPasskeyResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, }, @@ -109,6 +111,7 @@ func TestServer_RegisterPasskey(t *testing.T) { }, want: &user.RegisterPasskeyResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, }, @@ -187,6 +190,7 @@ func TestServer_VerifyPasskeyRegistration(t *testing.T) { }, want: &user.VerifyPasskeyRegistrationResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, }, @@ -253,6 +257,7 @@ func TestServer_CreatePasskeyRegistrationLink(t *testing.T) { }, want: &user.CreatePasskeyRegistrationLinkResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, }, @@ -272,6 +277,7 @@ func TestServer_CreatePasskeyRegistrationLink(t *testing.T) { }, want: &user.CreatePasskeyRegistrationLinkResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, }, @@ -287,6 +293,7 @@ func TestServer_CreatePasskeyRegistrationLink(t *testing.T) { }, want: &user.CreatePasskeyRegistrationLinkResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, }, diff --git a/internal/api/grpc/user/v2/password_integration_test.go b/internal/api/grpc/user/v2/password_integration_test.go index 517bde0a418..03b18a5fa70 100644 --- a/internal/api/grpc/user/v2/password_integration_test.go +++ b/internal/api/grpc/user/v2/password_integration_test.go @@ -143,6 +143,7 @@ func TestServer_SetPassword(t *testing.T) { }, want: &user.SetPasswordResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, }, @@ -173,6 +174,7 @@ func TestServer_SetPassword(t *testing.T) { }, want: &user.SetPasswordResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, }, @@ -206,6 +208,7 @@ func TestServer_SetPassword(t *testing.T) { }, want: &user.SetPasswordResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, }, diff --git a/internal/api/grpc/user/v2/totp_integration_test.go b/internal/api/grpc/user/v2/totp_integration_test.go index 4fef7bbf9b8..086a281af91 100644 --- a/internal/api/grpc/user/v2/totp_integration_test.go +++ b/internal/api/grpc/user/v2/totp_integration_test.go @@ -10,6 +10,7 @@ import ( "github.com/pquerna/otp/totp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/internal/integration" object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" @@ -60,6 +61,7 @@ func TestServer_RegisterTOTP(t *testing.T) { }, want: &user.RegisterTOTPResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, }, @@ -136,6 +138,7 @@ func TestServer_VerifyTOTPRegistration(t *testing.T) { }, want: &user.VerifyTOTPRegistrationResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ResourceOwner, }, }, diff --git a/internal/api/grpc/user/v2/u2f_integration_test.go b/internal/api/grpc/user/v2/u2f_integration_test.go index 77653f8789d..78c620e3c2a 100644 --- a/internal/api/grpc/user/v2/u2f_integration_test.go +++ b/internal/api/grpc/user/v2/u2f_integration_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/internal/integration" object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" @@ -60,6 +61,7 @@ func TestServer_RegisterU2F(t *testing.T) { }, want: &user.RegisterU2FResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, }, @@ -134,6 +136,7 @@ func TestServer_VerifyU2FRegistration(t *testing.T) { }, want: &user.VerifyU2FRegistrationResponse{ Details: &object.Details{ + ChangeDate: timestamppb.Now(), ResourceOwner: Tester.Organisation.ID, }, }, diff --git a/internal/api/grpc/user/v2/user_test.go b/internal/api/grpc/user/v2/user_test.go index 45775dc9e43..9e398e83ff0 100644 --- a/internal/api/grpc/user/v2/user_test.go +++ b/internal/api/grpc/user/v2/user_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" - "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -22,8 +21,6 @@ import ( user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta" ) -var ignoreTypes = []protoreflect.FullName{"google.protobuf.Duration", "google.protobuf.Struct"} - func Test_idpIntentToIDPIntentPb(t *testing.T) { decryption := func(err error) crypto.EncryptionAlgorithm { mCrypto := crypto.NewMockEncryptionAlgorithm(gomock.NewController(t)) diff --git a/internal/api/http/middleware/instance_interceptor_test.go b/internal/api/http/middleware/instance_interceptor_test.go index 444403fd559..4bb02769139 100644 --- a/internal/api/http/middleware/instance_interceptor_test.go +++ b/internal/api/http/middleware/instance_interceptor_test.go @@ -345,6 +345,10 @@ func (m *mockInstance) SecurityPolicyAllowedOrigins() []string { return nil } +func (m *mockInstance) EnableImpersonation() bool { + return false +} + func (m *mockInstance) Features() feature.Features { return feature.Features{} } diff --git a/internal/command/instance_policy_security.go b/internal/command/instance_policy_security.go index 0222eca9fc7..8869223f8d8 100644 --- a/internal/command/instance_policy_security.go +++ b/internal/command/instance_policy_security.go @@ -10,9 +10,15 @@ import ( "github.com/zitadel/zitadel/internal/repository/instance" ) -func (c *Commands) SetSecurityPolicy(ctx context.Context, enabled bool, allowedOrigins []string) (*domain.ObjectDetails, error) { +type SecurityPolicy struct { + EnableIframeEmbedding bool + AllowedOrigins []string + EnableImpersonation bool +} + +func (c *Commands) SetSecurityPolicy(ctx context.Context, policy *SecurityPolicy) (*domain.ObjectDetails, error) { instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) - validation := c.prepareSetSecurityPolicy(instanceAgg, enabled, allowedOrigins) + validation := c.prepareSetSecurityPolicy(instanceAgg, policy) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation) if err != nil { return nil, err @@ -28,14 +34,14 @@ func (c *Commands) SetSecurityPolicy(ctx context.Context, enabled bool, allowedO }, nil } -func (c *Commands) prepareSetSecurityPolicy(a *instance.Aggregate, enabled bool, allowedOrigins []string) preparation.Validation { +func (c *Commands) prepareSetSecurityPolicy(a *instance.Aggregate, policy *SecurityPolicy) preparation.Validation { return func() (preparation.CreateCommands, error) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { writeModel, err := c.getSecurityPolicyWriteModel(ctx, filter) if err != nil { return nil, err } - cmd, err := writeModel.NewSetEvent(ctx, &a.Aggregate, enabled, allowedOrigins) + cmd, err := writeModel.NewSetEvent(ctx, &a.Aggregate, policy) if err != nil { return nil, err } diff --git a/internal/command/instance_policy_security_model.go b/internal/command/instance_policy_security_model.go index 152b59014eb..75d21268546 100644 --- a/internal/command/instance_policy_security_model.go +++ b/internal/command/instance_policy_security_model.go @@ -2,7 +2,7 @@ package command import ( "context" - "reflect" + "slices" "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/eventstore" @@ -11,9 +11,7 @@ import ( type InstanceSecurityPolicyWriteModel struct { eventstore.WriteModel - - Enabled bool - AllowedOrigins []string + SecurityPolicy } func NewInstanceSecurityPolicyWriteModel(ctx context.Context) *InstanceSecurityPolicyWriteModel { @@ -28,8 +26,11 @@ func NewInstanceSecurityPolicyWriteModel(ctx context.Context) *InstanceSecurityP func (wm *InstanceSecurityPolicyWriteModel) Reduce() error { for _, event := range wm.Events { if e, ok := event.(*instance.SecurityPolicySetEvent); ok { - if e.Enabled != nil { - wm.Enabled = *e.Enabled + + if e.EnableIframeEmbedding != nil { + wm.EnableIframeEmbedding = *e.EnableIframeEmbedding + } else if e.Enabled != nil { + wm.EnableIframeEmbedding = *e.Enabled } if e.AllowedOrigins != nil { wm.AllowedOrigins = *e.AllowedOrigins @@ -53,17 +54,19 @@ func (wm *InstanceSecurityPolicyWriteModel) Query() *eventstore.SearchQueryBuild func (wm *InstanceSecurityPolicyWriteModel) NewSetEvent( ctx context.Context, aggregate *eventstore.Aggregate, - enabled bool, - allowedOrigins []string, + policy *SecurityPolicy, ) (*instance.SecurityPolicySetEvent, error) { changes := make([]instance.SecurityPolicyChanges, 0, 2) var err error - if wm.Enabled != enabled { - changes = append(changes, instance.ChangeSecurityPolicyEnabled(enabled)) + if wm.EnableIframeEmbedding != policy.EnableIframeEmbedding { + changes = append(changes, instance.ChangeSecurityPolicyEnableIframeEmbedding(policy.EnableIframeEmbedding)) } - if enabled && !reflect.DeepEqual(wm.AllowedOrigins, allowedOrigins) { - changes = append(changes, instance.ChangeSecurityPolicyAllowedOrigins(allowedOrigins)) + if !slices.Equal(wm.AllowedOrigins, policy.AllowedOrigins) { + changes = append(changes, instance.ChangeSecurityPolicyAllowedOrigins(policy.AllowedOrigins)) + } + if wm.EnableImpersonation != policy.EnableImpersonation { + changes = append(changes, instance.ChangeSecurityPolicyEnableImpersonation(policy.EnableImpersonation)) } changeEvent, err := instance.NewSecurityPolicySetEvent(ctx, aggregate, changes) if err != nil { diff --git a/internal/command/main_test.go b/internal/command/main_test.go index db2866c41d2..26a6d479dc7 100644 --- a/internal/command/main_test.go +++ b/internal/command/main_test.go @@ -216,6 +216,10 @@ func (m *mockInstance) SecurityPolicyAllowedOrigins() []string { return nil } +func (m *mockInstance) EnableImpersonation() bool { + return false +} + func (m *mockInstance) Features() feature.Features { return feature.Features{} } diff --git a/internal/integration/assert.go b/internal/integration/assert.go index 82bb731685f..a12c8126732 100644 --- a/internal/integration/assert.go +++ b/internal/integration/assert.go @@ -5,12 +5,22 @@ import ( "time" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/timestamppb" object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" ) -type DetailsMsg interface { - GetDetails() *object.Details +// Details is the interface that covers both v1 and v2 proto generated object details. +type Details interface { + comparable + GetSequence() uint64 + GetChangeDate() *timestamppb.Timestamp + GetResourceOwner() string +} + +// DetailsMsg is the interface that covers all proto messages which contain v1 or v2 object details. +type DetailsMsg[D Details] interface { + GetDetails() D } type ListDetailsMsg interface { @@ -24,22 +34,24 @@ type ListDetailsMsg interface { // Dynamically generated values are not compared with expected. // Instead a sanity check is performed. // For the sequence a non-zero value is expected. -// The change date has to be now, with a tollerance of 1 second. +// If the change date is populated, it is checked with a tolerance of 1 minute around Now. // -// The resource owner is compared with expected and is -// therefore the only value that has to be set. -func AssertDetails[D DetailsMsg](t testing.TB, expected, actual D) { +// The resource owner is compared with expected. +func AssertDetails[D Details, M DetailsMsg[D]](t testing.TB, expected, actual M) { wantDetails, gotDetails := expected.GetDetails(), actual.GetDetails() - if wantDetails == nil { + var nilDetails D + if wantDetails == nilDetails { assert.Nil(t, gotDetails) return } assert.NotZero(t, gotDetails.GetSequence()) - gotCD := gotDetails.GetChangeDate().AsTime() - now := time.Now() - assert.WithinRange(t, gotCD, now.Add(-time.Minute), now.Add(time.Minute)) + if wantDetails.GetChangeDate() != nil { + wantChangeDate := time.Now() + gotChangeDate := gotDetails.GetChangeDate().AsTime() + assert.WithinRange(t, gotChangeDate, wantChangeDate.Add(-time.Minute), wantChangeDate.Add(time.Minute)) + } assert.Equal(t, wantDetails.GetResourceOwner(), gotDetails.GetResourceOwner()) } diff --git a/internal/integration/assert_test.go b/internal/integration/assert_test.go index 31855e1e515..0355ffec986 100644 --- a/internal/integration/assert_test.go +++ b/internal/integration/assert_test.go @@ -32,6 +32,7 @@ func TestAssertDetails(t *testing.T) { exptected: myMsg{ details: &object.Details{ ResourceOwner: "me", + ChangeDate: timestamppb.Now(), }, }, actual: myMsg{ diff --git a/internal/integration/client.go b/internal/integration/client.go index 6c5e8dd7a9e..12a1b0c8461 100644 --- a/internal/integration/client.go +++ b/internal/integration/client.go @@ -34,6 +34,7 @@ import ( org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" organisation "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta" + settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta" "github.com/zitadel/zitadel/pkg/grpc/system" user_pb "github.com/zitadel/zitadel/pkg/grpc/user" user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta" @@ -46,6 +47,7 @@ type Client struct { Auth auth.AuthServiceClient UserV2 user.UserServiceClient SessionV2 session.SessionServiceClient + SettingsV2 settings.SettingsServiceClient OIDCv2 oidc_pb.OIDCServiceClient OrgV2 organisation.OrganizationServiceClient System system.SystemServiceClient @@ -61,6 +63,7 @@ func newClient(cc *grpc.ClientConn) Client { Auth: auth.NewAuthServiceClient(cc), UserV2: user.NewUserServiceClient(cc), SessionV2: session.NewSessionServiceClient(cc), + SettingsV2: settings.NewSettingsServiceClient(cc), OIDCv2: oidc_pb.NewOIDCServiceClient(cc), OrgV2: organisation.NewOrganizationServiceClient(cc), System: system.NewSystemServiceClient(cc), diff --git a/internal/query/instance.go b/internal/query/instance.go index ed6d173a777..e528f3bcb77 100644 --- a/internal/query/instance.go +++ b/internal/query/instance.go @@ -411,23 +411,24 @@ func prepareInstanceDomainQuery(ctx context.Context, db prepareDatabase) (sq.Sel } type authzInstance struct { - id string - iamProjectID string - consoleID string - consoleAppID string - host string - domain string - defaultLang language.Tag - defaultOrgID string - csp csp - block *bool - auditLogRetention *time.Duration - features feature.Features + id string + iamProjectID string + consoleID string + consoleAppID string + host string + domain string + defaultLang language.Tag + defaultOrgID string + csp csp + enableImpersonation bool + block *bool + auditLogRetention *time.Duration + features feature.Features } type csp struct { - enabled bool - allowedOrigins database.TextArray[string] + enableIframeEmbedding bool + allowedOrigins database.TextArray[string] } func (i *authzInstance) InstanceID() string { @@ -463,12 +464,16 @@ func (i *authzInstance) DefaultOrganisationID() string { } func (i *authzInstance) SecurityPolicyAllowedOrigins() []string { - if !i.csp.enabled { + if !i.csp.enableIframeEmbedding { return nil } return i.csp.allowedOrigins } +func (i *authzInstance) EnableImpersonation() bool { + return i.enableImpersonation +} + func (i *authzInstance) Block() *bool { return i.block } @@ -489,7 +494,8 @@ func scanAuthzInstance(host, domain string) (*authzInstance, func(row *sql.Row) return instance, func(row *sql.Row) error { var ( lang string - securityPolicyEnabled sql.NullBool + enableIframeEmbedding sql.NullBool + enableImpersonation sql.NullBool auditLogRetention database.NullDuration block sql.NullBool features []byte @@ -501,8 +507,9 @@ func scanAuthzInstance(host, domain string) (*authzInstance, func(row *sql.Row) &instance.consoleID, &instance.consoleAppID, &lang, - &securityPolicyEnabled, + &enableIframeEmbedding, &instance.csp.allowedOrigins, + &enableImpersonation, &auditLogRetention, &block, &features, @@ -520,7 +527,8 @@ func scanAuthzInstance(host, domain string) (*authzInstance, func(row *sql.Row) if block.Valid { instance.block = &block.Bool } - instance.csp.enabled = securityPolicyEnabled.Bool + instance.csp.enableIframeEmbedding = enableIframeEmbedding.Bool + instance.enableImpersonation = enableImpersonation.Bool if len(features) == 0 { return nil } diff --git a/internal/query/instance_by_domain.sql b/internal/query/instance_by_domain.sql index b7fc8c3fe7c..ae6cb242492 100644 --- a/internal/query/instance_by_domain.sql +++ b/internal/query/instance_by_domain.sql @@ -18,13 +18,14 @@ select i.console_client_id, i.console_app_id, i.default_language, - s.enabled, + s.enable_iframe_embedding, s.origins, + s.enable_impersonation, l.audit_log_retention, l.block, f.features from domain d join projections.instances i on i.id = d.instance_id -left join projections.security_policies s on i.id = s.instance_id +left join projections.security_policies2 s on i.id = s.instance_id left join projections.limits l on i.id = l.instance_id left join features f on i.id = f.instance_id; diff --git a/internal/query/instance_by_id.sql b/internal/query/instance_by_id.sql index b316c4c6663..54d2b7a9494 100644 --- a/internal/query/instance_by_id.sql +++ b/internal/query/instance_by_id.sql @@ -15,13 +15,14 @@ select i.console_client_id, i.console_app_id, i.default_language, - s.enabled, + s.enable_iframe_embedding, s.origins, + s.enable_impersonation, l.audit_log_retention, l.block, f.features from projections.instances i -left join projections.security_policies s on i.id = s.instance_id +left join projections.security_policies2 s on i.id = s.instance_id left join projections.limits l on i.id = l.instance_id left join features f on i.id = f.instance_id where i.id = $1; diff --git a/internal/query/projection/security_policy.go b/internal/query/projection/security_policy.go index 062bc5d47cd..2de5ace8404 100644 --- a/internal/query/projection/security_policy.go +++ b/internal/query/projection/security_policy.go @@ -11,13 +11,14 @@ import ( ) const ( - SecurityPolicyProjectionTable = "projections.security_policies" - SecurityPolicyColumnInstanceID = "instance_id" - SecurityPolicyColumnCreationDate = "creation_date" - SecurityPolicyColumnChangeDate = "change_date" - SecurityPolicyColumnSequence = "sequence" - SecurityPolicyColumnEnabled = "enabled" - SecurityPolicyColumnAllowedOrigins = "origins" + SecurityPolicyProjectionTable = "projections.security_policies2" + SecurityPolicyColumnInstanceID = "instance_id" + SecurityPolicyColumnCreationDate = "creation_date" + SecurityPolicyColumnChangeDate = "change_date" + SecurityPolicyColumnSequence = "sequence" + SecurityPolicyColumnEnableIframeEmbedding = "enable_iframe_embedding" + SecurityPolicyColumnAllowedOrigins = "origins" + SecurityPolicyColumnEnableImpersonation = "enable_impersonation" ) type securityPolicyProjection struct{} @@ -37,8 +38,9 @@ func (*securityPolicyProjection) Init() *old_handler.Check { handler.NewColumn(SecurityPolicyColumnChangeDate, handler.ColumnTypeTimestamp), handler.NewColumn(SecurityPolicyColumnInstanceID, handler.ColumnTypeText), handler.NewColumn(SecurityPolicyColumnSequence, handler.ColumnTypeInt64), - handler.NewColumn(SecurityPolicyColumnEnabled, handler.ColumnTypeBool, handler.Default(false)), + handler.NewColumn(SecurityPolicyColumnEnableIframeEmbedding, handler.ColumnTypeBool, handler.Default(false)), handler.NewColumn(SecurityPolicyColumnAllowedOrigins, handler.ColumnTypeTextArray, handler.Nullable()), + handler.NewColumn(SecurityPolicyColumnEnableImpersonation, handler.ColumnTypeBool, handler.Default(false)), }, handler.NewPrimaryKey(SecurityPolicyColumnInstanceID), ), @@ -74,12 +76,17 @@ func (p *securityPolicyProjection) reduceSecurityPolicySet(event eventstore.Even handler.NewCol(SecurityPolicyColumnInstanceID, e.Aggregate().InstanceID), handler.NewCol(SecurityPolicyColumnSequence, e.Sequence()), } - if e.Enabled != nil { - changes = append(changes, handler.NewCol(SecurityPolicyColumnEnabled, *e.Enabled)) + if e.EnableIframeEmbedding != nil { + changes = append(changes, handler.NewCol(SecurityPolicyColumnEnableIframeEmbedding, *e.EnableIframeEmbedding)) + } else if e.Enabled != nil { + changes = append(changes, handler.NewCol(SecurityPolicyColumnEnableIframeEmbedding, *e.Enabled)) } if e.AllowedOrigins != nil { changes = append(changes, handler.NewCol(SecurityPolicyColumnAllowedOrigins, e.AllowedOrigins)) } + if e.EnableImpersonation != nil { + changes = append(changes, handler.NewCol(SecurityPolicyColumnEnableImpersonation, e.EnableImpersonation)) + } return handler.NewUpsertStatement( e, []handler.Column{ diff --git a/internal/query/security_policy.go b/internal/query/security_policy.go index 3a4a3abb204..51938abdaec 100644 --- a/internal/query/security_policy.go +++ b/internal/query/security_policy.go @@ -36,14 +36,18 @@ var ( name: projection.SecurityPolicyColumnSequence, table: securityPolicyTable, } - SecurityPolicyColumnEnabled = Column{ - name: projection.SecurityPolicyColumnEnabled, + SecurityPolicyColumnEnableIframeEmbedding = Column{ + name: projection.SecurityPolicyColumnEnableIframeEmbedding, table: securityPolicyTable, } SecurityPolicyColumnAllowedOrigins = Column{ name: projection.SecurityPolicyColumnAllowedOrigins, table: securityPolicyTable, } + SecurityPolicyColumnEnableImpersonation = Column{ + name: projection.SecurityPolicyColumnEnableImpersonation, + table: securityPolicyTable, + } ) type SecurityPolicy struct { @@ -53,8 +57,9 @@ type SecurityPolicy struct { ResourceOwner string Sequence uint64 - Enabled bool - AllowedOrigins database.TextArray[string] + EnableIframeEmbedding bool + AllowedOrigins database.TextArray[string] + EnableImpersonation bool } func (q *Queries) SecurityPolicy(ctx context.Context) (policy *SecurityPolicy, err error) { @@ -80,8 +85,9 @@ func prepareSecurityPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sel SecurityPolicyColumnChangeDate.identifier(), SecurityPolicyColumnInstanceID.identifier(), SecurityPolicyColumnSequence.identifier(), - SecurityPolicyColumnEnabled.identifier(), - SecurityPolicyColumnAllowedOrigins.identifier()). + SecurityPolicyColumnEnableIframeEmbedding.identifier(), + SecurityPolicyColumnAllowedOrigins.identifier(), + SecurityPolicyColumnEnableImpersonation.identifier()). From(securityPolicyTable.identifier() + db.Timetravel(call.Took(ctx))). PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*SecurityPolicy, error) { @@ -92,8 +98,9 @@ func prepareSecurityPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sel &securityPolicy.ChangeDate, &securityPolicy.ResourceOwner, &securityPolicy.Sequence, - &securityPolicy.Enabled, + &securityPolicy.EnableIframeEmbedding, &securityPolicy.AllowedOrigins, + &securityPolicy.EnableImpersonation, ) if err != nil && !errors.Is(err, sql.ErrNoRows) { // ignore not found errors return nil, zerrors.ThrowInternal(err, "QUERY-Dfrt2", "Errors.Internal") diff --git a/internal/repository/instance/policy_security.go b/internal/repository/instance/policy_security.go index f5627c54ca9..ac91e398f6d 100644 --- a/internal/repository/instance/policy_security.go +++ b/internal/repository/instance/policy_security.go @@ -15,8 +15,12 @@ const ( type SecurityPolicySetEvent struct { eventstore.BaseEvent `json:"-"` - Enabled *bool `json:"enabled,omitempty"` - AllowedOrigins *[]string `json:"allowedOrigins,omitempty"` + // Enabled is a legacy field which was used before for Iframe Embedding. + // It is kept so older events can still be reduced. + Enabled *bool `json:"enabled,omitempty"` + EnableIframeEmbedding *bool `json:"enable_iframe_embedding,omitempty"` + AllowedOrigins *[]string `json:"allowedOrigins,omitempty"` + EnableImpersonation *bool `json:"enable_impersonation,omitempty"` } func NewSecurityPolicySetEvent( @@ -42,9 +46,9 @@ func NewSecurityPolicySetEvent( type SecurityPolicyChanges func(event *SecurityPolicySetEvent) -func ChangeSecurityPolicyEnabled(enabled bool) func(event *SecurityPolicySetEvent) { +func ChangeSecurityPolicyEnableIframeEmbedding(enabled bool) func(event *SecurityPolicySetEvent) { return func(e *SecurityPolicySetEvent) { - e.Enabled = &enabled + e.EnableIframeEmbedding = &enabled } } @@ -57,6 +61,12 @@ func ChangeSecurityPolicyAllowedOrigins(allowedOrigins []string) func(event *Sec } } +func ChangeSecurityPolicyEnableImpersonation(enabled bool) func(event *SecurityPolicySetEvent) { + return func(e *SecurityPolicySetEvent) { + e.EnableImpersonation = &enabled + } +} + func (e *SecurityPolicySetEvent) Payload() interface{} { return e } diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 2f60c46321e..3e42109619c 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -681,7 +681,7 @@ service AdminService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "Settings"; summary: "Get Security Settings"; - description: "Returns the security settings of the ZITADEL instance. The settings define if the iframe is allowed and from which origins." + description: "Returns the security settings of the ZITADEL instance." }; } @@ -698,7 +698,7 @@ service AdminService { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { tags: "Settings"; summary: "Set Security Settings"; - description: "Set the security settings of the ZITADEL instance. The settings define if the iframe is allowed and from which origins." + description: "Set the security settings of the ZITADEL instance." }; } @@ -4295,6 +4295,8 @@ message SetSecurityPolicyRequest{ bool enable_iframe_embedding = 1; // origins allowed loading ZITADEL in an iframe if enable_iframe_embedding is true repeated string allowed_origins = 2; + // allows users to impersonate other users. The impersonator needs the appropriate `*_IMPERSONATOR` roles assigned as well" + bool enable_impersonation = 3; } message SetSecurityPolicyResponse{ diff --git a/proto/zitadel/settings.proto b/proto/zitadel/settings.proto index 41e249f791e..ffb2dd9da8f 100644 --- a/proto/zitadel/settings.proto +++ b/proto/zitadel/settings.proto @@ -122,4 +122,6 @@ message SecurityPolicy { bool enable_iframe_embedding = 2; // origins allowed loading ZITADEL in an iframe if enable_iframe_embedding is true repeated string allowed_origins = 3; + // allows users to impersonate other users. The impersonator needs the appropriate `*_IMPERSONATOR` roles assigned as well" + bool enable_impersonation = 4; } diff --git a/proto/zitadel/settings/v2beta/security_settings.proto b/proto/zitadel/settings/v2beta/security_settings.proto new file mode 100644 index 00000000000..c2d53029132 --- /dev/null +++ b/proto/zitadel/settings/v2beta/security_settings.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package zitadel.settings.v2beta; + +option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta;settings"; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +message SecuritySettings { + EmbeddedIframeSettings embedded_iframe = 1; + bool enable_impersonation = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "default language for the current context" + example: "\"en\"" + } + ]; +} + +message EmbeddedIframeSettings{ + bool enabled = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "states if iframe embedding is enabled or disabled" + } + ]; + repeated string allowed_origins = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "origins allowed loading ZITADEL in an iframe if enabled." + example: "[\"foo.bar.com\", \"localhost:8080\"]" + } + ]; +} diff --git a/proto/zitadel/settings/v2beta/settings_service.proto b/proto/zitadel/settings/v2beta/settings_service.proto index 9c5d54d6c41..6ac86badadb 100644 --- a/proto/zitadel/settings/v2beta/settings_service.proto +++ b/proto/zitadel/settings/v2beta/settings_service.proto @@ -10,6 +10,7 @@ import "zitadel/settings/v2beta/legal_settings.proto"; import "zitadel/settings/v2beta/lockout_settings.proto"; import "zitadel/settings/v2beta/login_settings.proto"; import "zitadel/settings/v2beta/password_settings.proto"; +import "zitadel/settings/v2beta/security_settings.proto"; import "google/api/annotations.proto"; import "google/api/field_behavior.proto"; import "protoc-gen-openapiv2/options/annotations.proto"; @@ -298,6 +299,45 @@ service SettingsService { }; }; } + +// Get the security settings + rpc GetSecuritySettings(GetSecuritySettingsRequest) returns (GetSecuritySettingsResponse) { + option (google.api.http) = { + get: "/v2beta/settings/security"; + }; + + option (zitadel.protoc_gen_zitadel.v2.options) = { + auth_option: { + permission: "iam.policy.read" + } + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Settings"; + summary: "Get Security Settings"; + description: "Returns the security settings of the ZITADEL instance." + }; + } + +// Set the security settings + rpc SetSecuritySettings(SetSecuritySettingsRequest) returns (SetSecuritySettingsResponse) { + option (google.api.http) = { + put: "/v2beta/policies/security"; + body: "*" + }; + + option (zitadel.protoc_gen_zitadel.v2.options) = { + auth_option: { + permission: "iam.policy.write" + } + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Settings"; + summary: "Set Security Settings"; + description: "Set the security settings of the ZITADEL instance." + }; + } } message GetLoginSettingsRequest { @@ -383,3 +423,24 @@ message GetGeneralSettingsResponse { } ]; } + +// This is an empty request +message GetSecuritySettingsRequest{} + +message GetSecuritySettingsResponse{ + zitadel.object.v2beta.Details details = 1; + SecuritySettings settings = 2; +} + +message SetSecuritySettingsRequest{ + EmbeddedIframeSettings embedded_iframe = 1; + bool enable_impersonation = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "allows users to impersonate other users. The impersonator needs the appropriate `*_IMPERSONATOR` roles assigned as well" + } + ]; +} + +message SetSecuritySettingsResponse{ + zitadel.object.v2beta.Details details = 1; +} \ No newline at end of file