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();
|
.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."
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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
14
package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
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",
|
"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:
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue