From b2d7dc9e43e6144a3937819e9c69e6dd29415234 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste WATENBERG Date: Thu, 11 Mar 2021 17:59:54 +0100 Subject: [PATCH] shell-ui: Add ability to configure user group mapping --- shell-ui/src/NavBar.js | 6 ++++-- shell-ui/src/UserDataListener.js | 7 +++++-- shell-ui/src/auth/permissionUtils.js | 19 ++++++++++++++++++- shell-ui/src/auth/permissionUtils.spec.js | 19 +++++++++++++++++++ shell-ui/src/index.js | 7 +++++-- 5 files changed, 51 insertions(+), 7 deletions(-) diff --git a/shell-ui/src/NavBar.js b/shell-ui/src/NavBar.js index f71e6d761a..296fa988c3 100644 --- a/shell-ui/src/NavBar.js +++ b/shell-ui/src/NavBar.js @@ -6,11 +6,13 @@ import type { Options, SolutionsNavbarProps, TranslationAndGroups, + UserGroupsMapping } from './index'; import type { Node } from 'react'; import { logOut } from './auth/logout'; import { getAccessiblePathsFromOptions, + getUserGroups, isEntryAccessibleByTheUser, normalizePath, } from './auth/permissionUtils'; @@ -60,10 +62,10 @@ const translateOptionsToMenu = ( ); }; -export const Navbar = ({ options }: { options: Options }): Node => { +export const Navbar = ({ options, userGroupsMapping }: { options: Options, userGroupsMapping?: UserGroupsMapping }): Node => { const auth = useAuth(); - const userGroups: string[] = auth.userData?.profile?.groups || []; + const userGroups: string[] = getUserGroups(auth.userData, userGroupsMapping); const accessiblePaths = getAccessiblePathsFromOptions(options, userGroups); useLayoutEffect(() => { accessiblePaths.forEach((accessiblePath) => { diff --git a/shell-ui/src/UserDataListener.js b/shell-ui/src/UserDataListener.js index a0fea142b1..2805aefeb1 100644 --- a/shell-ui/src/UserDataListener.js +++ b/shell-ui/src/UserDataListener.js @@ -1,10 +1,13 @@ import { useAuth } from 'oidc-react'; import { useLayoutEffect } from 'react'; -import { AUTHENTICATED_EVENT, SolutionsNavbarProps } from './index'; +import { getUserGroups } from './auth/permissionUtils'; +import { AUTHENTICATED_EVENT, SolutionsNavbarProps, type UserGroupsMapping } from './index'; export const UserDataListener = ({ + userGroupsMapping, onAuthenticated, }: { + userGroupsMapping?: UserGroupsMapping, onAuthenticated?: $PropertyType, }) => { const auth = useAuth(); @@ -12,7 +15,7 @@ export const UserDataListener = ({ useLayoutEffect(() => { if (onAuthenticated) { onAuthenticated( - new CustomEvent(AUTHENTICATED_EVENT, { detail: auth.userData }), + new CustomEvent(AUTHENTICATED_EVENT, { detail: {...auth.userData, groups: getUserGroups(auth.userData, userGroupsMapping) } }), ); } }, [JSON.stringify(auth.userData), !!onAuthenticated]); diff --git a/shell-ui/src/auth/permissionUtils.js b/shell-ui/src/auth/permissionUtils.js index 4d152babcf..badb318452 100644 --- a/shell-ui/src/auth/permissionUtils.js +++ b/shell-ui/src/auth/permissionUtils.js @@ -1,5 +1,10 @@ //@flow -import type { Options, TranslationAndGroups } from '../index'; +import type { User } from 'oidc-react'; +import type { + Options, + TranslationAndGroups, + UserGroupsMapping, +} from '../index'; export const isEntryAccessibleByTheUser = ( [path, translationAndGroup]: [string, TranslationAndGroups], @@ -39,3 +44,15 @@ export const isPathAccessible = ( (accessiblePath) => normalizePath(accessiblePath) === normalizedPath, ); }; + +export const getUserGroups = ( + user?: User, + userGroupsMapping?: UserGroupsMapping, +): string[] => { + const userOIDCGroups: string[] = user?.profile?.groups || []; + const userMappedGroups = userGroupsMapping + ? userGroupsMapping[user?.profile?.email || ''] || [] + : []; + + return Array.from(new Set([...userOIDCGroups, ...userMappedGroups])); +}; diff --git a/shell-ui/src/auth/permissionUtils.spec.js b/shell-ui/src/auth/permissionUtils.spec.js index 8652e74660..e4656cadfd 100644 --- a/shell-ui/src/auth/permissionUtils.spec.js +++ b/shell-ui/src/auth/permissionUtils.spec.js @@ -1,6 +1,7 @@ //@flow import { getAccessiblePathsFromOptions, + getUserGroups, isEntryAccessibleByTheUser, isPathAccessible, normalizePath, @@ -116,3 +117,21 @@ describe('permission utils - isPathAccessible', () => { expect(isAccessible).toBe(false); }); }); + +describe('permission utils - getUserGroups', () => { + it('should return an array of groups when defined in OIDC claims ', () => { + //E + const oidcGroups = ["oidcGroup"]; + const groups = getUserGroups({profile: {email: "test@test.com", groups: oidcGroups}}, undefined); + //V + expect(groups).toEqual(oidcGroups); + }); + + it('should return a merged array of groups when defined in OIDC claims and mapping ', () => { + //E + const oidcAndStaticGroups = ["group"]; + const groups = getUserGroups({profile: {email: "test@test.com", groups: oidcAndStaticGroups}}, {'test@test.com': oidcAndStaticGroups}); + //V + expect(groups).toEqual(oidcAndStaticGroups); + }); +}); diff --git a/shell-ui/src/index.js b/shell-ui/src/index.js index 1dbefbad03..403734c090 100644 --- a/shell-ui/src/index.js +++ b/shell-ui/src/index.js @@ -21,6 +21,8 @@ export type MenuItems = {[path: string]: TranslationAndGroups } export type Options = { main: MenuItems, subLogin: MenuItems }; +export type UserGroupsMapping = {[email: string]: string[]}; + export type SolutionsNavbarProps = { 'oidc-provider-url'?: string, scopes?: string, @@ -44,6 +46,7 @@ type Config = { scopes?: string, }, options?: Options, + userGroupsMapping?: UserGroupsMapping }; const SolutionsNavbar = ({ @@ -133,7 +136,7 @@ const SolutionsNavbar = ({ return ( - + - + );