1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-12 13:48:35 +02:00

feat: add service method to retrieve group and project access for all users (#4708)

This commit is contained in:
Simon Hornby 2023-09-14 11:43:39 +02:00 committed by GitHub
parent 4b90fe7790
commit 10afbc8a9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 123 additions and 4 deletions

View File

@ -12,7 +12,7 @@ import {
IUserRole, IUserRole,
IUserWithProjectRoles, IUserWithProjectRoles,
} from '../types/stores/access-store'; } from '../types/stores/access-store';
import { IPermission } 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,
@ -758,4 +758,40 @@ export class AccessStore implements IAccessStore {
[destinationEnvironment, sourceEnvironment], [destinationEnvironment, sourceEnvironment],
); );
} }
async getUserAccessOverview(): Promise<IUserAccessOverview[]> {
const result = await this.db
.raw(`SELECT u.id, u.created_at, u.name, u.email, u.seen_at, up.p_array as projects, gr.p_array as groups, r.name as root_role
FROM users u, LATERAL (
SELECT ARRAY (
SELECT ru.project
FROM role_user ru
WHERE ru.user_id = u.id
) AS p_array
) up, LATERAL (
SELECT r.name
FROM role_user ru
inner join roles r on ru.role_id = r.id
where ru.user_id = u.id and r.type='root'
) r, LATERAL (
SELECT ARRAY (
select g.name from group_user gu
left join groups g on g.id = gu.group_id
WHERE gu.user_id = u.id
) AS p_array
) gr
order by u.id;`);
return result.rows.map((row) => {
return {
userId: row.id,
createdAt: row.created_at,
userName: row.name,
userEmail: row.email,
lastSeen: row.seen_at,
accessibleProjects: row.projects,
groups: row.groups,
rootRole: row.root_role,
};
});
}
} }

View File

@ -40,7 +40,7 @@ import InvalidOperationError from '../error/invalid-operation-error';
import BadDataError from '../error/bad-data-error'; import BadDataError from '../error/bad-data-error';
import { IGroup } from '../types/group'; import { IGroup } from '../types/group';
import { GroupService } from './group-service'; import { GroupService } from './group-service';
import { IFlagResolver, IUnleashConfig } from 'lib/types'; import { IFlagResolver, IUnleashConfig, IUserAccessOverview } from 'lib/types';
const { ADMIN } = permissions; const { ADMIN } = permissions;
@ -736,4 +736,8 @@ export class AccessService {
await this.validateRoleIsUnique(role.name, existingId); await this.validateRoleIsUnique(role.name, existingId);
return cleanedRole; return cleanedRole;
} }
async getUserAccessOverview(): Promise<IUserAccessOverview[]> {
return this.store.getUserAccessOverview();
}
} }

View File

@ -459,3 +459,14 @@ export interface IFeatureStrategySegment {
featureStrategyId: string; featureStrategyId: string;
segmentId: number; segmentId: number;
} }
export interface IUserAccessOverview {
userId: number;
createdAt?: Date;
userName?: string;
userEmail: number;
lastSeen?: Date;
accessibleProjects: string[];
groups: string[];
rootRole: string;
}

View File

@ -1,6 +1,6 @@
import { PermissionRef } from 'lib/services/access-service'; import { PermissionRef } from 'lib/services/access-service';
import { IGroupModelWithProjectRole } from '../group'; import { IGroupModelWithProjectRole } from '../group';
import { IPermission, IUserWithRole } from '../model'; import { IPermission, IUserAccessOverview, IUserWithRole } from '../model';
import { Store } from './store'; import { Store } from './store';
export interface IUserPermission { export interface IUserPermission {
@ -200,4 +200,5 @@ export interface IAccessStore extends Store<IRole, number> {
): Promise<number[]>; ): Promise<number[]>;
removeUserAccess(projectId: string, userId: number): Promise<void>; removeUserAccess(projectId: string, userId: number): Promise<void>;
removeGroupAccess(projectId: string, groupId: number): Promise<void>; removeGroupAccess(projectId: string, groupId: number): Promise<void>;
getUserAccessOverview(): Promise<IUserAccessOverview[]>;
} }

View File

@ -10,6 +10,7 @@ import {
ICreateGroupUserModel, ICreateGroupUserModel,
IPermission, IPermission,
IUnleashStores, IUnleashStores,
IUserAccessOverview,
} from '../../../lib/types'; } from '../../../lib/types';
import FeatureToggleService from '../../../lib/services/feature-toggle-service'; import FeatureToggleService from '../../../lib/services/feature-toggle-service';
import ProjectService from '../../../lib/services/project-service'; import ProjectService from '../../../lib/services/project-service';
@ -1851,3 +1852,65 @@ test('remove group access should remove all project roles, while leaving root ro
expect(newAssignedPermissions.length).toBe(1); expect(newAssignedPermissions.length).toBe(1);
expect(newAssignedPermissions[0].permission).toBe(permissions.ADMIN); expect(newAssignedPermissions[0].permission).toBe(permissions.ADMIN);
}); });
test('access overview should have admin access and default project for admin user', async () => {
const email = 'a-person@places.com';
const { userStore } = stores;
const user = await userStore.insert({
name: 'Some User',
email,
});
await accessService.setUserRootRole(user.id, adminRole.id);
const accessOverView: IUserAccessOverview[] =
await accessService.getUserAccessOverview();
const userAccess = accessOverView.find(
(overviewRow) => overviewRow.userId == user.id,
)!;
expect(userAccess.userId).toBe(user.id);
expect(userAccess.rootRole).toBe('Admin');
expect(userAccess.accessibleProjects).toStrictEqual(['default']);
});
test('access overview should have group access for groups that they are in', async () => {
const email = 'a-nother-person@places.com';
const { userStore } = stores;
const user = await userStore.insert({
name: 'Some Other User',
email,
});
await accessService.setUserRootRole(user.id, adminRole.id);
const group = await stores.groupStore.create({
name: 'Test Group',
});
await stores.groupStore.addUsersToGroup(
group.id,
[
{
user: {
id: user.id,
},
},
],
'Admin',
);
const accessOverView: IUserAccessOverview[] =
await accessService.getUserAccessOverview();
const userAccess = accessOverView.find(
(overviewRow) => overviewRow.userId == user.id,
)!;
expect(userAccess.userId).toBe(user.id);
expect(userAccess.rootRole).toBe('Admin');
expect(userAccess.groups).toStrictEqual(['Test Group']);
});

View File

@ -10,7 +10,7 @@ import {
IUserWithProjectRoles, IUserWithProjectRoles,
} from '../../lib/types/stores/access-store'; } from '../../lib/types/stores/access-store';
import { IPermission } from 'lib/types/model'; import { IPermission } from 'lib/types/model';
import { IRoleStore } from 'lib/types'; import { IRoleStore, IUserAccessOverview } from 'lib/types';
import FakeRoleStore from './fake-role-store'; import FakeRoleStore from './fake-role-store';
import { PermissionRef } from 'lib/services/access-service'; import { PermissionRef } from 'lib/services/access-service';
@ -289,6 +289,10 @@ class AccessStoreMock implements IAccessStore {
removeGroupAccess(projectId: string, groupId: number): Promise<void> { removeGroupAccess(projectId: string, groupId: number): Promise<void> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
getUserAccessOverview(): Promise<IUserAccessOverview[]> {
throw new Error('Method not implemented.');
}
} }
module.exports = AccessStoreMock; module.exports = AccessStoreMock;