1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-04 00:18:40 +01:00

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)
This commit is contained in:
Nuno Góis 2024-01-15 13:13:29 +00:00 committed by GitHub
parent 9d839299e2
commit 0ba37e8622
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 93 additions and 31 deletions

View File

@ -3,40 +3,49 @@ import {
FormControl, FormControl,
FormControlLabel, FormControlLabel,
Grid, Grid,
InputLabel,
MenuItem,
Select,
Switch, Switch,
TextField, TextField,
SelectChangeEvent,
} from '@mui/material'; } 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 { interface IAutoCreateFormProps {
data?: { data?: {
enabled: boolean; enabled: boolean;
autoCreate: boolean; autoCreate: boolean;
defaultRootRole?: string; defaultRootRole?: string;
defaultRootRoleId?: number;
emailDomains?: string; emailDomains?: string;
}; };
setValue: (name: string, value: string | boolean) => void; setValue: (
name: string,
value: string | boolean | number | undefined,
) => void;
} }
export const AutoCreateForm = ({ export const AutoCreateForm = ({
data = { enabled: false, autoCreate: false }, data = { enabled: false, autoCreate: false },
setValue, setValue,
}: IAutoCreateFormProps) => { }: IAutoCreateFormProps) => {
const { roles } = useRoles();
const updateAutoCreate = () => { const updateAutoCreate = () => {
setValue('autoCreate', !data.autoCreate); setValue('autoCreate', !data.autoCreate);
}; };
const updateDefaultRootRole = (evt: SelectChangeEvent) => { const updateDefaultRootRoleId = (role: IRole | null) => {
setValue('defaultRootRole', evt.target.value); setValue('defaultRootRoleId', role?.id);
}; };
const updateField = (e: ChangeEvent<HTMLInputElement>) => { const updateField = (e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.name, e.target.value); setValue(e.target.name, e.target.value);
}; };
const roleIdToRole = (rootRoleId: number | undefined): IRole | null => {
return roles.find((role: IRole) => role.id === rootRoleId) || null;
};
return ( return (
<Fragment> <Fragment>
<Grid container spacing={3} mb={2}> <Grid container spacing={3} mb={2}>
@ -69,24 +78,15 @@ export const AutoCreateForm = ({
</p> </p>
</Grid> </Grid>
<Grid item md={6}> <Grid item md={6}>
<FormControl style={{ minWidth: '200px' }}> <FormControl style={{ width: '400px' }}>
<InputLabel id='defaultRootRole-label'> <RoleSelect
Default Role roles={roles}
</InputLabel> value={roleIdToRole(data.defaultRootRoleId)}
<Select setValue={updateDefaultRootRoleId}
label='Default Role'
labelId='defaultRootRole-label'
id='defaultRootRole'
name='defaultRootRole'
disabled={!data.autoCreate || !data.enabled} disabled={!data.autoCreate || !data.enabled}
value={data.defaultRootRole || 'Editor'} required
onChange={updateDefaultRootRole} hideDescription
> />
{/*consider these from API or constants. */}
<MenuItem value='Viewer'>Viewer</MenuItem>
<MenuItem value='Editor'>Editor</MenuItem>
<MenuItem value='Admin'>Admin</MenuItem>
</Select>
</FormControl> </FormControl>
</Grid> </Grid>
</Grid> </Grid>

View File

@ -60,7 +60,10 @@ export const OidcAuth = () => {
setData({ ...data, enableSingleSignOut: !data.enableSingleSignOut }); setData({ ...data, enableSingleSignOut: !data.enableSingleSignOut });
}; };
const setValue = (name: string, value: string | boolean) => { const setValue = (
name: string,
value: string | boolean | number | undefined,
) => {
setData({ setData({
...data, ...data,
[name]: value, [name]: value,

View File

@ -51,7 +51,10 @@ export const SamlAuth = () => {
setData({ ...data, enabled: !data.enabled }); setData({ ...data, enabled: !data.enabled });
}; };
const setValue = (name: string, value: string | boolean) => { const setValue = (
name: string,
value: string | boolean | number | undefined,
) => {
setData({ setData({
...data, ...data,
[name]: value, [name]: value,

View File

@ -23,6 +23,7 @@ interface IRoleSelectProps
value: IRole | null; value: IRole | null;
setValue: (role: IRole | null) => void; setValue: (role: IRole | null) => void;
required?: boolean; required?: boolean;
hideDescription?: boolean;
} }
export const RoleSelect = ({ export const RoleSelect = ({
@ -30,6 +31,7 @@ export const RoleSelect = ({
value, value,
setValue, setValue,
required, required,
hideDescription,
...rest ...rest
}: IRoleSelectProps) => { }: IRoleSelectProps) => {
const renderRoleOption = ( const renderRoleOption = (
@ -60,7 +62,7 @@ export const RoleSelect = ({
{...rest} {...rest}
/> />
<ConditionallyRender <ConditionallyRender
condition={Boolean(value)} condition={Boolean(value) && !hideDescription}
show={() => ( show={() => (
<RoleDescription sx={{ marginTop: 1 }} roleId={value!.id} /> <RoleDescription sx={{ marginTop: 1 }} roleId={value!.id} />
)} )}

View File

@ -12,12 +12,13 @@ import {
IUserRole, IUserRole,
IUserWithProjectRoles, IUserWithProjectRoles,
} from '../types/stores/access-store'; } 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 NotFoundError from '../error/notfound-error';
import { import {
ENVIRONMENT_PERMISSION_TYPE, ENVIRONMENT_PERMISSION_TYPE,
PROJECT_ROLE_TYPES, PROJECT_ROLE_TYPES,
ROOT_PERMISSION_TYPE, ROOT_PERMISSION_TYPE,
ROOT_ROLE_TYPES,
} from '../util/constants'; } from '../util/constants';
import { Db } from './db'; import { Db } from './db';
import { import {
@ -407,8 +408,8 @@ export class AccessStore implements IAccessStore {
.select(['id', 'name', 'type', 'description']) .select(['id', 'name', 'type', 'description'])
.from<IRole[]>(T.ROLES) .from<IRole[]>(T.ROLES)
.innerJoin(`${T.ROLE_USER} as ru`, 'ru.role_id', 'id') .innerJoin(`${T.ROLE_USER} as ru`, 'ru.role_id', 'id')
.where('ru.user_id', '=', userId) .whereIn('type', ROOT_ROLE_TYPES)
.andWhere('type', '=', RoleType.ROOT) .andWhere('ru.user_id', '=', userId)
.first(); .first();
} }

View File

@ -18,12 +18,14 @@ import { BadDataError } from '../../../lib/error';
import PasswordMismatch from '../../../lib/error/password-mismatch'; import PasswordMismatch from '../../../lib/error/password-mismatch';
import { EventService } from '../../../lib/services'; import { EventService } from '../../../lib/services';
import { import {
CREATE_ADDON,
IUnleashStores, IUnleashStores,
IUserStore, IUserStore,
USER_CREATED, USER_CREATED,
USER_DELETED, USER_DELETED,
USER_UPDATED, USER_UPDATED,
} from '../../../lib/types'; } from '../../../lib/types';
import { CUSTOM_ROOT_ROLE_TYPE } from '../../../lib/util';
let db: ITestDb; let db: ITestDb;
let stores: IUnleashStores; let stores: IUnleashStores;
@ -31,9 +33,11 @@ let userService: UserService;
let userStore: IUserStore; let userStore: IUserStore;
let adminRole: IRole; let adminRole: IRole;
let viewerRole: IRole; let viewerRole: IRole;
let customRole: IRole;
let sessionService: SessionService; let sessionService: SessionService;
let settingService: SettingService; let settingService: SettingService;
let eventService: EventService; let eventService: EventService;
let accessService: AccessService;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('user_service_serial', getLogger); db = await dbInit('user_service_serial', getLogger);
@ -41,7 +45,7 @@ beforeAll(async () => {
const config = createTestConfig(); const config = createTestConfig();
eventService = new EventService(stores, config); eventService = new EventService(stores, config);
const groupService = new GroupService(stores, config, eventService); const groupService = new GroupService(stores, config, eventService);
const accessService = new AccessService( accessService = new AccessService(
stores, stores,
config, config,
groupService, groupService,
@ -64,6 +68,17 @@ beforeAll(async () => {
const rootRoles = await accessService.getRootRoles(); const rootRoles = await accessService.getRootRoles();
adminRole = rootRoles.find((r) => r.name === RoleName.ADMIN)!; adminRole = rootRoles.find((r) => r.name === RoleName.ADMIN)!;
viewerRole = rootRoles.find((r) => r.name === RoleName.VIEWER)!; 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 () => { afterAll(async () => {
@ -401,3 +416,41 @@ test('should throw if autoCreate is false via SSO', async () => {
}), }),
).rejects.toThrow(new NotFoundError('No user found')); ).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);
});