mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
chore(core): Move scopes and roles into database in preparation for custom roles (#17226)
This commit is contained in:
@@ -17,6 +17,8 @@ import { InvalidAuthToken } from './invalid-auth-token';
|
|||||||
import { ProcessedData } from './processed-data';
|
import { ProcessedData } from './processed-data';
|
||||||
import { Project } from './project';
|
import { Project } from './project';
|
||||||
import { ProjectRelation } from './project-relation';
|
import { ProjectRelation } from './project-relation';
|
||||||
|
import { Role } from './role';
|
||||||
|
import { Scope } from './scope';
|
||||||
import { Settings } from './settings';
|
import { Settings } from './settings';
|
||||||
import { SharedCredentials } from './shared-credentials';
|
import { SharedCredentials } from './shared-credentials';
|
||||||
import { SharedWorkflow } from './shared-workflow';
|
import { SharedWorkflow } from './shared-workflow';
|
||||||
@@ -46,6 +48,8 @@ export {
|
|||||||
Folder,
|
Folder,
|
||||||
Project,
|
Project,
|
||||||
ProjectRelation,
|
ProjectRelation,
|
||||||
|
Role,
|
||||||
|
Scope,
|
||||||
SharedCredentials,
|
SharedCredentials,
|
||||||
SharedWorkflow,
|
SharedWorkflow,
|
||||||
TagEntity,
|
TagEntity,
|
||||||
@@ -81,6 +85,7 @@ export const entities = {
|
|||||||
Folder,
|
Folder,
|
||||||
Project,
|
Project,
|
||||||
ProjectRelation,
|
ProjectRelation,
|
||||||
|
Scope,
|
||||||
SharedCredentials,
|
SharedCredentials,
|
||||||
SharedWorkflow,
|
SharedWorkflow,
|
||||||
TagEntity,
|
TagEntity,
|
||||||
@@ -99,4 +104,5 @@ export const entities = {
|
|||||||
TestRun,
|
TestRun,
|
||||||
TestCaseExecution,
|
TestCaseExecution,
|
||||||
ExecutionEntity,
|
ExecutionEntity,
|
||||||
|
Role,
|
||||||
};
|
};
|
||||||
|
|||||||
57
packages/@n8n/db/src/entities/role.ts
Normal file
57
packages/@n8n/db/src/entities/role.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { Column, Entity, JoinTable, ManyToMany, PrimaryColumn } from '@n8n/typeorm';
|
||||||
|
|
||||||
|
import { Scope } from './scope';
|
||||||
|
|
||||||
|
@Entity({
|
||||||
|
name: 'role',
|
||||||
|
})
|
||||||
|
export class Role {
|
||||||
|
@PrimaryColumn({
|
||||||
|
type: String,
|
||||||
|
name: 'slug',
|
||||||
|
})
|
||||||
|
slug: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: String,
|
||||||
|
nullable: false,
|
||||||
|
name: 'displayName',
|
||||||
|
})
|
||||||
|
displayName: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: String,
|
||||||
|
nullable: true,
|
||||||
|
name: 'description',
|
||||||
|
})
|
||||||
|
description: string | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
name: 'systemRole',
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Indicates if the role is managed by the system and cannot be edited.
|
||||||
|
*/
|
||||||
|
systemRole: boolean;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: String,
|
||||||
|
name: 'roleType',
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Type of the role, e.g., global, project, or workflow.
|
||||||
|
*/
|
||||||
|
roleType: 'global' | 'project' | 'workflow' | 'credential';
|
||||||
|
|
||||||
|
@ManyToMany(() => Scope, {
|
||||||
|
eager: true,
|
||||||
|
})
|
||||||
|
@JoinTable({
|
||||||
|
name: 'role_scope',
|
||||||
|
joinColumn: { name: 'roleSlug', referencedColumnName: 'slug' },
|
||||||
|
inverseJoinColumn: { name: 'scopeSlug', referencedColumnName: 'slug' },
|
||||||
|
})
|
||||||
|
scopes: Scope[];
|
||||||
|
}
|
||||||
27
packages/@n8n/db/src/entities/scope.ts
Normal file
27
packages/@n8n/db/src/entities/scope.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { Scope as ScopeType } from '@n8n/permissions';
|
||||||
|
import { Column, Entity, PrimaryColumn } from '@n8n/typeorm';
|
||||||
|
|
||||||
|
@Entity({
|
||||||
|
name: 'scope',
|
||||||
|
})
|
||||||
|
export class Scope {
|
||||||
|
@PrimaryColumn({
|
||||||
|
type: String,
|
||||||
|
name: 'slug',
|
||||||
|
})
|
||||||
|
slug: ScopeType;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: String,
|
||||||
|
nullable: true,
|
||||||
|
name: 'displayName',
|
||||||
|
})
|
||||||
|
displayName: string | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: String,
|
||||||
|
nullable: true,
|
||||||
|
name: 'description',
|
||||||
|
})
|
||||||
|
description: string | null;
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { AuthPrincipal } from '@n8n/permissions';
|
import type { AuthPrincipal, GlobalRole } from '@n8n/permissions';
|
||||||
import { GlobalRole } from '@n8n/permissions';
|
|
||||||
import {
|
import {
|
||||||
AfterLoad,
|
AfterLoad,
|
||||||
AfterUpdate,
|
AfterUpdate,
|
||||||
|
|||||||
@@ -33,3 +33,5 @@ export { wrapMigration } from './migrations/migration-helpers';
|
|||||||
export * from './migrations/migration-types';
|
export * from './migrations/migration-types';
|
||||||
export { DbConnection } from './connection/db-connection';
|
export { DbConnection } from './connection/db-connection';
|
||||||
export { DbConnectionOptions } from './connection/db-connection-options';
|
export { DbConnectionOptions } from './connection/db-connection-options';
|
||||||
|
|
||||||
|
export { AuthRolesService } from './services/auth.roles.service';
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { MigrationContext, ReversibleMigration } from '../migration-types';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We introduce a scope table, this will hold all scopes that we know about.
|
||||||
|
*
|
||||||
|
* The scope table should never be edited by users, on every startup
|
||||||
|
* the system will make sure that all scopes that it knows about are stored
|
||||||
|
* in here.
|
||||||
|
*
|
||||||
|
* ColumnName | Type | Description
|
||||||
|
* =================================
|
||||||
|
* slug | Text | Unique identifier of the scope for example: 'project:create'
|
||||||
|
* displayName | Text | Name used to display in the UI
|
||||||
|
* description | Text | Text describing the scope in more detail of users
|
||||||
|
*/
|
||||||
|
export class AddScopeTables1750252139166 implements ReversibleMigration {
|
||||||
|
async up({ schemaBuilder: { createTable, column } }: MigrationContext) {
|
||||||
|
await createTable('scope').withColumns(
|
||||||
|
column('slug')
|
||||||
|
.varchar(128)
|
||||||
|
.primary.notNull.comment('Unique identifier of the scope for example: "project:create"'),
|
||||||
|
column('displayName').text.default(null).comment('Name used to display in the UI'),
|
||||||
|
column('description')
|
||||||
|
.text.default(null)
|
||||||
|
.comment('Text describing the scope in more detail of users'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down({ schemaBuilder: { dropTable } }: MigrationContext) {
|
||||||
|
await dropTable('scope');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import type { MigrationContext, ReversibleMigration } from '../migration-types';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We introduce roles table, this will hold all roles that we know about
|
||||||
|
*
|
||||||
|
* There are roles that can't be edited by users, these are marked as system-only and will
|
||||||
|
* be managed by the system itself. On every startup, the system will ensure
|
||||||
|
* that these roles are synchronized.
|
||||||
|
*
|
||||||
|
* ColumnName | Type | Description
|
||||||
|
* =================================
|
||||||
|
* slug | Text | Unique identifier of the role for example: 'global:owner'
|
||||||
|
* displayName | Text | Name used to display in the UI
|
||||||
|
* description | Text | Text describing the scope in more detail of users
|
||||||
|
* roleType | Text | Text type of role, such as 'global', 'project', etc.
|
||||||
|
* systemRole | Bool | Indicates if the role is managed by the system and cannot be edited by users
|
||||||
|
*
|
||||||
|
* For the role table there is a junction table that will hold the
|
||||||
|
* relationships between the roles and the scopes that are associated with them.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class AddRolesTables1750252139167 implements ReversibleMigration {
|
||||||
|
async up({
|
||||||
|
schemaBuilder: { createTable, column, createIndex },
|
||||||
|
queryRunner,
|
||||||
|
tablePrefix,
|
||||||
|
dbType,
|
||||||
|
}: MigrationContext) {
|
||||||
|
await createTable('role').withColumns(
|
||||||
|
column('slug')
|
||||||
|
.varchar(128)
|
||||||
|
.primary.notNull.comment('Unique identifier of the role for example: "global:owner"'),
|
||||||
|
column('displayName').text.default(null).comment('Name used to display in the UI'),
|
||||||
|
column('description')
|
||||||
|
.text.default(null)
|
||||||
|
.comment('Text describing the scope in more detail of users'),
|
||||||
|
column('roleType')
|
||||||
|
.text.default(null)
|
||||||
|
.comment('Type of the role, e.g., global, project, or workflow'),
|
||||||
|
column('systemRole')
|
||||||
|
.bool.default(false)
|
||||||
|
.notNull.comment('Indicates if the role is managed by the system and cannot be edited'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// MYSQL
|
||||||
|
if (dbType === 'postgresdb' || dbType === 'sqlite') {
|
||||||
|
// POSTGRES
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE ${tablePrefix}role_scope (
|
||||||
|
"roleSlug" VARCHAR(128) NOT NULL,
|
||||||
|
"scopeSlug" VARCHAR(128) NOT NULL,
|
||||||
|
CONSTRAINT "PK_${tablePrefix}role_scope" PRIMARY KEY ("roleSlug", "scopeSlug"),
|
||||||
|
CONSTRAINT "FK_${tablePrefix}role" FOREIGN KEY ("roleSlug") REFERENCES ${tablePrefix}role ("slug") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "FK_${tablePrefix}scope" FOREIGN KEY ("scopeSlug") REFERENCES "${tablePrefix}scope" ("slug") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// MYSQL
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE ${tablePrefix}role_scope (
|
||||||
|
\`roleSlug\` VARCHAR(128) NOT NULL,
|
||||||
|
\`scopeSlug\` VARCHAR(128) NOT NULL,
|
||||||
|
FOREIGN KEY (\`scopeSlug\`) REFERENCES ${tablePrefix}scope (\`slug\`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
FOREIGN KEY (\`roleSlug\`) REFERENCES ${tablePrefix}role (\`slug\`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
PRIMARY KEY (\`roleSlug\`, \`scopeSlug\`)
|
||||||
|
) ENGINE=InnoDB;`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await createIndex('role_scope', ['scopeSlug']);
|
||||||
|
/*
|
||||||
|
await createTable('role_scope')
|
||||||
|
.withColumns(
|
||||||
|
column('id').int.primary.autoGenerate2,
|
||||||
|
column('roleSlug').varchar(128).notNull,
|
||||||
|
column('scopeSlug').varchar(128).notNull,
|
||||||
|
)
|
||||||
|
.withForeignKey('roleSlug', {
|
||||||
|
tableName: 'role',
|
||||||
|
columnName: 'slug',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
onUpdate: 'CASCADE',
|
||||||
|
})
|
||||||
|
.withForeignKey('scopeSlug', {
|
||||||
|
tableName: 'scope',
|
||||||
|
columnName: 'slug',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
onUpdate: 'CASCADE',
|
||||||
|
})
|
||||||
|
.withIndexOn('scopeSlug') // For fast lookup of which roles have access to a scope
|
||||||
|
.withIndexOn(['roleSlug', 'scopeSlug'], true); */
|
||||||
|
}
|
||||||
|
|
||||||
|
async down({ schemaBuilder: { dropTable } }: MigrationContext) {
|
||||||
|
await dropTable('role_scope');
|
||||||
|
await dropTable('role');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import type { MigrationContext, ReversibleMigration } from '../migration-types';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This migration links the role table to the user table, by adding a new column 'roleSlug'
|
||||||
|
* to the user table. It also ensures that all users have a valid role set in the 'roleSlug' column.
|
||||||
|
* The migration will insert the global roles that we need into the role table if they do not exist.
|
||||||
|
*
|
||||||
|
* The old 'role' column in the user table will be removed in a later migration.
|
||||||
|
*/
|
||||||
|
export class LinkRoleToUserTable1750252139168 implements ReversibleMigration {
|
||||||
|
async up({
|
||||||
|
schemaBuilder: { addForeignKey, addColumns, column },
|
||||||
|
escape,
|
||||||
|
dbType,
|
||||||
|
runQuery,
|
||||||
|
}: MigrationContext) {
|
||||||
|
const roleTableName = escape.tableName('role');
|
||||||
|
const userTableName = escape.tableName('user');
|
||||||
|
const slugColumn = escape.columnName('slug');
|
||||||
|
const roleColumn = escape.columnName('role');
|
||||||
|
const roleSlugColumn = escape.columnName('roleSlug');
|
||||||
|
const roleTypeColumn = escape.columnName('roleType');
|
||||||
|
const systemRoleColumn = escape.columnName('systemRole');
|
||||||
|
|
||||||
|
const isPostgresOrSqlite = dbType === 'postgresdb' || dbType === 'sqlite';
|
||||||
|
const upsertQuery = isPostgresOrSqlite
|
||||||
|
? `INSERT INTO ${roleTableName} (${slugColumn}, ${roleTypeColumn}, ${systemRoleColumn}) VALUES (:slug, :roleType, :systemRole) ON CONFLICT DO NOTHING`
|
||||||
|
: `INSERT IGNORE INTO ${roleTableName} (${slugColumn}, ${roleTypeColumn}, ${systemRoleColumn}) VALUES (:slug, :roleType, :systemRole)`;
|
||||||
|
|
||||||
|
// Make sure that the global roles that we need exist
|
||||||
|
for (const role of ['global:owner', 'global:admin', 'global:member']) {
|
||||||
|
await runQuery(upsertQuery, {
|
||||||
|
slug: role,
|
||||||
|
roleType: 'global',
|
||||||
|
systemRole: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await addColumns('user', [column('roleSlug').varchar(128).default("'global:member'").notNull]);
|
||||||
|
|
||||||
|
await runQuery(
|
||||||
|
`UPDATE ${userTableName} SET ${roleSlugColumn} = ${roleColumn} WHERE ${roleColumn} != ${roleSlugColumn}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fallback to 'global:member' for users that do not have a correct role set
|
||||||
|
// This should not happen in a correctly set up system, but we want to ensure
|
||||||
|
// that all users have a role set, before we add the foreign key constraint
|
||||||
|
await runQuery(
|
||||||
|
`UPDATE ${userTableName} SET ${roleSlugColumn} = 'global:member' WHERE NOT EXISTS (SELECT 1 FROM ${roleTableName} WHERE ${slugColumn} = ${roleSlugColumn})`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await addForeignKey('user', 'roleSlug', ['role', 'slug']);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down({ schemaBuilder: { dropForeignKey, dropColumns } }: MigrationContext) {
|
||||||
|
await dropForeignKey('user', 'roleSlug', ['role', 'slug']);
|
||||||
|
await dropColumns('user', ['roleSlug']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,6 +88,9 @@ import { AddWorkflowArchivedColumn1745934666076 } from '../common/1745934666076-
|
|||||||
import { DropRoleTable1745934666077 } from '../common/1745934666077-DropRoleTable';
|
import { DropRoleTable1745934666077 } from '../common/1745934666077-DropRoleTable';
|
||||||
import { AddProjectDescriptionColumn1747824239000 } from '../common/1747824239000-AddProjectDescriptionColumn';
|
import { AddProjectDescriptionColumn1747824239000 } from '../common/1747824239000-AddProjectDescriptionColumn';
|
||||||
import { AddLastActiveAtColumnToUser1750252139166 } from '../common/1750252139166-AddLastActiveAtColumnToUser';
|
import { AddLastActiveAtColumnToUser1750252139166 } from '../common/1750252139166-AddLastActiveAtColumnToUser';
|
||||||
|
import { AddScopeTables1750252139166 } from '../common/1750252139166-AddScopeTables';
|
||||||
|
import { AddRolesTables1750252139167 } from '../common/1750252139167-AddRolesTables';
|
||||||
|
import { LinkRoleToUserTable1750252139168 } from '../common/1750252139168-LinkRoleToUserTable';
|
||||||
import { AddInputsOutputsToTestCaseExecution1752669793000 } from '../common/1752669793000-AddInputsOutputsToTestCaseExecution';
|
import { AddInputsOutputsToTestCaseExecution1752669793000 } from '../common/1752669793000-AddInputsOutputsToTestCaseExecution';
|
||||||
import { CreateDataStoreTables1754475614601 } from '../common/1754475614601-CreateDataStoreTables';
|
import { CreateDataStoreTables1754475614601 } from '../common/1754475614601-CreateDataStoreTables';
|
||||||
import type { Migration } from '../migration-types';
|
import type { Migration } from '../migration-types';
|
||||||
@@ -185,6 +188,9 @@ export const mysqlMigrations: Migration[] = [
|
|||||||
ClearEvaluation1745322634000,
|
ClearEvaluation1745322634000,
|
||||||
AddProjectDescriptionColumn1747824239000,
|
AddProjectDescriptionColumn1747824239000,
|
||||||
AddLastActiveAtColumnToUser1750252139166,
|
AddLastActiveAtColumnToUser1750252139166,
|
||||||
|
AddScopeTables1750252139166,
|
||||||
|
AddRolesTables1750252139167,
|
||||||
|
LinkRoleToUserTable1750252139168,
|
||||||
AddInputsOutputsToTestCaseExecution1752669793000,
|
AddInputsOutputsToTestCaseExecution1752669793000,
|
||||||
CreateDataStoreTables1754475614601,
|
CreateDataStoreTables1754475614601,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ import { AddWorkflowArchivedColumn1745934666076 } from '../common/1745934666076-
|
|||||||
import { DropRoleTable1745934666077 } from '../common/1745934666077-DropRoleTable';
|
import { DropRoleTable1745934666077 } from '../common/1745934666077-DropRoleTable';
|
||||||
import { AddProjectDescriptionColumn1747824239000 } from '../common/1747824239000-AddProjectDescriptionColumn';
|
import { AddProjectDescriptionColumn1747824239000 } from '../common/1747824239000-AddProjectDescriptionColumn';
|
||||||
import { AddLastActiveAtColumnToUser1750252139166 } from '../common/1750252139166-AddLastActiveAtColumnToUser';
|
import { AddLastActiveAtColumnToUser1750252139166 } from '../common/1750252139166-AddLastActiveAtColumnToUser';
|
||||||
|
import { AddScopeTables1750252139166 } from '../common/1750252139166-AddScopeTables';
|
||||||
|
import { AddRolesTables1750252139167 } from '../common/1750252139167-AddRolesTables';
|
||||||
|
import { LinkRoleToUserTable1750252139168 } from '../common/1750252139168-LinkRoleToUserTable';
|
||||||
import { CreateDataStoreTables1754475614601 } from '../common/1754475614601-CreateDataStoreTables';
|
import { CreateDataStoreTables1754475614601 } from '../common/1754475614601-CreateDataStoreTables';
|
||||||
import type { Migration } from '../migration-types';
|
import type { Migration } from '../migration-types';
|
||||||
|
|
||||||
@@ -183,6 +186,9 @@ export const postgresMigrations: Migration[] = [
|
|||||||
ClearEvaluation1745322634000,
|
ClearEvaluation1745322634000,
|
||||||
AddProjectDescriptionColumn1747824239000,
|
AddProjectDescriptionColumn1747824239000,
|
||||||
AddLastActiveAtColumnToUser1750252139166,
|
AddLastActiveAtColumnToUser1750252139166,
|
||||||
|
AddScopeTables1750252139166,
|
||||||
|
AddRolesTables1750252139167,
|
||||||
|
LinkRoleToUserTable1750252139168,
|
||||||
AddInputsOutputsToTestCaseExecution1752669793000,
|
AddInputsOutputsToTestCaseExecution1752669793000,
|
||||||
CreateDataStoreTables1754475614601,
|
CreateDataStoreTables1754475614601,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ import { AddWorkflowArchivedColumn1745934666076 } from '../common/1745934666076-
|
|||||||
import { DropRoleTable1745934666077 } from '../common/1745934666077-DropRoleTable';
|
import { DropRoleTable1745934666077 } from '../common/1745934666077-DropRoleTable';
|
||||||
import { AddProjectDescriptionColumn1747824239000 } from '../common/1747824239000-AddProjectDescriptionColumn';
|
import { AddProjectDescriptionColumn1747824239000 } from '../common/1747824239000-AddProjectDescriptionColumn';
|
||||||
import { AddLastActiveAtColumnToUser1750252139166 } from '../common/1750252139166-AddLastActiveAtColumnToUser';
|
import { AddLastActiveAtColumnToUser1750252139166 } from '../common/1750252139166-AddLastActiveAtColumnToUser';
|
||||||
|
import { AddScopeTables1750252139166 } from '../common/1750252139166-AddScopeTables';
|
||||||
|
import { AddRolesTables1750252139167 } from '../common/1750252139167-AddRolesTables';
|
||||||
|
import { LinkRoleToUserTable1750252139168 } from '../common/1750252139168-LinkRoleToUserTable';
|
||||||
import { AddInputsOutputsToTestCaseExecution1752669793000 } from '../common/1752669793000-AddInputsOutputsToTestCaseExecution';
|
import { AddInputsOutputsToTestCaseExecution1752669793000 } from '../common/1752669793000-AddInputsOutputsToTestCaseExecution';
|
||||||
import { CreateDataStoreTables1754475614601 } from '../common/1754475614601-CreateDataStoreTables';
|
import { CreateDataStoreTables1754475614601 } from '../common/1754475614601-CreateDataStoreTables';
|
||||||
import type { Migration } from '../migration-types';
|
import type { Migration } from '../migration-types';
|
||||||
@@ -177,6 +180,9 @@ const sqliteMigrations: Migration[] = [
|
|||||||
ClearEvaluation1745322634000,
|
ClearEvaluation1745322634000,
|
||||||
AddProjectDescriptionColumn1747824239000,
|
AddProjectDescriptionColumn1747824239000,
|
||||||
AddLastActiveAtColumnToUser1750252139166,
|
AddLastActiveAtColumnToUser1750252139166,
|
||||||
|
AddScopeTables1750252139166,
|
||||||
|
AddRolesTables1750252139167,
|
||||||
|
LinkRoleToUserTable1750252139168,
|
||||||
AddInputsOutputsToTestCaseExecution1752669793000,
|
AddInputsOutputsToTestCaseExecution1752669793000,
|
||||||
CreateDataStoreTables1754475614601,
|
CreateDataStoreTables1754475614601,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ export { ExecutionRepository } from './execution.repository';
|
|||||||
export { EventDestinationsRepository } from './event-destinations.repository';
|
export { EventDestinationsRepository } from './event-destinations.repository';
|
||||||
export { FolderRepository } from './folder.repository';
|
export { FolderRepository } from './folder.repository';
|
||||||
export { FolderTagMappingRepository } from './folder-tag-mapping.repository';
|
export { FolderTagMappingRepository } from './folder-tag-mapping.repository';
|
||||||
|
export { ScopeRepository } from './scope.repository';
|
||||||
export { InstalledNodesRepository } from './installed-nodes.repository';
|
export { InstalledNodesRepository } from './installed-nodes.repository';
|
||||||
export { InstalledPackagesRepository } from './installed-packages.repository';
|
export { InstalledPackagesRepository } from './installed-packages.repository';
|
||||||
export { InvalidAuthTokenRepository } from './invalid-auth-token.repository';
|
export { InvalidAuthTokenRepository } from './invalid-auth-token.repository';
|
||||||
export { LicenseMetricsRepository } from './license-metrics.repository';
|
export { LicenseMetricsRepository } from './license-metrics.repository';
|
||||||
export { ProjectRelationRepository } from './project-relation.repository';
|
export { ProjectRelationRepository } from './project-relation.repository';
|
||||||
export { ProjectRepository } from './project.repository';
|
export { ProjectRepository } from './project.repository';
|
||||||
|
export { RoleRepository } from './role.repository';
|
||||||
export { ProcessedDataRepository } from './processed-data.repository';
|
export { ProcessedDataRepository } from './processed-data.repository';
|
||||||
export { SettingsRepository } from './settings.repository';
|
export { SettingsRepository } from './settings.repository';
|
||||||
export { TagRepository } from './tag.repository';
|
export { TagRepository } from './tag.repository';
|
||||||
|
|||||||
11
packages/@n8n/db/src/repositories/role.repository.ts
Normal file
11
packages/@n8n/db/src/repositories/role.repository.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Service } from '@n8n/di';
|
||||||
|
import { DataSource, Repository } from '@n8n/typeorm';
|
||||||
|
|
||||||
|
import { Role } from '../entities';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class RoleRepository extends Repository<Role> {
|
||||||
|
constructor(dataSource: DataSource) {
|
||||||
|
super(Role, dataSource.manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
packages/@n8n/db/src/repositories/scope.repository.ts
Normal file
11
packages/@n8n/db/src/repositories/scope.repository.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Service } from '@n8n/di';
|
||||||
|
import { DataSource, Repository } from '@n8n/typeorm';
|
||||||
|
|
||||||
|
import { Scope } from '../entities';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class ScopeRepository extends Repository<Scope> {
|
||||||
|
constructor(dataSource: DataSource) {
|
||||||
|
super(Scope, dataSource.manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { UsersListFilterDto } from '@n8n/api-types';
|
import type { UsersListFilterDto } from '@n8n/api-types';
|
||||||
import { Service } from '@n8n/di';
|
import { Service } from '@n8n/di';
|
||||||
import type { GlobalRole } from '@n8n/permissions';
|
|
||||||
import type { DeepPartial, EntityManager, SelectQueryBuilder } from '@n8n/typeorm';
|
import type { DeepPartial, EntityManager, SelectQueryBuilder } from '@n8n/typeorm';
|
||||||
import { Brackets, DataSource, In, IsNull, Not, Repository } from '@n8n/typeorm';
|
import { Brackets, DataSource, In, IsNull, Not, Repository } from '@n8n/typeorm';
|
||||||
|
|
||||||
@@ -65,13 +64,13 @@ export class UserRepository extends Repository<User> {
|
|||||||
const rows = (await this.createQueryBuilder()
|
const rows = (await this.createQueryBuilder()
|
||||||
.select(['role', 'COUNT(role) as count'])
|
.select(['role', 'COUNT(role) as count'])
|
||||||
.groupBy('role')
|
.groupBy('role')
|
||||||
.execute()) as Array<{ role: GlobalRole; count: string }>;
|
.execute()) as Array<{ role: string; count: string }>;
|
||||||
return rows.reduce(
|
return rows.reduce(
|
||||||
(acc, row) => {
|
(acc, row) => {
|
||||||
acc[row.role] = parseInt(row.count, 10);
|
acc[row.role] = parseInt(row.count, 10);
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{} as Record<GlobalRole, number>,
|
{} as Record<string, number>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
136
packages/@n8n/db/src/services/auth.roles.service.ts
Normal file
136
packages/@n8n/db/src/services/auth.roles.service.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import { Logger } from '@n8n/backend-common';
|
||||||
|
import { Service } from '@n8n/di';
|
||||||
|
import { ALL_SCOPES, ALL_ROLES, scopeInformation } from '@n8n/permissions';
|
||||||
|
|
||||||
|
import { Scope } from '../entities';
|
||||||
|
import { RoleRepository, ScopeRepository } from '../repositories';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class AuthRolesService {
|
||||||
|
constructor(
|
||||||
|
private readonly logger: Logger,
|
||||||
|
private readonly scopeRepository: ScopeRepository,
|
||||||
|
private readonly roleRepository: RoleRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private async syncScopes() {
|
||||||
|
const availableScopes = await this.scopeRepository.find({
|
||||||
|
select: {
|
||||||
|
slug: true,
|
||||||
|
displayName: true,
|
||||||
|
description: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const availableScopesMap = new Map(availableScopes.map((scope) => [scope.slug, scope]));
|
||||||
|
|
||||||
|
const scopesToUpdate = ALL_SCOPES.map((slug) => {
|
||||||
|
const info = scopeInformation[slug] ?? {
|
||||||
|
displayName: slug,
|
||||||
|
description: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const existingScope = availableScopesMap.get(slug);
|
||||||
|
if (!existingScope) {
|
||||||
|
const newScope = new Scope();
|
||||||
|
newScope.slug = slug;
|
||||||
|
newScope.displayName = info.displayName;
|
||||||
|
newScope.description = info.description ?? null;
|
||||||
|
return newScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
const needsUpdate =
|
||||||
|
existingScope.displayName !== info.displayName ||
|
||||||
|
existingScope.description !== info.description;
|
||||||
|
|
||||||
|
if (needsUpdate) {
|
||||||
|
existingScope.displayName = info.displayName;
|
||||||
|
existingScope.description = info.description ?? null;
|
||||||
|
return existingScope;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}).filter((scope) => scope !== null);
|
||||||
|
|
||||||
|
if (scopesToUpdate.length > 0) {
|
||||||
|
this.logger.info(`Updating ${scopesToUpdate.length} scopes...`);
|
||||||
|
await this.scopeRepository.save(scopesToUpdate);
|
||||||
|
this.logger.info('Scopes updated successfully.');
|
||||||
|
} else {
|
||||||
|
this.logger.debug('No scopes to update.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async syncRoles() {
|
||||||
|
const existingRoles = await this.roleRepository.find({
|
||||||
|
select: {
|
||||||
|
slug: true,
|
||||||
|
displayName: true,
|
||||||
|
description: true,
|
||||||
|
systemRole: true,
|
||||||
|
roleType: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
systemRole: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const allScopes = await this.scopeRepository.find({
|
||||||
|
select: {
|
||||||
|
slug: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingRolesMap = new Map(existingRoles.map((role) => [role.slug, role]));
|
||||||
|
|
||||||
|
for (const roleNamespace of Object.keys(ALL_ROLES) as Array<keyof typeof ALL_ROLES>) {
|
||||||
|
const rolesToUpdate = ALL_ROLES[roleNamespace]
|
||||||
|
.map((role) => {
|
||||||
|
const existingRole = existingRolesMap.get(role.role);
|
||||||
|
|
||||||
|
if (!existingRole) {
|
||||||
|
const newRole = this.roleRepository.create({
|
||||||
|
slug: role.role,
|
||||||
|
displayName: role.name,
|
||||||
|
description: role.description ?? null,
|
||||||
|
roleType: roleNamespace,
|
||||||
|
systemRole: true,
|
||||||
|
scopes: allScopes.filter((scope) => role.scopes.includes(scope.slug)),
|
||||||
|
});
|
||||||
|
return newRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
const needsUpdate =
|
||||||
|
existingRole.displayName !== role.name ||
|
||||||
|
existingRole.description !== role.description ||
|
||||||
|
existingRole.roleType !== roleNamespace ||
|
||||||
|
existingRole.scopes.some((scope) => !role.scopes.includes(scope.slug)) || // DB roles has scope that it should not have
|
||||||
|
role.scopes.some((scope) => !existingRole.scopes.some((s) => s.slug === scope)); // A role has scope that is not in DB
|
||||||
|
|
||||||
|
if (needsUpdate) {
|
||||||
|
existingRole.displayName = role.name;
|
||||||
|
existingRole.description = role.description ?? null;
|
||||||
|
existingRole.roleType = roleNamespace;
|
||||||
|
existingRole.scopes = allScopes.filter((scope) => role.scopes.includes(scope.slug));
|
||||||
|
return existingRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((role) => role !== null);
|
||||||
|
if (rolesToUpdate.length > 0) {
|
||||||
|
this.logger.info(`Updating ${rolesToUpdate.length} ${roleNamespace} roles...`);
|
||||||
|
await this.roleRepository.save(rolesToUpdate);
|
||||||
|
this.logger.info(`${roleNamespace} roles updated successfully.`);
|
||||||
|
} else {
|
||||||
|
this.logger.debug(`No ${roleNamespace} roles to update.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.logger.info('Initializing AuthRolesService...');
|
||||||
|
await this.syncScopes();
|
||||||
|
await this.syncRoles();
|
||||||
|
this.logger.info('AuthRolesService initialized successfully.');
|
||||||
|
}
|
||||||
|
}
|
||||||
1
packages/@n8n/db/src/services/index.ts
Normal file
1
packages/@n8n/db/src/services/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { AuthRolesService } from './auth.roles.service';
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Scope Information ensure scopes are defined correctly 1`] = `
|
||||||
|
[
|
||||||
|
"annotationTag:create",
|
||||||
|
"annotationTag:read",
|
||||||
|
"annotationTag:update",
|
||||||
|
"annotationTag:delete",
|
||||||
|
"annotationTag:list",
|
||||||
|
"annotationTag:*",
|
||||||
|
"auditLogs:manage",
|
||||||
|
"auditLogs:*",
|
||||||
|
"banner:dismiss",
|
||||||
|
"banner:*",
|
||||||
|
"community:register",
|
||||||
|
"community:*",
|
||||||
|
"communityPackage:install",
|
||||||
|
"communityPackage:uninstall",
|
||||||
|
"communityPackage:update",
|
||||||
|
"communityPackage:list",
|
||||||
|
"communityPackage:manage",
|
||||||
|
"communityPackage:*",
|
||||||
|
"credential:share",
|
||||||
|
"credential:move",
|
||||||
|
"credential:create",
|
||||||
|
"credential:read",
|
||||||
|
"credential:update",
|
||||||
|
"credential:delete",
|
||||||
|
"credential:list",
|
||||||
|
"credential:*",
|
||||||
|
"externalSecretsProvider:sync",
|
||||||
|
"externalSecretsProvider:create",
|
||||||
|
"externalSecretsProvider:read",
|
||||||
|
"externalSecretsProvider:update",
|
||||||
|
"externalSecretsProvider:delete",
|
||||||
|
"externalSecretsProvider:list",
|
||||||
|
"externalSecretsProvider:*",
|
||||||
|
"externalSecret:list",
|
||||||
|
"externalSecret:use",
|
||||||
|
"externalSecret:*",
|
||||||
|
"eventBusDestination:test",
|
||||||
|
"eventBusDestination:create",
|
||||||
|
"eventBusDestination:read",
|
||||||
|
"eventBusDestination:update",
|
||||||
|
"eventBusDestination:delete",
|
||||||
|
"eventBusDestination:list",
|
||||||
|
"eventBusDestination:*",
|
||||||
|
"ldap:sync",
|
||||||
|
"ldap:manage",
|
||||||
|
"ldap:*",
|
||||||
|
"license:manage",
|
||||||
|
"license:*",
|
||||||
|
"logStreaming:manage",
|
||||||
|
"logStreaming:*",
|
||||||
|
"orchestration:read",
|
||||||
|
"orchestration:list",
|
||||||
|
"orchestration:*",
|
||||||
|
"project:create",
|
||||||
|
"project:read",
|
||||||
|
"project:update",
|
||||||
|
"project:delete",
|
||||||
|
"project:list",
|
||||||
|
"project:*",
|
||||||
|
"saml:manage",
|
||||||
|
"saml:*",
|
||||||
|
"securityAudit:generate",
|
||||||
|
"securityAudit:*",
|
||||||
|
"sourceControl:pull",
|
||||||
|
"sourceControl:push",
|
||||||
|
"sourceControl:manage",
|
||||||
|
"sourceControl:*",
|
||||||
|
"tag:create",
|
||||||
|
"tag:read",
|
||||||
|
"tag:update",
|
||||||
|
"tag:delete",
|
||||||
|
"tag:list",
|
||||||
|
"tag:*",
|
||||||
|
"user:resetPassword",
|
||||||
|
"user:changeRole",
|
||||||
|
"user:enforceMfa",
|
||||||
|
"user:create",
|
||||||
|
"user:read",
|
||||||
|
"user:update",
|
||||||
|
"user:delete",
|
||||||
|
"user:list",
|
||||||
|
"user:*",
|
||||||
|
"variable:create",
|
||||||
|
"variable:read",
|
||||||
|
"variable:update",
|
||||||
|
"variable:delete",
|
||||||
|
"variable:list",
|
||||||
|
"variable:*",
|
||||||
|
"workersView:manage",
|
||||||
|
"workersView:*",
|
||||||
|
"workflow:share",
|
||||||
|
"workflow:execute",
|
||||||
|
"workflow:move",
|
||||||
|
"workflow:create",
|
||||||
|
"workflow:read",
|
||||||
|
"workflow:update",
|
||||||
|
"workflow:delete",
|
||||||
|
"workflow:list",
|
||||||
|
"workflow:*",
|
||||||
|
"folder:create",
|
||||||
|
"folder:read",
|
||||||
|
"folder:update",
|
||||||
|
"folder:delete",
|
||||||
|
"folder:list",
|
||||||
|
"folder:move",
|
||||||
|
"folder:*",
|
||||||
|
"insights:list",
|
||||||
|
"insights:*",
|
||||||
|
"oidc:manage",
|
||||||
|
"oidc:*",
|
||||||
|
"dataStore:create",
|
||||||
|
"dataStore:read",
|
||||||
|
"dataStore:update",
|
||||||
|
"dataStore:delete",
|
||||||
|
"dataStore:list",
|
||||||
|
"dataStore:readRow",
|
||||||
|
"dataStore:writeRow",
|
||||||
|
"dataStore:*",
|
||||||
|
"*",
|
||||||
|
]
|
||||||
|
`;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { ALL_SCOPES } from '@/scope-information';
|
||||||
|
|
||||||
|
describe('Scope Information', () => {
|
||||||
|
it('ensure scopes are defined correctly', () => {
|
||||||
|
expect(ALL_SCOPES).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,6 +2,7 @@ export type * from './types.ee';
|
|||||||
export * from './constants.ee';
|
export * from './constants.ee';
|
||||||
|
|
||||||
export * from './roles/scopes/global-scopes.ee';
|
export * from './roles/scopes/global-scopes.ee';
|
||||||
|
export * from './scope-information';
|
||||||
export * from './roles/role-maps.ee';
|
export * from './roles/role-maps.ee';
|
||||||
export * from './roles/all-roles';
|
export * from './roles/all-roles';
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const mapToRoleObject = <T extends keyof typeof ROLE_NAMES>(roles: Record<T, Sco
|
|||||||
role,
|
role,
|
||||||
name: ROLE_NAMES[role],
|
name: ROLE_NAMES[role],
|
||||||
scopes: getRoleScopes(role),
|
scopes: getRoleScopes(role),
|
||||||
|
description: ROLE_NAMES[role],
|
||||||
licensed: false,
|
licensed: false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
21
packages/@n8n/permissions/src/scope-information.ts
Normal file
21
packages/@n8n/permissions/src/scope-information.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { RESOURCES } from './constants.ee';
|
||||||
|
import type { Scope, ScopeInformation } from './types.ee';
|
||||||
|
|
||||||
|
function buildResourceScopes() {
|
||||||
|
const resourceScopes = Object.entries(RESOURCES).flatMap(([resource, operations]) => [
|
||||||
|
...operations.map((op) => `${resource}:${op}` as const),
|
||||||
|
`${resource}:*` as const,
|
||||||
|
]) as Scope[];
|
||||||
|
|
||||||
|
resourceScopes.push('*' as const); // Global wildcard
|
||||||
|
return resourceScopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ALL_SCOPES = buildResourceScopes();
|
||||||
|
|
||||||
|
export const scopeInformation: Partial<Record<Scope, ScopeInformation>> = {
|
||||||
|
'annotationTag:create': {
|
||||||
|
displayName: 'Create Annotation Tag',
|
||||||
|
description: 'Allows creating new annotation tags.',
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -11,6 +11,11 @@ import type {
|
|||||||
workflowSharingRoleSchema,
|
workflowSharingRoleSchema,
|
||||||
} from './schemas.ee';
|
} from './schemas.ee';
|
||||||
|
|
||||||
|
export type ScopeInformation = {
|
||||||
|
displayName: string;
|
||||||
|
description?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
/** Represents a resource that can have permissions applied to it */
|
/** Represents a resource that can have permissions applied to it */
|
||||||
export type Resource = keyof typeof RESOURCES;
|
export type Resource = keyof typeof RESOURCES;
|
||||||
|
|
||||||
@@ -59,6 +64,7 @@ export type AllRoleTypes = GlobalRole | ProjectRole | WorkflowSharingRole | Cred
|
|||||||
type RoleObject<T extends AllRoleTypes> = {
|
type RoleObject<T extends AllRoleTypes> = {
|
||||||
role: T;
|
role: T;
|
||||||
name: string;
|
name: string;
|
||||||
|
description?: string | null;
|
||||||
scopes: Scope[];
|
scopes: Scope[];
|
||||||
licensed: boolean;
|
licensed: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
import { LICENSE_FEATURES } from '@n8n/constants';
|
import { LICENSE_FEATURES } from '@n8n/constants';
|
||||||
import { ExecutionRepository, SettingsRepository } from '@n8n/db';
|
import { AuthRolesService, ExecutionRepository, SettingsRepository } from '@n8n/db';
|
||||||
import { Command } from '@n8n/decorators';
|
import { Command } from '@n8n/decorators';
|
||||||
import { Container } from '@n8n/di';
|
import { Container } from '@n8n/di';
|
||||||
import glob from 'fast-glob';
|
import glob from 'fast-glob';
|
||||||
@@ -172,6 +172,9 @@ export class Start extends BaseCommand<z.infer<typeof flagsSchema>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await super.init();
|
await super.init();
|
||||||
|
|
||||||
|
await Container.get(AuthRolesService).init();
|
||||||
|
|
||||||
this.activeWorkflowManager = Container.get(ActiveWorkflowManager);
|
this.activeWorkflowManager = Container.get(ActiveWorkflowManager);
|
||||||
|
|
||||||
const isMultiMainEnabled =
|
const isMultiMainEnabled =
|
||||||
|
|||||||
@@ -23,17 +23,20 @@ let expectedGlobalRoles: Array<{
|
|||||||
role: GlobalRole;
|
role: GlobalRole;
|
||||||
scopes: Scope[];
|
scopes: Scope[];
|
||||||
licensed: boolean;
|
licensed: boolean;
|
||||||
|
description: string;
|
||||||
}>;
|
}>;
|
||||||
let expectedProjectRoles: Array<{
|
let expectedProjectRoles: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
role: ProjectRole;
|
role: ProjectRole;
|
||||||
scopes: Scope[];
|
scopes: Scope[];
|
||||||
licensed: boolean;
|
licensed: boolean;
|
||||||
|
description: string;
|
||||||
}>;
|
}>;
|
||||||
let expectedCredentialRoles: Array<{
|
let expectedCredentialRoles: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
role: CredentialSharingRole;
|
role: CredentialSharingRole;
|
||||||
scopes: Scope[];
|
scopes: Scope[];
|
||||||
|
description: string;
|
||||||
licensed: boolean;
|
licensed: boolean;
|
||||||
}>;
|
}>;
|
||||||
let expectedWorkflowRoles: Array<{
|
let expectedWorkflowRoles: Array<{
|
||||||
@@ -41,6 +44,7 @@ let expectedWorkflowRoles: Array<{
|
|||||||
role: WorkflowSharingRole;
|
role: WorkflowSharingRole;
|
||||||
scopes: Scope[];
|
scopes: Scope[];
|
||||||
licensed: boolean;
|
licensed: boolean;
|
||||||
|
description: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@@ -52,18 +56,21 @@ beforeAll(async () => {
|
|||||||
role: 'global:owner',
|
role: 'global:owner',
|
||||||
scopes: getRoleScopes('global:owner'),
|
scopes: getRoleScopes('global:owner'),
|
||||||
licensed: true,
|
licensed: true,
|
||||||
|
description: 'Owner',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Admin',
|
name: 'Admin',
|
||||||
role: 'global:admin',
|
role: 'global:admin',
|
||||||
scopes: getRoleScopes('global:admin'),
|
scopes: getRoleScopes('global:admin'),
|
||||||
licensed: false,
|
licensed: false,
|
||||||
|
description: 'Admin',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Member',
|
name: 'Member',
|
||||||
role: 'global:member',
|
role: 'global:member',
|
||||||
scopes: getRoleScopes('global:member'),
|
scopes: getRoleScopes('global:member'),
|
||||||
licensed: true,
|
licensed: true,
|
||||||
|
description: 'Member',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
expectedProjectRoles = [
|
expectedProjectRoles = [
|
||||||
@@ -72,18 +79,21 @@ beforeAll(async () => {
|
|||||||
role: 'project:personalOwner',
|
role: 'project:personalOwner',
|
||||||
scopes: getRoleScopes('project:personalOwner'),
|
scopes: getRoleScopes('project:personalOwner'),
|
||||||
licensed: true,
|
licensed: true,
|
||||||
|
description: 'Project Owner',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Project Admin',
|
name: 'Project Admin',
|
||||||
role: 'project:admin',
|
role: 'project:admin',
|
||||||
scopes: getRoleScopes('project:admin'),
|
scopes: getRoleScopes('project:admin'),
|
||||||
licensed: false,
|
licensed: false,
|
||||||
|
description: 'Project Admin',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Project Editor',
|
name: 'Project Editor',
|
||||||
role: 'project:editor',
|
role: 'project:editor',
|
||||||
scopes: getRoleScopes('project:editor'),
|
scopes: getRoleScopes('project:editor'),
|
||||||
licensed: false,
|
licensed: false,
|
||||||
|
description: 'Project Editor',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
expectedCredentialRoles = [
|
expectedCredentialRoles = [
|
||||||
@@ -92,12 +102,14 @@ beforeAll(async () => {
|
|||||||
role: 'credential:owner',
|
role: 'credential:owner',
|
||||||
scopes: getRoleScopes('credential:owner'),
|
scopes: getRoleScopes('credential:owner'),
|
||||||
licensed: true,
|
licensed: true,
|
||||||
|
description: 'Credential Owner',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Credential User',
|
name: 'Credential User',
|
||||||
role: 'credential:user',
|
role: 'credential:user',
|
||||||
scopes: getRoleScopes('credential:user'),
|
scopes: getRoleScopes('credential:user'),
|
||||||
licensed: true,
|
licensed: true,
|
||||||
|
description: 'Credential User',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
expectedWorkflowRoles = [
|
expectedWorkflowRoles = [
|
||||||
@@ -106,12 +118,14 @@ beforeAll(async () => {
|
|||||||
role: 'workflow:owner',
|
role: 'workflow:owner',
|
||||||
scopes: getRoleScopes('workflow:owner'),
|
scopes: getRoleScopes('workflow:owner'),
|
||||||
licensed: true,
|
licensed: true,
|
||||||
|
description: 'Workflow Owner',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Workflow Editor',
|
name: 'Workflow Editor',
|
||||||
role: 'workflow:editor',
|
role: 'workflow:editor',
|
||||||
scopes: getRoleScopes('workflow:editor'),
|
scopes: getRoleScopes('workflow:editor'),
|
||||||
licensed: true,
|
licensed: true,
|
||||||
|
description: 'Workflow Editor',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -82,11 +82,12 @@ const credentialRoleTranslations = computed<Record<string, string>>(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const credentialRoles = computed<AllRolesMap['credential']>(() => {
|
const credentialRoles = computed<AllRolesMap['credential']>(() => {
|
||||||
return rolesStore.processedCredentialRoles.map(({ role, scopes, licensed }) => ({
|
return rolesStore.processedCredentialRoles.map(({ role, scopes, licensed, description }) => ({
|
||||||
role,
|
role,
|
||||||
name: credentialRoleTranslations.value[role],
|
name: credentialRoleTranslations.value[role],
|
||||||
scopes,
|
scopes,
|
||||||
licensed,
|
licensed,
|
||||||
|
description,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -88,8 +88,14 @@ describe('WorkflowShareModal.ee.vue', () => {
|
|||||||
workflowsEEStore.getWorkflowOwnerName = vi.fn(() => 'Owner Name');
|
workflowsEEStore.getWorkflowOwnerName = vi.fn(() => 'Owner Name');
|
||||||
projectsStore.personalProjects = [createProjectListItem()];
|
projectsStore.personalProjects = [createProjectListItem()];
|
||||||
rolesStore.processedWorkflowRoles = [
|
rolesStore.processedWorkflowRoles = [
|
||||||
{ name: 'Editor', role: 'workflow:editor', scopes: [], licensed: false },
|
{
|
||||||
{ name: 'Owner', role: 'workflow:owner', scopes: [], licensed: false },
|
name: 'Editor',
|
||||||
|
role: 'workflow:editor',
|
||||||
|
scopes: [],
|
||||||
|
licensed: false,
|
||||||
|
description: 'Editor',
|
||||||
|
},
|
||||||
|
{ name: 'Owner', role: 'workflow:owner', scopes: [], licensed: false, description: 'Owner' },
|
||||||
];
|
];
|
||||||
|
|
||||||
workflowSaving = useWorkflowSaving({ router: useRouter() });
|
workflowSaving = useWorkflowSaving({ router: useRouter() });
|
||||||
|
|||||||
@@ -108,11 +108,12 @@ const workflowRoleTranslations = computed(() => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const workflowRoles = computed(() =>
|
const workflowRoles = computed(() =>
|
||||||
rolesStore.processedWorkflowRoles.map(({ role, scopes, licensed }) => ({
|
rolesStore.processedWorkflowRoles.map(({ role, scopes, licensed, description }) => ({
|
||||||
role,
|
role,
|
||||||
name: workflowRoleTranslations.value[role],
|
name: workflowRoleTranslations.value[role],
|
||||||
scopes,
|
scopes,
|
||||||
licensed,
|
licensed,
|
||||||
|
description,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ describe('roles store', () => {
|
|||||||
{
|
{
|
||||||
name: 'Project Admin',
|
name: 'Project Admin',
|
||||||
role: 'project:admin',
|
role: 'project:admin',
|
||||||
|
description: 'Project Admin',
|
||||||
scopes: [
|
scopes: [
|
||||||
'workflow:create',
|
'workflow:create',
|
||||||
'workflow:read',
|
'workflow:read',
|
||||||
@@ -43,6 +44,7 @@ describe('roles store', () => {
|
|||||||
{
|
{
|
||||||
name: 'Project Owner',
|
name: 'Project Owner',
|
||||||
role: 'project:personalOwner',
|
role: 'project:personalOwner',
|
||||||
|
description: 'Project Owner',
|
||||||
scopes: [
|
scopes: [
|
||||||
'workflow:create',
|
'workflow:create',
|
||||||
'workflow:read',
|
'workflow:read',
|
||||||
@@ -67,6 +69,7 @@ describe('roles store', () => {
|
|||||||
{
|
{
|
||||||
name: 'Project Editor',
|
name: 'Project Editor',
|
||||||
role: 'project:editor',
|
role: 'project:editor',
|
||||||
|
description: 'Project Editor',
|
||||||
scopes: [
|
scopes: [
|
||||||
'workflow:create',
|
'workflow:create',
|
||||||
'workflow:read',
|
'workflow:read',
|
||||||
@@ -87,6 +90,7 @@ describe('roles store', () => {
|
|||||||
{
|
{
|
||||||
name: 'Project Viewer',
|
name: 'Project Viewer',
|
||||||
role: 'project:viewer',
|
role: 'project:viewer',
|
||||||
|
description: 'Project Viewer',
|
||||||
scopes: [
|
scopes: [
|
||||||
'credential:list',
|
'credential:list',
|
||||||
'credential:read',
|
'credential:read',
|
||||||
|
|||||||
Reference in New Issue
Block a user