8000 [OID4VCI] [ABCA] Implemented the Default Trust IDP polish by Ogenbertrand · Pull Request #49398 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2009,7 +2009,7 @@ scopeParameter=Scope parameter
unsigned=Unsigned
userGroupsRetrieveStrategy=User groups retrieve strategy
addSubFlow=Add sub-flow
validatingPublicKeyHelp=The public key in PEM or JWKS format that must be used to verify external IDP signatures. The button below can be used to import an external file with different key and certificate formats. The provider needs to be saved after the import to store the changes.
validatingPublicKeyHelp=The public key in PEM or JWKS format that must be used to verify external IDP signatures. When JWKS is used, it can contain multiple public keys. The button below can be used to import an external file with different key and certificate formats. The provider needs to be saved after the import to store the changes.
client-uris-must-match.label=Client URIs Must Match
webAuthnPolicyAcceptableAaguids=Acceptable AAGUIDs
noRoles-roles=No roles in this realm
Expand Down Expand Up @@ -4125,4 +4125,4 @@ scimApiEnabled=SCIM API
scimApiEnabledHelp=If enabled, exposes realm resources through an API based on the System for Cross-domain Identity Management (SCIM) specification, namely RFC7643 and RFC7644.
excludedDomains=Excluded domains
excludedDomainsHelp=A comma-separated list of domains to skip automatic redirection. You can use wildcard domains like `*.example.com` to exclude all subdomains of `example.com`. For instance, `*.example.com, example.com`.
clearInputField=Clear input field
clearInputField=Clear input field
96 changes: 96 additions & 0 deletions js/apps/admin-ui/src/identity-providers/add/AddDefaultTrust.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
import {
ActionGroup,
AlertVariant,
Button,
PageSection,
} from "@patternfly/react-core";
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import { useAdminClient } from "../../admin-client";
import { useAlerts } from "@keycloak/keycloak-ui-shared";
import { FormAccess } from "../../components/form/FormAccess";
import { ViewHeader } from "../../components/view-header/ViewHeader";
import { useRealm } from "../../context/realm-context/RealmContext";
import { toIdentityProvider } from "../routes/IdentityProvider";
import { toIdentityProviders } from "../routes/IdentityProviders";
import DefaultTrustSettings from "./DefaultTrustSettings";

