mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-24 17:51:14 +02:00
fix: SSO auto create with SCIM tend to override each other (#10675)
## About the changes Having SCIM enabled with SAML and auto-create can generate issues with each protocol stepping into the other protocol's toes. This PR adds protection to avoid updating SCIM-managed users with SAML data (cause SCIM will override this data later). It also adds a new method in the store to check if we have cases where deleted_at is set but the email is not cleared, and there's no delete event in the audit log (we've found one case, and we believe it might be related to interoperability issues between SAML and SCIM)
This commit is contained in:
parent
8d03ce340d
commit
a628755506
@ -344,7 +344,7 @@ export class UserStore implements IUserStore {
|
||||
}
|
||||
|
||||
async getFirstUserDate(): Promise<Date | null> {
|
||||
const firstInstanceUser = await this.db('users')
|
||||
const firstInstanceUser = await this.db(TABLE)
|
||||
.select('created_at')
|
||||
.where('is_system', '=', false)
|
||||
.orderBy('created_at', 'asc')
|
||||
@ -352,4 +352,13 @@ export class UserStore implements IUserStore {
|
||||
|
||||
return firstInstanceUser ? firstInstanceUser.created_at : null;
|
||||
}
|
||||
|
||||
// this is temporary to find out how many cases we have
|
||||
async findDeletedUsersWithEmail(): Promise<User[]> {
|
||||
return this.db(TABLE)
|
||||
.select('*')
|
||||
.whereNotNull('deleted_at')
|
||||
.andWhereRaw('length(email) > 0')
|
||||
.then((rows) => rows.map(rowToUser));
|
||||
}
|
||||
}
|
||||
|
@ -506,9 +506,30 @@ export class UserService {
|
||||
|
||||
try {
|
||||
user = await this.store.getByQuery({ email });
|
||||
// Update user if autCreate is enabled.
|
||||
if (name && user.name !== name) {
|
||||
user = await this.store.update(user.id, { name, email });
|
||||
// Update user if not managed by scim
|
||||
if (name && user.name !== name && !user.scimId) {
|
||||
const currentRole = await this.accessService.getRootRoleForUser(
|
||||
user.id,
|
||||
);
|
||||
const updatedUser = await this.store.update(user.id, {
|
||||
name,
|
||||
email,
|
||||
});
|
||||
|
||||
await this.eventService.storeEvent(
|
||||
new UserUpdatedEvent({
|
||||
auditUser: SYSTEM_USER_AUDIT,
|
||||
preUser: {
|
||||
...user,
|
||||
rootRole: currentRole.id,
|
||||
},
|
||||
postUser: {
|
||||
...updatedUser,
|
||||
rootRole: currentRole.id,
|
||||
},
|
||||
}),
|
||||
);
|
||||
user = { ...user, ...updatedUser };
|
||||
}
|
||||
} catch (e) {
|
||||
// User does not exists. Create if 'autoCreate' is enabled
|
||||
|
@ -435,7 +435,8 @@ test('updating a user without an email should not strip the email', async () =>
|
||||
expect(updatedUser.email).toBe(email);
|
||||
});
|
||||
|
||||
test('should login and create user via SSO', async () => {
|
||||
describe('loginUserSSO', () => {
|
||||
test('should login and create user via SSO', async () => {
|
||||
const recordedEvents: Array<{ loginOrder: number }> = [];
|
||||
eventBus.on(USER_LOGIN, (data) => {
|
||||
recordedEvents.push(data);
|
||||
@ -454,9 +455,9 @@ test('should login and create user via SSO', async () => {
|
||||
expect(userWithRole.name).toBe('some');
|
||||
expect(userWithRole.rootRole).toBe(viewerRole.id);
|
||||
expect(recordedEvents).toEqual([{ loginOrder: 0 }]);
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw if rootRole is wrong via SSO', async () => {
|
||||
test('should throw if rootRole is wrong via SSO', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
await expect(
|
||||
@ -469,9 +470,9 @@ test('should throw if rootRole is wrong via SSO', async () => {
|
||||
).rejects.errorWithMessage(
|
||||
new BadDataError('Could not find rootRole=Member'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('should update user name when signing in via SSO', async () => {
|
||||
test('should update user name when signing in via SSO', async () => {
|
||||
const email = 'some@test.com';
|
||||
const originalUser = await userService.createUser(
|
||||
{
|
||||
@ -494,9 +495,14 @@ test('should update user name when signing in via SSO', async () => {
|
||||
expect(actualUser.email).toBe(email);
|
||||
expect(actualUser.name).toBe('New name!');
|
||||
expect(actualUser.rootRole).toBe(viewerRole.id);
|
||||
});
|
||||
const { events } = await eventService.getEvents();
|
||||
const updateEvent = events.find(
|
||||
(e) => e.data.id === originalUser.id && e.data.name === 'New name!',
|
||||
);
|
||||
expect(updateEvent).toBeDefined();
|
||||
});
|
||||
|
||||
test('should update name if it is different via SSO', async () => {
|
||||
test('should update name if it is different via SSO', async () => {
|
||||
const email = 'some@test.com';
|
||||
const originalUser = await userService.createUser(
|
||||
{
|
||||
@ -519,9 +525,9 @@ test('should update name if it is different via SSO', async () => {
|
||||
expect(actualUser.email).toBe(email);
|
||||
expect(actualUser.name).toBe('New name!');
|
||||
expect(actualUser.rootRole).toBe(viewerRole.id);
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw if autoCreate is false via SSO', async () => {
|
||||
test('should throw if autoCreate is false via SSO', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
await expect(
|
||||
@ -532,9 +538,9 @@ test('should throw if autoCreate is false via SSO', async () => {
|
||||
autoCreate: false,
|
||||
}),
|
||||
).rejects.errorWithMessage(new NotFoundError('No user found'));
|
||||
});
|
||||
});
|
||||
|
||||
test('should support a root role id when logging in and creating user via SSO', async () => {
|
||||
test('should support a root role id when logging in and creating user via SSO', async () => {
|
||||
const name = 'root-role-id';
|
||||
const email = `${name}@test.com`;
|
||||
const user = await userService.loginUserSSO({
|
||||
@ -549,9 +555,9 @@ test('should support a root role id when logging in and creating user via SSO',
|
||||
expect(user.name).toBe(name);
|
||||
expect(userWithRole.name).toBe(name);
|
||||
expect(userWithRole.rootRole).toBe(viewerRole.id);
|
||||
});
|
||||
});
|
||||
|
||||
test('should support a custom root role id when logging in and creating user via SSO', async () => {
|
||||
test('should support a custom root role id when logging in and creating user via SSO', async () => {
|
||||
const name = 'custom-root-role-id';
|
||||
const email = `${name}@test.com`;
|
||||
const user = await userService.loginUserSSO({
|
||||
@ -570,6 +576,36 @@ test('should support a custom root role id when logging in and creating user via
|
||||
const permissions = await accessService.getPermissionsForUser(user);
|
||||
expect(permissions).toHaveLength(1);
|
||||
expect(permissions[0].permission).toBe(CREATE_ADDON);
|
||||
});
|
||||
|
||||
test(`should not update the username if managed by SCIM`, async () => {
|
||||
const email = 'test-1@getunleash.io';
|
||||
const originalName = 'Original Name';
|
||||
const name = 'Updated Name';
|
||||
const createdUser = await userStore.insert({
|
||||
name: originalName,
|
||||
username: 'random-1234',
|
||||
email,
|
||||
});
|
||||
await db
|
||||
.rawDatabase('users')
|
||||
.update({
|
||||
scim_id: '123',
|
||||
})
|
||||
.where({ id: createdUser.id });
|
||||
|
||||
const user = await userService.loginUserSSO({
|
||||
email,
|
||||
autoCreate: true,
|
||||
name,
|
||||
});
|
||||
|
||||
expect(user.name).toBe(originalName);
|
||||
|
||||
// Fetch the user directly from the store to verify
|
||||
const storedUser = await userStore.get(user.id);
|
||||
expect(storedUser!.name).toBe(originalName);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Should not be able to use any of previous 5 passwords', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user