refactor(core): Remove roleId indirection (no-changelog) (#8413)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2024-01-24 13:38:57 +01:00
committed by GitHub
parent 1affebd85e
commit d6deceacde
139 changed files with 922 additions and 1684 deletions

View File

@@ -55,7 +55,7 @@ export class AuthController {
const preliminaryUser = await handleEmailLogin(email, password);
// if the user is an owner, continue with the login
if (
preliminaryUser?.globalRole?.name === 'owner' ||
preliminaryUser?.role === 'global:owner' ||
preliminaryUser?.settings?.allowSSOManualLogin
) {
user = preliminaryUser;
@@ -65,7 +65,7 @@ export class AuthController {
}
} else if (isLdapCurrentAuthenticationMethod()) {
const preliminaryUser = await handleEmailLogin(email, password);
if (preliminaryUser?.globalRole?.name === 'owner') {
if (preliminaryUser?.role === 'global:owner') {
user = preliminaryUser;
usedAuthenticationMethod = 'email';
} else {
@@ -138,7 +138,7 @@ export class AuthController {
}
try {
user = await this.userRepository.findOneOrFail({ where: {}, relations: ['globalRole'] });
user = await this.userRepository.findOneOrFail({ where: {} });
} catch (error) {
throw new InternalServerError(
'No users found in database - did you wipe the users table? Create at least one user.',

View File

@@ -1,8 +1,6 @@
import { Request } from 'express';
import { v4 as uuid } from 'uuid';
import config from '@/config';
import type { Role } from '@db/entities/Role';
import { RoleRepository } from '@db/repositories/role.repository';
import { SettingsRepository } from '@db/repositories/settings.repository';
import { UserRepository } from '@db/repositories/user.repository';
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
@@ -39,7 +37,6 @@ const tablesToTruncate = [
'installed_packages',
'installed_nodes',
'user',
'role',
'variables',
];
@@ -87,7 +84,6 @@ export class E2EController {
constructor(
license: License,
private readonly roleRepo: RoleRepository,
private readonly settingsRepo: SettingsRepository,
private readonly userRepo: UserRepository,
private readonly workflowRunner: ActiveWorkflowRunner,
@@ -148,7 +144,7 @@ export class E2EController {
private async truncateAll() {
for (const table of tablesToTruncate) {
try {
const { connection } = this.roleRepo.manager;
const { connection } = this.settingsRepo.manager;
await connection.query(
`DELETE FROM ${table}; DELETE FROM sqlite_sequence WHERE name=${table};`,
);
@@ -163,27 +159,12 @@ export class E2EController {
members: UserSetupPayload[],
admin: UserSetupPayload,
) {
const roles: Array<[Role['name'], Role['scope']]> = [
['owner', 'global'],
['member', 'global'],
['admin', 'global'],
['owner', 'workflow'],
['owner', 'credential'],
['user', 'credential'],
['editor', 'workflow'],
];
const [{ id: globalOwnerRoleId }, { id: globalMemberRoleId }, { id: globalAdminRoleId }] =
await this.roleRepo.save(
roles.map(([name, scope], index) => ({ name, scope, id: (index + 1).toString() })),
);
const instanceOwner = {
const instanceOwner = this.userRepo.create({
id: uuid(),
...owner,
password: await this.passwordUtility.hash(owner.password),
globalRoleId: globalOwnerRoleId,
};
role: 'global:owner',
});
if (owner?.mfaSecret && owner.mfaRecoveryCodes?.length) {
const { encryptedRecoveryCodes, encryptedSecret } =
@@ -192,12 +173,12 @@ export class E2EController {
instanceOwner.mfaRecoveryCodes = encryptedRecoveryCodes;
}
const adminUser = {
const adminUser = this.userRepo.create({
id: uuid(),
...admin,
password: await this.passwordUtility.hash(admin.password),
globalRoleId: globalAdminRoleId,
};
role: 'global:admin',
});
const users = [];
@@ -209,7 +190,7 @@ export class E2EController {
id: uuid(),
...payload,
password: await this.passwordUtility.hash(password),
globalRoleId: globalMemberRoleId,
role: 'global:member',
}),
);
}

View File

@@ -1,4 +1,5 @@
import { Response } from 'express';
import validator from 'validator';
import config from '@/config';
import { Authorized, NoAuthRequired, Post, RequireGlobalScope, RestController } from '@/decorators';
@@ -12,12 +13,11 @@ import { isSamlLicensedAndEnabled } from '@/sso/saml/samlHelpers';
import { PasswordUtility } from '@/services/password.utility';
import { PostHogClient } from '@/posthog';
import type { User } from '@/databases/entities/User';
import validator from 'validator';
import { UserRepository } from '@db/repositories/user.repository';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { InternalHooks } from '@/InternalHooks';
import { ExternalHooks } from '@/ExternalHooks';
import { UserRepository } from '@/databases/repositories/user.repository';
@Authorized()
@RestController('/invitations')
@@ -91,13 +91,13 @@ export class InvitationController {
);
}
if (invite.role && !['member', 'admin'].includes(invite.role)) {
if (invite.role && !['global:member', 'global:admin'].includes(invite.role)) {
throw new BadRequestError(
`Cannot invite user with invalid role: ${invite.role}. Please ensure all invitees' roles are either 'member' or 'admin'.`,
`Cannot invite user with invalid role: ${invite.role}. Please ensure all invitees' roles are either 'global:member' or 'global:admin'.`,
);
}
if (invite.role === 'admin' && !this.license.isAdvancedPermissionsLicensed()) {
if (invite.role === 'global:admin' && !this.license.isAdvancedPermissionsLicensed()) {
throw new UnauthorizedError(
'Cannot invite admin user without advanced permissions. Please upgrade to a license that includes this feature.',
);
@@ -106,7 +106,7 @@ export class InvitationController {
const attributes = req.body.map(({ email, role }) => ({
email,
role: role ?? 'member',
role: role ?? 'global:member',
}));
const { usersInvited, usersCreated } = await this.userService.inviteUsers(req.user, attributes);

View File

@@ -80,7 +80,6 @@ export class MeController {
await this.userService.update(userId, payload);
const user = await this.userRepository.findOneOrFail({
where: { id: userId },
relations: ['globalRole'],
});
this.logger.info('User updated successfully', { userId });
@@ -235,7 +234,6 @@ export class MeController {
const user = await this.userRepository.findOneOrFail({
select: ['settings'],
where: { id },
relations: ['globalRole'],
});
return user.settings;

View File

@@ -15,7 +15,7 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { InternalHooks } from '@/InternalHooks';
import { UserRepository } from '@/databases/repositories/user.repository';
@Authorized(['global', 'owner'])
@Authorized('global:owner')
@RestController('/owner')
export class OwnerController {
constructor(
@@ -35,7 +35,7 @@ export class OwnerController {
@Post('/setup')
async setupOwner(req: OwnerRequest.Post, res: Response) {
const { email, firstName, lastName, password } = req.body;
const { id: userId, globalRole } = req.user;
const { id: userId } = req.user;
if (config.getEnv('userManagement.isInstanceOwnerSetUp')) {
this.logger.debug(
@@ -65,17 +65,6 @@ export class OwnerController {
throw new BadRequestError('First and last names are mandatory');
}
// TODO: This check should be in a middleware outside this class
if (globalRole.scope === 'global' && globalRole.name !== 'owner') {
this.logger.debug(
'Request to claim instance ownership failed because user shell does not exist or has wrong role!',
{
userId,
},
);
throw new BadRequestError('Invalid request');
}
let owner = req.user;
Object.assign(owner, {

View File

@@ -1,22 +0,0 @@
import { License } from '@/License';
import { Get, RestController } from '@/decorators';
import { RoleService } from '@/services/role.service';
@RestController('/roles')
export class RoleController {
constructor(
private readonly roleService: RoleService,
private readonly license: License,
) {}
@Get('/')
async listRoles() {
return this.roleService.listRoles().map((role) => {
if (role.scope === 'global' && role.name === 'admin') {
return { ...role, isAvailable: this.license.isAdvancedPermissionsLicensed() };
}
return { ...role, isAvailable: true };
});
}
}

View File

@@ -23,7 +23,6 @@ import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
import { UserRepository } from '@db/repositories/user.repository';
import { plainToInstance } from 'class-transformer';
import { RoleService } from '@/services/role.service';
import { UserService } from '@/services/user.service';
import { listQueryMiddleware } from '@/middlewares';
import { Logger } from '@/Logger';
@@ -45,7 +44,6 @@ export class UsersController {
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
private readonly userRepository: UserRepository,
private readonly activeWorkflowRunner: ActiveWorkflowRunner,
private readonly roleService: RoleService,
private readonly userService: UserService,
) {}
@@ -70,7 +68,7 @@ export class UsersController {
}
if (filter?.isOwner) {
for (const user of publicUsers) delete user.globalRole;
for (const user of publicUsers) delete user.role;
}
// remove computed fields (unselectable)
@@ -92,12 +90,7 @@ export class UsersController {
async listUsers(req: ListQuery.Request) {
const { listQueryOptions } = req;
const globalOwner = await this.roleService.findGlobalOwnerRole();
const findManyOptions = await this.userRepository.toFindManyOptions(
listQueryOptions,
globalOwner.id,
);
const findManyOptions = await this.userRepository.toFindManyOptions(listQueryOptions);
const users = await this.userRepository.find(findManyOptions);
@@ -118,7 +111,6 @@ export class UsersController {
async getUserPasswordResetLink(req: UserRequest.PasswordResetLink) {
const user = await this.userRepository.findOneOrFail({
where: { id: req.params.id },
relations: ['globalRole'],
});
if (!user) {
throw new NotFoundError('User not found');
@@ -140,7 +132,6 @@ export class UsersController {
const user = await this.userRepository.findOneOrFail({
select: ['settings'],
where: { id },
relations: ['globalRole'],
});
return user.settings;
@@ -194,11 +185,6 @@ export class UsersController {
telemetryData.migration_user_id = transferId;
}
const [workflowOwnerRole, credentialOwnerRole] = await Promise.all([
this.roleService.findWorkflowOwnerRole(),
this.roleService.findCredentialOwnerRole(),
]);
if (transferId) {
const transferee = users.find((user) => user.id === transferId);
@@ -208,7 +194,7 @@ export class UsersController {
.getRepository(SharedWorkflow)
.find({
select: ['workflowId'],
where: { userId: userToDelete.id, roleId: workflowOwnerRole?.id },
where: { userId: userToDelete.id, role: 'workflow:owner' },
})
.then((sharedWorkflows) => sharedWorkflows.map(({ workflowId }) => workflowId));
@@ -223,7 +209,7 @@ export class UsersController {
// Transfer ownership of owned workflows
await transactionManager.update(
SharedWorkflow,
{ user: userToDelete, role: workflowOwnerRole },
{ user: userToDelete, role: 'workflow:owner' },
{ user: transferee },
);
@@ -234,7 +220,7 @@ export class UsersController {
.getRepository(SharedCredentials)
.find({
select: ['credentialsId'],
where: { userId: userToDelete.id, roleId: credentialOwnerRole?.id },
where: { userId: userToDelete.id, role: 'credential:owner' },
})
.then((sharedCredentials) => sharedCredentials.map(({ credentialsId }) => credentialsId));
@@ -249,7 +235,7 @@ export class UsersController {
// Transfer ownership of owned credentials
await transactionManager.update(
SharedCredentials,
{ user: userToDelete, role: credentialOwnerRole },
{ user: userToDelete, role: 'credential:owner' },
{ user: transferee },
);
@@ -271,11 +257,11 @@ export class UsersController {
const [ownedSharedWorkflows, ownedSharedCredentials] = await Promise.all([
this.sharedWorkflowRepository.find({
relations: ['workflow'],
where: { userId: userToDelete.id, roleId: workflowOwnerRole?.id },
where: { userId: userToDelete.id, role: 'workflow:owner' },
}),
this.sharedCredentialsRepository.find({
relations: ['credentials'],
where: { userId: userToDelete.id, roleId: credentialOwnerRole?.id },
where: { userId: userToDelete.id, role: 'credential:owner' },
}),
]);
@@ -318,23 +304,20 @@ export class UsersController {
const targetUser = await this.userRepository.findOne({
where: { id: req.params.id },
relations: ['globalRole'],
});
if (targetUser === null) {
throw new NotFoundError(NO_USER);
}
if (req.user.globalRole.name === 'admin' && targetUser.globalRole.name === 'owner') {
if (req.user.role === 'global:admin' && targetUser.role === 'global:owner') {
throw new UnauthorizedError(NO_ADMIN_ON_OWNER);
}
if (req.user.globalRole.name === 'owner' && targetUser.globalRole.name === 'owner') {
if (req.user.role === 'global:owner' && targetUser.role === 'global:owner') {
throw new UnauthorizedError(NO_OWNER_ON_OWNER);
}
const roleToSet = await this.roleService.findCached('global', payload.newRoleName);
await this.userService.update(targetUser.id, { globalRoleId: roleToSet.id });
await this.userService.update(targetUser.id, { role: payload.newRoleName });
void this.internalHooks.onUserRoleChange({
user: req.user,