1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-14 00:19:16 +01:00

feat: implement a way to purge scim users

This commit is contained in:
Simon Hornby 2025-02-03 09:28:35 +02:00
parent bf71d397a9
commit e203d26f64
No known key found for this signature in database
GPG Key ID: 57BE3E58BA999B19
6 changed files with 75 additions and 0 deletions

View File

@ -272,6 +272,10 @@ class UserStore implements IUserStore {
await this.activeUsers().del();
}
async deleteScimUsers(): Promise<void> {
await this.db(TABLE).whereNotNull('scim_id').del();
}
async count(): Promise<number> {
return this.activeUsers()
.count('*')

View File

@ -441,6 +441,26 @@ export default class UserAdminController extends Controller {
}),
],
});
//add a method to delete all scim users
this.route({
method: 'delete',
path: '/scim-users',
handler: this.deleteScimUsers,
permission: ADMIN,
middleware: [
openApiService.validPath({
tags: ['Users'],
operationId: 'deleteScimUsers',
summary: 'Delete all SCIM users',
description: 'Deletes all users managed by SCIM',
responses: {
200: emptyResponse,
...getStandardResponses(401, 403),
},
}),
],
});
}
async resetUserPassword(
@ -766,4 +786,10 @@ export default class UserAdminController extends Controller {
Boolean((await this.userService.getUser(id)).scimId)
);
}
async deleteScimUsers(req: IAuthRequest, res: Response): Promise<void> {
const { audit } = req;
await this.userService.deleteScimUsers(audit);
res.status(200).send();
}
}

View File

@ -24,6 +24,7 @@ import type SessionService from './session-service';
import type { IUnleashStores } from '../types/stores';
import PasswordUndefinedError from '../error/password-undefined';
import {
ScimUsersDeleted,
UserCreatedEvent,
UserDeletedEvent,
UserUpdatedEvent,
@ -398,6 +399,17 @@ class UserService {
);
}
async deleteScimUsers(auditUser: IAuditUser): Promise<void> {
await this.store.deleteScimUsers();
await this.eventService.storeEvent(
new ScimUsersDeleted({
data: null,
auditUser,
}),
);
}
async loginUser(
usernameOrEmail: string,
password: string,

View File

@ -217,6 +217,8 @@ export const RELEASE_PLAN_MILESTONE_STARTED =
'release-plan-milestone-started' as const;
export const USER_PREFERENCE_UPDATED = 'user-preference-updated' as const;
export const SCIM_USERS_DELETED = 'scim-users-deleted' as const;
export const SCIM_GROUPS_DELETED = 'scim-groups-deleted' as const;
export const IEventTypes = [
APPLICATION_CREATED,
@ -372,6 +374,8 @@ export const IEventTypes = [
RELEASE_PLAN_REMOVED,
RELEASE_PLAN_MILESTONE_STARTED,
USER_PREFERENCE_UPDATED,
SCIM_USERS_DELETED,
SCIM_GROUPS_DELETED,
] as const;
export type IEventType = (typeof IEventTypes)[number];
@ -1608,6 +1612,30 @@ export class UserDeletedEvent extends BaseEvent {
}
}
export class ScimUsersDeleted extends BaseEvent {
readonly data: any;
constructor(eventData: {
data: any;
auditUser: IAuditUser;
}) {
super(SCIM_USERS_DELETED, eventData.auditUser);
this.data = eventData.data;
}
}
export class ScimGroupsDeleted extends BaseEvent {
readonly data: any;
constructor(eventData: {
data: any;
auditUser: IAuditUser;
}) {
super(SCIM_GROUPS_DELETED, eventData.auditUser);
this.data = eventData.data;
}
}
export class TagTypeCreatedEvent extends BaseEvent {
readonly data: any;

View File

@ -40,4 +40,5 @@ export interface IUserStore extends Store<IUser, number> {
count(): Promise<number>;
countRecentlyDeleted(): Promise<number>;
countServiceAccounts(): Promise<number>;
deleteScimUsers(): Promise<void>;
}

View File

@ -159,6 +159,10 @@ class UserStoreMock implements IUserStore {
return Promise.resolve(undefined);
}
deleteScimUsers(): Promise<void> {
throw new Error('Method not implemented.');
}
upsert(user: ICreateUser): Promise<IUser> {
this.data.splice(this.data.findIndex((u) => u.email === user.email));
const userToReturn = {