From dd85ca7c72e011ba44d059c5cb8e4b0f4a088c3f Mon Sep 17 00:00:00 2001 From: StylusFrost <43682773+StylusFrost@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:22:39 +0100 Subject: [PATCH] test(ui): add M365 provider management E2E tests (#8954) --- .github/workflows/ui-e2e-tests.yml | 5 + ui/tests/providers/providers-page.ts | 130 ++++++++++++- ui/tests/providers/providers.md | 106 +++++++++++ ui/tests/providers/providers.spec.ts | 177 +++++++++++++++++- .../manage-cloud-providers.auth.setup.ts | 2 +- 5 files changed, 417 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ui-e2e-tests.yml b/.github/workflows/ui-e2e-tests.yml index 26177653f4..3339349943 100644 --- a/.github/workflows/ui-e2e-tests.yml +++ b/.github/workflows/ui-e2e-tests.yml @@ -28,6 +28,11 @@ jobs: E2E_AZURE_CLIENT_ID: ${{ secrets.E2E_AZURE_CLIENT_ID }} E2E_AZURE_SECRET_ID: ${{ secrets.E2E_AZURE_SECRET_ID }} E2E_AZURE_TENANT_ID: ${{ secrets.E2E_AZURE_TENANT_ID }} + E2E_M365_DOMAIN_ID: ${{ secrets.E2E_M365_DOMAIN_ID }} + E2E_M365_CLIENT_ID: ${{ secrets.E2E_M365_CLIENT_ID }} + E2E_M365_SECRET_ID: ${{ secrets.E2E_M365_SECRET_ID }} + E2E_M365_TENANT_ID: ${{ secrets.E2E_M365_TENANT_ID }} + E2E_M365_CERTIFICATE_CONTENT: ${{ secrets.E2E_M365_CERTIFICATE_CONTENT }} E2E_NEW_PASSWORD: ${{ secrets.E2E_NEW_PASSWORD }} steps: - name: Checkout repository diff --git a/ui/tests/providers/providers-page.ts b/ui/tests/providers/providers-page.ts index 701f4ea226..2b638f68b5 100644 --- a/ui/tests/providers/providers-page.ts +++ b/ui/tests/providers/providers-page.ts @@ -17,6 +17,12 @@ export interface AZUREProviderData { alias?: string; } +// M365 provider data +export interface M365ProviderData { + domainId: string; + alias?: string; +} + // AWS credential options export const AWS_CREDENTIAL_OPTIONS = { AWS_ROLE_ARN: "role", @@ -51,6 +57,23 @@ export interface AZUREProviderCredential { tenantId:string; } +// M365 credential options +export const M365_CREDENTIAL_OPTIONS = { + M365_CREDENTIALS: "credentials", + M365_CERTIFICATE_CREDENTIALS: "certificate" +} as const; + +// M365 credential type +type M365CredentialType = (typeof M365_CREDENTIAL_OPTIONS)[keyof typeof M365_CREDENTIAL_OPTIONS]; + +// M365 provider credential +export interface M365ProviderCredential { + type: M365CredentialType; + clientId:string; + clientSecret?:string; + tenantId:string; + certificateContent?:string; +} // Providers page export class ProvidersPage extends BasePage { @@ -79,6 +102,10 @@ export class ProvidersPage extends BasePage { readonly roleCredentialsRadio: Locator; readonly staticCredentialsRadio: Locator; + // M365 credentials type selection + readonly m365StaticCredentialsRadio: Locator; + readonly m365CertificateCredentialsRadio: Locator; + // AWS role credentials form readonly roleArnInput: Locator; readonly externalIdInput: Locator; @@ -93,6 +120,13 @@ export class ProvidersPage extends BasePage { readonly azureClientSecretInput: Locator; readonly azureTenantIdInput: Locator; + // M365 provider form elements + readonly m365domainIdInput: Locator; + readonly m365ClientIdInput: Locator; + readonly m365ClientSecretInput: Locator; + readonly m365TenantIdInput: Locator; + readonly m365CertificateContentInput: Locator; + // Delete button readonly deleteProviderConfirmationButton: Locator; @@ -131,7 +165,14 @@ export class ProvidersPage extends BasePage { this.azureClientIdInput = page.getByRole("textbox", { name: "Client ID" }); this.azureClientSecretInput = page.getByRole("textbox", { name: "Client Secret" }); this.azureTenantIdInput = page.getByRole("textbox", { name: "Tenant ID" }); - + + // M365 provider form inputs + this.m365domainIdInput = page.getByRole("textbox", { name: "Domain ID" }); + this.m365ClientIdInput = page.getByRole("textbox", { name: "Client ID" }); + this.m365ClientSecretInput = page.getByRole("textbox", { name: "Client Secret" }); + this.m365TenantIdInput = page.getByRole("textbox", { name: "Tenant ID" }); + this.m365CertificateContentInput = page.getByRole("textbox", { name: "Certificate Content" }); + // Alias input this.aliasInput = page.getByRole("textbox", { name: "Provider alias (optional)" }); @@ -158,6 +199,14 @@ export class ProvidersPage extends BasePage { name: /Connect via Credentials/i, }); + // Radios for selecting M365 credentials method + this.m365StaticCredentialsRadio = page.getByRole("radio", { + name: /App Client Secret Credentials/i, + }); + this.m365CertificateCredentialsRadio = page.getByRole("radio", { + name: /App Certificate Credentials/i, + }); + // Inputs for IAM Role credentials this.roleArnInput = page.getByRole("textbox", { name: "Role ARN" }); this.externalIdInput = page.getByRole("textbox", { name: "External ID" }); @@ -200,6 +249,14 @@ export class ProvidersPage extends BasePage { await this.waitForPageLoad(); } + async selectM365Provider(): Promise { + // Select the M365 provider + + await this.m365ProviderRadio.click({ force: true }); + await this.waitForPageLoad(); + } + + async fillAWSProviderDetails(data: AWSProviderData): Promise { // Fill the AWS provider details @@ -220,6 +277,16 @@ export class ProvidersPage extends BasePage { } } + async fillM365ProviderDetails(data: M365ProviderData): Promise { + // Fill the M365 provider details + + await this.m365domainIdInput.fill(data.domainId); + + if (data.alias) { + await this.aliasInput.fill(data.alias); + } + } + async clickNext(): Promise { // The wizard interface may use different labels for its primary action button on each step. // This function determines which button to click depending on the current URL and page content. @@ -342,6 +409,21 @@ export class ProvidersPage extends BasePage { await this.waitForPageLoad(); } + async selectM365CredentialsType(type: M365CredentialType): Promise { + // Ensure we are on the add-credentials page where the selector exists + + await expect(this.page).toHaveURL(/\/providers\/add-credentials/); + if (type === M365_CREDENTIAL_OPTIONS.M365_CREDENTIALS) { + await this.m365StaticCredentialsRadio.click({ force: true }); + } else if (type === M365_CREDENTIAL_OPTIONS.M365_CERTIFICATE_CREDENTIALS) { + await this.m365CertificateCredentialsRadio.click({ force: true }); + } else { + throw new Error(`Invalid M365 credential type: ${type}`); + } + // Wait for the page to load + await this.waitForPageLoad(); + } + async fillRoleCredentials(credentials: AWSProviderCredential): Promise { // Fill the role credentials form @@ -387,6 +469,34 @@ export class ProvidersPage extends BasePage { } } + async fillM365Credentials(credentials: M365ProviderCredential): Promise { + // Fill the m365 credentials form + + if (credentials.clientId) { + await this.m365ClientIdInput.fill(credentials.clientId); + } + if (credentials.clientSecret) { + await this.m365ClientSecretInput.fill(credentials.clientSecret); + } + if (credentials.tenantId) { + await this.m365TenantIdInput.fill(credentials.tenantId); + } + } + + async fillM365CertificateCredentials(credentials: M365ProviderCredential): Promise { + // Fill the m365 certificate credentials form + + if (credentials.clientId) { + await this.m365ClientIdInput.fill(credentials.clientId); + } + if (credentials.certificateContent) { + await this.m365CertificateContentInput.fill(credentials.certificateContent); + } + if (credentials.tenantId) { + await this.m365TenantIdInput.fill(credentials.tenantId); + } + } + async verifyPageLoaded(): Promise { // Verify the providers page is loaded @@ -409,6 +519,24 @@ export class ProvidersPage extends BasePage { await expect(this.roleCredentialsRadio).toBeVisible(); } + async verifyM365CredentialsPageLoaded(): Promise { + // Verify the M365 credentials page is loaded + + await expect(this.page).toHaveTitle(/Prowler/); + await expect(this.m365ClientIdInput).toBeVisible(); + await expect(this.m365ClientSecretInput).toBeVisible(); + await expect(this.m365TenantIdInput).toBeVisible(); + } + + async verifyM365CertificateCredentialsPageLoaded(): Promise { + // Verify the M365 certificate credentials page is loaded + + await expect(this.page).toHaveTitle(/Prowler/); + await expect(this.m365ClientIdInput).toBeVisible(); + await expect(this.m365TenantIdInput).toBeVisible(); + await expect(this.m365CertificateContentInput).toBeVisible(); + } + async verifyLaunchScanPageLoaded(): Promise { // Verify the launch scan page is loaded diff --git a/ui/tests/providers/providers.md b/ui/tests/providers/providers.md index 675ee167bb..7ed8a74755 100644 --- a/ui/tests/providers/providers.md +++ b/ui/tests/providers/providers.md @@ -159,3 +159,109 @@ - Provider cleanup performed before each test to ensure clean state - Requires valid Azure subscription with appropriate permissions - Client credentials must have sufficient permissions for security scanning + +--- + +## Test Case: `PROVIDER-E2E-004` - Add M365 Provider with Static Credentials + +**Priority:** `critical` + +**Tags:** + +- type → @e2e, @serial +- feature → @providers +- provider → @m365 + +**Description/Objective:** Validates the complete flow of adding a new Microsoft 365 provider using static client credentials (Client ID, Client Secret, Tenant ID) tied to a Domain ID. + +**Preconditions:** + +- Admin user authentication required (admin.auth.setup setup) +- Environment variables configured: E2E_M365_DOMAIN_ID, E2E_M365_CLIENT_ID, E2E_M365_SECRET_ID, E2E_M365_TENANT_ID +- Remove any existing provider with the same Domain ID before starting the test +- This test must be run serially and never in parallel with other tests, as it requires the Domain ID not to be already registered beforehand. + +### Flow Steps: + +1. Navigate to providers page +2. Click "Add Provider" button +3. Select M365 provider type +4. Fill provider details (domain ID and alias) +5. Select static credentials type +6. Fill M365 credentials (client ID, client secret, tenant ID) +7. Launch initial scan +8. Verify redirect to provider management page + +### Expected Result: + +- M365 provider successfully added with static credentials +- Initial scan launched successfully +- User redirected to provider details page + +### Key verification points: + +- Provider page loads correctly +- Connect account page displays M365 option +- M365 credentials form accepts all required fields +- Launch scan page appears +- Successful redirect to provider page after scan launch + +### Notes: + +- Test uses environment variables for M365 credentials +- Provider cleanup performed before each test to ensure clean state +- Requires valid Microsoft 365 tenant with appropriate permissions +- Client credentials must have sufficient permissions for security scanning + +--- + +## Test Case: `PROVIDER-E2E-005` - Add M365 Provider with Certificate Credentials + +**Priority:** `critical` + +**Tags:** + +- type → @e2e, @serial +- feature → @providers +- provider → @m365 + +**Description/Objective:** Validates the complete flow of adding a new Microsoft 365 provider using certificate-based authentication (Client ID, Tenant ID, Certificate Content) tied to a Domain ID. + +**Preconditions:** + +- Admin user authentication required (admin.auth.setup setup) +- Environment variables configured: E2E_M365_DOMAIN_ID, E2E_M365_CLIENT_ID, E2E_M365_TENANT_ID, E2E_M365_CERTIFICATE_CONTENT +- Remove any existing provider with the same Domain ID before starting the test +- This test must be run serially and never in parallel with other tests, as it requires the Domain ID not to be already registered beforehand. + +### Flow Steps: + +1. Navigate to providers page +2. Click "Add Provider" button +3. Select M365 provider type +4. Fill provider details (domain ID and alias) +5. Select certificate credentials type +6. Fill M365 certificate credentials (client ID, tenant ID, certificate content) +7. Launch initial scan +8. Verify redirect to provider management page + +### Expected Result: + +- M365 provider successfully added with certificate credentials +- Initial scan launched successfully +- User redirected to provider details page + +### Key verification points: + +- Provider page loads correctly +- Connect account page displays M365 option +- Certificate credentials form accepts all required fields +- Launch scan page appears +- Successful redirect to provider page after scan launch + +### Notes: + +- Test uses environment variables for M365 certificate credentials +- Provider cleanup performed before each test to ensure clean state +- Requires valid Microsoft 365 tenant with certificate-based authentication +- Certificate must be properly configured and have sufficient permissions for security scanning diff --git a/ui/tests/providers/providers.spec.ts b/ui/tests/providers/providers.spec.ts index c55f76e854..6471a17863 100644 --- a/ui/tests/providers/providers.spec.ts +++ b/ui/tests/providers/providers.spec.ts @@ -1,5 +1,4 @@ import { test } from "@playwright/test"; -import { ScansPage } from "../scans/scans-page"; import { ProvidersPage, AWSProviderData, @@ -8,7 +7,11 @@ import { AZUREProviderData, AZUREProviderCredential, AZURE_CREDENTIAL_OPTIONS, + M365ProviderData, + M365ProviderCredential, + M365_CREDENTIAL_OPTIONS, } from "./providers-page"; +import { ScansPage } from "../scans/scans-page"; test.describe("Add Provider", () => { test.describe.serial("Add AWS Provider", () => { @@ -260,4 +263,176 @@ test.describe("Add Provider", () => { }, ); }); + + test.describe.serial("Add M365 Provider", () => { + // Providers page object + let providersPage: ProvidersPage; + let scansPage: ScansPage; + + // Test data from environment variables + const domainId = process.env.E2E_M365_DOMAIN_ID; + const clientId = process.env.E2E_M365_CLIENT_ID; + const tenantId = process.env.E2E_M365_TENANT_ID; + + // Validate required environment variables + if (!domainId || !clientId || !tenantId) { + throw new Error( + "E2E_M365_DOMAIN_ID, E2E_M365_CLIENT_ID, and E2E_M365_TENANT_ID environment variables are not set", + ); + } + + // Setup before each test + test.beforeEach(async ({ page }) => { + providersPage = new ProvidersPage(page); + // Clean up existing provider to ensure clean test state + await providersPage.deleteProviderIfExists(domainId); + }); + + // Use admin authentication for provider management + test.use({ storageState: "playwright/.auth/admin_user.json" }); + + test( + "should add a new M365 provider with static credentials", + { + tag: [ + "@critical", + "@e2e", + "@providers", + "@m365", + "@serial", + "@PROVIDER-E2E-004", + ], + }, + async ({ page }) => { + // Validate required environment variables + const clientSecret = process.env.E2E_M365_SECRET_ID; + + if (!clientSecret) { + throw new Error("E2E_M365_SECRET_ID environment variable is not set"); + } + // Prepare test data for M365 provider + const m365ProviderData: M365ProviderData = { + domainId: domainId, + alias: "Test E2E M365 Account - Credentials", + }; + + // Prepare static credentials + const m365Credentials: M365ProviderCredential = { + type: M365_CREDENTIAL_OPTIONS.M365_CREDENTIALS, + clientId: clientId, + clientSecret: clientSecret, + tenantId: tenantId, + }; + + // Navigate to providers page + await providersPage.goto(); + await providersPage.verifyPageLoaded(); + + // Start adding new provider + await providersPage.clickAddProvider(); + await providersPage.verifyConnectAccountPageLoaded(); + + // Select M365 provider + await providersPage.selectM365Provider(); + + // Fill provider details + await providersPage.fillM365ProviderDetails(m365ProviderData); + await providersPage.clickNext(); + + // Select static credentials type + await providersPage.selectM365CredentialsType( + M365_CREDENTIAL_OPTIONS.M365_CREDENTIALS, + ); + + // Verify M365 credentials page is loaded + await providersPage.verifyM365CredentialsPageLoaded(); + + // Fill static credentials details + await providersPage.fillM365Credentials(m365Credentials); + await providersPage.clickNext(); + + // Launch scan + await providersPage.verifyLaunchScanPageLoaded(); + await providersPage.clickNext(); + + // Wait for redirect to scan page + scansPage = new ScansPage(page); + await scansPage.verifyPageLoaded(); + }, + ); + + test( + "should add a new M365 provider with certificate", + { + tag: [ + "@critical", + "@e2e", + "@providers", + "@m365", + "@serial", + "@PROVIDER-E2E-005", + ], + }, + async ({ page }) => { + // Validate required environment variables + const certificateContent = process.env.E2E_M365_CERTIFICATE_CONTENT; + + if (!certificateContent) { + throw new Error( + "E2E_M365_CERTIFICATE_CONTENT environment variable is not set", + ); + } + + // Prepare test data for M365 provider + const m365ProviderData: M365ProviderData = { + domainId: domainId, + alias: "Test E2E M365 Account - Certificate", + }; + + // Prepare static credentials + const m365Credentials: M365ProviderCredential = { + type: M365_CREDENTIAL_OPTIONS.M365_CERTIFICATE_CREDENTIALS, + clientId: clientId, + tenantId: tenantId, + certificateContent: certificateContent, + }; + + // Navigate to providers page + await providersPage.goto(); + await providersPage.verifyPageLoaded(); + + // Start adding new provider + await providersPage.clickAddProvider(); + await providersPage.verifyConnectAccountPageLoaded(); + + // Select M365 provider + await providersPage.selectM365Provider(); + + // Fill provider details + await providersPage.fillM365ProviderDetails(m365ProviderData); + await providersPage.clickNext(); + + // Select static credentials type + await providersPage.selectM365CredentialsType( + M365_CREDENTIAL_OPTIONS.M365_CERTIFICATE_CREDENTIALS, + ); + + // Verify M365 certificate credentials page is loaded + await providersPage.verifyM365CertificateCredentialsPageLoaded(); + + // Fill static credentials details + await providersPage.fillM365CertificateCredentials(m365Credentials); + await providersPage.clickNext(); + + // Launch scan + await providersPage.verifyLaunchScanPageLoaded(); + await providersPage.clickNext(); + + // Wait for redirect to scan page + scansPage = new ScansPage(page); + await scansPage.verifyPageLoaded(); + }, + ); + }); + }); diff --git a/ui/tests/setups/manage-cloud-providers.auth.setup.ts b/ui/tests/setups/manage-cloud-providers.auth.setup.ts index 205e2b50e1..0213ab0cb2 100644 --- a/ui/tests/setups/manage-cloud-providers.auth.setup.ts +++ b/ui/tests/setups/manage-cloud-providers.auth.setup.ts @@ -6,7 +6,7 @@ const manageCloudProvidersUserFile = 'playwright/.auth/manage_cloud_providers_us authManageCloudProvidersSetup('authenticate as manage cloud providers e2e user', async ({ page }) => { const cloudProvidersEmail = process.env.E2E_MANAGE_CLOUD_PROVIDERS_USER; const cloudProvidersPassword = process.env.E2E_MANAGE_CLOUD_PROVIDERS_PASSWORD; - + if (!cloudProvidersEmail || !cloudProvidersPassword) { throw new Error('E2E_MANAGE_CLOUD_PROVIDERS_USER and E2E_MANAGE_CLOUD_PROVIDERS_PASSWORD environment variables are required'); }