diff --git a/package.json b/package.json index 8f6471bb25..4e73cbe540 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@patternfly/react-table": "4.16.20", "file-saver": "^2.0.2", "i18next": "^19.6.2", - "keycloak-js": "^11.0.0", + "keycloak-admin": "^1.14.1", "react": "^16.8.5", "react-dom": "^16.8.5", "react-hook-form": "^6.8.2", diff --git a/src/App.tsx b/src/App.tsx index f2c396d770..4b9fc710ce 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,6 @@ import { Header } from "./PageHeader"; import { PageNav } from "./PageNav"; import { Help } from "./components/help-enabler/HelpHeader"; -import { RealmContextProvider } from "./context/realm-context/RealmContext"; import { WhoAmIContextProvider } from "./context/whoami/WhoAmI"; import { ServerInfoProvider } from "./context/server-info/ServerInfoProvider"; import { AlertProvider } from "./components/alert/Alerts"; @@ -18,15 +17,13 @@ import { ForbiddenSection } from "./ForbiddenSection"; const AppContexts = ({ children }: { children: ReactNode }) => ( - - - - - {children} - - - - + + + + {children} + + + ); diff --git a/src/PageHeader.tsx b/src/PageHeader.tsx index c7a22e771f..86bd789a1d 100644 --- a/src/PageHeader.tsx +++ b/src/PageHeader.tsx @@ -14,12 +14,139 @@ import { PageHeaderToolsGroup, } from "@patternfly/react-core"; import { HelpIcon } from "@patternfly/react-icons"; -import { KeycloakContext } from "./context/auth/KeycloakContext"; import { WhoAmIContext } from "./context/whoami/WhoAmI"; import { HelpHeader } from "./components/help-enabler/HelpHeader"; import { Link } from "react-router-dom"; +import { useAdminClient } from "./context/auth/AdminClient"; export const Header = () => { + const adminClient = useAdminClient(); + const { t } = useTranslation(); + + const ManageAccountDropdownItem = () => { + return ( + adminClient.keycloak.accountManagement()} + > + {t("manageAccount")} + + ); + }; + + const SignOutDropdownItem = () => { + return ( + adminClient.keycloak.logout({ redirectUri: "" })} + > + {t("signOut")} + + ); + }; + + const ServerInfoDropdownItem = () => { + const { t } = useTranslation(); + return {t("serverInfo")}; + }; + + const HelpDropdownItem = () => { + const { t } = useTranslation(); + return }>{t("help")}; + }; + + const kebabDropdownItems = [ + , + , + , + , + , + ]; + + const userDropdownItems = [ + , + , + , + , + ]; + + const headerTools = () => { + return ( + + + + + + + + + + + + + + ); + }; + + const KebabDropdown = () => { + const [isDropdownOpen, setDropdownOpen] = useState(false); + + const onDropdownToggle = () => { + setDropdownOpen(!isDropdownOpen); + }; + + return ( + } + isOpen={isDropdownOpen} + dropdownItems={kebabDropdownItems} + /> + ); + }; + + const UserDropdown = () => { + const whoami = useContext(WhoAmIContext); + const [isDropdownOpen, setDropdownOpen] = useState(false); + + const onDropdownToggle = () => { + setDropdownOpen(!isDropdownOpen); + }; + + return ( + + {whoami.getDisplayName()} + + } + dropdownItems={userDropdownItems} + /> + ); + }; + return ( { /> ); }; - -const ManageAccountDropdownItem = () => { - const keycloak = useContext(KeycloakContext); - const { t } = useTranslation(); - return ( - keycloak?.account()}> - {t("manageAccount")} - - ); -}; - -const SignOutDropdownItem = () => { - const keycloak = useContext(KeycloakContext); - const { t } = useTranslation(); - return ( - keycloak?.logout()}> - {t("signOut")} - - ); -}; - -const ServerInfoDropdownItem = () => { - const { t } = useTranslation(); - return {t("serverInfo")}; -}; - -const HelpDropdownItem = () => { - const { t } = useTranslation(); - return }>{t("help")}; -}; - -const kebabDropdownItems = [ - , - , - , - , - , -]; - -const userDropdownItems = [ - , - , - , - , -]; - -const headerTools = () => { - return ( - - - - - - - - - - - - - - ); -}; - -const KebabDropdown = () => { - const [isDropdownOpen, setDropdownOpen] = useState(false); - - const onDropdownToggle = () => { - setDropdownOpen(!isDropdownOpen); - }; - - return ( - } - isOpen={isDropdownOpen} - dropdownItems={kebabDropdownItems} - /> - ); -}; - -const UserDropdown = () => { - const keycloak = useContext(KeycloakContext); - const whoami = useContext(WhoAmIContext); - const [isDropdownOpen, setDropdownOpen] = useState(false); - - const onDropdownToggle = () => { - setDropdownOpen(!isDropdownOpen); - }; - - return ( - - {whoami.getDisplayName()} - - } - dropdownItems={userDropdownItems} - /> - ); -}; diff --git a/src/PageNav.tsx b/src/PageNav.tsx index 350b7ee4ea..d3dd3b300c 100644 --- a/src/PageNav.tsx +++ b/src/PageNav.tsx @@ -1,4 +1,4 @@ -import React, { useState, useContext } from "react"; +import React, { useState } from "react"; import { useHistory } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { @@ -10,20 +10,16 @@ import { } from "@patternfly/react-core"; import { RealmSelector } from "./components/realm-selector/RealmSelector"; import { DataLoader } from "./components/data-loader/DataLoader"; -import { HttpClientContext } from "./context/http-service/HttpClientContext"; +import { useAdminClient } from "./context/auth/AdminClient"; import { useAccess } from "./context/access/Access"; -import { RealmRepresentation } from "./realm/models/Realm"; import { routes } from "./route-config"; export const PageNav: React.FunctionComponent = () => { const { t } = useTranslation("common"); const { hasAccess, hasSomeAccess } = useAccess(); - const httpClient = useContext(HttpClientContext)!; + const adminClient = useAdminClient(); const realmLoader = async () => { - const response = await httpClient.doGet( - "/admin/realms" - ); - return response.data; + return await adminClient.realms.find(); }; const history = useHistory(); diff --git a/src/client-scopes/ClientScopesSection.tsx b/src/client-scopes/ClientScopesSection.tsx index f9bb79a6ff..4972ee0d08 100644 --- a/src/client-scopes/ClientScopesSection.tsx +++ b/src/client-scopes/ClientScopesSection.tsx @@ -1,14 +1,13 @@ -import React, { useContext, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { Button, PageSection, Spinner } from "@patternfly/react-core"; import { useTranslation } from "react-i18next"; import { useHistory } from "react-router-dom"; -import { RealmContext } from "../context/realm-context/RealmContext"; -import { HttpClientContext } from "../context/http-service/HttpClientContext"; -import { ClientRepresentation } from "../realm/models/Realm"; +import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; import { TableToolbar } from "../components/table-toolbar/TableToolbar"; import { ClientScopeList } from "./ClientScopesList"; import { ViewHeader } from "../components/view-header/ViewHeader"; +import { useAdminClient } from "../context/auth/AdminClient"; export const ClientScopesSection = () => { const { t } = useTranslation("client-scopes"); @@ -16,25 +15,22 @@ export const ClientScopesSection = () => { const [rawData, setRawData] = useState(); const [filteredData, setFilteredData] = useState(); - const httpClient = useContext(HttpClientContext)!; - const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); useEffect(() => { (async () => { if (filteredData) { return filteredData; } - const result = await httpClient.doGet( - `/admin/realms/${realm}/client-scopes` - ); - setRawData(result.data!); + const result = await adminClient.clientScopes.find(); + setRawData(result); })(); }, []); const filterData = (search: string) => { setFilteredData( rawData!.filter((group) => - group.name.toLowerCase().includes(search.toLowerCase()) + group.name!.toLowerCase().includes(search.toLowerCase()) ) ); }; diff --git a/src/client-scopes/add/MapperDialog.tsx b/src/client-scopes/add/MapperDialog.tsx index fb81fd1c82..d8ba827921 100644 --- a/src/client-scopes/add/MapperDialog.tsx +++ b/src/client-scopes/add/MapperDialog.tsx @@ -19,12 +19,10 @@ import { TableVariant, } from "@patternfly/react-table"; import { useTranslation } from "react-i18next"; +import ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation"; +import { ProtocolMapperTypeRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; -import { - ProtocolMapperRepresentation, - ProtocolMapperTypeRepresentation, -} from "../../context/server-info/server-info"; import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState"; export type AddMapperDialogModalProps = { @@ -45,8 +43,8 @@ export const AddMapperDialog = (props: AddMapperDialogProps) => { const serverInfo = useServerInfo(); const protocol = props.protocol; - const protocolMappers = serverInfo.protocolMapperTypes[protocol]; - const builtInMappers = serverInfo.builtinProtocolMappers[protocol]; + const protocolMappers = serverInfo.protocolMapperTypes![protocol]; + const builtInMappers = serverInfo.builtinProtocolMappers![protocol]; const [filter, setFilter] = useState([]); const allRows = builtInMappers.map((mapper) => { diff --git a/src/client-scopes/add/NewClientScopeForm.tsx b/src/client-scopes/add/NewClientScopeForm.tsx new file mode 100644 index 0000000000..0a362e7c6f --- /dev/null +++ b/src/client-scopes/add/NewClientScopeForm.tsx @@ -0,0 +1,222 @@ +import React, { useState } from "react"; +import { useHistory } from "react-router-dom"; +import { + ActionGroup, + AlertVariant, + Button, + Form, + FormGroup, + PageSection, + Select, + SelectOption, + SelectVariant, + Switch, + TextInput, +} from "@patternfly/react-core"; +import { useTranslation } from "react-i18next"; +import { Controller, useForm } from "react-hook-form"; + +import { ClientScopeRepresentation } from "../models/client-scope"; +import { HelpItem } from "../../components/help-enabler/HelpItem"; +import { useAlerts } from "../../components/alert/Alerts"; +import { useAdminClient } from "../../context/auth/AdminClient"; +import { ViewHeader } from "../../components/view-header/ViewHeader"; +import { useLoginProviders } from "../../context/server-info/ServerInfoProvider"; + +export const NewClientScopeForm = () => { + const { t } = useTranslation("client-scopes"); + const helpText = useTranslation("client-scopes-help").t; + const { register, control, handleSubmit, errors } = useForm< + ClientScopeRepresentation + >(); + const history = useHistory(); + + const adminClient = useAdminClient(); + const providers = useLoginProviders(); + + const [open, isOpen] = useState(false); + const { addAlert } = useAlerts(); + + const save = async (clientScopes: ClientScopeRepresentation) => { + try { + const keyValues = Object.keys(clientScopes.attributes!).map((key) => { + const newKey = key.replace(/_/g, "."); + return { [newKey]: clientScopes.attributes![key] }; + }); + clientScopes.attributes = Object.assign({}, ...keyValues); + + await adminClient.clientScopes.create({ ...clientScopes }); + addAlert(t("createClientScopeSuccess"), AlertVariant.success); + } catch (error) { + addAlert( + `${t("createClientScopeError")} '${error}'`, + AlertVariant.danger + ); + } + }; + + return ( + <> + + + +
+ + } + fieldId="kc-name" + isRequired + validated={errors.name ? "error" : "default"} + helperTextInvalid={t("common:required")} + > + + + + } + fieldId="kc-description" + > + + + + } + fieldId="kc-protocol" + > + ( + + )} + /> + + + } + fieldId="kc-display.on.consent.screen" + > + ( + + )} + /> + + + } + fieldId="kc-consent-screen-text" + > + + + + } + fieldId="kc-gui-order" + > + + + + + + +
+
+ + ); +}; diff --git a/src/client-scopes/add/RoleMappingForm.tsx b/src/client-scopes/add/RoleMappingForm.tsx index eebfade001..cd6c50f693 100644 --- a/src/client-scopes/add/RoleMappingForm.tsx +++ b/src/client-scopes/add/RoleMappingForm.tsx @@ -19,16 +19,14 @@ import { ValidatedOptions, } from "@patternfly/react-core"; +import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation"; +import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; import { useAlerts } from "../../components/alert/Alerts"; import { RealmContext } from "../../context/realm-context/RealmContext"; -import { HttpClientContext } from "../../context/http-service/HttpClientContext"; +import { useAdminClient } from "../../context/auth/AdminClient"; import { ViewHeader } from "../../components/view-header/ViewHeader"; import { HelpItem } from "../../components/help-enabler/HelpItem"; -import { - ClientRepresentation, - RoleRepresentation, -} from "../../realm/models/Realm"; import { ProtocolMapperRepresentation } from "../models/client-scope"; export type RoleMappingFormProps = { @@ -36,8 +34,8 @@ export type RoleMappingFormProps = { }; export const RoleMappingForm = ({ clientScopeId }: RoleMappingFormProps) => { - const httpClient = useContext(HttpClientContext)!; const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const history = useHistory(); const { addAlert } = useAlerts(); @@ -54,18 +52,13 @@ export const RoleMappingForm = ({ clientScopeId }: RoleMappingFormProps) => { useEffect(() => { (async () => { - const response = await httpClient.doGet( - `/admin/realms/${realm}/roles` - ); - setRoles(response.data!); - const clientResponse = await httpClient.doGet( - `/admin/realms/${realm}/clients` - ); - const clients = clientResponse.data!; + const roles = await adminClient.roles.find(); + setRoles(roles); + const clients = await adminClient.clients.find(); clients.map( (client) => (client.toString = function () { - return this.name; + return this.name!; }) ); setClients(clients); @@ -76,19 +69,15 @@ export const RoleMappingForm = ({ clientScopeId }: RoleMappingFormProps) => { (async () => { const client = selectedClient as ClientRepresentation; if (client && client.name !== "realmRoles") { - const response = await httpClient.doGet( - `/admin/realms/master/clients/${client.id}/roles` - ); - - setClientRoles(response.data!); + setClientRoles(await adminClient.clients.listRoles({ id: client.id! })); } })(); }, [selectedClient]); const save = async (mapping: ProtocolMapperRepresentation) => { try { - await httpClient.doPost( - `/admin/realms/${realm}/client-scopes/${clientScopeId}/protocol-mappers/models`, + await adminClient.clientScopes.addProtocolMapper( + { id: clientScopeId }, mapping ); addAlert(t("mapperCreateSuccess")); @@ -219,8 +208,8 @@ export const RoleMappingForm = ({ clientScopeId }: RoleMappingFormProps) => { } else { return createSelectGroup( clients.filter((client) => - client.name - .toLowerCase() + client + .name!.toLowerCase() .includes(textInput.toLowerCase()) ) ); diff --git a/src/client-scopes/add/__tests__/__snapshots__/MapperDialog.test.tsx.snap b/src/client-scopes/add/__tests__/__snapshots__/MapperDialog.test.tsx.snap index 7201f652b9..caf40430c3 100644 --- a/src/client-scopes/add/__tests__/__snapshots__/MapperDialog.test.tsx.snap +++ b/src/client-scopes/add/__tests__/__snapshots__/MapperDialog.test.tsx.snap @@ -21,14 +21,14 @@ exports[` should return empty array when selecting nothing 1`] = isDisabled={false} onClick={[Function]} > - add + Add , , ] } @@ -42,7 +42,7 @@ exports[` should return empty array when selecting nothing 1`] = onClose={[Function]} ouiaSafe={true} showClose={true} - title="chooseAMapperType" + title="Choose a mapper type" variant="medium" > should return empty array when selecting nothing 1`] = class="pf-c-modal-box__title pf-c-modal-box__title" id="pf-modal-part-1" > - chooseAMapperType + Choose a mapper type
should return empty array when selecting nothing 1`] = class="" data-pf-content="true" > - predefinedMappingDescription + Choose one of the predefined mappings from this table

should return empty array when selecting nothing 1`] = @@ -169,14 +169,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -200,14 +200,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -231,14 +231,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -262,14 +262,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -293,14 +293,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -324,14 +324,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -355,14 +355,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -386,14 +386,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -417,14 +417,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -448,14 +448,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -479,14 +479,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -510,14 +510,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -541,14 +541,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -572,14 +572,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -603,14 +603,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -634,14 +634,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -665,14 +665,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -696,14 +696,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -727,14 +727,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -758,14 +758,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -789,14 +789,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -820,14 +820,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -851,14 +851,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -882,14 +882,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -945,14 +945,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -976,14 +976,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -1007,14 +1007,14 @@ exports[` should return empty array when selecting nothing 1`] = @@ -1034,7 +1034,7 @@ exports[` should return empty array when selecting nothing 1`] = id="modal-confirm" type="button" > - add + Add @@ -1062,14 +1062,14 @@ exports[` should return empty array when selecting nothing 1`] = isDisabled={false} onClick={[Function]} > - add + Add , , ] } @@ -1086,7 +1086,7 @@ exports[` should return empty array when selecting nothing 1`] = ouiaId="OUIA-Generated-Modal-medium-1" ouiaSafe={true} showClose={true} - title="chooseAMapperType" + title="Choose a mapper type" variant="medium" > @@ -1185,13 +1185,13 @@ exports[` should return empty array when selecting nothing 1`] =

