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:
parent
4b90fe7790
commit
10afbc8a9e
@ -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,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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[]>;
|
||||||
}
|
}
|
||||||
|
@ -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']);
|
||||||
|
});
|
||||||
|
6
src/test/fixtures/fake-access-store.ts
vendored
6
src/test/fixtures/fake-access-store.ts
vendored
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user