1
0
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:
sjaanus 2022-10-14 12:08:14 +02:00 committed by GitHub
parent e153eab2d1
commit 06ebe4fca0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 206 additions and 15 deletions

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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,

View File

@ -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',

View 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');
});

View File

@ -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.');
}
}