refactor(core): Consolidate CredentialsService.getMany() (no-changelog) (#7028)

Consolidate `CredentialsService.getMany()` in preparation for adding
list query middleware to `GET /credentials`.
This commit is contained in:
Iván Ovejero
2023-09-04 10:37:16 +02:00
committed by GitHub
parent 9dd5f0e579
commit 442b910ffb
13 changed files with 205 additions and 130 deletions

View File

@@ -1,16 +1,16 @@
import express from 'express';
import type { INodeCredentialTestResult } from 'n8n-workflow';
import { deepCopy, LoggerProxy } from 'n8n-workflow';
import { deepCopy } from 'n8n-workflow';
import * as Db from '@/Db';
import * as ResponseHelper from '@/ResponseHelper';
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
import type { CredentialRequest } from '@/requests';
import { isSharingEnabled, rightDiff } from '@/UserManagement/UserManagementHelper';
import { EECredentialsService as EECredentials } from './credentials.service.ee';
import type { CredentialWithSharings } from './credentials.types';
import { OwnershipService } from '@/services/ownership.service';
import { Container } from 'typedi';
import { InternalHooks } from '@/InternalHooks';
import type { CredentialsEntity } from '@/databases/entities/CredentialsEntity';
// eslint-disable-next-line @typescript-eslint/naming-convention
export const EECredentialsController = express.Router();
@@ -25,27 +25,6 @@ EECredentialsController.use((req, res, next) => {
next();
});
/**
* GET /credentials
*/
EECredentialsController.get(
'/',
ResponseHelper.send(async (req: CredentialRequest.GetAll): Promise<CredentialWithSharings[]> => {
try {
const allCredentials = await EECredentials.getAll(req.user, {
relations: ['shared', 'shared.role', 'shared.user'],
});
return allCredentials.map((credential: CredentialsEntity & CredentialWithSharings) =>
EECredentials.addOwnerAndSharings(credential),
);
} catch (error) {
LoggerProxy.error('Request to list credentials failed', error as Error);
throw error;
}
}),
);
/**
* GET /credentials/:id
*/
@@ -59,7 +38,7 @@ EECredentialsController.get(
let credential = (await EECredentials.get(
{ id: credentialId },
{ relations: ['shared', 'shared.role', 'shared.user'] },
)) as CredentialsEntity & CredentialWithSharings;
)) as CredentialsEntity;
if (!credential) {
throw new ResponseHelper.NotFoundError(
@@ -73,7 +52,7 @@ EECredentialsController.get(
throw new ResponseHelper.UnauthorizedError('Forbidden.');
}
credential = EECredentials.addOwnerAndSharings(credential);
credential = Container.get(OwnershipService).addOwnedByAndSharedWith(credential);
if (!includeDecryptedData || !userSharing || userSharing.role.name !== 'owner') {
const { data: _, ...rest } = credential;

View File

@@ -35,8 +35,8 @@ credentialsController.use('/', EECredentialsController);
*/
credentialsController.get(
'/',
ResponseHelper.send(async (req: CredentialRequest.GetAll): Promise<ICredentialsDb[]> => {
return CredentialsService.getAll(req.user, { roles: ['owner'] });
ResponseHelper.send(async (req: CredentialRequest.GetAll) => {
return CredentialsService.getMany(req.user);
}),
);

View File

@@ -6,7 +6,6 @@ import { SharedCredentials } from '@db/entities/SharedCredentials';
import type { User } from '@db/entities/User';
import { UserService } from '@/services/user.service';
import { CredentialsService } from './credentials.service';
import type { CredentialWithSharings } from './credentials.types';
import { RoleService } from '@/services/role.service';
import Container from 'typedi';
@@ -93,27 +92,4 @@ export class EECredentialsService extends CredentialsService {
return transaction.save(newSharedCredentials);
}
static addOwnerAndSharings(
credential: CredentialsEntity & CredentialWithSharings,
): CredentialsEntity & CredentialWithSharings {
credential.ownedBy = null;
credential.sharedWith = [];
credential.shared?.forEach(({ user, role }) => {
const { id, email, firstName, lastName } = user;
if (role.name === 'owner') {
credential.ownedBy = { id, email, firstName, lastName };
return;
}
credential.sharedWith?.push({ id, email, firstName, lastName });
});
// @ts-ignore
delete credential.shared;
return credential;
}
}

View File

@@ -24,6 +24,7 @@ import type { User } from '@db/entities/User';
import type { CredentialRequest } from '@/requests';
import { CredentialTypes } from '@/CredentialTypes';
import { RoleService } from '@/services/role.service';
import { OwnershipService } from '@/services/ownership.service';
export class CredentialsService {
static async get(
@@ -36,48 +37,58 @@ export class CredentialsService {
});
}
static async getAll(
user: User,
options?: { relations?: string[]; roles?: string[]; disableGlobalRole?: boolean },
): Promise<ICredentialsDb[]> {
const SELECT_FIELDS: Array<keyof ICredentialsDb> = [
'id',
'name',
'type',
'nodesAccess',
'createdAt',
'updatedAt',
];
static async getMany(user: User, options?: { disableGlobalRole: boolean }) {
type Select = Array<keyof ICredentialsDb>;
// if instance owner, return all credentials
const select: Select = ['id', 'name', 'type', 'nodesAccess', 'createdAt', 'updatedAt'];
if (user.globalRole.name === 'owner' && options?.disableGlobalRole !== true) {
return Db.collections.Credentials.find({
select: SELECT_FIELDS,
relations: options?.relations,
});
const relations = ['shared', 'shared.role', 'shared.user'];
const returnAll = user.globalRole.name === 'owner' && options?.disableGlobalRole !== true;
const addOwnedByAndSharedWith = (c: CredentialsEntity) =>
Container.get(OwnershipService).addOwnedByAndSharedWith(c);
if (returnAll) {
const credentials = await Db.collections.Credentials.find({ select, relations });
return credentials.map(addOwnedByAndSharedWith);
}
// if member, return credentials owned by or shared with member
const userSharings = await Db.collections.SharedCredentials.find({
where: {
userId: user.id,
...(options?.roles?.length ? { role: { name: In(options.roles) } } : {}),
},
relations: options?.roles?.length ? ['role'] : [],
const ids = await CredentialsService.getAccessibleCredentials(user.id);
const credentials = await Db.collections.Credentials.find({
select,
relations,
where: { id: In(ids) },
});
return Db.collections.Credentials.find({
select: SELECT_FIELDS,
relations: options?.relations,
where: {
id: In(userSharings.map((x) => x.credentialsId)),
},
});
return credentials.map(addOwnedByAndSharedWith);
}
static async getMany(filter: FindManyOptions<ICredentialsDb>): Promise<ICredentialsDb[]> {
return Db.collections.Credentials.find(filter);
/**
* Get the IDs of all credentials owned by or shared with a user.
*/
private static async getAccessibleCredentials(userId: string) {
const sharings = await Db.collections.SharedCredentials.find({
relations: ['role'],
where: {
userId,
role: { name: In(['owner', 'user']), scope: 'credential' },
},
});
return sharings.map((s) => s.credentialsId);
}
static async getManyByIds(ids: string[], { withSharings } = { withSharings: false }) {
const options: FindManyOptions<CredentialsEntity> = { where: { id: In(ids) } };
if (withSharings) {
options.relations = ['shared', 'shared.user', 'shared.role'];
}
return Db.collections.Credentials.find(options);
}
/**

View File

@@ -1,7 +0,0 @@
import type { IUser } from 'n8n-workflow';
import type { ICredentialsDb } from '@/Interfaces';
export interface CredentialWithSharings extends ICredentialsDb {
ownedBy?: IUser | null;
sharedWith?: IUser[];
}