From f800e000be775bb96620e0c2f8884b815dfe4e5e Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Fri, 2 Oct 2020 02:47:32 -0400 Subject: [PATCH] Create whoami context (#136) * Fix conflicts * Fix formatting * Get tests to pass * Fix conflict again --- src/App.tsx | 35 ++-- src/PageHeader.tsx | 4 +- .../__snapshots__/ClientList.test.tsx.snap | 164 +++++++++--------- .../PageBreadCrumbs.test.tsx.snap | 4 +- src/components/realm-context/RealmContext.tsx | 8 +- .../realm-selector/RealmSelector.tsx | 23 ++- .../__snapshots__/RealmSelector.test.tsx.snap | 56 +----- src/whoami/WhoAmI.tsx | 78 +++++++++ src/whoami/__tests__/WhoAmI.test.tsx | 28 +++ src/whoami/__tests__/mock-whoami.json | 49 ++++++ src/whoami/who-am-i-model.ts | 8 + 11 files changed, 292 insertions(+), 165 deletions(-) create mode 100644 src/whoami/WhoAmI.tsx create mode 100644 src/whoami/__tests__/WhoAmI.test.tsx create mode 100644 src/whoami/__tests__/mock-whoami.json create mode 100644 src/whoami/who-am-i-model.ts diff --git a/src/App.tsx b/src/App.tsx index 14f2a0faae..3e20243304 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import { PageNav } from "./PageNav"; import { Help } from "./components/help-enabler/HelpHeader"; import { RealmContextProvider } from "./components/realm-context/RealmContext"; +import { WhoAmIContextProvider } from "./whoami/WhoAmI"; import { routes } from "./route-config"; import { PageBreadCrumbs } from "./components/bread-crumb/PageBreadCrumbs"; @@ -14,22 +15,24 @@ import { PageBreadCrumbs } from "./components/bread-crumb/PageBreadCrumbs"; export const App = () => { return ( - - - } - isManagedSidebar - sidebar={} - breadcrumb={} - > - - {routes(() => {}).map((route, i) => ( - - ))} - - - - + + + + } + isManagedSidebar + sidebar={} + breadcrumb={} + > + + {routes(() => {}).map((route, i) => ( + + ))} + + + + + ); }; diff --git a/src/PageHeader.tsx b/src/PageHeader.tsx index 703f985cbe..eba6226067 100644 --- a/src/PageHeader.tsx +++ b/src/PageHeader.tsx @@ -15,6 +15,7 @@ import { } from "@patternfly/react-core"; import { HelpIcon } from "@patternfly/react-icons"; import { KeycloakContext } from "./auth/KeycloakContext"; +import { WhoAmIContext } from "./whoami/WhoAmI"; import { HelpHeader } from "./components/help-enabler/HelpHeader"; import { Link } from "react-router-dom"; @@ -134,6 +135,7 @@ const KebabDropdown = () => { const UserDropdown = () => { const keycloak = useContext(KeycloakContext); + const whoami = useContext(WhoAmIContext); const [isDropdownOpen, setDropdownOpen] = useState(false); const onDropdownToggle = () => { @@ -147,7 +149,7 @@ const UserDropdown = () => { isOpen={isDropdownOpen} toggle={ - {keycloak?.loggedInUser} + {whoami.getDisplayName()} } dropdownItems={userDropdownItems} diff --git a/src/clients/__tests__/__snapshots__/ClientList.test.tsx.snap b/src/clients/__tests__/__snapshots__/ClientList.test.tsx.snap index cfe8551d2b..131a750aad 100644 --- a/src/clients/__tests__/__snapshots__/ClientList.test.tsx.snap +++ b/src/clients/__tests__/__snapshots__/ClientList.test.tsx.snap @@ -6,7 +6,7 @@ Object { "baseElement":
- clientID + Client ID @@ -339,7 +339,7 @@ Object { @@ -412,7 +412,7 @@ Object { @@ -483,7 +483,7 @@ Object { @@ -653,7 +653,7 @@ Object {
- type + Type - description + Description - homeURL + Home URL openid-connect openid-connect openid-connect openid-connect openid-connect openid-connect , "container":
- clientID + Client ID @@ -1087,7 +1087,7 @@ Object { @@ -1160,7 +1160,7 @@ Object { @@ -1231,7 +1231,7 @@ Object { @@ -1401,7 +1401,7 @@ Object {
- type + Type - description + Description - homeURL + Home URL openid-connect openid-connect openid-connect openid-connect openid-connect openid-connect - home + Home @@ -81,7 +81,7 @@ exports[`BreadCrumbs tests couple of crumbs 1`] = ` - createClient + Create client diff --git a/src/components/realm-context/RealmContext.tsx b/src/components/realm-context/RealmContext.tsx index 4d45eb1910..d6db32e5e3 100644 --- a/src/components/realm-context/RealmContext.tsx +++ b/src/components/realm-context/RealmContext.tsx @@ -1,7 +1,8 @@ -import React, { useState } from "react"; +import React, { useState, useContext } from "react"; +import { WhoAmIContext } from "../../whoami/WhoAmI"; export const RealmContext = React.createContext({ - realm: "master", + realm: "", setRealm: (realm: string) => {}, }); @@ -10,7 +11,8 @@ type RealmContextProviderProps = { children: React.ReactNode }; export const RealmContextProvider = ({ children, }: RealmContextProviderProps) => { - const [realm, setRealm] = useState("master"); + const homeRealm = useContext(WhoAmIContext).getHomeRealm(); + const [realm, setRealm] = useState(homeRealm); return ( diff --git a/src/components/realm-selector/RealmSelector.tsx b/src/components/realm-selector/RealmSelector.tsx index 22814bda62..77861eccee 100644 --- a/src/components/realm-selector/RealmSelector.tsx +++ b/src/components/realm-selector/RealmSelector.tsx @@ -16,6 +16,7 @@ import { CheckIcon } from "@patternfly/react-icons"; import { RealmRepresentation } from "../../realm/models/Realm"; import { RealmContext } from "../realm-context/RealmContext"; +import { WhoAmIContext } from "../../whoami/WhoAmI"; import "./realm-selector.css"; @@ -25,6 +26,7 @@ type RealmSelectorProps = { export const RealmSelector = ({ realmList }: RealmSelectorProps) => { const { realm, setRealm } = useContext(RealmContext); + const whoami = useContext(WhoAmIContext); const [open, setOpen] = useState(false); const [search, setSearch] = useState(""); const [filteredItems, setFilteredItems] = useState(realmList); @@ -77,6 +79,19 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => { )); + const addRealmComponent = ( + + {whoami.canCreateRealm() && ( + <> + + + + + + )} + + ); + return ( <> {realmList.length > 5 && ( @@ -119,13 +134,7 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => { {toUpperCase(realm)} } - dropdownItems={[ - ...dropdownItems, - , - - - , - ]} + dropdownItems={[...dropdownItems, addRealmComponent]} /> )} diff --git a/src/components/realm-selector/__tests__/__snapshots__/RealmSelector.test.tsx.snap b/src/components/realm-selector/__tests__/__snapshots__/RealmSelector.test.tsx.snap index b42255b914..c9b1ef6168 100644 --- a/src/components/realm-selector/__tests__/__snapshots__/RealmSelector.test.tsx.snap +++ b/src/components/realm-selector/__tests__/__snapshots__/RealmSelector.test.tsx.snap @@ -23,10 +23,7 @@ exports[`renders realm selector 1`] = ` value="another" /> , - , - - - , + , ] } id="realm-select" @@ -54,10 +51,7 @@ exports[`renders realm selector 1`] = ` value="another" /> , - , - - - , + , ] } id="realm-select" @@ -160,29 +154,6 @@ exports[`renders realm selector 1`] = ` -
-
  • - -
    - Create Realm -
    -
    -
  • , } @@ -268,29 +239,6 @@ exports[`renders realm selector 1`] = ` -
    -
  • - -
    - Create Realm -
    -
    -
  • , } diff --git a/src/whoami/WhoAmI.tsx b/src/whoami/WhoAmI.tsx new file mode 100644 index 0000000000..8a61313e19 --- /dev/null +++ b/src/whoami/WhoAmI.tsx @@ -0,0 +1,78 @@ +import React, { useContext } from "react"; +import i18n from "../i18n"; + +import WhoAmIRepresentation from "./who-am-i-model"; + +import { HttpClientContext } from "../http-service/HttpClientContext"; +import { KeycloakContext } from "../auth/KeycloakContext"; +import { DataLoader } from "../components/data-loader/DataLoader"; + +export class WhoAmI { + constructor( + private homeRealm?: string | undefined, + private me?: WhoAmIRepresentation | undefined + ) { + if (this.me !== undefined && this.me.locale) { + i18n.changeLanguage(this.me.locale, (error) => { + if (error) console.log("Unable to set locale to " + this.me?.locale); + }); + } + } + + public getDisplayName(): string { + if (this.me === undefined) return ""; + + return this.me.displayName; + } + + /** + * Return the realm I am signed in to. + */ + public getHomeRealm(): string { + let realm: string | undefined = this.homeRealm; + if (realm === undefined) realm = this.me?.realm; + if (realm === undefined) realm = "master"; // this really can't happen in the real world + + return realm; + } + + public canCreateRealm(): boolean { + return this.me !== undefined && this.me.createRealm; + } + + public getRealmAccess(): Readonly<{ [key: string]: ReadonlyArray }> { + if (this.me === undefined) return {}; + + return this.me.realm_access; + } +} + +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 whoAmILoader = async () => { + if (keycloak === undefined) return undefined; + + const realm = keycloak.realm(); + + return await httpClient + .doGet(`/admin/${realm}/console/whoami/`) + .then((r) => r.data as WhoAmIRepresentation); + }; + + return ( + + {(whoamirep) => ( + + {children} + + )} + + ); +}; diff --git a/src/whoami/__tests__/WhoAmI.test.tsx b/src/whoami/__tests__/WhoAmI.test.tsx new file mode 100644 index 0000000000..19b3cf7f40 --- /dev/null +++ b/src/whoami/__tests__/WhoAmI.test.tsx @@ -0,0 +1,28 @@ +import whoamiMock from "./mock-whoami.json"; +import { WhoAmI } from "../WhoAmI"; + +test("returns display name", () => { + const whoami = new WhoAmI("master", whoamiMock); + expect(whoami.getDisplayName()).toEqual("Stan Silvert"); +}); + +test("returns correct home realm", () => { + let whoami = new WhoAmI("myrealm", whoamiMock); + expect(whoami.getHomeRealm()).toEqual("myrealm"); + whoami = new WhoAmI(undefined, whoamiMock); + expect(whoami.getHomeRealm()).toEqual("master"); +}); + +test("can not create realm", () => { + const whoami = new WhoAmI("master", whoamiMock); + expect(whoami.canCreateRealm()).toEqual(false); +}); + +test("getRealmAccess", () => { + const whoami = new WhoAmI("master", whoamiMock); + expect(Object.keys(whoami.getRealmAccess()).length).toEqual(2); + expect(whoami.getRealmAccess()["master"].length).toEqual(18); +}); + +//TODO: When we have easy access to i18n, create test for setting locale. +// Tested manually and it does work. diff --git a/src/whoami/__tests__/mock-whoami.json b/src/whoami/__tests__/mock-whoami.json new file mode 100644 index 0000000000..be3778279c --- /dev/null +++ b/src/whoami/__tests__/mock-whoami.json @@ -0,0 +1,49 @@ +{ + "userId": "1b635073-7ac8-49db-8eb9-f9fa9cd15bf5", + "realm": "master", + "displayName": "Stan Silvert", + "locale": "en", + "createRealm": false, + "realm_access": { + "aaa": [ + "view-identity-providers", + "view-realm", + "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" + ], + "master": [ + "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" + ] + } +} diff --git a/src/whoami/who-am-i-model.ts b/src/whoami/who-am-i-model.ts new file mode 100644 index 0000000000..865cf72648 --- /dev/null +++ b/src/whoami/who-am-i-model.ts @@ -0,0 +1,8 @@ +export default interface WhoAmIRepresentation { + userId: string; + realm: string; + displayName: string; + locale: string; + createRealm: boolean; + realm_access: { [key: string]: string[] }; +}