diff --git a/packages/cli/src/Db.ts b/packages/cli/src/Db.ts index bc83c12b46..ead4d247ab 100644 --- a/packages/cli/src/Db.ts +++ b/packages/cli/src/Db.ts @@ -169,23 +169,27 @@ export async function init(testConnectionOptions?: ConnectionOptions): Promise { * Return the user role to be assigned to LDAP users */ export const getLdapUserRole = async (): Promise => { - return Container.get(RoleRepository).findGlobalMemberRoleOrFail(); + return Container.get(RoleService).findGlobalMemberRole(); }; /** diff --git a/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts b/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts index 41460ccb7c..b15640e160 100644 --- a/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts +++ b/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts @@ -5,11 +5,11 @@ import type { ICredentialsDb } from '@/Interfaces'; import { CredentialsEntity } from '@db/entities/CredentialsEntity'; import { SharedCredentials } from '@db/entities/SharedCredentials'; import type { User } from '@db/entities/User'; -import { RoleRepository } from '@db/repositories'; import { ExternalHooks } from '@/ExternalHooks'; import type { IDependency, IJsonSchema } from '../../../types'; import type { CredentialRequest } from '@/requests'; import { Container } from 'typedi'; +import { RoleService } from '@/services/role.service'; export async function getCredentials(credentialId: string): Promise { return Db.collections.Credentials.findOneBy({ id: credentialId }); @@ -58,7 +58,7 @@ export async function saveCredential( user: User, encryptedData: ICredentialsDb, ): Promise { - const role = await Container.get(RoleRepository).findCredentialOwnerRoleOrFail(); + const role = await Container.get(RoleService).findCredentialOwnerRole(); await Container.get(ExternalHooks).run('credentials.create', [encryptedData]); diff --git a/packages/cli/src/PublicApi/v1/handlers/users/users.service.ts b/packages/cli/src/PublicApi/v1/handlers/users/users.service.ts deleted file mode 100644 index bccab33bb9..0000000000 --- a/packages/cli/src/PublicApi/v1/handlers/users/users.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Container } from 'typedi'; -import { RoleRepository } from '@db/repositories'; -import type { Role } from '@db/entities/Role'; - -export async function getWorkflowOwnerRole(): Promise { - return Container.get(RoleRepository).findWorkflowOwnerRoleOrFail(); -} diff --git a/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts b/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts index d428b63966..b4a280779c 100644 --- a/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts +++ b/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts @@ -11,7 +11,6 @@ import { addNodeIds, replaceInvalidCredentials } from '@/WorkflowHelpers'; import type { WorkflowRequest } from '../../../types'; import { authorize, validCursor } from '../../shared/middlewares/global.middleware'; import { encodeNextCursor } from '../../shared/services/pagination.service'; -import { getWorkflowOwnerRole } from '../users/users.service'; import { getWorkflowById, getSharedWorkflow, @@ -26,6 +25,7 @@ import { } from './workflows.service'; import { WorkflowsService } from '@/workflows/workflows.services'; import { InternalHooks } from '@/InternalHooks'; +import { RoleService } from '@/services/role.service'; export = { createWorkflow: [ @@ -39,7 +39,7 @@ export = { addNodeIds(workflow); - const role = await getWorkflowOwnerRole(); + const role = await Container.get(RoleService).findWorkflowOwnerRole(); const createdWorkflow = await createWorkflow(workflow, req.user, role); diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 01110021c0..9d1f58cb31 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -170,6 +170,7 @@ import { SourceControlController } from '@/environments/sourceControl/sourceCont import { ExecutionRepository } from '@db/repositories'; import type { ExecutionEntity } from '@db/entities/ExecutionEntity'; import { JwtService } from './services/jwt.service'; +import { RoleService } from './services/role.service'; const exec = promisify(callbackExec); @@ -496,6 +497,7 @@ export class Server extends AbstractServer { logger, postHog, jwtService, + roleService: Container.get(RoleService), }), Container.get(SamlController), Container.get(SourceControlController), diff --git a/packages/cli/src/UserManagement/PermissionChecker.ts b/packages/cli/src/UserManagement/PermissionChecker.ts index 8ea5080ea0..493a0859f9 100644 --- a/packages/cli/src/UserManagement/PermissionChecker.ts +++ b/packages/cli/src/UserManagement/PermissionChecker.ts @@ -9,11 +9,12 @@ import { In } from 'typeorm'; import * as Db from '@/Db'; import config from '@/config'; import type { SharedCredentials } from '@db/entities/SharedCredentials'; -import { getRoleId, isSharingEnabled } from './UserManagementHelper'; +import { isSharingEnabled } from './UserManagementHelper'; import { WorkflowsService } from '@/workflows/workflows.services'; import { UserService } from '@/user/user.service'; import { OwnershipService } from '@/services/ownership.service'; import Container from 'typedi'; +import { RoleService } from '@/services/role.service'; export class PermissionChecker { /** @@ -54,8 +55,9 @@ export class PermissionChecker { const credentialsWhere: FindOptionsWhere = { userId: In(workflowUserIds) }; if (!isSharingEnabled()) { + const role = await Container.get(RoleService).findCredentialOwnerRole(); // If credential sharing is not enabled, get only credentials owned by this user - credentialsWhere.roleId = await getRoleId('credential', 'owner'); + credentialsWhere.roleId = role.id; } const credentialSharings = await Db.collections.SharedCredentials.find({ diff --git a/packages/cli/src/UserManagement/UserManagementHelper.ts b/packages/cli/src/UserManagement/UserManagementHelper.ts index fa80d8f2ff..14efff5e16 100644 --- a/packages/cli/src/UserManagement/UserManagementHelper.ts +++ b/packages/cli/src/UserManagement/UserManagementHelper.ts @@ -7,12 +7,11 @@ import * as ResponseHelper from '@/ResponseHelper'; import type { CurrentUser, PublicUser, WhereClause } from '@/Interfaces'; import type { User } from '@db/entities/User'; import { MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH } from '@db/entities/User'; -import type { Role } from '@db/entities/Role'; -import { RoleRepository } from '@db/repositories'; import config from '@/config'; import { License } from '@/License'; import { getWebhookBaseUrl } from '@/WebhookHelpers'; import type { PostHogClient } from '@/posthog'; +import { RoleService } from '@/services/role.service'; export function isEmailSetUp(): boolean { const smtp = config.getEnv('userManagement.emails.mode') === 'smtp'; @@ -27,22 +26,15 @@ export function isSharingEnabled(): boolean { return Container.get(License).isSharingEnabled(); } -export async function getRoleId(scope: Role['scope'], name: Role['name']): Promise { - return Container.get(RoleRepository) - .findRoleOrFail(scope, name) - .then((role) => role.id); -} +export async function getInstanceOwner() { + const globalOwnerRole = await Container.get(RoleService).findGlobalOwnerRole(); -export async function getInstanceOwner(): Promise { - const ownerRoleId = await getRoleId('global', 'owner'); - - const owner = await Db.collections.User.findOneOrFail({ + return Db.collections.User.findOneOrFail({ relations: ['globalRole'], where: { - globalRoleId: ownerRoleId, + globalRoleId: globalOwnerRole.id, }, }); - return owner; } /** diff --git a/packages/cli/src/WorkflowHelpers.ts b/packages/cli/src/WorkflowHelpers.ts index 94b792f412..b7afe3c5c0 100644 --- a/packages/cli/src/WorkflowHelpers.ts +++ b/packages/cli/src/WorkflowHelpers.ts @@ -30,13 +30,14 @@ import { WorkflowRunner } from '@/WorkflowRunner'; import config from '@/config'; import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; import type { User } from '@db/entities/User'; -import { RoleRepository } from '@db/repositories'; import omit from 'lodash/omit'; import { PermissionChecker } from './UserManagement/PermissionChecker'; import { isWorkflowIdValid } from './utils'; import { UserService } from './user/user.service'; import type { SharedWorkflow } from '@db/entities/SharedWorkflow'; import type { RoleNames } from '@db/entities/Role'; +import { RoleService } from './services/role.service'; +import { RoleRepository } from './databases/repositories'; import { VariablesService } from './environments/variables/variables.service'; const ERROR_TRIGGER_TYPE = config.getEnv('nodes.errorTriggerType'); @@ -378,10 +379,13 @@ export async function getSharedWorkflowIds(user: User, roles?: RoleNames[]): Pro where.userId = user.id; } if (roles?.length) { - const roleIds = await Db.collections.Role.find({ - select: ['id'], - where: { name: In(roles), scope: 'workflow' }, - }).then((data) => data.map(({ id }) => id)); + const roleIds = await Container.get(RoleRepository) + .find({ + select: ['id'], + where: { name: In(roles), scope: 'workflow' }, + }) + .then((role) => role.map(({ id }) => id)); + where.roleId = In(roleIds); } const sharedWorkflows = await Db.collections.SharedWorkflow.find({ @@ -398,7 +402,7 @@ export async function isBelowOnboardingThreshold(user: User): Promise { let belowThreshold = true; const skippedTypes = ['n8n-nodes-base.start', 'n8n-nodes-base.stickyNote']; - const workflowOwnerRole = await Container.get(RoleRepository).findWorkflowOwnerRole(); + const workflowOwnerRole = await Container.get(RoleService).findWorkflowOwnerRole(); const ownedWorkflowsIds = await Db.collections.SharedWorkflow.find({ where: { userId: user.id, diff --git a/packages/cli/src/commands/import/credentials.ts b/packages/cli/src/commands/import/credentials.ts index 4975cb66df..6eb87aebd2 100644 --- a/packages/cli/src/commands/import/credentials.ts +++ b/packages/cli/src/commands/import/credentials.ts @@ -9,11 +9,11 @@ import type { User } from '@db/entities/User'; import { SharedCredentials } from '@db/entities/SharedCredentials'; import type { Role } from '@db/entities/Role'; import { CredentialsEntity } from '@db/entities/CredentialsEntity'; -import { RoleRepository } from '@db/repositories'; import { disableAutoGeneratedIds } from '@db/utils/commandHelpers'; import { BaseCommand, UM_FIX_INSTRUCTION } from '../BaseCommand'; import type { ICredentialsEncrypted } from 'n8n-workflow'; import { jsonParse } from 'n8n-workflow'; +import { RoleService } from '@/services/role.service'; export class ImportCredentialsCommand extends BaseCommand { static description = 'Import credentials'; @@ -147,7 +147,7 @@ export class ImportCredentialsCommand extends BaseCommand { } private async initOwnerCredentialRole() { - const ownerCredentialRole = await Container.get(RoleRepository).findCredentialOwnerRole(); + const ownerCredentialRole = await Container.get(RoleService).findCredentialOwnerRole(); if (!ownerCredentialRole) { throw new Error(`Failed to find owner credential role. ${UM_FIX_INSTRUCTION}`); @@ -170,7 +170,7 @@ export class ImportCredentialsCommand extends BaseCommand { } private async getOwner() { - const ownerGlobalRole = await Container.get(RoleRepository).findGlobalOwnerRole(); + const ownerGlobalRole = await Container.get(RoleService).findGlobalOwnerRole(); const owner = ownerGlobalRole && diff --git a/packages/cli/src/commands/import/workflow.ts b/packages/cli/src/commands/import/workflow.ts index 4f85151ab3..f3c54f793b 100644 --- a/packages/cli/src/commands/import/workflow.ts +++ b/packages/cli/src/commands/import/workflow.ts @@ -12,12 +12,12 @@ import { WorkflowEntity } from '@db/entities/WorkflowEntity'; import type { Role } from '@db/entities/Role'; import type { User } from '@db/entities/User'; import { setTagsForImport } from '@/TagHelpers'; -import { RoleRepository } from '@db/repositories'; import { disableAutoGeneratedIds } from '@db/utils/commandHelpers'; import type { ICredentialsDb, IWorkflowToImport } from '@/Interfaces'; import { replaceInvalidCredentials } from '@/WorkflowHelpers'; import { BaseCommand, UM_FIX_INSTRUCTION } from '../BaseCommand'; import { generateNanoId } from '@db/utils/generators'; +import { RoleService } from '@/services/role.service'; function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IWorkflowToImport[] { if (!Array.isArray(workflows)) { @@ -208,7 +208,7 @@ export class ImportWorkflowsCommand extends BaseCommand { } private async initOwnerWorkflowRole() { - const ownerWorkflowRole = await Container.get(RoleRepository).findWorkflowOwnerRole(); + const ownerWorkflowRole = await Container.get(RoleService).findWorkflowOwnerRole(); if (!ownerWorkflowRole) { throw new Error(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`); @@ -231,7 +231,7 @@ export class ImportWorkflowsCommand extends BaseCommand { } private async getOwner() { - const ownerGlobalRole = await Container.get(RoleRepository).findGlobalOwnerRole(); + const ownerGlobalRole = await Container.get(RoleService).findGlobalOwnerRole(); const owner = ownerGlobalRole && diff --git a/packages/cli/src/commands/user-management/reset.ts b/packages/cli/src/commands/user-management/reset.ts index 09a5e06080..64b1be3cf3 100644 --- a/packages/cli/src/commands/user-management/reset.ts +++ b/packages/cli/src/commands/user-management/reset.ts @@ -3,8 +3,8 @@ import { Not } from 'typeorm'; import * as Db from '@/Db'; import type { CredentialsEntity } from '@db/entities/CredentialsEntity'; import { User } from '@db/entities/User'; -import { RoleRepository } from '@db/repositories'; import { BaseCommand } from '../BaseCommand'; +import { RoleService } from '@/services/role.service'; const defaultUserProps = { firstName: null, @@ -21,8 +21,8 @@ export class Reset extends BaseCommand { async run(): Promise { const owner = await this.getInstanceOwner(); - const ownerWorkflowRole = await Container.get(RoleRepository).findWorkflowOwnerRoleOrFail(); - const ownerCredentialRole = await Container.get(RoleRepository).findCredentialOwnerRoleOrFail(); + const ownerWorkflowRole = await Container.get(RoleService).findWorkflowOwnerRole(); + const ownerCredentialRole = await Container.get(RoleService).findCredentialOwnerRole(); await Db.collections.SharedWorkflow.update( { userId: Not(owner.id), roleId: ownerWorkflowRole.id }, @@ -60,7 +60,7 @@ export class Reset extends BaseCommand { } async getInstanceOwner(): Promise { - const globalRole = await Container.get(RoleRepository).findGlobalOwnerRoleOrFail(); + const globalRole = await Container.get(RoleService).findGlobalOwnerRole(); const owner = await Db.collections.User.findOneBy({ globalRoleId: globalRole.id }); diff --git a/packages/cli/src/controllers/e2e.controller.ts b/packages/cli/src/controllers/e2e.controller.ts index 3e0a5fae3b..1160c3f948 100644 --- a/packages/cli/src/controllers/e2e.controller.ts +++ b/packages/cli/src/controllers/e2e.controller.ts @@ -130,7 +130,7 @@ export class E2EController { ]; const [{ id: globalOwnerRoleId }, { id: globalMemberRoleId }] = await this.roleRepo.save( - roles.map(([name, scope], index) => ({ name, scope, id: index.toString() })), + roles.map(([name, scope], index) => ({ name, scope, id: (index + 1).toString() })), ); const users = []; @@ -151,6 +151,8 @@ export class E2EController { ); } + console.log('users', users); + await this.userRepo.insert(users); await this.settingsRepo.update( diff --git a/packages/cli/src/controllers/users.controller.ts b/packages/cli/src/controllers/users.controller.ts index cef9e9fa71..4af36805f6 100644 --- a/packages/cli/src/controllers/users.controller.ts +++ b/packages/cli/src/controllers/users.controller.ts @@ -39,7 +39,6 @@ import { AuthIdentity } from '@db/entities/AuthIdentity'; import type { PostHogClient } from '@/posthog'; import { isSamlLicensedAndEnabled } from '../sso/saml/samlHelpers'; import type { - RoleRepository, SharedCredentialsRepository, SharedWorkflowRepository, UserRepository, @@ -50,6 +49,7 @@ import { License } from '@/License'; import { Container } from 'typedi'; import { RESPONSE_ERROR_MESSAGES } from '@/constants'; import type { JwtService } from '@/services/jwt.service'; +import type { RoleService } from '@/services/role.service'; @Authorized(['global', 'owner']) @RestController('/users') @@ -64,8 +64,6 @@ export class UsersController { private userRepository: UserRepository; - private roleRepository: RoleRepository; - private sharedCredentialsRepository: SharedCredentialsRepository; private sharedWorkflowRepository: SharedWorkflowRepository; @@ -78,6 +76,8 @@ export class UsersController { private postHog?: PostHogClient; + private roleService: RoleService; + constructor({ config, logger, @@ -88,32 +88,31 @@ export class UsersController { mailer, jwtService, postHog, + roleService, }: { config: Config; logger: ILogger; externalHooks: IExternalHooksClass; internalHooks: IInternalHooksClass; - repositories: Pick< - IDatabaseCollections, - 'User' | 'Role' | 'SharedCredentials' | 'SharedWorkflow' - >; + repositories: Pick; activeWorkflowRunner: ActiveWorkflowRunner; mailer: UserManagementMailer; jwtService: JwtService; postHog?: PostHogClient; + roleService: RoleService; }) { this.config = config; this.logger = logger; this.externalHooks = externalHooks; this.internalHooks = internalHooks; this.userRepository = repositories.User; - this.roleRepository = repositories.Role; this.sharedCredentialsRepository = repositories.SharedCredentials; this.sharedWorkflowRepository = repositories.SharedWorkflow; this.activeWorkflowRunner = activeWorkflowRunner; this.mailer = mailer; this.jwtService = jwtService; this.postHog = postHog; + this.roleService = roleService; } /** @@ -176,7 +175,7 @@ export class UsersController { createUsers[invite.email.toLowerCase()] = null; }); - const role = await this.roleRepository.findGlobalMemberRole(); + const role = await this.roleService.findGlobalMemberRole(); if (!role) { this.logger.error( @@ -469,8 +468,8 @@ export class UsersController { } const [workflowOwnerRole, credentialOwnerRole] = await Promise.all([ - this.roleRepository.findWorkflowOwnerRole(), - this.roleRepository.findCredentialOwnerRole(), + this.roleService.findWorkflowOwnerRole(), + this.roleService.findCredentialOwnerRole(), ]); if (transferId) { diff --git a/packages/cli/src/credentials/credentials.service.ee.ts b/packages/cli/src/credentials/credentials.service.ee.ts index 29e2630604..4e9bb0a9c5 100644 --- a/packages/cli/src/credentials/credentials.service.ee.ts +++ b/packages/cli/src/credentials/credentials.service.ee.ts @@ -1,13 +1,14 @@ import type { DeleteResult, EntityManager, FindOptionsWhere } from 'typeorm'; import { In, Not } from 'typeorm'; import * as Db from '@/Db'; -import { RoleService } from '@/role/role.service'; import { CredentialsEntity } from '@db/entities/CredentialsEntity'; import { SharedCredentials } from '@db/entities/SharedCredentials'; import type { User } from '@db/entities/User'; import { UserService } from '@/user/user.service'; import { CredentialsService } from './credentials.service'; import type { CredentialWithSharings } from './credentials.types'; +import { RoleService } from '@/services/role.service'; +import Container from 'typedi'; export class EECredentialsService extends CredentialsService { static async isOwned( @@ -77,10 +78,8 @@ export class EECredentialsService extends CredentialsService { credential: CredentialsEntity, shareWithIds: string[], ): Promise { - const [users, role] = await Promise.all([ - UserService.getByIds(transaction, shareWithIds), - RoleService.trxGet(transaction, { scope: 'credential', name: 'user' }), - ]); + const users = await UserService.getByIds(transaction, shareWithIds); + const role = await Container.get(RoleService).findCredentialUserRole(); const newSharedCredentials = users .filter((user) => !user.isPending) diff --git a/packages/cli/src/credentials/credentials.service.ts b/packages/cli/src/credentials/credentials.service.ts index 50d445ec0d..94be5b9fb6 100644 --- a/packages/cli/src/credentials/credentials.service.ts +++ b/packages/cli/src/credentials/credentials.service.ts @@ -21,9 +21,9 @@ import { SharedCredentials } from '@db/entities/SharedCredentials'; import { validateEntity } from '@/GenericHelpers'; import { ExternalHooks } from '@/ExternalHooks'; import type { User } from '@db/entities/User'; -import { RoleRepository } from '@db/repositories'; import type { CredentialRequest } from '@/requests'; import { CredentialTypes } from '@/CredentialTypes'; +import { RoleService } from '@/services/role.service'; export class CredentialsService { static async get( @@ -221,7 +221,7 @@ export class CredentialsService { await Container.get(ExternalHooks).run('credentials.create', [encryptedData]); - const role = await Container.get(RoleRepository).findCredentialOwnerRoleOrFail(); + const role = await Container.get(RoleService).findCredentialOwnerRole(); const result = await Db.transaction(async (transactionManager) => { const savedCredential = await transactionManager.save(newCredential); diff --git a/packages/cli/src/databases/entities/Role.ts b/packages/cli/src/databases/entities/Role.ts index b3dc3c3d0a..7d5e287307 100644 --- a/packages/cli/src/databases/entities/Role.ts +++ b/packages/cli/src/databases/entities/Role.ts @@ -32,4 +32,8 @@ export class Role extends WithTimestamps { @OneToMany('SharedCredentials', 'role') sharedCredentials: SharedCredentials[]; + + get cacheKey() { + return `role:${this.scope}:${this.name}`; + } } diff --git a/packages/cli/src/databases/repositories/role.repository.ts b/packages/cli/src/databases/repositories/role.repository.ts index 6b2537a26d..a78efadf10 100644 --- a/packages/cli/src/databases/repositories/role.repository.ts +++ b/packages/cli/src/databases/repositories/role.repository.ts @@ -9,51 +9,7 @@ export class RoleRepository extends Repository { super(Role, dataSource.manager); } - async findGlobalOwnerRole(): Promise { - return this.findRole('global', 'owner'); - } - - async findGlobalOwnerRoleOrFail(): Promise { - return this.findRoleOrFail('global', 'owner'); - } - - async findGlobalMemberRole(): Promise { - return this.findRole('global', 'member'); - } - - async findGlobalMemberRoleOrFail(): Promise { - return this.findRoleOrFail('global', 'member'); - } - - async findWorkflowOwnerRole(): Promise { - return this.findRole('workflow', 'owner'); - } - - async findWorkflowOwnerRoleOrFail(): Promise { - return this.findRoleOrFail('workflow', 'owner'); - } - - async findWorkflowEditorRoleOrFail(): Promise { - return this.findRoleOrFail('workflow', 'editor'); - } - - async findCredentialOwnerRole(): Promise { - return this.findRole('credential', 'owner'); - } - - async findCredentialOwnerRoleOrFail(): Promise { - return this.findRoleOrFail('credential', 'owner'); - } - - async findCredentialUserRole(): Promise { - return this.findRole('credential', 'user'); - } - - async findRole(scope: RoleScopes, name: RoleNames): Promise { + async findRole(scope: RoleScopes, name: RoleNames) { return this.findOne({ where: { scope, name } }); } - - async findRoleOrFail(scope: RoleScopes, name: RoleNames): Promise { - return this.findOneOrFail({ where: { scope, name } }); - } } diff --git a/packages/cli/src/environments/sourceControl/sourceControlImport.service.ee.ts b/packages/cli/src/environments/sourceControl/sourceControlImport.service.ee.ts index d92af3d9d2..c01a55efa1 100644 --- a/packages/cli/src/environments/sourceControl/sourceControlImport.service.ee.ts +++ b/packages/cli/src/environments/sourceControl/sourceControlImport.service.ee.ts @@ -1,4 +1,4 @@ -import { Service } from 'typedi'; +import Container, { Service } from 'typedi'; import path from 'path'; import { SOURCE_CONTROL_CREDENTIAL_EXPORT_FOLDER, @@ -25,6 +25,7 @@ import { isUniqueConstraintError } from '@/ResponseHelper'; import type { SourceControlWorkflowVersionId } from './types/sourceControlWorkflowVersionId'; import { getCredentialExportPath, getWorkflowExportPath } from './sourceControlHelper.ee'; import type { SourceControlledFile } from './types/sourceControlledFile'; +import { RoleService } from '@/services/role.service'; import { VariablesService } from '../variables/variables.service'; @Service() @@ -49,39 +50,33 @@ export class SourceControlImportService { } private async getOwnerGlobalRole() { - const ownerCredentiallRole = await Db.collections.Role.findOne({ - where: { name: 'owner', scope: 'global' }, - }); + const globalOwnerRole = await Container.get(RoleService).findGlobalOwnerRole(); - if (!ownerCredentiallRole) { + if (!globalOwnerRole) { throw new Error(`Failed to find owner. ${UM_FIX_INSTRUCTION}`); } - return ownerCredentiallRole; + return globalOwnerRole; } - private async getOwnerCredentialRole() { - const ownerCredentiallRole = await Db.collections.Role.findOne({ - where: { name: 'owner', scope: 'credential' }, - }); + private async getCredentialOwnerRole() { + const credentialOwnerRole = await Container.get(RoleService).findCredentialOwnerRole(); - if (!ownerCredentiallRole) { + if (!credentialOwnerRole) { throw new Error(`Failed to find owner. ${UM_FIX_INSTRUCTION}`); } - return ownerCredentiallRole; + return credentialOwnerRole; } - private async getOwnerWorkflowRole() { - const ownerWorkflowRole = await Db.collections.Role.findOne({ - where: { name: 'owner', scope: 'workflow' }, - }); + private async getWorkflowOwnerRole() { + const workflowOwnerRole = await Container.get(RoleService).findWorkflowOwnerRole(); - if (!ownerWorkflowRole) { + if (!workflowOwnerRole) { throw new Error(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`); } - return ownerWorkflowRole; + return workflowOwnerRole; } private async importCredentialsFromFiles( @@ -92,7 +87,7 @@ export class SourceControlImportService { absolute: true, }); const existingCredentials = await Db.collections.Credentials.find(); - const ownerCredentialRole = await this.getOwnerCredentialRole(); + const ownerCredentialRole = await this.getCredentialOwnerRole(); const ownerGlobalRole = await this.getOwnerGlobalRole(); const encryptionKey = await UserSettings.getEncryptionKey(); let importCredentialsResult: Array<{ id: string; name: string; type: string }> = []; @@ -280,7 +275,7 @@ export class SourceControlImportService { } public async importWorkflowFromWorkFolder(candidates: SourceControlledFile[], userId: string) { - const ownerWorkflowRole = await this.getOwnerWorkflowRole(); + const ownerWorkflowRole = await this.getWorkflowOwnerRole(); const workflowRunner = this.activeWorkflowRunner; const candidateIds = candidates.map((c) => c.id); const existingWorkflows = await Db.collections.Workflow.find({ @@ -401,7 +396,7 @@ export class SourceControlImportService { }, select: ['id', 'name', 'type', 'data'], }); - const ownerCredentialRole = await this.getOwnerCredentialRole(); + const ownerCredentialRole = await this.getCredentialOwnerRole(); const ownerGlobalRole = await this.getOwnerGlobalRole(); const existingSharedCredentials = await Db.collections.SharedCredentials.find({ select: ['userId', 'credentialsId', 'roleId'], diff --git a/packages/cli/src/role/role.service.ts b/packages/cli/src/role/role.service.ts deleted file mode 100644 index d0d52cba35..0000000000 --- a/packages/cli/src/role/role.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Service } from 'typedi'; -import type { EntityManager, FindOptionsWhere } from 'typeorm'; -import { Role } from '@db/entities/Role'; -import { SharedWorkflowRepository } from '@db/repositories'; - -@Service() -export class RoleService { - constructor(private sharedWorkflowRepository: SharedWorkflowRepository) {} - - static async trxGet(transaction: EntityManager, role: FindOptionsWhere) { - return transaction.findOneBy(Role, role); - } - - async getUserRoleForWorkflow(userId: string, workflowId: string) { - const shared = await this.sharedWorkflowRepository.findOne({ - where: { workflowId, userId }, - relations: ['role'], - }); - return shared?.role; - } -} diff --git a/packages/cli/src/services/ownership.service.ts b/packages/cli/src/services/ownership.service.ts index 9602dc6241..5b78489003 100644 --- a/packages/cli/src/services/ownership.service.ts +++ b/packages/cli/src/services/ownership.service.ts @@ -1,14 +1,15 @@ import { Service } from 'typedi'; import { CacheService } from './cache.service'; -import { RoleRepository, SharedWorkflowRepository, UserRepository } from '@/databases/repositories'; +import { SharedWorkflowRepository, UserRepository } from '@/databases/repositories'; import type { User } from '@/databases/entities/User'; +import { RoleService } from './role.service'; @Service() export class OwnershipService { constructor( private cacheService: CacheService, private userRepository: UserRepository, - private roleRepository: RoleRepository, + private roleService: RoleService, private sharedWorkflowRepository: SharedWorkflowRepository, ) {} @@ -20,7 +21,7 @@ export class OwnershipService { if (cachedValue) return this.userRepository.create(cachedValue); - const workflowOwnerRole = await this.roleRepository.findWorkflowOwnerRole(); + const workflowOwnerRole = await this.roleService.findWorkflowOwnerRole(); if (!workflowOwnerRole) throw new Error('Failed to find workflow owner role'); diff --git a/packages/cli/src/services/role.service.ts b/packages/cli/src/services/role.service.ts new file mode 100644 index 0000000000..d30f47c499 --- /dev/null +++ b/packages/cli/src/services/role.service.ts @@ -0,0 +1,94 @@ +import { RoleRepository, SharedWorkflowRepository } from '@/databases/repositories'; +import { Service } from 'typedi'; +import { CacheService } from './cache.service'; +import type { RoleNames, RoleScopes } from '@/databases/entities/Role'; + +class InvalidRoleError extends Error {} + +@Service() +export class RoleService { + constructor( + private roleRepository: RoleRepository, + private sharedWorkflowRepository: SharedWorkflowRepository, + private cacheService: CacheService, + ) { + void this.populateCache(); + } + + async populateCache() { + const allRoles = await this.roleRepository.find({}); + + if (!allRoles) return; + + void this.cacheService.setMany(allRoles.map((r) => [r.cacheKey, r])); + } + + private async findCached(scope: RoleScopes, name: RoleNames) { + const cacheKey = `role:${scope}:${name}`; + + const cachedRole = await this.cacheService.get(cacheKey); + + if (cachedRole) return this.roleRepository.create(cachedRole); + + let dbRole = await this.roleRepository.findRole(scope, name); + + if (dbRole === null) { + if (!this.isValid(scope, name)) { + throw new InvalidRoleError(`${scope}:${name} is not a valid role`); + } + + const toSave = this.roleRepository.create({ scope, name }); + dbRole = await this.roleRepository.save(toSave); + } + + void this.cacheService.set(cacheKey, dbRole); + + return dbRole; + } + + private roles: Array<{ name: RoleNames; scope: RoleScopes }> = [ + { scope: 'global', name: 'owner' }, + { scope: 'global', name: 'member' }, + { scope: 'workflow', name: 'owner' }, + { scope: 'credential', name: 'owner' }, + { scope: 'credential', name: 'user' }, + { scope: 'workflow', name: 'editor' }, + ]; + + private isValid(scope: RoleScopes, name: RoleNames) { + return this.roles.some((r) => r.scope === scope && r.name === name); + } + + async findGlobalOwnerRole() { + return this.findCached('global', 'owner'); + } + + async findGlobalMemberRole() { + return this.findCached('global', 'member'); + } + + async findWorkflowOwnerRole() { + return this.findCached('workflow', 'owner'); + } + + async findWorkflowEditorRole() { + return this.findCached('workflow', 'editor'); + } + + async findCredentialOwnerRole() { + return this.findCached('credential', 'owner'); + } + + async findCredentialUserRole() { + return this.findCached('credential', 'user'); + } + + async findRoleByUserAndWorkflow(userId: string, workflowId: string) { + return this.sharedWorkflowRepository + .findOne({ + where: { workflowId, userId }, + relations: ['role'], + }) + .then((shared) => shared?.role); + } +} diff --git a/packages/cli/src/sso/saml/samlHelpers.ts b/packages/cli/src/sso/saml/samlHelpers.ts index db2ad210e1..6c9f76648c 100644 --- a/packages/cli/src/sso/saml/samlHelpers.ts +++ b/packages/cli/src/sso/saml/samlHelpers.ts @@ -3,7 +3,6 @@ import config from '@/config'; import * as Db from '@/Db'; import { AuthIdentity } from '@db/entities/AuthIdentity'; import { User } from '@db/entities/User'; -import { RoleRepository } from '@db/repositories'; import { License } from '@/License'; import { AuthError, InternalServerError } from '@/ResponseHelper'; import { hashPassword } from '@/UserManagement/UserManagementHelper'; @@ -20,6 +19,7 @@ import { } from '../ssoHelpers'; import { getServiceProviderConfigTestReturnUrl } from './serviceProvider.ee'; import type { SamlConfiguration } from './types/requests'; +import { RoleService } from '@/services/role.service'; /** * Check whether the SAML feature is licensed and enabled in the instance */ @@ -101,7 +101,7 @@ export async function createUserFromSamlAttributes(attributes: SamlUserAttribute user.email = lowerCasedEmail; user.firstName = attributes.firstName; user.lastName = attributes.lastName; - user.globalRole = await Container.get(RoleRepository).findGlobalMemberRoleOrFail(); + user.globalRole = await Container.get(RoleService).findGlobalMemberRole(); // generates a password that is not used or known to the user user.password = await hashPassword(generatePassword()); authIdentity.providerId = attributes.userPrincipalName; diff --git a/packages/cli/src/workflows/workflows.controller.ee.ts b/packages/cli/src/workflows/workflows.controller.ee.ts index c50860c359..40327c2b28 100644 --- a/packages/cli/src/workflows/workflows.controller.ee.ts +++ b/packages/cli/src/workflows/workflows.controller.ee.ts @@ -11,7 +11,6 @@ import { isSharingEnabled, rightDiff } from '@/UserManagement/UserManagementHelp import { EEWorkflowsService as EEWorkflows } from './workflows.services.ee'; import { ExternalHooks } from '@/ExternalHooks'; import { SharedWorkflow } from '@db/entities/SharedWorkflow'; -import { RoleRepository } from '@db/repositories'; import { LoggerProxy } from 'n8n-workflow'; import * as TagHelpers from '@/TagHelpers'; import { EECredentialsService as EECredentials } from '../credentials/credentials.service.ee'; @@ -20,6 +19,7 @@ import * as GenericHelpers from '@/GenericHelpers'; import { In } from 'typeorm'; import { Container } from 'typedi'; import { InternalHooks } from '@/InternalHooks'; +import { RoleService } from '@/services/role.service'; // eslint-disable-next-line @typescript-eslint/naming-convention export const EEWorkflowController = express.Router(); @@ -164,7 +164,7 @@ EEWorkflowController.post( await Db.transaction(async (transactionManager) => { savedWorkflow = await transactionManager.save(newWorkflow); - const role = await Container.get(RoleRepository).findWorkflowOwnerRoleOrFail(); + const role = await Container.get(RoleService).findWorkflowOwnerRole(); const newSharedWorkflow = new SharedWorkflow(); @@ -205,7 +205,7 @@ EEWorkflowController.get( ResponseHelper.send(async (req: WorkflowRequest.GetAll) => { const [workflows, workflowOwnerRole] = await Promise.all([ EEWorkflows.getMany(req.user, req.query.filter), - Container.get(RoleRepository).findWorkflowOwnerRoleOrFail(), + Container.get(RoleService).findWorkflowOwnerRole(), ]); return workflows.map((workflow) => { diff --git a/packages/cli/src/workflows/workflows.controller.ts b/packages/cli/src/workflows/workflows.controller.ts index ead52e3063..b81739a76d 100644 --- a/packages/cli/src/workflows/workflows.controller.ts +++ b/packages/cli/src/workflows/workflows.controller.ts @@ -12,7 +12,6 @@ import config from '@/config'; import * as TagHelpers from '@/TagHelpers'; import { SharedWorkflow } from '@db/entities/SharedWorkflow'; import { WorkflowEntity } from '@db/entities/WorkflowEntity'; -import { RoleRepository } from '@db/repositories'; import { validateEntity } from '@/GenericHelpers'; import { ExternalHooks } from '@/ExternalHooks'; import { getLogger } from '@/Logger'; @@ -24,6 +23,7 @@ import { whereClause } from '@/UserManagement/UserManagementHelper'; import { In } from 'typeorm'; import { Container } from 'typedi'; import { InternalHooks } from '@/InternalHooks'; +import { RoleService } from '@/services/role.service'; export const workflowsController = express.Router(); @@ -79,7 +79,7 @@ workflowsController.post( await Db.transaction(async (transactionManager) => { savedWorkflow = await transactionManager.save(newWorkflow); - const role = await Container.get(RoleRepository).findWorkflowOwnerRoleOrFail(); + const role = await Container.get(RoleService).findWorkflowOwnerRole(); const newSharedWorkflow = new SharedWorkflow(); diff --git a/packages/cli/src/workflows/workflows.services.ee.ts b/packages/cli/src/workflows/workflows.services.ee.ts index 44ddaa9e1c..ac0e9fc290 100644 --- a/packages/cli/src/workflows/workflows.services.ee.ts +++ b/packages/cli/src/workflows/workflows.services.ee.ts @@ -8,7 +8,6 @@ import { SharedWorkflow } from '@db/entities/SharedWorkflow'; import type { Role } from '@db/entities/Role'; import type { User } from '@db/entities/User'; import { WorkflowEntity } from '@db/entities/WorkflowEntity'; -import { RoleService } from '@/role/role.service'; import { UserService } from '@/user/user.service'; import { WorkflowsService } from './workflows.services'; import type { @@ -19,6 +18,8 @@ import type { import { EECredentialsService as EECredentials } from '@/credentials/credentials.service.ee'; import { getSharedWorkflowIds } from '@/WorkflowHelpers'; import { NodeOperationError } from 'n8n-workflow'; +import { RoleService } from '@/services/role.service'; +import Container from 'typedi'; export class EEWorkflowsService extends WorkflowsService { static async getWorkflowIdsForUser(user: User) { @@ -68,10 +69,8 @@ export class EEWorkflowsService extends WorkflowsService { workflow: WorkflowEntity, shareWithIds: string[], ): Promise { - const [users, role] = await Promise.all([ - UserService.getByIds(transaction, shareWithIds), - RoleService.trxGet(transaction, { scope: 'workflow', name: 'editor' }), - ]); + const users = await UserService.getByIds(transaction, shareWithIds); + const role = await Container.get(RoleService).findWorkflowEditorRole(); const newSharedWorkflows = users.reduce((acc, user) => { if (user.isPending) { diff --git a/packages/cli/test/integration/shared/random.ts b/packages/cli/test/integration/shared/random.ts index 876670720d..0972ba5c18 100644 --- a/packages/cli/test/integration/shared/random.ts +++ b/packages/cli/test/integration/shared/random.ts @@ -16,7 +16,7 @@ export function randomApiKey() { return `n8n_api_${randomBytes(20).toString('hex')}`; } -const chooseRandomly = (array: T[]) => array[Math.floor(Math.random() * array.length)]; +export const chooseRandomly = (array: T[]) => array[Math.floor(Math.random() * array.length)]; export const randomInteger = (max = 1000) => Math.floor(Math.random() * max); diff --git a/packages/cli/test/integration/shared/testDb.ts b/packages/cli/test/integration/shared/testDb.ts index 82e6531e11..695389e642 100644 --- a/packages/cli/test/integration/shared/testDb.ts +++ b/packages/cli/test/integration/shared/testDb.ts @@ -20,7 +20,6 @@ import type { Role } from '@db/entities/Role'; import type { TagEntity } from '@db/entities/TagEntity'; import type { User } from '@db/entities/User'; import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; -import { RoleRepository } from '@db/repositories'; import type { ICredentialsDb } from '@/Interfaces'; import { DB_INITIALIZATION_TIMEOUT } from './constants'; @@ -34,6 +33,7 @@ import type { } from './types'; import type { ExecutionData } from '@db/entities/ExecutionData'; import { generateNanoId } from '@db/utils/generators'; +import { RoleService } from '@/services/role.service'; import { VariablesService } from '@/environments/variables/variables.service'; export type TestDBType = 'postgres' | 'mysql'; @@ -151,7 +151,7 @@ export async function saveCredential( } export async function shareCredentialWithUsers(credential: CredentialsEntity, users: User[]) { - const role = await Container.get(RoleRepository).findCredentialUserRole(); + const role = await Container.get(RoleService).findCredentialUserRole(); const newSharedCredentials = users.map((user) => Db.collections.SharedCredentials.create({ userId: user.id, @@ -276,23 +276,23 @@ export async function addApiKey(user: User): Promise { // ---------------------------------- export async function getGlobalOwnerRole() { - return Container.get(RoleRepository).findGlobalOwnerRoleOrFail(); + return Container.get(RoleService).findGlobalOwnerRole(); } export async function getGlobalMemberRole() { - return Container.get(RoleRepository).findGlobalMemberRoleOrFail(); + return Container.get(RoleService).findGlobalMemberRole(); } export async function getWorkflowOwnerRole() { - return Container.get(RoleRepository).findWorkflowOwnerRoleOrFail(); + return Container.get(RoleService).findWorkflowOwnerRole(); } export async function getWorkflowEditorRole() { - return Container.get(RoleRepository).findWorkflowEditorRoleOrFail(); + return Container.get(RoleService).findWorkflowEditorRole(); } export async function getCredentialOwnerRole() { - return Container.get(RoleRepository).findCredentialOwnerRoleOrFail(); + return Container.get(RoleService).findCredentialOwnerRole(); } export async function getAllRoles() { diff --git a/packages/cli/test/integration/shared/utils/testServer.ts b/packages/cli/test/integration/shared/utils/testServer.ts index 80f6c70acc..1f4e2ecee5 100644 --- a/packages/cli/test/integration/shared/utils/testServer.ts +++ b/packages/cli/test/integration/shared/utils/testServer.ts @@ -50,6 +50,7 @@ import { AUTHLESS_ENDPOINTS, PUBLIC_API_REST_PATH_SEGMENT, REST_PATH_SEGMENT } f import type { EndpointGroup, SetupProps, TestServer } from '../types'; import { mockInstance } from './mocking'; import { JwtService } from '@/services/jwt.service'; +import { RoleService } from '@/services/role.service'; /** * Plugin to prefix a path segment into a request URL pathname. @@ -264,6 +265,7 @@ export const setupTestServer = ({ activeWorkflowRunner: Container.get(ActiveWorkflowRunner), logger, jwtService, + roleService: Container.get(RoleService), }), ); break; diff --git a/packages/cli/test/unit/repositories/role.repository.test.ts b/packages/cli/test/unit/repositories/role.repository.test.ts index f47a93a7df..c7669967cf 100644 --- a/packages/cli/test/unit/repositories/role.repository.test.ts +++ b/packages/cli/test/unit/repositories/role.repository.test.ts @@ -29,20 +29,6 @@ describe('RoleRepository', () => { }); }); - describe('findRoleOrFail', () => { - test('should return the role when present', async () => { - entityManager.findOneOrFail.mockResolvedValueOnce(createRole('global', 'owner')); - const role = await roleRepository.findRoleOrFail('global', 'owner'); - expect(role?.name).toEqual('owner'); - expect(role?.scope).toEqual('global'); - }); - - test('should throw otherwise', async () => { - entityManager.findOneOrFail.mockRejectedValueOnce(new Error()); - await expect(async () => roleRepository.findRoleOrFail('global', 'owner')).rejects.toThrow(); - }); - }); - const createRole = (scope: RoleScopes, name: RoleNames) => Object.assign(new Role(), { name, scope, id: `${randomInteger()}` }); }); diff --git a/packages/cli/test/unit/services/ownership.service.test.ts b/packages/cli/test/unit/services/ownership.service.test.ts index 9de9169123..fc1852c2cf 100644 --- a/packages/cli/test/unit/services/ownership.service.test.ts +++ b/packages/cli/test/unit/services/ownership.service.test.ts @@ -1,11 +1,12 @@ import { OwnershipService } from '@/services/ownership.service'; -import { RoleRepository, SharedWorkflowRepository, UserRepository } from '@/databases/repositories'; +import { SharedWorkflowRepository, UserRepository } from '@/databases/repositories'; import { mockInstance } from '../../integration/shared/utils'; import { Role } from '@/databases/entities/Role'; import { randomInteger } from '../../integration/shared/random'; import { SharedWorkflow } from '@/databases/entities/SharedWorkflow'; import { CacheService } from '@/services/cache.service'; import { User } from '@/databases/entities/User'; +import { RoleService } from '@/services/role.service'; const wfOwnerRole = () => Object.assign(new Role(), { @@ -16,14 +17,14 @@ const wfOwnerRole = () => describe('OwnershipService', () => { const cacheService = mockInstance(CacheService); - const roleRepository = mockInstance(RoleRepository); + const roleService = mockInstance(RoleService); const userRepository = mockInstance(UserRepository); const sharedWorkflowRepository = mockInstance(SharedWorkflowRepository); const ownershipService = new OwnershipService( cacheService, userRepository, - roleRepository, + roleService, sharedWorkflowRepository, ); @@ -33,7 +34,7 @@ describe('OwnershipService', () => { describe('getWorkflowOwner()', () => { test('should retrieve a workflow owner', async () => { - roleRepository.findWorkflowOwnerRole.mockResolvedValueOnce(wfOwnerRole()); + roleService.findWorkflowOwnerRole.mockResolvedValueOnce(wfOwnerRole()); const mockOwner = new User(); const mockNonOwner = new User(); @@ -52,13 +53,13 @@ describe('OwnershipService', () => { }); test('should throw if no workflow owner role found', async () => { - roleRepository.findWorkflowOwnerRole.mockRejectedValueOnce(new Error()); + roleService.findWorkflowOwnerRole.mockRejectedValueOnce(new Error()); await expect(ownershipService.getWorkflowOwnerCached('some-workflow-id')).rejects.toThrow(); }); test('should throw if no workflow owner found', async () => { - roleRepository.findWorkflowOwnerRole.mockResolvedValueOnce(wfOwnerRole()); + roleService.findWorkflowOwnerRole.mockResolvedValueOnce(wfOwnerRole()); sharedWorkflowRepository.findOneOrFail.mockRejectedValue(new Error()); diff --git a/packages/cli/test/unit/services/role.service.test.ts b/packages/cli/test/unit/services/role.service.test.ts index 971b4bbf80..9b0f524a10 100644 --- a/packages/cli/test/unit/services/role.service.test.ts +++ b/packages/cli/test/unit/services/role.service.test.ts @@ -1,28 +1,80 @@ import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; +import type { RoleNames, RoleScopes } from '@db/entities/Role'; import { Role } from '@db/entities/Role'; -import { SharedWorkflow } from '@db/entities/SharedWorkflow'; -import { RoleService } from '@/role/role.service'; import { mockInstance } from '../../integration/shared/utils/'; +import { RoleService } from '@/services/role.service'; +import { RoleRepository } from '@/databases/repositories'; +import { CacheService } from '@/services/cache.service'; +import { SharedWorkflow } from '@/databases/entities/SharedWorkflow'; +import { chooseRandomly } from '../../integration/shared/random'; +import config from '@/config'; + +const ROLE_PROPS: Array<{ name: RoleNames; scope: RoleScopes }> = [ + { name: 'owner', scope: 'global' }, + { name: 'member', scope: 'global' }, + { name: 'owner', scope: 'workflow' }, + { name: 'owner', scope: 'credential' }, + { name: 'user', scope: 'credential' }, + { name: 'editor', scope: 'workflow' }, +]; + +export const uppercaseInitial = (str: string) => str[0].toUpperCase() + str.slice(1); describe('RoleService', () => { const sharedWorkflowRepository = mockInstance(SharedWorkflowRepository); - const roleService = new RoleService(sharedWorkflowRepository); + const roleRepository = mockInstance(RoleRepository); + const cacheService = mockInstance(CacheService); + const roleService = new RoleService(roleRepository, sharedWorkflowRepository, cacheService); const userId = '1'; const workflowId = '42'; - describe('getUserRoleForWorkflow', () => { - test('should return the role if a shared workflow is found', async () => { - const sharedWorkflow = Object.assign(new SharedWorkflow(), { role: new Role() }); - sharedWorkflowRepository.findOne.mockResolvedValueOnce(sharedWorkflow); - const role = await roleService.getUserRoleForWorkflow(userId, workflowId); - expect(role).toBe(sharedWorkflow.role); + const { name, scope } = chooseRandomly(ROLE_PROPS); + + const display = { + name: uppercaseInitial(name), + scope: uppercaseInitial(scope), + }; + + beforeEach(() => { + config.load(config.default); + jest.clearAllMocks(); + }); + + [true, false].forEach((cacheEnabled) => { + const tag = ['cache', cacheEnabled ? 'enabled' : 'disabled'].join(' '); + + describe(`find${display.scope}${display.name}Role() [${tag}]`, () => { + test(`should return the ${scope} ${name} role if found`, async () => { + config.set('cache.enabled', cacheEnabled); + + const role = roleRepository.create({ name, scope }); + roleRepository.findRole.mockResolvedValueOnce(role); + const returnedRole = await roleRepository.findRole(scope, name); + + expect(returnedRole).toBe(role); + }); }); - test('should return undefined if no shared workflow is found', async () => { - sharedWorkflowRepository.findOne.mockResolvedValueOnce(null); - const role = await roleService.getUserRoleForWorkflow(userId, workflowId); - expect(role).toBeUndefined(); + describe(`findRoleByUserAndWorkflow() [${tag}]`, () => { + test('should return the role if a shared workflow is found', async () => { + config.set('cache.enabled', cacheEnabled); + + const sharedWorkflow = Object.assign(new SharedWorkflow(), { role: new Role() }); + sharedWorkflowRepository.findOne.mockResolvedValueOnce(sharedWorkflow); + const returnedRole = await roleService.findRoleByUserAndWorkflow(userId, workflowId); + + expect(returnedRole).toBe(sharedWorkflow.role); + }); + + test('should return undefined if no shared workflow is found', async () => { + config.set('cache.enabled', cacheEnabled); + + sharedWorkflowRepository.findOne.mockResolvedValueOnce(null); + const returnedRole = await roleService.findRoleByUserAndWorkflow(userId, workflowId); + + expect(returnedRole).toBeUndefined(); + }); }); }); });