Fixed issue using import (#393)

* add icons change description to use TextArea

* add missing saml config

* switch based on protocol

* add capabilty config to import

* fixed key element warning

* fixed merge error

* don't clear on file drag event

* don't allow editing of json

* prettier

* fix tests

* missing timeout
This commit is contained in:
Erik Jan de Wit 2021-03-09 14:59:41 +01:00 committed by GitHub
parent 4ba5fcc723
commit 7bf85196e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 281 additions and 156 deletions

View file

@ -1,7 +1,14 @@
import React from "react";
import { FormGroup, TextInput, ValidatedOptions } from "@patternfly/react-core";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
FormGroup,
TextArea,
TextInput,
ValidatedOptions,
} from "@patternfly/react-core";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormAccess } from "../components/form-access/FormAccess";
import { ClientForm } from "./ClientDetails";
@ -12,6 +19,13 @@ export const ClientDescription = () => {
return (
<FormAccess role="manage-clients" unWrap>
<FormGroup
labelIcon={
<HelpItem
helpText="clients-help:clientID"
forLabel={t("clientID")}
forID="kc-client-id"
/>
}
label={t("clientID")}
fieldId="kc-client-id"
helperTextInvalid={t("common:required")}
@ -30,10 +44,27 @@ export const ClientDescription = () => {
}
/>
</FormGroup>
<FormGroup label={t("common:name")} fieldId="kc-name">
<FormGroup
labelIcon={
<HelpItem
helpText="clients-help:clientName"
forLabel={t("common:name")}
forID="kc-name"
/>
}
label={t("common:name")}
fieldId="kc-name"
>
<TextInput ref={register()} type="text" id="kc-name" name="name" />
</FormGroup>
<FormGroup
labelIcon={
<HelpItem
helpText="clients-help:description"
forLabel={t("common:description")}
forID="kc-description"
/>
}
label={t("common:description")}
fieldId="kc-description"
validated={
@ -41,7 +72,7 @@ export const ClientDescription = () => {
}
helperTextInvalid={errors.description?.message}
>
<TextInput
<TextArea
ref={register({
maxLength: {
value: 255,

View file

@ -74,7 +74,7 @@ export const ClientsSection = () => {
<Link key={client.id} to={`/${realm}/clients/${client.id}/settings`}>
{client.clientId}
{!client.enabled && (
<Badge isRead className="pf-u-ml-sm">
<Badge key={`${client.id}-disabled`} isRead className="pf-u-ml-sm">
Disabled
</Badge>
)}

View file

@ -8,129 +8,203 @@ import {
GridItem,
} from "@patternfly/react-core";
import { Controller, useFormContext } from "react-hook-form";
import { FormAccess } from "../../components/form-access/FormAccess";
import { ClientForm } from "../ClientDetails";
import { HelpItem } from "../../components/help-enabler/HelpItem";
export const CapabilityConfig = () => {
type CapabilityConfigProps = {
unWrap?: boolean;
protocol?: string;
};
export const CapabilityConfig = ({
unWrap,
protocol: type,
}: CapabilityConfigProps) => {
const { t } = useTranslation("clients");
const { control } = useFormContext<ClientForm>();
const { control, watch } = useFormContext<ClientForm>();
const protocol = type || watch("protocol");
return (
<FormAccess isHorizontal role="manage-clients">
<FormGroup
hasNoPaddingTop
label={t("clientAuthentication")}
fieldId="kc-authentication"
>
<Controller
name="publicClient"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Switch
id="kc-authentication"
name="publicClient"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup
hasNoPaddingTop
label={t("clientAuthorization")}
fieldId="kc-authorization"
>
<Controller
name="authorizationServicesEnabled"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Switch
id="kc-authorization"
name="authorizationServicesEnabled"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup
hasNoPaddingTop
label={t("authenticationFlow")}
fieldId="kc-flow"
>
<Grid>
<GridItem lg={4} sm={6}>
<Controller
name="standardFlowEnabled"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Checkbox
label={t("standardFlow")}
id="kc-flow-standard"
name="standardFlowEnabled"
isChecked={value}
onChange={onChange}
<FormAccess isHorizontal role="manage-clients" unWrap={unWrap}>
<>
{protocol === "openid-connect" && (
<>
<FormGroup
hasNoPaddingTop
label={t("clientAuthentication")}
fieldId="kc-authentication"
>
<Controller
name="publicClient"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Switch
id="kc-authentication"
name="publicClient"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup
hasNoPaddingTop
label={t("clientAuthorization")}
fieldId="kc-authorization"
>
<Controller
name="authorizationServicesEnabled"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Switch
id="kc-authorization"
name="authorizationServicesEnabled"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup
hasNoPaddingTop
label={t("authenticationFlow")}
fieldId="kc-flow"
>
<Grid>
<GridItem lg={4} sm={6}>
<Controller
name="standardFlowEnabled"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Checkbox
label={t("standardFlow")}
id="kc-flow-standard"
name="standardFlowEnabled"
isChecked={value}
onChange={onChange}
/>
)}
/>
</GridItem>
<GridItem lg={8} sm={6}>
<Controller
name="directAccessGrantsEnabled"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Checkbox
label={t("directAccess")}
id="kc-flow-direct"
name="directAccessGrantsEnabled"
isChecked={value}
onChange={onChange}
/>
)}
/>
</GridItem>
<GridItem lg={4} sm={6}>
<Controller
name="implicitFlowEnabled"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Checkbox
label={t("implicitFlow")}
id="kc-flow-implicit"
name="implicitFlowEnabled"
isChecked={value}
onChange={onChange}
/>
)}
/>
</GridItem>
<GridItem lg={8} sm={6}>
<Controller
name="serviceAccountsEnabled"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Checkbox
label={t("serviceAccount")}
id="kc-flow-service-account"
name="serviceAccountsEnabled"
isChecked={value}
onChange={onChange}
/>
)}
/>
</GridItem>
</Grid>
</FormGroup>
</>
)}
</>
<>
{protocol === "saml" && (
<>
<FormGroup
labelIcon={
<HelpItem
helpText="clients-help:encryptAssertions"
forLabel={t("encryptAssertions")}
forID="kc-encrypt"
/>
)}
/>
</GridItem>
<GridItem lg={8} sm={6}>
<Controller
name="directAccessGrantsEnabled"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Checkbox
label={t("directAccess")}
id="kc-flow-direct"
name="directAccessGrantsEnabled"
isChecked={value}
onChange={onChange}
}
label={t("encryptAssertions")}
fieldId="kc-encrypt"
>
<Controller
name="attributes.saml_encrypt"
control={control}
defaultValue="false"
render={({ onChange, value }) => (
<Switch
id="kc-encrypt"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value === "true"}
onChange={(value) => onChange("" + value)}
/>
)}
/>
</FormGroup>
<FormGroup
labelIcon={
<HelpItem
helpText="clients-help:clientSignature"
forLabel={t("clientSignature")}
forID="kc-client-signature"
/>
)}
/>
</GridItem>
<GridItem lg={4} sm={6}>
<Controller
name="implicitFlowEnabled"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Checkbox
label={t("implicitFlow")}
id="kc-flow-implicit"
name="implicitFlowEnabled"
isChecked={value}
onChange={onChange}
/>
)}
/>
</GridItem>
<GridItem lg={8} sm={6}>
<Controller
name="serviceAccountsEnabled"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<Checkbox
label={t("serviceAccount")}
id="kc-flow-service-account"
name="serviceAccountsEnabled"
isChecked={value}
onChange={onChange}
/>
)}
/>
</GridItem>
</Grid>
</FormGroup>
}
label={t("clientSignature")}
fieldId="kc-client-signature"
>
<Controller
name="attributes.saml_client_signature"
control={control}
defaultValue="false"
render={({ onChange, value }) => (
<Switch
id="kc-client-signature"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value === "true"}
onChange={(value) => onChange("" + value)}
/>
)}
/>
</FormGroup>
</>
)}
</>
</FormAccess>
);
};

