mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-04 02:04:06 +00:00
558 lines
22 KiB
TypeScript
558 lines
22 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { PROVIDER_WIZARD_STEP } from "@/types/provider-wizard";
|
|
|
|
import {
|
|
buildCloudflareAccountOwnedApiTokenUrl,
|
|
buildGitHubPersonalAccessTokenOrgUrl,
|
|
getAWSCredentialsTemplateLinks,
|
|
getAWSOrgDeploymentQuickLink,
|
|
PRECONFIGURED_CREDENTIAL_URLS,
|
|
getProviderHelpText,
|
|
PROWLER_CF_TEMPLATE_URL,
|
|
} from "./external-urls";
|
|
|
|
function getQuickCreateParams(link: string): URLSearchParams {
|
|
const hashQuery = new URL(link).hash.split("?")[1];
|
|
return new URLSearchParams(hashQuery);
|
|
}
|
|
|
|
describe("getAWSCredentialsTemplateLinks", () => {
|
|
it("should preserve dynamic values as single CloudFormation parameters", () => {
|
|
// Given
|
|
const externalId = "tenant&id";
|
|
const bucketName = "bucket¶m_DeployStackSet=false";
|
|
|
|
// When
|
|
const links = getAWSCredentialsTemplateLinks(
|
|
externalId,
|
|
bucketName,
|
|
"amazon_s3",
|
|
"123456789012",
|
|
);
|
|
const params = getQuickCreateParams(links.cloudformationQuickLink);
|
|
|
|
// Then
|
|
expect(params.get("param_ExternalId")).toBe(externalId);
|
|
expect(params.get("param_S3IntegrationBucketName")).toBe(bucketName);
|
|
expect(params.get("param_S3IntegrationBucketAccountId")).toBe(
|
|
"123456789012",
|
|
);
|
|
expect(params.get("param_DeployStackSet")).toBeNull();
|
|
});
|
|
|
|
it("should omit S3 integration parameters when the bucket account id is missing", () => {
|
|
// Given - the template requires S3IntegrationBucketAccountId whenever
|
|
// EnableS3Integration is true, so an incomplete link would fail CFN
|
|
// validation. This is reachable from the edit-credentials flow, where the
|
|
// account id can resolve to an empty string.
|
|
const externalId = "tenant-id";
|
|
const bucketName = "my-findings-bucket";
|
|
|
|
// When
|
|
const links = getAWSCredentialsTemplateLinks(
|
|
externalId,
|
|
bucketName,
|
|
"amazon_s3",
|
|
);
|
|
const params = getQuickCreateParams(links.cloudformationQuickLink);
|
|
|
|
// Then
|
|
expect(params.get("param_ExternalId")).toBe(externalId);
|
|
expect(params.get("param_EnableS3Integration")).toBeNull();
|
|
expect(params.get("param_S3IntegrationBucketName")).toBeNull();
|
|
expect(params.get("param_S3IntegrationBucketAccountId")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("getAWSOrgDeploymentQuickLink", () => {
|
|
it("should include the one-step organization deployment parameters", () => {
|
|
// Given
|
|
const externalId = "tenant&id";
|
|
const organizationalUnitId = "ou-abcd-12345678";
|
|
|
|
// When
|
|
const link = getAWSOrgDeploymentQuickLink({
|
|
externalId,
|
|
organizationalUnitId,
|
|
deployFromDelegatedAdmin: true,
|
|
});
|
|
const params = getQuickCreateParams(link);
|
|
|
|
// Then
|
|
expect(params.get("templateURL")).toBe(PROWLER_CF_TEMPLATE_URL);
|
|
expect(params.get("param_ExternalId")).toBe(externalId);
|
|
expect(params.get("param_AWSOrganizationalUnitId")).toBe(
|
|
organizationalUnitId,
|
|
);
|
|
expect(params.get("param_EnableOrganizations")).toBe("true");
|
|
expect(params.get("param_DeployLocalRole")).toBe("true");
|
|
expect(params.get("param_DeployStackSet")).toBe("true");
|
|
expect(params.get("param_DeployFromDelegatedAdmin")).toBe("true");
|
|
});
|
|
|
|
it("should omit delegated administrator mode for management accounts", () => {
|
|
// Given
|
|
const organizationalUnitId = "r-abcd";
|
|
|
|
// When
|
|
const link = getAWSOrgDeploymentQuickLink({
|
|
externalId: "tenant-id",
|
|
organizationalUnitId,
|
|
});
|
|
const params = getQuickCreateParams(link);
|
|
|
|
// Then
|
|
expect(params.get("param_AWSOrganizationalUnitId")).toBe(
|
|
organizationalUnitId,
|
|
);
|
|
expect(params.get("param_DeployFromDelegatedAdmin")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("PRECONFIGURED_CREDENTIAL_URLS", () => {
|
|
it("keeps the Cloudflare User API Token URL under the profile route with the seven required read scopes", () => {
|
|
// Snapshot check: fixes the exact URL so a stray edit to the permission
|
|
// scopes, token name, account/zone selectors or console origin trips a
|
|
// failing test instead of silently shipping a broken pre-configured
|
|
// token flow to users. Matches the "User API Token" link in
|
|
// docs/user-guide/providers/cloudflare/authentication.mdx.
|
|
expect(PRECONFIGURED_CREDENTIAL_URLS.CLOUDFLARE_API_TOKEN_USER).toBe(
|
|
"https://dash.cloudflare.com/profile/api-tokens?permissionGroupKeys=%5B%7B%22key%22%3A%22account_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22zone%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22zone_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22dns%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22ssl_and_certificates%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22bot_management%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22zone_waf%22%2C%22type%22%3A%22read%22%7D%5D&accountId=%2A&zoneId=all&name=Prowler%20Security%20Scanner",
|
|
);
|
|
});
|
|
|
|
it("carries the seven Prowler read scopes as decoded permissionGroupKeys on the Cloudflare User API Token URL", () => {
|
|
// Semantic contract: the URL must request read on account_settings, zone,
|
|
// zone_settings, dns, ssl_and_certificates, bot_management and zone_waf and reuse the shared Prowler token name.
|
|
const parsed = new URL(
|
|
PRECONFIGURED_CREDENTIAL_URLS.CLOUDFLARE_API_TOKEN_USER,
|
|
);
|
|
const permissionGroupKeys = JSON.parse(
|
|
parsed.searchParams.get("permissionGroupKeys") ?? "[]",
|
|
);
|
|
|
|
expect(permissionGroupKeys).toEqual([
|
|
{ key: "account_settings", type: "read" },
|
|
{ key: "zone", type: "read" },
|
|
{ key: "zone_settings", type: "read" },
|
|
{ key: "dns", type: "read" },
|
|
{ key: "ssl_and_certificates", type: "read" },
|
|
{ key: "bot_management", type: "read" },
|
|
{ key: "zone_waf", type: "read" },
|
|
]);
|
|
expect(parsed.searchParams.get("name")).toBe("Prowler Security Scanner");
|
|
});
|
|
|
|
it("keeps the GitHub user-scope PAT URL pre-filled with the four required read permissions", () => {
|
|
// Snapshot check: fixes the exact URL so any accidental scope broadening
|
|
// (or a rename of `expires_in` / permission slugs on GitHub's side) trips
|
|
// a failing test. Matches the "user repositories" URL published in
|
|
// docs/user-guide/providers/github/authentication.mdx.
|
|
expect(
|
|
PRECONFIGURED_CREDENTIAL_URLS.GITHUB_PERSONAL_ACCESS_TOKEN_USER,
|
|
).toBe(
|
|
"https://github.com/settings/personal-access-tokens/new?name=Prowler+Security+Scanner&description=Fine-grained+PAT+for+Prowler+security+scanning&expires_in=90&administration=read&contents=read&vulnerability_alerts=read&emails=read",
|
|
);
|
|
});
|
|
|
|
it("carries only read-level permissions on the GitHub user-scope PAT URL and no organization-only scopes", () => {
|
|
// Semantic contract: every permission query-param must be `read`, and the
|
|
// two organization-only permissions must NOT leak into the user URL
|
|
// (otherwise GitHub would reject the whole permission set with a
|
|
// Resource-Owner mismatch when the caller is a personal account).
|
|
const parsed = new URL(
|
|
PRECONFIGURED_CREDENTIAL_URLS.GITHUB_PERSONAL_ACCESS_TOKEN_USER,
|
|
);
|
|
|
|
expect(parsed.searchParams.get("name")).toBe("Prowler Security Scanner");
|
|
expect(parsed.searchParams.get("expires_in")).toBe("90");
|
|
expect(parsed.searchParams.get("organization_administration")).toBeNull();
|
|
expect(parsed.searchParams.get("members")).toBeNull();
|
|
|
|
const NON_PERMISSION_PARAMS = new Set([
|
|
"name",
|
|
"description",
|
|
"expires_in",
|
|
]);
|
|
for (const [key, value] of Array.from(parsed.searchParams.entries())) {
|
|
if (NON_PERMISSION_PARAMS.has(key)) continue;
|
|
expect(
|
|
value,
|
|
`permission "${key}" should be granted at "read" level`,
|
|
).toBe("read");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("buildCloudflareAccountOwnedApiTokenUrl", () => {
|
|
it("pins the token to the account by routing through the dashboard `to=` param with the account id substituted", () => {
|
|
// Cloudflare's SPA only reads the pre-fill query params
|
|
// (`permissionGroupKeys`, `name`) when the user arrives via the dashboard
|
|
// router with a `to=` value — navigating straight to
|
|
// `/<accountId>/api-tokens/create?params` renders the form but drops the
|
|
// params on the floor. Substituting the account id in place of the docs'
|
|
// `:account` placeholder keeps the pre-fill working and avoids ambiguity
|
|
// for users signed into multiple accounts.
|
|
const url = new URL(
|
|
buildCloudflareAccountOwnedApiTokenUrl(
|
|
"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
|
|
),
|
|
);
|
|
|
|
expect(url.origin).toBe("https://dash.cloudflare.com");
|
|
expect(url.pathname).toBe("/");
|
|
expect(url.searchParams.get("to")).toBe(
|
|
"/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/api-tokens",
|
|
);
|
|
expect(url.searchParams.get("name")).toBe("Prowler Security Scanner");
|
|
const permissionGroupKeys = JSON.parse(
|
|
url.searchParams.get("permissionGroupKeys") ?? "[]",
|
|
);
|
|
expect(permissionGroupKeys).toEqual([
|
|
{ key: "account_settings", type: "read" },
|
|
{ key: "zone", type: "read" },
|
|
{ key: "zone_settings", type: "read" },
|
|
{ key: "dns", type: "read" },
|
|
{ key: "ssl_and_certificates", type: "read" },
|
|
{ key: "bot_management", type: "read" },
|
|
{ key: "zone_waf", type: "read" },
|
|
]);
|
|
});
|
|
|
|
it("keeps the `to=` path unencoded so Cloudflare's router matches it", () => {
|
|
// Cloudflare's router matches on the raw string in `to`, so the slashes
|
|
// inside `/<accountId>/api-tokens` must NOT be percent-encoded.
|
|
// URLSearchParams would encode them; asserting the raw substring guards
|
|
// against a future refactor that swaps the manual query-string build for
|
|
// URLSearchParams.
|
|
const url = buildCloudflareAccountOwnedApiTokenUrl(
|
|
"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
|
|
);
|
|
|
|
expect(url).toContain("?to=/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/api-tokens&");
|
|
});
|
|
|
|
it("URL-encodes account ids that contain characters requiring escaping", () => {
|
|
// Real Cloudflare account ids are hex strings today, but the wizard
|
|
// accepts whatever the user typed. Encoding the account id inside the
|
|
// `to=` path prevents a stray `/` or `#` from silently breaking the URL:
|
|
// an unencoded `#` would truncate the query string, and an unencoded `/`
|
|
// would let Cloudflare mis-parse the account id boundary.
|
|
const url = buildCloudflareAccountOwnedApiTokenUrl("acct/with#gaps");
|
|
|
|
expect(url).toContain("?to=/acct%2Fwith%23gaps/api-tokens&");
|
|
});
|
|
});
|
|
|
|
describe("buildGitHubPersonalAccessTokenOrgUrl", () => {
|
|
it("pins the token Resource Owner via target_name and requests the org-only permissions", () => {
|
|
// Without a `target_name`, GitHub silently ignores the two organization
|
|
// permissions (`organization_administration`, `members`) because the
|
|
// Resource Owner defaults to the caller's personal account. Pinning the
|
|
// owner is what makes the pre-checked org permissions actually surface.
|
|
const url = new URL(buildGitHubPersonalAccessTokenOrgUrl("prowler-cloud"));
|
|
|
|
expect(url.origin + url.pathname).toBe(
|
|
"https://github.com/settings/personal-access-tokens/new",
|
|
);
|
|
expect(url.searchParams.get("target_name")).toBe("prowler-cloud");
|
|
expect(url.searchParams.get("organization_administration")).toBe("read");
|
|
expect(url.searchParams.get("members")).toBe("read");
|
|
expect(url.searchParams.get("administration")).toBe("read");
|
|
expect(url.searchParams.get("contents")).toBe("read");
|
|
expect(url.searchParams.get("vulnerability_alerts")).toBe("read");
|
|
expect(url.searchParams.get("name")).toBe("Prowler Security Scanner");
|
|
expect(url.searchParams.get("expires_in")).toBe("90");
|
|
});
|
|
|
|
it("omits the account-only `emails` permission from the org URL", () => {
|
|
// `emails` is an account-level permission that only makes sense when the
|
|
// Resource Owner is a personal user account. Including it on an org URL
|
|
// would cause GitHub to reject the whole permission set.
|
|
const url = new URL(buildGitHubPersonalAccessTokenOrgUrl("prowler-cloud"));
|
|
|
|
expect(url.searchParams.get("emails")).toBeNull();
|
|
});
|
|
|
|
it("URL-encodes an organization slug that contains characters requiring escaping", () => {
|
|
// GitHub org slugs cannot contain `&` or spaces today, but callers pass
|
|
// whatever the wizard collected verbatim, so the helper must not blindly
|
|
// concatenate. URLSearchParams enforces percent-encoding.
|
|
const url = new URL(
|
|
buildGitHubPersonalAccessTokenOrgUrl("prowler & friends"),
|
|
);
|
|
|
|
expect(url.searchParams.get("target_name")).toBe("prowler & friends");
|
|
expect(url.search).toContain("target_name=prowler+%26+friends");
|
|
});
|
|
});
|
|
|
|
describe("getProviderHelpText", () => {
|
|
const AWS_SHORTLINK = "https://goto.prowler.com/provider-aws";
|
|
const AWS_CREDENTIALS_STEP_DOCS =
|
|
"https://docs.prowler.com/user-guide/providers/aws/getting-started-aws#step-3-set-up-aws-authentication";
|
|
|
|
it("returns the provider shortlink on the connect step", () => {
|
|
// Given the user is picking a provider (no deep-link into auth yet)
|
|
// When
|
|
const { link } = getProviderHelpText("aws", PROVIDER_WIZARD_STEP.CONNECT);
|
|
|
|
// Then
|
|
expect(link).toBe(AWS_SHORTLINK);
|
|
});
|
|
|
|
it("points to the credentials section of the getting-started page on the credentials step", () => {
|
|
// No method picked yet — link should scroll the getting-started page to
|
|
// the credentials/authentication step so the user reads about the choice
|
|
// in the same page they came from.
|
|
const { link } = getProviderHelpText(
|
|
"aws",
|
|
PROVIDER_WIZARD_STEP.CREDENTIALS,
|
|
);
|
|
|
|
expect(link).toBe(AWS_CREDENTIALS_STEP_DOCS);
|
|
});
|
|
|
|
it("points AWS assume role credentials to the exact setup section", () => {
|
|
const { link } = getProviderHelpText(
|
|
"aws",
|
|
PROVIDER_WIZARD_STEP.CREDENTIALS,
|
|
"role",
|
|
);
|
|
|
|
expect(link).toBe(
|
|
"https://docs.prowler.com/user-guide/providers/aws/getting-started-aws#assume-role-recommended",
|
|
);
|
|
});
|
|
|
|
it("points AWS static credentials to the exact setup section", () => {
|
|
const { link } = getProviderHelpText(
|
|
"aws",
|
|
PROVIDER_WIZARD_STEP.CREDENTIALS,
|
|
"credentials",
|
|
);
|
|
|
|
expect(link).toBe(
|
|
"https://docs.prowler.com/user-guide/providers/aws/getting-started-aws#credentials-static-access-keys",
|
|
);
|
|
});
|
|
|
|
it("falls back to the credentials step section when the picked method has no dedicated subsection", () => {
|
|
// GCP's methods render inside a Mintlify <Tabs> component in the docs
|
|
// page, so no per-method anchor exists. Any method-selected variant
|
|
// resolves to the general credentials step anchor.
|
|
const { link } = getProviderHelpText(
|
|
"gcp",
|
|
PROVIDER_WIZARD_STEP.CREDENTIALS,
|
|
"service-account",
|
|
);
|
|
|
|
expect(link).toBe(
|
|
"https://docs.prowler.com/user-guide/providers/gcp/getting-started-gcp#step-3-set-up-gcp-authentication",
|
|
);
|
|
});
|
|
|
|
it("keeps the shortlink on the test connection step", () => {
|
|
// Credentials-step docs are only surfaced while the user is still
|
|
// supplying credentials; after that the shortlink landing is the useful
|
|
// destination.
|
|
const { link } = getProviderHelpText("aws", PROVIDER_WIZARD_STEP.TEST);
|
|
|
|
expect(link).toBe(AWS_SHORTLINK);
|
|
});
|
|
|
|
it("keeps the shortlink on the launch step", () => {
|
|
const { link } = getProviderHelpText("aws", PROVIDER_WIZARD_STEP.LAUNCH);
|
|
|
|
expect(link).toBe(AWS_SHORTLINK);
|
|
});
|
|
|
|
it("resolves the credentials-step link for every supported provider", () => {
|
|
// Guard against silently dropping a provider from
|
|
// PROVIDER_CREDENTIALS_STEP_DOCS_URL. When no auth method is selected
|
|
// yet, every provider should deep-link to its own getting-started
|
|
// credentials section (never to authentication.mdx).
|
|
const cases: Array<[string, string]> = [
|
|
[
|
|
"aws",
|
|
"https://docs.prowler.com/user-guide/providers/aws/getting-started-aws#step-3-set-up-aws-authentication",
|
|
],
|
|
[
|
|
"azure",
|
|
"https://docs.prowler.com/user-guide/providers/azure/getting-started-azure#step-3-add-credentials-to-prowler-cloud",
|
|
],
|
|
[
|
|
"m365",
|
|
"https://docs.prowler.com/user-guide/providers/microsoft365/getting-started-m365#step-3-choose-and-provide-authentication",
|
|
],
|
|
[
|
|
"gcp",
|
|
"https://docs.prowler.com/user-guide/providers/gcp/getting-started-gcp#step-3-set-up-gcp-authentication",
|
|
],
|
|
[
|
|
"kubernetes",
|
|
"https://docs.prowler.com/user-guide/providers/kubernetes/getting-started-k8s#step-2-configure-kubernetes-authentication",
|
|
],
|
|
[
|
|
"github",
|
|
"https://docs.prowler.com/user-guide/providers/github/getting-started-github#step-3-choose-authentication-method",
|
|
],
|
|
[
|
|
"iac",
|
|
"https://docs.prowler.com/user-guide/providers/iac/getting-started-iac#step-2-enter-authentication-details",
|
|
],
|
|
[
|
|
"image",
|
|
"https://docs.prowler.com/user-guide/providers/image/getting-started-image#step-2-enter-authentication-and-scan-filters",
|
|
],
|
|
[
|
|
"oraclecloud",
|
|
"https://docs.prowler.com/user-guide/providers/oci/getting-started-oci#step-3-add-oci-api-key-credentials",
|
|
],
|
|
[
|
|
"mongodbatlas",
|
|
"https://docs.prowler.com/user-guide/providers/mongodbatlas/getting-started-mongodbatlas#step-2-provide-api-credentials",
|
|
],
|
|
[
|
|
"alibabacloud",
|
|
"https://docs.prowler.com/user-guide/providers/alibabacloud/getting-started-alibabacloud#step-3-choose-and-provide-authentication",
|
|
],
|
|
[
|
|
"cloudflare",
|
|
"https://docs.prowler.com/user-guide/providers/cloudflare/getting-started-cloudflare#step-3-choose-and-provide-authentication",
|
|
],
|
|
[
|
|
"openstack",
|
|
"https://docs.prowler.com/user-guide/providers/openstack/getting-started-openstack#step-2-provide-credentials",
|
|
],
|
|
[
|
|
"googleworkspace",
|
|
"https://docs.prowler.com/user-guide/providers/googleworkspace/getting-started-googleworkspace#step-3-provide-credentials",
|
|
],
|
|
[
|
|
"vercel",
|
|
"https://docs.prowler.com/user-guide/providers/vercel/getting-started-vercel#step-2-provide-credentials",
|
|
],
|
|
[
|
|
"okta",
|
|
"https://docs.prowler.com/user-guide/providers/okta/getting-started-okta#step-2-provide-credentials",
|
|
],
|
|
];
|
|
|
|
for (const [provider, expected] of cases) {
|
|
expect(
|
|
getProviderHelpText(provider, PROVIDER_WIZARD_STEP.CREDENTIALS).link,
|
|
).toBe(expected);
|
|
}
|
|
});
|
|
|
|
it("resolves the method-specific credentials link for every provider with a per-method subsection", () => {
|
|
// Providers whose docs have a heading per auth method: verify each
|
|
// (provider, method) combo maps to the exact subsection anchor. Missing
|
|
// an entry in PROVIDER_CREDENTIALS_METHOD_DOCS_URL silently regresses
|
|
// the user to the general step section — this test catches that.
|
|
const cases: Array<[string, string, string]> = [
|
|
[
|
|
"aws",
|
|
"role",
|
|
"https://docs.prowler.com/user-guide/providers/aws/getting-started-aws#assume-role-recommended",
|
|
],
|
|
[
|
|
"aws",
|
|
"credentials",
|
|
"https://docs.prowler.com/user-guide/providers/aws/getting-started-aws#credentials-static-access-keys",
|
|
],
|
|
[
|
|
"m365",
|
|
"app_certificate",
|
|
"https://docs.prowler.com/user-guide/providers/microsoft365/getting-started-m365#application-certificate-authentication-recommended",
|
|
],
|
|
[
|
|
"m365",
|
|
"app_client_secret",
|
|
"https://docs.prowler.com/user-guide/providers/microsoft365/getting-started-m365#application-client-secret-authentication",
|
|
],
|
|
[
|
|
"alibabacloud",
|
|
"role",
|
|
"https://docs.prowler.com/user-guide/providers/alibabacloud/getting-started-alibabacloud#ram-role-assumption-recommended",
|
|
],
|
|
[
|
|
"alibabacloud",
|
|
"credentials",
|
|
"https://docs.prowler.com/user-guide/providers/alibabacloud/getting-started-alibabacloud#credentials-static-access-keys",
|
|
],
|
|
[
|
|
"cloudflare",
|
|
"api_token",
|
|
"https://docs.prowler.com/user-guide/providers/cloudflare/getting-started-cloudflare#user-api-token-authentication-recommended",
|
|
],
|
|
[
|
|
"cloudflare",
|
|
"api_key",
|
|
"https://docs.prowler.com/user-guide/providers/cloudflare/getting-started-cloudflare#api-key-and-email-authentication-legacy",
|
|
],
|
|
];
|
|
|
|
for (const [provider, method, expected] of cases) {
|
|
expect(
|
|
getProviderHelpText(provider, PROVIDER_WIZARD_STEP.CREDENTIALS, method)
|
|
.link,
|
|
).toBe(expected);
|
|
}
|
|
});
|
|
|
|
it("falls back to the generic help shortlink for unknown providers regardless of step", () => {
|
|
// Unknown providers have no dedicated docs page, so a step-specific
|
|
// anchor would deep-link into nothing.
|
|
const { link } = getProviderHelpText(
|
|
"not-a-real-provider",
|
|
PROVIDER_WIZARD_STEP.CREDENTIALS,
|
|
);
|
|
|
|
expect(link).toBe("https://goto.prowler.com/provider-help");
|
|
});
|
|
|
|
it("falls back for unknown providers colliding with Object.prototype", () => {
|
|
// Given
|
|
const providers = ["constructor", "toString", "__proto__"];
|
|
|
|
for (const provider of providers) {
|
|
// When
|
|
const { link } = getProviderHelpText(
|
|
provider,
|
|
PROVIDER_WIZARD_STEP.CREDENTIALS,
|
|
);
|
|
|
|
// Then
|
|
expect(link).toBe("https://goto.prowler.com/provider-help");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("Prowler CloudFormation template", () => {
|
|
it("should define every parameter used by the UI quick-create links", () => {
|
|
// Given
|
|
const template = readFileSync(
|
|
join(
|
|
process.cwd(),
|
|
"..",
|
|
"permissions/templates/cloudformation/prowler-scan-role.yml",
|
|
),
|
|
"utf8",
|
|
);
|
|
|
|
// Then
|
|
expect(template).toContain(" EnableOrganizations:");
|
|
expect(template).toContain(" S3IntegrationBucketAccountId:");
|
|
expect(template).toContain(" DeployStackSet:");
|
|
expect(template).toContain(" DeployLocalRole:");
|
|
expect(template).toContain(" AWSOrganizationalUnitId:");
|
|
expect(template).toContain(" DeployFromDelegatedAdmin:");
|
|
});
|
|
});
|