From 0ba37e8622ac99315fcbab7e537910b8752c112c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Mon, 15 Jan 2024 13:13:29 +0000 Subject: [PATCH] feat: list all root roles in SSO config (#5887) Lists all root roles in SSO config, including custom root roles. ![image](https://github.com/Unleash/unleash/assets/14320932/30114169-4184-4a22-9671-c7041b750d1c) --- .../auth/AutoCreateForm/AutoCreateForm.tsx | 48 ++++++++-------- .../admin/auth/OidcAuth/OidcAuth.tsx | 5 +- .../admin/auth/SamlAuth/SamlAuth.tsx | 5 +- .../common/RoleSelect/RoleSelect.tsx | 4 +- src/lib/db/access-store.ts | 7 ++- .../e2e/services/user-service.e2e.test.ts | 55 ++++++++++++++++++- 6 files changed, 93 insertions(+), 31 deletions(-) diff --git a/frontend/src/component/admin/auth/AutoCreateForm/AutoCreateForm.tsx b/frontend/src/component/admin/auth/AutoCreateForm/AutoCreateForm.tsx index 03bbbb14e6f..73ff46fa9d2 100644 --- a/frontend/src/component/admin/auth/AutoCreateForm/AutoCreateForm.tsx +++ b/frontend/src/component/admin/auth/AutoCreateForm/AutoCreateForm.tsx @@ -3,40 +3,49 @@ import { FormControl, FormControlLabel, Grid, - InputLabel, - MenuItem, - Select, Switch, TextField, - SelectChangeEvent, } from '@mui/material'; +import { RoleSelect } from 'component/common/RoleSelect/RoleSelect'; +import { useRoles } from 'hooks/api/getters/useRoles/useRoles'; +import { IRole } from 'interfaces/role'; interface IAutoCreateFormProps { data?: { enabled: boolean; autoCreate: boolean; defaultRootRole?: string; + defaultRootRoleId?: number; emailDomains?: string; }; - setValue: (name: string, value: string | boolean) => void; + setValue: ( + name: string, + value: string | boolean | number | undefined, + ) => void; } export const AutoCreateForm = ({ data = { enabled: false, autoCreate: false }, setValue, }: IAutoCreateFormProps) => { + const { roles } = useRoles(); + const updateAutoCreate = () => { setValue('autoCreate', !data.autoCreate); }; - const updateDefaultRootRole = (evt: SelectChangeEvent) => { - setValue('defaultRootRole', evt.target.value); + const updateDefaultRootRoleId = (role: IRole | null) => { + setValue('defaultRootRoleId', role?.id); }; const updateField = (e: ChangeEvent) => { setValue(e.target.name, e.target.value); }; + const roleIdToRole = (rootRoleId: number | undefined): IRole | null => { + return roles.find((role: IRole) => role.id === rootRoleId) || null; + }; + return ( @@ -69,24 +78,15 @@ export const AutoCreateForm = ({

- - - Default Role - - + required + hideDescription + /> diff --git a/frontend/src/component/admin/auth/OidcAuth/OidcAuth.tsx b/frontend/src/component/admin/auth/OidcAuth/OidcAuth.tsx index 93b08dd2d01..c7f0044200b 100644 --- a/frontend/src/component/admin/auth/OidcAuth/OidcAuth.tsx +++ b/frontend/src/component/admin/auth/OidcAuth/OidcAuth.tsx @@ -60,7 +60,10 @@ export const OidcAuth = () => { setData({ ...data, enableSingleSignOut: !data.enableSingleSignOut }); }; - const setValue = (name: string, value: string | boolean) => { + const setValue = ( + name: string, + value: string | boolean | number | undefined, + ) => { setData({ ...data, [name]: value, diff --git a/frontend/src/component/admin/auth/SamlAuth/SamlAuth.tsx b/frontend/src/component/admin/auth/SamlAuth/SamlAuth.tsx index bc1feed06d6..5842e4b18da 100644 --- a/frontend/src/component/admin/auth/SamlAuth/SamlAuth.tsx +++ b/frontend/src/component/admin/auth/SamlAuth/SamlAuth.tsx @@ -51,7 +51,10 @@ export const SamlAuth = () => { setData({ ...data, enabled: !data.enabled }); }; - const setValue = (name: string, value: string | boolean) => { + const setValue = ( + name: string, + value: string | boolean | number | undefined, + ) => { setData({ ...data, [name]: value, diff --git a/frontend/src/component/common/RoleSelect/RoleSelect.tsx b/frontend/src/component/common/RoleSelect/RoleSelect.tsx index 5fada87da81..42f851579b5 100644 --- a/frontend/src/component/common/RoleSelect/RoleSelect.tsx +++ b/frontend/src/component/common/RoleSelect/RoleSelect.tsx @@ -23,6 +23,7 @@ interface IRoleSelectProps value: IRole | null; setValue: (role: IRole | null) => void; required?: boolean; + hideDescription?: boolean; } export const RoleSelect = ({ @@ -30,6 +31,7 @@ export const RoleSelect = ({ value, setValue, required, + hideDescription, ...rest }: IRoleSelectProps) => { const renderRoleOption = ( @@ -60,7 +62,7 @@ export const RoleSelect = ({ {...rest} /> ( )} diff --git a/src/lib/db/access-store.ts b/src/lib/db/access-store.ts index 5c9a6d13ad9..1c6f9202d50 100644 --- a/src/lib/db/access-store.ts +++ b/src/lib/db/access-store.ts @@ -12,12 +12,13 @@ import { IUserRole, IUserWithProjectRoles, } from '../types/stores/access-store'; -import { IPermission, IUserAccessOverview, RoleType } from '../types/model'; +import { IPermission, IUserAccessOverview } from '../types/model'; import NotFoundError from '../error/notfound-error'; import { ENVIRONMENT_PERMISSION_TYPE, PROJECT_ROLE_TYPES, ROOT_PERMISSION_TYPE, + ROOT_ROLE_TYPES, } from '../util/constants'; import { Db } from './db'; import { @@ -407,8 +408,8 @@ export class AccessStore implements IAccessStore { .select(['id', 'name', 'type', 'description']) .from(T.ROLES) .innerJoin(`${T.ROLE_USER} as ru`, 'ru.role_id', 'id') - .where('ru.user_id', '=', userId) - .andWhere('type', '=', RoleType.ROOT) + .whereIn('type', ROOT_ROLE_TYPES) + .andWhere('ru.user_id', '=', userId) .first(); } diff --git a/src/test/e2e/services/user-service.e2e.test.ts b/src/test/e2e/services/user-service.e2e.test.ts index dd7f370f9ef..a7562b72130 100644 --- a/src/test/e2e/services/user-service.e2e.test.ts +++ b/src/test/e2e/services/user-service.e2e.test.ts @@ -18,12 +18,14 @@ import { BadDataError } from '../../../lib/error'; import PasswordMismatch from '../../../lib/error/password-mismatch'; import { EventService } from '../../../lib/services'; import { + CREATE_ADDON, IUnleashStores, IUserStore, USER_CREATED, USER_DELETED, USER_UPDATED, } from '../../../lib/types'; +import { CUSTOM_ROOT_ROLE_TYPE } from '../../../lib/util'; let db: ITestDb; let stores: IUnleashStores; @@ -31,9 +33,11 @@ let userService: UserService; let userStore: IUserStore; let adminRole: IRole; let viewerRole: IRole; +let customRole: IRole; let sessionService: SessionService; let settingService: SettingService; let eventService: EventService; +let accessService: AccessService; beforeAll(async () => { db = await dbInit('user_service_serial', getLogger); @@ -41,7 +45,7 @@ beforeAll(async () => { const config = createTestConfig(); eventService = new EventService(stores, config); const groupService = new GroupService(stores, config, eventService); - const accessService = new AccessService( + accessService = new AccessService( stores, config, groupService, @@ -64,6 +68,17 @@ beforeAll(async () => { const rootRoles = await accessService.getRootRoles(); adminRole = rootRoles.find((r) => r.name === RoleName.ADMIN)!; viewerRole = rootRoles.find((r) => r.name === RoleName.VIEWER)!; + customRole = await accessService.createRole({ + name: 'Custom role', + type: CUSTOM_ROOT_ROLE_TYPE, + description: 'A custom role', + permissions: [ + { + name: CREATE_ADDON, + }, + ], + createdByUserId: 1, + }); }); afterAll(async () => { @@ -401,3 +416,41 @@ test('should throw if autoCreate is false via SSO', async () => { }), ).rejects.toThrow(new NotFoundError('No user found')); }); + +test('should support a root role id when logging in and creating user via SSO', async () => { + const name = 'root-role-id'; + const email = `${name}@test.com`; + const user = await userService.loginUserSSO({ + email, + rootRole: viewerRole.id, + name, + autoCreate: true, + }); + + const userWithRole = await userService.getUser(user.id); + expect(user.email).toBe(email); + expect(user.name).toBe(name); + expect(userWithRole.name).toBe(name); + expect(userWithRole.rootRole).toBe(viewerRole.id); +}); + +test('should support a custom root role id when logging in and creating user via SSO', async () => { + const name = 'custom-root-role-id'; + const email = `${name}@test.com`; + const user = await userService.loginUserSSO({ + email, + rootRole: customRole.id, + name, + autoCreate: true, + }); + + const userWithRole = await userService.getUser(user.id); + expect(user.email).toBe(email); + expect(user.name).toBe(name); + expect(userWithRole.name).toBe(name); + expect(userWithRole.rootRole).toBe(customRole.id); + + const permissions = await accessService.getPermissionsForUser(user); + expect(permissions).toHaveLength(1); + expect(permissions[0].permission).toBe(CREATE_ADDON); +});