View file

@ -111,11 +111,11 @@ export const NewClientForm = () => {
},
{
name: t("capabilityConfig"),
component: <CapabilityConfig />,
component: <CapabilityConfig protocol={client.protocol} />,
},
]}
footer={<Footer />}
onSave={() => save()}
onSave={save}
/>
</FormProvider>
</PageSection>

View file

@ -2,6 +2,11 @@
"clients-help": {
"webOrigins": "Allowed CORS origins. To permit all origins of Valid Redirect URIs, add '+'. This does not include the '*' wildcard though. To permit all origins, explicitly add '*'.",
"adminURL": "URL to the admin interface of the client. Set this if the client supports the adapter REST API. This REST API allows the auth server to push revocation policies and other administrative tasks. Usually this is set to the base URL of the client.",
"clientID": "Specifies ID referenced in URI and tokens. For example 'my-client'. For SAML this is also the expected issuer value from authn requests",
"clientName": "Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example: ${my_client}",
"description": "Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example: ${my_client_description}",
"encryptAssertions": "Should SAML assertions be encrypted with client's public key using AES?",
"clientSignature": "Will the client sign their saml requests and responses? And should they be validated?",
"downloadType": "this is information about the download type",
"details": "this is information about the details",
"createToken": "An initial access token can only be used to create clients",

View file

@ -1,4 +1,6 @@
import React from "react";
import { useHistory } from "react-router-dom";
import { useTranslation } from "react-i18next";
import {
PageSection,
FormGroup,
@ -8,7 +10,6 @@ import {
AlertVariant,
} from "@patternfly/react-core";
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { ClientDescription } from "../ClientDescription";
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
@ -17,10 +18,15 @@ import { ViewHeader } from "../../components/view-header/ViewHeader";
import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
import { useAdminClient } from "../../context/auth/AdminClient";
import { FormAccess } from "../../components/form-access/FormAccess";
import { useRealm } from "../../context/realm-context/RealmContext";
import { convertFormValuesToObject, convertToFormValues } from "../../util";
import { CapabilityConfig } from "../add/CapabilityConfig";
export const ImportForm = () => {
const { t } = useTranslation("clients");
const history = useHistory();
const adminClient = useAdminClient();
const { realm } = useRealm();
const form = useForm<ClientRepresentation>();
const { register, handleSubmit, setValue } = form;
@ -36,18 +42,27 @@ export const ImportForm = () => {
const obj = value ? JSON.parse(value as string) : defaultClient;
Object.keys(obj).forEach((k) => {
setValue(k, obj[k]);
if (k === "attributes") {
convertToFormValues(obj[k], "attributes", form.setValue);
} else {
setValue(k, obj[k]);
}
});
};
const save = async (client: ClientRepresentation) => {
try {
await adminClient.clients.create({ ...client });
const newClient = await adminClient.clients.create({
...client,
attributes: convertFormValuesToObject(client.attributes || {}),
});
addAlert(t("clientImportSuccess"), AlertVariant.success);
history.push(`/${realm}/clients/${newClient.id}`);
} catch (error) {
addAlert(`${t("clientImportError")} '${error}'`, AlertVariant.danger);
addAlert(t("clientImportError", { error }), AlertVariant.danger);
}
};
return (
<>
<ViewHeader
@ -72,11 +87,17 @@ export const ImportForm = () => {
ref={register()}
/>
</FormGroup>
<CapabilityConfig unWrap={true} />
<ActionGroup>
<Button variant="primary" type="submit">
{t("common:save")}
</Button>
<Button variant="link">{t("common:cancel")}</Button>
<Button
variant="link"
onClick={() => history.push(`/${realm}/clients`)}
>
{t("common:cancel")}
</Button>
</ActionGroup>
</FormProvider>
</FormAccess>

View file

@ -9,6 +9,8 @@
"webOrigins": "Web origins",
"adminURL": "Admin URL",
"formatOption": "Format option",
"encryptAssertions": "Encrypt assertions",
"clientSignature": "Client signature required",
"downloadAdaptorTitle": "Download adaptor configs",
"credentials": "Credentials",
"roles": "Roles",
@ -61,7 +63,7 @@
"clientsExplain": "Clients are applications and services that can request authentication of a user",
"createSuccess": "Client created successfully",
"createError": "Could not create client: '{{error}}'",
"clientImportError": "Could not import client",
"clientImportError": "Could not import client: {{error}}",
"clientSaveSuccess": "Client successfully updated",
"clientSaveError": "Client could not be updated:",
"clientImportSuccess": "Client imported successfully",

View file

@ -22,23 +22,22 @@ export function AlertPanel({ alerts, onCloseAlert }: AlertPanelProps) {
return (
<AlertGroup isToast>
{alerts.map(({ key, variant, message, description }) => (
<>
<Alert
key={key}
isLiveRegion
variant={AlertVariant[variant]}
variantLabel=""
title={message}
actionClose={
<AlertActionCloseButton
title={message}
onClose={() => onCloseAlert(key)}
/>
}
>
{description && <p>{description}</p>}
</Alert>
</>
<Alert
timeout={true}
key={key}
isLiveRegion
variant={AlertVariant[variant]}
variantLabel=""
title={message}
actionClose={
<AlertActionCloseButton
title={message}
onClose={() => onCloseAlert(key)}
/>
}
>
{description && <p>{description}</p>}
</Alert>
))}
</AlertGroup>
);

View file

@ -16,11 +16,6 @@ export const AlertContext = createContext<AlertProps>({
export const useAlerts = () => useContext(AlertContext);
type TimeOut = {
key: number;
timeOut: NodeJS.Timeout;
};
export const AlertProvider = ({ children }: { children: ReactNode }) => {
const [alerts, setAlerts] = useState<AlertType[]>([]);

View file

@ -49,7 +49,10 @@ export const JsonFileUpload = ({
| React.ChangeEvent<HTMLTextAreaElement>
| React.MouseEvent<HTMLButtonElement, MouseEvent>
): void => {
if (event.nativeEvent instanceof MouseEvent) {
if (
event.nativeEvent instanceof MouseEvent &&
!(event.nativeEvent instanceof DragEvent)
) {
setFileUpload({ ...fileUpload, modal: true });
} else {
setFileUpload({
@ -100,7 +103,6 @@ export const JsonFileUpload = ({
value={fileUpload.value}
filename={fileUpload.filename}
onChange={handleChange}
allowEditingUploadedText
onReadStarted={() =>
setFileUpload({ ...fileUpload, isLoading: true })
}

View file

@ -32,7 +32,6 @@ exports[`<JsonFileUpload /> render 1`] = `
className="pf-c-form__group-control"
>
<FileUpload
allowEditingUploadedText={true}
dropzoneProps={
Object {
"accept": ".json",
@ -59,7 +58,6 @@ exports[`<JsonFileUpload /> render 1`] = `
preventDropOnDocument={true}
>
<FileUploadField
allowEditingUploadedText={true}
containerRef={[Function]}
filename=""
id="test"
@ -303,7 +301,6 @@ exports[`<JsonFileUpload /> upload file 1`] = `
className="pf-c-form__group-control"
>
<FileUpload
allowEditingUploadedText={true}
dropzoneProps={
Object {
"accept": ".json",
@ -330,7 +327,6 @@ exports[`<JsonFileUpload /> upload file 1`] = `
preventDropOnDocument={true}
>
<FileUploadField
allowEditingUploadedText={true}
containerRef={[Function]}
filename=""
id="upload"