Only show activated required actions in credentials reset dialog (#19048)

This commit is contained in:
Oliver 2023-03-28 14:44:01 +02:00 committed by GitHub
parent e40fa5fcb4
commit fc0a9be79f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 83 deletions

View file

@ -45,6 +45,7 @@
"emailInvalid": "You must enter a valid email.", "emailInvalid": "You must enter a valid email.",
"notVerified": "Not verified", "notVerified": "Not verified",
"requiredUserActions": "Required user actions", "requiredUserActions": "Required user actions",
"requiredActionPlaceholder": "Select action",
"federationLink": "Federation link", "federationLink": "Federation link",
"addUser": "Add user", "addUser": "Add user",
"impersonate": "Impersonate", "impersonate": "Impersonate",

View file

@ -1,6 +1,5 @@
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation"; import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import type RequiredActionProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation";
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation"; import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import { import {
ActionGroup, ActionGroup,
@ -10,8 +9,6 @@ import {
ChipGroup, ChipGroup,
FormGroup, FormGroup,
InputGroup, InputGroup,
Select,
SelectOption,
Switch, Switch,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { useState } from "react"; import { useState } from "react";
@ -32,6 +29,7 @@ import useFormatDate from "../utils/useFormatDate";
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled"; import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import { FederatedUserLink } from "./FederatedUserLink"; import { FederatedUserLink } from "./FederatedUserLink";
import { UserProfileFields } from "./UserProfileFields"; import { UserProfileFields } from "./UserProfileFields";
import { RequiredActionMultiSelect } from "./user-credentials/RequiredActionMultiSelect";
export type BruteForced = { export type BruteForced = {
isBruteForceProtected?: boolean; isBruteForceProtected?: boolean;
@ -93,10 +91,6 @@ export const UserForm = ({
const formatDate = useFormatDate(); const formatDate = useFormatDate();
const isFeatureEnabled = useIsFeatureEnabled(); const isFeatureEnabled = useIsFeatureEnabled();
const [
isRequiredUserActionsDropdownOpen,
setRequiredUserActionsDropdownOpen,
] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const { adminClient } = useAdminClient(); const { adminClient } = useAdminClient();
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
@ -118,22 +112,14 @@ export const UserForm = ({
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [locked, setLocked] = useState(isLocked); const [locked, setLocked] = useState(isLocked);
const [realm, setRealm] = useState<RealmRepresentation>(); const [realm, setRealm] = useState<RealmRepresentation>();
const [requiredActions, setRequiredActions] = useState<
RequiredActionProviderRepresentation[]
>([]);
useFetch( useFetch(
() => () => adminClient.realms.findOne({ realm: realmName }),
Promise.all([ (realm) => {
adminClient.realms.findOne({ realm: realmName }),
adminClient.authenticationManagement.getRequiredActions(),
]),
([realm, actions]) => {
if (!realm) { if (!realm) {
throw new Error(t("common:notFound")); throw new Error(t("common:notFound"));
} }
setRealm(realm); setRealm(realm);
setRequiredActions(actions);
}, },
[] []
); );
@ -147,10 +133,6 @@ export const UserForm = ({
} }
}; };
const clearSelection = () => {
setRequiredUserActionsDropdownOpen(false);
};
const deleteItem = (id: string) => { const deleteItem = (id: string) => {
setSelectedGroups(selectedGroups.filter((item) => item.name !== id)); setSelectedGroups(selectedGroups.filter((item) => item.name !== id));
onGroupsUpdate?.(selectedGroups); onGroupsUpdate?.(selectedGroups);
@ -233,56 +215,11 @@ export const UserForm = ({
</FormGroup> </FormGroup>
</> </>
)} )}
<FormGroup <RequiredActionMultiSelect
label={t("requiredUserActions")} name="requiredActions"
fieldId="kc-required-user-actions" label="requiredUserActions"
validated={errors.requiredActions ? "error" : "default"} help="users-help:requiredUserActions"
helperTextInvalid={t("common:required")} />
labelIcon={
<HelpItem
helpText={t("users-help:requiredUserActions")}
fieldLabelId="users:requiredUserActions"
/>
}
>
<Controller
name="requiredActions"
defaultValue={[]}
control={control}
render={({ field }) => (
<Select
data-testid="required-actions-select"
placeholderText="Select action"
toggleId="kc-required-user-actions"
onToggle={() =>
setRequiredUserActionsDropdownOpen(
!isRequiredUserActionsDropdownOpen
)
}
isOpen={isRequiredUserActionsDropdownOpen}
selections={field.value}
onSelect={(_, v) => {
const option = v as string;
if (field.value.includes(option)) {
field.onChange(
field.value.filter((item: string) => item !== option)
);
} else {
field.onChange([...field.value, option]);
}
}}
onClear={clearSelection}
variant="typeaheadmulti"
>
{requiredActions.map(({ alias, name }) => (
<SelectOption key={alias} value={alias}>
{name}
</SelectOption>
))}
</Select>
)}
/>
</FormGroup>
{(user?.federationLink || user?.origin) && ( {(user?.federationLink || user?.origin) && (
<FormGroup <FormGroup
label={t("federationLink")} label={t("federationLink")}

View file

@ -12,7 +12,17 @@ import { useTranslation } from "react-i18next";
import { HelpItem } from "ui-shared"; import { HelpItem } from "ui-shared";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
export const CredentialsResetActionMultiSelect = () => { type RequiredActionMultiSelectProps = {
name: string;
label: string;
help: string;
};
export const RequiredActionMultiSelect = ({
name,
label,
help,
}: RequiredActionMultiSelectProps) => {
const { t } = useTranslation("users"); const { t } = useTranslation("users");
const { adminClient } = useAdminClient(); const { adminClient } = useAdminClient();
const { control } = useFormContext(); const { control } = useFormContext();
@ -24,24 +34,22 @@ export const CredentialsResetActionMultiSelect = () => {
useFetch( useFetch(
() => adminClient.authenticationManagement.getRequiredActions(), () => adminClient.authenticationManagement.getRequiredActions(),
(actions) => { (actions) => {
setRequiredActions(actions); const enabledUserActions = actions.filter((action) => {
return action.enabled;
});
setRequiredActions(enabledUserActions);
}, },
[] []
); );
return ( return (
<FormGroup <FormGroup
label={t("resetActions")} label={t(label)}
labelIcon={ labelIcon={<HelpItem helpText={t(help)} fieldLabelId="resetActions" />}
<HelpItem
helpText={t("clients-help:resetActions")}
fieldLabelId="resetActions"
/>
}
fieldId="actions" fieldId="actions"
> >
<Controller <Controller
name="actions" name={name}
defaultValue={[]} defaultValue={[]}
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
@ -52,6 +60,7 @@ export const CredentialsResetActionMultiSelect = () => {
chipGroupProps={{ chipGroupProps={{
numChips: 3, numChips: 3,
}} }}
placeholderText={t("requiredActionPlaceholder")}
menuAppendTo="parent" menuAppendTo="parent"
onToggle={(open) => setOpen(open)} onToggle={(open) => setOpen(open)}
isOpen={open} isOpen={open}

View file

@ -3,7 +3,7 @@ import { FormProvider, useForm, useWatch } from "react-hook-form";
import { ModalVariant, Form, AlertVariant } from "@patternfly/react-core"; import { ModalVariant, Form, AlertVariant } from "@patternfly/react-core";
import type { RequiredActionAlias } from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation"; import type { RequiredActionAlias } from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation";
import { CredentialsResetActionMultiSelect } from "./CredentialsResetActionMultiSelect"; import { RequiredActionMultiSelect } from "./RequiredActionMultiSelect";
import { ConfirmDialogModal } from "../../components/confirm-dialog/ConfirmDialog"; import { ConfirmDialogModal } from "../../components/confirm-dialog/ConfirmDialog";
import { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient } from "../../context/auth/AdminClient";
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
@ -84,7 +84,11 @@ export const ResetCredentialDialog = ({
data-testid="credential-reset-modal" data-testid="credential-reset-modal"
> >
<FormProvider {...form}> <FormProvider {...form}>
<CredentialsResetActionMultiSelect /> <RequiredActionMultiSelect
name="actions"
label="resetActions"
help="clients-help:resetActions"
/>
<LifespanField /> <LifespanField />
</FormProvider> </FormProvider>
</Form> </Form>