- chooseAMapperType + Choose a mapper type

@@ -1212,19 +1212,19 @@ exports[` should return empty array when selecting nothing 1`] = className="" data-pf-content={true} > - predefinedMappingDescription + Choose one of the predefined mappings from this table

- name + Name - description + Description
zoneinfo Map a custom user attribute to a token claim. birthdate Map a custom user attribute to a token claim. family name Map a built in user property (email, firstName, lastName) to a token claim. gender Map a custom user attribute to a token claim. Impersonator Username Map a custom user session note to a token claim. phone number verified Map a custom user attribute to a token claim. locale Map a custom user attribute to a token claim. gss delegation credential Map a custom user session note to a token claim. allowed web origins Adds all allowed web origins to the 'allowed-origins' claim in the token middle name Map a custom user attribute to a token claim. nickname Map a custom user attribute to a token claim. updated at Map a custom user attribute to a token claim. email verified Map a built in user property (email, firstName, lastName) to a token claim. email Map a built in user property (email, firstName, lastName) to a token claim. client roles Map a user client role to a token claim. Impersonator User ID Map a custom user session note to a token claim. website Map a custom user attribute to a token claim. address Maps user address attributes (street, locality, region, postal_code, and country) to the OpenID Connect 'address' claim. given name Map a built in user property (email, firstName, lastName) to a token claim. profile Map a custom user attribute to a token claim. groups Map a user realm role to a token claim. phone number Map a custom user attribute to a token claim. full name Maps the user's first and last name to the OpenID Connect 'name' claim. Format is <first> + ' ' + <last> audience resolve Adds all client_ids of "allowed" clients to the audience field of the token. Allowed client means the client for which user has at least one client role @@ -914,14 +914,14 @@ exports[` should return empty array when selecting nothing 1`] = picture Map a custom user attribute to a token claim. upn Map a built in user property (email, firstName, lastName) to a token claim. realm roles Map a user realm role to a token claim. username Map a built in user property (email, firstName, lastName) to a token claim.
should return empty array when selecting nothing 1`] = variant="compact" > should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -1902,7 +1902,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -1936,7 +1936,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -1945,7 +1945,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -1968,7 +1968,7 @@ exports[` should return empty array when selecting nothing 1`] = role="grid" >
should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -2071,7 +2071,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -2105,7 +2105,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -2114,7 +2114,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -2225,7 +2225,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -2234,7 +2234,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -2268,7 +2268,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -2277,7 +2277,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -2300,34 +2300,34 @@ exports[` should return empty array when selecting nothing 1`] = @@ -2417,7 +2417,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -2426,7 +2426,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -2460,7 +2460,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -2469,7 +2469,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -5349,7 +5349,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -5358,7 +5358,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -5392,7 +5392,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -5401,7 +5401,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -8828,7 +8828,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -8837,7 +8837,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -8871,7 +8871,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -8880,7 +8880,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -9030,14 +9030,14 @@ exports[` should return empty array when selecting nothing 1`] =
- name + Name - description + Description
zoneinfo @@ -9045,14 +9045,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user attribute to a token claim. @@ -9139,7 +9139,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -9148,7 +9148,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -9182,7 +9182,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -9191,7 +9191,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -9341,14 +9341,14 @@ exports[` should return empty array when selecting nothing 1`] = birthdate @@ -9356,14 +9356,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user attribute to a token claim. @@ -9450,7 +9450,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -9459,7 +9459,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -9493,7 +9493,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -9502,7 +9502,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -9652,14 +9652,14 @@ exports[` should return empty array when selecting nothing 1`] = family name @@ -9667,14 +9667,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a built in user property (email, firstName, lastName) to a token claim. @@ -9761,7 +9761,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -9770,7 +9770,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -9804,7 +9804,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -9813,7 +9813,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -9963,14 +9963,14 @@ exports[` should return empty array when selecting nothing 1`] = gender @@ -9978,14 +9978,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user attribute to a token claim. @@ -10072,7 +10072,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -10081,7 +10081,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -10115,7 +10115,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -10124,7 +10124,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -10272,14 +10272,14 @@ exports[` should return empty array when selecting nothing 1`] = Impersonator Username @@ -10287,14 +10287,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user session note to a token claim. @@ -10381,7 +10381,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -10390,7 +10390,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -10424,7 +10424,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -10433,7 +10433,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -10583,14 +10583,14 @@ exports[` should return empty array when selecting nothing 1`] = phone number verified @@ -10598,14 +10598,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user attribute to a token claim. @@ -10692,7 +10692,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -10701,7 +10701,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -10735,7 +10735,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -10744,7 +10744,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -10894,14 +10894,14 @@ exports[` should return empty array when selecting nothing 1`] = locale @@ -10909,14 +10909,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user attribute to a token claim. @@ -11003,7 +11003,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -11012,7 +11012,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -11046,7 +11046,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -11055,7 +11055,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -11201,14 +11201,14 @@ exports[` should return empty array when selecting nothing 1`] = gss delegation credential @@ -11216,14 +11216,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user session note to a token claim. @@ -11310,7 +11310,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -11319,7 +11319,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -11353,7 +11353,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -11362,7 +11362,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -11498,14 +11498,14 @@ exports[` should return empty array when selecting nothing 1`] = allowed web origins @@ -11513,14 +11513,14 @@ exports[` should return empty array when selecting nothing 1`] = Adds all allowed web origins to the 'allowed-origins' claim in the token @@ -11607,7 +11607,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -11616,7 +11616,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -11650,7 +11650,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -11659,7 +11659,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -11809,14 +11809,14 @@ exports[` should return empty array when selecting nothing 1`] = middle name @@ -11824,14 +11824,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user attribute to a token claim. @@ -11918,7 +11918,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -11927,7 +11927,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -11961,7 +11961,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -11970,7 +11970,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -12120,14 +12120,14 @@ exports[` should return empty array when selecting nothing 1`] = nickname @@ -12135,14 +12135,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user attribute to a token claim. @@ -12229,7 +12229,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -12238,7 +12238,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -12272,7 +12272,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -12281,7 +12281,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -12431,14 +12431,14 @@ exports[` should return empty array when selecting nothing 1`] = updated at @@ -12446,14 +12446,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user attribute to a token claim. @@ -12540,7 +12540,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -12549,7 +12549,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -12583,7 +12583,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -12592,7 +12592,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -12742,14 +12742,14 @@ exports[` should return empty array when selecting nothing 1`] = email verified @@ -12757,14 +12757,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a built in user property (email, firstName, lastName) to a token claim. @@ -12851,7 +12851,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -12860,7 +12860,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -12894,7 +12894,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -12903,7 +12903,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -13053,14 +13053,14 @@ exports[` should return empty array when selecting nothing 1`] = email @@ -13068,14 +13068,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a built in user property (email, firstName, lastName) to a token claim. @@ -13162,7 +13162,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -13171,7 +13171,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -13205,7 +13205,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -13214,7 +13214,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -13362,14 +13362,14 @@ exports[` should return empty array when selecting nothing 1`] = client roles @@ -13377,14 +13377,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a user client role to a token claim. @@ -13471,7 +13471,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -13480,7 +13480,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -13514,7 +13514,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -13523,7 +13523,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -13671,14 +13671,14 @@ exports[` should return empty array when selecting nothing 1`] = Impersonator User ID @@ -13686,14 +13686,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user session note to a token claim. @@ -13780,7 +13780,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -13789,7 +13789,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -13823,7 +13823,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -13832,7 +13832,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -13982,14 +13982,14 @@ exports[` should return empty array when selecting nothing 1`] = website @@ -13997,14 +13997,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user attribute to a token claim. @@ -14091,7 +14091,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -14100,7 +14100,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -14134,7 +14134,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -14143,7 +14143,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -14299,14 +14299,14 @@ exports[` should return empty array when selecting nothing 1`] = address @@ -14314,14 +14314,14 @@ exports[` should return empty array when selecting nothing 1`] = Maps user address attributes (street, locality, region, postal_code, and country) to the OpenID Connect 'address' claim. @@ -14408,7 +14408,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -14417,7 +14417,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -14451,7 +14451,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -14460,7 +14460,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -14610,14 +14610,14 @@ exports[` should return empty array when selecting nothing 1`] = given name @@ -14625,14 +14625,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a built in user property (email, firstName, lastName) to a token claim. @@ -14719,7 +14719,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -14728,7 +14728,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -14762,7 +14762,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -14771,7 +14771,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -14921,14 +14921,14 @@ exports[` should return empty array when selecting nothing 1`] = profile @@ -14936,14 +14936,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user attribute to a token claim. @@ -15030,7 +15030,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -15039,7 +15039,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -15073,7 +15073,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -15082,7 +15082,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -15232,14 +15232,14 @@ exports[` should return empty array when selecting nothing 1`] = groups @@ -15247,14 +15247,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a user realm role to a token claim. @@ -15341,7 +15341,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -15350,7 +15350,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -15384,7 +15384,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -15393,7 +15393,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -15543,14 +15543,14 @@ exports[` should return empty array when selecting nothing 1`] = phone number @@ -15558,14 +15558,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user attribute to a token claim. @@ -15652,7 +15652,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -15661,7 +15661,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -15695,7 +15695,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -15704,7 +15704,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -15848,14 +15848,14 @@ exports[` should return empty array when selecting nothing 1`] = full name @@ -15863,14 +15863,14 @@ exports[` should return empty array when selecting nothing 1`] = Maps the user's first and last name to the OpenID Connect 'name' claim. Format is <first> + ' ' + <last> @@ -15957,7 +15957,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -15966,7 +15966,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -16000,7 +16000,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -16009,7 +16009,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -16149,14 +16149,14 @@ exports[` should return empty array when selecting nothing 1`] = audience resolve @@ -16164,14 +16164,14 @@ exports[` should return empty array when selecting nothing 1`] = Adds all client_ids of "allowed" clients to the audience field of the token. Allowed client means the client @@ -16259,7 +16259,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -16268,7 +16268,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -16302,7 +16302,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -16311,7 +16311,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -16461,14 +16461,14 @@ exports[` should return empty array when selecting nothing 1`] = picture @@ -16476,14 +16476,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a custom user attribute to a token claim. @@ -16570,7 +16570,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -16579,7 +16579,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -16613,7 +16613,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -16622,7 +16622,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -16772,14 +16772,14 @@ exports[` should return empty array when selecting nothing 1`] = upn @@ -16787,14 +16787,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a built in user property (email, firstName, lastName) to a token claim. @@ -16881,7 +16881,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -16890,7 +16890,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -16924,7 +16924,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -16933,7 +16933,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -17081,14 +17081,14 @@ exports[` should return empty array when selecting nothing 1`] = realm roles @@ -17096,14 +17096,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a user realm role to a token claim. @@ -17190,7 +17190,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "name", + "label": "Name", "transforms": Array [ [Function], [Function], @@ -17199,7 +17199,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "name", "props": Object { "data-key": 1, - "data-label": "name", + "data-label": "Name", }, }, Object { @@ -17233,7 +17233,7 @@ exports[` should return empty array when selecting nothing 1`] = }, "header": Object { "formatters": Array [], - "label": "description", + "label": "Description", "transforms": Array [ [Function], [Function], @@ -17242,7 +17242,7 @@ exports[` should return empty array when selecting nothing 1`] = "property": "description", "props": Object { "data-key": 2, - "data-label": "description", + "data-label": "Description", }, }, ] @@ -17392,14 +17392,14 @@ exports[` should return empty array when selecting nothing 1`] = username @@ -17407,14 +17407,14 @@ exports[` should return empty array when selecting nothing 1`] = Map a built in user property (email, firstName, lastName) to a token claim. @@ -17456,7 +17456,7 @@ exports[` should return empty array when selecting nothing 1`] = onClick={[Function]} type="button" > - add + Add @@ -17537,7 +17537,7 @@ exports[` should return selected protocol mapping type on click 1 onClose={[Function]} ouiaSafe={true} showClose={true} - title="chooseAMapperType" + title="Choose a mapper type" variant="medium" > should return selected protocol mapping type on click 1 class="pf-c-modal-box__title pf-c-modal-box__title" id="pf-modal-part-3" > - chooseAMapperType + Choose a mapper type
should return selected protocol mapping type on click 1 class="" data-pf-content="true" > - predefinedMappingDescription + Choose one of the predefined mappings from this table

  • should return selected protocol mapping type on click 1 ouiaId="OUIA-Generated-Modal-medium-3" ouiaSafe={true} showClose={true} - title="chooseAMapperType" + title="Choose a mapper type" variant="medium" > @@ -18109,13 +18109,13 @@ exports[` should return selected protocol mapping type on click 1

    - chooseAMapperType + Choose a mapper type

    @@ -18136,18 +18136,18 @@ exports[` should return selected protocol mapping type on click 1 className="" data-pf-content={true} > - predefinedMappingDescription + Choose one of the predefined mappings from this table

      { const { t } = useTranslation("client-scopes"); - const httpClient = useContext(HttpClientContext)!; - const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const { addAlert } = useAlerts(); const [filteredData, setFilteredData] = useState< @@ -56,7 +49,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => { >(); const [mapperAction, setMapperAction] = useState(false); const mapperList = clientScope.protocolMappers!; - const mapperTypes = useServerInfo().protocolMapperTypes[ + const mapperTypes = useServerInfo().protocolMapperTypes![ clientScope.protocol! ]; @@ -67,9 +60,9 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => { mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[] ) => { try { - await httpClient.doPost( - `/admin/realms/${realm}/client-scopes/${clientScope.id}/protocol-mappers/add-models`, - mappers + await adminClient.clientScopes.addMultipleProtocolMappers( + { id: clientScope.id! }, + mappers as ProtocolMapperRepresentation[] ); refresh(); addAlert(t("mappingCreatedSuccess"), AlertVariant.success); @@ -83,7 +76,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => { <> { > { title: t("common:delete"), onClick: async (_, rowId) => { try { - await httpClient.doDelete( - `/admin/realms/${realm}/client-scopes/${clientScope.id}/protocol-mappers/models/${data[rowId].mapper.id}` - ); + await adminClient.clientScopes.delProtocolMapper({ + id: clientScope.id!, + mapperId: data[rowId].mapper.id!, + }); refresh(); addAlert(t("mappingDeletedSuccess"), AlertVariant.success); } catch (error) { diff --git a/src/client-scopes/details/MappingDetails.tsx b/src/client-scopes/details/MappingDetails.tsx index be275b6a9f..aa7e5a86eb 100644 --- a/src/client-scopes/details/MappingDetails.tsx +++ b/src/client-scopes/details/MappingDetails.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { useHistory, useParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { @@ -19,23 +19,21 @@ import { Switch, TextInput, } from "@patternfly/react-core"; +import { ConfigPropertyRepresentation } from "keycloak-admin/lib/defs/configPropertyRepresentation"; import { ViewHeader } from "../../components/view-header/ViewHeader"; -import { HttpClientContext } from "../../context/http-service/HttpClientContext"; -import { RealmContext } from "../../context/realm-context/RealmContext"; +import { useAdminClient } from "../../context/auth/AdminClient"; import { ProtocolMapperRepresentation } from "../models/client-scope"; import { Controller, useForm } from "react-hook-form"; import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; import { useAlerts } from "../../components/alert/Alerts"; import { HelpItem } from "../../components/help-enabler/HelpItem"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; -import { ConfigPropertyRepresentation } from "../../context/server-info/server-info"; import { convertFormValuesToObject, convertToFormValues } from "../../util"; export const MappingDetails = () => { const { t } = useTranslation("client-scopes"); - const httpClient = useContext(HttpClientContext)!; - const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const { addAlert } = useAlerts(); const { scopeId, id } = useParams<{ scopeId: string; id: string }>(); @@ -47,28 +45,27 @@ export const MappingDetails = () => { >(); const serverInfo = useServerInfo(); - const url = `/admin/realms/${realm}/client-scopes/${scopeId}/protocol-mappers/models/${id}`; const history = useHistory(); useEffect(() => { (async () => { - const response = await httpClient.doGet( - url - ); - if (response.data) { - Object.entries(response.data).map((entry) => { + const data = await adminClient.clientScopes.findProtocolMapper({ + id: scopeId, + mapperId: id, + }); + if (data) { + Object.entries(data).map((entry) => { if (entry[0] === "config") { convertToFormValues(entry[1], "config", setValue); } setValue(entry[0], entry[1]); }); } - setMapping(response.data); - const mapperTypes = - serverInfo.protocolMapperTypes[response.data!.protocol!]; + setMapping(data); + const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!]; const properties = mapperTypes.find( - (type) => type.id === response.data!.protocolMapper - )?.properties; + (type) => type.id === data.protocolMapper + )?.properties!; setConfigProperties(properties); })(); }, []); @@ -78,9 +75,12 @@ export const MappingDetails = () => { messageKey: "client-scopes:deleteMappingConfirm", continueButtonLabel: "common:delete", continueButtonVariant: ButtonVariant.danger, - onConfirm: () => { + onConfirm: async () => { try { - httpClient.doDelete(url); + await adminClient.clientScopes.delClientScopeMappings( + { client: scopeId, id }, + [] + ); addAlert(t("mappingDeletedSuccess"), AlertVariant.success); history.push(`/client-scopes/${scopeId}`); } catch (error) { @@ -93,7 +93,10 @@ export const MappingDetails = () => { const config = convertFormValuesToObject(formMapping.config); const map = { ...mapping, config }; try { - await httpClient.doPut(url, map); + await adminClient.clientScopes.updateProtocolMapper( + { id: scopeId, mapperId: id }, + map + ); addAlert(t("mappingUpdatedSuccess"), AlertVariant.success); } catch (error) { addAlert(t("mappingUpdatedError", { error }), AlertVariant.danger); @@ -211,8 +214,8 @@ export const MappingDetails = () => { > {configProperties && configProperties - .find((property) => property.name === "jsonType.label") - ?.options.map((option) => ( + .find((property) => property.name! === "jsonType.label") + ?.options!.map((option) => ( { const [clientScope, setClientScope] = useState(); const [activeTab, setActiveTab] = useState(0); - const httpClient = useContext(HttpClientContext)!; - const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const providers = useLoginProviders(); const { id } = useParams<{ id: string }>(); @@ -49,11 +47,9 @@ export const ClientScopeForm = () => { const load = async () => { if (id) { - const response = await httpClient.doGet( - `/admin/realms/${realm}/client-scopes/${id}` - ); - if (response.data) { - Object.entries(response.data).map((entry) => { + const data = await adminClient.clientScopes.findOne({ id }); + if (data) { + Object.entries(data).map((entry) => { if (entry[0] === "attributes") { convertToFormValues(entry[1], "attributes", setValue); } @@ -61,7 +57,7 @@ export const ClientScopeForm = () => { }); } - setClientScope(response.data); + setClientScope(data); } }; @@ -75,11 +71,10 @@ export const ClientScopeForm = () => { clientScopes.attributes! ); - const url = `/admin/realms/${realm}/client-scopes/`; if (id) { - await httpClient.doPut(url + id, clientScopes); + await adminClient.clientScopes.update({ id }, clientScopes); } else { - await httpClient.doPost(url, clientScopes); + await adminClient.clientScopes.create(clientScopes); } addAlert(t((id ? "update" : "create") + "Success"), AlertVariant.success); } catch (error) { diff --git a/src/clients/ClientDetails.tsx b/src/clients/ClientDetails.tsx index 36089e1144..07f313d735 100644 --- a/src/clients/ClientDetails.tsx +++ b/src/clients/ClientDetails.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { AlertVariant, ButtonVariant, @@ -11,16 +11,15 @@ import { import { useTranslation } from "react-i18next"; import { Controller, useForm, useWatch } from "react-hook-form"; import { useParams } from "react-router-dom"; +import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; import { ClientSettings } from "./ClientSettings"; import { useAlerts } from "../components/alert/Alerts"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useDownloadDialog } from "../components/download-dialog/DownloadDialog"; import { ViewHeader } from "../components/view-header/ViewHeader"; -import { HttpClientContext } from "../context/http-service/HttpClientContext"; -import { RealmContext } from "../context/realm-context/RealmContext"; +import { useAdminClient } from "../context/auth/AdminClient"; import { Credentials } from "./credentials/Credentials"; -import { ClientRepresentation } from "../realm/models/Realm"; import { convertFormValuesToObject, convertToFormValues, @@ -33,8 +32,7 @@ import { export const ClientDetails = () => { const { t } = useTranslation("clients"); - const httpClient = useContext(HttpClientContext)!; - const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const { addAlert } = useAlerts(); const form = useForm(); @@ -47,16 +45,15 @@ export const ClientDetails = () => { const [activeTab, setActiveTab] = useState(0); const [name, setName] = useState(""); - const url = `/admin/realms/${realm}/clients/${id}`; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: "clients:clientDeleteConfirmTitle", messageKey: "clients:clientDeleteConfirm", continueButtonLabel: "common:delete", continueButtonVariant: ButtonVariant.danger, - onConfirm: () => { + onConfirm: async () => { try { - httpClient.doDelete(`/admin/realms/${realm}/clients/${id}`); + await adminClient.clients.del({ id }); addAlert(t("clientDeletedSuccess"), AlertVariant.success); } catch (error) { addAlert(`${t("clientDeleteError")} ${error}`, AlertVariant.danger); @@ -84,10 +81,10 @@ export const ClientDetails = () => { useEffect(() => { (async () => { - const fetchedClient = await httpClient.doGet(url); - if (fetchedClient.data) { - setName(fetchedClient.data.clientId); - setupForm(fetchedClient.data); + const fetchedClient = await adminClient.clients.findOne({ id }); + if (fetchedClient) { + setName(fetchedClient.clientId!); + setupForm(fetchedClient); } })(); }, []); @@ -105,7 +102,7 @@ export const ClientDetails = () => { redirectUris, attributes, }; - await httpClient.doPut(url, client); + await adminClient.clients.update({ id }, client); setupForm(client as ClientRepresentation); addAlert(t("clientSaveSuccess"), AlertVariant.success); } catch (error) { diff --git a/src/clients/ClientList.tsx b/src/clients/ClientList.tsx index f140c4a862..63c18112eb 100644 --- a/src/clients/ClientList.tsx +++ b/src/clients/ClientList.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import { @@ -10,12 +10,11 @@ import { IFormatterValueType, } from "@patternfly/react-table"; import { Badge, AlertVariant } from "@patternfly/react-core"; +import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; import { ExternalLink } from "../components/external-link/ExternalLink"; -import { HttpClientContext } from "../context/http-service/HttpClientContext"; import { useAlerts } from "../components/alert/Alerts"; -import { ClientRepresentation } from "./models/client-model"; -import { RealmContext } from "../context/realm-context/RealmContext"; +import { useAdminClient } from "../context/auth/AdminClient"; import { exportClient } from "../util"; type ClientListProps = { @@ -33,8 +32,7 @@ const columns: (keyof ClientRepresentation)[] = [ export const ClientList = ({ baseUrl, clients, refresh }: ClientListProps) => { const { t } = useTranslation("clients"); - const httpClient = useContext(HttpClientContext)!; - const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const { addAlert } = useAlerts(); const emptyFormatter = (): IFormatter => (data?: IFormatterValueType) => { @@ -108,9 +106,9 @@ export const ClientList = ({ baseUrl, clients, refresh }: ClientListProps) => { title: t("common:delete"), onClick: async (_, rowId) => { try { - await httpClient.doDelete( - `/admin/realms/${realm}/clients/${data[rowId].client.id}` - ); + await adminClient.clients.del({ + id: data[rowId].client.id!, + }); refresh(); addAlert(t("clientDeletedSuccess"), AlertVariant.success); } catch (error) { diff --git a/src/clients/ClientsSection.tsx b/src/clients/ClientsSection.tsx index eb87751cfd..babd090cdf 100644 --- a/src/clients/ClientsSection.tsx +++ b/src/clients/ClientsSection.tsx @@ -1,15 +1,13 @@ -import React, { useState, useContext, useEffect } from "react"; +import React, { useState, useEffect } from "react"; import { useHistory } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { Button, PageSection, Spinner } from "@patternfly/react-core"; +import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; import { ClientList } from "./ClientList"; -import { HttpClientContext } from "../context/http-service/HttpClientContext"; -import { KeycloakContext } from "../context/auth/KeycloakContext"; -import { ClientRepresentation } from "./models/client-model"; -import { RealmContext } from "../context/realm-context/RealmContext"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTableToolbar"; +import { useAdminClient } from "../context/auth/AdminClient"; export const ClientsSection = () => { const { t } = useTranslation("clients"); @@ -18,10 +16,8 @@ export const ClientsSection = () => { const [max, setMax] = useState(10); const [first, setFirst] = useState(0); const [search, setSearch] = useState(""); + const adminClient = useAdminClient(); const [clients, setClients] = useState(); - const httpClient = useContext(HttpClientContext)!; - const keycloak = useContext(KeycloakContext); - const { realm } = useContext(RealmContext); const loader = async () => { const params: { [name: string]: string | number } = { first, max }; @@ -29,11 +25,8 @@ export const ClientsSection = () => { params.clientId = search; params.search = "true"; } - const result = await httpClient.doGet( - `/admin/realms/${realm}/clients`, - { params: params } - ); - setClients(result.data); + const result = await adminClient.clients.find({ ...params }); + setClients(result); }; useEffect(() => { @@ -84,7 +77,7 @@ export const ClientsSection = () => { )} diff --git a/src/clients/__tests__/ClientList.test.tsx b/src/clients/__tests__/ClientList.test.tsx index be79ab3a2c..c7ab1696bf 100644 --- a/src/clients/__tests__/ClientList.test.tsx +++ b/src/clients/__tests__/ClientList.test.tsx @@ -1,18 +1,28 @@ import React from "react"; import { MemoryRouter } from "react-router-dom"; import { render } from "@testing-library/react"; +import KeycloakAdminClient from "keycloak-admin"; import clientMock from "./mock-clients.json"; import { ClientList } from "../ClientList"; +import { AdminClient } from "../../context/auth/AdminClient"; test("renders ClientList", () => { const container = render( - {}} - /> + {}, + } as unknown) as KeycloakAdminClient + } + > + {}} + /> + ); expect(container).toMatchSnapshot(); diff --git a/src/clients/add/NewClientForm.tsx b/src/clients/add/NewClientForm.tsx index ac3c46f8d8..3f5a8a3abe 100644 --- a/src/clients/add/NewClientForm.tsx +++ b/src/clients/add/NewClientForm.tsx @@ -1,4 +1,4 @@ -import React, { useState, useContext } from "react"; +import React, { useState } from "react"; import { useHistory } from "react-router-dom"; import { PageSection, @@ -11,18 +11,16 @@ import { import { useTranslation } from "react-i18next"; import { useForm } from "react-hook-form"; -import { HttpClientContext } from "../../context/http-service/HttpClientContext"; import { GeneralSettings } from "./GeneralSettings"; import { CapabilityConfig } from "./CapabilityConfig"; -import { ClientRepresentation } from "../models/client-model"; import { useAlerts } from "../../components/alert/Alerts"; -import { RealmContext } from "../../context/realm-context/RealmContext"; import { ViewHeader } from "../../components/view-header/ViewHeader"; +import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; +import { useAdminClient } from "../../context/auth/AdminClient"; export const NewClientForm = () => { const { t } = useTranslation("clients"); - const httpClient = useContext(HttpClientContext)!; - const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const history = useHistory(); const [client, setClient] = useState({ @@ -42,7 +40,7 @@ export const NewClientForm = () => { const save = async () => { try { - await httpClient.doPost(`/admin/realms/${realm}/clients`, client); + await adminClient.clients.create({ ...client }); addAlert(t("createSuccess"), AlertVariant.success); } catch (error) { addAlert(t("createError", { error }), AlertVariant.danger); diff --git a/src/clients/credentials/Credentials.tsx b/src/clients/credentials/Credentials.tsx index 5f2aeab352..ca9f8e6640 100644 --- a/src/clients/credentials/Credentials.tsx +++ b/src/clients/credentials/Credentials.tsx @@ -14,15 +14,15 @@ import { Split, SplitItem, } from "@patternfly/react-core"; -import React, { useContext, useEffect, useState } from "react"; +import CredentialRepresentation from "keycloak-admin/lib/defs/credentialRepresentation"; +import React, { useEffect, useState } from "react"; import { Controller, UseFormMethods, useWatch } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { useAlerts } from "../../components/alert/Alerts"; import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; import { HelpItem } from "../../components/help-enabler/HelpItem"; -import { HttpClientContext } from "../../context/http-service/HttpClientContext"; -import { RealmContext } from "../../context/realm-context/RealmContext"; +import { useAdminClient } from "../../context/auth/AdminClient"; import { ClientSecret } from "./ClientSecret"; import { SignedJWT } from "./SignedJWT"; import { X509 } from "./X509"; @@ -49,8 +49,7 @@ export type CredentialsProps = { export const Credentials = ({ clientId, form, save }: CredentialsProps) => { const { t } = useTranslation("clients"); - const httpClient = useContext(HttpClientContext)!; - const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const { addAlert } = useAlerts(); const clientAuthenticatorType = useWatch({ control: form.control, @@ -66,36 +65,37 @@ export const Credentials = ({ clientId, form, save }: CredentialsProps) => { useEffect(() => { (async () => { - const response = await httpClient.doGet( - `/admin/realms/${realm}/authentication/client-authenticator-providers` + const providers = await adminClient.authenticationManagement.getClientAuthenticatorProviders( + { id: clientId } ); - setProviders(response.data!); + setProviders(providers); - const secretResponse = await httpClient.doGet( - `/admin/realms/${realm}/clients/${clientId}/client-secret` - ); - setSecret(secretResponse.data!.value); + const secret = await adminClient.clients.getClientSecret({ + id: clientId, + }); + setSecret(secret.value!); })(); }, []); async function regenerate( - endpoint: string, + call: (clientId: string) => Promise, message: string ): Promise { try { - const response = await httpClient.doPost( - `/admin/realms/${realm}/clients/${clientId}/${endpoint}`, - { client: clientId, realm } - ); + const data = await call(clientId); addAlert(t(`${message}Success`), AlertVariant.success); - return response.data!; + return data; } catch (error) { addAlert(t(`${message}Error`, { error }), AlertVariant.danger); } } const regenerateClientSecret = async () => { - const secret = await regenerate("client-secret", "clientSecret"); + const secret = await regenerate( + (clientId) => + adminClient.clients.generateNewClientSecret({ id: clientId }), + "clientSecret" + ); setSecret(secret?.value || ""); }; @@ -109,7 +109,8 @@ export const Credentials = ({ clientId, form, save }: CredentialsProps) => { const regenerateAccessToken = async () => { const accessToken = await regenerate( - "registration-access-token", + (clientId) => + adminClient.clients.generateRegistrationAccessToken({ id: clientId }), "accessToken" ); setAccessToken(accessToken?.registrationAccessToken || ""); diff --git a/src/clients/credentials/SignedJWT.tsx b/src/clients/credentials/SignedJWT.tsx index 4b766ba640..00942d64ad 100644 --- a/src/clients/credentials/SignedJWT.tsx +++ b/src/clients/credentials/SignedJWT.tsx @@ -18,7 +18,7 @@ export type SignedJWTProps = { export const SignedJWT = ({ form }: SignedJWTProps) => { const providers = sortProviders( - useServerInfo().providers.clientSignature.providers + useServerInfo().providers!.clientSignature.providers ); const { t } = useTranslation("clients"); diff --git a/src/clients/import/ImportForm.tsx b/src/clients/import/ImportForm.tsx index 85a1d78a0f..7c262e0460 100644 --- a/src/clients/import/ImportForm.tsx +++ b/src/clients/import/ImportForm.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React from "react"; import { PageSection, Form, @@ -11,18 +11,16 @@ import { import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; -import { ClientRepresentation } from "../models/client-model"; import { ClientDescription } from "../ClientDescription"; -import { HttpClientContext } from "../../context/http-service/HttpClientContext"; import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload"; import { useAlerts } from "../../components/alert/Alerts"; -import { RealmContext } from "../../context/realm-context/RealmContext"; import { ViewHeader } from "../../components/view-header/ViewHeader"; +import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation"; +import { useAdminClient } from "../../context/auth/AdminClient"; export const ImportForm = () => { const { t } = useTranslation("clients"); - const httpClient = useContext(HttpClientContext)!; - const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const form = useForm(); const { register, handleSubmit, setValue } = form; @@ -44,7 +42,7 @@ export const ImportForm = () => { const save = async (client: ClientRepresentation) => { try { - await httpClient.doPost(`/admin/realms/${realm}/clients`, client); + await adminClient.clients.create({ ...client }); addAlert(t("clientImportSuccess"), AlertVariant.success); } catch (error) { addAlert(`${t("clientImportError")} '${error}'`, AlertVariant.danger); diff --git a/src/components/download-dialog/DownloadDialog.tsx b/src/components/download-dialog/DownloadDialog.tsx index 0e22a9e49c..e4ee750ff2 100644 --- a/src/components/download-dialog/DownloadDialog.tsx +++ b/src/components/download-dialog/DownloadDialog.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState, useEffect, ReactElement } from "react"; +import React, { useState, useEffect, ReactElement, useContext } from "react"; import { Alert, AlertVariant, @@ -15,11 +15,10 @@ import { import FileSaver from "file-saver"; import { ConfirmDialogModal } from "../confirm-dialog/ConfirmDialog"; -import { HttpClientContext } from "../../context/http-service/HttpClientContext"; -import { RealmContext } from "../../context/realm-context/RealmContext"; import { HelpItem } from "../help-enabler/HelpItem"; import { useTranslation } from "react-i18next"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; +import { useAdminClient } from "../../context/auth/AdminClient"; import { HelpContext } from "../help-enabler/HelpHeader"; export type DownloadDialogProps = { @@ -53,13 +52,12 @@ export const DownloadDialog = ({ toggleDialog, protocol = "openid-connect", }: DownloadDialogModalProps) => { - const httpClient = useContext(HttpClientContext)!; - const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const { t } = useTranslation("common"); const { enabled } = useContext(HelpContext); const serverInfo = useServerInfo(); - const configFormats = serverInfo.clientInstallations[protocol]; + const configFormats = serverInfo.clientInstallations![protocol]; const [selected, setSelected] = useState( configFormats[configFormats.length - 1].id ); @@ -69,11 +67,16 @@ export const DownloadDialog = ({ useEffect(() => { let isMounted = true; (async () => { - const response = await httpClient.doGet( - `/admin/realms/${realm}/clients/${id}/installation/providers/${selected}` - ); + const snippet = await adminClient.clients.getInstallationProviders({ + id, + providerId: selected, + }); if (isMounted) { - setSnippet(await response.text()); + if (typeof snippet === "string") { + setSnippet(snippet); + } else { + setSnippet(JSON.stringify(snippet, undefined, 3)); + } } })(); return () => { diff --git a/src/components/form-access/FormAccess.tsx b/src/components/form-access/FormAccess.tsx index 3f143df292..5b1b000cab 100644 --- a/src/components/form-access/FormAccess.tsx +++ b/src/components/form-access/FormAccess.tsx @@ -14,9 +14,9 @@ import { GridItem, TextArea, } from "@patternfly/react-core"; +import { AccessType } from "keycloak-admin/lib/defs/whoAmIRepresentation"; import { useAccess } from "../../context/access/Access"; -import { AccessType } from "../../context/whoami/who-am-i-model"; export type FormAccessProps = FormProps & { /** diff --git a/src/components/realm-selector/RealmSelector.tsx b/src/components/realm-selector/RealmSelector.tsx index 644e65969e..f0ca95a988 100644 --- a/src/components/realm-selector/RealmSelector.tsx +++ b/src/components/realm-selector/RealmSelector.tsx @@ -15,7 +15,7 @@ import { } from "@patternfly/react-core"; import { CheckIcon } from "@patternfly/react-icons"; -import { RealmRepresentation } from "../../realm/models/Realm"; +import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation"; import { RealmContext } from "../../context/realm-context/RealmContext"; import { WhoAmIContext } from "../../context/whoami/WhoAmI"; @@ -60,7 +60,7 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => { search === "" ? realmList : realmList.filter( - (r) => r.realm.toLowerCase().indexOf(search.toLowerCase()) !== -1 + (r) => r.realm!.toLowerCase().indexOf(search.toLowerCase()) !== -1 ); setFilteredItems(filtered || []); }; @@ -73,11 +73,11 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => { { - setRealm(r.realm); + setRealm(r.realm!); setOpen(!open); }} > - + )); @@ -114,7 +114,7 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => { > {filteredItems.map((item) => ( - + ))} diff --git a/src/context/access/Access.tsx b/src/context/access/Access.tsx index 0688b2cbd5..62df305bff 100644 --- a/src/context/access/Access.tsx +++ b/src/context/access/Access.tsx @@ -1,8 +1,8 @@ import React, { createContext, useContext } from "react"; +import { AccessType } from "keycloak-admin/lib/defs/whoAmIRepresentation"; import { RealmContext } from "../../context/realm-context/RealmContext"; import { WhoAmIContext } from "../../context/whoami/WhoAmI"; -import { AccessType } from "../../context/whoami/who-am-i-model"; type AccessContextProps = { hasAccess: (...types: AccessType[]) => boolean; diff --git a/src/context/auth/AdminClient.tsx b/src/context/auth/AdminClient.tsx new file mode 100644 index 0000000000..839cfe05fb --- /dev/null +++ b/src/context/auth/AdminClient.tsx @@ -0,0 +1,18 @@ +import { createContext, useContext } from "react"; +import KeycloakAdminClient from "keycloak-admin"; +import { RealmContext } from "../realm-context/RealmContext"; + +export const AdminClient = createContext( + undefined +); + +export const useAdminClient = () => { + const adminClient = useContext(AdminClient)!; + const { realm } = useContext(RealmContext); + + adminClient.setConfig({ + realmName: realm, + }); + + return adminClient; +}; diff --git a/src/context/auth/KeycloakContext.tsx b/src/context/auth/KeycloakContext.tsx deleted file mode 100644 index 8a426ca73f..0000000000 --- a/src/context/auth/KeycloakContext.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import * as React from "react"; -import { KeycloakService } from "./keycloak.service"; - -export const KeycloakContext = React.createContext( - undefined -); diff --git a/src/context/auth/keycloak.service.ts b/src/context/auth/keycloak.service.ts deleted file mode 100644 index 7bcf8fd5b6..0000000000 --- a/src/context/auth/keycloak.service.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { KeycloakLoginOptions } from "keycloak-js"; -import { useTranslation } from "react-i18next"; - -export type KeycloakClient = Keycloak.KeycloakInstance; - -type Token = { - given_name: string; - family_name: string; - preferred_username: string; -}; - -export class KeycloakService { - private keycloakAuth: KeycloakClient; - - public constructor(keycloak: KeycloakClient) { - this.keycloakAuth = keycloak; - } - - public authenticated(): boolean { - return this.keycloakAuth.authenticated - ? this.keycloakAuth.authenticated - : false; - } - - public login(options?: KeycloakLoginOptions): void { - this.keycloakAuth.login(options); - } - - public logout(redirectUri: string = ""): void { - this.keycloakAuth.logout({ redirectUri: redirectUri }); - } - - public account(): void { - this.keycloakAuth.accountManagement(); - } - - public authServerUrl(): string | undefined { - const authServerUrl = this.keycloakAuth.authServerUrl; - return authServerUrl!.charAt(authServerUrl!.length - 1) === "/" - ? authServerUrl - : authServerUrl + "/"; - } - - public realm(): string | undefined { - return this.keycloakAuth.realm; - } - - public get loggedInUser(): string { - const { t } = useTranslation(); - return this.loggedInUserName(t, this.keycloakAuth.tokenParsed as Token); - } - - private loggedInUserName = (t: Function, tokenParsed: Token) => { - let userName = t("unknownUser"); - if (tokenParsed) { - const givenName = tokenParsed.given_name; - const familyName = tokenParsed.family_name; - const preferredUsername = tokenParsed.preferred_username; - if (givenName && familyName) { - userName = t("fullName", { givenName, familyName }); - } else { - userName = givenName || familyName || preferredUsername || userName; - } - } - return userName; - }; - - public getToken(): Promise { - return new Promise((resolve, reject) => { - if (this.keycloakAuth.token) { - this.keycloakAuth - .updateToken(5) - .then(() => { - resolve(this.keycloakAuth.token as string); - }) - .catch(() => { - reject("Failed to refresh token"); - }); - } else { - reject("Not logged in"); - } - }); - } -} diff --git a/src/context/auth/keycloak.ts b/src/context/auth/keycloak.ts index 59d5785927..b8d0896959 100644 --- a/src/context/auth/keycloak.ts +++ b/src/context/auth/keycloak.ts @@ -1,17 +1,24 @@ -import Keycloak, { KeycloakInstance } from "keycloak-js"; +import KcAdminClient from "keycloak-admin"; -const realm = - new URLSearchParams(window.location.search).get("realm") || "master"; +export default async function (): Promise { + const realm = + new URLSearchParams(window.location.search).get("realm") || "master"; -const keycloak: KeycloakInstance = Keycloak({ - url: "http://localhost:8180/auth/", - realm: realm, - clientId: "security-admin-console-v2", -}); + const kcAdminClient = new KcAdminClient(); -export default async function (): Promise { - await keycloak.init({ onLoad: "check-sso", pkceMethod: "S256" }).catch(() => { + try { + await kcAdminClient.init( + { onLoad: "check-sso", pkceMethod: "S256" }, + { + url: "http://localhost:8180/auth/", + realm: realm, + clientId: "security-admin-console-v2", + } + ); + kcAdminClient.baseUrl = ""; + } catch (error) { alert("failed to initialize keycloak"); - }); - return keycloak; + } + + return kcAdminClient; } diff --git a/src/context/http-service/HttpClientContext.tsx b/src/context/http-service/HttpClientContext.tsx deleted file mode 100644 index 79389e6fc8..0000000000 --- a/src/context/http-service/HttpClientContext.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { createContext } from "react"; -import { HttpClient } from "./http-client"; - -export const HttpClientContext = createContext( - undefined -); diff --git a/src/context/http-service/http-client.ts b/src/context/http-service/http-client.ts deleted file mode 100644 index fe21d628a1..0000000000 --- a/src/context/http-service/http-client.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { KeycloakService } from "../auth/keycloak.service"; - -type ConfigResolve = (config: RequestInit) => void; - -export interface HttpResponse extends Response { - data?: T; -} - -export interface RequestInitWithParams extends RequestInit { - params?: { [name: string]: string | number }; -} - -export class AccountServiceError extends Error { - constructor(message: string) { - super(message); - } -} - -/** - * - * @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc. - */ -export class HttpClient { - private kcSvc: KeycloakService; - - public constructor(keycloakService: KeycloakService) { - this.kcSvc = keycloakService; - } - - public async doGet( - endpoint: string, - config?: RequestInitWithParams - ): Promise> { - return this.doRequest(endpoint, { ...config, method: "get" }); - } - - public async doDelete( - endpoint: string, - config?: RequestInitWithParams - ): Promise> { - return this.doRequest(endpoint, { ...config, method: "delete" }); - } - - public async doPost( - endpoint: string, - body: string | {}, - config?: RequestInitWithParams - ): Promise> { - return this.doRequest(endpoint, { - ...config, - body: JSON.stringify(body), - method: "post", - }); - } - - public async doPut( - endpoint: string, - body: string | {}, - config?: RequestInitWithParams - ): Promise> { - return this.doRequest(endpoint, { - ...config, - body: JSON.stringify(body), - method: "put", - }); - } - - public async doRequest( - endpoint: string, - config?: RequestInitWithParams - ): Promise> { - const response: HttpResponse = await fetch( - this.makeUrl(endpoint, config).toString(), - await this.makeConfig(config) - ); - - const contentType = response.headers.get("content-type"); - if (contentType && contentType.indexOf("application/json") !== -1) { - try { - response.data = await response.json(); - } catch (e) { - console.warn(e); - } - } - - if (!response.ok) { - this.handleError(response); - } - - return response; - } - - private handleError(response: HttpResponse): void { - if (response != null && response.status === 401) { - // session timed out? - this.kcSvc.login(); - } - - if (response != null && response.data != null) { - throw new AccountServiceError((response.data as any).errorMessage); - } else { - throw new AccountServiceError(response.statusText); - } - } - - private makeUrl(url: string, config?: RequestInitWithParams): string { - const searchParams = new URLSearchParams(); - // add request params - if (config && {}.hasOwnProperty.call(config, "params")) { - const params: { [name: string]: string } = (config.params as {}) || {}; - Object.keys(params).forEach((key) => - searchParams.append(key, params[key]) - ); - } - - return url + "?" + searchParams.toString(); - } - - private makeConfig(config: RequestInit = {}): Promise { - return new Promise((resolve: ConfigResolve) => { - this.kcSvc - .getToken() - .then((token: string) => { - resolve({ - ...config, - headers: { - "Content-Type": "application/json", - ...config.headers, - Authorization: "Bearer " + token, - }, - }); - }) - .catch(() => { - this.kcSvc.login(); - }); - }); - } -} - -window.addEventListener( - "unhandledrejection", - (event: PromiseRejectionEvent) => { - event.promise.catch((error) => { - if (error instanceof AccountServiceError) { - // We already handled the error. Ignore unhandled rejection. - event.preventDefault(); - } - }); - } -); diff --git a/src/context/server-info/ServerInfoProvider.tsx b/src/context/server-info/ServerInfoProvider.tsx index a20e5fa6ba..35c9810950 100644 --- a/src/context/server-info/ServerInfoProvider.tsx +++ b/src/context/server-info/ServerInfoProvider.tsx @@ -1,8 +1,9 @@ import React, { createContext, ReactNode, useContext } from "react"; -import { ServerInfoRepresentation } from "./server-info"; -import { HttpClientContext } from "../http-service/HttpClientContext"; +import { ServerInfoRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation"; + import { sortProviders } from "../../util"; import { DataLoader } from "../../components/data-loader/DataLoader"; +import { useAdminClient } from "../auth/AdminClient"; export const ServerInfoContext = createContext( {} as ServerInfoRepresentation @@ -11,16 +12,13 @@ export const ServerInfoContext = createContext( export const useServerInfo = () => useContext(ServerInfoContext); export const useLoginProviders = () => { - return sortProviders(useServerInfo().providers["login-protocol"].providers); + return sortProviders(useServerInfo().providers!["login-protocol"].providers); }; export const ServerInfoProvider = ({ children }: { children: ReactNode }) => { - const httpClient = useContext(HttpClientContext)!; + const adminClient = useAdminClient(); const loader = async () => { - const response = await httpClient.doGet( - "/admin/serverinfo" - ); - return response.data!; + return await adminClient.serverInfo.find(); }; return ( diff --git a/src/context/server-info/server-info.ts b/src/context/server-info/server-info.ts deleted file mode 100644 index 4c88d54031..0000000000 --- a/src/context/server-info/server-info.ts +++ /dev/null @@ -1,123 +0,0 @@ -export interface ServerInfoRepresentation { - systemInfo: SystemInfoRepresentation; - memoryInfo: MemoryInfoRepresentation; - profileInfo: ProfileInfoRepresentation; - themes: { [index: string]: ThemeInfoRepresentation[] }; - socialProviders: { [index: string]: string }[]; - identityProviders: { [index: string]: string }[]; - clientImporters: { [index: string]: string }[]; - providers: { [index: string]: SpiInfoRepresentation }; - protocolMapperTypes: { [index: string]: ProtocolMapperTypeRepresentation[] }; - builtinProtocolMappers: { [index: string]: ProtocolMapperRepresentation[] }; - clientInstallations: { [index: string]: ClientInstallationRepresentation[] }; - componentTypes: { [index: string]: ComponentTypeRepresentation[] }; - passwordPolicies: PasswordPolicyTypeRepresentation[]; - enums: { [index: string]: string[] }; -} - -export interface SystemInfoRepresentation { - version: string; - serverTime: string; - uptime: string; - uptimeMillis: number; - javaVersion: string; - javaVendor: string; - javaVm: string; - javaVmVersion: string; - javaRuntime: string; - javaHome: string; - osName: string; - osArchitecture: string; - osVersion: string; - fileEncoding: string; - userName: string; - userDir: string; - userTimezone: string; - userLocale: string; -} - -export interface MemoryInfoRepresentation { - total: number; - totalFormated: string; - used: number; - usedFormated: string; - free: number; - freePercentage: number; - freeFormated: string; -} - -export interface ProfileInfoRepresentation { - name: string; - disabledFeatures: string[]; - previewFeatures: string[]; - experimentalFeatures: string[]; -} - -export interface ThemeInfoRepresentation { - name: string; - locales: string[]; -} - -export interface SpiInfoRepresentation { - internal: boolean; - providers: { [index: string]: ProviderRepresentation }; -} - -export interface ProtocolMapperTypeRepresentation { - id: string; - name: string; - category: string; - helpText: string; - priority: number; - properties: ConfigPropertyRepresentation[]; -} - -export interface ProtocolMapperRepresentation { - id: string; - name: string; - protocol: string; - protocolMapper: string; - consentRequired: boolean; - consentText: string; - config: { [index: string]: string }; -} - -export interface ClientInstallationRepresentation { - id: string; - protocol: string; - downloadOnly: boolean; - displayType: string; - helpText: string; - filename: string; - mediaType: string; -} - -export interface ComponentTypeRepresentation { - id: string; - helpText: string; - properties: ConfigPropertyRepresentation[]; - metadata: { [index: string]: any }; -} - -export interface PasswordPolicyTypeRepresentation { - id: string; - displayName: string; - configType: string; - defaultValue: string; - multipleSupported: boolean; -} - -export interface ProviderRepresentation { - order: number; - operationalInfo: { [index: string]: string }; -} - -export interface ConfigPropertyRepresentation { - name: string; - label: string; - helpText: string; - type: string; - defaultValue: any; - options: string[]; - secret: boolean; -} diff --git a/src/context/whoami/WhoAmI.tsx b/src/context/whoami/WhoAmI.tsx index b742cb06e6..74b4ae6245 100644 --- a/src/context/whoami/WhoAmI.tsx +++ b/src/context/whoami/WhoAmI.tsx @@ -1,11 +1,11 @@ -import React, { useContext } from "react"; +import React from "react"; import i18n from "../../i18n"; -import WhoAmIRepresentation, { AccessType } from "./who-am-i-model"; - -import { HttpClientContext } from "../http-service/HttpClientContext"; -import { KeycloakContext } from "../auth/KeycloakContext"; import { DataLoader } from "../../components/data-loader/DataLoader"; +import { useAdminClient } from "../auth/AdminClient"; +import WhoAmIRepresentation, { + AccessType, +} from "keycloak-admin/lib/defs/whoAmIRepresentation"; export class WhoAmI { constructor( @@ -53,24 +53,19 @@ export const WhoAmIContext = React.createContext(new WhoAmI()); type WhoAmIProviderProps = { children: React.ReactNode }; export const WhoAmIContextProvider = ({ children }: WhoAmIProviderProps) => { - const httpClient = useContext(HttpClientContext)!; - const keycloak = useContext(KeycloakContext); + const adminClient = useAdminClient(); const whoAmILoader = async () => { - if (keycloak === undefined) return undefined; + if (adminClient.keycloak === undefined) return undefined; - const realm = keycloak.realm(); - - return await httpClient - .doGet(`/admin/${realm}/console/whoami/`) - .then((r) => r.data as WhoAmIRepresentation); + return await adminClient.whoAmI.find(); }; return ( {(whoamirep) => ( {children} diff --git a/src/context/whoami/who-am-i-model.ts b/src/context/whoami/who-am-i-model.ts deleted file mode 100644 index 1086f93a82..0000000000 --- a/src/context/whoami/who-am-i-model.ts +++ /dev/null @@ -1,29 +0,0 @@ -export type AccessType = - | "view-realm" - | "view-identity-providers" - | "manage-identity-providers" - | "impersonation" - | "create-client" - | "manage-users" - | "query-realms" - | "view-authorization" - | "query-clients" - | "query-users" - | "manage-events" - | "manage-realm" - | "view-events" - | "view-users" - | "view-clients" - | "manage-authorization" - | "manage-clients" - | "query-groups" - | "anyone"; - -export default interface WhoAmIRepresentation { - userId: string; - realm: string; - displayName: string; - locale: string; - createRealm: boolean; - realm_access: { [key: string]: AccessType[] }; -} diff --git a/src/groups/GroupsCreateModal.tsx b/src/groups/GroupsCreateModal.tsx index b0e27c12df..e6c9ffdd64 100644 --- a/src/groups/GroupsCreateModal.tsx +++ b/src/groups/GroupsCreateModal.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React from "react"; import { AlertVariant, Button, @@ -10,8 +10,7 @@ import { ValidatedOptions, } from "@patternfly/react-core"; import { useTranslation } from "react-i18next"; -import { HttpClientContext } from "../context/http-service/HttpClientContext"; -import { RealmContext } from "../context/realm-context/RealmContext"; +import { useAdminClient } from "../context/auth/AdminClient"; import { useAlerts } from "../components/alert/Alerts"; import { useForm } from "react-hook-form"; @@ -33,8 +32,7 @@ export const GroupsCreateModal = ({ refresh, }: GroupsCreateModalProps) => { const { t } = useTranslation("groups"); - const httpClient = useContext(HttpClientContext)!; - const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const { addAlert } = useAlerts(); const form = useForm(); const { register, errors } = form; @@ -46,9 +44,7 @@ export const GroupsCreateModal = ({ const submitForm = async () => { if (await form.trigger()) { try { - await httpClient.doPost(`/admin/realms/${realm}/groups`, { - name: createGroupName, - }); + await adminClient.groups.create({ name: createGroupName }); refresh(); setIsCreateModalOpen(false); setCreateGroupName(""); diff --git a/src/groups/GroupsList.tsx b/src/groups/GroupsList.tsx index 9194d8b4f8..a2514c3379 100644 --- a/src/groups/GroupsList.tsx +++ b/src/groups/GroupsList.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useContext } from "react"; +import React, { useState, useEffect } from "react"; import { Table, TableHeader, @@ -9,8 +9,7 @@ import { Button, AlertVariant } from "@patternfly/react-core"; import { useTranslation } from "react-i18next"; import { GroupRepresentation } from "./models/groups"; import { UsersIcon } from "@patternfly/react-icons"; -import { HttpClientContext } from "../context/http-service/HttpClientContext"; -import { RealmContext } from "../context/realm-context/RealmContext"; +import { useAdminClient } from "../context/auth/AdminClient"; import { useAlerts } from "../components/alert/Alerts"; export type GroupsListProps = { @@ -32,10 +31,9 @@ export const GroupsList = ({ setTableRowSelectedArray, }: GroupsListProps) => { const { t } = useTranslation("groups"); - const httpClient = useContext(HttpClientContext)!; + const adminClient = useAdminClient(); const columnGroupName: keyof GroupRepresentation = "name"; const columnGroupNumber: keyof GroupRepresentation = "membersLength"; - const { realm } = useContext(RealmContext); const { addAlert } = useAlerts(); const [formattedData, setFormattedData] = useState([]); @@ -107,9 +105,7 @@ export const GroupsList = ({ rowId: number ) => { try { - await httpClient.doDelete( - `/admin/realms/${realm}/groups/${list![rowId].id}` - ); + await adminClient.groups.del({ id: list![rowId].id! }); refresh(); setTableRowSelectedArray([]); addAlert(t("Group deleted"), AlertVariant.success); diff --git a/src/groups/GroupsSection.tsx b/src/groups/GroupsSection.tsx index e78153cbfa..d28673087c 100644 --- a/src/groups/GroupsSection.tsx +++ b/src/groups/GroupsSection.tsx @@ -1,17 +1,11 @@ -import React, { useContext, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { HttpClientContext } from "../context/http-service/HttpClientContext"; import { GroupsList } from "./GroupsList"; import { GroupsCreateModal } from "./GroupsCreateModal"; -import { GroupRepresentation } from "./models/groups"; -import { - ServerGroupsArrayRepresentation, - ServerGroupMembersRepresentation, -} from "./models/server-info"; import { TableToolbar } from "../components/table-toolbar/TableToolbar"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; -import { RealmContext } from "../context/realm-context/RealmContext"; +import { useAdminClient } from "../context/auth/AdminClient"; import { useAlerts } from "../components/alert/Alerts"; import { Button, @@ -25,10 +19,11 @@ import { AlertVariant, } from "@patternfly/react-core"; import "./GroupsSection.css"; +import GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation"; export const GroupsSection = () => { const { t } = useTranslation("groups"); - const httpClient = useContext(HttpClientContext)!; + const adminClient = useAdminClient(); const [rawData, setRawData] = useState<{ [key: string]: any }[]>(); const [filteredData, setFilteredData] = useState<{ [key: string]: any }[]>(); const [isKebabOpen, setIsKebabOpen] = useState(false); @@ -38,22 +33,16 @@ export const GroupsSection = () => { Array >([]); const columnID: keyof GroupRepresentation = "id"; - const membersLength: keyof GroupRepresentation = "membersLength"; const columnGroupName: keyof GroupRepresentation = "name"; const { addAlert } = useAlerts(); - const { realm } = useContext(RealmContext); + const membersLength = "membersLength"; const loader = async () => { - const groups = await httpClient.doGet( - `/admin/realms/${realm}/groups` - ); - const groupsData = groups.data!; - const getMembers = async (id: number) => { - const response = await httpClient.doGet< - ServerGroupMembersRepresentation[] - >(`/admin/realms/${realm}/groups/${id}/members`); - const responseData = response.data!; - return responseData.length; + const groupsData = await adminClient.groups.find(); + + const getMembers = async (id: string) => { + const response = await adminClient.groups.listMembers({ id }); + return response.length; }; const memberPromises = groupsData.map((group: { [key: string]: any }) => @@ -101,11 +90,9 @@ export const GroupsSection = () => { if (tableRowSelectedArray.length !== 0) { const deleteGroup = async (rowId: number) => { try { - await httpClient.doDelete( - `/admin/realms/${realm}/groups/${ - filteredData ? filteredData![rowId].id : rawData![rowId].id - }` - ); + await adminClient.groups.del({ + id: filteredData ? filteredData![rowId].id : rawData![rowId].id, + }); loader(); } catch (error) { addAlert(`${t("groupDeleteError")} ${error}`, AlertVariant.danger); diff --git a/src/index.tsx b/src/index.tsx index 7c82d9db61..3db73c3f4f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,22 +2,20 @@ import React from "react"; import ReactDom from "react-dom"; import i18n from "./i18n"; -import { App } from "./App"; +import { AdminClient } from "./context/auth/AdminClient"; import init from "./context/auth/keycloak"; -import { KeycloakContext } from "./context/auth/KeycloakContext"; -import { KeycloakService } from "./context/auth/keycloak.service"; -import { HttpClientContext } from "./context/http-service/HttpClientContext"; -import { HttpClient } from "./context/http-service/http-client"; +import { App } from "./App"; +import { RealmContextProvider } from "./context/realm-context/RealmContext"; console.info("supported languages", ...i18n.languages); -init().then((keycloak) => { - const keycloakService = new KeycloakService(keycloak); + +init().then((adminClient) => { ReactDom.render( - - + + - - , + + , document.getElementById("app") ); }); diff --git a/src/realm-roles/RealmRolesSection.tsx b/src/realm-roles/RealmRolesSection.tsx index e6511ff236..c71e7e84db 100644 --- a/src/realm-roles/RealmRolesSection.tsx +++ b/src/realm-roles/RealmRolesSection.tsx @@ -1,14 +1,13 @@ -import React, { useContext, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { useHistory } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { Button, PageSection } from "@patternfly/react-core"; -import { HttpClientContext } from "../context/http-service/HttpClientContext"; -import { RoleRepresentation } from "../model/role-model"; import { RolesList } from "./RoleList"; -import { RealmContext } from "../context/realm-context/RealmContext"; -import { ViewHeader } from "../components/view-header/ViewHeader"; +import { useAdminClient } from "../context/auth/AdminClient"; import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTableToolbar"; +import { ViewHeader } from "../components/view-header/ViewHeader"; +import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; export const RealmRolesSection = () => { @@ -16,19 +15,11 @@ export const RealmRolesSection = () => { const [first, setFirst] = useState(0); const { t } = useTranslation("roles"); const history = useHistory(); - const httpClient = useContext(HttpClientContext)!; + const adminClient = useAdminClient(); const [roles, setRoles] = useState(); - const { realm } = useContext(RealmContext); - const loader = async () => { - const params: { [name: string]: string | number } = { first, max }; - - const result = await httpClient.doGet( - `/admin/realms/${realm}/roles`, - { params: params } - ); - setRoles(result.data); - }; + const params: { [name: string]: string | number } = { first, max }; + const loader = async () => setRoles(await adminClient.roles.find(params)); useEffect(() => { loader(); diff --git a/src/realm-roles/RoleList.tsx b/src/realm-roles/RoleList.tsx index 2fc4e2b1a7..47ed4e3f17 100644 --- a/src/realm-roles/RoleList.tsx +++ b/src/realm-roles/RoleList.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from "react"; +import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { @@ -11,11 +11,10 @@ import { } from "@patternfly/react-table"; import { ExternalLink } from "../components/external-link/ExternalLink"; -import { RoleRepresentation } from "../model/role-model"; +import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation"; import { AlertVariant, ButtonVariant } from "@patternfly/react-core"; -import { HttpClientContext } from "../context/http-service/HttpClientContext"; +import { useAdminClient } from "../context/auth/AdminClient"; import { useAlerts } from "../components/alert/Alerts"; -import { RealmContext } from "../context/realm-context/RealmContext"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; type RolesListProps = { @@ -31,8 +30,7 @@ const columns: (keyof RoleRepresentation)[] = [ export const RolesList = ({ roles, refresh }: RolesListProps) => { const { t } = useTranslation("roles"); - const httpClient = useContext(HttpClientContext)!; - const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const { addAlert } = useAlerts(); const [selectedRowId, setSelectedRowId] = useState(-1); @@ -71,9 +69,9 @@ export const RolesList = ({ roles, refresh }: RolesListProps) => { continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { try { - await httpClient.doDelete( - `/admin/realms/${realm}/roles/${data[selectedRowId].role.name}` - ); + await adminClient.roles.delByName({ + name: data[selectedRowId].role.name!, + }); refresh(); addAlert(t("roleDeletedSuccess"), AlertVariant.success); } catch (error) { diff --git a/src/realm-roles/add/NewRoleForm.tsx b/src/realm-roles/add/NewRoleForm.tsx index 8cc567da44..83a3e142a4 100644 --- a/src/realm-roles/add/NewRoleForm.tsx +++ b/src/realm-roles/add/NewRoleForm.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React from "react"; import { useTranslation } from "react-i18next"; import { Text, @@ -15,17 +15,15 @@ import { ValidatedOptions, } from "@patternfly/react-core"; -import { RoleRepresentation } from "../../model/role-model"; -import { HttpClientContext } from "../../context/http-service/HttpClientContext"; import { useAlerts } from "../../components/alert/Alerts"; import { Controller, useForm } from "react-hook-form"; -import { RealmContext } from "../../context/realm-context/RealmContext"; +import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation"; +import { useAdminClient } from "../../context/auth/AdminClient"; export const NewRoleForm = () => { const { t } = useTranslation("roles"); - const httpClient = useContext(HttpClientContext)!; const { addAlert } = useAlerts(); - const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const { register, control, errors, handleSubmit } = useForm< RoleRepresentation @@ -33,7 +31,7 @@ export const NewRoleForm = () => { const save = async (role: RoleRepresentation) => { try { - await httpClient.doPost(`admin/realms/${realm}/roles`, role); + await adminClient.roles.create(role); addAlert(t("roleCreated"), AlertVariant.success); } catch (error) { addAlert(`${t("roleCreateError")} '${error}'`, AlertVariant.danger); diff --git a/src/realm/add/NewRealmForm.tsx b/src/realm/add/NewRealmForm.tsx index 2f94645ba9..d44354d687 100644 --- a/src/realm/add/NewRealmForm.tsx +++ b/src/realm/add/NewRealmForm.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React from "react"; import { useTranslation } from "react-i18next"; import { PageSection, @@ -12,15 +12,15 @@ import { } from "@patternfly/react-core"; import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload"; -import { RealmRepresentation } from "../models/Realm"; -import { HttpClientContext } from "../../context/http-service/HttpClientContext"; import { useAlerts } from "../../components/alert/Alerts"; import { useForm, Controller } from "react-hook-form"; import { ViewHeader } from "../../components/view-header/ViewHeader"; +import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation"; +import { useAdminClient } from "../../context/auth/AdminClient"; export const NewRealmForm = () => { const { t } = useTranslation("realm"); - const httpClient = useContext(HttpClientContext)!; + const adminClient = useAdminClient(); const { addAlert } = useAlerts(); const { register, handleSubmit, setValue, control } = useForm< @@ -38,7 +38,7 @@ export const NewRealmForm = () => { const save = async (realm: RealmRepresentation) => { try { - await httpClient.doPost("/admin/realms", realm); + await adminClient.realms.create(realm); addAlert(t("Realm created"), AlertVariant.success); } catch (error) { addAlert( diff --git a/src/route-config.ts b/src/route-config.ts index 7701322d8b..2e8090584d 100644 --- a/src/route-config.ts +++ b/src/route-config.ts @@ -1,4 +1,6 @@ import { TFunction } from "i18next"; +import { AccessType } from "keycloak-admin/lib/defs/whoAmIRepresentation"; + import { AuthenticationSection } from "./authentication/AuthenticationSection"; import { ClientScopeForm } from "./client-scopes/form/ClientScopeForm"; import { ClientScopesSection } from "./client-scopes/ClientScopesSection"; @@ -17,8 +19,6 @@ import { SessionsSection } from "./sessions/SessionsSection"; import { UserFederationSection } from "./user-federation/UserFederationSection"; import { UsersSection } from "./user/UsersSection"; import { MappingDetails } from "./client-scopes/details/MappingDetails"; - -import { AccessType } from "./context/whoami/who-am-i-model"; import { ClientDetails } from "./clients/ClientDetails"; export type RouteDef = { diff --git a/src/stories/RoleMappingForm.stories.tsx b/src/stories/RoleMappingForm.stories.tsx index 248d938ee4..79c1eabc0c 100644 --- a/src/stories/RoleMappingForm.stories.tsx +++ b/src/stories/RoleMappingForm.stories.tsx @@ -7,8 +7,8 @@ import roles from "../realm-roles/__tests__/mock-roles.json"; import { ServerInfoContext } from "../context/server-info/ServerInfoProvider"; import { RoleMappingForm } from "../client-scopes/add/RoleMappingForm"; -import { HttpClientContext } from "../context/http-service/HttpClientContext"; -import { HttpClient } from "../context/http-service/http-client"; +import { AdminClient } from "../context/auth/AdminClient"; +import KeycloakAdminClient from "keycloak-admin"; export default { title: "Role Mapping Form", @@ -17,18 +17,24 @@ export default { export const RoleMappingFormExample = () => ( - { - return { data: roles }; + setConfig: () => {}, + roles: { + find: () => { + return roles; + }, }, - } as unknown) as HttpClient + clients: { + find: () => roles, + }, + } as unknown) as KeycloakAdminClient } > - + ); diff --git a/src/user-federation/UserFederationSection.tsx b/src/user-federation/UserFederationSection.tsx index 4b93257904..934d38cfcc 100644 --- a/src/user-federation/UserFederationSection.tsx +++ b/src/user-federation/UserFederationSection.tsx @@ -15,45 +15,43 @@ import { TextVariants, } from "@patternfly/react-core"; +import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation"; import { KeycloakCard } from "../components/keycloak-card/KeycloakCard"; import { useAlerts } from "../components/alert/Alerts"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { DatabaseIcon } from "@patternfly/react-icons"; import { useTranslation } from "react-i18next"; import { RealmContext } from "../context/realm-context/RealmContext"; -import { HttpClientContext } from "../context/http-service/HttpClientContext"; -import { UserFederationRepresentation } from "./model/userFederation"; +import { useAdminClient } from "../context/auth/AdminClient"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import "./user-federation.css"; +type Config = { + enabled: string[]; +}; + export const UserFederationSection = () => { const [userFederations, setUserFederations] = useState< - UserFederationRepresentation[] + ComponentRepresentation[] >(); const { addAlert } = useAlerts(); + const { t } = useTranslation("user-federation"); + const { realm } = useContext(RealmContext); + const adminClient = useAdminClient(); const loader = async () => { const testParams: { [name: string]: string | number } = { parentId: realm, type: "org.keycloak.storage.UserStorageProvider", // MF note that this is providerType in the output, but API call is still type }; - const result = await httpClient.doGet( - `/admin/realms/${realm}/components`, - { - params: testParams, - } - ); - setUserFederations(result.data); + const userFederations = await adminClient.components.find(testParams); + setUserFederations(userFederations); }; useEffect(() => { loader(); }, []); - const { t } = useTranslation("user-federation"); - const httpClient = useContext(HttpClientContext)!; - const { realm } = useContext(RealmContext); - const ufAddProviderDropdownItems = [ LDAP, Kerberos, @@ -75,9 +73,8 @@ export const UserFederationSection = () => { continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { try { - httpClient - .doDelete(`/admin/realms/${realm}/components/${currentCard}`) - .then(() => loader()); + await adminClient.components.del({ id: currentCard }); + await loader(); addAlert(t("userFedDeletedSuccess"), AlertVariant.success); } catch (error) { addAlert(t("userFedDeleteError", { error }), AlertVariant.danger); @@ -96,7 +93,7 @@ export const UserFederationSection = () => { { - toggleDeleteForCard(userFederation.id); + toggleDeleteForCard(userFederation.id!); }} > {t("common:delete")} @@ -105,14 +102,14 @@ export const UserFederationSection = () => { return ( =3.5 <5", lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.5, lodash@~4.17.10, lodash@~4.17.5: +"lodash@>=3.5 <5", lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@~4.17.10, lodash@~4.17.5: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -15492,6 +15522,15 @@ query-string@^4.1.0: object-assign "^4.1.0" strict-uri-encode "^1.0.0" +query-string@^6.13.7: + version "6.13.7" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.7.tgz#af53802ff6ed56f3345f92d40a056f93681026ee" + integrity sha512-CsGs8ZYb39zu0WLkeOhe0NMePqgYdAuCqxOYKDR5LVCytDZYMGx3Bb+xypvQvPHVPijRXB0HZNFllCzHRe4gEA== + dependencies: + decode-uri-component "^0.2.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -17270,6 +17309,11 @@ spdy@^4.0.1: select-hose "^2.0.0" spdy-transport "^3.0.0" +split-on-first@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" + integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -17399,6 +17443,11 @@ strict-uri-encode@^1.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= +strict-uri-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" + integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= + string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" @@ -18421,6 +18470,11 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +url-join@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" + integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== + url-loader@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.3.0.tgz#e0e2ef658f003efb8ca41b0f3ffbf76bab88658b" @@ -18447,6 +18501,11 @@ url-parse@^1.4.3: querystringify "^2.1.1" requires-port "^1.0.0" +url-template@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" + integrity sha1-/FZaPMy/93MMd19WQflVV5FDnyE= + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"