mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
perf(core): Cache roles (#6803)
* refactor: Create `RoleService` * refactor: Refactor to use service * refactor: Move `getUserRoleForWorkflow` * refactor: Clear out old `RoleService` * refactor: Consolidate utils into service * refactor: Remove unused methods * test: Add tests * refactor: Remove redundant return types * refactor: Missing utility * chore: Remove commented out bit * refactor: Make `Db.collections.Repository` inaccessible * chore: Cleanup * feat: Prepopulate cache * chore: Remove logging * fix: Account for tests where roles are undefined * fix: Restore `prettier.prettierPath` * test: Account for cache enabled and disabled * fix: Restore `Role` in `Db.collections` * refactor: Simplify by removing `orFail` * refactor: Rename for clarity * refactor: Use `cacheKey` for readability * refactor: Validate role before creation * refacator: Remove redundant `cache` prefix * ci: Lint fix * test: Fix e2e
This commit is contained in:
parent
f93270abd5
commit
e4f041815a
|
@ -169,23 +169,27 @@ export async function init(testConnectionOptions?: ConnectionOptions): Promise<v
|
||||||
|
|
||||||
collections.AuthIdentity = Container.get(AuthIdentityRepository);
|
collections.AuthIdentity = Container.get(AuthIdentityRepository);
|
||||||
collections.AuthProviderSyncHistory = Container.get(AuthProviderSyncHistoryRepository);
|
collections.AuthProviderSyncHistory = Container.get(AuthProviderSyncHistoryRepository);
|
||||||
collections.Credentials = Container.get(CredentialsRepository);
|
|
||||||
collections.EventDestinations = Container.get(EventDestinationsRepository);
|
collections.EventDestinations = Container.get(EventDestinationsRepository);
|
||||||
collections.Execution = Container.get(ExecutionRepository);
|
collections.Execution = Container.get(ExecutionRepository);
|
||||||
collections.ExecutionData = Container.get(ExecutionDataRepository);
|
collections.ExecutionData = Container.get(ExecutionDataRepository);
|
||||||
collections.ExecutionMetadata = Container.get(ExecutionMetadataRepository);
|
collections.ExecutionMetadata = Container.get(ExecutionMetadataRepository);
|
||||||
collections.InstalledNodes = Container.get(InstalledNodesRepository);
|
collections.InstalledNodes = Container.get(InstalledNodesRepository);
|
||||||
collections.InstalledPackages = Container.get(InstalledPackagesRepository);
|
collections.InstalledPackages = Container.get(InstalledPackagesRepository);
|
||||||
collections.Role = Container.get(RoleRepository);
|
|
||||||
collections.Settings = Container.get(SettingsRepository);
|
|
||||||
collections.SharedCredentials = Container.get(SharedCredentialsRepository);
|
collections.SharedCredentials = Container.get(SharedCredentialsRepository);
|
||||||
collections.SharedWorkflow = Container.get(SharedWorkflowRepository);
|
collections.SharedWorkflow = Container.get(SharedWorkflowRepository);
|
||||||
collections.Tag = Container.get(TagRepository);
|
collections.Tag = Container.get(TagRepository);
|
||||||
collections.User = Container.get(UserRepository);
|
|
||||||
collections.Variables = Container.get(VariablesRepository);
|
collections.Variables = Container.get(VariablesRepository);
|
||||||
collections.Workflow = Container.get(WorkflowRepository);
|
|
||||||
collections.WorkflowStatistics = Container.get(WorkflowStatisticsRepository);
|
collections.WorkflowStatistics = Container.get(WorkflowStatisticsRepository);
|
||||||
collections.WorkflowTagMapping = Container.get(WorkflowTagMappingRepository);
|
collections.WorkflowTagMapping = Container.get(WorkflowTagMappingRepository);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @important Do not remove these collections until cloud hooks are backwards compatible.
|
||||||
|
*/
|
||||||
|
collections.Role = Container.get(RoleRepository);
|
||||||
|
collections.User = Container.get(UserRepository);
|
||||||
|
collections.Settings = Container.get(SettingsRepository);
|
||||||
|
collections.Credentials = Container.get(CredentialsRepository);
|
||||||
|
collections.Workflow = Container.get(WorkflowRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function migrate() {
|
export async function migrate() {
|
||||||
|
|
|
@ -22,7 +22,6 @@ import type {
|
||||||
} from '@/Interfaces';
|
} from '@/Interfaces';
|
||||||
import { Telemetry } from '@/telemetry';
|
import { Telemetry } from '@/telemetry';
|
||||||
import type { AuthProviderType } from '@db/entities/AuthIdentity';
|
import type { AuthProviderType } from '@db/entities/AuthIdentity';
|
||||||
import { RoleService } from './role/role.service';
|
|
||||||
import { eventBus } from './eventbus';
|
import { eventBus } from './eventbus';
|
||||||
import { EventsService } from '@/services/events.service';
|
import { EventsService } from '@/services/events.service';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
|
@ -30,6 +29,7 @@ import { N8N_VERSION } from '@/constants';
|
||||||
import { NodeTypes } from './NodeTypes';
|
import { NodeTypes } from './NodeTypes';
|
||||||
import type { ExecutionMetadata } from '@db/entities/ExecutionMetadata';
|
import type { ExecutionMetadata } from '@db/entities/ExecutionMetadata';
|
||||||
import { ExecutionRepository } from '@db/repositories';
|
import { ExecutionRepository } from '@db/repositories';
|
||||||
|
import { RoleService } from './services/role.service';
|
||||||
|
|
||||||
function userToPayload(user: User): {
|
function userToPayload(user: User): {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
@ -175,7 +175,7 @@ export class InternalHooks implements IInternalHooksClass {
|
||||||
|
|
||||||
let userRole: 'owner' | 'sharee' | undefined = undefined;
|
let userRole: 'owner' | 'sharee' | undefined = undefined;
|
||||||
if (user.id && workflow.id) {
|
if (user.id && workflow.id) {
|
||||||
const role = await this.roleService.getUserRoleForWorkflow(user.id, workflow.id);
|
const role = await this.roleService.findRoleByUserAndWorkflow(user.id, workflow.id);
|
||||||
if (role) {
|
if (role) {
|
||||||
userRole = role.name === 'owner' ? 'owner' : 'sharee';
|
userRole = role.name === 'owner' ? 'owner' : 'sharee';
|
||||||
}
|
}
|
||||||
|
@ -381,7 +381,7 @@ export class InternalHooks implements IInternalHooksClass {
|
||||||
|
|
||||||
let userRole: 'owner' | 'sharee' | undefined = undefined;
|
let userRole: 'owner' | 'sharee' | undefined = undefined;
|
||||||
if (userId) {
|
if (userId) {
|
||||||
const role = await this.roleService.getUserRoleForWorkflow(userId, workflow.id);
|
const role = await this.roleService.findRoleByUserAndWorkflow(userId, workflow.id);
|
||||||
if (role) {
|
if (role) {
|
||||||
userRole = role.name === 'owner' ? 'owner' : 'sharee';
|
userRole = role.name === 'owner' ? 'owner' : 'sharee';
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import config from '@/config';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
import { User } from '@db/entities/User';
|
import { User } from '@db/entities/User';
|
||||||
import { AuthIdentity } from '@db/entities/AuthIdentity';
|
import { AuthIdentity } from '@db/entities/AuthIdentity';
|
||||||
import { RoleRepository } from '@db/repositories';
|
|
||||||
import type { AuthProviderSyncHistory } from '@db/entities/AuthProviderSyncHistory';
|
import type { AuthProviderSyncHistory } from '@db/entities/AuthProviderSyncHistory';
|
||||||
import { LdapManager } from './LdapManager.ee';
|
import { LdapManager } from './LdapManager.ee';
|
||||||
|
|
||||||
|
@ -32,6 +31,7 @@ import {
|
||||||
setCurrentAuthenticationMethod,
|
setCurrentAuthenticationMethod,
|
||||||
} from '@/sso/ssoHelpers';
|
} from '@/sso/ssoHelpers';
|
||||||
import { InternalServerError } from '../ResponseHelper';
|
import { InternalServerError } from '../ResponseHelper';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the LDAP feature is disabled in the instance
|
* Check whether the LDAP feature is disabled in the instance
|
||||||
|
@ -92,7 +92,7 @@ export const randomPassword = (): string => {
|
||||||
* Return the user role to be assigned to LDAP users
|
* Return the user role to be assigned to LDAP users
|
||||||
*/
|
*/
|
||||||
export const getLdapUserRole = async (): Promise<Role> => {
|
export const getLdapUserRole = async (): Promise<Role> => {
|
||||||
return Container.get(RoleRepository).findGlobalMemberRoleOrFail();
|
return Container.get(RoleService).findGlobalMemberRole();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,11 +5,11 @@ import type { ICredentialsDb } from '@/Interfaces';
|
||||||
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||||
import { SharedCredentials } from '@db/entities/SharedCredentials';
|
import { SharedCredentials } from '@db/entities/SharedCredentials';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { RoleRepository } from '@db/repositories';
|
|
||||||
import { ExternalHooks } from '@/ExternalHooks';
|
import { ExternalHooks } from '@/ExternalHooks';
|
||||||
import type { IDependency, IJsonSchema } from '../../../types';
|
import type { IDependency, IJsonSchema } from '../../../types';
|
||||||
import type { CredentialRequest } from '@/requests';
|
import type { CredentialRequest } from '@/requests';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
export async function getCredentials(credentialId: string): Promise<ICredentialsDb | null> {
|
export async function getCredentials(credentialId: string): Promise<ICredentialsDb | null> {
|
||||||
return Db.collections.Credentials.findOneBy({ id: credentialId });
|
return Db.collections.Credentials.findOneBy({ id: credentialId });
|
||||||
|
@ -58,7 +58,7 @@ export async function saveCredential(
|
||||||
user: User,
|
user: User,
|
||||||
encryptedData: ICredentialsDb,
|
encryptedData: ICredentialsDb,
|
||||||
): Promise<CredentialsEntity> {
|
): Promise<CredentialsEntity> {
|
||||||
const role = await Container.get(RoleRepository).findCredentialOwnerRoleOrFail();
|
const role = await Container.get(RoleService).findCredentialOwnerRole();
|
||||||
|
|
||||||
await Container.get(ExternalHooks).run('credentials.create', [encryptedData]);
|
await Container.get(ExternalHooks).run('credentials.create', [encryptedData]);
|
||||||
|
|
||||||
|
|
|
@ -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<Role> {
|
|
||||||
return Container.get(RoleRepository).findWorkflowOwnerRoleOrFail();
|
|
||||||
}
|
|
|
@ -11,7 +11,6 @@ import { addNodeIds, replaceInvalidCredentials } from '@/WorkflowHelpers';
|
||||||
import type { WorkflowRequest } from '../../../types';
|
import type { WorkflowRequest } from '../../../types';
|
||||||
import { authorize, validCursor } from '../../shared/middlewares/global.middleware';
|
import { authorize, validCursor } from '../../shared/middlewares/global.middleware';
|
||||||
import { encodeNextCursor } from '../../shared/services/pagination.service';
|
import { encodeNextCursor } from '../../shared/services/pagination.service';
|
||||||
import { getWorkflowOwnerRole } from '../users/users.service';
|
|
||||||
import {
|
import {
|
||||||
getWorkflowById,
|
getWorkflowById,
|
||||||
getSharedWorkflow,
|
getSharedWorkflow,
|
||||||
|
@ -26,6 +25,7 @@ import {
|
||||||
} from './workflows.service';
|
} from './workflows.service';
|
||||||
import { WorkflowsService } from '@/workflows/workflows.services';
|
import { WorkflowsService } from '@/workflows/workflows.services';
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
createWorkflow: [
|
createWorkflow: [
|
||||||
|
@ -39,7 +39,7 @@ export = {
|
||||||
|
|
||||||
addNodeIds(workflow);
|
addNodeIds(workflow);
|
||||||
|
|
||||||
const role = await getWorkflowOwnerRole();
|
const role = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||||
|
|
||||||
const createdWorkflow = await createWorkflow(workflow, req.user, role);
|
const createdWorkflow = await createWorkflow(workflow, req.user, role);
|
||||||
|
|
||||||
|
|
|
@ -170,6 +170,7 @@ import { SourceControlController } from '@/environments/sourceControl/sourceCont
|
||||||
import { ExecutionRepository } from '@db/repositories';
|
import { ExecutionRepository } from '@db/repositories';
|
||||||
import type { ExecutionEntity } from '@db/entities/ExecutionEntity';
|
import type { ExecutionEntity } from '@db/entities/ExecutionEntity';
|
||||||
import { JwtService } from './services/jwt.service';
|
import { JwtService } from './services/jwt.service';
|
||||||
|
import { RoleService } from './services/role.service';
|
||||||
|
|
||||||
const exec = promisify(callbackExec);
|
const exec = promisify(callbackExec);
|
||||||
|
|
||||||
|
@ -496,6 +497,7 @@ export class Server extends AbstractServer {
|
||||||
logger,
|
logger,
|
||||||
postHog,
|
postHog,
|
||||||
jwtService,
|
jwtService,
|
||||||
|
roleService: Container.get(RoleService),
|
||||||
}),
|
}),
|
||||||
Container.get(SamlController),
|
Container.get(SamlController),
|
||||||
Container.get(SourceControlController),
|
Container.get(SourceControlController),
|
||||||
|
|
|
@ -9,11 +9,12 @@ import { In } from 'typeorm';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import type { SharedCredentials } from '@db/entities/SharedCredentials';
|
import type { SharedCredentials } from '@db/entities/SharedCredentials';
|
||||||
import { getRoleId, isSharingEnabled } from './UserManagementHelper';
|
import { isSharingEnabled } from './UserManagementHelper';
|
||||||
import { WorkflowsService } from '@/workflows/workflows.services';
|
import { WorkflowsService } from '@/workflows/workflows.services';
|
||||||
import { UserService } from '@/user/user.service';
|
import { UserService } from '@/user/user.service';
|
||||||
import { OwnershipService } from '@/services/ownership.service';
|
import { OwnershipService } from '@/services/ownership.service';
|
||||||
import Container from 'typedi';
|
import Container from 'typedi';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
export class PermissionChecker {
|
export class PermissionChecker {
|
||||||
/**
|
/**
|
||||||
|
@ -54,8 +55,9 @@ export class PermissionChecker {
|
||||||
const credentialsWhere: FindOptionsWhere<SharedCredentials> = { userId: In(workflowUserIds) };
|
const credentialsWhere: FindOptionsWhere<SharedCredentials> = { userId: In(workflowUserIds) };
|
||||||
|
|
||||||
if (!isSharingEnabled()) {
|
if (!isSharingEnabled()) {
|
||||||
|
const role = await Container.get(RoleService).findCredentialOwnerRole();
|
||||||
// If credential sharing is not enabled, get only credentials owned by this user
|
// 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({
|
const credentialSharings = await Db.collections.SharedCredentials.find({
|
||||||
|
|
|
@ -7,12 +7,11 @@ import * as ResponseHelper from '@/ResponseHelper';
|
||||||
import type { CurrentUser, PublicUser, WhereClause } from '@/Interfaces';
|
import type { CurrentUser, PublicUser, WhereClause } from '@/Interfaces';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH } 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 config from '@/config';
|
||||||
import { License } from '@/License';
|
import { License } from '@/License';
|
||||||
import { getWebhookBaseUrl } from '@/WebhookHelpers';
|
import { getWebhookBaseUrl } from '@/WebhookHelpers';
|
||||||
import type { PostHogClient } from '@/posthog';
|
import type { PostHogClient } from '@/posthog';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
export function isEmailSetUp(): boolean {
|
export function isEmailSetUp(): boolean {
|
||||||
const smtp = config.getEnv('userManagement.emails.mode') === 'smtp';
|
const smtp = config.getEnv('userManagement.emails.mode') === 'smtp';
|
||||||
|
@ -27,22 +26,15 @@ export function isSharingEnabled(): boolean {
|
||||||
return Container.get(License).isSharingEnabled();
|
return Container.get(License).isSharingEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRoleId(scope: Role['scope'], name: Role['name']): Promise<Role['id']> {
|
export async function getInstanceOwner() {
|
||||||
return Container.get(RoleRepository)
|
const globalOwnerRole = await Container.get(RoleService).findGlobalOwnerRole();
|
||||||
.findRoleOrFail(scope, name)
|
|
||||||
.then((role) => role.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getInstanceOwner(): Promise<User> {
|
return Db.collections.User.findOneOrFail({
|
||||||
const ownerRoleId = await getRoleId('global', 'owner');
|
|
||||||
|
|
||||||
const owner = await Db.collections.User.findOneOrFail({
|
|
||||||
relations: ['globalRole'],
|
relations: ['globalRole'],
|
||||||
where: {
|
where: {
|
||||||
globalRoleId: ownerRoleId,
|
globalRoleId: globalOwnerRole.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return owner;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -30,13 +30,14 @@ import { WorkflowRunner } from '@/WorkflowRunner';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { RoleRepository } from '@db/repositories';
|
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
import { PermissionChecker } from './UserManagement/PermissionChecker';
|
import { PermissionChecker } from './UserManagement/PermissionChecker';
|
||||||
import { isWorkflowIdValid } from './utils';
|
import { isWorkflowIdValid } from './utils';
|
||||||
import { UserService } from './user/user.service';
|
import { UserService } from './user/user.service';
|
||||||
import type { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
import type { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||||
import type { RoleNames } from '@db/entities/Role';
|
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';
|
import { VariablesService } from './environments/variables/variables.service';
|
||||||
|
|
||||||
const ERROR_TRIGGER_TYPE = config.getEnv('nodes.errorTriggerType');
|
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;
|
where.userId = user.id;
|
||||||
}
|
}
|
||||||
if (roles?.length) {
|
if (roles?.length) {
|
||||||
const roleIds = await Db.collections.Role.find({
|
const roleIds = await Container.get(RoleRepository)
|
||||||
|
.find({
|
||||||
select: ['id'],
|
select: ['id'],
|
||||||
where: { name: In(roles), scope: 'workflow' },
|
where: { name: In(roles), scope: 'workflow' },
|
||||||
}).then((data) => data.map(({ id }) => id));
|
})
|
||||||
|
.then((role) => role.map(({ id }) => id));
|
||||||
|
|
||||||
where.roleId = In(roleIds);
|
where.roleId = In(roleIds);
|
||||||
}
|
}
|
||||||
const sharedWorkflows = await Db.collections.SharedWorkflow.find({
|
const sharedWorkflows = await Db.collections.SharedWorkflow.find({
|
||||||
|
@ -398,7 +402,7 @@ export async function isBelowOnboardingThreshold(user: User): Promise<boolean> {
|
||||||
let belowThreshold = true;
|
let belowThreshold = true;
|
||||||
const skippedTypes = ['n8n-nodes-base.start', 'n8n-nodes-base.stickyNote'];
|
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({
|
const ownedWorkflowsIds = await Db.collections.SharedWorkflow.find({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
|
|
@ -9,11 +9,11 @@ import type { User } from '@db/entities/User';
|
||||||
import { SharedCredentials } from '@db/entities/SharedCredentials';
|
import { SharedCredentials } from '@db/entities/SharedCredentials';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||||
import { RoleRepository } from '@db/repositories';
|
|
||||||
import { disableAutoGeneratedIds } from '@db/utils/commandHelpers';
|
import { disableAutoGeneratedIds } from '@db/utils/commandHelpers';
|
||||||
import { BaseCommand, UM_FIX_INSTRUCTION } from '../BaseCommand';
|
import { BaseCommand, UM_FIX_INSTRUCTION } from '../BaseCommand';
|
||||||
import type { ICredentialsEncrypted } from 'n8n-workflow';
|
import type { ICredentialsEncrypted } from 'n8n-workflow';
|
||||||
import { jsonParse } from 'n8n-workflow';
|
import { jsonParse } from 'n8n-workflow';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
export class ImportCredentialsCommand extends BaseCommand {
|
export class ImportCredentialsCommand extends BaseCommand {
|
||||||
static description = 'Import credentials';
|
static description = 'Import credentials';
|
||||||
|
@ -147,7 +147,7 @@ export class ImportCredentialsCommand extends BaseCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initOwnerCredentialRole() {
|
private async initOwnerCredentialRole() {
|
||||||
const ownerCredentialRole = await Container.get(RoleRepository).findCredentialOwnerRole();
|
const ownerCredentialRole = await Container.get(RoleService).findCredentialOwnerRole();
|
||||||
|
|
||||||
if (!ownerCredentialRole) {
|
if (!ownerCredentialRole) {
|
||||||
throw new Error(`Failed to find owner credential role. ${UM_FIX_INSTRUCTION}`);
|
throw new Error(`Failed to find owner credential role. ${UM_FIX_INSTRUCTION}`);
|
||||||
|
@ -170,7 +170,7 @@ export class ImportCredentialsCommand extends BaseCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getOwner() {
|
private async getOwner() {
|
||||||
const ownerGlobalRole = await Container.get(RoleRepository).findGlobalOwnerRole();
|
const ownerGlobalRole = await Container.get(RoleService).findGlobalOwnerRole();
|
||||||
|
|
||||||
const owner =
|
const owner =
|
||||||
ownerGlobalRole &&
|
ownerGlobalRole &&
|
||||||
|
|
|
@ -12,12 +12,12 @@ import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { setTagsForImport } from '@/TagHelpers';
|
import { setTagsForImport } from '@/TagHelpers';
|
||||||
import { RoleRepository } from '@db/repositories';
|
|
||||||
import { disableAutoGeneratedIds } from '@db/utils/commandHelpers';
|
import { disableAutoGeneratedIds } from '@db/utils/commandHelpers';
|
||||||
import type { ICredentialsDb, IWorkflowToImport } from '@/Interfaces';
|
import type { ICredentialsDb, IWorkflowToImport } from '@/Interfaces';
|
||||||
import { replaceInvalidCredentials } from '@/WorkflowHelpers';
|
import { replaceInvalidCredentials } from '@/WorkflowHelpers';
|
||||||
import { BaseCommand, UM_FIX_INSTRUCTION } from '../BaseCommand';
|
import { BaseCommand, UM_FIX_INSTRUCTION } from '../BaseCommand';
|
||||||
import { generateNanoId } from '@db/utils/generators';
|
import { generateNanoId } from '@db/utils/generators';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IWorkflowToImport[] {
|
function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IWorkflowToImport[] {
|
||||||
if (!Array.isArray(workflows)) {
|
if (!Array.isArray(workflows)) {
|
||||||
|
@ -208,7 +208,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initOwnerWorkflowRole() {
|
private async initOwnerWorkflowRole() {
|
||||||
const ownerWorkflowRole = await Container.get(RoleRepository).findWorkflowOwnerRole();
|
const ownerWorkflowRole = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||||
|
|
||||||
if (!ownerWorkflowRole) {
|
if (!ownerWorkflowRole) {
|
||||||
throw new Error(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`);
|
throw new Error(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`);
|
||||||
|
@ -231,7 +231,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getOwner() {
|
private async getOwner() {
|
||||||
const ownerGlobalRole = await Container.get(RoleRepository).findGlobalOwnerRole();
|
const ownerGlobalRole = await Container.get(RoleService).findGlobalOwnerRole();
|
||||||
|
|
||||||
const owner =
|
const owner =
|
||||||
ownerGlobalRole &&
|
ownerGlobalRole &&
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { Not } from 'typeorm';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||||
import { User } from '@db/entities/User';
|
import { User } from '@db/entities/User';
|
||||||
import { RoleRepository } from '@db/repositories';
|
|
||||||
import { BaseCommand } from '../BaseCommand';
|
import { BaseCommand } from '../BaseCommand';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
const defaultUserProps = {
|
const defaultUserProps = {
|
||||||
firstName: null,
|
firstName: null,
|
||||||
|
@ -21,8 +21,8 @@ export class Reset extends BaseCommand {
|
||||||
async run(): Promise<void> {
|
async run(): Promise<void> {
|
||||||
const owner = await this.getInstanceOwner();
|
const owner = await this.getInstanceOwner();
|
||||||
|
|
||||||
const ownerWorkflowRole = await Container.get(RoleRepository).findWorkflowOwnerRoleOrFail();
|
const ownerWorkflowRole = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||||
const ownerCredentialRole = await Container.get(RoleRepository).findCredentialOwnerRoleOrFail();
|
const ownerCredentialRole = await Container.get(RoleService).findCredentialOwnerRole();
|
||||||
|
|
||||||
await Db.collections.SharedWorkflow.update(
|
await Db.collections.SharedWorkflow.update(
|
||||||
{ userId: Not(owner.id), roleId: ownerWorkflowRole.id },
|
{ userId: Not(owner.id), roleId: ownerWorkflowRole.id },
|
||||||
|
@ -60,7 +60,7 @@ export class Reset extends BaseCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getInstanceOwner(): Promise<User> {
|
async getInstanceOwner(): Promise<User> {
|
||||||
const globalRole = await Container.get(RoleRepository).findGlobalOwnerRoleOrFail();
|
const globalRole = await Container.get(RoleService).findGlobalOwnerRole();
|
||||||
|
|
||||||
const owner = await Db.collections.User.findOneBy({ globalRoleId: globalRole.id });
|
const owner = await Db.collections.User.findOneBy({ globalRoleId: globalRole.id });
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,7 @@ export class E2EController {
|
||||||
];
|
];
|
||||||
|
|
||||||
const [{ id: globalOwnerRoleId }, { id: globalMemberRoleId }] = await this.roleRepo.save(
|
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 = [];
|
const users = [];
|
||||||
|
@ -151,6 +151,8 @@ export class E2EController {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('users', users);
|
||||||
|
|
||||||
await this.userRepo.insert(users);
|
await this.userRepo.insert(users);
|
||||||
|
|
||||||
await this.settingsRepo.update(
|
await this.settingsRepo.update(
|
||||||
|
|
|
@ -39,7 +39,6 @@ import { AuthIdentity } from '@db/entities/AuthIdentity';
|
||||||
import type { PostHogClient } from '@/posthog';
|
import type { PostHogClient } from '@/posthog';
|
||||||
import { isSamlLicensedAndEnabled } from '../sso/saml/samlHelpers';
|
import { isSamlLicensedAndEnabled } from '../sso/saml/samlHelpers';
|
||||||
import type {
|
import type {
|
||||||
RoleRepository,
|
|
||||||
SharedCredentialsRepository,
|
SharedCredentialsRepository,
|
||||||
SharedWorkflowRepository,
|
SharedWorkflowRepository,
|
||||||
UserRepository,
|
UserRepository,
|
||||||
|
@ -50,6 +49,7 @@ import { License } from '@/License';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||||
import type { JwtService } from '@/services/jwt.service';
|
import type { JwtService } from '@/services/jwt.service';
|
||||||
|
import type { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
@Authorized(['global', 'owner'])
|
@Authorized(['global', 'owner'])
|
||||||
@RestController('/users')
|
@RestController('/users')
|
||||||
|
@ -64,8 +64,6 @@ export class UsersController {
|
||||||
|
|
||||||
private userRepository: UserRepository;
|
private userRepository: UserRepository;
|
||||||
|
|
||||||
private roleRepository: RoleRepository;
|
|
||||||
|
|
||||||
private sharedCredentialsRepository: SharedCredentialsRepository;
|
private sharedCredentialsRepository: SharedCredentialsRepository;
|
||||||
|
|
||||||
private sharedWorkflowRepository: SharedWorkflowRepository;
|
private sharedWorkflowRepository: SharedWorkflowRepository;
|
||||||
|
@ -78,6 +76,8 @@ export class UsersController {
|
||||||
|
|
||||||
private postHog?: PostHogClient;
|
private postHog?: PostHogClient;
|
||||||
|
|
||||||
|
private roleService: RoleService;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
config,
|
config,
|
||||||
logger,
|
logger,
|
||||||
|
@ -88,32 +88,31 @@ export class UsersController {
|
||||||
mailer,
|
mailer,
|
||||||
jwtService,
|
jwtService,
|
||||||
postHog,
|
postHog,
|
||||||
|
roleService,
|
||||||
}: {
|
}: {
|
||||||
config: Config;
|
config: Config;
|
||||||
logger: ILogger;
|
logger: ILogger;
|
||||||
externalHooks: IExternalHooksClass;
|
externalHooks: IExternalHooksClass;
|
||||||
internalHooks: IInternalHooksClass;
|
internalHooks: IInternalHooksClass;
|
||||||
repositories: Pick<
|
repositories: Pick<IDatabaseCollections, 'User' | 'SharedCredentials' | 'SharedWorkflow'>;
|
||||||
IDatabaseCollections,
|
|
||||||
'User' | 'Role' | 'SharedCredentials' | 'SharedWorkflow'
|
|
||||||
>;
|
|
||||||
activeWorkflowRunner: ActiveWorkflowRunner;
|
activeWorkflowRunner: ActiveWorkflowRunner;
|
||||||
mailer: UserManagementMailer;
|
mailer: UserManagementMailer;
|
||||||
jwtService: JwtService;
|
jwtService: JwtService;
|
||||||
postHog?: PostHogClient;
|
postHog?: PostHogClient;
|
||||||
|
roleService: RoleService;
|
||||||
}) {
|
}) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.externalHooks = externalHooks;
|
this.externalHooks = externalHooks;
|
||||||
this.internalHooks = internalHooks;
|
this.internalHooks = internalHooks;
|
||||||
this.userRepository = repositories.User;
|
this.userRepository = repositories.User;
|
||||||
this.roleRepository = repositories.Role;
|
|
||||||
this.sharedCredentialsRepository = repositories.SharedCredentials;
|
this.sharedCredentialsRepository = repositories.SharedCredentials;
|
||||||
this.sharedWorkflowRepository = repositories.SharedWorkflow;
|
this.sharedWorkflowRepository = repositories.SharedWorkflow;
|
||||||
this.activeWorkflowRunner = activeWorkflowRunner;
|
this.activeWorkflowRunner = activeWorkflowRunner;
|
||||||
this.mailer = mailer;
|
this.mailer = mailer;
|
||||||
this.jwtService = jwtService;
|
this.jwtService = jwtService;
|
||||||
this.postHog = postHog;
|
this.postHog = postHog;
|
||||||
|
this.roleService = roleService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -176,7 +175,7 @@ export class UsersController {
|
||||||
createUsers[invite.email.toLowerCase()] = null;
|
createUsers[invite.email.toLowerCase()] = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const role = await this.roleRepository.findGlobalMemberRole();
|
const role = await this.roleService.findGlobalMemberRole();
|
||||||
|
|
||||||
if (!role) {
|
if (!role) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
|
@ -469,8 +468,8 @@ export class UsersController {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [workflowOwnerRole, credentialOwnerRole] = await Promise.all([
|
const [workflowOwnerRole, credentialOwnerRole] = await Promise.all([
|
||||||
this.roleRepository.findWorkflowOwnerRole(),
|
this.roleService.findWorkflowOwnerRole(),
|
||||||
this.roleRepository.findCredentialOwnerRole(),
|
this.roleService.findCredentialOwnerRole(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (transferId) {
|
if (transferId) {
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import type { DeleteResult, EntityManager, FindOptionsWhere } from 'typeorm';
|
import type { DeleteResult, EntityManager, FindOptionsWhere } from 'typeorm';
|
||||||
import { In, Not } from 'typeorm';
|
import { In, Not } from 'typeorm';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import { RoleService } from '@/role/role.service';
|
|
||||||
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||||
import { SharedCredentials } from '@db/entities/SharedCredentials';
|
import { SharedCredentials } from '@db/entities/SharedCredentials';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { UserService } from '@/user/user.service';
|
import { UserService } from '@/user/user.service';
|
||||||
import { CredentialsService } from './credentials.service';
|
import { CredentialsService } from './credentials.service';
|
||||||
import type { CredentialWithSharings } from './credentials.types';
|
import type { CredentialWithSharings } from './credentials.types';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
import Container from 'typedi';
|
||||||
|
|
||||||
export class EECredentialsService extends CredentialsService {
|
export class EECredentialsService extends CredentialsService {
|
||||||
static async isOwned(
|
static async isOwned(
|
||||||
|
@ -77,10 +78,8 @@ export class EECredentialsService extends CredentialsService {
|
||||||
credential: CredentialsEntity,
|
credential: CredentialsEntity,
|
||||||
shareWithIds: string[],
|
shareWithIds: string[],
|
||||||
): Promise<SharedCredentials[]> {
|
): Promise<SharedCredentials[]> {
|
||||||
const [users, role] = await Promise.all([
|
const users = await UserService.getByIds(transaction, shareWithIds);
|
||||||
UserService.getByIds(transaction, shareWithIds),
|
const role = await Container.get(RoleService).findCredentialUserRole();
|
||||||
RoleService.trxGet(transaction, { scope: 'credential', name: 'user' }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const newSharedCredentials = users
|
const newSharedCredentials = users
|
||||||
.filter((user) => !user.isPending)
|
.filter((user) => !user.isPending)
|
||||||
|
|
|
@ -21,9 +21,9 @@ import { SharedCredentials } from '@db/entities/SharedCredentials';
|
||||||
import { validateEntity } from '@/GenericHelpers';
|
import { validateEntity } from '@/GenericHelpers';
|
||||||
import { ExternalHooks } from '@/ExternalHooks';
|
import { ExternalHooks } from '@/ExternalHooks';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { RoleRepository } from '@db/repositories';
|
|
||||||
import type { CredentialRequest } from '@/requests';
|
import type { CredentialRequest } from '@/requests';
|
||||||
import { CredentialTypes } from '@/CredentialTypes';
|
import { CredentialTypes } from '@/CredentialTypes';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
export class CredentialsService {
|
export class CredentialsService {
|
||||||
static async get(
|
static async get(
|
||||||
|
@ -221,7 +221,7 @@ export class CredentialsService {
|
||||||
|
|
||||||
await Container.get(ExternalHooks).run('credentials.create', [encryptedData]);
|
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 result = await Db.transaction(async (transactionManager) => {
|
||||||
const savedCredential = await transactionManager.save<CredentialsEntity>(newCredential);
|
const savedCredential = await transactionManager.save<CredentialsEntity>(newCredential);
|
||||||
|
|
|
@ -32,4 +32,8 @@ export class Role extends WithTimestamps {
|
||||||
|
|
||||||
@OneToMany('SharedCredentials', 'role')
|
@OneToMany('SharedCredentials', 'role')
|
||||||
sharedCredentials: SharedCredentials[];
|
sharedCredentials: SharedCredentials[];
|
||||||
|
|
||||||
|
get cacheKey() {
|
||||||
|
return `role:${this.scope}:${this.name}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,51 +9,7 @@ export class RoleRepository extends Repository<Role> {
|
||||||
super(Role, dataSource.manager);
|
super(Role, dataSource.manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findGlobalOwnerRole(): Promise<Role | null> {
|
async findRole(scope: RoleScopes, name: RoleNames) {
|
||||||
return this.findRole('global', 'owner');
|
|
||||||
}
|
|
||||||
|
|
||||||
async findGlobalOwnerRoleOrFail(): Promise<Role> {
|
|
||||||
return this.findRoleOrFail('global', 'owner');
|
|
||||||
}
|
|
||||||
|
|
||||||
async findGlobalMemberRole(): Promise<Role | null> {
|
|
||||||
return this.findRole('global', 'member');
|
|
||||||
}
|
|
||||||
|
|
||||||
async findGlobalMemberRoleOrFail(): Promise<Role> {
|
|
||||||
return this.findRoleOrFail('global', 'member');
|
|
||||||
}
|
|
||||||
|
|
||||||
async findWorkflowOwnerRole(): Promise<Role | null> {
|
|
||||||
return this.findRole('workflow', 'owner');
|
|
||||||
}
|
|
||||||
|
|
||||||
async findWorkflowOwnerRoleOrFail(): Promise<Role> {
|
|
||||||
return this.findRoleOrFail('workflow', 'owner');
|
|
||||||
}
|
|
||||||
|
|
||||||
async findWorkflowEditorRoleOrFail(): Promise<Role> {
|
|
||||||
return this.findRoleOrFail('workflow', 'editor');
|
|
||||||
}
|
|
||||||
|
|
||||||
async findCredentialOwnerRole(): Promise<Role | null> {
|
|
||||||
return this.findRole('credential', 'owner');
|
|
||||||
}
|
|
||||||
|
|
||||||
async findCredentialOwnerRoleOrFail(): Promise<Role> {
|
|
||||||
return this.findRoleOrFail('credential', 'owner');
|
|
||||||
}
|
|
||||||
|
|
||||||
async findCredentialUserRole(): Promise<Role | null> {
|
|
||||||
return this.findRole('credential', 'user');
|
|
||||||
}
|
|
||||||
|
|
||||||
async findRole(scope: RoleScopes, name: RoleNames): Promise<Role | null> {
|
|
||||||
return this.findOne({ where: { scope, name } });
|
return this.findOne({ where: { scope, name } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async findRoleOrFail(scope: RoleScopes, name: RoleNames): Promise<Role> {
|
|
||||||
return this.findOneOrFail({ where: { scope, name } });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Service } from 'typedi';
|
import Container, { Service } from 'typedi';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {
|
import {
|
||||||
SOURCE_CONTROL_CREDENTIAL_EXPORT_FOLDER,
|
SOURCE_CONTROL_CREDENTIAL_EXPORT_FOLDER,
|
||||||
|
@ -25,6 +25,7 @@ import { isUniqueConstraintError } from '@/ResponseHelper';
|
||||||
import type { SourceControlWorkflowVersionId } from './types/sourceControlWorkflowVersionId';
|
import type { SourceControlWorkflowVersionId } from './types/sourceControlWorkflowVersionId';
|
||||||
import { getCredentialExportPath, getWorkflowExportPath } from './sourceControlHelper.ee';
|
import { getCredentialExportPath, getWorkflowExportPath } from './sourceControlHelper.ee';
|
||||||
import type { SourceControlledFile } from './types/sourceControlledFile';
|
import type { SourceControlledFile } from './types/sourceControlledFile';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
import { VariablesService } from '../variables/variables.service';
|
import { VariablesService } from '../variables/variables.service';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
|
@ -49,39 +50,33 @@ export class SourceControlImportService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getOwnerGlobalRole() {
|
private async getOwnerGlobalRole() {
|
||||||
const ownerCredentiallRole = await Db.collections.Role.findOne({
|
const globalOwnerRole = await Container.get(RoleService).findGlobalOwnerRole();
|
||||||
where: { name: 'owner', scope: 'global' },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!ownerCredentiallRole) {
|
if (!globalOwnerRole) {
|
||||||
throw new Error(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
throw new Error(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ownerCredentiallRole;
|
return globalOwnerRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getOwnerCredentialRole() {
|
private async getCredentialOwnerRole() {
|
||||||
const ownerCredentiallRole = await Db.collections.Role.findOne({
|
const credentialOwnerRole = await Container.get(RoleService).findCredentialOwnerRole();
|
||||||
where: { name: 'owner', scope: 'credential' },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!ownerCredentiallRole) {
|
if (!credentialOwnerRole) {
|
||||||
throw new Error(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
throw new Error(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ownerCredentiallRole;
|
return credentialOwnerRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getOwnerWorkflowRole() {
|
private async getWorkflowOwnerRole() {
|
||||||
const ownerWorkflowRole = await Db.collections.Role.findOne({
|
const workflowOwnerRole = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||||
where: { name: 'owner', scope: 'workflow' },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!ownerWorkflowRole) {
|
if (!workflowOwnerRole) {
|
||||||
throw new Error(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`);
|
throw new Error(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ownerWorkflowRole;
|
return workflowOwnerRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async importCredentialsFromFiles(
|
private async importCredentialsFromFiles(
|
||||||
|
@ -92,7 +87,7 @@ export class SourceControlImportService {
|
||||||
absolute: true,
|
absolute: true,
|
||||||
});
|
});
|
||||||
const existingCredentials = await Db.collections.Credentials.find();
|
const existingCredentials = await Db.collections.Credentials.find();
|
||||||
const ownerCredentialRole = await this.getOwnerCredentialRole();
|
const ownerCredentialRole = await this.getCredentialOwnerRole();
|
||||||
const ownerGlobalRole = await this.getOwnerGlobalRole();
|
const ownerGlobalRole = await this.getOwnerGlobalRole();
|
||||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||||
let importCredentialsResult: Array<{ id: string; name: string; type: string }> = [];
|
let importCredentialsResult: Array<{ id: string; name: string; type: string }> = [];
|
||||||
|
@ -280,7 +275,7 @@ export class SourceControlImportService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async importWorkflowFromWorkFolder(candidates: SourceControlledFile[], userId: string) {
|
public async importWorkflowFromWorkFolder(candidates: SourceControlledFile[], userId: string) {
|
||||||
const ownerWorkflowRole = await this.getOwnerWorkflowRole();
|
const ownerWorkflowRole = await this.getWorkflowOwnerRole();
|
||||||
const workflowRunner = this.activeWorkflowRunner;
|
const workflowRunner = this.activeWorkflowRunner;
|
||||||
const candidateIds = candidates.map((c) => c.id);
|
const candidateIds = candidates.map((c) => c.id);
|
||||||
const existingWorkflows = await Db.collections.Workflow.find({
|
const existingWorkflows = await Db.collections.Workflow.find({
|
||||||
|
@ -401,7 +396,7 @@ export class SourceControlImportService {
|
||||||
},
|
},
|
||||||
select: ['id', 'name', 'type', 'data'],
|
select: ['id', 'name', 'type', 'data'],
|
||||||
});
|
});
|
||||||
const ownerCredentialRole = await this.getOwnerCredentialRole();
|
const ownerCredentialRole = await this.getCredentialOwnerRole();
|
||||||
const ownerGlobalRole = await this.getOwnerGlobalRole();
|
const ownerGlobalRole = await this.getOwnerGlobalRole();
|
||||||
const existingSharedCredentials = await Db.collections.SharedCredentials.find({
|
const existingSharedCredentials = await Db.collections.SharedCredentials.find({
|
||||||
select: ['userId', 'credentialsId', 'roleId'],
|
select: ['userId', 'credentialsId', 'roleId'],
|
||||||
|
|
|
@ -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<Role>) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +1,15 @@
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
import { CacheService } from './cache.service';
|
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 type { User } from '@/databases/entities/User';
|
||||||
|
import { RoleService } from './role.service';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class OwnershipService {
|
export class OwnershipService {
|
||||||
constructor(
|
constructor(
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private userRepository: UserRepository,
|
private userRepository: UserRepository,
|
||||||
private roleRepository: RoleRepository,
|
private roleService: RoleService,
|
||||||
private sharedWorkflowRepository: SharedWorkflowRepository,
|
private sharedWorkflowRepository: SharedWorkflowRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ export class OwnershipService {
|
||||||
|
|
||||||
if (cachedValue) return this.userRepository.create(cachedValue);
|
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');
|
if (!workflowOwnerRole) throw new Error('Failed to find workflow owner role');
|
||||||
|
|
||||||
|
|
94
packages/cli/src/services/role.service.ts
Normal file
94
packages/cli/src/services/role.service.ts
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ import config from '@/config';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import { AuthIdentity } from '@db/entities/AuthIdentity';
|
import { AuthIdentity } from '@db/entities/AuthIdentity';
|
||||||
import { User } from '@db/entities/User';
|
import { User } from '@db/entities/User';
|
||||||
import { RoleRepository } from '@db/repositories';
|
|
||||||
import { License } from '@/License';
|
import { License } from '@/License';
|
||||||
import { AuthError, InternalServerError } from '@/ResponseHelper';
|
import { AuthError, InternalServerError } from '@/ResponseHelper';
|
||||||
import { hashPassword } from '@/UserManagement/UserManagementHelper';
|
import { hashPassword } from '@/UserManagement/UserManagementHelper';
|
||||||
|
@ -20,6 +19,7 @@ import {
|
||||||
} from '../ssoHelpers';
|
} from '../ssoHelpers';
|
||||||
import { getServiceProviderConfigTestReturnUrl } from './serviceProvider.ee';
|
import { getServiceProviderConfigTestReturnUrl } from './serviceProvider.ee';
|
||||||
import type { SamlConfiguration } from './types/requests';
|
import type { SamlConfiguration } from './types/requests';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
/**
|
/**
|
||||||
* Check whether the SAML feature is licensed and enabled in the instance
|
* 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.email = lowerCasedEmail;
|
||||||
user.firstName = attributes.firstName;
|
user.firstName = attributes.firstName;
|
||||||
user.lastName = attributes.lastName;
|
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
|
// generates a password that is not used or known to the user
|
||||||
user.password = await hashPassword(generatePassword());
|
user.password = await hashPassword(generatePassword());
|
||||||
authIdentity.providerId = attributes.userPrincipalName;
|
authIdentity.providerId = attributes.userPrincipalName;
|
||||||
|
|
|
@ -11,7 +11,6 @@ import { isSharingEnabled, rightDiff } from '@/UserManagement/UserManagementHelp
|
||||||
import { EEWorkflowsService as EEWorkflows } from './workflows.services.ee';
|
import { EEWorkflowsService as EEWorkflows } from './workflows.services.ee';
|
||||||
import { ExternalHooks } from '@/ExternalHooks';
|
import { ExternalHooks } from '@/ExternalHooks';
|
||||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||||
import { RoleRepository } from '@db/repositories';
|
|
||||||
import { LoggerProxy } from 'n8n-workflow';
|
import { LoggerProxy } from 'n8n-workflow';
|
||||||
import * as TagHelpers from '@/TagHelpers';
|
import * as TagHelpers from '@/TagHelpers';
|
||||||
import { EECredentialsService as EECredentials } from '../credentials/credentials.service.ee';
|
import { EECredentialsService as EECredentials } from '../credentials/credentials.service.ee';
|
||||||
|
@ -20,6 +19,7 @@ import * as GenericHelpers from '@/GenericHelpers';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
export const EEWorkflowController = express.Router();
|
export const EEWorkflowController = express.Router();
|
||||||
|
@ -164,7 +164,7 @@ EEWorkflowController.post(
|
||||||
await Db.transaction(async (transactionManager) => {
|
await Db.transaction(async (transactionManager) => {
|
||||||
savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
|
savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
|
||||||
|
|
||||||
const role = await Container.get(RoleRepository).findWorkflowOwnerRoleOrFail();
|
const role = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||||
|
|
||||||
const newSharedWorkflow = new SharedWorkflow();
|
const newSharedWorkflow = new SharedWorkflow();
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ EEWorkflowController.get(
|
||||||
ResponseHelper.send(async (req: WorkflowRequest.GetAll) => {
|
ResponseHelper.send(async (req: WorkflowRequest.GetAll) => {
|
||||||
const [workflows, workflowOwnerRole] = await Promise.all([
|
const [workflows, workflowOwnerRole] = await Promise.all([
|
||||||
EEWorkflows.getMany(req.user, req.query.filter),
|
EEWorkflows.getMany(req.user, req.query.filter),
|
||||||
Container.get(RoleRepository).findWorkflowOwnerRoleOrFail(),
|
Container.get(RoleService).findWorkflowOwnerRole(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return workflows.map((workflow) => {
|
return workflows.map((workflow) => {
|
||||||
|
|
|
@ -12,7 +12,6 @@ import config from '@/config';
|
||||||
import * as TagHelpers from '@/TagHelpers';
|
import * as TagHelpers from '@/TagHelpers';
|
||||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import { RoleRepository } from '@db/repositories';
|
|
||||||
import { validateEntity } from '@/GenericHelpers';
|
import { validateEntity } from '@/GenericHelpers';
|
||||||
import { ExternalHooks } from '@/ExternalHooks';
|
import { ExternalHooks } from '@/ExternalHooks';
|
||||||
import { getLogger } from '@/Logger';
|
import { getLogger } from '@/Logger';
|
||||||
|
@ -24,6 +23,7 @@ import { whereClause } from '@/UserManagement/UserManagementHelper';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
export const workflowsController = express.Router();
|
export const workflowsController = express.Router();
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ workflowsController.post(
|
||||||
await Db.transaction(async (transactionManager) => {
|
await Db.transaction(async (transactionManager) => {
|
||||||
savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
|
savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
|
||||||
|
|
||||||
const role = await Container.get(RoleRepository).findWorkflowOwnerRoleOrFail();
|
const role = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||||
|
|
||||||
const newSharedWorkflow = new SharedWorkflow();
|
const newSharedWorkflow = new SharedWorkflow();
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import { RoleService } from '@/role/role.service';
|
|
||||||
import { UserService } from '@/user/user.service';
|
import { UserService } from '@/user/user.service';
|
||||||
import { WorkflowsService } from './workflows.services';
|
import { WorkflowsService } from './workflows.services';
|
||||||
import type {
|
import type {
|
||||||
|
@ -19,6 +18,8 @@ import type {
|
||||||
import { EECredentialsService as EECredentials } from '@/credentials/credentials.service.ee';
|
import { EECredentialsService as EECredentials } from '@/credentials/credentials.service.ee';
|
||||||
import { getSharedWorkflowIds } from '@/WorkflowHelpers';
|
import { getSharedWorkflowIds } from '@/WorkflowHelpers';
|
||||||
import { NodeOperationError } from 'n8n-workflow';
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
import Container from 'typedi';
|
||||||
|
|
||||||
export class EEWorkflowsService extends WorkflowsService {
|
export class EEWorkflowsService extends WorkflowsService {
|
||||||
static async getWorkflowIdsForUser(user: User) {
|
static async getWorkflowIdsForUser(user: User) {
|
||||||
|
@ -68,10 +69,8 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||||
workflow: WorkflowEntity,
|
workflow: WorkflowEntity,
|
||||||
shareWithIds: string[],
|
shareWithIds: string[],
|
||||||
): Promise<SharedWorkflow[]> {
|
): Promise<SharedWorkflow[]> {
|
||||||
const [users, role] = await Promise.all([
|
const users = await UserService.getByIds(transaction, shareWithIds);
|
||||||
UserService.getByIds(transaction, shareWithIds),
|
const role = await Container.get(RoleService).findWorkflowEditorRole();
|
||||||
RoleService.trxGet(transaction, { scope: 'workflow', name: 'editor' }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const newSharedWorkflows = users.reduce<SharedWorkflow[]>((acc, user) => {
|
const newSharedWorkflows = users.reduce<SharedWorkflow[]>((acc, user) => {
|
||||||
if (user.isPending) {
|
if (user.isPending) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ export function randomApiKey() {
|
||||||
return `n8n_api_${randomBytes(20).toString('hex')}`;
|
return `n8n_api_${randomBytes(20).toString('hex')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const chooseRandomly = <T>(array: T[]) => array[Math.floor(Math.random() * array.length)];
|
export const chooseRandomly = <T>(array: T[]) => array[Math.floor(Math.random() * array.length)];
|
||||||
|
|
||||||
export const randomInteger = (max = 1000) => Math.floor(Math.random() * max);
|
export const randomInteger = (max = 1000) => Math.floor(Math.random() * max);
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ import type { Role } from '@db/entities/Role';
|
||||||
import type { TagEntity } from '@db/entities/TagEntity';
|
import type { TagEntity } from '@db/entities/TagEntity';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import { RoleRepository } from '@db/repositories';
|
|
||||||
import type { ICredentialsDb } from '@/Interfaces';
|
import type { ICredentialsDb } from '@/Interfaces';
|
||||||
|
|
||||||
import { DB_INITIALIZATION_TIMEOUT } from './constants';
|
import { DB_INITIALIZATION_TIMEOUT } from './constants';
|
||||||
|
@ -34,6 +33,7 @@ import type {
|
||||||
} from './types';
|
} from './types';
|
||||||
import type { ExecutionData } from '@db/entities/ExecutionData';
|
import type { ExecutionData } from '@db/entities/ExecutionData';
|
||||||
import { generateNanoId } from '@db/utils/generators';
|
import { generateNanoId } from '@db/utils/generators';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
import { VariablesService } from '@/environments/variables/variables.service';
|
import { VariablesService } from '@/environments/variables/variables.service';
|
||||||
|
|
||||||
export type TestDBType = 'postgres' | 'mysql';
|
export type TestDBType = 'postgres' | 'mysql';
|
||||||
|
@ -151,7 +151,7 @@ export async function saveCredential(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function shareCredentialWithUsers(credential: CredentialsEntity, users: User[]) {
|
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) =>
|
const newSharedCredentials = users.map((user) =>
|
||||||
Db.collections.SharedCredentials.create({
|
Db.collections.SharedCredentials.create({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
@ -276,23 +276,23 @@ export async function addApiKey(user: User): Promise<User> {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
export async function getGlobalOwnerRole() {
|
export async function getGlobalOwnerRole() {
|
||||||
return Container.get(RoleRepository).findGlobalOwnerRoleOrFail();
|
return Container.get(RoleService).findGlobalOwnerRole();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getGlobalMemberRole() {
|
export async function getGlobalMemberRole() {
|
||||||
return Container.get(RoleRepository).findGlobalMemberRoleOrFail();
|
return Container.get(RoleService).findGlobalMemberRole();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getWorkflowOwnerRole() {
|
export async function getWorkflowOwnerRole() {
|
||||||
return Container.get(RoleRepository).findWorkflowOwnerRoleOrFail();
|
return Container.get(RoleService).findWorkflowOwnerRole();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getWorkflowEditorRole() {
|
export async function getWorkflowEditorRole() {
|
||||||
return Container.get(RoleRepository).findWorkflowEditorRoleOrFail();
|
return Container.get(RoleService).findWorkflowEditorRole();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCredentialOwnerRole() {
|
export async function getCredentialOwnerRole() {
|
||||||
return Container.get(RoleRepository).findCredentialOwnerRoleOrFail();
|
return Container.get(RoleService).findCredentialOwnerRole();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllRoles() {
|
export async function getAllRoles() {
|
||||||
|
|
|
@ -50,6 +50,7 @@ import { AUTHLESS_ENDPOINTS, PUBLIC_API_REST_PATH_SEGMENT, REST_PATH_SEGMENT } f
|
||||||
import type { EndpointGroup, SetupProps, TestServer } from '../types';
|
import type { EndpointGroup, SetupProps, TestServer } from '../types';
|
||||||
import { mockInstance } from './mocking';
|
import { mockInstance } from './mocking';
|
||||||
import { JwtService } from '@/services/jwt.service';
|
import { JwtService } from '@/services/jwt.service';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin to prefix a path segment into a request URL pathname.
|
* Plugin to prefix a path segment into a request URL pathname.
|
||||||
|
@ -264,6 +265,7 @@ export const setupTestServer = ({
|
||||||
activeWorkflowRunner: Container.get(ActiveWorkflowRunner),
|
activeWorkflowRunner: Container.get(ActiveWorkflowRunner),
|
||||||
logger,
|
logger,
|
||||||
jwtService,
|
jwtService,
|
||||||
|
roleService: Container.get(RoleService),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -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) =>
|
const createRole = (scope: RoleScopes, name: RoleNames) =>
|
||||||
Object.assign(new Role(), { name, scope, id: `${randomInteger()}` });
|
Object.assign(new Role(), { name, scope, id: `${randomInteger()}` });
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { OwnershipService } from '@/services/ownership.service';
|
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 { mockInstance } from '../../integration/shared/utils';
|
||||||
import { Role } from '@/databases/entities/Role';
|
import { Role } from '@/databases/entities/Role';
|
||||||
import { randomInteger } from '../../integration/shared/random';
|
import { randomInteger } from '../../integration/shared/random';
|
||||||
import { SharedWorkflow } from '@/databases/entities/SharedWorkflow';
|
import { SharedWorkflow } from '@/databases/entities/SharedWorkflow';
|
||||||
import { CacheService } from '@/services/cache.service';
|
import { CacheService } from '@/services/cache.service';
|
||||||
import { User } from '@/databases/entities/User';
|
import { User } from '@/databases/entities/User';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
const wfOwnerRole = () =>
|
const wfOwnerRole = () =>
|
||||||
Object.assign(new Role(), {
|
Object.assign(new Role(), {
|
||||||
|
@ -16,14 +17,14 @@ const wfOwnerRole = () =>
|
||||||
|
|
||||||
describe('OwnershipService', () => {
|
describe('OwnershipService', () => {
|
||||||
const cacheService = mockInstance(CacheService);
|
const cacheService = mockInstance(CacheService);
|
||||||
const roleRepository = mockInstance(RoleRepository);
|
const roleService = mockInstance(RoleService);
|
||||||
const userRepository = mockInstance(UserRepository);
|
const userRepository = mockInstance(UserRepository);
|
||||||
const sharedWorkflowRepository = mockInstance(SharedWorkflowRepository);
|
const sharedWorkflowRepository = mockInstance(SharedWorkflowRepository);
|
||||||
|
|
||||||
const ownershipService = new OwnershipService(
|
const ownershipService = new OwnershipService(
|
||||||
cacheService,
|
cacheService,
|
||||||
userRepository,
|
userRepository,
|
||||||
roleRepository,
|
roleService,
|
||||||
sharedWorkflowRepository,
|
sharedWorkflowRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ describe('OwnershipService', () => {
|
||||||
|
|
||||||
describe('getWorkflowOwner()', () => {
|
describe('getWorkflowOwner()', () => {
|
||||||
test('should retrieve a workflow owner', async () => {
|
test('should retrieve a workflow owner', async () => {
|
||||||
roleRepository.findWorkflowOwnerRole.mockResolvedValueOnce(wfOwnerRole());
|
roleService.findWorkflowOwnerRole.mockResolvedValueOnce(wfOwnerRole());
|
||||||
|
|
||||||
const mockOwner = new User();
|
const mockOwner = new User();
|
||||||
const mockNonOwner = new User();
|
const mockNonOwner = new User();
|
||||||
|
@ -52,13 +53,13 @@ describe('OwnershipService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should throw if no workflow owner role found', async () => {
|
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();
|
await expect(ownershipService.getWorkflowOwnerCached('some-workflow-id')).rejects.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should throw if no workflow owner found', async () => {
|
test('should throw if no workflow owner found', async () => {
|
||||||
roleRepository.findWorkflowOwnerRole.mockResolvedValueOnce(wfOwnerRole());
|
roleService.findWorkflowOwnerRole.mockResolvedValueOnce(wfOwnerRole());
|
||||||
|
|
||||||
sharedWorkflowRepository.findOneOrFail.mockRejectedValue(new Error());
|
sharedWorkflowRepository.findOneOrFail.mockRejectedValue(new Error());
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,80 @@
|
||||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||||
|
import type { RoleNames, RoleScopes } from '@db/entities/Role';
|
||||||
import { Role } 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 { 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', () => {
|
describe('RoleService', () => {
|
||||||
const sharedWorkflowRepository = mockInstance(SharedWorkflowRepository);
|
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 userId = '1';
|
||||||
const workflowId = '42';
|
const workflowId = '42';
|
||||||
|
|
||||||
describe('getUserRoleForWorkflow', () => {
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`findRoleByUserAndWorkflow() [${tag}]`, () => {
|
||||||
test('should return the role if a shared workflow is found', async () => {
|
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() });
|
const sharedWorkflow = Object.assign(new SharedWorkflow(), { role: new Role() });
|
||||||
sharedWorkflowRepository.findOne.mockResolvedValueOnce(sharedWorkflow);
|
sharedWorkflowRepository.findOne.mockResolvedValueOnce(sharedWorkflow);
|
||||||
const role = await roleService.getUserRoleForWorkflow(userId, workflowId);
|
const returnedRole = await roleService.findRoleByUserAndWorkflow(userId, workflowId);
|
||||||
expect(role).toBe(sharedWorkflow.role);
|
|
||||||
|
expect(returnedRole).toBe(sharedWorkflow.role);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return undefined if no shared workflow is found', async () => {
|
test('should return undefined if no shared workflow is found', async () => {
|
||||||
|
config.set('cache.enabled', cacheEnabled);
|
||||||
|
|
||||||
sharedWorkflowRepository.findOne.mockResolvedValueOnce(null);
|
sharedWorkflowRepository.findOne.mockResolvedValueOnce(null);
|
||||||
const role = await roleService.getUserRoleForWorkflow(userId, workflowId);
|
const returnedRole = await roleService.findRoleByUserAndWorkflow(userId, workflowId);
|
||||||
expect(role).toBeUndefined();
|
|
||||||
|
expect(returnedRole).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue