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

14
package-lock.json generated
View file

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

View file

@ -23,7 +23,7 @@
"prepare": "husky install"
},
"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/react-code-editor": "^4.33.14",
"@patternfly/react-core": "^4.192.14",

View file

@ -70,6 +70,7 @@ import {
toAuthorizationTab,
} from "./routes/AuthenticationTab";
import { toClientScopesTab } from "./routes/ClientScopeTab";
import { AuthorizationExport } from "./authorization/AuthorizationExport";
type ClientDetailHeaderProps = {
onChange: (value: boolean) => void;
@ -586,7 +587,7 @@ export default function ClientDetails() {
<AuthorizationPermissions clientId={clientId} />
</Tab>
<Tab
id="Evaluate"
id="evaluate"
data-testid="authorizationEvaluate"
title={<TabTitleText>{t("evaluate")}</TabTitleText>}
{...authenticationRoute("evaluate")}
@ -600,6 +601,14 @@ export default function ClientDetails() {
reset={() => setupForm(client)}
/>
</Tab>
<Tab
id="export"
data-testid="authorizationExport"
title={<TabTitleText>{t("common:export")}</TabTitleText>}
{...authenticationRoute("export")}
>
<AuthorizationExport />
</Tab>
</RoutableTabs>
</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",
authorization:
"Enable/Disable fine-grained authorization support for a client",
authDetails:
"Export and download all resource settings for this resource server.",
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.",
standardFlow:

View file

@ -6,6 +6,11 @@ export default {
all: "All",
},
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",
clientAuthorization: "Authorization",
implicitFlow: "Implicit flow",
@ -42,6 +47,7 @@ export default {
lastEvaluation: "Last Evaluation",
resourcesAndAuthScopes: "Resources and Authentication Scopes",
authScopes: "Authorization scopes",
authDetails: "Authorization details",
anyResource: "Any resource",
anyScope: "Any scope",
selectScope: "Select a scope",

View file

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