mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-17 01:17:29 +02:00
Syncing external groups with unleash group (#2194)
* Syncing groups * Add tests
This commit is contained in:
parent
e153eab2d1
commit
06ebe4fca0
@ -169,7 +169,7 @@ export default class GroupStore implements IGroupStore {
|
||||
return rowToGroup(row[0]);
|
||||
}
|
||||
|
||||
async addNewUsersToGroup(
|
||||
async addUsersToGroup(
|
||||
groupId: number,
|
||||
users: IGroupUserModel[],
|
||||
userName: string,
|
||||
@ -185,7 +185,7 @@ export default class GroupStore implements IGroupStore {
|
||||
return (transaction || this.db).batchInsert(T.GROUP_USER, rows);
|
||||
}
|
||||
|
||||
async deleteOldUsersFromGroup(
|
||||
async deleteUsersFromGroup(
|
||||
deletableUsers: IGroupUser[],
|
||||
transaction?: Transaction,
|
||||
): Promise<void> {
|
||||
@ -205,8 +205,65 @@ export default class GroupStore implements IGroupStore {
|
||||
userName: string,
|
||||
): Promise<void> {
|
||||
await this.db.transaction(async (tx) => {
|
||||
await this.addNewUsersToGroup(groupId, newUsers, userName, tx);
|
||||
await this.deleteOldUsersFromGroup(deletableUsers, tx);
|
||||
await this.addUsersToGroup(groupId, newUsers, userName, tx);
|
||||
await this.deleteUsersFromGroup(deletableUsers, tx);
|
||||
});
|
||||
}
|
||||
|
||||
async getNewGroupsForExternalUser(
|
||||
userId: number,
|
||||
externalGroups: string[],
|
||||
): Promise<IGroup[]> {
|
||||
const rows = await this.db(`${T.GROUPS} as g`)
|
||||
.leftJoin(`${T.GROUP_USER} as gs`, function () {
|
||||
this.on('g.id', 'gs.group_id').andOnVal(
|
||||
'gs.user_id',
|
||||
'=',
|
||||
userId,
|
||||
);
|
||||
})
|
||||
.where('gs.user_id', null)
|
||||
.whereRaw('mappings_sso \\?| :groups', { groups: externalGroups });
|
||||
return rows.map(rowToGroup);
|
||||
}
|
||||
|
||||
async addUserToGroups(
|
||||
userId: number,
|
||||
groupIds: number[],
|
||||
createdBy?: string,
|
||||
): Promise<void> {
|
||||
const rows = groupIds.map((groupId) => {
|
||||
return {
|
||||
group_id: groupId,
|
||||
user_id: userId,
|
||||
created_by: createdBy,
|
||||
};
|
||||
});
|
||||
return this.db.batchInsert(T.GROUP_USER, rows);
|
||||
}
|
||||
|
||||
async getOldGroupsForExternalUser(
|
||||
userId: number,
|
||||
externalGroups: string[],
|
||||
): Promise<IGroupUser[]> {
|
||||
const rows = await this.db(`${T.GROUP_USER} as gu`)
|
||||
.leftJoin(`${T.GROUPS} as g`, 'g.id', 'gu.group_id')
|
||||
.whereNotIn(
|
||||
'g.id',
|
||||
this.db(T.GROUPS)
|
||||
.select('id')
|
||||
.whereRaw('mappings_sso \\?| :groups', {
|
||||
groups: externalGroups,
|
||||
}),
|
||||
)
|
||||
.where('gu.user_id', userId);
|
||||
return rows.map(rowToGroupUser);
|
||||
}
|
||||
|
||||
async getGroupsForUser(userId: number): Promise<Group[]> {
|
||||
const rows = await this.db(T.GROUPS)
|
||||
.leftJoin(T.GROUP_USER, 'groups.id', 'group_user.group_id')
|
||||
.where('user_id', userId);
|
||||
return rows.map(rowToGroup);
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ export class GroupService {
|
||||
|
||||
const newGroup = await this.groupStore.create(group);
|
||||
|
||||
await this.groupStore.addNewUsersToGroup(
|
||||
await this.groupStore.addUsersToGroup(
|
||||
newGroup.id,
|
||||
group.users,
|
||||
userName,
|
||||
@ -215,4 +215,27 @@ export class GroupService {
|
||||
});
|
||||
return { ...group, users: finalUsers };
|
||||
}
|
||||
|
||||
async syncExternalGroups(
|
||||
userId: number,
|
||||
externalGroups: string[],
|
||||
): Promise<void> {
|
||||
let newGroups = await this.groupStore.getNewGroupsForExternalUser(
|
||||
userId,
|
||||
externalGroups,
|
||||
);
|
||||
await this.groupStore.addUserToGroups(
|
||||
userId,
|
||||
newGroups.map((g) => g.id),
|
||||
);
|
||||
let oldGroups = await this.groupStore.getOldGroupsForExternalUser(
|
||||
userId,
|
||||
externalGroups,
|
||||
);
|
||||
await this.groupStore.deleteUsersFromGroup(oldGroups);
|
||||
}
|
||||
|
||||
async getGroupsForUser(userId: number): Promise<IGroup[]> {
|
||||
return this.groupStore.getGroupsForUser(userId);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Store } from './store';
|
||||
import {
|
||||
import Group, {
|
||||
IGroup,
|
||||
IGroupModel,
|
||||
IGroupProject,
|
||||
@ -15,6 +15,20 @@ export interface IStoreGroup {
|
||||
}
|
||||
|
||||
export interface IGroupStore extends Store<IGroup, number> {
|
||||
getGroupsForUser(userId: number): Promise<Group[]>;
|
||||
getOldGroupsForExternalUser(
|
||||
userId: number,
|
||||
externalGroups: string[],
|
||||
): Promise<IGroupUser[]>;
|
||||
addUserToGroups(
|
||||
userId: number,
|
||||
groupIds: number[],
|
||||
createdBy?: string,
|
||||
): Promise<void>;
|
||||
getNewGroupsForExternalUser(
|
||||
userId: number,
|
||||
externalGroups: string[],
|
||||
): Promise<IGroup[]>;
|
||||
getGroupProjects(groupIds: number[]): Promise<IGroupProject[]>;
|
||||
|
||||
getProjectGroupRoles(projectId: string): Promise<IGroupRole[]>;
|
||||
@ -29,13 +43,13 @@ export interface IGroupStore extends Store<IGroup, number> {
|
||||
userName: string,
|
||||
): Promise<void>;
|
||||
|
||||
deleteOldUsersFromGroup(deletableUsers: IGroupUser[]): Promise<void>;
|
||||
deleteUsersFromGroup(deletableUsers: IGroupUser[]): Promise<void>;
|
||||
|
||||
update(group: IGroupModel): Promise<IGroup>;
|
||||
|
||||
getAllUsersByGroups(groupIds: number[]): Promise<IGroupUser[]>;
|
||||
|
||||
addNewUsersToGroup(
|
||||
addUsersToGroup(
|
||||
groupId: number,
|
||||
users: IGroupUserModel[],
|
||||
userName: string,
|
||||
|
@ -881,7 +881,7 @@ test('Should be allowed move feature toggle to project when given access through
|
||||
description: '',
|
||||
});
|
||||
|
||||
await groupStore.addNewUsersToGroup(
|
||||
await groupStore.addUsersToGroup(
|
||||
groupWithProjectAccess.id,
|
||||
[{ user: viewerUser }],
|
||||
'Admin',
|
||||
@ -918,7 +918,7 @@ test('Should not lose user role access when given permissions from a group', asy
|
||||
description: '',
|
||||
});
|
||||
|
||||
await groupStore.addNewUsersToGroup(
|
||||
await groupStore.addUsersToGroup(
|
||||
groupWithNoAccess.id,
|
||||
[{ user: user }],
|
||||
'Admin',
|
||||
@ -967,13 +967,13 @@ test('Should allow user to take multiple group roles and have expected permissio
|
||||
description: '',
|
||||
});
|
||||
|
||||
await groupStore.addNewUsersToGroup(
|
||||
await groupStore.addUsersToGroup(
|
||||
groupWithCreateAccess.id,
|
||||
[{ user: viewerUser }],
|
||||
'Admin',
|
||||
);
|
||||
|
||||
await groupStore.addNewUsersToGroup(
|
||||
await groupStore.addUsersToGroup(
|
||||
groupWithDeleteAccess.id,
|
||||
[{ user: viewerUser }],
|
||||
'Admin',
|
||||
|
71
src/test/e2e/services/group-service.e2e.test.ts
Normal file
71
src/test/e2e/services/group-service.e2e.test.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import dbInit, { ITestDb } from '../helpers/database-init';
|
||||
import getLogger from '../../fixtures/no-logger';
|
||||
import { createTestConfig } from '../../config/test-config';
|
||||
import { GroupService } from '../../../lib/services/group-service';
|
||||
|
||||
let stores;
|
||||
let db: ITestDb;
|
||||
|
||||
let groupService: GroupService;
|
||||
let user;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('group_service_serial', getLogger);
|
||||
stores = db.stores;
|
||||
user = await stores.userStore.insert({
|
||||
name: 'Some Name',
|
||||
email: 'test@getunleash.io',
|
||||
});
|
||||
const config = createTestConfig({
|
||||
getLogger,
|
||||
});
|
||||
groupService = new GroupService(stores, config);
|
||||
|
||||
await stores.groupStore.create({
|
||||
name: 'dev_group',
|
||||
description: 'dev_group',
|
||||
mappingsSSO: ['dev'],
|
||||
});
|
||||
await stores.groupStore.create({
|
||||
name: 'maintainer_group',
|
||||
description: 'maintainer_group',
|
||||
mappingsSSO: ['maintainer'],
|
||||
});
|
||||
|
||||
await stores.groupStore.create({
|
||||
name: 'admin_group',
|
||||
description: 'admin_group',
|
||||
mappingsSSO: ['admin'],
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await db.destroy();
|
||||
});
|
||||
|
||||
afterEach(async () => {});
|
||||
|
||||
test('should have three group', async () => {
|
||||
const project = await groupService.getAll();
|
||||
expect(project.length).toBe(3);
|
||||
});
|
||||
|
||||
test('should add person to 2 groups', async () => {
|
||||
await groupService.syncExternalGroups(user.id, ['dev', 'maintainer']);
|
||||
const groups = await groupService.getGroupsForUser(user.id);
|
||||
expect(groups.length).toBe(2);
|
||||
});
|
||||
|
||||
test('should remove person from one group', async () => {
|
||||
await groupService.syncExternalGroups(user.id, ['maintainer']);
|
||||
const groups = await groupService.getGroupsForUser(user.id);
|
||||
expect(groups.length).toBe(1);
|
||||
expect(groups[0].name).toEqual('maintainer_group');
|
||||
});
|
||||
|
||||
test('should add person to completely new group with new name', async () => {
|
||||
await groupService.syncExternalGroups(user.id, ['dev']);
|
||||
const groups = await groupService.getGroupsForUser(user.id);
|
||||
expect(groups.length).toBe(1);
|
||||
expect(groups[0].name).toEqual('dev_group');
|
||||
});
|
32
src/test/fixtures/fake-group-store.ts
vendored
32
src/test/fixtures/fake-group-store.ts
vendored
@ -1,5 +1,5 @@
|
||||
import { IGroupStore, IStoreGroup } from '../../lib/types/stores/group-store';
|
||||
import {
|
||||
import Group, {
|
||||
IGroup,
|
||||
IGroupModel,
|
||||
IGroupProject,
|
||||
@ -42,7 +42,7 @@ export default class FakeGroupStore implements IGroupStore {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
addNewUsersToGroup(
|
||||
addUsersToGroup(
|
||||
id: number,
|
||||
users: IGroupUserModel[],
|
||||
userName: string,
|
||||
@ -54,7 +54,7 @@ export default class FakeGroupStore implements IGroupStore {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
deleteOldUsersFromGroup(deletableUsers: IGroupUser[]): Promise<void> {
|
||||
deleteUsersFromGroup(deletableUsers: IGroupUser[]): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
@ -83,4 +83,30 @@ export default class FakeGroupStore implements IGroupStore {
|
||||
getGroupProjects(groupIds: number[]): Promise<IGroupProject[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getNewGroupsForExternalUser(
|
||||
userId: number,
|
||||
externalGroups: string[],
|
||||
): Promise<IGroup[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
addUserToGroups(
|
||||
userId: number,
|
||||
groupIds: number[],
|
||||
createdBy?: string,
|
||||
): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getOldGroupsForExternalUser(
|
||||
userId: number,
|
||||
externalGroups: string[],
|
||||
): Promise<IGroupUser[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getGroupsForUser(userId: number): Promise<Group[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user