mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
refactor(core): Decouple RoleService from repositories (#14944)
This commit is contained in:
@@ -8,11 +8,11 @@ import nock from 'nock';
|
|||||||
|
|
||||||
import { Time } from '@/constants';
|
import { Time } from '@/constants';
|
||||||
import { OAuth1CredentialController } from '@/controllers/oauth/oauth1-credential.controller';
|
import { OAuth1CredentialController } from '@/controllers/oauth/oauth1-credential.controller';
|
||||||
|
import { CredentialsFinderService } from '@/credentials/credentials-finder.service';
|
||||||
import { CredentialsHelper } from '@/credentials-helper';
|
import { CredentialsHelper } from '@/credentials-helper';
|
||||||
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
|
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
|
||||||
import type { User } from '@/databases/entities/user';
|
import type { User } from '@/databases/entities/user';
|
||||||
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
|
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
|
||||||
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
|
|
||||||
import { VariablesService } from '@/environments.ee/variables/variables.service.ee';
|
import { VariablesService } from '@/environments.ee/variables/variables.service.ee';
|
||||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||||
@@ -38,7 +38,7 @@ describe('OAuth1CredentialController', () => {
|
|||||||
Container.set(Cipher, cipher);
|
Container.set(Cipher, cipher);
|
||||||
const credentialsHelper = mockInstance(CredentialsHelper);
|
const credentialsHelper = mockInstance(CredentialsHelper);
|
||||||
const credentialsRepository = mockInstance(CredentialsRepository);
|
const credentialsRepository = mockInstance(CredentialsRepository);
|
||||||
const sharedCredentialsRepository = mockInstance(SharedCredentialsRepository);
|
const credentialsFinderService = mockInstance(CredentialsFinderService);
|
||||||
|
|
||||||
const csrfSecret = 'csrf-secret';
|
const csrfSecret = 'csrf-secret';
|
||||||
const user = mock<User>({
|
const user = mock<User>({
|
||||||
@@ -73,7 +73,7 @@ describe('OAuth1CredentialController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should throw a NotFoundError when no matching credential is found for the user', async () => {
|
it('should throw a NotFoundError when no matching credential is found for the user', async () => {
|
||||||
sharedCredentialsRepository.findCredentialForUser.mockResolvedValueOnce(null);
|
credentialsFinderService.findCredentialForUser.mockResolvedValueOnce(null);
|
||||||
|
|
||||||
const req = mock<OAuthRequest.OAuth1Credential.Auth>({ user, query: { id: '1' } });
|
const req = mock<OAuthRequest.OAuth1Credential.Auth>({ user, query: { id: '1' } });
|
||||||
await expect(controller.getAuthUri(req)).rejects.toThrowError(
|
await expect(controller.getAuthUri(req)).rejects.toThrowError(
|
||||||
@@ -84,7 +84,7 @@ describe('OAuth1CredentialController', () => {
|
|||||||
it('should return a valid auth URI', async () => {
|
it('should return a valid auth URI', async () => {
|
||||||
jest.spyOn(Csrf.prototype, 'secretSync').mockReturnValueOnce(csrfSecret);
|
jest.spyOn(Csrf.prototype, 'secretSync').mockReturnValueOnce(csrfSecret);
|
||||||
jest.spyOn(Csrf.prototype, 'create').mockReturnValueOnce('token');
|
jest.spyOn(Csrf.prototype, 'create').mockReturnValueOnce('token');
|
||||||
sharedCredentialsRepository.findCredentialForUser.mockResolvedValueOnce(credential);
|
credentialsFinderService.findCredentialForUser.mockResolvedValueOnce(credential);
|
||||||
credentialsHelper.getDecrypted.mockResolvedValueOnce({});
|
credentialsHelper.getDecrypted.mockResolvedValueOnce({});
|
||||||
credentialsHelper.applyDefaultsAndOverwrites.mockResolvedValueOnce({
|
credentialsHelper.applyDefaultsAndOverwrites.mockResolvedValueOnce({
|
||||||
requestTokenUrl: 'https://example.domain/oauth/request_token',
|
requestTokenUrl: 'https://example.domain/oauth/request_token',
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import nock from 'nock';
|
|||||||
|
|
||||||
import { CREDENTIAL_BLANKING_VALUE, Time } from '@/constants';
|
import { CREDENTIAL_BLANKING_VALUE, Time } from '@/constants';
|
||||||
import { OAuth2CredentialController } from '@/controllers/oauth/oauth2-credential.controller';
|
import { OAuth2CredentialController } from '@/controllers/oauth/oauth2-credential.controller';
|
||||||
|
import { CredentialsFinderService } from '@/credentials/credentials-finder.service';
|
||||||
import { CredentialsHelper } from '@/credentials-helper';
|
import { CredentialsHelper } from '@/credentials-helper';
|
||||||
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
|
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
|
||||||
import type { User } from '@/databases/entities/user';
|
import type { User } from '@/databases/entities/user';
|
||||||
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
|
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
|
||||||
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
|
|
||||||
import { VariablesService } from '@/environments.ee/variables/variables.service.ee';
|
import { VariablesService } from '@/environments.ee/variables/variables.service.ee';
|
||||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||||
@@ -39,7 +39,7 @@ describe('OAuth2CredentialController', () => {
|
|||||||
const externalHooks = mockInstance(ExternalHooks);
|
const externalHooks = mockInstance(ExternalHooks);
|
||||||
const credentialsHelper = mockInstance(CredentialsHelper);
|
const credentialsHelper = mockInstance(CredentialsHelper);
|
||||||
const credentialsRepository = mockInstance(CredentialsRepository);
|
const credentialsRepository = mockInstance(CredentialsRepository);
|
||||||
const sharedCredentialsRepository = mockInstance(SharedCredentialsRepository);
|
const credentialsFinderService = mockInstance(CredentialsFinderService);
|
||||||
|
|
||||||
const csrfSecret = 'csrf-secret';
|
const csrfSecret = 'csrf-secret';
|
||||||
const user = mock<User>({
|
const user = mock<User>({
|
||||||
@@ -81,7 +81,7 @@ describe('OAuth2CredentialController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should throw a NotFoundError when no matching credential is found for the user', async () => {
|
it('should throw a NotFoundError when no matching credential is found for the user', async () => {
|
||||||
sharedCredentialsRepository.findCredentialForUser.mockResolvedValueOnce(null);
|
credentialsFinderService.findCredentialForUser.mockResolvedValueOnce(null);
|
||||||
|
|
||||||
const req = mock<OAuthRequest.OAuth2Credential.Auth>({ user, query: { id: '1' } });
|
const req = mock<OAuthRequest.OAuth2Credential.Auth>({ user, query: { id: '1' } });
|
||||||
await expect(controller.getAuthUri(req)).rejects.toThrowError(
|
await expect(controller.getAuthUri(req)).rejects.toThrowError(
|
||||||
@@ -92,7 +92,7 @@ describe('OAuth2CredentialController', () => {
|
|||||||
it('should return a valid auth URI', async () => {
|
it('should return a valid auth URI', async () => {
|
||||||
jest.spyOn(Csrf.prototype, 'secretSync').mockReturnValueOnce(csrfSecret);
|
jest.spyOn(Csrf.prototype, 'secretSync').mockReturnValueOnce(csrfSecret);
|
||||||
jest.spyOn(Csrf.prototype, 'create').mockReturnValueOnce('token');
|
jest.spyOn(Csrf.prototype, 'create').mockReturnValueOnce('token');
|
||||||
sharedCredentialsRepository.findCredentialForUser.mockResolvedValueOnce(credential);
|
credentialsFinderService.findCredentialForUser.mockResolvedValueOnce(credential);
|
||||||
credentialsHelper.getDecrypted.mockResolvedValueOnce({});
|
credentialsHelper.getDecrypted.mockResolvedValueOnce({});
|
||||||
|
|
||||||
const req = mock<OAuthRequest.OAuth2Credential.Auth>({ user, query: { id: '1' } });
|
const req = mock<OAuthRequest.OAuth2Credential.Auth>({ user, query: { id: '1' } });
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import type { ICredentialDataDecryptedObject, IWorkflowExecuteAdditionalData } f
|
|||||||
import { jsonParse, UnexpectedError } from 'n8n-workflow';
|
import { jsonParse, UnexpectedError } from 'n8n-workflow';
|
||||||
|
|
||||||
import { RESPONSE_ERROR_MESSAGES, Time } from '@/constants';
|
import { RESPONSE_ERROR_MESSAGES, Time } from '@/constants';
|
||||||
|
import { CredentialsFinderService } from '@/credentials/credentials-finder.service';
|
||||||
import { CredentialsHelper } from '@/credentials-helper';
|
import { CredentialsHelper } from '@/credentials-helper';
|
||||||
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
|
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
|
||||||
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
|
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
|
||||||
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
|
|
||||||
import { AuthError } from '@/errors/response-errors/auth.error';
|
import { AuthError } from '@/errors/response-errors/auth.error';
|
||||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||||
@@ -46,7 +46,7 @@ export abstract class AbstractOAuthController {
|
|||||||
protected readonly externalHooks: ExternalHooks,
|
protected readonly externalHooks: ExternalHooks,
|
||||||
private readonly credentialsHelper: CredentialsHelper,
|
private readonly credentialsHelper: CredentialsHelper,
|
||||||
private readonly credentialsRepository: CredentialsRepository,
|
private readonly credentialsRepository: CredentialsRepository,
|
||||||
private readonly sharedCredentialsRepository: SharedCredentialsRepository,
|
private readonly credentialsFinderService: CredentialsFinderService,
|
||||||
private readonly urlService: UrlService,
|
private readonly urlService: UrlService,
|
||||||
private readonly globalConfig: GlobalConfig,
|
private readonly globalConfig: GlobalConfig,
|
||||||
) {}
|
) {}
|
||||||
@@ -65,7 +65,7 @@ export abstract class AbstractOAuthController {
|
|||||||
throw new BadRequestError('Required credential ID is missing');
|
throw new BadRequestError('Required credential ID is missing');
|
||||||
}
|
}
|
||||||
|
|
||||||
const credential = await this.sharedCredentialsRepository.findCredentialForUser(
|
const credential = await this.credentialsFinderService.findCredentialForUser(
|
||||||
credentialId,
|
credentialId,
|
||||||
req.user,
|
req.user,
|
||||||
['credential:read'],
|
['credential:read'],
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { Logger } from 'n8n-core';
|
|||||||
|
|
||||||
import type { WorkflowStatistics } from '@/databases/entities/workflow-statistics';
|
import type { WorkflowStatistics } from '@/databases/entities/workflow-statistics';
|
||||||
import { StatisticsNames } from '@/databases/entities/workflow-statistics';
|
import { StatisticsNames } from '@/databases/entities/workflow-statistics';
|
||||||
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
|
||||||
import { WorkflowStatisticsRepository } from '@/databases/repositories/workflow-statistics.repository';
|
import { WorkflowStatisticsRepository } from '@/databases/repositories/workflow-statistics.repository';
|
||||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||||
import type { IWorkflowStatisticsDataLoaded } from '@/interfaces';
|
import type { IWorkflowStatisticsDataLoaded } from '@/interfaces';
|
||||||
|
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
|
||||||
|
|
||||||
import { StatisticsRequest } from './workflow-statistics.types';
|
import { StatisticsRequest } from './workflow-statistics.types';
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ interface WorkflowStatisticsData<T> {
|
|||||||
@RestController('/workflow-stats')
|
@RestController('/workflow-stats')
|
||||||
export class WorkflowStatisticsController {
|
export class WorkflowStatisticsController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
private readonly workflowFinderService: WorkflowFinderService,
|
||||||
private readonly workflowStatisticsRepository: WorkflowStatisticsRepository,
|
private readonly workflowStatisticsRepository: WorkflowStatisticsRepository,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
@@ -35,7 +35,7 @@ export class WorkflowStatisticsController {
|
|||||||
const { user } = req;
|
const { user } = req;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
|
|
||||||
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(workflowId, user, [
|
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
|
||||||
'workflow:read',
|
'workflow:read',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ describe('CredentialsController', () => {
|
|||||||
sharedCredentialsRepository,
|
sharedCredentialsRepository,
|
||||||
mock(),
|
mock(),
|
||||||
eventService,
|
eventService,
|
||||||
|
mock(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let req: AuthenticatedRequest;
|
let req: AuthenticatedRequest;
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ describe('CredentialsService', () => {
|
|||||||
mock(),
|
mock(),
|
||||||
mock(),
|
mock(),
|
||||||
mock(),
|
mock(),
|
||||||
|
mock(),
|
||||||
);
|
);
|
||||||
|
|
||||||
beforeEach(() => jest.resetAllMocks());
|
beforeEach(() => jest.resetAllMocks());
|
||||||
|
|||||||
140
packages/cli/src/credentials/credentials-finder.service.ts
Normal file
140
packages/cli/src/credentials/credentials-finder.service.ts
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import type { ProjectRole } from '@n8n/api-types';
|
||||||
|
import { Service } from '@n8n/di';
|
||||||
|
import type { Scope } from '@n8n/permissions';
|
||||||
|
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
|
||||||
|
import type { EntityManager, FindOptionsWhere } from '@n8n/typeorm';
|
||||||
|
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
|
||||||
|
import { In } from '@n8n/typeorm';
|
||||||
|
|
||||||
|
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
|
||||||
|
import type {
|
||||||
|
SharedCredentials,
|
||||||
|
CredentialSharingRole,
|
||||||
|
} from '@/databases/entities/shared-credentials';
|
||||||
|
import type { User } from '@/databases/entities/user';
|
||||||
|
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
|
||||||
|
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class CredentialsFinderService {
|
||||||
|
constructor(
|
||||||
|
private readonly sharedCredentialsRepository: SharedCredentialsRepository,
|
||||||
|
private readonly roleService: RoleService,
|
||||||
|
private readonly credentialsRepository: CredentialsRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all credentials that the user has access to taking the scopes into
|
||||||
|
* account.
|
||||||
|
*
|
||||||
|
* This also returns `credentials.shared` which is useful for constructing
|
||||||
|
* all scopes the user has for the credential using `RoleService.addScopes`.
|
||||||
|
**/
|
||||||
|
async findCredentialsForUser(user: User, scopes: Scope[]) {
|
||||||
|
let where: FindOptionsWhere<CredentialsEntity> = {};
|
||||||
|
|
||||||
|
if (!user.hasGlobalScope(scopes, { mode: 'allOf' })) {
|
||||||
|
const projectRoles = this.roleService.rolesWithScope('project', scopes);
|
||||||
|
const credentialRoles = this.roleService.rolesWithScope('credential', scopes);
|
||||||
|
where = {
|
||||||
|
...where,
|
||||||
|
shared: {
|
||||||
|
role: In(credentialRoles),
|
||||||
|
project: {
|
||||||
|
projectRelations: {
|
||||||
|
role: In(projectRoles),
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.credentialsRepository.find({ where, relations: { shared: true } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get a credential if it has been shared with a user */
|
||||||
|
async findCredentialForUser(credentialsId: string, user: User, scopes: Scope[]) {
|
||||||
|
let where: FindOptionsWhere<SharedCredentials> = { credentialsId };
|
||||||
|
|
||||||
|
if (!user.hasGlobalScope(scopes, { mode: 'allOf' })) {
|
||||||
|
const projectRoles = this.roleService.rolesWithScope('project', scopes);
|
||||||
|
const credentialRoles = this.roleService.rolesWithScope('credential', scopes);
|
||||||
|
where = {
|
||||||
|
...where,
|
||||||
|
role: In(credentialRoles),
|
||||||
|
project: {
|
||||||
|
projectRelations: {
|
||||||
|
role: In(projectRoles),
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharedCredential = await this.sharedCredentialsRepository.findOne({
|
||||||
|
where,
|
||||||
|
// TODO: write a small relations merger and use that one here
|
||||||
|
relations: {
|
||||||
|
credentials: {
|
||||||
|
shared: { project: { projectRelations: { user: true } } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!sharedCredential) return null;
|
||||||
|
return sharedCredential.credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get all credentials shared to a user */
|
||||||
|
async findAllCredentialsForUser(user: User, scopes: Scope[], trx?: EntityManager) {
|
||||||
|
let where: FindOptionsWhere<SharedCredentials> = {};
|
||||||
|
|
||||||
|
if (!user.hasGlobalScope(scopes, { mode: 'allOf' })) {
|
||||||
|
const projectRoles = this.roleService.rolesWithScope('project', scopes);
|
||||||
|
const credentialRoles = this.roleService.rolesWithScope('credential', scopes);
|
||||||
|
where = {
|
||||||
|
role: In(credentialRoles),
|
||||||
|
project: {
|
||||||
|
projectRelations: {
|
||||||
|
role: In(projectRoles),
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharedCredential = await this.sharedCredentialsRepository.findCredentialsWithOptions(
|
||||||
|
where,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
|
||||||
|
return sharedCredential.map((sc) => ({ ...sc.credentials, projectId: sc.projectId }));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCredentialIdsByUserAndRole(
|
||||||
|
userIds: string[],
|
||||||
|
options:
|
||||||
|
| { scopes: Scope[] }
|
||||||
|
| { projectRoles: ProjectRole[]; credentialRoles: CredentialSharingRole[] },
|
||||||
|
trx?: EntityManager,
|
||||||
|
) {
|
||||||
|
const projectRoles =
|
||||||
|
'scopes' in options
|
||||||
|
? this.roleService.rolesWithScope('project', options.scopes)
|
||||||
|
: options.projectRoles;
|
||||||
|
const credentialRoles =
|
||||||
|
'scopes' in options
|
||||||
|
? this.roleService.rolesWithScope('credential', options.scopes)
|
||||||
|
: options.credentialRoles;
|
||||||
|
|
||||||
|
const sharings = await this.sharedCredentialsRepository.findCredentialsByRoles(
|
||||||
|
userIds,
|
||||||
|
projectRoles,
|
||||||
|
credentialRoles,
|
||||||
|
trx,
|
||||||
|
);
|
||||||
|
|
||||||
|
return sharings.map((s) => s.credentialsId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,6 +40,7 @@ import { NamingService } from '@/services/naming.service';
|
|||||||
import { UserManagementMailer } from '@/user-management/email';
|
import { UserManagementMailer } from '@/user-management/email';
|
||||||
import * as utils from '@/utils';
|
import * as utils from '@/utils';
|
||||||
|
|
||||||
|
import { CredentialsFinderService } from './credentials-finder.service';
|
||||||
import { CredentialsService } from './credentials.service';
|
import { CredentialsService } from './credentials.service';
|
||||||
import { EnterpriseCredentialsService } from './credentials.service.ee';
|
import { EnterpriseCredentialsService } from './credentials.service.ee';
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@ export class CredentialsController {
|
|||||||
private readonly sharedCredentialsRepository: SharedCredentialsRepository,
|
private readonly sharedCredentialsRepository: SharedCredentialsRepository,
|
||||||
private readonly projectRelationRepository: ProjectRelationRepository,
|
private readonly projectRelationRepository: ProjectRelationRepository,
|
||||||
private readonly eventService: EventService,
|
private readonly eventService: EventService,
|
||||||
|
private readonly credentialsFinderService: CredentialsFinderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('/', { middlewares: listQueryMiddleware })
|
@Get('/', { middlewares: listQueryMiddleware })
|
||||||
@@ -131,7 +133,7 @@ export class CredentialsController {
|
|||||||
async testCredentials(req: CredentialRequest.Test) {
|
async testCredentials(req: CredentialRequest.Test) {
|
||||||
const { credentials } = req.body;
|
const { credentials } = req.body;
|
||||||
|
|
||||||
const storedCredential = await this.sharedCredentialsRepository.findCredentialForUser(
|
const storedCredential = await this.credentialsFinderService.findCredentialForUser(
|
||||||
credentials.id,
|
credentials.id,
|
||||||
req.user,
|
req.user,
|
||||||
['credential:read'],
|
['credential:read'],
|
||||||
@@ -201,7 +203,7 @@ export class CredentialsController {
|
|||||||
params: { credentialId },
|
params: { credentialId },
|
||||||
} = req;
|
} = req;
|
||||||
|
|
||||||
const credential = await this.sharedCredentialsRepository.findCredentialForUser(
|
const credential = await this.credentialsFinderService.findCredentialForUser(
|
||||||
credentialId,
|
credentialId,
|
||||||
user,
|
user,
|
||||||
['credential:update'],
|
['credential:update'],
|
||||||
@@ -262,7 +264,7 @@ export class CredentialsController {
|
|||||||
async deleteCredentials(req: CredentialRequest.Delete) {
|
async deleteCredentials(req: CredentialRequest.Delete) {
|
||||||
const { credentialId } = req.params;
|
const { credentialId } = req.params;
|
||||||
|
|
||||||
const credential = await this.sharedCredentialsRepository.findCredentialForUser(
|
const credential = await this.credentialsFinderService.findCredentialForUser(
|
||||||
credentialId,
|
credentialId,
|
||||||
req.user,
|
req.user,
|
||||||
['credential:delete'],
|
['credential:delete'],
|
||||||
@@ -303,7 +305,7 @@ export class CredentialsController {
|
|||||||
throw new BadRequestError('Bad request');
|
throw new BadRequestError('Bad request');
|
||||||
}
|
}
|
||||||
|
|
||||||
const credential = await this.sharedCredentialsRepository.findCredentialForUser(
|
const credential = await this.credentialsFinderService.findCredentialForUser(
|
||||||
credentialId,
|
credentialId,
|
||||||
req.user,
|
req.user,
|
||||||
['credential:share'],
|
['credential:share'],
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { OwnershipService } from '@/services/ownership.service';
|
|||||||
import { ProjectService } from '@/services/project.service.ee';
|
import { ProjectService } from '@/services/project.service.ee';
|
||||||
import { RoleService } from '@/services/role.service';
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
|
import { CredentialsFinderService } from './credentials-finder.service';
|
||||||
import { CredentialsService } from './credentials.service';
|
import { CredentialsService } from './credentials.service';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -24,6 +25,7 @@ export class EnterpriseCredentialsService {
|
|||||||
private readonly credentialsService: CredentialsService,
|
private readonly credentialsService: CredentialsService,
|
||||||
private readonly projectService: ProjectService,
|
private readonly projectService: ProjectService,
|
||||||
private readonly roleService: RoleService,
|
private readonly roleService: RoleService,
|
||||||
|
private readonly credentialsFinderService: CredentialsFinderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async shareWithProjects(
|
async shareWithProjects(
|
||||||
@@ -83,7 +85,7 @@ export class EnterpriseCredentialsService {
|
|||||||
credential = includeDecryptedData
|
credential = includeDecryptedData
|
||||||
? // Try to get the credential with `credential:update` scope, which
|
? // Try to get the credential with `credential:update` scope, which
|
||||||
// are required for decrypting the data.
|
// are required for decrypting the data.
|
||||||
await this.sharedCredentialsRepository.findCredentialForUser(
|
await this.credentialsFinderService.findCredentialForUser(
|
||||||
credentialId,
|
credentialId,
|
||||||
user,
|
user,
|
||||||
// TODO: replace credential:update with credential:decrypt once it lands
|
// TODO: replace credential:update with credential:decrypt once it lands
|
||||||
@@ -99,11 +101,9 @@ export class EnterpriseCredentialsService {
|
|||||||
} else {
|
} else {
|
||||||
// Otherwise try to find them with only the `credential:read` scope. In
|
// Otherwise try to find them with only the `credential:read` scope. In
|
||||||
// that case we return them without the decrypted data.
|
// that case we return them without the decrypted data.
|
||||||
credential = await this.sharedCredentialsRepository.findCredentialForUser(
|
credential = await this.credentialsFinderService.findCredentialForUser(credentialId, user, [
|
||||||
credentialId,
|
'credential:read',
|
||||||
user,
|
]);
|
||||||
['credential:read'],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!credential) {
|
if (!credential) {
|
||||||
@@ -130,7 +130,7 @@ export class EnterpriseCredentialsService {
|
|||||||
|
|
||||||
async transferOne(user: User, credentialId: string, destinationProjectId: string) {
|
async transferOne(user: User, credentialId: string, destinationProjectId: string) {
|
||||||
// 1. get credential
|
// 1. get credential
|
||||||
const credential = await this.sharedCredentialsRepository.findCredentialForUser(
|
const credential = await this.credentialsFinderService.findCredentialForUser(
|
||||||
credentialId,
|
credentialId,
|
||||||
user,
|
user,
|
||||||
['credential:move'],
|
['credential:move'],
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ import { ProjectService } from '@/services/project.service.ee';
|
|||||||
import type { ScopesField } from '@/services/role.service';
|
import type { ScopesField } from '@/services/role.service';
|
||||||
import { RoleService } from '@/services/role.service';
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
|
import { CredentialsFinderService } from './credentials-finder.service';
|
||||||
|
|
||||||
export type CredentialsGetSharedOptions =
|
export type CredentialsGetSharedOptions =
|
||||||
| { allowGlobalScope: true; globalScope: Scope }
|
| { allowGlobalScope: true; globalScope: Scope }
|
||||||
| { allowGlobalScope: false };
|
| { allowGlobalScope: false };
|
||||||
@@ -64,6 +66,7 @@ export class CredentialsService {
|
|||||||
private readonly projectService: ProjectService,
|
private readonly projectService: ProjectService,
|
||||||
private readonly roleService: RoleService,
|
private readonly roleService: RoleService,
|
||||||
private readonly userRepository: UserRepository,
|
private readonly userRepository: UserRepository,
|
||||||
|
private readonly credentialsFinderService: CredentialsFinderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getMany(
|
async getMany(
|
||||||
@@ -145,7 +148,7 @@ export class CredentialsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ids = await this.sharedCredentialsRepository.getCredentialIdsByUserAndRole([user.id], {
|
const ids = await this.credentialsFinderService.getCredentialIdsByUserAndRole([user.id], {
|
||||||
scopes: ['credential:read'],
|
scopes: ['credential:read'],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -203,7 +206,7 @@ export class CredentialsService {
|
|||||||
const projectRelations = await this.projectService.getProjectRelationsForUser(user);
|
const projectRelations = await this.projectService.getProjectRelationsForUser(user);
|
||||||
|
|
||||||
// get all credentials the user has access to
|
// get all credentials the user has access to
|
||||||
const allCredentials = await this.credentialsRepository.findCredentialsForUser(user, [
|
const allCredentials = await this.credentialsFinderService.findCredentialsForUser(user, [
|
||||||
'credential:read',
|
'credential:read',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -434,7 +437,7 @@ export class CredentialsService {
|
|||||||
async delete(user: User, credentialId: string) {
|
async delete(user: User, credentialId: string) {
|
||||||
await this.externalHooks.run('credentials.delete', [credentialId]);
|
await this.externalHooks.run('credentials.delete', [credentialId]);
|
||||||
|
|
||||||
const credential = await this.sharedCredentialsRepository.findCredentialForUser(
|
const credential = await this.credentialsFinderService.findCredentialForUser(
|
||||||
credentialId,
|
credentialId,
|
||||||
user,
|
user,
|
||||||
['credential:delete'],
|
['credential:delete'],
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
import { Service } from '@n8n/di';
|
import { Service } from '@n8n/di';
|
||||||
import type { Scope } from '@n8n/permissions';
|
|
||||||
import { DataSource, In, Repository, Like } from '@n8n/typeorm';
|
import { DataSource, In, Repository, Like } from '@n8n/typeorm';
|
||||||
import type { FindManyOptions, FindOptionsWhere } from '@n8n/typeorm';
|
import type { FindManyOptions } from '@n8n/typeorm';
|
||||||
|
|
||||||
import type { ListQuery } from '@/requests';
|
import type { ListQuery } from '@/requests';
|
||||||
import { RoleService } from '@/services/role.service';
|
|
||||||
|
|
||||||
import { CredentialsEntity } from '../entities/credentials-entity';
|
import { CredentialsEntity } from '../entities/credentials-entity';
|
||||||
import type { User } from '../entities/user';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CredentialsRepository extends Repository<CredentialsEntity> {
|
export class CredentialsRepository extends Repository<CredentialsEntity> {
|
||||||
constructor(
|
constructor(dataSource: DataSource) {
|
||||||
dataSource: DataSource,
|
|
||||||
readonly roleService: RoleService,
|
|
||||||
) {
|
|
||||||
super(CredentialsEntity, dataSource.manager);
|
super(CredentialsEntity, dataSource.manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,34 +125,4 @@ export class CredentialsRepository extends Repository<CredentialsEntity> {
|
|||||||
async findAllCredentialsForProject(projectId: string): Promise<CredentialsEntity[]> {
|
async findAllCredentialsForProject(projectId: string): Promise<CredentialsEntity[]> {
|
||||||
return await this.findBy({ shared: { projectId } });
|
return await this.findBy({ shared: { projectId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Find all credentials that the user has access to taking the scopes into
|
|
||||||
* account.
|
|
||||||
*
|
|
||||||
* This also returns `credentials.shared` which is useful for constructing
|
|
||||||
* all scopes the user has for the credential using `RoleService.addScopes`.
|
|
||||||
**/
|
|
||||||
async findCredentialsForUser(user: User, scopes: Scope[]) {
|
|
||||||
let where: FindOptionsWhere<CredentialsEntity> = {};
|
|
||||||
|
|
||||||
if (!user.hasGlobalScope(scopes, { mode: 'allOf' })) {
|
|
||||||
const projectRoles = this.roleService.rolesWithScope('project', scopes);
|
|
||||||
const credentialRoles = this.roleService.rolesWithScope('credential', scopes);
|
|
||||||
where = {
|
|
||||||
...where,
|
|
||||||
shared: {
|
|
||||||
role: In(credentialRoles),
|
|
||||||
project: {
|
|
||||||
projectRelations: {
|
|
||||||
role: In(projectRoles),
|
|
||||||
userId: user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.find({ where, relations: { shared: true } });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,94 +1,17 @@
|
|||||||
import type { ProjectRole } from '@n8n/api-types';
|
import type { ProjectRole } from '@n8n/api-types';
|
||||||
import { Service } from '@n8n/di';
|
import { Service } from '@n8n/di';
|
||||||
import type { Scope } from '@n8n/permissions';
|
import type { EntityManager, FindOptionsWhere } from '@n8n/typeorm';
|
||||||
import type { EntityManager, FindOptionsRelations, FindOptionsWhere } from '@n8n/typeorm';
|
|
||||||
import { DataSource, In, Not, Repository } from '@n8n/typeorm';
|
import { DataSource, In, Not, Repository } from '@n8n/typeorm';
|
||||||
|
|
||||||
import { RoleService } from '@/services/role.service';
|
|
||||||
|
|
||||||
import type { Project } from '../entities/project';
|
import type { Project } from '../entities/project';
|
||||||
import { type CredentialSharingRole, SharedCredentials } from '../entities/shared-credentials';
|
import { type CredentialSharingRole, SharedCredentials } from '../entities/shared-credentials';
|
||||||
import type { User } from '../entities/user';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SharedCredentialsRepository extends Repository<SharedCredentials> {
|
export class SharedCredentialsRepository extends Repository<SharedCredentials> {
|
||||||
constructor(
|
constructor(dataSource: DataSource) {
|
||||||
dataSource: DataSource,
|
|
||||||
private readonly roleService: RoleService,
|
|
||||||
) {
|
|
||||||
super(SharedCredentials, dataSource.manager);
|
super(SharedCredentials, dataSource.manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get a credential if it has been shared with a user */
|
|
||||||
async findCredentialForUser(
|
|
||||||
credentialsId: string,
|
|
||||||
user: User,
|
|
||||||
scopes: Scope[],
|
|
||||||
_relations?: FindOptionsRelations<SharedCredentials>,
|
|
||||||
) {
|
|
||||||
let where: FindOptionsWhere<SharedCredentials> = { credentialsId };
|
|
||||||
|
|
||||||
if (!user.hasGlobalScope(scopes, { mode: 'allOf' })) {
|
|
||||||
const projectRoles = this.roleService.rolesWithScope('project', scopes);
|
|
||||||
const credentialRoles = this.roleService.rolesWithScope('credential', scopes);
|
|
||||||
where = {
|
|
||||||
...where,
|
|
||||||
role: In(credentialRoles),
|
|
||||||
project: {
|
|
||||||
projectRelations: {
|
|
||||||
role: In(projectRoles),
|
|
||||||
userId: user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const sharedCredential = await this.findOne({
|
|
||||||
where,
|
|
||||||
// TODO: write a small relations merger and use that one here
|
|
||||||
relations: {
|
|
||||||
credentials: {
|
|
||||||
shared: { project: { projectRelations: { user: true } } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!sharedCredential) return null;
|
|
||||||
return sharedCredential.credentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get all credentials shared to a user */
|
|
||||||
async findAllCredentialsForUser(user: User, scopes: Scope[], trx?: EntityManager) {
|
|
||||||
trx = trx ?? this.manager;
|
|
||||||
|
|
||||||
let where: FindOptionsWhere<SharedCredentials> = {};
|
|
||||||
|
|
||||||
if (!user.hasGlobalScope(scopes, { mode: 'allOf' })) {
|
|
||||||
const projectRoles = this.roleService.rolesWithScope('project', scopes);
|
|
||||||
const credentialRoles = this.roleService.rolesWithScope('credential', scopes);
|
|
||||||
where = {
|
|
||||||
role: In(credentialRoles),
|
|
||||||
project: {
|
|
||||||
projectRelations: {
|
|
||||||
role: In(projectRoles),
|
|
||||||
userId: user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const sharedCredential = await trx.find(SharedCredentials, {
|
|
||||||
where,
|
|
||||||
// TODO: write a small relations merger and use that one here
|
|
||||||
relations: {
|
|
||||||
credentials: {
|
|
||||||
shared: { project: { projectRelations: { user: true } } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return sharedCredential.map((sc) => ({ ...sc.credentials, projectId: sc.projectId }));
|
|
||||||
}
|
|
||||||
|
|
||||||
async findByCredentialIds(credentialIds: string[], role: CredentialSharingRole) {
|
async findByCredentialIds(credentialIds: string[], role: CredentialSharingRole) {
|
||||||
return await this.find({
|
return await this.find({
|
||||||
relations: { credentials: true, project: { projectRelations: { user: true } } },
|
relations: { credentials: true, project: { projectRelations: { user: true } } },
|
||||||
@@ -125,39 +48,6 @@ export class SharedCredentialsRepository extends Repository<SharedCredentials> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCredentialIdsByUserAndRole(
|
|
||||||
userIds: string[],
|
|
||||||
options:
|
|
||||||
| { scopes: Scope[] }
|
|
||||||
| { projectRoles: ProjectRole[]; credentialRoles: CredentialSharingRole[] },
|
|
||||||
trx?: EntityManager,
|
|
||||||
) {
|
|
||||||
trx = trx ?? this.manager;
|
|
||||||
|
|
||||||
const projectRoles =
|
|
||||||
'scopes' in options
|
|
||||||
? this.roleService.rolesWithScope('project', options.scopes)
|
|
||||||
: options.projectRoles;
|
|
||||||
const credentialRoles =
|
|
||||||
'scopes' in options
|
|
||||||
? this.roleService.rolesWithScope('credential', options.scopes)
|
|
||||||
: options.credentialRoles;
|
|
||||||
|
|
||||||
const sharings = await trx.find(SharedCredentials, {
|
|
||||||
where: {
|
|
||||||
role: In(credentialRoles),
|
|
||||||
project: {
|
|
||||||
projectRelations: {
|
|
||||||
userId: In(userIds),
|
|
||||||
role: In(projectRoles),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return sharings.map((s) => s.credentialsId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteByIds(sharedCredentialsIds: string[], projectId: string, trx?: EntityManager) {
|
async deleteByIds(sharedCredentialsIds: string[], projectId: string, trx?: EntityManager) {
|
||||||
trx = trx ?? this.manager;
|
trx = trx ?? this.manager;
|
||||||
|
|
||||||
@@ -199,4 +89,41 @@ export class SharedCredentialsRepository extends Repository<SharedCredentials> {
|
|||||||
relations: ['project'],
|
relations: ['project'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findCredentialsWithOptions(
|
||||||
|
where: FindOptionsWhere<SharedCredentials> = {},
|
||||||
|
trx?: EntityManager,
|
||||||
|
) {
|
||||||
|
trx = trx ?? this.manager;
|
||||||
|
|
||||||
|
return await trx.find(SharedCredentials, {
|
||||||
|
where,
|
||||||
|
relations: {
|
||||||
|
credentials: {
|
||||||
|
shared: { project: { projectRelations: { user: true } } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findCredentialsByRoles(
|
||||||
|
userIds: string[],
|
||||||
|
projectRoles: ProjectRole[],
|
||||||
|
credentialRoles: CredentialSharingRole[],
|
||||||
|
trx?: EntityManager,
|
||||||
|
) {
|
||||||
|
trx = trx ?? this.manager;
|
||||||
|
|
||||||
|
return await trx.find(SharedCredentials, {
|
||||||
|
where: {
|
||||||
|
role: In(credentialRoles),
|
||||||
|
project: {
|
||||||
|
projectRelations: {
|
||||||
|
userId: In(userIds),
|
||||||
|
role: In(projectRoles),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
import { Service } from '@n8n/di';
|
import { Service } from '@n8n/di';
|
||||||
import type { Scope } from '@n8n/permissions';
|
|
||||||
import { DataSource, Repository, In, Not } from '@n8n/typeorm';
|
import { DataSource, Repository, In, Not } from '@n8n/typeorm';
|
||||||
import type { EntityManager, FindManyOptions, FindOptionsWhere } from '@n8n/typeorm';
|
import type { EntityManager, FindManyOptions, FindOptionsWhere } from '@n8n/typeorm';
|
||||||
|
|
||||||
import { RoleService } from '@/services/role.service';
|
|
||||||
|
|
||||||
import type { Project } from '../entities/project';
|
import type { Project } from '../entities/project';
|
||||||
import { SharedWorkflow, type WorkflowSharingRole } from '../entities/shared-workflow';
|
import { SharedWorkflow, type WorkflowSharingRole } from '../entities/shared-workflow';
|
||||||
import { type User } from '../entities/user';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
|
export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
|
||||||
constructor(
|
constructor(dataSource: DataSource) {
|
||||||
dataSource: DataSource,
|
|
||||||
private roleService: RoleService,
|
|
||||||
) {
|
|
||||||
super(SharedWorkflow, dataSource.manager);
|
super(SharedWorkflow, dataSource.manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,79 +101,6 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findWorkflowForUser(
|
|
||||||
workflowId: string,
|
|
||||||
user: User,
|
|
||||||
scopes: Scope[],
|
|
||||||
{ includeTags = false, includeParentFolder = false, em = this.manager } = {},
|
|
||||||
) {
|
|
||||||
let where: FindOptionsWhere<SharedWorkflow> = { workflowId };
|
|
||||||
|
|
||||||
if (!user.hasGlobalScope(scopes, { mode: 'allOf' })) {
|
|
||||||
const projectRoles = this.roleService.rolesWithScope('project', scopes);
|
|
||||||
const workflowRoles = this.roleService.rolesWithScope('workflow', scopes);
|
|
||||||
|
|
||||||
where = {
|
|
||||||
...where,
|
|
||||||
role: In(workflowRoles),
|
|
||||||
project: {
|
|
||||||
projectRelations: {
|
|
||||||
role: In(projectRoles),
|
|
||||||
userId: user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const sharedWorkflow = await em.findOne(SharedWorkflow, {
|
|
||||||
where,
|
|
||||||
relations: {
|
|
||||||
workflow: {
|
|
||||||
shared: { project: { projectRelations: { user: true } } },
|
|
||||||
tags: includeTags,
|
|
||||||
parentFolder: includeParentFolder,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!sharedWorkflow) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sharedWorkflow.workflow;
|
|
||||||
}
|
|
||||||
|
|
||||||
async findAllWorkflowsForUser(user: User, scopes: Scope[]) {
|
|
||||||
let where: FindOptionsWhere<SharedWorkflow> = {};
|
|
||||||
|
|
||||||
if (!user.hasGlobalScope(scopes, { mode: 'allOf' })) {
|
|
||||||
const projectRoles = this.roleService.rolesWithScope('project', scopes);
|
|
||||||
const workflowRoles = this.roleService.rolesWithScope('workflow', scopes);
|
|
||||||
|
|
||||||
where = {
|
|
||||||
...where,
|
|
||||||
role: In(workflowRoles),
|
|
||||||
project: {
|
|
||||||
projectRelations: {
|
|
||||||
role: In(projectRoles),
|
|
||||||
userId: user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const sharedWorkflows = await this.find({
|
|
||||||
where,
|
|
||||||
relations: {
|
|
||||||
workflow: {
|
|
||||||
shared: { project: { projectRelations: { user: true } } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return sharedWorkflows.map((sw) => ({ ...sw.workflow, projectId: sw.projectId }));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the IDs of all the projects where a workflow is accessible.
|
* Find the IDs of all the projects where a workflow is accessible.
|
||||||
*/
|
*/
|
||||||
@@ -221,4 +141,35 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
|
|||||||
relations: ['project'],
|
relations: ['project'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findWorkflowWithOptions(
|
||||||
|
workflowId: string,
|
||||||
|
options: {
|
||||||
|
where?: FindOptionsWhere<SharedWorkflow>;
|
||||||
|
includeTags?: boolean;
|
||||||
|
includeParentFolder?: boolean;
|
||||||
|
em?: EntityManager;
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
where = {},
|
||||||
|
includeTags = false,
|
||||||
|
includeParentFolder = false,
|
||||||
|
em = this.manager,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
return await em.findOne(SharedWorkflow, {
|
||||||
|
where: {
|
||||||
|
workflowId,
|
||||||
|
...where,
|
||||||
|
},
|
||||||
|
relations: {
|
||||||
|
workflow: {
|
||||||
|
shared: { project: { projectRelations: { user: true } } },
|
||||||
|
tags: includeTags,
|
||||||
|
parentFolder: includeParentFolder,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import { z } from 'zod';
|
|||||||
import { ActiveWorkflowManager } from '@/active-workflow-manager';
|
import { ActiveWorkflowManager } from '@/active-workflow-manager';
|
||||||
import { WorkflowEntity } from '@/databases/entities/workflow-entity';
|
import { WorkflowEntity } from '@/databases/entities/workflow-entity';
|
||||||
import { ProjectRepository } from '@/databases/repositories/project.repository';
|
import { ProjectRepository } from '@/databases/repositories/project.repository';
|
||||||
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
|
||||||
import { TagRepository } from '@/databases/repositories/tag.repository';
|
import { TagRepository } from '@/databases/repositories/tag.repository';
|
||||||
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
||||||
import { EventService } from '@/events/event.service';
|
import { EventService } from '@/events/event.service';
|
||||||
import { ExternalHooks } from '@/external-hooks';
|
import { ExternalHooks } from '@/external-hooks';
|
||||||
import { addNodeIds, replaceInvalidCredentials } from '@/workflow-helpers';
|
import { addNodeIds, replaceInvalidCredentials } from '@/workflow-helpers';
|
||||||
|
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
|
||||||
import { WorkflowHistoryService } from '@/workflows/workflow-history.ee/workflow-history.service.ee';
|
import { WorkflowHistoryService } from '@/workflows/workflow-history.ee/workflow-history.service.ee';
|
||||||
import { WorkflowService } from '@/workflows/workflow.service';
|
import { WorkflowService } from '@/workflows/workflow.service';
|
||||||
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';
|
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';
|
||||||
@@ -115,7 +115,7 @@ export = {
|
|||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { excludePinnedData = false } = req.query;
|
const { excludePinnedData = false } = req.query;
|
||||||
|
|
||||||
const workflow = await Container.get(SharedWorkflowRepository).findWorkflowForUser(
|
const workflow = await Container.get(WorkflowFinderService).findWorkflowForUser(
|
||||||
id,
|
id,
|
||||||
req.user,
|
req.user,
|
||||||
['workflow:read'],
|
['workflow:read'],
|
||||||
@@ -169,7 +169,7 @@ export = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
const workflows = await Container.get(SharedWorkflowRepository).findAllWorkflowsForUser(
|
const workflows = await Container.get(WorkflowFinderService).findAllWorkflowsForUser(
|
||||||
req.user,
|
req.user,
|
||||||
['workflow:read'],
|
['workflow:read'],
|
||||||
);
|
);
|
||||||
@@ -189,7 +189,7 @@ export = {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let workflows = await Container.get(SharedWorkflowRepository).findAllWorkflowsForUser(
|
let workflows = await Container.get(WorkflowFinderService).findAllWorkflowsForUser(
|
||||||
req.user,
|
req.user,
|
||||||
['workflow:read'],
|
['workflow:read'],
|
||||||
);
|
);
|
||||||
@@ -252,7 +252,7 @@ export = {
|
|||||||
updateData.id = id;
|
updateData.id = id;
|
||||||
updateData.versionId = uuid();
|
updateData.versionId = uuid();
|
||||||
|
|
||||||
const workflow = await Container.get(SharedWorkflowRepository).findWorkflowForUser(
|
const workflow = await Container.get(WorkflowFinderService).findWorkflowForUser(
|
||||||
id,
|
id,
|
||||||
req.user,
|
req.user,
|
||||||
['workflow:update'],
|
['workflow:update'],
|
||||||
@@ -319,7 +319,7 @@ export = {
|
|||||||
async (req: WorkflowRequest.Activate, res: express.Response): Promise<express.Response> => {
|
async (req: WorkflowRequest.Activate, res: express.Response): Promise<express.Response> => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
const workflow = await Container.get(SharedWorkflowRepository).findWorkflowForUser(
|
const workflow = await Container.get(WorkflowFinderService).findWorkflowForUser(
|
||||||
id,
|
id,
|
||||||
req.user,
|
req.user,
|
||||||
['workflow:update'],
|
['workflow:update'],
|
||||||
@@ -358,7 +358,7 @@ export = {
|
|||||||
async (req: WorkflowRequest.Activate, res: express.Response): Promise<express.Response> => {
|
async (req: WorkflowRequest.Activate, res: express.Response): Promise<express.Response> => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
const workflow = await Container.get(SharedWorkflowRepository).findWorkflowForUser(
|
const workflow = await Container.get(WorkflowFinderService).findWorkflowForUser(
|
||||||
id,
|
id,
|
||||||
req.user,
|
req.user,
|
||||||
['workflow:update'],
|
['workflow:update'],
|
||||||
@@ -396,7 +396,7 @@ export = {
|
|||||||
return res.status(400).json({ message: 'Workflow Tags Disabled' });
|
return res.status(400).json({ message: 'Workflow Tags Disabled' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflow = await Container.get(SharedWorkflowRepository).findWorkflowForUser(
|
const workflow = await Container.get(WorkflowFinderService).findWorkflowForUser(
|
||||||
id,
|
id,
|
||||||
req.user,
|
req.user,
|
||||||
['workflow:read'],
|
['workflow:read'],
|
||||||
@@ -424,7 +424,7 @@ export = {
|
|||||||
return res.status(400).json({ message: 'Workflow Tags Disabled' });
|
return res.status(400).json({ message: 'Workflow Tags Disabled' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const sharedWorkflow = await Container.get(SharedWorkflowRepository).findWorkflowForUser(
|
const sharedWorkflow = await Container.get(WorkflowFinderService).findWorkflowForUser(
|
||||||
id,
|
id,
|
||||||
req.user,
|
req.user,
|
||||||
['workflow:update'],
|
['workflow:update'],
|
||||||
|
|||||||
@@ -7,17 +7,20 @@ import type { SharedWorkflowRepository } from '@/databases/repositories/shared-w
|
|||||||
import type { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
import type { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
||||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||||
import { ActiveWorkflowsService } from '@/services/active-workflows.service';
|
import { ActiveWorkflowsService } from '@/services/active-workflows.service';
|
||||||
|
import type { WorkflowFinderService } from '@/workflows/workflow-finder.service';
|
||||||
|
|
||||||
describe('ActiveWorkflowsService', () => {
|
describe('ActiveWorkflowsService', () => {
|
||||||
const user = mock<User>();
|
const user = mock<User>();
|
||||||
const workflowRepository = mock<WorkflowRepository>();
|
const workflowRepository = mock<WorkflowRepository>();
|
||||||
const sharedWorkflowRepository = mock<SharedWorkflowRepository>();
|
const sharedWorkflowRepository = mock<SharedWorkflowRepository>();
|
||||||
|
const workflowFinderService = mock<WorkflowFinderService>();
|
||||||
const activationErrorsService = mock<ActivationErrorsService>();
|
const activationErrorsService = mock<ActivationErrorsService>();
|
||||||
const service = new ActiveWorkflowsService(
|
const service = new ActiveWorkflowsService(
|
||||||
mock(),
|
mock(),
|
||||||
workflowRepository,
|
workflowRepository,
|
||||||
sharedWorkflowRepository,
|
sharedWorkflowRepository,
|
||||||
activationErrorsService,
|
activationErrorsService,
|
||||||
|
workflowFinderService,
|
||||||
);
|
);
|
||||||
const activeIds = ['1', '2', '3', '4'];
|
const activeIds = ['1', '2', '3', '4'];
|
||||||
|
|
||||||
@@ -63,22 +66,22 @@ describe('ActiveWorkflowsService', () => {
|
|||||||
const workflowId = 'workflowId';
|
const workflowId = 'workflowId';
|
||||||
|
|
||||||
it('should throw a BadRequestError a user does not have access to the workflow id', async () => {
|
it('should throw a BadRequestError a user does not have access to the workflow id', async () => {
|
||||||
sharedWorkflowRepository.findWorkflowForUser.mockResolvedValue(null);
|
workflowFinderService.findWorkflowForUser.mockResolvedValue(null);
|
||||||
await expect(service.getActivationError(workflowId, user)).rejects.toThrow(BadRequestError);
|
await expect(service.getActivationError(workflowId, user)).rejects.toThrow(BadRequestError);
|
||||||
|
|
||||||
expect(sharedWorkflowRepository.findWorkflowForUser).toHaveBeenCalledWith(workflowId, user, [
|
expect(workflowFinderService.findWorkflowForUser).toHaveBeenCalledWith(workflowId, user, [
|
||||||
'workflow:read',
|
'workflow:read',
|
||||||
]);
|
]);
|
||||||
expect(activationErrorsService.get).not.toHaveBeenCalled();
|
expect(activationErrorsService.get).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the error when the user has access', async () => {
|
it('should return the error when the user has access', async () => {
|
||||||
sharedWorkflowRepository.findWorkflowForUser.mockResolvedValue(new WorkflowEntity());
|
workflowFinderService.findWorkflowForUser.mockResolvedValue(new WorkflowEntity());
|
||||||
activationErrorsService.get.mockResolvedValue('some-error');
|
activationErrorsService.get.mockResolvedValue('some-error');
|
||||||
const error = await service.getActivationError(workflowId, user);
|
const error = await service.getActivationError(workflowId, user);
|
||||||
|
|
||||||
expect(error).toEqual('some-error');
|
expect(error).toEqual('some-error');
|
||||||
expect(sharedWorkflowRepository.findWorkflowForUser).toHaveBeenCalledWith(workflowId, user, [
|
expect(workflowFinderService.findWorkflowForUser).toHaveBeenCalledWith(workflowId, user, [
|
||||||
'workflow:read',
|
'workflow:read',
|
||||||
]);
|
]);
|
||||||
expect(activationErrorsService.get).toHaveBeenCalledWith(workflowId);
|
expect(activationErrorsService.get).toHaveBeenCalledWith(workflowId);
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ import { hasScope } from '@n8n/permissions';
|
|||||||
import { In } from '@n8n/typeorm';
|
import { In } from '@n8n/typeorm';
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
|
|
||||||
|
import { CredentialsFinderService } from '@/credentials/credentials-finder.service';
|
||||||
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
|
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
|
||||||
import { SharedCredentials } from '@/databases/entities/shared-credentials';
|
import { SharedCredentials } from '@/databases/entities/shared-credentials';
|
||||||
import type { User } from '@/databases/entities/user';
|
import type { User } from '@/databases/entities/user';
|
||||||
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
|
|
||||||
import { GLOBAL_MEMBER_SCOPES, GLOBAL_OWNER_SCOPES } from '@/permissions.ee/global-roles';
|
import { GLOBAL_MEMBER_SCOPES, GLOBAL_OWNER_SCOPES } from '@/permissions.ee/global-roles';
|
||||||
import { mockEntityManager } from '@test/mocking';
|
import { mockEntityManager } from '@test/mocking';
|
||||||
|
|
||||||
describe('SharedCredentialsRepository', () => {
|
describe('CredentialsFinderService', () => {
|
||||||
const entityManager = mockEntityManager(SharedCredentials);
|
const entityManager = mockEntityManager(SharedCredentials);
|
||||||
const repository = Container.get(SharedCredentialsRepository);
|
const credentialsFinderService = Container.get(CredentialsFinderService);
|
||||||
|
|
||||||
describe('findCredentialForUser', () => {
|
describe('findCredentialForUser', () => {
|
||||||
const credentialsId = 'cred_123';
|
const credentialsId = 'cred_123';
|
||||||
@@ -40,9 +40,11 @@ describe('SharedCredentialsRepository', () => {
|
|||||||
|
|
||||||
test('should allow instance owner access to all credentials', async () => {
|
test('should allow instance owner access to all credentials', async () => {
|
||||||
entityManager.findOne.mockResolvedValueOnce(sharedCredential);
|
entityManager.findOne.mockResolvedValueOnce(sharedCredential);
|
||||||
const credential = await repository.findCredentialForUser(credentialsId, owner, [
|
const credential = await credentialsFinderService.findCredentialForUser(
|
||||||
'credential:read',
|
credentialsId,
|
||||||
]);
|
owner,
|
||||||
|
['credential:read'],
|
||||||
|
);
|
||||||
expect(entityManager.findOne).toHaveBeenCalledWith(SharedCredentials, {
|
expect(entityManager.findOne).toHaveBeenCalledWith(SharedCredentials, {
|
||||||
relations: { credentials: { shared: { project: { projectRelations: { user: true } } } } },
|
relations: { credentials: { shared: { project: { projectRelations: { user: true } } } } },
|
||||||
where: { credentialsId },
|
where: { credentialsId },
|
||||||
@@ -52,9 +54,11 @@ describe('SharedCredentialsRepository', () => {
|
|||||||
|
|
||||||
test('should allow members', async () => {
|
test('should allow members', async () => {
|
||||||
entityManager.findOne.mockResolvedValueOnce(sharedCredential);
|
entityManager.findOne.mockResolvedValueOnce(sharedCredential);
|
||||||
const credential = await repository.findCredentialForUser(credentialsId, member, [
|
const credential = await credentialsFinderService.findCredentialForUser(
|
||||||
'credential:read',
|
credentialsId,
|
||||||
]);
|
member,
|
||||||
|
['credential:read'],
|
||||||
|
);
|
||||||
expect(entityManager.findOne).toHaveBeenCalledWith(SharedCredentials, {
|
expect(entityManager.findOne).toHaveBeenCalledWith(SharedCredentials, {
|
||||||
relations: { credentials: { shared: { project: { projectRelations: { user: true } } } } },
|
relations: { credentials: { shared: { project: { projectRelations: { user: true } } } } },
|
||||||
where: {
|
where: {
|
||||||
@@ -78,9 +82,11 @@ describe('SharedCredentialsRepository', () => {
|
|||||||
|
|
||||||
test('should return null when no shared credential is found', async () => {
|
test('should return null when no shared credential is found', async () => {
|
||||||
entityManager.findOne.mockResolvedValueOnce(null);
|
entityManager.findOne.mockResolvedValueOnce(null);
|
||||||
const credential = await repository.findCredentialForUser(credentialsId, member, [
|
const credential = await credentialsFinderService.findCredentialForUser(
|
||||||
'credential:read',
|
credentialsId,
|
||||||
]);
|
member,
|
||||||
|
['credential:read'],
|
||||||
|
);
|
||||||
expect(entityManager.findOne).toHaveBeenCalledWith(SharedCredentials, {
|
expect(entityManager.findOne).toHaveBeenCalledWith(SharedCredentials, {
|
||||||
relations: { credentials: { shared: { project: { projectRelations: { user: true } } } } },
|
relations: { credentials: { shared: { project: { projectRelations: { user: true } } } } },
|
||||||
where: {
|
where: {
|
||||||
@@ -2,8 +2,8 @@ import { Service } from '@n8n/di';
|
|||||||
import type { Workflow } from 'n8n-workflow';
|
import type { Workflow } from 'n8n-workflow';
|
||||||
|
|
||||||
import type { User } from '@/databases/entities/user';
|
import type { User } from '@/databases/entities/user';
|
||||||
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
|
||||||
import { UserRepository } from '@/databases/repositories/user.repository';
|
import { UserRepository } from '@/databases/repositories/user.repository';
|
||||||
|
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for checking whether a user has access to a resource.
|
* Responsible for checking whether a user has access to a resource.
|
||||||
@@ -12,7 +12,7 @@ import { UserRepository } from '@/databases/repositories/user.repository';
|
|||||||
export class AccessService {
|
export class AccessService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly userRepository: UserRepository,
|
private readonly userRepository: UserRepository,
|
||||||
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
private readonly workflowFinderService: WorkflowFinderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/** Whether a user has read access to a workflow based on their project and scope. */
|
/** Whether a user has read access to a workflow based on their project and scope. */
|
||||||
@@ -21,7 +21,7 @@ export class AccessService {
|
|||||||
|
|
||||||
if (!user) return false;
|
if (!user) return false;
|
||||||
|
|
||||||
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(workflowId, user, [
|
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
|
||||||
'workflow:read',
|
'workflow:read',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { User } from '@/databases/entities/user';
|
|||||||
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
||||||
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
||||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||||
|
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ActiveWorkflowsService {
|
export class ActiveWorkflowsService {
|
||||||
@@ -14,6 +15,7 @@ export class ActiveWorkflowsService {
|
|||||||
private readonly workflowRepository: WorkflowRepository,
|
private readonly workflowRepository: WorkflowRepository,
|
||||||
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
||||||
private readonly activationErrorsService: ActivationErrorsService,
|
private readonly activationErrorsService: ActivationErrorsService,
|
||||||
|
private readonly workflowFinderService: WorkflowFinderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getAllActiveIdsInStorage() {
|
async getAllActiveIdsInStorage() {
|
||||||
@@ -37,7 +39,7 @@ export class ActiveWorkflowsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getActivationError(workflowId: string, user: User) {
|
async getActivationError(workflowId: string, user: User) {
|
||||||
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(workflowId, user, [
|
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
|
||||||
'workflow:read',
|
'workflow:read',
|
||||||
]);
|
]);
|
||||||
if (!workflow) {
|
if (!workflow) {
|
||||||
|
|||||||
91
packages/cli/src/workflows/workflow-finder.service.ts
Normal file
91
packages/cli/src/workflows/workflow-finder.service.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { Service } from '@n8n/di';
|
||||||
|
import type { Scope } from '@n8n/permissions';
|
||||||
|
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
|
||||||
|
import type { EntityManager, FindOptionsWhere } from '@n8n/typeorm';
|
||||||
|
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
|
||||||
|
import { In } from '@n8n/typeorm';
|
||||||
|
|
||||||
|
import type { SharedWorkflow } from '@/databases/entities/shared-workflow';
|
||||||
|
import type { User } from '@/databases/entities/user';
|
||||||
|
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
||||||
|
import { RoleService } from '@/services/role.service';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class WorkflowFinderService {
|
||||||
|
constructor(
|
||||||
|
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
||||||
|
private readonly roleService: RoleService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async findWorkflowForUser(
|
||||||
|
workflowId: string,
|
||||||
|
user: User,
|
||||||
|
scopes: Scope[],
|
||||||
|
options: {
|
||||||
|
includeTags?: boolean;
|
||||||
|
includeParentFolder?: boolean;
|
||||||
|
em?: EntityManager;
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
let where: FindOptionsWhere<SharedWorkflow> = {};
|
||||||
|
|
||||||
|
if (!user.hasGlobalScope(scopes, { mode: 'allOf' })) {
|
||||||
|
const projectRoles = this.roleService.rolesWithScope('project', scopes);
|
||||||
|
const workflowRoles = this.roleService.rolesWithScope('workflow', scopes);
|
||||||
|
|
||||||
|
where = {
|
||||||
|
role: In(workflowRoles),
|
||||||
|
project: {
|
||||||
|
projectRelations: {
|
||||||
|
role: In(projectRoles),
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharedWorkflow = await this.sharedWorkflowRepository.findWorkflowWithOptions(workflowId, {
|
||||||
|
where,
|
||||||
|
includeTags: options.includeTags,
|
||||||
|
includeParentFolder: options.includeParentFolder,
|
||||||
|
em: options.em,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sharedWorkflow) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sharedWorkflow.workflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllWorkflowsForUser(user: User, scopes: Scope[]) {
|
||||||
|
let where: FindOptionsWhere<SharedWorkflow> = {};
|
||||||
|
|
||||||
|
if (!user.hasGlobalScope(scopes, { mode: 'allOf' })) {
|
||||||
|
const projectRoles = this.roleService.rolesWithScope('project', scopes);
|
||||||
|
const workflowRoles = this.roleService.rolesWithScope('workflow', scopes);
|
||||||
|
|
||||||
|
where = {
|
||||||
|
...where,
|
||||||
|
role: In(workflowRoles),
|
||||||
|
project: {
|
||||||
|
projectRelations: {
|
||||||
|
role: In(projectRoles),
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharedWorkflows = await this.sharedWorkflowRepository.find({
|
||||||
|
where,
|
||||||
|
relations: {
|
||||||
|
workflow: {
|
||||||
|
shared: { project: { projectRelations: { user: true } } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return sharedWorkflows.map((sw) => ({ ...sw.workflow, projectId: sw.projectId }));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
import { mockClear } from 'jest-mock-extended';
|
import { mockClear } from 'jest-mock-extended';
|
||||||
|
|
||||||
import { User } from '@/databases/entities/user';
|
import { User } from '@/databases/entities/user';
|
||||||
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
|
||||||
import { WorkflowHistoryRepository } from '@/databases/repositories/workflow-history.repository';
|
import { WorkflowHistoryRepository } from '@/databases/repositories/workflow-history.repository';
|
||||||
|
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
|
||||||
import { WorkflowHistoryService } from '@/workflows/workflow-history.ee/workflow-history.service.ee';
|
import { WorkflowHistoryService } from '@/workflows/workflow-history.ee/workflow-history.service.ee';
|
||||||
import { mockInstance, mockLogger } from '@test/mocking';
|
import { mockInstance, mockLogger } from '@test/mocking';
|
||||||
import { getWorkflow } from '@test-integration/workflow';
|
import { getWorkflow } from '@test-integration/workflow';
|
||||||
|
|
||||||
const workflowHistoryRepository = mockInstance(WorkflowHistoryRepository);
|
const workflowHistoryRepository = mockInstance(WorkflowHistoryRepository);
|
||||||
const logger = mockLogger();
|
const logger = mockLogger();
|
||||||
const sharedWorkflowRepository = mockInstance(SharedWorkflowRepository);
|
const workflowFinderService = mockInstance(WorkflowFinderService);
|
||||||
const workflowHistoryService = new WorkflowHistoryService(
|
const workflowHistoryService = new WorkflowHistoryService(
|
||||||
logger,
|
logger,
|
||||||
workflowHistoryRepository,
|
workflowHistoryRepository,
|
||||||
sharedWorkflowRepository,
|
workflowFinderService,
|
||||||
);
|
);
|
||||||
const testUser = Object.assign(new User(), {
|
const testUser = Object.assign(new User(), {
|
||||||
id: '1234',
|
id: '1234',
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ import { ensureError } from 'n8n-workflow';
|
|||||||
|
|
||||||
import type { User } from '@/databases/entities/user';
|
import type { User } from '@/databases/entities/user';
|
||||||
import type { WorkflowHistory } from '@/databases/entities/workflow-history';
|
import type { WorkflowHistory } from '@/databases/entities/workflow-history';
|
||||||
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
|
||||||
import { WorkflowHistoryRepository } from '@/databases/repositories/workflow-history.repository';
|
import { WorkflowHistoryRepository } from '@/databases/repositories/workflow-history.repository';
|
||||||
import { SharedWorkflowNotFoundError } from '@/errors/shared-workflow-not-found.error';
|
import { SharedWorkflowNotFoundError } from '@/errors/shared-workflow-not-found.error';
|
||||||
import { WorkflowHistoryVersionNotFoundError } from '@/errors/workflow-history-version-not-found.error';
|
import { WorkflowHistoryVersionNotFoundError } from '@/errors/workflow-history-version-not-found.error';
|
||||||
|
|
||||||
import { isWorkflowHistoryEnabled } from './workflow-history-helper.ee';
|
import { isWorkflowHistoryEnabled } from './workflow-history-helper.ee';
|
||||||
|
import { WorkflowFinderService } from '../workflow-finder.service';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class WorkflowHistoryService {
|
export class WorkflowHistoryService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly workflowHistoryRepository: WorkflowHistoryRepository,
|
private readonly workflowHistoryRepository: WorkflowHistoryRepository,
|
||||||
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
private readonly workflowFinderService: WorkflowFinderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getList(
|
async getList(
|
||||||
@@ -26,7 +26,7 @@ export class WorkflowHistoryService {
|
|||||||
take: number,
|
take: number,
|
||||||
skip: number,
|
skip: number,
|
||||||
): Promise<Array<Omit<WorkflowHistory, 'nodes' | 'connections'>>> {
|
): Promise<Array<Omit<WorkflowHistory, 'nodes' | 'connections'>>> {
|
||||||
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(workflowId, user, [
|
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
|
||||||
'workflow:read',
|
'workflow:read',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ export class WorkflowHistoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getVersion(user: User, workflowId: string, versionId: string): Promise<WorkflowHistory> {
|
async getVersion(user: User, workflowId: string, versionId: string): Promise<WorkflowHistory> {
|
||||||
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(workflowId, user, [
|
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
|
||||||
'workflow:read',
|
'workflow:read',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type { IWorkflowBase, WorkflowId } from 'n8n-workflow';
|
|||||||
import { NodeOperationError, UserError, WorkflowActivationError } from 'n8n-workflow';
|
import { NodeOperationError, UserError, WorkflowActivationError } from 'n8n-workflow';
|
||||||
|
|
||||||
import { ActiveWorkflowManager } from '@/active-workflow-manager';
|
import { ActiveWorkflowManager } from '@/active-workflow-manager';
|
||||||
|
import { CredentialsFinderService } from '@/credentials/credentials-finder.service';
|
||||||
import { CredentialsService } from '@/credentials/credentials.service';
|
import { CredentialsService } from '@/credentials/credentials.service';
|
||||||
import { EnterpriseCredentialsService } from '@/credentials/credentials.service.ee';
|
import { EnterpriseCredentialsService } from '@/credentials/credentials.service.ee';
|
||||||
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
|
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
|
||||||
@@ -14,7 +15,6 @@ import { Project } from '@/databases/entities/project';
|
|||||||
import { SharedWorkflow } from '@/databases/entities/shared-workflow';
|
import { SharedWorkflow } from '@/databases/entities/shared-workflow';
|
||||||
import type { User } from '@/databases/entities/user';
|
import type { User } from '@/databases/entities/user';
|
||||||
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
|
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
|
||||||
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
|
|
||||||
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
||||||
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
||||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||||
@@ -23,6 +23,7 @@ import { TransferWorkflowError } from '@/errors/response-errors/transfer-workflo
|
|||||||
import { OwnershipService } from '@/services/ownership.service';
|
import { OwnershipService } from '@/services/ownership.service';
|
||||||
import { ProjectService } from '@/services/project.service.ee';
|
import { ProjectService } from '@/services/project.service.ee';
|
||||||
|
|
||||||
|
import { WorkflowFinderService } from './workflow-finder.service';
|
||||||
import type {
|
import type {
|
||||||
WorkflowWithSharingsAndCredentials,
|
WorkflowWithSharingsAndCredentials,
|
||||||
WorkflowWithSharingsMetaDataAndCredentials,
|
WorkflowWithSharingsMetaDataAndCredentials,
|
||||||
@@ -39,8 +40,9 @@ export class EnterpriseWorkflowService {
|
|||||||
private readonly ownershipService: OwnershipService,
|
private readonly ownershipService: OwnershipService,
|
||||||
private readonly projectService: ProjectService,
|
private readonly projectService: ProjectService,
|
||||||
private readonly activeWorkflowManager: ActiveWorkflowManager,
|
private readonly activeWorkflowManager: ActiveWorkflowManager,
|
||||||
private readonly sharedCredentialsRepository: SharedCredentialsRepository,
|
private readonly credentialsFinderService: CredentialsFinderService,
|
||||||
private readonly enterpriseCredentialsService: EnterpriseCredentialsService,
|
private readonly enterpriseCredentialsService: EnterpriseCredentialsService,
|
||||||
|
private readonly workflowFinderService: WorkflowFinderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async shareWithProjects(
|
async shareWithProjects(
|
||||||
@@ -265,7 +267,7 @@ export class EnterpriseWorkflowService {
|
|||||||
shareCredentials: string[] = [],
|
shareCredentials: string[] = [],
|
||||||
) {
|
) {
|
||||||
// 1. get workflow
|
// 1. get workflow
|
||||||
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(workflowId, user, [
|
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
|
||||||
'workflow:move',
|
'workflow:move',
|
||||||
]);
|
]);
|
||||||
NotFoundError.isDefinedAndNotNull(
|
NotFoundError.isDefinedAndNotNull(
|
||||||
@@ -324,7 +326,7 @@ export class EnterpriseWorkflowService {
|
|||||||
|
|
||||||
// 8. share credentials into the destination project
|
// 8. share credentials into the destination project
|
||||||
await this.workflowRepository.manager.transaction(async (trx) => {
|
await this.workflowRepository.manager.transaction(async (trx) => {
|
||||||
const allCredentials = await this.sharedCredentialsRepository.findAllCredentialsForUser(
|
const allCredentials = await this.credentialsFinderService.findAllCredentialsForUser(
|
||||||
user,
|
user,
|
||||||
['credential:share'],
|
['credential:share'],
|
||||||
trx,
|
trx,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { RoleService } from '@/services/role.service';
|
|||||||
import { TagService } from '@/services/tag.service';
|
import { TagService } from '@/services/tag.service';
|
||||||
import * as WorkflowHelpers from '@/workflow-helpers';
|
import * as WorkflowHelpers from '@/workflow-helpers';
|
||||||
|
|
||||||
|
import { WorkflowFinderService } from './workflow-finder.service';
|
||||||
import { WorkflowHistoryService } from './workflow-history.ee/workflow-history.service.ee';
|
import { WorkflowHistoryService } from './workflow-history.ee/workflow-history.service.ee';
|
||||||
import { WorkflowSharingService } from './workflow-sharing.service';
|
import { WorkflowSharingService } from './workflow-sharing.service';
|
||||||
|
|
||||||
@@ -60,6 +61,7 @@ export class WorkflowService {
|
|||||||
private readonly eventService: EventService,
|
private readonly eventService: EventService,
|
||||||
private readonly globalConfig: GlobalConfig,
|
private readonly globalConfig: GlobalConfig,
|
||||||
private readonly folderService: FolderService,
|
private readonly folderService: FolderService,
|
||||||
|
private readonly workflowFinderService: WorkflowFinderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getMany(
|
async getMany(
|
||||||
@@ -185,7 +187,7 @@ export class WorkflowService {
|
|||||||
parentFolderId?: string,
|
parentFolderId?: string,
|
||||||
forceSave?: boolean,
|
forceSave?: boolean,
|
||||||
): Promise<WorkflowEntity> {
|
): Promise<WorkflowEntity> {
|
||||||
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(workflowId, user, [
|
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
|
||||||
'workflow:update',
|
'workflow:update',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -367,7 +369,7 @@ export class WorkflowService {
|
|||||||
async delete(user: User, workflowId: string): Promise<WorkflowEntity | undefined> {
|
async delete(user: User, workflowId: string): Promise<WorkflowEntity | undefined> {
|
||||||
await this.externalHooks.run('workflow.delete', [workflowId]);
|
await this.externalHooks.run('workflow.delete', [workflowId]);
|
||||||
|
|
||||||
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(workflowId, user, [
|
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
|
||||||
'workflow:delete',
|
'workflow:delete',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import * as utils from '@/utils';
|
|||||||
import * as WorkflowHelpers from '@/workflow-helpers';
|
import * as WorkflowHelpers from '@/workflow-helpers';
|
||||||
|
|
||||||
import { WorkflowExecutionService } from './workflow-execution.service';
|
import { WorkflowExecutionService } from './workflow-execution.service';
|
||||||
|
import { WorkflowFinderService } from './workflow-finder.service';
|
||||||
import { WorkflowHistoryService } from './workflow-history.ee/workflow-history.service.ee';
|
import { WorkflowHistoryService } from './workflow-history.ee/workflow-history.service.ee';
|
||||||
import { WorkflowRequest } from './workflow.request';
|
import { WorkflowRequest } from './workflow.request';
|
||||||
import { WorkflowService } from './workflow.service';
|
import { WorkflowService } from './workflow.service';
|
||||||
@@ -84,6 +85,7 @@ export class WorkflowsController {
|
|||||||
private readonly eventService: EventService,
|
private readonly eventService: EventService,
|
||||||
private readonly globalConfig: GlobalConfig,
|
private readonly globalConfig: GlobalConfig,
|
||||||
private readonly folderService: FolderService,
|
private readonly folderService: FolderService,
|
||||||
|
private readonly workflowFinderService: WorkflowFinderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('/')
|
@Post('/')
|
||||||
@@ -176,7 +178,7 @@ export class WorkflowsController {
|
|||||||
|
|
||||||
await transactionManager.save<SharedWorkflow>(newSharedWorkflow);
|
await transactionManager.save<SharedWorkflow>(newSharedWorkflow);
|
||||||
|
|
||||||
return await this.sharedWorkflowRepository.findWorkflowForUser(
|
return await this.workflowFinderService.findWorkflowForUser(
|
||||||
workflow.id,
|
workflow.id,
|
||||||
req.user,
|
req.user,
|
||||||
['workflow:read'],
|
['workflow:read'],
|
||||||
@@ -292,7 +294,7 @@ export class WorkflowsController {
|
|||||||
relations.tags = true;
|
relations.tags = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(
|
const workflow = await this.workflowFinderService.findWorkflowForUser(
|
||||||
workflowId,
|
workflowId,
|
||||||
req.user,
|
req.user,
|
||||||
['workflow:read'],
|
['workflow:read'],
|
||||||
@@ -320,7 +322,7 @@ export class WorkflowsController {
|
|||||||
|
|
||||||
// sharing disabled
|
// sharing disabled
|
||||||
|
|
||||||
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(
|
const workflow = await this.workflowFinderService.findWorkflowForUser(
|
||||||
workflowId,
|
workflowId,
|
||||||
req.user,
|
req.user,
|
||||||
['workflow:read'],
|
['workflow:read'],
|
||||||
@@ -442,7 +444,7 @@ export class WorkflowsController {
|
|||||||
throw new BadRequestError('Bad request');
|
throw new BadRequestError('Bad request');
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflow = await this.sharedWorkflowRepository.findWorkflowForUser(workflowId, req.user, [
|
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, req.user, [
|
||||||
'workflow:share',
|
'workflow:share',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Container } from '@n8n/di';
|
import { Container } from '@n8n/di';
|
||||||
|
|
||||||
|
import { CredentialsFinderService } from '@/credentials/credentials-finder.service';
|
||||||
import { CredentialsService } from '@/credentials/credentials.service';
|
import { CredentialsService } from '@/credentials/credentials.service';
|
||||||
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
|
import type { CredentialsEntity } from '@/databases/entities/credentials-entity';
|
||||||
import type { User } from '@/databases/entities/user';
|
import type { User } from '@/databases/entities/user';
|
||||||
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
|
|
||||||
import { createTeamProject, linkUserToProject } from '@test-integration/db/projects';
|
import { createTeamProject, linkUserToProject } from '@test-integration/db/projects';
|
||||||
|
|
||||||
import { saveCredential, shareCredentialWithUsers } from '../shared/db/credentials';
|
import { saveCredential, shareCredentialWithUsers } from '../shared/db/credentials';
|
||||||
@@ -32,9 +32,11 @@ beforeAll(async () => {
|
|||||||
describe('credentials service', () => {
|
describe('credentials service', () => {
|
||||||
describe('replaceCredentialContentsForSharee', () => {
|
describe('replaceCredentialContentsForSharee', () => {
|
||||||
it('should replace the contents of the credential for sharee', async () => {
|
it('should replace the contents of the credential for sharee', async () => {
|
||||||
const storedCredential = await Container.get(
|
const storedCredential = await Container.get(CredentialsFinderService).findCredentialForUser(
|
||||||
SharedCredentialsRepository,
|
credential.id,
|
||||||
).findCredentialForUser(credential.id, memberWhoDoesNotOwnCredential, ['credential:read']);
|
memberWhoDoesNotOwnCredential,
|
||||||
|
['credential:read'],
|
||||||
|
);
|
||||||
|
|
||||||
const decryptedData = Container.get(CredentialsService).decrypt(storedCredential!);
|
const decryptedData = Container.get(CredentialsService).decrypt(storedCredential!);
|
||||||
|
|
||||||
@@ -64,10 +66,12 @@ describe('credentials service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const storedProjectCredential = await Container.get(
|
const storedProjectCredential = await Container.get(
|
||||||
SharedCredentialsRepository,
|
CredentialsFinderService,
|
||||||
).findCredentialForUser(projectCredential.id, viewerMember, ['credential:read']);
|
).findCredentialForUser(projectCredential.id, viewerMember, ['credential:read']);
|
||||||
|
|
||||||
const decryptedData = Container.get(CredentialsService).decrypt(storedProjectCredential!);
|
if (!storedProjectCredential) throw new Error('Could not find credential');
|
||||||
|
|
||||||
|
const decryptedData = Container.get(CredentialsService).decrypt(storedProjectCredential);
|
||||||
|
|
||||||
const mergedCredentials = {
|
const mergedCredentials = {
|
||||||
id: projectCredential.id,
|
id: projectCredential.id,
|
||||||
@@ -78,7 +82,7 @@ describe('credentials service', () => {
|
|||||||
|
|
||||||
await Container.get(CredentialsService).replaceCredentialContentsForSharee(
|
await Container.get(CredentialsService).replaceCredentialContentsForSharee(
|
||||||
viewerMember,
|
viewerMember,
|
||||||
storedProjectCredential!,
|
storedProjectCredential,
|
||||||
decryptedData,
|
decryptedData,
|
||||||
mergedCredentials,
|
mergedCredentials,
|
||||||
);
|
);
|
||||||
@@ -95,10 +99,12 @@ describe('credentials service', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const storedProjectCredential = await Container.get(
|
const storedProjectCredential = await Container.get(
|
||||||
SharedCredentialsRepository,
|
CredentialsFinderService,
|
||||||
).findCredentialForUser(projectCredential.id, editorMember, ['credential:read']);
|
).findCredentialForUser(projectCredential.id, editorMember, ['credential:read']);
|
||||||
|
|
||||||
const decryptedData = Container.get(CredentialsService).decrypt(storedProjectCredential!);
|
if (!storedProjectCredential) throw new Error('Could not find credential');
|
||||||
|
|
||||||
|
const decryptedData = Container.get(CredentialsService).decrypt(storedProjectCredential);
|
||||||
|
|
||||||
const originalData = { accessToken: '' };
|
const originalData = { accessToken: '' };
|
||||||
const mergedCredentials = {
|
const mergedCredentials = {
|
||||||
@@ -110,7 +116,7 @@ describe('credentials service', () => {
|
|||||||
|
|
||||||
await Container.get(CredentialsService).replaceCredentialContentsForSharee(
|
await Container.get(CredentialsService).replaceCredentialContentsForSharee(
|
||||||
editorMember,
|
editorMember,
|
||||||
storedProjectCredential!,
|
storedProjectCredential,
|
||||||
decryptedData,
|
decryptedData,
|
||||||
mergedCredentials,
|
mergedCredentials,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ describe('EnterpriseWorkflowService', () => {
|
|||||||
mock(),
|
mock(),
|
||||||
mock(),
|
mock(),
|
||||||
mock(),
|
mock(),
|
||||||
|
mock(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { WorkflowRepository } from '@/databases/repositories/workflow.repository
|
|||||||
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
||||||
import { OrchestrationService } from '@/services/orchestration.service';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { Telemetry } from '@/telemetry';
|
import { Telemetry } from '@/telemetry';
|
||||||
|
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
|
||||||
import { WorkflowService } from '@/workflows/workflow.service';
|
import { WorkflowService } from '@/workflows/workflow.service';
|
||||||
|
|
||||||
import { mockInstance } from '../../shared/mocking';
|
import { mockInstance } from '../../shared/mocking';
|
||||||
@@ -42,6 +43,7 @@ beforeAll(async () => {
|
|||||||
mock(),
|
mock(),
|
||||||
mock(),
|
mock(),
|
||||||
mock(),
|
mock(),
|
||||||
|
Container.get(WorkflowFinderService),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user