diff --git a/apps/admin-ui/package.json b/apps/admin-ui/package.json index f933535357..e66e674f81 100644 --- a/apps/admin-ui/package.json +++ b/apps/admin-ui/package.json @@ -46,7 +46,7 @@ } }, "dependencies": { - "@keycloak/keycloak-admin-client": "^19.0.3", + "@keycloak/keycloak-admin-client": "^21.0.0-dev.3", "@patternfly/patternfly": "^4.219.2", "@patternfly/react-code-editor": "^4.82.55", "@patternfly/react-core": "^4.258.3", diff --git a/apps/admin-ui/public/resources/en/users.json b/apps/admin-ui/public/resources/en/users.json index e6316bdb54..e5b22e5ebf 100644 --- a/apps/admin-ui/public/resources/en/users.json +++ b/apps/admin-ui/public/resources/en/users.json @@ -150,6 +150,7 @@ "type": "Type", "userLabel": "User label", "data": "Data", + "providedBy": "Provided by", "passwordDataTitle": "Password data", "updateCredentialUserLabelSuccess": "The user label has been changed successfully.", "updateCredentialUserLabelError": "Error changing user label: {{error}}", diff --git a/apps/admin-ui/src/clients/authorization/ResourcesPolicySelect.tsx b/apps/admin-ui/src/clients/authorization/ResourcesPolicySelect.tsx index a70b45befc..3c843c9a39 100644 --- a/apps/admin-ui/src/clients/authorization/ResourcesPolicySelect.tsx +++ b/apps/admin-ui/src/clients/authorization/ResourcesPolicySelect.tsx @@ -94,6 +94,10 @@ export const ResourcesPolicySelect = ({ ]) ) .flat() + .filter( + (r): r is PolicyRepresentation | ResourceRepresentation => + typeof r !== "string" + ) .map(convert) .filter( ({ id }, index, self) => diff --git a/apps/admin-ui/src/realm-settings/NewAttributeSettings.tsx b/apps/admin-ui/src/realm-settings/NewAttributeSettings.tsx index ed9e1fae9a..0001be6bf8 100644 --- a/apps/admin-ui/src/realm-settings/NewAttributeSettings.tsx +++ b/apps/admin-ui/src/realm-settings/NewAttributeSettings.tsx @@ -134,24 +134,13 @@ export default function NewAttributeSettings() { flatten({ permissions, selector, required }, { safe: true }) ).map(([key, value]) => form.setValue(key, value)); form.setValue("annotations", convert(annotations)); - form.setValue("validations", convert(validations)); + form.setValue("validations", validations); form.setValue("isRequired", required !== undefined); }, [] ); const save = async (profileConfig: UserProfileAttributeType) => { - const validations = profileConfig.validations?.reduce( - (prevValidations: any, currentValidations: any) => { - prevValidations[currentValidations.key] = - currentValidations.value?.length === 0 - ? {} - : currentValidations.value; - return prevValidations; - }, - {} - ); - const annotations = (profileConfig.annotations! as KeyValueType[]).reduce( (obj, item) => Object.assign(obj, { [item.key]: item.value }), {} @@ -169,7 +158,6 @@ export default function NewAttributeSettings() { ...attribute, name: attributeName, displayName: profileConfig.displayName!, - validations, selector: profileConfig.selector, permissions: profileConfig.permissions!, annotations, @@ -188,7 +176,6 @@ export default function NewAttributeSettings() { name: profileConfig.name, displayName: profileConfig.displayName!, required: profileConfig.isRequired ? profileConfig.required : {}, - validations, selector: profileConfig.selector, permissions: profileConfig.permissions!, annotations, diff --git a/apps/admin-ui/src/user/UserCredentials.tsx b/apps/admin-ui/src/user/UserCredentials.tsx index 90b04a39c5..8f7bccb7d4 100644 --- a/apps/admin-ui/src/user/UserCredentials.tsx +++ b/apps/admin-ui/src/user/UserCredentials.tsx @@ -37,6 +37,7 @@ import { CredentialRow } from "./user-credentials/CredentialRow"; import { toUpperCase } from "../util"; import "./user-credentials.css"; +import { FederatedCredentials } from "./user-credentials/FederatedCredentials"; type UserCredentialsProps = { user: UserRepresentation; @@ -361,7 +362,7 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => { )} - {groupedUserCredentials.length !== 0 ? ( + {groupedUserCredentials.length !== 0 && ( <> {user.email && ( )} - + @@ -490,26 +488,31 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => { - ) : ( - )} + {(user.federationLink || user.origin) && ( + + )} + {groupedUserCredentials.length === 0 && + !(user.federationLink || user.origin) && ( + + )} ); }; diff --git a/apps/admin-ui/src/user/user-credentials/FederatedCredentials.tsx b/apps/admin-ui/src/user/user-credentials/FederatedCredentials.tsx new file mode 100644 index 0000000000..d2dd61a664 --- /dev/null +++ b/apps/admin-ui/src/user/user-credentials/FederatedCredentials.tsx @@ -0,0 +1,112 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + Button, + PageSection, + PageSectionVariants, +} from "@patternfly/react-core"; +import { + TableComposable, + Tbody, + Td, + Th, + Thead, + Tr, +} from "@patternfly/react-table"; + +import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation"; +import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation"; +import { useAdminClient, useFetch } from "../../context/auth/AdminClient"; +import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner"; +import { useAccess } from "../../context/access/Access"; +import { toUserFederationLdap } from "../../user-federation/routes/UserFederationLdap"; +import { useRealm } from "../../context/realm-context/RealmContext"; +import { Link } from "react-router-dom"; + +type FederatedCredentialsProps = { + user: UserRepresentation; + onSetPassword: () => void; +}; + +export const FederatedCredentials = ({ + user, + onSetPassword, +}: FederatedCredentialsProps) => { + const { t } = useTranslation("users"); + const { adminClient } = useAdminClient(); + const { realm } = useRealm(); + + const [credentialTypes, setCredentialTypes] = useState(); + const [component, setComponent] = useState(); + const access = useAccess(); + + useFetch( + () => + Promise.all([ + adminClient.users.getUserStorageCredentialTypes({ id: user.id! }), + access.hasAccess("view-realm") + ? adminClient.components.findOne({ + id: (user.federationLink || user.origin)!, + }) + : adminClient.userStorageProvider.name({ + id: (user.federationLink || user.origin)!, + }), + ]), + ([credentialTypes, component]) => { + setCredentialTypes(credentialTypes); + setComponent(component); + }, + [] + ); + + if (!credentialTypes || !component) { + return ; + } + + return ( + + + + + {t("type")} + {t("providedBy")} + + + + + {credentialTypes.map((credential) => ( + + + {credential} + + + + + {credential === "password" && ( + + + + )} + + ))} + + + + ); +}; diff --git a/package-lock.json b/package-lock.json index f85ace6e40..cff740b7be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -140,7 +140,7 @@ }, "apps/admin-ui": { "dependencies": { - "@keycloak/keycloak-admin-client": "^19.0.3", + "@keycloak/keycloak-admin-client": "^21.0.0-dev.3", "@patternfly/patternfly": "^4.219.2", "@patternfly/react-code-editor": "^4.82.55", "@patternfly/react-core": "^4.258.3", @@ -2953,13 +2953,13 @@ } }, "node_modules/@keycloak/keycloak-admin-client": { - "version": "19.0.3", - "license": "Apache-2.0", + "version": "21.0.0-dev.3", + "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-21.0.0-dev.3.tgz", + "integrity": "sha512-wFvxZXrVGiHgd3OCab4YWAwcinykPYhuNlO8BokyP6XClKKL5qCQgNm+iWxgDB/njUdY3ovqDBTnY9KUKI3qWA==", "dependencies": { "axios": "^0.27.2", "camelize-ts": "^2.1.1", "lodash-es": "^4.17.21", - "query-string": "^7.0.1", "url-join": "^5.0.0", "url-template": "^3.0.0" }, @@ -6822,6 +6822,7 @@ }, "node_modules/decode-uri-component": { "version": "0.2.0", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10" @@ -8354,13 +8355,6 @@ "node": ">=8" } }, - "node_modules/filter-obj": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/find-cache-dir": { "version": "3.3.2", "dev": true, @@ -11726,22 +11720,6 @@ "node": ">=0.6" } }, - "node_modules/query-string": { - "version": "7.1.1", - "license": "MIT", - "dependencies": { - "decode-uri-component": "^0.2.0", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/querystring": { "version": "0.2.0", "dev": true, @@ -12856,13 +12834,6 @@ "dev": true, "license": "MIT" }, - "node_modules/split-on-first": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/split-string": { "version": "3.1.0", "dev": true, @@ -13054,13 +13025,6 @@ "dev": true, "license": "MIT" }, - "node_modules/strict-uri-encode": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/string_decoder": { "version": "1.1.1", "dev": true, @@ -16898,12 +16862,13 @@ } }, "@keycloak/keycloak-admin-client": { - "version": "19.0.3", + "version": "21.0.0-dev.3", + "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-21.0.0-dev.3.tgz", + "integrity": "sha512-wFvxZXrVGiHgd3OCab4YWAwcinykPYhuNlO8BokyP6XClKKL5qCQgNm+iWxgDB/njUdY3ovqDBTnY9KUKI3qWA==", "requires": { "axios": "^0.27.2", "camelize-ts": "^2.1.1", "lodash-es": "^4.17.21", - "query-string": "^7.0.1", "url-join": "^5.0.0", "url-template": "^3.0.0" } @@ -18311,7 +18276,7 @@ "@babel/preset-env": "^7.19.4", "@cypress/webpack-batteries-included-preprocessor": "^2.2.3", "@cypress/webpack-preprocessor": "^5.15.3", - "@keycloak/keycloak-admin-client": "^19.0.3", + "@keycloak/keycloak-admin-client": "^21.0.0-dev.3", "@octokit/rest": "^19.0.5", "@patternfly/patternfly": "^4.219.2", "@patternfly/react-code-editor": "^4.82.55", @@ -19725,7 +19690,8 @@ "dev": true }, "decode-uri-component": { - "version": "0.2.0" + "version": "0.2.0", + "dev": true }, "decompress": { "version": "4.2.1", @@ -20742,9 +20708,6 @@ "to-regex-range": "^5.0.1" } }, - "filter-obj": { - "version": "1.1.0" - }, "find-cache-dir": { "version": "3.3.2", "dev": true, @@ -22943,15 +22906,6 @@ "version": "6.5.3", "dev": true }, - "query-string": { - "version": "7.1.1", - "requires": { - "decode-uri-component": "^0.2.0", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - } - }, "querystring": { "version": "0.2.0", "dev": true @@ -23686,9 +23640,6 @@ "version": "1.4.8", "dev": true }, - "split-on-first": { - "version": "1.1.0" - }, "split-string": { "version": "3.1.0", "dev": true, @@ -23830,9 +23781,6 @@ "version": "1.0.1", "dev": true }, - "strict-uri-encode": { - "version": "2.0.0" - }, "string_decoder": { "version": "1.1.1", "dev": true,