Repository: SAP/luigi (js)

Issue:
container: add possibility to configure feature policies and sandbox rules
For iframe based micro frontends.
See https://docs.luigi-project.io/docs/general-settings/?section=allowrules and https://docs.luigi-project.io/docs/general-settings/?section=customsandboxrules in luigi-core.

Pull request description:
Specify iframe sandbox and allow rules
Fixes #3633

No default sandbox rules compared to core.

Patch:
diff --git a/container/src/LuigiContainer.svelte b/container/src/LuigiContainer.svelte
index defaae70..3401d26d 100644
--- a/container/src/LuigiContainer.svelte
+++ b/container/src/LuigiContainer.svelte
@@ -41,6 +41,16 @@
         reflect: false,
         attribute: 'client-permissions',
       },
+      allowRules: {
+        type: 'Array',
+        reflect: false,
+        attribute: 'allow-rules',
+      },
+      sandboxRules: {
+        type: 'Array',
+        reflect: false,
+        attribute: 'sandbox-rules',
+      },
     },
     extend: (customElementConstructor) => {
       let notInitFn = (name) => {
@@ -71,6 +81,7 @@
   import { ContainerAPI } from './api/container-api';
   import { Events } from './constants/communication';
   import { GenericHelperFunctions } from './utilities/helpers';
+  import { getAllowRules } from './services/iframe-helpers';
 
   export let viewurl: string;
   export let context: string;
@@ -85,6 +96,8 @@
   export let searchParams: any;
   export let pathParams: any;
   export let clientPermissions: any;
+  export let allowRules: string[];
+  export let sandboxRules: string[];
 
   export let userSettings: any;
   export let anchor: string;
@@ -111,7 +124,9 @@
       pathParams &&
       clientPermissions &&
       userSettings &&
-      anchor
+      anchor &&
+      allowRules &&
+      sandboxRules
     );
   };
 
@@ -197,7 +212,13 @@
 >
   {#if containerInitialized}
     {#if !webcomponent}
-      <iframe bind:this={iframeHandle.iframe} src={viewurl} title={label} />
+      <iframe
+        bind:this={iframeHandle.iframe}
+        src={viewurl}
+        title={label}
+        allow={getAllowRules(allowRules)}
+        sandbox={sandboxRules ? sandboxRules.join(' ') : undefined}
+      />
     {/if}
   {/if}
 </main>
diff --git a/container/src/services/iframe-helpers.ts b/container/src/services/iframe-helpers.ts
new file mode 100644
index 00000000..85ed4d32
--- /dev/null
+++ b/container/src/services/iframe-helpers.ts
@@ -0,0 +1,14 @@
+/**
+ * Retrieves the allow rules for iframes and modifies them according to w3c standard conditions.
+ * @param {string[]} allowRules - An array of allow rules.
+ * @returns {string|undefined} The modified allow rules joined as a single string, or undefined if allowRules is falsy.
+ */
+export const getAllowRules = (allowRules: string[]) => {
+    if (!allowRules) return undefined;
+    const rules = allowRules;
+    rules.forEach((rule, index) => {
+        rules[index] = rule + (rule.indexOf(';') != -1 ? '' : ';');
+        rules[index] = (allowRules[index] as any).replaceAll('"', "'");
+    });
+    return rules.join(' ');
+};
\ No newline at end of file
diff --git a/container/typings/LuigiContainer.svelte.d.ts b/container/typings/LuigiContainer.svelte.d.ts
index 96f6ba97..7c13eafd 100644
--- a/container/typings/LuigiContainer.svelte.d.ts
+++ b/container/typings/LuigiContainer.svelte.d.ts
@@ -103,6 +103,18 @@ export default class LuigiContainer extends HTMLElement {
    */
   anchor: string;
 
+  /**
+   * The list of rules for the content in the iframe, managed by the HTML `allow` attribute.
+   * You can use one or more rules by adding them to the array, for example allowRules: ["microphone", "camera"].
+   */
+  allowRules: string[];
+
+  /**
+   * The list of rules for the content in the iframe, managed by the HTML `sandbox` attribute.
+   * You can use one or more rules by adding them to the array, for example sandboxRules: ["allow-scripts", "allow-same-origin"].
+   */
+  sandboxRules: string[];
+
   /**
    * Function that updates the context of the microfrontend
    * @param {Object} contextObj The context data


Test patch:
diff --git a/container/test-app/index.html b/container/test-app/index.html
index 7f6d68b2..73893687 100644
--- a/container/test-app/index.html
+++ b/container/test-app/index.html
@@ -332,6 +332,8 @@
       <luigi-container
         viewURL="https://fiddle.luigi-project.io/examples/microfrontends/fundamental/table-demo-page.html"
         context='{"title": "Team", "content":""}'
+        allow-rules='["fullscreen", "microphone", "camera \"none\"", "geolocation \"self\" https://a.example.com https://b.example.com"]'
+        sandbox-rules='["allow-scripts", "allow-same-origin"]'
       ></luigi-container>
     </div>
     <div class="lui-wrapper" route="projects">
diff --git a/container/test/services/iframe-helpers.spec.ts b/container/test/services/iframe-helpers.spec.ts
new file mode 100644
index 00000000..e3320472
--- /dev/null
+++ b/container/test/services/iframe-helpers.spec.ts
@@ -0,0 +1,23 @@
+import { getAllowRules } from '../../src/services/iframe-helpers';
+
+describe('getAllowRules function', () => {
+    it('returns undefined if allowRules is undefined', () => {
+        expect(getAllowRules(undefined)).toBeUndefined();
+    });
+
+    it('returns an empty string if allowRules is an empty array', () => {
+        expect(getAllowRules([])).toBe('');
+    });
+
+    it('if semicolon already present, keep it.', () => {
+        const allowRules = ['rule1', 'rule2;', 'rule3;'];
+        const expectedRules = 'rule1; rule2; rule3;';
+        expect(getAllowRules(allowRules)).toBe(expectedRules);
+    });
+
+    it('replaces double quotes with single quotes in each rule', () => {
+        const allowRules = ["fullscreen", "microphone", "camera \"none\"", "geolocation \"self\" https://a.example.com https://b.example.com"];
+        const expectedRules = "fullscreen; microphone; camera 'none'; geolocation 'self' https://a.example.com https://b.example.com;";
+        expect(getAllowRules(allowRules)).toBe(expectedRules);
+    });
+});
\ No newline at end of file