diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index 90c9bd74ce..42b5fa4024 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 29ae3d02d7..6d89b70c11 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 ae809c4d43..6698c16f12 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 b76982d4fd..fef8da25a7 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 38dbcb0ebd..c1f99d1ed2 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 71baadfc45..54be4f2f99 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 508db351b6..56ecafe8ba 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 ec475db3b8..0e611c23de 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 84d71a3a95..2fe15802c7 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 4e61fdf75a..14e09e8784 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 9650749548..97ee129f72 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 5de4521a10..838ed74857 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 91f08126bc..fd8ace082c 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 55e4acd9da..615e88d6f6 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 d01d279f7a..fd20e13efc 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 3528222c41..52764d4b95 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 3c82142cd7..c3f1d76f64 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 04f1a8d053..f7742d9bfe 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 f71bc5698d..59d4a02bfb 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 3dfd88e892..18767a3151 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 7057a18293..c6cac09d4b 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 0000000000..12f4f321c0 --- /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 53a5969d14..dde4100c32 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 575fe75b35..8fd1a26f06 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 0000000000..10e24ef8a1 --- /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 f3907dddb0..c1739d46b3 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 0000000000..de8f2f215a --- /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 0000000000..2269e905e1 --- /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 5d612b158f..bc89c5c772 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 f3e7e0a75d..27884e80a5 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 2025970d99..5de348c8dc 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 5cdc350f88..c2893c1b59 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 0000000000..703e8cb971 --- /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 5e09f8e89a..c16178c370 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 69a494d027..9c2e002da2 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 6e441a33ba..4fb2107fb2 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 0000000000..3accc0d63f --- /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 cad0a2924d..7471675c7e 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 383eeb0c82..230a744a64 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 517bde0a41..03b18a5fa7 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 4fef7bbf9b..086a281af9 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 77653f8789..78c620e3c2 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 45775dc9e4..9e398e83ff 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 444403fd55..4bb0276913 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 0222eca9fc..8869223f8d 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 152b59014e..75d2126854 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 db2866c41d..26a6d479dc 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 82bb731685..a12c812673 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 31855e1e51..0355ffec98 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 6c5e8dd7a9..12a1b0c846 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 ed6d173a77..e528f3bcb7 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 b7fc8c3fe7..ae6cb24249 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 b316c4c666..54d2b7a949 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 062bc5d47c..2de5ace840 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 3a4a3abb20..51938abdae 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 f5627c54ca..ac91e398f6 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 2f60c46321..3e42109619 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 41e249f791..ffb2dd9da8 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 0000000000..c2d5302913 --- /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 9c5d54d6c4..6ac86badad 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