Clients(Authentication): Adds "Export" tab (#1937)
This commit is contained in:
parent
8de96be98b
commit
5968e1a7ee
10 changed files with 172 additions and 10 deletions
|
@ -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."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
14
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
|
106
src/clients/authorization/AuthorizationExport.tsx
Normal file
106
src/clients/authorization/AuthorizationExport.tsx
Normal 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>
|
||||
);
|
||||
};
|
3
src/clients/authorization/authorization-details.css
Normal file
3
src/clients/authorization/authorization-details.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
textarea#authorizationDetails {
|
||||
height: 280px;
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -9,7 +9,8 @@ export type AuthorizationTab =
|
|||
| "scopes"
|
||||
| "policies"
|
||||
| "permissions"
|
||||
| "evaluate";
|
||||
| "evaluate"
|
||||
| "export";
|
||||
|
||||
export type AuthorizationParams = {
|
||||
realm: string;
|
||||
|
|
Loading…
Reference in a new issue