Clients(Authentication): Adds "Export" tab (#1937)

This commit is contained in:
Jenny 2022-02-09 17:37:31 -05:00 committed by GitHub
parent 8de96be98b
commit 5968e1a7ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 172 additions and 10 deletions

View file

@ -146,5 +146,22 @@ describe("Client authentication subtab", () => {
.save(); .save();
masthead.checkNotificationMessage("Successfully created the permission"); masthead.checkNotificationMessage("Successfully created the permission");
authenticationTab.cancel();
});
it("Should copy auth details", () => {
authenticationTab.goToExportSubTab();
authenticationTab.copy();
masthead.checkNotificationMessage("Authorization details copied.");
});
it("Should export auth details", () => {
authenticationTab.goToExportSubTab();
authenticationTab.export();
masthead.checkNotificationMessage(
"Successfully exported authorization details."
);
}); });
}); });

View file

@ -10,6 +10,9 @@ export default class AuthorizationTab {
private scopeTabName = "authorizationScopes"; private scopeTabName = "authorizationScopes";
private policyTabName = "authorizationPolicies"; private policyTabName = "authorizationPolicies";
private permissionsTabName = "authorizationPermissions"; private permissionsTabName = "authorizationPermissions";
private exportTabName = "authorizationExport";
private exportDownloadButton = "authorization-export-download";
private exportCopyButton = "authorization-export-copy";
private nameColumnPrefix = "name-column-"; private nameColumnPrefix = "name-column-";
private emptyPolicyCreateButton = "no-policies-empty-action"; private emptyPolicyCreateButton = "no-policies-empty-action";
private createPolicyButton = "createPolicy"; private createPolicyButton = "createPolicy";
@ -43,6 +46,11 @@ export default class AuthorizationTab {
return this; return this;
} }
goToExportSubTab() {
cy.findByTestId(this.exportTabName).click();
return this;
}
goToCreateResource() { goToCreateResource() {
cy.findByTestId(this.createResourceButton).click(); cy.findByTestId(this.createResourceButton).click();
return this; return this;
@ -124,6 +132,16 @@ export default class AuthorizationTab {
return this; return this;
} }
copy() {
cy.findByTestId(this.exportCopyButton).click();
return this;
}
export() {
cy.findByTestId(this.exportDownloadButton).click();
return this;
}
save() { save() {
cy.findByTestId("save").click(); cy.findByTestId("save").click();
return this; return this;

14
package-lock.json generated
View file

@ -7,7 +7,7 @@
"name": "keycloak-admin-ui", "name": "keycloak-admin-ui",
"license": "Apache", "license": "Apache",
"dependencies": { "dependencies": {
"@keycloak/keycloak-admin-client": "^17.0.0-dev.26", "@keycloak/keycloak-admin-client": "^17.0.0-dev.29",
"@patternfly/patternfly": "^4.171.1", "@patternfly/patternfly": "^4.171.1",
"@patternfly/react-code-editor": "^4.33.14", "@patternfly/react-code-editor": "^4.33.14",
"@patternfly/react-core": "^4.192.14", "@patternfly/react-core": "^4.192.14",
@ -3414,9 +3414,9 @@
} }
}, },
"node_modules/@keycloak/keycloak-admin-client": { "node_modules/@keycloak/keycloak-admin-client": {
"version": "17.0.0-dev.26", "version": "17.0.0-dev.29",
"resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-17.0.0-dev.26.tgz", "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-17.0.0-dev.29.tgz",
"integrity": "sha512-D7wP/cu4shzbq2FbmFVfZp1ipFoVDdH4YyFHRlnzb0Y0zvS58eGlDqE0YaI23I8dn5qsyj8oFUYgi4ntayAGCQ==", "integrity": "sha512-EfPbXG0e2rbfvdJFc6je3gULHYmcBHGbxVmtEpRJOZ7PogaAOtSp6n8EPLIKeHsd1tszaS4g/87B8Ee1FYBmlg==",
"dependencies": { "dependencies": {
"axios": "^0.25.0", "axios": "^0.25.0",
"camelize-ts": "^1.0.8", "camelize-ts": "^1.0.8",
@ -23966,9 +23966,9 @@
} }
}, },
"@keycloak/keycloak-admin-client": { "@keycloak/keycloak-admin-client": {
"version": "17.0.0-dev.26", "version": "17.0.0-dev.29",
"resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-17.0.0-dev.26.tgz", "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-17.0.0-dev.29.tgz",
"integrity": "sha512-D7wP/cu4shzbq2FbmFVfZp1ipFoVDdH4YyFHRlnzb0Y0zvS58eGlDqE0YaI23I8dn5qsyj8oFUYgi4ntayAGCQ==", "integrity": "sha512-EfPbXG0e2rbfvdJFc6je3gULHYmcBHGbxVmtEpRJOZ7PogaAOtSp6n8EPLIKeHsd1tszaS4g/87B8Ee1FYBmlg==",
"requires": { "requires": {
"axios": "^0.25.0", "axios": "^0.25.0",
"camelize-ts": "^1.0.8", "camelize-ts": "^1.0.8",

View file

@ -23,7 +23,7 @@
"prepare": "husky install" "prepare": "husky install"
}, },
"dependencies": { "dependencies": {
"@keycloak/keycloak-admin-client": "^17.0.0-dev.26", "@keycloak/keycloak-admin-client": "^17.0.0-dev.29",
"@patternfly/patternfly": "^4.171.1", "@patternfly/patternfly": "^4.171.1",
"@patternfly/react-code-editor": "^4.33.14", "@patternfly/react-code-editor": "^4.33.14",
"@patternfly/react-core": "^4.192.14", "@patternfly/react-core": "^4.192.14",

View file

@ -70,6 +70,7 @@ import {
toAuthorizationTab, toAuthorizationTab,
} from "./routes/AuthenticationTab"; } from "./routes/AuthenticationTab";
import { toClientScopesTab } from "./routes/ClientScopeTab"; import { toClientScopesTab } from "./routes/ClientScopeTab";
import { AuthorizationExport } from "./authorization/AuthorizationExport";
type ClientDetailHeaderProps = { type ClientDetailHeaderProps = {
onChange: (value: boolean) => void; onChange: (value: boolean) => void;
@ -586,7 +587,7 @@ export default function ClientDetails() {
<AuthorizationPermissions clientId={clientId} /> <AuthorizationPermissions clientId={clientId} />
</Tab> </Tab>
<Tab <Tab
id="Evaluate" id="evaluate"
data-testid="authorizationEvaluate" data-testid="authorizationEvaluate"
title={<TabTitleText>{t("evaluate")}</TabTitleText>} title={<TabTitleText>{t("evaluate")}</TabTitleText>}
{...authenticationRoute("evaluate")} {...authenticationRoute("evaluate")}
@ -600,6 +601,14 @@ export default function ClientDetails() {
reset={() => setupForm(client)} reset={() => setupForm(client)}
/> />
</Tab> </Tab>
<Tab
id="export"
data-testid="authorizationExport"
title={<TabTitleText>{t("common:export")}</TabTitleText>}
{...authenticationRoute("export")}
>
<AuthorizationExport />
</Tab>
</RoutableTabs> </RoutableTabs>
</Tab> </Tab>
)} )}

View file

@ -0,0 +1,106 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import {
FormGroup,
PageSection,
ActionGroup,
Button,
TextArea,
AlertVariant,
} from "@patternfly/react-core";
import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { useParams } from "react-router-dom";
import FileSaver from "file-saver";
import { prettyPrintJSON } from "../../util";
import "./authorization-details.css";
import { useAlerts } from "../../components/alert/Alerts";
import type { ClientParams } from "../routes/Client";
import type ResourceServerRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceServerRepresentation";
export const AuthorizationExport = () => {
const { t } = useTranslation("clients");
const adminClient = useAdminClient();
const { clientId } = useParams<ClientParams>();
const { addAlert, addError } = useAlerts();
const [code, setCode] = useState<string>();
const [authorizationDetails, setAuthorizationDetails] =
useState<ResourceServerRepresentation>();
useFetch(
() =>
adminClient.clients.exportResource({
id: clientId,
}),
(authDetails) => {
setCode(JSON.stringify(authDetails, null, 2));
setAuthorizationDetails(authDetails);
},
[]
);
const exportAuthDetails = () => {
try {
FileSaver.saveAs(
new Blob([prettyPrintJSON(authorizationDetails)], {
type: "application/json",
}),
"test-authz-config.json"
);
addAlert(t("exportAuthDetailsSuccess"), AlertVariant.success);
} catch (error) {
addError("exportAuthDetailsError", error);
}
};
return (
<PageSection>
<FormAccess isHorizontal role="manage-realm" className="pf-u-mt-lg">
<FormGroup
label={t("authDetails")}
labelIcon={
<HelpItem
helpText="clients-help:authDetails"
fieldLabelId="clients:authDetails"
/>
}
fieldId="client"
>
<TextArea
id="authorizationDetails"
readOnly
resizeOrientation="vertical"
value={code}
aria-label={t("authDetails")}
/>
</FormGroup>
<ActionGroup>
<Button
data-testid="authorization-export-download"
onClick={() => exportAuthDetails()}
>
{t("common:download")}
</Button>
<Button
data-testid="authorization-export-copy"
variant="secondary"
onClick={async () => {
try {
await navigator.clipboard.writeText(code!);
addAlert(t("copied"), AlertVariant.success);
} catch (error) {
addError(t("copyError"), error);
}
}}
>
{t("copy")}
</Button>
</ActionGroup>
</FormAccess>
</PageSection>
);
};

View file

@ -0,0 +1,3 @@
textarea#authorizationDetails {
height: 280px;
}

View file

@ -10,6 +10,8 @@ export default {
"This defines the type of the OIDC client. When it's ON, the OIDC type is set to confidential access type. When it's OFF, it is set to public access type", "This defines the type of the OIDC client. When it's ON, the OIDC type is set to confidential access type. When it's OFF, it is set to public access type",
authorization: authorization:
"Enable/Disable fine-grained authorization support for a client", "Enable/Disable fine-grained authorization support for a client",
authDetails:
"Export and download all resource settings for this resource server.",
directAccess: directAccess:
"This enables support for Direct Access Grants, which means that client has access to username/password of user and exchange it directly with Keycloak server for access token. In terms of OAuth2 specification, this enables support of 'Resource Owner Password Credentials Grant' for this client.", "This enables support for Direct Access Grants, which means that client has access to username/password of user and exchange it directly with Keycloak server for access token. In terms of OAuth2 specification, this enables support of 'Resource Owner Password Credentials Grant' for this client.",
standardFlow: standardFlow:

View file

@ -6,6 +6,11 @@ export default {
all: "All", all: "All",
}, },
protocol: "Protocol", protocol: "Protocol",
copy: "Copy",
copied: "Authorization details copied.",
copyError: "Error copying authorization details: {{error}}",
exportAuthDetailsSuccess: "Successfully exported authorization details.",
exportAuthDetailsError: "Error exporting authorization details: {{error}}",
clientType: "Client type", clientType: "Client type",
clientAuthorization: "Authorization", clientAuthorization: "Authorization",
implicitFlow: "Implicit flow", implicitFlow: "Implicit flow",
@ -42,6 +47,7 @@ export default {
lastEvaluation: "Last Evaluation", lastEvaluation: "Last Evaluation",
resourcesAndAuthScopes: "Resources and Authentication Scopes", resourcesAndAuthScopes: "Resources and Authentication Scopes",
authScopes: "Authorization scopes", authScopes: "Authorization scopes",
authDetails: "Authorization details",
anyResource: "Any resource", anyResource: "Any resource",
anyScope: "Any scope", anyScope: "Any scope",
selectScope: "Select a scope", selectScope: "Select a scope",

View file

@ -9,7 +9,8 @@ export type AuthorizationTab =
| "scopes" | "scopes"
| "policies" | "policies"
| "permissions" | "permissions"
| "evaluate"; | "evaluate"
| "export";
export type AuthorizationParams = { export type AuthorizationParams = {
realm: string; realm: string;