export default function AddDefaultTrust() {
const { adminClient } = useAdminClient();
const { t } = useTranslation();
const navigate = useNavigate();
const id = "default-trust";

const form = useForm<IdentityProviderRepresentation>({
defaultValues: { alias: id },
mode: "onChange",
});
const {
handleSubmit,
formState: { isDirty },
} = form;

const { addAlert, addError } = useAlerts();
const { realm } = useRealm();

const (provider: IdentityProviderRepresentation) => {
try {
await adminClient.identityProviders.create({
...provider,
providerId: id,
});
addAlert(t("createIdentityProviderSuccess"), AlertVariant.success);
navigate(
toIdentityProvider({
realm,
providerId: id,
alias: provider.alias!,
tab: "settings",
}),
);
} catch (error) {
addError("createIdentityProviderError", error);
}
};

return (
<>
<ViewHeader
titleKey={t("addIdentityProvider", { provider: "Default Trust" })}
/>
<PageSection variant="light">
<FormProvider {...form}>
<FormAccess
role="manage-identity-providers"
isHorizontal
>
>
<DefaultTrustSettings />

<ActionGroup>
<Button
isDisabled={!isDirty}
variant="primary"
type="submit"
data-testid="createProvider"
>
{t("add")}
</Button>
<Button
variant="link"
data-testid="cancel"
component={(props) => (
<Link {...props} to={toIdentityProviders({ realm })} />
)}
>
{t("cancel")}
</Button>
</ActionGroup>
</FormAccess>
</FormProvider>
</PageSection>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useTranslation } from "react-i18next";
import { TextControl } from "@keycloak/keycloak-ui-shared";
import { JwksSettings } from "./JwksSettings";
import { useParams } from "react-router-dom";
import type { IdentityProviderParams } from "../routes/IdentityProvider";

export default function DefaultTrustSettings() {
const { t } = useTranslation();
const { tab } = useParams<IdentityProviderParams>();

return (
<>
<TextControl
name="alias"
label={t("alias")}
labelIcon={t("aliasHelp")}
readOnly={tab === "settings"}
rules={{
required: t("required"),
}}
/>
<JwksSettings />
</>
);
}
34 changes: 31 additions & 3 deletions js/apps/admin-ui/src/identity-providers/add/DetailSettings.tsx
Original file line number Diff line 10BC8 number Diff line change
Expand Up @@ -77,6 +77,7 @@ import { JWTAuthorizationGrantAssertionSettings } from "./JWTAuthorizationGrantA
import JWTAuthorizationGrantSettings from "./JWTAuthorizationGrantSettings";
import { DefaultSwitchControl } from "../../components/SwitchControl";
import { GroupResourceContext } from "../../context/group-resource/GroupResourceContext";
import DefaultTrustSettings from "./DefaultTrustSettings";

type HeaderProps = {
onChange: (value: boolean) => void;
Expand Down Expand Up @@ -438,6 +439,7 @@ export default function DetailSettings() {
const isJWTAuthorizationGrant = provider.providerId!.includes(
"jwt-authorization-grant",
);
const isDefaultTrust = provider.providerId === "default-trust";
const isSocial = !isOIDC && !isSAML && !isOAuth2;
const isJWTAuthorizationGrantSupported =
(isOAuth2 || isOIDC) &&
Expand Down Expand Up @@ -475,7 +477,8 @@ export default function DetailSettings() {
const sections = [
{
title: t("generalSettings"),
isHidden: isSPIFFE || isKubernetes || isJWTAuthorizationGrant,
isHidden:
isSPIFFE || isKubernetes || isJWTAuthorizationGrant || isDefaultTrust,
panel: (
<FormAccess
role="manage-identity-providers"
Expand Down Expand Up @@ -604,6 +607,25 @@ export default function DetailSettings() {
</Form>
),
},
{
title: t("generalSettings"),
isHidden: !isDefaultTrust,
panel: (
<Form
isHorizontal
className="pf-v5-u-py-lg"
>
>
<DefaultTrustSettings />
<FixedButtonsGroup
name="idp-details"
isSubmit
reset={reset}
isDisabled={!isDirty}
/>
</Form>
),
},
{
title: t("samlSettings"),
isHidden: !isSAML,
Expand All @@ -624,7 +646,8 @@ export default function DetailSettings() {
},
{
title: t("advancedSettings"),
isHidden: isSPIFFE || isKubernetes || isJWTAuthorizationGrant,
isHidden:
isSPIFFE || isKubernetes || isJWTAuthorizationGrant || isDefaultTrust,
panel: (
<FormAccess
role="manage-identity-providers"
Expand Down Expand Up @@ -681,7 +704,12 @@ export default function DetailSettings() {
</Tab>
<Tab
id="mappers"
isHidden={isSPIFFE || isKubernetes || isJWTAuthorizationGrant}
isHidden={
isSPIFFE ||
isKubernetes ||
isJWTAuthorizationGrant ||
isDefaultTrust
}
data-testid="mappers-tab"
title={<TabTitleText>{t("mappers")}</TabTitleText>}
{...mappersTab}
Expand Down
2 changes: 2 additions & 0 deletions js/apps/admin-ui/src/identity-providers/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IdentityProviderOidcRoute } from "./routes/IdentityProviderOidc";
import { IdentityProviderSamlRoute } from "./routes/IdentityProviderSaml";
import { IdentityProviderSpiffeRoute } from "./routes/IdentityProviderSpiffe";
import { IdentityProviderKubernetesRoute } from "./routes/IdentityProviderKubernetes";
import { IdentityProviderDefaultTrustRoute } from "./routes/IdentityProviderDefaultTrust";
import { IdentityProvidersRoute } from "./routes/IdentityProviders";
import { IdentityProviderAddMapperRoute } from "./routes/AddMapper";
import { IdentityProviderEditMapperRoute } from "./routes/EditMapper";
Expand All @@ -21,6 +22,7 @@ const routes: AppRouteObject[] = [
IdentityProviderSpiffeRoute,
IdentityProviderJWTAuthorizationGrantRoute,
IdentityProviderKubernetesRoute,
IdentityProviderDefaultTrustRoute,
IdentityProviderKeycloakOidcRoute,
IdentityProviderCreateRoute,
IdentityProviderRoute,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { lazy } from "react";
import type { Path } from "react-router-dom";
import { generateEncodedPath } from "../../utils/generateEncodedPath";
import type { AppRouteObject } from "../../routes";

export type IdentityProviderDefaultTrustParams = { realm: string };

const AddDefaultTrust = lazy(() => import("../add/AddDefaultTrust"));

export const IdentityProviderDefaultTrustRoute: AppRouteObject = {
path: "/:realm/identity-providers/default-trust/add",
element: <AddDefaultTrust />,
handle: {
access: "manage-identity-providers",
breadcrumb: (t) => t("addProvider"),
},
};

export const toIdentityProviderDefaultTrust = (
params: IdentityProviderDefaultTrustParams,
): Partial<Path> => ({
pathname: generateEncodedPath(IdentityProviderDefaultTrustRoute.path, params),
});
84 changes: 84 additions & 0 deletions js/apps/admin-ui/test/identity-providers/default-trust.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { expect, test } from "@playwright/test";
import adminClient from "../utils/AdminClient.ts";
import { login } from "../utils/login.ts";
import { assertNotificationMessage } from "../utils/masthead.ts";
import { goToIdentityProviders } from "../utils/sidebar.ts";
import { clickTableRowItem } from "../utils/table.ts";
import { clickSaveButton, createDefaultTrustProvider } from "./main.ts";

const alias = "default-trust";
const addDefaultTrustProviderUrl =
"http://localhost:8080/admin/master/console/#/master/identity-providers/default-trust/add";
const jwksUrl = "https://localhost/realms/test/protocol/openid-connect/certs";
const jwks = '{"keys":[]}';

test.describe.serial("Default Trust identity provider test", () => {
test.beforeEach(async ({ page }) => {
try {
await adminClient.deleteIdentityProvider(alias);
} catch {
// The provider may not exist before the test starts.
}

await login(page);
await goToIdentityProviders(page);
});

test.afterEach(async ({}, testInfo) => {
if (testInfo.title.includes("create and edit")) {
await adminClient.deleteIdentityProvider(alias);
}
});

test("should only show trust material settings", async ({ page }) => {
await page.goto(addDefaultTrustProviderUrl);

await expect(page.getByTestId("alias")).toHaveValue(alias);
await expect(page.getByTestId("config.useJwksUrl")).toBeChecked();
await expect(page.getByTestId("config.jwksUrl")).toBeVisible();

await expect(page.getByTestId("displayName")).toBeHidden();
await expect(page.getByTestId("config.clientId")).toBeHidden();
await expect(page.getByTestId("config.clientSecret")).toBeHidden();
await expect(page.getByTestId("displayOrder")).toBeHidden();

await page.getByTestId("config.useJwksUrl").click({ force: true });

await expect(page.getByTestId("config.jwksUrl")).toBeHidden();
await expect(
page.getByTestId("config.publicKeySignatureVerifier"),
).toBeVisible();
await expect(
page.getByTestId("config.publicKeySignatureVerifierKeyId"),
).toBeVisible();
await expect(page.getByTestId("import-certificate-button")).toBeVisible();
});

test("should create and edit a Default Trust provider", async ({ page }) => {
await createDefaultTrustProvider(page, jwksUrl);
await assertNotificationMessage(
page,
"Identity provider successfully created",
);

await goToIdentityProviders(page);
await clickTableRowItem(page, alias);

await expect(page.getByTestId("config.useJwksUrl")).toBeChecked();
await expect(page.getByTestId("config.jwksUrl")).toHaveValue(jwksUrl);
await expect(page.getByTestId("displayName")).toBeHidden();
await expect(page.getByTestId("config.clientId")).toBeHidden();
await expect(page.getByTestId("config.clientSecret")).toBeHidden();
await expect(page.getByTestId("mappers-tab")).toBeHidden();

await page.getByTestId("config.useJwksUrl").click({ force: true });
await page.getByTestId("config.publicKeySignatureVerifier").fill(jwks);
await clickSaveButton(page);

await assertNotificationMessage(page, "Provider successfully updated");
await expect(page.getByTestId("config.useJwksUrl")).not.toBeChecked();
await expect(
page.getByTestId("config.publicKeySignatureVerifier"),
).toHaveValue(jwks);
});
});
9 changes: 9 additions & 0 deletions js/apps/admin-ui/test/identity-providers/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ export async function createJwtAuthorizationGrantProviderKey(
await clickAddButton(page);
}

export async function createDefaultTrustProvider(page: Page, jwksUrl: string) {
await page.goto(
`${SERVER_URL}/admin/master/console/#/master/identity-providers/default-trust/add`,
);
await expect(page.getByTestId("config.useJwksUrl")).toBeChecked();
await page.getByTestId("config.jwksUrl").fill(jwksUrl);
await clickAddButton(page);
}

export async function createKubernetesProvider(
page: Page,
providerName: string,
Expand Down
Loading
Loading
0