mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat: RBAC (#8922)
Signed-off-by: Oleg Ivaniv <me@olegivaniv.com> Co-authored-by: Val <68596159+valya@users.noreply.github.com> Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in> Co-authored-by: Valya Bullions <valya@n8n.io> Co-authored-by: Danny Martini <danny@n8n.io> Co-authored-by: Danny Martini <despair.blue@gmail.com> Co-authored-by: Iván Ovejero <ivov.src@gmail.com> Co-authored-by: Omar Ajoue <krynble@gmail.com> Co-authored-by: oleg <me@olegivaniv.com> Co-authored-by: Michael Kret <michael.k@radency.com> Co-authored-by: Michael Kret <88898367+michael-radency@users.noreply.github.com> Co-authored-by: Elias Meire <elias@meire.dev> Co-authored-by: Giulio Andreini <andreini@netseven.it> Co-authored-by: Giulio Andreini <g.andreini@gmail.com> Co-authored-by: Ayato Hayashi <go12limchangyong@gmail.com>
This commit is contained in:
@@ -6,15 +6,19 @@ import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.
|
||||
import type { CredentialSharingRole } from '@db/entities/SharedCredentials';
|
||||
import type { ICredentialsDb } from '@/Interfaces';
|
||||
import type { CredentialPayload } from '../types';
|
||||
import { ProjectRepository } from '@/databases/repositories/project.repository';
|
||||
import type { Project } from '@/databases/entities/Project';
|
||||
|
||||
async function encryptCredentialData(credential: CredentialsEntity) {
|
||||
export async function encryptCredentialData(
|
||||
credential: CredentialsEntity,
|
||||
): Promise<ICredentialsDb> {
|
||||
const { createCredentialsFromCredentialsEntity } = await import('@/CredentialsHelper');
|
||||
const coreCredential = createCredentialsFromCredentialsEntity(credential, true);
|
||||
|
||||
// @ts-ignore
|
||||
coreCredential.setData(credential.data);
|
||||
|
||||
return coreCredential.getDataToSave() as ICredentialsDb;
|
||||
return Object.assign(credential, coreCredential.getDataToSave());
|
||||
}
|
||||
|
||||
const emptyAttributes = {
|
||||
@@ -46,43 +50,89 @@ export async function createCredentials(attributes: Partial<CredentialsEntity> =
|
||||
*/
|
||||
export async function saveCredential(
|
||||
credentialPayload: CredentialPayload,
|
||||
{ user, role }: { user: User; role: CredentialSharingRole },
|
||||
options:
|
||||
| { user: User; role: CredentialSharingRole }
|
||||
| {
|
||||
project: Project;
|
||||
role: CredentialSharingRole;
|
||||
},
|
||||
) {
|
||||
const role = options.role;
|
||||
const newCredential = new CredentialsEntity();
|
||||
|
||||
Object.assign(newCredential, credentialPayload);
|
||||
|
||||
const encryptedData = await encryptCredentialData(newCredential);
|
||||
|
||||
Object.assign(newCredential, encryptedData);
|
||||
await encryptCredentialData(newCredential);
|
||||
|
||||
const savedCredential = await Container.get(CredentialsRepository).save(newCredential);
|
||||
|
||||
savedCredential.data = newCredential.data;
|
||||
|
||||
await Container.get(SharedCredentialsRepository).save({
|
||||
user,
|
||||
credentials: savedCredential,
|
||||
role,
|
||||
});
|
||||
if ('user' in options) {
|
||||
const user = options.user;
|
||||
const personalProject = await Container.get(ProjectRepository).getPersonalProjectForUserOrFail(
|
||||
user.id,
|
||||
);
|
||||
|
||||
await Container.get(SharedCredentialsRepository).save({
|
||||
user,
|
||||
credentials: savedCredential,
|
||||
role,
|
||||
project: personalProject,
|
||||
});
|
||||
} else {
|
||||
const project = options.project;
|
||||
|
||||
await Container.get(SharedCredentialsRepository).save({
|
||||
credentials: savedCredential,
|
||||
role,
|
||||
project,
|
||||
});
|
||||
}
|
||||
|
||||
return savedCredential;
|
||||
}
|
||||
|
||||
export async function shareCredentialWithUsers(credential: CredentialsEntity, users: User[]) {
|
||||
const newSharedCredentials = users.map((user) =>
|
||||
Container.get(SharedCredentialsRepository).create({
|
||||
userId: user.id,
|
||||
credentialsId: credential.id,
|
||||
role: 'credential:user',
|
||||
const newSharedCredentials = await Promise.all(
|
||||
users.map(async (user) => {
|
||||
const personalProject = await Container.get(
|
||||
ProjectRepository,
|
||||
).getPersonalProjectForUserOrFail(user.id);
|
||||
|
||||
return Container.get(SharedCredentialsRepository).create({
|
||||
credentialsId: credential.id,
|
||||
role: 'credential:user',
|
||||
projectId: personalProject.id,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return await Container.get(SharedCredentialsRepository).save(newSharedCredentials);
|
||||
}
|
||||
|
||||
export async function shareCredentialWithProjects(
|
||||
credential: CredentialsEntity,
|
||||
projects: Project[],
|
||||
) {
|
||||
const newSharedCredentials = await Promise.all(
|
||||
projects.map(async (project) => {
|
||||
return Container.get(SharedCredentialsRepository).create({
|
||||
credentialsId: credential.id,
|
||||
role: 'credential:user',
|
||||
projectId: project.id,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return await Container.get(SharedCredentialsRepository).save(newSharedCredentials);
|
||||
}
|
||||
|
||||
export function affixRoleToSaveCredential(role: CredentialSharingRole) {
|
||||
return async (credentialPayload: CredentialPayload, { user }: { user: User }) =>
|
||||
await saveCredential(credentialPayload, { user, role });
|
||||
return async (
|
||||
credentialPayload: CredentialPayload,
|
||||
options: { user: User } | { project: Project },
|
||||
) => await saveCredential(credentialPayload, { ...options, role });
|
||||
}
|
||||
|
||||
export async function getAllCredentials() {
|
||||
|
||||
63
packages/cli/test/integration/shared/db/projects.ts
Normal file
63
packages/cli/test/integration/shared/db/projects.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import Container from 'typedi';
|
||||
|
||||
import { ProjectRepository } from '@/databases/repositories/project.repository';
|
||||
import { randomName } from '../random';
|
||||
import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository';
|
||||
import type { User } from '@/databases/entities/User';
|
||||
import type { Project } from '@/databases/entities/Project';
|
||||
import type { ProjectRelation, ProjectRole } from '@/databases/entities/ProjectRelation';
|
||||
|
||||
export const createTeamProject = async (name?: string, adminUser?: User) => {
|
||||
const projectRepository = Container.get(ProjectRepository);
|
||||
const project = await projectRepository.save(
|
||||
projectRepository.create({
|
||||
name: name ?? randomName(),
|
||||
type: 'team',
|
||||
}),
|
||||
);
|
||||
|
||||
if (adminUser) {
|
||||
await linkUserToProject(adminUser, project, 'project:admin');
|
||||
}
|
||||
|
||||
return project;
|
||||
};
|
||||
|
||||
export const linkUserToProject = async (user: User, project: Project, role: ProjectRole) => {
|
||||
const projectRelationRepository = Container.get(ProjectRelationRepository);
|
||||
await projectRelationRepository.save(
|
||||
projectRelationRepository.create({
|
||||
projectId: project.id,
|
||||
userId: user.id,
|
||||
role,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const getPersonalProject = async (user: User): Promise<Project> => {
|
||||
return await Container.get(ProjectRepository).findOneOrFail({
|
||||
where: {
|
||||
projectRelations: {
|
||||
userId: user.id,
|
||||
role: 'project:personalOwner',
|
||||
},
|
||||
type: 'personal',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const findProject = async (id: string): Promise<Project> => {
|
||||
return await Container.get(ProjectRepository).findOneOrFail({
|
||||
where: { id },
|
||||
});
|
||||
};
|
||||
|
||||
export const getProjectRelations = async ({
|
||||
projectId,
|
||||
userId,
|
||||
role,
|
||||
}: Partial<ProjectRelation>): Promise<ProjectRelation[]> => {
|
||||
return await Container.get(ProjectRelationRepository).find({
|
||||
where: { projectId, userId, role },
|
||||
});
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import Container from 'typedi';
|
||||
import { hash } from 'bcryptjs';
|
||||
import { AuthIdentity } from '@db/entities/AuthIdentity';
|
||||
import type { GlobalRole, User } from '@db/entities/User';
|
||||
import { type GlobalRole, type User } from '@db/entities/User';
|
||||
import { AuthIdentityRepository } from '@db/repositories/authIdentity.repository';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { TOTPService } from '@/Mfa/totp.service';
|
||||
@@ -27,9 +27,10 @@ export async function newUser(attributes: Partial<User> = {}): Promise<User> {
|
||||
|
||||
/** Store a user object in the DB */
|
||||
export async function createUser(attributes: Partial<User> = {}): Promise<User> {
|
||||
const user = await newUser(attributes);
|
||||
const userInstance = await newUser(attributes);
|
||||
const { user } = await Container.get(UserRepository).createUserWithProject(userInstance);
|
||||
user.computeIsOwner();
|
||||
return await Container.get(UserRepository).save(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function createLdapUser(attributes: Partial<User>, ldapId: string): Promise<User> {
|
||||
@@ -90,7 +91,8 @@ export async function createUserShell(role: GlobalRole): Promise<User> {
|
||||
shell.email = randomEmail();
|
||||
}
|
||||
|
||||
return await Container.get(UserRepository).save(shell);
|
||||
const { user } = await Container.get(UserRepository).createUserWithProject(shell);
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,12 +102,15 @@ export async function createManyUsers(
|
||||
amount: number,
|
||||
attributes: Partial<User> = {},
|
||||
): Promise<User[]> {
|
||||
const users = await Promise.all(
|
||||
const result = await Promise.all(
|
||||
Array(amount)
|
||||
.fill(0)
|
||||
.map(async () => await newUser(attributes)),
|
||||
.map(async () => {
|
||||
const userInstance = await newUser(attributes);
|
||||
return await Container.get(UserRepository).createUserWithProject(userInstance);
|
||||
}),
|
||||
);
|
||||
return await Container.get(UserRepository).save(users);
|
||||
return result.map((result) => result.user);
|
||||
}
|
||||
|
||||
export async function addApiKey(user: User): Promise<User> {
|
||||
@@ -127,7 +132,7 @@ export const getUserById = async (id: string) =>
|
||||
export const getLdapIdentities = async () =>
|
||||
await Container.get(AuthIdentityRepository).find({
|
||||
where: { providerType: 'ldap' },
|
||||
relations: ['user'],
|
||||
relations: { user: true },
|
||||
});
|
||||
|
||||
export async function getGlobalOwner() {
|
||||
|
||||
@@ -2,11 +2,13 @@ import Container from 'typedi';
|
||||
import type { DeepPartial } from '@n8n/typeorm';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import type { User } from '@db/entities/User';
|
||||
import { User } from '@db/entities/User';
|
||||
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||
import type { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import type { SharedWorkflow, WorkflowSharingRole } from '@db/entities/SharedWorkflow';
|
||||
import { ProjectRepository } from '@/databases/repositories/project.repository';
|
||||
import { Project } from '@/databases/entities/Project';
|
||||
|
||||
export async function createManyWorkflows(
|
||||
amount: number,
|
||||
@@ -48,28 +50,71 @@ export function newWorkflow(attributes: Partial<WorkflowEntity> = {}): WorkflowE
|
||||
* @param attributes workflow attributes
|
||||
* @param user user to assign the workflow to
|
||||
*/
|
||||
export async function createWorkflow(attributes: Partial<WorkflowEntity> = {}, user?: User) {
|
||||
export async function createWorkflow(
|
||||
attributes: Partial<WorkflowEntity> = {},
|
||||
userOrProject?: User | Project,
|
||||
) {
|
||||
const workflow = await Container.get(WorkflowRepository).save(newWorkflow(attributes));
|
||||
|
||||
if (user) {
|
||||
await Container.get(SharedWorkflowRepository).save({
|
||||
user,
|
||||
workflow,
|
||||
role: 'workflow:owner',
|
||||
});
|
||||
if (userOrProject instanceof User) {
|
||||
const user = userOrProject;
|
||||
const project = await Container.get(ProjectRepository).getPersonalProjectForUserOrFail(user.id);
|
||||
await Container.get(SharedWorkflowRepository).save(
|
||||
Container.get(SharedWorkflowRepository).create({
|
||||
project,
|
||||
workflow,
|
||||
role: 'workflow:owner',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (userOrProject instanceof Project) {
|
||||
const project = userOrProject;
|
||||
await Container.get(SharedWorkflowRepository).save(
|
||||
Container.get(SharedWorkflowRepository).create({
|
||||
project,
|
||||
workflow,
|
||||
role: 'workflow:owner',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return workflow;
|
||||
}
|
||||
|
||||
export async function shareWorkflowWithUsers(workflow: WorkflowEntity, users: User[]) {
|
||||
const sharedWorkflows: Array<DeepPartial<SharedWorkflow>> = users.map((user) => ({
|
||||
userId: user.id,
|
||||
workflowId: workflow.id,
|
||||
role: 'workflow:editor',
|
||||
}));
|
||||
const sharedWorkflows: Array<DeepPartial<SharedWorkflow>> = await Promise.all(
|
||||
users.map(async (user) => {
|
||||
const project = await Container.get(ProjectRepository).getPersonalProjectForUserOrFail(
|
||||
user.id,
|
||||
);
|
||||
return {
|
||||
projectId: project.id,
|
||||
workflowId: workflow.id,
|
||||
role: 'workflow:editor',
|
||||
};
|
||||
}),
|
||||
);
|
||||
return await Container.get(SharedWorkflowRepository).save(sharedWorkflows);
|
||||
}
|
||||
|
||||
export async function shareWorkflowWithProjects(
|
||||
workflow: WorkflowEntity,
|
||||
projectsWithRole: Array<{ project: Project; role?: WorkflowSharingRole }>,
|
||||
) {
|
||||
const newSharedWorkflow = await Promise.all(
|
||||
projectsWithRole.map(async ({ project, role }) => {
|
||||
return Container.get(SharedWorkflowRepository).create({
|
||||
workflowId: workflow.id,
|
||||
role: role ?? 'workflow:editor',
|
||||
projectId: project.id,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return await Container.get(SharedWorkflowRepository).save(newSharedWorkflow);
|
||||
}
|
||||
|
||||
export async function getWorkflowSharing(workflow: WorkflowEntity) {
|
||||
return await Container.get(SharedWorkflowRepository).findBy({
|
||||
workflowId: workflow.id,
|
||||
|
||||
Reference in New Issue
Block a user