mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-01-25 02:08:11 +00:00
fix(oci): pass provider UID to update credentials forms (#9746)
This commit is contained in:
@@ -21,6 +21,14 @@ All notable changes to the **Prowler UI** are documented in this file.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [1.16.2] (Prowler v5.16.2) (UNRELEASED)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- OCI update credentials form failing silently due to missing provider UID [(#9746)](https://github.com/prowler-cloud/prowler/pull/9746)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [1.16.1] (Prowler v5.16.1)
|
## [1.16.1] (Prowler v5.16.1)
|
||||||
|
|
||||||
### 🔄 Changed
|
### 🔄 Changed
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { redirect } from "next/navigation";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { getProvider } from "@/actions/providers/providers";
|
||||||
import { CredentialsUpdateInfo } from "@/components/providers";
|
import { CredentialsUpdateInfo } from "@/components/providers";
|
||||||
import {
|
import {
|
||||||
UpdateViaCredentialsForm,
|
UpdateViaCredentialsForm,
|
||||||
@@ -20,9 +22,24 @@ interface Props {
|
|||||||
|
|
||||||
export default async function UpdateCredentialsPage({ searchParams }: Props) {
|
export default async function UpdateCredentialsPage({ searchParams }: Props) {
|
||||||
const resolvedSearchParams = await searchParams;
|
const resolvedSearchParams = await searchParams;
|
||||||
const { type: providerType, via } = resolvedSearchParams;
|
const { type: providerType, via, id: providerId } = resolvedSearchParams;
|
||||||
|
|
||||||
|
if (!providerId) {
|
||||||
|
redirect("/providers");
|
||||||
|
}
|
||||||
|
|
||||||
const formType = getProviderFormType(providerType, via);
|
const formType = getProviderFormType(providerType, via);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("id", providerId);
|
||||||
|
const providerResponse = await getProvider(formData);
|
||||||
|
|
||||||
|
if (providerResponse?.errors) {
|
||||||
|
redirect("/providers");
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerUid = providerResponse?.data?.attributes?.uid;
|
||||||
|
|
||||||
switch (formType) {
|
switch (formType) {
|
||||||
case "selector":
|
case "selector":
|
||||||
return (
|
return (
|
||||||
@@ -30,14 +47,27 @@ export default async function UpdateCredentialsPage({ searchParams }: Props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
case "credentials":
|
case "credentials":
|
||||||
return <UpdateViaCredentialsForm searchParams={resolvedSearchParams} />;
|
return (
|
||||||
|
<UpdateViaCredentialsForm
|
||||||
|
searchParams={resolvedSearchParams}
|
||||||
|
providerUid={providerUid}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
case "role":
|
case "role":
|
||||||
return <UpdateViaRoleForm searchParams={resolvedSearchParams} />;
|
return (
|
||||||
|
<UpdateViaRoleForm
|
||||||
|
searchParams={resolvedSearchParams}
|
||||||
|
providerUid={providerUid}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
case "service-account":
|
case "service-account":
|
||||||
return (
|
return (
|
||||||
<UpdateViaServiceAccountForm searchParams={resolvedSearchParams} />
|
<UpdateViaServiceAccountForm
|
||||||
|
searchParams={resolvedSearchParams}
|
||||||
|
providerUid={providerUid}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import { BaseCredentialsForm } from "./base-credentials-form";
|
|||||||
|
|
||||||
export const UpdateViaCredentialsForm = ({
|
export const UpdateViaCredentialsForm = ({
|
||||||
searchParams,
|
searchParams,
|
||||||
|
providerUid,
|
||||||
}: {
|
}: {
|
||||||
searchParams: { type: string; id: string; secretId?: string };
|
searchParams: { type: string; id: string; secretId?: string };
|
||||||
|
providerUid?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const providerType = searchParams.type as ProviderType;
|
const providerType = searchParams.type as ProviderType;
|
||||||
const providerId = searchParams.id;
|
const providerId = searchParams.id;
|
||||||
@@ -24,6 +26,7 @@ export const UpdateViaCredentialsForm = ({
|
|||||||
<BaseCredentialsForm
|
<BaseCredentialsForm
|
||||||
providerType={providerType}
|
providerType={providerType}
|
||||||
providerId={providerId}
|
providerId={providerId}
|
||||||
|
providerUid={providerUid}
|
||||||
onSubmit={handleUpdateCredentials}
|
onSubmit={handleUpdateCredentials}
|
||||||
successNavigationUrl={successNavigationUrl}
|
successNavigationUrl={successNavigationUrl}
|
||||||
submitButtonText="Next"
|
submitButtonText="Next"
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import { BaseCredentialsForm } from "./base-credentials-form";
|
|||||||
|
|
||||||
export const UpdateViaRoleForm = ({
|
export const UpdateViaRoleForm = ({
|
||||||
searchParams,
|
searchParams,
|
||||||
|
providerUid,
|
||||||
}: {
|
}: {
|
||||||
searchParams: { type: string; id: string; secretId?: string };
|
searchParams: { type: string; id: string; secretId?: string };
|
||||||
|
providerUid?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const providerType = searchParams.type as ProviderType;
|
const providerType = searchParams.type as ProviderType;
|
||||||
const providerId = searchParams.id;
|
const providerId = searchParams.id;
|
||||||
@@ -24,6 +26,7 @@ export const UpdateViaRoleForm = ({
|
|||||||
<BaseCredentialsForm
|
<BaseCredentialsForm
|
||||||
providerType={providerType}
|
providerType={providerType}
|
||||||
providerId={providerId}
|
providerId={providerId}
|
||||||
|
providerUid={providerUid}
|
||||||
onSubmit={handleUpdateCredentials}
|
onSubmit={handleUpdateCredentials}
|
||||||
successNavigationUrl={successNavigationUrl}
|
successNavigationUrl={successNavigationUrl}
|
||||||
submitButtonText="Next"
|
submitButtonText="Next"
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import { BaseCredentialsForm } from "./base-credentials-form";
|
|||||||
|
|
||||||
export const UpdateViaServiceAccountForm = ({
|
export const UpdateViaServiceAccountForm = ({
|
||||||
searchParams,
|
searchParams,
|
||||||
|
providerUid,
|
||||||
}: {
|
}: {
|
||||||
searchParams: { type: string; id: string; secretId?: string };
|
searchParams: { type: string; id: string; secretId?: string };
|
||||||
|
providerUid?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const providerType = searchParams.type as ProviderType;
|
const providerType = searchParams.type as ProviderType;
|
||||||
const providerId = searchParams.id;
|
const providerId = searchParams.id;
|
||||||
@@ -24,6 +26,7 @@ export const UpdateViaServiceAccountForm = ({
|
|||||||
<BaseCredentialsForm
|
<BaseCredentialsForm
|
||||||
providerType={providerType}
|
providerType={providerType}
|
||||||
providerId={providerId}
|
providerId={providerId}
|
||||||
|
providerUid={providerUid}
|
||||||
onSubmit={handleUpdateCredentials}
|
onSubmit={handleUpdateCredentials}
|
||||||
successNavigationUrl={successNavigationUrl}
|
successNavigationUrl={successNavigationUrl}
|
||||||
submitButtonText="Next"
|
submitButtonText="Next"
|
||||||
|
|||||||
@@ -607,18 +607,22 @@ export class ProvidersPage extends BasePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback logic: try finding any common primary action buttons in expected order
|
// Fallback logic: try finding any common primary action buttons in expected order
|
||||||
const candidates: Array<{ name: string | RegExp }> = [
|
const candidates: Array<{ name: string | RegExp; exact?: boolean }> = [
|
||||||
{ name: "Next" }, // Try the "Next" button
|
{ name: "Next", exact: true }, // Try the "Next" button (exact match to avoid Next.js dev tools)
|
||||||
{ name: "Save" }, // Try the "Save" button
|
{ name: "Save", exact: true }, // Try the "Save" button
|
||||||
{ name: "Launch scan" }, // Try the "Launch scan" button
|
{ name: "Launch scan" }, // Try the "Launch scan" button
|
||||||
{ name: /Continue|Proceed/i }, // Try "Continue" or "Proceed" (case-insensitive)
|
{ name: /Continue|Proceed/i }, // Try "Continue" or "Proceed" (case-insensitive)
|
||||||
];
|
];
|
||||||
|
|
||||||
// Try each candidate name and click it if found
|
// Try each candidate name and click it if found
|
||||||
for (const candidate of candidates) {
|
for (const candidate of candidates) {
|
||||||
const btn = this.page.getByRole("button", {
|
// Exclude Next.js dev tools button by filtering out buttons with aria-haspopup attribute
|
||||||
name: candidate.name,
|
const btn = this.page
|
||||||
});
|
.getByRole("button", {
|
||||||
|
name: candidate.name,
|
||||||
|
exact: candidate.exact,
|
||||||
|
})
|
||||||
|
.and(this.page.locator(":not([aria-haspopup])"));
|
||||||
|
|
||||||
if (await btn.count()) {
|
if (await btn.count()) {
|
||||||
await btn.click();
|
await btn.click();
|
||||||
@@ -847,7 +851,7 @@ export class ProvidersPage extends BasePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async verifyOCICredentialsPageLoaded(): Promise<void> {
|
async verifyOCICredentialsPageLoaded(): Promise<void> {
|
||||||
// Verify the OCI credentials page is loaded
|
// Verify the OCI credentials page is loaded (add flow - all fields visible)
|
||||||
|
|
||||||
await this.verifyPageHasProwlerTitle();
|
await this.verifyPageHasProwlerTitle();
|
||||||
await expect(this.ociTenancyIdInput).toBeVisible();
|
await expect(this.ociTenancyIdInput).toBeVisible();
|
||||||
@@ -857,6 +861,17 @@ export class ProvidersPage extends BasePage {
|
|||||||
await expect(this.ociRegionInput).toBeVisible();
|
await expect(this.ociRegionInput).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async verifyOCIUpdateCredentialsPageLoaded(): Promise<void> {
|
||||||
|
// Verify the OCI update credentials page is loaded
|
||||||
|
// Note: Tenancy OCID is hidden in update flow (auto-populated from provider UID)
|
||||||
|
|
||||||
|
await this.verifyPageHasProwlerTitle();
|
||||||
|
await expect(this.ociUserIdInput).toBeVisible();
|
||||||
|
await expect(this.ociFingerprintInput).toBeVisible();
|
||||||
|
await expect(this.ociKeyContentInput).toBeVisible();
|
||||||
|
await expect(this.ociRegionInput).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
async verifyPageLoaded(): Promise<void> {
|
async verifyPageLoaded(): Promise<void> {
|
||||||
// Verify the providers page is loaded
|
// Verify the providers page is loaded
|
||||||
|
|
||||||
@@ -995,4 +1010,42 @@ export class ProvidersPage extends BasePage {
|
|||||||
throw new Error(`Invalid authentication method: ${method}`);
|
throw new Error(`Invalid authentication method: ${method}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async clickProviderRowActions(providerUid: string): Promise<void> {
|
||||||
|
// Click the actions dropdown for a specific provider row
|
||||||
|
const row = this.providersTable.locator("tbody tr", {
|
||||||
|
hasText: providerUid,
|
||||||
|
});
|
||||||
|
await expect(row).toBeVisible();
|
||||||
|
|
||||||
|
// Click the dropdown trigger - it's the last button in the row (after the copy button)
|
||||||
|
const actionsButton = row.locator("button").last();
|
||||||
|
await actionsButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickUpdateCredentials(providerUid: string): Promise<void> {
|
||||||
|
// Click update credentials for a specific provider
|
||||||
|
await this.clickProviderRowActions(providerUid);
|
||||||
|
|
||||||
|
// Wait for dropdown menu to stabilize and click Update Credentials
|
||||||
|
const updateCredentialsOption = this.page.getByRole("menuitem", {
|
||||||
|
name: /Update Credentials/i,
|
||||||
|
});
|
||||||
|
await expect(updateCredentialsOption).toBeVisible();
|
||||||
|
// Wait a bit for the menu to stabilize before clicking
|
||||||
|
await this.page.waitForTimeout(100);
|
||||||
|
await updateCredentialsOption.click({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyUpdateCredentialsPageLoaded(): Promise<void> {
|
||||||
|
// Verify the update credentials page is loaded
|
||||||
|
await this.verifyPageHasProwlerTitle();
|
||||||
|
await expect(this.page).toHaveURL(/\/providers\/update-credentials/);
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyTestConnectionPageLoaded(): Promise<void> {
|
||||||
|
// Verify the test connection page is loaded
|
||||||
|
await this.verifyPageHasProwlerTitle();
|
||||||
|
await expect(this.page).toHaveURL(/\/providers\/test-connection/);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -708,3 +708,61 @@
|
|||||||
- Provider cleanup performed before each test to ensure clean state
|
- Provider cleanup performed before each test to ensure clean state
|
||||||
- Requires valid OCI account with API Key set up
|
- Requires valid OCI account with API Key set up
|
||||||
- API Key credential type is automatically used for OCI providers
|
- API Key credential type is automatically used for OCI providers
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Test Case: `PROVIDER-E2E-013` - Update OCI Provider Credentials
|
||||||
|
|
||||||
|
**Priority:** `normal`
|
||||||
|
|
||||||
|
**Tags:**
|
||||||
|
|
||||||
|
- type → @e2e, @serial
|
||||||
|
- feature → @providers
|
||||||
|
- provider → @oci
|
||||||
|
|
||||||
|
**Description/Objective:** Validates the complete flow of updating credentials for an existing OCI provider. This test verifies that the provider UID is correctly passed to the update credentials form, which is required for OCI credential validation.
|
||||||
|
|
||||||
|
**Preconditions:**
|
||||||
|
|
||||||
|
- Admin user authentication required (admin.auth.setup setup)
|
||||||
|
- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT, E2E_OCI_REGION
|
||||||
|
- An OCI provider with the specified Tenancy ID must already exist (run PROVIDER-E2E-012 first)
|
||||||
|
- This test must be run serially and never in parallel with other tests
|
||||||
|
|
||||||
|
### Flow Steps:
|
||||||
|
|
||||||
|
1. Navigate to providers page
|
||||||
|
2. Verify OCI provider exists in the table
|
||||||
|
3. Click row actions menu for the OCI provider
|
||||||
|
4. Click "Update Credentials" option
|
||||||
|
5. Verify update credentials page is loaded
|
||||||
|
6. Verify OCI credentials form fields are visible (confirms providerUid is loaded)
|
||||||
|
7. Fill OCI credentials (user ID, fingerprint, key content, region)
|
||||||
|
8. Click Next to submit
|
||||||
|
9. Verify successful navigation to test connection page
|
||||||
|
|
||||||
|
### Expected Result:
|
||||||
|
|
||||||
|
- Update credentials page loads successfully
|
||||||
|
- OCI credentials form is displayed with all required fields
|
||||||
|
- Provider UID is correctly passed to the form (hidden field populated)
|
||||||
|
- Credentials can be updated and submitted
|
||||||
|
- User is redirected to test connection page after successful update
|
||||||
|
|
||||||
|
### Key verification points:
|
||||||
|
|
||||||
|
- Provider page loads correctly
|
||||||
|
- OCI provider row is visible in providers table
|
||||||
|
- Row actions dropdown opens and displays "Update Credentials" option
|
||||||
|
- Update credentials page URL contains correct parameters
|
||||||
|
- OCI credentials form displays all fields (tenancy ID, user ID, fingerprint, key content, region)
|
||||||
|
- Form submission succeeds (no silent failures due to missing provider UID)
|
||||||
|
- Successful redirect to test connection page
|
||||||
|
|
||||||
|
### Notes:
|
||||||
|
|
||||||
|
- Test uses same environment variables as PROVIDER-E2E-012 (add OCI provider)
|
||||||
|
- Requires PROVIDER-E2E-012 to be run first to create the OCI provider
|
||||||
|
- This test validates the fix for OCI update credentials form failing silently due to missing provider UID
|
||||||
|
- The provider UID is required for OCI credential validation (tenancy field auto-populated from UID)
|
||||||
|
|||||||
@@ -1139,3 +1139,87 @@ test.describe("Add Provider", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe("Update Provider Credentials", () => {
|
||||||
|
test.describe.serial("Update OCI Provider Credentials", () => {
|
||||||
|
let providersPage: ProvidersPage;
|
||||||
|
|
||||||
|
// Test data from environment variables (same as add OCI provider test)
|
||||||
|
const tenancyId = process.env.E2E_OCI_TENANCY_ID;
|
||||||
|
const userId = process.env.E2E_OCI_USER_ID;
|
||||||
|
const fingerprint = process.env.E2E_OCI_FINGERPRINT;
|
||||||
|
const keyContent = process.env.E2E_OCI_KEY_CONTENT;
|
||||||
|
const region = process.env.E2E_OCI_REGION;
|
||||||
|
|
||||||
|
// Validate required environment variables
|
||||||
|
if (!tenancyId || !userId || !fingerprint || !keyContent || !region) {
|
||||||
|
throw new Error(
|
||||||
|
"E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT, and E2E_OCI_REGION environment variables are not set",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup before each test
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
providersPage = new ProvidersPage(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use admin authentication for provider management
|
||||||
|
test.use({ storageState: "playwright/.auth/admin_user.json" });
|
||||||
|
|
||||||
|
test(
|
||||||
|
"should update OCI provider credentials successfully",
|
||||||
|
{
|
||||||
|
tag: [
|
||||||
|
"@e2e",
|
||||||
|
"@providers",
|
||||||
|
"@oci",
|
||||||
|
"@serial",
|
||||||
|
"@PROVIDER-E2E-013",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
// Prepare updated credentials
|
||||||
|
const ociCredentials: OCIProviderCredential = {
|
||||||
|
type: OCI_CREDENTIAL_OPTIONS.OCI_API_KEY,
|
||||||
|
tenancyId: tenancyId,
|
||||||
|
userId: userId,
|
||||||
|
fingerprint: fingerprint,
|
||||||
|
keyContent: keyContent,
|
||||||
|
region: region,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Navigate to providers page
|
||||||
|
await providersPage.goto();
|
||||||
|
await providersPage.verifyPageLoaded();
|
||||||
|
|
||||||
|
// Verify OCI provider exists in the table
|
||||||
|
const providerExists =
|
||||||
|
await providersPage.verifySingleRowForProviderUID(tenancyId);
|
||||||
|
if (!providerExists) {
|
||||||
|
throw new Error(
|
||||||
|
`OCI provider with tenancy ID ${tenancyId} not found. Run the add OCI provider test first.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click update credentials for the OCI provider
|
||||||
|
await providersPage.clickUpdateCredentials(tenancyId);
|
||||||
|
|
||||||
|
// Verify update credentials page is loaded
|
||||||
|
await providersPage.verifyUpdateCredentialsPageLoaded();
|
||||||
|
|
||||||
|
// Verify OCI credentials form fields are visible (confirms providerUid is loaded)
|
||||||
|
// Note: Tenancy OCID is hidden in update flow (auto-populated from provider UID)
|
||||||
|
await providersPage.verifyOCIUpdateCredentialsPageLoaded();
|
||||||
|
|
||||||
|
// Fill updated credentials
|
||||||
|
await providersPage.fillOCICredentials(ociCredentials);
|
||||||
|
|
||||||
|
// Click Next to submit
|
||||||
|
await providersPage.clickNext();
|
||||||
|
|
||||||
|
// Verify successful navigation to test connection page
|
||||||
|
await providersPage.verifyTestConnectionPageLoaded();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user