{isLoadingGroup ? (
|
@@ -958,6 +959,13 @@ class BaseManageGroupPermissionResources extends React.Component {
{/* //App till here */}
+ {/* Worklfow Permission */}
+
{/* Data source */}
)}
diff --git a/frontend/src/modules/WorkspaceSettings/pages/Groups/components/WorkflowPermissionsUI/WorkflowPermissionsUI.jsx b/frontend/src/modules/WorkspaceSettings/pages/Groups/components/WorkflowPermissionsUI/WorkflowPermissionsUI.jsx
new file mode 100644
index 0000000000..e018aae80b
--- /dev/null
+++ b/frontend/src/modules/WorkspaceSettings/pages/Groups/components/WorkflowPermissionsUI/WorkflowPermissionsUI.jsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
+
+const WorkflowPermissionsUI = () => {
+ return <>>;
+};
+
+export default withEditionSpecificComponent(WorkflowPermissionsUI, 'WorkspaceSettings');
diff --git a/frontend/src/modules/WorkspaceSettings/pages/Groups/components/WorkflowPermissionsUI/index.js b/frontend/src/modules/WorkspaceSettings/pages/Groups/components/WorkflowPermissionsUI/index.js
new file mode 100644
index 0000000000..d7281c66c6
--- /dev/null
+++ b/frontend/src/modules/WorkspaceSettings/pages/Groups/components/WorkflowPermissionsUI/index.js
@@ -0,0 +1 @@
+export { default } from './WorkflowPermissionsUI.jsx';
diff --git a/frontend/src/modules/WorkspaceSettings/pages/Groups/index.js b/frontend/src/modules/WorkspaceSettings/pages/Groups/index.js
index 89a07985c0..f813d7b946 100644
--- a/frontend/src/modules/WorkspaceSettings/pages/Groups/index.js
+++ b/frontend/src/modules/WorkspaceSettings/pages/Groups/index.js
@@ -1 +1,18 @@
export { default } from './ManageGroupPermissionsPage';
+
+export const RESOURCE_TYPE = {
+ APPS: 'app',
+ DATA_SOURCES: 'data_source',
+ WORKFLOWS: 'workflow',
+};
+
+export const APP_TYPES = {
+ FRONT_END: 'front-end',
+ WORKFLOW: 'workflow',
+};
+
+export const RESOURCE_NAME_MAPPING = {
+ [RESOURCE_TYPE.APPS]: 'Apps',
+ [RESOURCE_TYPE.DATA_SOURCES]: 'Data Sources',
+ [RESOURCE_TYPE.WORKFLOWS]: 'Workflows',
+};
diff --git a/server/migrations/1746705301652-AddWorkflowPermissionsInGroupPermissions.ts b/server/migrations/1746705301652-AddWorkflowPermissionsInGroupPermissions.ts
new file mode 100644
index 0000000000..93b0a09767
--- /dev/null
+++ b/server/migrations/1746705301652-AddWorkflowPermissionsInGroupPermissions.ts
@@ -0,0 +1,19 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddWorkflowPermissionsInGroupPermissions1746705301652 implements MigrationInterface {
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`
+ ALTER TABLE permission_groups
+ ADD COLUMN workflow_create BOOLEAN NOT NULL DEFAULT FALSE,
+ ADD COLUMN workflow_delete BOOLEAN NOT NULL DEFAULT FALSE;
+ `);
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`
+ ALTER TABLE permission_groups
+ DROP COLUMN workflow_delete,
+ DROP COLUMN workflow_create;
+ `);
+ }
+}
diff --git a/server/migrations/1746705371665-AddWorkflowTypeInResourceType.ts b/server/migrations/1746705371665-AddWorkflowTypeInResourceType.ts
new file mode 100644
index 0000000000..8e8620f742
--- /dev/null
+++ b/server/migrations/1746705371665-AddWorkflowTypeInResourceType.ts
@@ -0,0 +1,11 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddWorkflowTypeInResourceType1746705371665 implements MigrationInterface {
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`
+ ALTER TYPE "resource_type" ADD VALUE IF NOT EXISTS 'workflow';
+ `);
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {}
+}
diff --git a/server/migrations/1746705448788-AddAppTypeInAppsGroupPermissions.ts b/server/migrations/1746705448788-AddAppTypeInAppsGroupPermissions.ts
new file mode 100644
index 0000000000..7a933e454c
--- /dev/null
+++ b/server/migrations/1746705448788-AddAppTypeInAppsGroupPermissions.ts
@@ -0,0 +1,25 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddAppTypeInAppsGroupPermissions1746705448788 implements MigrationInterface {
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`
+ DO $$
+ BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'app_type') THEN
+ CREATE TYPE "app_type" AS ENUM ('front-end', 'workflow');
+ END IF;
+ END
+ $$;
+ ALTER TABLE "apps_group_permissions"
+ ADD COLUMN "app_type" "app_type" NOT NULL DEFAULT 'front-end';
+ `);
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`
+ ALTER TABLE "apps_group_permissions"
+ DROP COLUMN "app_type";
+ DROP TYPE IF EXISTS "app_type";
+ `);
+ }
+}
diff --git a/server/package-lock.json b/server/package-lock.json
index 3cef36d54f..fa1e6d68ba 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -20645,4 +20645,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/server/src/entities/app.entity.ts b/server/src/entities/app.entity.ts
index 257da6233e..7449af495c 100644
--- a/server/src/entities/app.entity.ts
+++ b/server/src/entities/app.entity.ts
@@ -20,14 +20,20 @@ import { GroupApps } from './group_apps.entity';
import { AppGroupPermission } from './app_group_permission.entity';
import { AiConversation } from './ai_conversation.entity';
import { Organization } from './organization.entity';
+import { APP_TYPES } from '@modules/apps/constants';
@Entity({ name: 'apps' })
export class App extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
- @Column({ name: 'type' })
- type: string = 'front-end';
+ @Column({
+ name: 'type',
+ type: 'enum',
+ enum: APP_TYPES,
+ default: APP_TYPES.FRONT_END,
+ })
+ type: APP_TYPES;
@Column({ name: 'name' })
name: string;
diff --git a/server/src/entities/app_base.entity.ts b/server/src/entities/app_base.entity.ts
index f0d3724df4..24ccbbe201 100644
--- a/server/src/entities/app_base.entity.ts
+++ b/server/src/entities/app_base.entity.ts
@@ -15,6 +15,7 @@ import { User } from './user.entity';
import { AppVersion } from './app_version.entity';
import { GroupPermission } from './group_permission.entity';
import { AppGroupPermission } from './app_group_permission.entity';
+import { APP_TYPES } from '@modules/apps/constants';
@Entity({ name: 'apps' })
export class AppBase extends BaseEntity {
@@ -24,8 +25,13 @@ export class AppBase extends BaseEntity {
@Column({ name: 'name' })
name: string;
- @Column({ name: 'type' })
- type: string = 'front-end';
+ @Column({
+ name: 'type',
+ type: 'enum',
+ enum: APP_TYPES,
+ default: APP_TYPES.FRONT_END,
+ })
+ type: APP_TYPES;
@Column({ name: 'slug', unique: true })
slug: string;
diff --git a/server/src/entities/apps_group_permissions.entity.ts b/server/src/entities/apps_group_permissions.entity.ts
index 12abd55183..a88ca67048 100644
--- a/server/src/entities/apps_group_permissions.entity.ts
+++ b/server/src/entities/apps_group_permissions.entity.ts
@@ -12,6 +12,7 @@ import {
} from 'typeorm';
import { GranularPermissions } from './granular_permissions.entity';
import { GroupApps } from './group_apps.entity';
+import { APP_TYPES } from '@modules/apps/constants';
@Entity({ name: 'apps_group_permissions' })
export class AppsGroupPermissions extends BaseEntity {
@@ -22,6 +23,13 @@ export class AppsGroupPermissions extends BaseEntity {
@Column({ name: 'granular_permission_id', unique: true })
granularPermissionId: string;
+ @Column({
+ name: 'app_type',
+ type: 'enum',
+ enum: APP_TYPES,
+ })
+ appType: APP_TYPES;
+
@Column({ name: 'can_edit', nullable: false, default: false })
canEdit: boolean;
diff --git a/server/src/entities/group_apps.entity.ts b/server/src/entities/group_apps.entity.ts
index 6f6503b248..8bcd84d3a9 100644
--- a/server/src/entities/group_apps.entity.ts
+++ b/server/src/entities/group_apps.entity.ts
@@ -34,7 +34,7 @@ export class GroupApps extends BaseEntity {
@JoinColumn({ name: 'app_id' })
app: App;
- @ManyToOne(() => AppsGroupPermissions, (appsPermissions) => appsPermissions.id)
+ @ManyToOne(() => AppsGroupPermissions, (appsPermissions) => appsPermissions.groupApps)
@JoinColumn({ name: 'apps_group_permissions_id' })
appsPermissions: AppsGroupPermissions;
}
diff --git a/server/src/entities/group_permissions.entity.ts b/server/src/entities/group_permissions.entity.ts
index 693f4f930c..d321d84b5f 100644
--- a/server/src/entities/group_permissions.entity.ts
+++ b/server/src/entities/group_permissions.entity.ts
@@ -35,6 +35,12 @@ export class GroupPermissions extends BaseEntity {
@Column({ name: 'app_delete', default: false })
appDelete: boolean;
+ @Column({ name: 'workflow_create', default: false })
+ workflowCreate: boolean;
+
+ @Column({ name: 'workflow_delete', default: false })
+ workflowDelete: boolean;
+
@Column({ name: 'folder_crud', default: false })
folderCRUD: boolean;
diff --git a/server/src/modules/ability/constants.ts b/server/src/modules/ability/constants.ts
index b0b7319b82..f160095d88 100644
--- a/server/src/modules/ability/constants.ts
+++ b/server/src/modules/ability/constants.ts
@@ -1,5 +1,6 @@
import { MODULES } from '@modules/app/constants/modules';
-import { UserAppsPermissions, UserDataSourcePermissions, UserPermissions } from './types';
+import { UserAppsPermissions, UserDataSourcePermissions, UserPermissions, UserWorkflowPermissions } from './types';
+import { APP_TYPES } from '@modules/apps/constants';
export const DEFAULT_USER_PERMISSIONS: UserPermissions = {
isSuperAdmin: false,
@@ -8,6 +9,8 @@ export const DEFAULT_USER_PERMISSIONS: UserPermissions = {
isEndUser: false,
appCreate: false,
appDelete: false,
+ workflowCreate: false,
+ workflowDelete: false,
dataSourceCreate: false,
dataSourceDelete: false,
folderCRUD: false,
@@ -21,8 +24,19 @@ export const DEFAULT_USER_PERMISSIONS: UserPermissions = {
hiddenAppsId: [],
hideAll: false,
},
+ [MODULES.WORKFLOWS]: {
+ editableWorkflowsId: [],
+ isAllEditable: false,
+ executableWorkflowsId: [],
+ isAllExecutable: false,
+ },
};
+export const RESOURCE_TO_APP_TYPE_MAP = {
+ [MODULES.APP]: APP_TYPES.FRONT_END,
+ [MODULES.WORKFLOWS]: APP_TYPES.WORKFLOW,
+} as const;
+
export const DEFAULT_USER_APPS_PERMISSIONS: UserAppsPermissions = {
editableAppsId: [],
isAllEditable: false,
@@ -32,6 +46,13 @@ export const DEFAULT_USER_APPS_PERMISSIONS: UserAppsPermissions = {
hideAll: false,
};
+export const DEFAULT_USER_WORKFLOW_PERMISSIONS: UserWorkflowPermissions = {
+ editableWorkflowsId: [],
+ isAllEditable: false,
+ executableWorkflowsId: [],
+ isAllExecutable: false,
+};
+
export const DEFAULT_USER_DATA_SOURCE_PERMISSIONS: UserDataSourcePermissions = {
usableDataSourcesId: [],
isAllUsable: false,
diff --git a/server/src/modules/ability/service.ts b/server/src/modules/ability/service.ts
index 4e061ba27d..f9149449eb 100644
--- a/server/src/modules/ability/service.ts
+++ b/server/src/modules/ability/service.ts
@@ -53,12 +53,14 @@ export class AbilityService extends IAbilityService {
folderCRUD: acc.folderCRUD || group.folderCRUD,
orgConstantCRUD: acc.orgConstantCRUD || group.orgConstantCRUD,
orgVariableCRUD: acc.orgVariableCRUD,
+ workflowCreate: acc.workflowCreate || group.workflowCreate,
+ workflowDelete: acc.workflowDelete || group.workflowDelete,
};
}, DEFAULT_USER_PERMISSIONS);
userPermissions.isAdmin = adminGroup;
userPermissions.isSuperAdmin = false;
-
+
if (!adminGroup) {
const isBuilder = await this.abilityUtilService.isBuilder(user);
if (isBuilder) {
@@ -84,8 +86,8 @@ export class AbilityService extends IAbilityService {
dsGranularPermissions
);
- if(userPermissions.isBuilder) {
- /* in community edition. builder can use the datasources */
+ if (userPermissions.isBuilder) {
+ /* in community edition. builder can use the datasources */
userPermissions[MODULES.GLOBAL_DATA_SOURCE].isAllUsable = true;
}
}
diff --git a/server/src/modules/ability/types.ts b/server/src/modules/ability/types.ts
index 3e7e175b2f..30838bb739 100644
--- a/server/src/modules/ability/types.ts
+++ b/server/src/modules/ability/types.ts
@@ -17,6 +17,8 @@ export interface UserPermissions {
isEndUser: boolean;
appCreate: boolean;
appDelete: boolean;
+ workflowCreate: boolean;
+ workflowDelete: boolean;
dataSourceCreate: boolean;
dataSourceDelete: boolean;
folderCRUD: boolean;
@@ -24,6 +26,13 @@ export interface UserPermissions {
orgVariableCRUD: boolean;
[MODULES.APP]?: UserAppsPermissions;
[MODULES.GLOBAL_DATA_SOURCE]?: UserDataSourcePermissions;
+ [MODULES.WORKFLOWS]?: UserWorkflowPermissions;
+}
+export interface UserWorkflowPermissions {
+ editableWorkflowsId: string[];
+ isAllEditable: boolean;
+ executableWorkflowsId: string[];
+ isAllExecutable: boolean;
}
export interface UserAppsPermissions {
diff --git a/server/src/modules/ability/util.service.ts b/server/src/modules/ability/util.service.ts
index 6c4da719a9..ebc06ae81a 100644
--- a/server/src/modules/ability/util.service.ts
+++ b/server/src/modules/ability/util.service.ts
@@ -8,12 +8,79 @@ import { AppBase } from '@entities/app_base.entity';
import { User } from '@entities/user.entity';
import { dbTransactionWrap } from '@helpers/database.helper';
import { USER_ROLE } from '@modules/group-permissions/constants';
-import { DEFAULT_USER_APPS_PERMISSIONS } from './constants';
+import { DEFAULT_USER_APPS_PERMISSIONS, RESOURCE_TO_APP_TYPE_MAP } from './constants';
import { RolesRepository } from '@modules/roles/repository';
+import { APP_TYPES } from '@modules/apps/constants';
@Injectable()
export class AbilityUtilService {
constructor(private readonly roleRepository: RolesRepository) {}
+
+ private getAppTypeConditions(resourcesList: ResourcesItem[]): { conditions: string[]; params: Record } {
+ const conditions: string[] = [];
+ const params: Record = {};
+ let paramIndex = 0;
+
+ // Get unique resource types from the list
+ const resourceTypes = Array.from(new Set(resourcesList.map((item) => item.resource)));
+
+ resourceTypes.forEach((resourceType) => {
+ const appType = RESOURCE_TO_APP_TYPE_MAP[resourceType];
+ if (appType) {
+ const paramName = `appType${paramIndex}`;
+ conditions.push(`appsGroupPermissions.appType = :${paramName}`);
+ params[paramName] = appType;
+ paramIndex++;
+ }
+ });
+
+ return { conditions, params };
+ }
+
+ private addAppsAndWorkflowPermissionsTOQuery(
+ query: SelectQueryBuilder,
+ resourcesList?: ResourcesItem[]
+ ) {
+ query
+ .leftJoin('granularPermissions.appsGroupPermissions', 'appsGroupPermissions')
+ .leftJoin('appsGroupPermissions.groupApps', 'groupApps')
+ .addSelect([
+ 'groupApps.appId',
+ 'appsGroupPermissions.canEdit',
+ 'appsGroupPermissions.canView',
+ 'appsGroupPermissions.hideFromDashboard',
+ 'appsGroupPermissions.appType',
+ ]);
+
+ const resourceIdList = Array.from(
+ new Set(resourcesList?.filter((item) => item?.resourceId).map((item) => item.resourceId))
+ );
+
+ if (resourceIdList?.length) {
+ query.andWhere(
+ new Brackets((qb) => {
+ resourceIdList.forEach((resourceId, index) => {
+ if (index === 0) {
+ const { conditions, params } = this.getAppTypeConditions(resourcesList);
+
+ // Combine conditions with OR if multiple types are present
+ const typeCondition = conditions.length > 1 ? `(${conditions.join(' OR ')})` : conditions[0];
+
+ qb.where(`(${typeCondition}) AND groupApps.appId = :resourceId`, {
+ resourceId,
+ ...params,
+ })
+ .orWhere('granularPermissions.isAll = true')
+ .orWhere('groupApps.id IS NULL');
+ } else {
+ qb.orWhere('groupApps.appId = :resourceId', { resourceId });
+ }
+ });
+ })
+ );
+ }
+ }
+
getUserPermissionsQuery(
userId: string,
resourcePermissionObject: ResourcePermissionQueryObject,
@@ -36,10 +103,13 @@ export class AbilityUtilService {
}
if (resources?.length) {
- const appsResourcesList = resources.filter((item) => item.resource === MODULES.APP);
+ const appsAndWorkflowResourcesList = resources.filter(
+ (item) => item.resource === MODULES.APP || item.resource === MODULES.WORKFLOWS
+ );
const dataSourcesResourcesList = resources.filter((item) => item.resource === MODULES.GLOBAL_DATA_SOURCE);
- if (appsResourcesList?.length) {
- this.addAppsPermissionsTOQuery(query, appsResourcesList);
+
+ if (appsAndWorkflowResourcesList?.length) {
+ this.addAppsAndWorkflowPermissionsTOQuery(query, appsAndWorkflowResourcesList);
}
if (dataSourcesResourcesList?.length) {
this.addDataSourcesPermissionsTOQuery(query, dataSourcesResourcesList);
@@ -49,36 +119,6 @@ export class AbilityUtilService {
return query;
}
- private addAppsPermissionsTOQuery(query: SelectQueryBuilder, appsList?: ResourcesItem[]) {
- query
- .leftJoin('granularPermissions.appsGroupPermissions', 'appsGroupPermissions')
- .leftJoin('appsGroupPermissions.groupApps', 'groupApps')
- .addSelect([
- 'groupApps.appId',
- 'appsGroupPermissions.canEdit',
- 'appsGroupPermissions.canView',
- 'appsGroupPermissions.hideFromDashboard',
- ]);
-
- const appsIdList = Array.from(new Set(appsList?.filter((item) => item?.resourceId).map((item) => item.resourceId)));
-
- if (appsIdList?.length) {
- query.andWhere(
- new Brackets((qb) => {
- appsIdList.forEach((appId, index) => {
- if (index === 0) {
- qb.where('groupApps.appId = :appId', { appId })
- .orWhere('granularPermissions.isAll = true')
- .orWhere('groupApps.id IS NULL');
- } else {
- qb.orWhere('groupApps.appId = :appId', { appId });
- }
- });
- })
- );
- }
- }
-
private addDataSourcesPermissionsTOQuery(
query: SelectQueryBuilder,
dataSourcesList?: ResourcesItem[]
@@ -145,7 +185,7 @@ export class AbilityUtilService {
// Use the provided manager to perform database operations
await dbTransactionWrap(async (manager: EntityManager) => {
const appsOwnedByUser = await manager.find(AppBase, {
- where: { userId: user.id, organizationId: user.organizationId },
+ where: { userId: user.id, organizationId: user.organizationId, type: APP_TYPES.FRONT_END },
});
const appsIdOwnedByUser = appsOwnedByUser.map((app) => app.id);
diff --git a/server/src/modules/app/ability-factory.ts b/server/src/modules/app/ability-factory.ts
index d461d53788..62b14c6c10 100644
--- a/server/src/modules/app/ability-factory.ts
+++ b/server/src/modules/app/ability-factory.ts
@@ -46,7 +46,7 @@ export abstract class AbilityFactory {
await this.defineAbilityFor(
can,
- { userPermission, superAdmin, isAdmin, isBuilder, isEndUser, user },
+ { userPermission, superAdmin, isAdmin, isBuilder, isEndUser, user, resource },
extractedMetadata,
request
);
diff --git a/server/src/modules/app/guards/ability.guard.ts b/server/src/modules/app/guards/ability.guard.ts
index 7532b61376..4fd98e60e8 100644
--- a/server/src/modules/app/guards/ability.guard.ts
+++ b/server/src/modules/app/guards/ability.guard.ts
@@ -22,6 +22,13 @@ export abstract class AbilityGuard implements CanActivate {
protected forwardAbility(): boolean {
return false;
}
+ protected resource: any;
+ protected getResourceObject(): any {
+ return this.resource;
+ }
+ protected setResourceObject(resource: any): void {
+ this.resource = resource;
+ }
protected getResource(): ResourceDetails | ResourceDetails[] {
return;
}
@@ -37,6 +44,9 @@ export abstract class AbilityGuard implements CanActivate {
const request = context.switchToHttp().getRequest();
const user = request.user;
const app: App = request.tj_app;
+ if (app) {
+ this.setResourceObject(app);
+ }
if (!features?.length) {
return false;
diff --git a/server/src/modules/app/loader.ts b/server/src/modules/app/loader.ts
index 7c505ab8a2..ab6cb2ce72 100644
--- a/server/src/modules/app/loader.ts
+++ b/server/src/modules/app/loader.ts
@@ -111,13 +111,13 @@ export class AppModuleLoader {
* â–ˆ â–ˆ
* ███████████████████████████████████████████████████████████████████████████████
*/
- const dynamicModules: DynamicModule[] = [];
+ const dynamicModules: Promise[] = [];
try {
const { LogToFileModule } = await import(`${await getImportPath(configs.IS_GET_CONTEXT)}/log-to-file/module`);
const { AuditLogsModule } = await import(`${await getImportPath(configs.IS_GET_CONTEXT)}/audit-logs/module`);
- dynamicModules.push(await LogToFileModule.register(configs));
- dynamicModules.push(await AuditLogsModule.register(configs));
+ dynamicModules.push(LogToFileModule.register(configs));
+ dynamicModules.push(AuditLogsModule.register(configs));
} catch (error) {
console.error('Error loading dynamic modules:', error);
}
diff --git a/server/src/modules/app/types.ts b/server/src/modules/app/types.ts
index 4ecc5960f5..16ee1ce0eb 100644
--- a/server/src/modules/app/types.ts
+++ b/server/src/modules/app/types.ts
@@ -11,6 +11,7 @@ export interface UserAllPermissions {
isBuilder: boolean;
isEndUser: boolean;
user: User;
+ resource: ResourceDetails[];
}
export interface FeatureConfig {
diff --git a/server/src/modules/apps/ability/app.ability.ts b/server/src/modules/apps/ability/app.ability.ts
new file mode 100644
index 0000000000..eb51ebc716
--- /dev/null
+++ b/server/src/modules/apps/ability/app.ability.ts
@@ -0,0 +1,78 @@
+import { AbilityBuilder } from '@casl/ability';
+import { UserAllPermissions } from '@modules/app/types';
+import { FEATURE_KEY } from '../constants';
+import { App } from '@entities/app.entity';
+import { MODULES } from '@modules/app/constants/modules';
+import { FeatureAbility } from './index';
+
+export function defineAppAbility(
+ can: AbilityBuilder['can'],
+ UserAllPermissions: UserAllPermissions,
+ appId?: string
+): void {
+ const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
+ const userAppPermissions = userPermission?.[MODULES.APP];
+ const isAllAppsEditable = !!userAppPermissions?.isAllEditable;
+ const isAllAppsCreatable = !!userPermission?.appCreate;
+ const isAllAppsDeletable = !!userPermission?.appDelete;
+ const isAllAppsViewable = !!userAppPermissions?.isAllViewable;
+
+ // App listing is available to all
+ can(FEATURE_KEY.GET, App);
+
+ if (isAdmin || superAdmin) {
+ // Admin or super admin and do all operations
+ can(
+ [
+ FEATURE_KEY.CREATE,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.GET_ASSOCIATED_TABLES,
+ FEATURE_KEY.GET_ONE,
+ FEATURE_KEY.GET_BY_SLUG,
+ FEATURE_KEY.RELEASE,
+ FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
+ FEATURE_KEY.UPDATE_ICON,
+ FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
+ ],
+ App
+ );
+ return;
+ }
+
+ if (isAllAppsCreatable) {
+ can(FEATURE_KEY.CREATE, App);
+ }
+
+ if (
+ isAllAppsEditable ||
+ (userAppPermissions?.editableAppsId?.length && appId && userAppPermissions.editableAppsId.includes(appId))
+ ) {
+ can(
+ [
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.GET_ASSOCIATED_TABLES,
+ FEATURE_KEY.GET_ONE,
+ FEATURE_KEY.GET_BY_SLUG,
+ FEATURE_KEY.RELEASE,
+ FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
+ FEATURE_KEY.UPDATE_ICON,
+ FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
+ ],
+ App
+ );
+ if (isAllAppsDeletable) {
+ // Gives delete permission only for editable apps
+ can(FEATURE_KEY.DELETE, App);
+ }
+ return;
+ }
+
+ if (
+ isAllAppsViewable ||
+ (userAppPermissions?.viewableAppsId?.length && appId && userAppPermissions.viewableAppsId.includes(appId))
+ ) {
+ // add view permissions for all apps or specific app
+ can([FEATURE_KEY.GET_ONE, FEATURE_KEY.GET_BY_SLUG, FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS], App);
+ }
+}
diff --git a/server/src/modules/apps/ability/guard.ts b/server/src/modules/apps/ability/guard.ts
index 2601231e14..cb79a718cd 100644
--- a/server/src/modules/apps/ability/guard.ts
+++ b/server/src/modules/apps/ability/guard.ts
@@ -4,13 +4,24 @@ import { AbilityGuard } from '@modules/app/guards/ability.guard';
import { App } from '@entities/app.entity';
import { ResourceDetails } from '@modules/app/types';
import { MODULES } from '@modules/app/constants/modules';
+import { APP_TYPES } from '../constants';
@Injectable()
export class FeatureAbilityGuard extends AbilityGuard {
protected getResource(): ResourceDetails {
- return {
- resourceType: MODULES.APP,
- };
+ const resource = this.getResourceObject();
+ switch (resource?.type) {
+ case APP_TYPES.FRONT_END:
+ return {
+ resourceType: MODULES.APP,
+ };
+ case APP_TYPES.WORKFLOW:
+ return {
+ resourceType: MODULES.WORKFLOWS,
+ };
+ default:
+ return null;
+ }
}
protected getAbilityFactory() {
return FeatureAbilityFactory;
diff --git a/server/src/modules/apps/ability/index.ts b/server/src/modules/apps/ability/index.ts
index d53309202f..970986ffa4 100644
--- a/server/src/modules/apps/ability/index.ts
+++ b/server/src/modules/apps/ability/index.ts
@@ -4,7 +4,7 @@ import { AbilityFactory } from '@modules/app/ability-factory';
import { UserAllPermissions } from '@modules/app/types';
import { FEATURE_KEY } from '../constants';
import { App } from '@entities/app.entity';
-import { MODULES } from '@modules/app/constants/modules';
+import { createAbility } from './utility';
type Subjects = InferSubjects | 'all';
export type FeatureAbility = Ability<[FEATURE_KEY, Subjects]>;
@@ -21,72 +21,7 @@ export class FeatureAbilityFactory extends AbilityFactory
extractedMetadata: { moduleName: string; features: string[] },
request?: any
): void {
- const appId = request?.tj_resource_id;
- const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
-
- const userAppPermissions = userPermission?.[MODULES.APP];
- const isAllAppsEditable = !!userAppPermissions?.isAllEditable;
- const isAllAppsCreatable = !!userPermission?.appCreate;
- const isAllAppsDeletable = !!userPermission?.appDelete;
- const isAllAppsViewable = !!userAppPermissions?.isAllViewable;
-
- // App listing is available to all
- can(FEATURE_KEY.GET, App);
-
- if (isAdmin || superAdmin) {
- // Admin or super admin and do all operations
- can(
- [
- FEATURE_KEY.CREATE,
- FEATURE_KEY.UPDATE,
- FEATURE_KEY.DELETE,
- FEATURE_KEY.GET_ASSOCIATED_TABLES,
- FEATURE_KEY.GET_ONE,
- FEATURE_KEY.GET_BY_SLUG,
- FEATURE_KEY.RELEASE,
- FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
- FEATURE_KEY.UPDATE_ICON,
- FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
- ],
- App
- );
- return;
- }
-
- if (isAllAppsCreatable) {
- can(FEATURE_KEY.CREATE, App);
- }
-
- if (
- isAllAppsEditable ||
- (userAppPermissions?.editableAppsId?.length && appId && userAppPermissions.editableAppsId.includes(appId))
- ) {
- can(
- [
- FEATURE_KEY.UPDATE,
- FEATURE_KEY.GET_ASSOCIATED_TABLES,
- FEATURE_KEY.GET_ONE,
- FEATURE_KEY.GET_BY_SLUG,
- FEATURE_KEY.RELEASE,
- FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
- FEATURE_KEY.UPDATE_ICON,
- FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
- ],
- App
- );
- if (isAllAppsDeletable) {
- // Gives delete permission only for editable apps
- can(FEATURE_KEY.DELETE, App);
- }
- return;
- }
-
- if (
- isAllAppsViewable ||
- (userAppPermissions?.viewableAppsId?.length && appId && userAppPermissions.viewableAppsId.includes(appId))
- ) {
- // add view permissions for all apps or specific app
- can([FEATURE_KEY.GET_ONE, FEATURE_KEY.GET_BY_SLUG, FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS], App);
- }
+ const resourceId = request?.tj_resource_id;
+ createAbility(can, UserAllPermissions, resourceId);
}
}
diff --git a/server/src/modules/apps/ability/utility.ts b/server/src/modules/apps/ability/utility.ts
new file mode 100644
index 0000000000..3247722661
--- /dev/null
+++ b/server/src/modules/apps/ability/utility.ts
@@ -0,0 +1,27 @@
+import { AbilityBuilder } from '@casl/ability';
+import { UserAllPermissions } from '@modules/app/types';
+import { MODULES } from '@modules/app/constants/modules';
+import { FeatureAbility } from './index';
+import { defineAppAbility } from './app.ability';
+import { defineWorkflowAbility } from './workflow.ability';
+
+export function createAbility(
+ can: AbilityBuilder['can'],
+ UserAllPermissions: UserAllPermissions,
+ resourceId?: string
+): void {
+ const resourceType = UserAllPermissions?.resource[0]?.resourceType
+ ? UserAllPermissions.resource[0].resourceType
+ : MODULES.APP;
+
+ switch (resourceType) {
+ case MODULES.APP:
+ defineAppAbility(can, UserAllPermissions, resourceId);
+ break;
+ case MODULES.WORKFLOWS:
+ defineWorkflowAbility(can, UserAllPermissions, resourceId);
+ break;
+ default:
+ throw new Error(`Unsupported resource type: ${resourceType}`);
+ }
+}
diff --git a/server/src/modules/apps/ability/workflow.ability.ts b/server/src/modules/apps/ability/workflow.ability.ts
new file mode 100644
index 0000000000..d02443c5a4
--- /dev/null
+++ b/server/src/modules/apps/ability/workflow.ability.ts
@@ -0,0 +1,78 @@
+import { AbilityBuilder } from '@casl/ability';
+import { UserAllPermissions } from '@modules/app/types';
+import { FEATURE_KEY } from '../constants';
+import { App } from '@entities/app.entity';
+import { MODULES } from '@modules/app/constants/modules';
+import { FeatureAbility } from './index';
+
+export function defineWorkflowAbility(
+ can: AbilityBuilder['can'],
+ UserAllPermissions: UserAllPermissions,
+ workflowId?: string
+): void {
+ const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
+ const userWorkflowPermissions = userPermission?.[MODULES.WORKFLOWS];
+ const isAllWorkflowsEditable = !!userWorkflowPermissions?.isAllEditable;
+ const isAllWorkflowsCreatable = !!userPermission?.workflowCreate;
+ const isAllWorkflowsDeletable = !!userPermission?.workflowDelete;
+ const isAllWorkflowsExecutable = !!userWorkflowPermissions?.isAllExecutable;
+
+ // Workflow listing is available to all
+ can(FEATURE_KEY.GET, App);
+
+ if (isAdmin || superAdmin) {
+ // Admin or super admin can do all operations
+ can(
+ [
+ FEATURE_KEY.CREATE,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.GET_ONE,
+ FEATURE_KEY.GET_BY_SLUG,
+ FEATURE_KEY.RELEASE,
+ FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
+ FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
+ ],
+ App
+ );
+ return;
+ }
+
+ if (isAllWorkflowsCreatable) {
+ can(FEATURE_KEY.CREATE, App);
+ }
+
+ if (
+ isAllWorkflowsEditable ||
+ (userWorkflowPermissions?.editableWorkflowsId?.length &&
+ workflowId &&
+ userWorkflowPermissions.editableWorkflowsId.includes(workflowId))
+ ) {
+ can(
+ [
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.GET_ONE,
+ FEATURE_KEY.GET_BY_SLUG,
+ FEATURE_KEY.RELEASE,
+ FEATURE_KEY.VALIDATE_PRIVATE_APP_ACCESS,
+ FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS,
+ ],
+ App
+ );
+ if (isAllWorkflowsDeletable) {
+ // Gives delete permission only for editable workflows
+ can(FEATURE_KEY.DELETE, App);
+ }
+ return;
+ }
+
+ if (
+ isAllWorkflowsExecutable ||
+ (userWorkflowPermissions?.executableWorkflowsId?.length &&
+ workflowId &&
+ userWorkflowPermissions.executableWorkflowsId.includes(workflowId))
+ ) {
+ // add view permissions for all workflows or specific workflow
+ can([FEATURE_KEY.GET_ONE, FEATURE_KEY.GET_BY_SLUG, FEATURE_KEY.VALIDATE_RELEASED_APP_ACCESS], App);
+ }
+}
diff --git a/server/src/modules/apps/service.ts b/server/src/modules/apps/service.ts
index 6d09dba04f..66c0c681bb 100644
--- a/server/src/modules/apps/service.ts
+++ b/server/src/modules/apps/service.ts
@@ -17,7 +17,7 @@ import {
ValidateAppAccessResponseDto,
VersionReleaseDto,
} from './dto';
-import { FEATURE_KEY } from './constants';
+import { APP_TYPES, FEATURE_KEY } from './constants';
import { camelizeKeys, decamelizeKeys } from 'humps';
import { App } from '@entities/app.entity';
import { AppsUtilService } from './util.service';
@@ -62,7 +62,7 @@ export class AppsService implements IAppsService {
async create(user: User, appCreateDto: AppCreateDto) {
const { name, icon, type } = appCreateDto;
return await dbTransactionWrap(async (manager: EntityManager) => {
- const app = await this.appsUtilService.create(name, user, type, manager);
+ const app = await this.appsUtilService.create(name, user, type as APP_TYPES, manager);
const appUpdateDto = new AppUpdateDto();
appUpdateDto.name = name;
@@ -200,7 +200,8 @@ export class AppsService implements IAppsService {
user,
folder,
parseInt(page || '1'),
- searchKey
+ searchKey,
+ type as APP_TYPES
);
apps = viewableApps;
totalFolderCount = totalCount;
@@ -215,7 +216,7 @@ export class AppsService implements IAppsService {
}
}
- const totalCount = await this.appsUtilService.count(user, searchKey, type);
+ const totalCount = await this.appsUtilService.count(user, searchKey, type as APP_TYPES);
const totalPageCount = folderId ? totalFolderCount : totalCount;
diff --git a/server/src/modules/apps/services/workflow.service.ts b/server/src/modules/apps/services/workflow.service.ts
index b92b3bb573..47c6c4d397 100644
--- a/server/src/modules/apps/services/workflow.service.ts
+++ b/server/src/modules/apps/services/workflow.service.ts
@@ -1,14 +1,14 @@
import { Injectable } from '@nestjs/common';
import { AppsRepository } from '../repository';
import { IWorkflowService } from '../interfaces/services/IWorkflowService';
-
+import { APP_TYPES } from '../constants';
@Injectable()
export class WorkflowService implements IWorkflowService {
constructor(protected readonly appsRepository: AppsRepository) {}
async getWorkflows(organizationId: string) {
const workflowApps = await this.appsRepository.find({
- where: { type: 'workflow', organizationId },
+ where: { type: APP_TYPES.WORKFLOW, organizationId },
});
const result = workflowApps.map((workflowApp) => ({ id: workflowApp.id, name: workflowApp.name }));
diff --git a/server/src/modules/apps/util.service.ts b/server/src/modules/apps/util.service.ts
index e90e98a467..5e94c25beb 100644
--- a/server/src/modules/apps/util.service.ts
+++ b/server/src/modules/apps/util.service.ts
@@ -32,18 +32,16 @@ import { cloneDeep } from 'lodash';
import { merge } from 'lodash';
import { mergeWith } from 'lodash';
import { isArray } from 'lodash';
-import { UserAppsPermissions } from '@modules/ability/types';
+import { UserAppsPermissions, UserWorkflowPermissions } from '@modules/ability/types';
import { AbilityService } from '@modules/ability/interfaces/IService';
import { DataSourcesRepository } from '@modules/data-sources/repository';
import { IAppsUtilService } from './interfaces/IUtilService';
import { DataSourcesUtilService } from '@modules/data-sources/util.service';
import { AppVersionUpdateDto } from '@dto/app-version-update.dto';
+import { APP_TYPES } from './constants';
import { Component } from 'src/entities/component.entity';
import { Layout } from 'src/entities/layout.entity';
import { WorkspaceAppsResponseDto } from '@modules/external-apis/dto';
-import { RequestContext } from '@modules/request-context/service';
-import { RenameAppOrVersionDto } from '@modules/app-git/dto';
-import got from 'got';
import { DataQuery } from '@entities/data_query.entity';
@Injectable()
@@ -58,7 +56,7 @@ export class AppsUtilService implements IAppsUtilService {
protected readonly dataSourceRepository: DataSourcesRepository,
protected readonly dataSourceUtilService: DataSourcesUtilService
) { }
- async create(name: string, user: User, type: string, manager: EntityManager): Promise {
+ async create(name: string, user: User, type: APP_TYPES, manager: EntityManager): Promise {
return await dbTransactionWrap(async (manager: EntityManager) => {
const app = await catchDbException(() => {
return manager.save(
@@ -69,8 +67,8 @@ export class AppsUtilService implements IAppsUtilService {
updatedAt: new Date(),
organizationId: user.organizationId,
userId: user.id,
- isMaintenanceOn: type === 'workflow' ? true : false,
- ...(type === 'workflow' && { workflowApiToken: uuidv4() }),
+ isMaintenanceOn: type === APP_TYPES.WORKFLOW ? true : false,
+ ...(type === APP_TYPES.WORKFLOW && { workflowApiToken: uuidv4() }),
})
);
}, [{ dbConstraint: DataBaseConstraints.APP_NAME_UNIQUE, message: 'This app name is already taken.' }]);
@@ -410,14 +408,26 @@ export class AppsUtilService implements IAppsUtilService {
async all(user: User, page: number, searchKey: string, type: string): Promise {
//Migrate it to app utility files
+ let resourceType: MODULES;
+
+ switch (type) {
+ case APP_TYPES.WORKFLOW:
+ resourceType = MODULES.WORKFLOWS;
+ break;
+ case APP_TYPES.FRONT_END:
+ resourceType = MODULES.APP;
+ break;
+ default:
+ resourceType = MODULES.APP;
+ }
const userPermission = await this.abilityService.resourceActionsPermission(user, {
- resources: [{ resource: MODULES.APP }],
+ resources: [{ resource: resourceType }],
organizationId: user.organizationId,
});
return await dbTransactionWrap(async (manager: EntityManager) => {
const viewableAppsQb = this.viewableAppsQueryUsingPermissions(
user,
- userPermission[MODULES.APP],
+ userPermission[resourceType],
manager,
searchKey,
undefined,
@@ -436,79 +446,119 @@ export class AppsUtilService implements IAppsUtilService {
protected viewableAppsQueryUsingPermissions(
user: User,
- userAppPermissions: UserAppsPermissions,
+ userAppPermissions: UserAppsPermissions | UserWorkflowPermissions,
manager: EntityManager,
searchKey?: string,
select?: Array,
type?: string
): SelectQueryBuilder {
- const viewableApps = userAppPermissions.hideAll
- ? [null, ...userAppPermissions.editableAppsId]
- : [
- null,
- ...Array.from(
- new Set([
- ...userAppPermissions.editableAppsId,
- ...userAppPermissions.viewableAppsId.filter((id) => !userAppPermissions.hiddenAppsId.includes(id)),
- ])
- ),
- ];
const viewableAppsQb = manager
- .createQueryBuilder(AppBase, 'viewable_apps')
- .innerJoin('viewable_apps.user', 'user')
+ .createQueryBuilder(AppBase, 'apps')
+ .innerJoin('apps.user', 'user')
.addSelect(['user.firstName', 'user.lastName'])
- .where('viewable_apps.organizationId = :organizationId', { organizationId: user.organizationId });
+ .where('apps.organizationId = :organizationId', { organizationId: user.organizationId });
- if (type === 'module') {
+ if (type === APP_TYPES.MODULE) {
viewableAppsQb.leftJoinAndSelect('viewable_apps.appVersions', 'versions');
}
- if (type) viewableAppsQb.andWhere('viewable_apps.type = :type', { type: type });
+ if (type) {
+ viewableAppsQb.andWhere('apps.type = :type', { type });
+ }
if (searchKey) {
- viewableAppsQb.andWhere('LOWER(viewable_apps.name) like :searchKey', {
+ viewableAppsQb.andWhere('LOWER(apps.name) like :searchKey', {
searchKey: `%${searchKey && searchKey.toLowerCase()}%`,
});
}
if (select) {
- viewableAppsQb.select(select.map((col) => `viewable_apps.${col}`));
+ viewableAppsQb.select(select.map((col) => `apps.${col}`));
}
- viewableAppsQb.orderBy('viewable_apps.createdAt', 'DESC');
+
+ viewableAppsQb.orderBy('apps.createdAt', 'DESC');
+
if (this.isSuperAdmin(user)) {
return viewableAppsQb;
}
+ const viewableApps = this.calculateViewableFrontEndApps(userAppPermissions as unknown as UserAppsPermissions);
+
+ switch (type) {
+ case APP_TYPES.FRONT_END:
+ default:
+ return this.addViewableFrontEndAppsFilter(
+ viewableAppsQb,
+ userAppPermissions as unknown as UserAppsPermissions,
+ viewableApps
+ );
+ }
+ }
+
+ private calculateViewableFrontEndApps(userAppPermissions: UserAppsPermissions): string[] {
+ return userAppPermissions.hideAll
+ ? [null, ...userAppPermissions.editableAppsId]
+ : [
+ null,
+ ...Array.from(
+ new Set([
+ ...userAppPermissions.editableAppsId,
+ ...userAppPermissions.viewableAppsId.filter((id) => !userAppPermissions.hiddenAppsId.includes(id)),
+ ])
+ ),
+ ];
+ }
+
+ private addViewableFrontEndAppsFilter(
+ query: SelectQueryBuilder,
+ userAppPermissions: UserAppsPermissions,
+ viewableApps: string[]
+ ): SelectQueryBuilder {
const { isAllEditable, isAllViewable, hideAll } = userAppPermissions;
- if (isAllEditable) return viewableAppsQb;
+ if (isAllEditable) return query;
+
if ((isAllViewable && hideAll) || (!isAllViewable && !hideAll) || (!isAllViewable && hideAll)) {
- viewableAppsQb.andWhere('viewable_apps.id IN (:...viewableApps)', {
+ query.andWhere('apps.id IN (:...viewableApps)', {
viewableApps,
});
- return viewableAppsQb;
+ return query;
}
+
const hiddenApps = userAppPermissions.hiddenAppsId.filter((id) => !userAppPermissions.editableAppsId.includes(id));
if (!userAppPermissions.hideAll && isAllViewable && hiddenApps.length > 0) {
- viewableAppsQb.andWhere('viewable_apps.id NOT IN (:...hiddenApps)', {
+ query.andWhere('apps.id NOT IN (:...hiddenApps)', {
hiddenApps,
});
}
- return viewableAppsQb;
+
+ return query;
}
protected isSuperAdmin(user: User) {
return !!(user?.userType === USER_TYPE.INSTANCE);
}
- async count(user: User, searchKey, type: string): Promise {
+ async count(user: User, searchKey, type: APP_TYPES): Promise {
+ let resourceType: MODULES;
+
+ switch (type) {
+ case APP_TYPES.WORKFLOW:
+ resourceType = MODULES.WORKFLOWS;
+ break;
+ case APP_TYPES.FRONT_END:
+ resourceType = MODULES.APP;
+ break;
+ default:
+ resourceType = MODULES.APP;
+ }
const userPermission = await this.abilityService.resourceActionsPermission(user, {
- resources: [{ resource: MODULES.APP }],
+ resources: [{ resource: resourceType }],
organizationId: user.organizationId,
});
return await dbTransactionWrap(async (manager: EntityManager) => {
const apps = await this.viewableAppsQueryUsingPermissions(
user,
- userPermission[MODULES.APP],
+ userPermission[resourceType],
manager,
searchKey,
undefined,
diff --git a/server/src/modules/data-queries/ability/app/data-query-app.ability.ts b/server/src/modules/data-queries/ability/app/data-query-app.ability.ts
new file mode 100644
index 0000000000..0d9a77201a
--- /dev/null
+++ b/server/src/modules/data-queries/ability/app/data-query-app.ability.ts
@@ -0,0 +1,84 @@
+import { AbilityBuilder } from '@casl/ability';
+import { UserAllPermissions } from '@modules/app/types';
+import { FEATURE_KEY } from '../../constants';
+import { App } from '@entities/app.entity';
+import { MODULES } from '@modules/app/constants/modules';
+import { FeatureAbility } from './index';
+
+export function defineDataQueryAppAbility(
+ can: AbilityBuilder['can'],
+ UserAllPermissions: UserAllPermissions,
+ appId?: string
+): void {
+ const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
+ const resourcePermissions = userPermission?.[MODULES.APP];
+ const isAllEditable = !!resourcePermissions?.isAllEditable;
+ const isCanCreate = userPermission.appCreate;
+ const isCanDelete = userPermission.appDelete;
+ const isAllViewable = !!resourcePermissions?.isAllViewable;
+
+ // Always grant RUN_EDITOR and RUN_VIEWER permissions
+ can([FEATURE_KEY.RUN_EDITOR, FEATURE_KEY.RUN_VIEWER], App);
+
+ if (isAdmin || superAdmin) {
+ can(
+ [
+ FEATURE_KEY.CREATE,
+ FEATURE_KEY.GET,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.UPDATE_DATA_SOURCE,
+ FEATURE_KEY.UPDATE_ONE,
+ FEATURE_KEY.RUN_EDITOR,
+ FEATURE_KEY.RUN_VIEWER,
+ FEATURE_KEY.PREVIEW,
+ ],
+ App
+ );
+ return;
+ }
+
+ if (isAllEditable || isCanCreate || isCanDelete) {
+ can(
+ [
+ FEATURE_KEY.GET,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.UPDATE_ONE,
+ FEATURE_KEY.RUN_EDITOR,
+ FEATURE_KEY.RUN_VIEWER,
+ FEATURE_KEY.PREVIEW,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.CREATE,
+ ],
+ App
+ );
+ return;
+ }
+
+ if (resourcePermissions?.editableAppsId?.length && appId && resourcePermissions?.editableAppsId?.includes(appId)) {
+ can(
+ [
+ FEATURE_KEY.GET,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.UPDATE_ONE,
+ FEATURE_KEY.RUN_EDITOR,
+ FEATURE_KEY.RUN_VIEWER,
+ FEATURE_KEY.PREVIEW,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.CREATE,
+ ],
+ App
+ );
+ return;
+ }
+
+ if (isAllViewable) {
+ can([FEATURE_KEY.GET, FEATURE_KEY.PREVIEW, FEATURE_KEY.RUN_VIEWER, FEATURE_KEY.RUN_EDITOR], App);
+ return;
+ }
+
+ if (resourcePermissions?.viewableAppsId?.length && appId && resourcePermissions?.viewableAppsId?.includes(appId)) {
+ can([FEATURE_KEY.GET, FEATURE_KEY.PREVIEW, FEATURE_KEY.RUN_VIEWER, FEATURE_KEY.RUN_EDITOR], App);
+ return;
+ }
+}
diff --git a/server/src/modules/data-queries/ability/app/data-query-workflow.ability.ts b/server/src/modules/data-queries/ability/app/data-query-workflow.ability.ts
new file mode 100644
index 0000000000..fc852d4a89
--- /dev/null
+++ b/server/src/modules/data-queries/ability/app/data-query-workflow.ability.ts
@@ -0,0 +1,92 @@
+import { AbilityBuilder } from '@casl/ability';
+import { UserAllPermissions } from '@modules/app/types';
+import { FEATURE_KEY } from '../../constants';
+import { App } from '@entities/app.entity';
+import { MODULES } from '@modules/app/constants/modules';
+import { FeatureAbility } from './index';
+
+export function defineDataQueryWorkflowAbility(
+ can: AbilityBuilder['can'],
+ UserAllPermissions: UserAllPermissions,
+ workflowId?: string
+): void {
+ const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
+ const resourcePermissions = userPermission?.[MODULES.WORKFLOWS];
+ const isAllEditable = !!resourcePermissions?.isAllEditable;
+ const isCanCreate = userPermission.workflowCreate;
+ const isCanDelete = userPermission.workflowDelete;
+ const isAllExecutable = !!resourcePermissions?.isAllExecutable;
+
+ // Always grant RUN_EDITOR and RUN_VIEWER permissions
+ can([FEATURE_KEY.RUN_EDITOR, FEATURE_KEY.RUN_VIEWER], App);
+
+ if (isAdmin || superAdmin) {
+ can(
+ [
+ FEATURE_KEY.CREATE,
+ FEATURE_KEY.GET,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.UPDATE_DATA_SOURCE,
+ FEATURE_KEY.UPDATE_ONE,
+ FEATURE_KEY.RUN_EDITOR,
+ FEATURE_KEY.RUN_VIEWER,
+ FEATURE_KEY.PREVIEW,
+ ],
+ App
+ );
+ return;
+ }
+
+ if (isAllEditable || isCanCreate || isCanDelete) {
+ can(
+ [
+ FEATURE_KEY.GET,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.UPDATE_ONE,
+ FEATURE_KEY.RUN_EDITOR,
+ FEATURE_KEY.RUN_VIEWER,
+ FEATURE_KEY.PREVIEW,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.CREATE,
+ ],
+ App
+ );
+ return;
+ }
+
+ if (
+ resourcePermissions?.editableWorkflowsId?.length &&
+ workflowId &&
+ resourcePermissions?.editableWorkflowsId?.includes(workflowId)
+ ) {
+ can(
+ [
+ FEATURE_KEY.GET,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.UPDATE_ONE,
+ FEATURE_KEY.RUN_EDITOR,
+ FEATURE_KEY.RUN_VIEWER,
+ FEATURE_KEY.PREVIEW,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.CREATE,
+ ],
+ App
+ );
+ return;
+ }
+
+ if (isAllExecutable) {
+ can([FEATURE_KEY.GET, FEATURE_KEY.PREVIEW, FEATURE_KEY.RUN_VIEWER, FEATURE_KEY.RUN_EDITOR], App);
+ return;
+ }
+
+ if (
+ resourcePermissions?.executableWorkflowsId?.length &&
+ workflowId &&
+ resourcePermissions?.executableWorkflowsId?.includes(workflowId)
+ ) {
+ can([FEATURE_KEY.GET, FEATURE_KEY.PREVIEW, FEATURE_KEY.RUN_VIEWER, FEATURE_KEY.RUN_EDITOR], App);
+ return;
+ }
+}
diff --git a/server/src/modules/data-queries/ability/app/guard.ts b/server/src/modules/data-queries/ability/app/guard.ts
index 87d8c2d92b..250682f32b 100644
--- a/server/src/modules/data-queries/ability/app/guard.ts
+++ b/server/src/modules/data-queries/ability/app/guard.ts
@@ -4,6 +4,7 @@ import { AbilityGuard } from '@modules/app/guards/ability.guard';
import { MODULES } from '@modules/app/constants/modules';
import { ResourceDetails } from '@modules/app/types';
import { App } from '@entities/app.entity';
+import { APP_TYPES } from '@modules/apps/constants';
@Injectable()
export class FeatureAbilityGuard extends AbilityGuard {
@@ -14,10 +15,23 @@ export class FeatureAbilityGuard extends AbilityGuard {
protected getSubjectType() {
return App;
}
+
+ private getAppResourceType(): MODULES {
+ const appResource: App = this.getResourceObject();
+ switch (appResource.type) {
+ case APP_TYPES.FRONT_END:
+ return MODULES.APP;
+ case APP_TYPES.WORKFLOW:
+ return MODULES.WORKFLOWS;
+ default:
+ return MODULES.APP;
+ }
+ }
protected getResource(): ResourceDetails | ResourceDetails[] {
+ const appResource: MODULES = this.getAppResourceType();
return [
{
- resourceType: MODULES.APP,
+ resourceType: appResource,
},
{
resourceType: MODULES.GLOBAL_DATA_SOURCE,
diff --git a/server/src/modules/data-queries/ability/app/index.ts b/server/src/modules/data-queries/ability/app/index.ts
index a6bd868805..fa614e5c51 100644
--- a/server/src/modules/data-queries/ability/app/index.ts
+++ b/server/src/modules/data-queries/ability/app/index.ts
@@ -3,8 +3,10 @@ import { Ability, AbilityBuilder, InferSubjects } from '@casl/ability';
import { AbilityFactory } from '@modules/app/ability-factory';
import { UserAllPermissions } from '@modules/app/types';
import { FEATURE_KEY } from '../../constants';
-import { MODULES } from '@modules/app/constants/modules';
import { App } from '@entities/app.entity';
+import { MODULES } from '@modules/app/constants/modules';
+import { defineDataQueryAppAbility } from './data-query-app.ability';
+import { defineDataQueryWorkflowAbility } from './data-query-workflow.ability';
type Subjects = InferSubjects | 'all';
export type FeatureAbility = Ability<[FEATURE_KEY, Subjects]>;
@@ -21,80 +23,18 @@ export class FeatureAbilityFactory extends AbilityFactory
extractedMetadata: { moduleName: string; features: string[] },
request?: any
): void {
- const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
+ const resourceId = request?.tj_resource_id;
+ const resourceType = UserAllPermissions.resource[0].resourceType;
- const resourcePermissions = userPermission?.[MODULES.APP];
- const isAllEditable = !!resourcePermissions?.isAllEditable;
- const isCanCreate = userPermission.appCreate;
- const isCanDelete = userPermission.appDelete;
- const isAllViewable = !!resourcePermissions?.isAllViewable;
-
- const appId = request?.tj_resource_id;
-
- // Always grant RUN_EDITOR and RUN_VIEWER permissions
- can([FEATURE_KEY.RUN_EDITOR, FEATURE_KEY.RUN_VIEWER], App);
-
- // Admin or super admin and do all operations
- if (isAdmin || superAdmin) {
- can(
- [
- FEATURE_KEY.CREATE,
- FEATURE_KEY.GET,
- FEATURE_KEY.UPDATE,
- FEATURE_KEY.DELETE,
- FEATURE_KEY.UPDATE_DATA_SOURCE,
- FEATURE_KEY.UPDATE_ONE,
- FEATURE_KEY.RUN_EDITOR,
- FEATURE_KEY.RUN_VIEWER,
- FEATURE_KEY.PREVIEW,
- ],
- App
- );
- return;
- }
-
- if (isAllEditable || isCanCreate || isCanDelete) {
- // Can create and can delete has master permissions
- can(
- [
- FEATURE_KEY.GET,
- FEATURE_KEY.UPDATE,
- FEATURE_KEY.UPDATE_ONE,
- FEATURE_KEY.RUN_EDITOR,
- FEATURE_KEY.RUN_VIEWER,
- FEATURE_KEY.PREVIEW,
- FEATURE_KEY.DELETE,
- FEATURE_KEY.CREATE,
- ],
- App
- );
- return;
- }
-
- if (resourcePermissions?.editableAppsId?.length && appId && resourcePermissions?.editableAppsId?.includes(appId)) {
- can(
- [
- FEATURE_KEY.GET,
- FEATURE_KEY.UPDATE,
- FEATURE_KEY.UPDATE_ONE,
- FEATURE_KEY.RUN_EDITOR,
- FEATURE_KEY.RUN_VIEWER,
- FEATURE_KEY.PREVIEW,
- FEATURE_KEY.DELETE,
- FEATURE_KEY.CREATE,
- ],
- App
- );
- return;
- }
-
- if (isAllViewable) {
- can([FEATURE_KEY.GET, FEATURE_KEY.PREVIEW, FEATURE_KEY.RUN_VIEWER, FEATURE_KEY.RUN_EDITOR], App);
- return;
- }
- if (resourcePermissions?.viewableAppsId?.length && appId && resourcePermissions?.viewableAppsId?.includes(appId)) {
- can([FEATURE_KEY.GET, FEATURE_KEY.PREVIEW, FEATURE_KEY.RUN_VIEWER, FEATURE_KEY.RUN_EDITOR], App);
- return;
+ switch (resourceType) {
+ case MODULES.APP:
+ defineDataQueryAppAbility(can, UserAllPermissions, resourceId);
+ break;
+ case MODULES.WORKFLOWS:
+ defineDataQueryWorkflowAbility(can, UserAllPermissions, resourceId);
+ break;
+ default:
+ throw new Error(`Unsupported resource type: ${resourceType}`);
}
}
}
diff --git a/server/src/modules/folder-apps/interfaces/IUtilService.ts b/server/src/modules/folder-apps/interfaces/IUtilService.ts
index 15f6e753b8..e073a62420 100644
--- a/server/src/modules/folder-apps/interfaces/IUtilService.ts
+++ b/server/src/modules/folder-apps/interfaces/IUtilService.ts
@@ -2,12 +2,13 @@ import { Folder } from '@entities/folder.entity';
import { User } from '@entities/user.entity';
import { EntityManager } from 'typeorm';
import { AppBase } from '@entities/app_base.entity';
-import { UserAppsPermissions } from '@modules/ability/types';
+import { UserAppsPermissions, UserWorkflowPermissions } from '@modules/ability/types';
+import { APP_TYPES } from '@modules/apps/constants';
export interface IFolderAppsUtilService {
allFoldersWithAppCount(
user: User,
- userAppPermissions: UserAppsPermissions,
+ userAppPermissions: UserAppsPermissions | UserWorkflowPermissions,
manager: EntityManager,
type?: string,
searchKey?: string
@@ -16,6 +17,7 @@ export interface IFolderAppsUtilService {
user: User,
folder: Folder,
page: number,
- searchKey: string
+ searchKey: string,
+ type?: APP_TYPES
): Promise<{ viewableApps: AppBase[]; totalCount: number }>;
}
diff --git a/server/src/modules/folder-apps/service.ts b/server/src/modules/folder-apps/service.ts
index 1299af06dd..7ed80df160 100644
--- a/server/src/modules/folder-apps/service.ts
+++ b/server/src/modules/folder-apps/service.ts
@@ -10,6 +10,7 @@ import { MODULES } from '@modules/app/constants/modules';
import { AbilityService } from '@modules/ability/interfaces/IService';
import { User } from '@entities/user.entity';
import { USER_ROLE } from '@modules/group-permissions/constants';
+import { APP_TYPES } from '@modules/apps/constants';
@Injectable()
export class FolderAppsService implements IFolderAppsService {
constructor(
@@ -49,16 +50,30 @@ export class FolderAppsService implements IFolderAppsService {
return await manager.delete(FolderApp, { folderId, appId });
});
}
+
+ private getResourceTypefromAppType(type: APP_TYPES) {
+ switch (type) {
+ case APP_TYPES.FRONT_END:
+ return MODULES.APP;
+ case APP_TYPES.WORKFLOW:
+ return MODULES.WORKFLOWS;
+ default:
+ throw new BadRequestException('Invalid resource type');
+ }
+ }
+
async getFolders(user: User, query) {
return dbTransactionWrap(async (manager: EntityManager) => {
const type = query.type;
const searchKey = query.searchKey;
+ const resourceType = this.getResourceTypefromAppType(type as APP_TYPES);
const userAppPermissions = (
await this.abilityService.resourceActionsPermission(user, {
- resources: [{ resource: MODULES.APP }],
+ resources: [{ resource: resourceType }],
organizationId: user.organizationId,
})
- )?.[MODULES.APP];
+ )?.[resourceType];
+
const allFolderList = await this.foldersUtilService.allFolders(user, manager, type);
if (allFolderList.length === 0) {
return { folders: [] };
diff --git a/server/src/modules/folder-apps/util.service.ts b/server/src/modules/folder-apps/util.service.ts
index 5613cd2be2..f7a50fb18d 100644
--- a/server/src/modules/folder-apps/util.service.ts
+++ b/server/src/modules/folder-apps/util.service.ts
@@ -7,26 +7,59 @@ import { AppBase } from '@entities/app_base.entity';
import { dbTransactionWrap } from '@helpers/database.helper';
import { FolderApp } from '@entities/folder_app.entity';
import { MODULES } from '@modules/app/constants/modules';
-import { UserAppsPermissions } from '@modules/ability/types';
+import { UserAppsPermissions, UserWorkflowPermissions } from '@modules/ability/types';
import { AbilityService } from '@modules/ability/interfaces/IService';
+import { APP_TYPES } from '@modules/apps/constants';
+
@Injectable()
export class FolderAppsUtilService implements IFolderAppsUtilService {
constructor(protected readonly abilityService: AbilityService) {}
+
async allFoldersWithAppCount(
user: User,
- userAppPermissions: UserAppsPermissions,
+ userAppPermissions: UserAppsPermissions | UserWorkflowPermissions,
manager: EntityManager,
- type = 'front-end',
+ type = APP_TYPES.FRONT_END,
searchKey?: string
): Promise {
- return this.getFolderQuery(user.organizationId, manager, userAppPermissions, type, searchKey).distinct().getMany();
+ return this.getFolderQuery(user.organizationId, manager, userAppPermissions as UserAppsPermissions, type, searchKey)
+ .distinct()
+ .getMany();
}
- private getFolderQuery(
+ protected getBaseFolderQuery(
+ organizationId: string,
+ manager: EntityManager,
+ type: APP_TYPES,
+ searchKey?: string
+ ): SelectQueryBuilder {
+ const query = manager.createQueryBuilder(Folder, 'folders');
+ query.leftJoinAndSelect('folders.folderApps', 'folder_apps');
+ query.leftJoin('folder_apps.app', 'app');
+
+ if (searchKey) {
+ query.andWhere('LOWER(app.name) like :searchKey', {
+ searchKey: `%${searchKey && searchKey.toLowerCase()}%`,
+ });
+ }
+
+ query
+ .andWhere('folders.organization_id = :organizationId', {
+ organizationId,
+ })
+ .andWhere('folders.type = :type', {
+ type,
+ })
+ .orderBy('folders.name', 'ASC');
+
+ return query;
+ }
+
+ protected getFolderQuery(
organizationId: string,
manager: EntityManager,
userAppPermissions: UserAppsPermissions,
- type = 'front-end',
+ type = APP_TYPES.FRONT_END,
searchKey?: string
): SelectQueryBuilder {
const { isAllEditable, isAllViewable, hideAll } = userAppPermissions;
@@ -44,9 +77,8 @@ export class FolderAppsUtilService implements IFolderAppsUtilService {
const hiddenApps = [
...userAppPermissions.hiddenAppsId.filter((id) => !userAppPermissions.editableAppsId.includes(id)),
];
- const query = manager.createQueryBuilder(Folder, 'folders');
- query.leftJoinAndSelect('folders.folderApps', 'folder_apps');
- query.leftJoin('folder_apps.app', 'app');
+
+ const query = this.getBaseFolderQuery(organizationId, manager, type, searchKey);
if (!isAllEditable) {
// Not all apps are editable - filter with view privilege
@@ -66,27 +98,34 @@ export class FolderAppsUtilService implements IFolderAppsUtilService {
}
}
+ return query;
+ }
+
+ protected getBaseAppsQuery(
+ manager: EntityManager,
+ folderAppIds: string[],
+ searchKey?: string
+ ): SelectQueryBuilder {
+ const query = manager
+ .createQueryBuilder(AppBase, 'apps')
+ .innerJoin('apps.user', 'user')
+ .addSelect(['user.firstName', 'user.lastName']);
+
if (searchKey) {
- query.andWhere('LOWER(app.name) like :searchKey', {
- searchKey: `%${searchKey && searchKey.toLowerCase()}%`,
+ query.andWhere('LOWER(apps.name) LIKE :searchKey', {
+ searchKey: `%${searchKey.toLowerCase()}%`,
});
}
- query
- .andWhere('folders.organization_id = :organizationId', {
- organizationId,
- })
- .andWhere('folders.type = :type', {
- type,
- })
- .orderBy('folders.name', 'ASC');
return query;
}
+
async getAppsFor(
user: User,
folder: Folder,
page: number,
- searchKey: string
+ searchKey: string,
+ type: APP_TYPES
): Promise<{
viewableApps: AppBase[];
totalCount: number;
@@ -113,33 +152,9 @@ export class FolderAppsUtilService implements IFolderAppsUtilService {
totalCount: 0,
};
}
- const { isAllEditable, isAllViewable, hideAll } = userAppPermissions;
- const viewableAppsTotal = isAllEditable
- ? [null, ...folderAppIds]
- : hideAll
- ? [null, ...userAppPermissions.editableAppsId]
- : isAllViewable
- ? [null, ...folderAppIds].filter((id) => !userAppPermissions.hiddenAppsId.includes(id))
- : [
- null,
- ...Array.from(
- new Set([
- ...userAppPermissions.editableAppsId,
- ...userAppPermissions.viewableAppsId.filter((id) => !userAppPermissions.hiddenAppsId.includes(id)),
- ])
- ),
- ];
- const viewableAppIds = [null, ...viewableAppsTotal.filter((id) => folderAppIds.includes(id))];
-
- const viewableAppsInFolder = manager
- .createQueryBuilder(AppBase, 'apps')
- .innerJoin('apps.user', 'user')
- .addSelect(['user.firstName', 'user.lastName']);
-
- viewableAppsInFolder.where('apps.id IN (:...viewableAppIds)', {
- viewableAppIds: viewableAppIds,
- });
+ const viewableAppsInFolder = this.getBaseAppsQuery(manager, folderAppIds, searchKey);
+ this.addViewableFrontendFilter(viewableAppsInFolder, folderAppIds, userAppPermissions);
const [viewableApps, totalCount] = await Promise.all([
viewableAppsInFolder
@@ -156,4 +171,36 @@ export class FolderAppsUtilService implements IFolderAppsUtilService {
};
});
}
+
+ protected addViewableFrontendFilter(
+ query: SelectQueryBuilder,
+ folderAppIds: string[],
+ userAppPermissions: UserAppsPermissions
+ ): SelectQueryBuilder {
+ const { isAllEditable, isAllViewable, hideAll } = userAppPermissions;
+
+ const viewableAppsTotal = isAllEditable
+ ? [null, ...folderAppIds]
+ : hideAll
+ ? [null, ...userAppPermissions.editableAppsId]
+ : isAllViewable
+ ? [null, ...folderAppIds].filter((id) => !userAppPermissions.hiddenAppsId.includes(id))
+ : [
+ null,
+ ...Array.from(
+ new Set([
+ ...userAppPermissions.editableAppsId,
+ ...userAppPermissions.viewableAppsId.filter((id) => !userAppPermissions.hiddenAppsId.includes(id)),
+ ])
+ ),
+ ];
+
+ const viewableAppIds = [null, ...viewableAppsTotal.filter((id) => folderAppIds.includes(id))];
+
+ query.where('apps.id IN (:...viewableAppIds)', {
+ viewableAppIds,
+ });
+
+ return query;
+ }
}
diff --git a/server/src/modules/group-permissions/constants/granular_permissions.ts b/server/src/modules/group-permissions/constants/granular_permissions.ts
index 726f07bbd8..c45915c0e7 100644
--- a/server/src/modules/group-permissions/constants/granular_permissions.ts
+++ b/server/src/modules/group-permissions/constants/granular_permissions.ts
@@ -3,4 +3,5 @@ import { ResourceType } from './index';
export const DEFAULT_GRANULAR_PERMISSIONS_NAME = {
[ResourceType.APP]: 'Apps',
[ResourceType.DATA_SOURCE]: 'Data sources',
+ [ResourceType.WORKFLOWS]: 'Workflows',
};
diff --git a/server/src/modules/group-permissions/constants/index.ts b/server/src/modules/group-permissions/constants/index.ts
index d63934d84f..7610c9e881 100644
--- a/server/src/modules/group-permissions/constants/index.ts
+++ b/server/src/modules/group-permissions/constants/index.ts
@@ -17,6 +17,7 @@ export const HUMANIZED_USER_LIST = ['End-user', 'Builder', 'Admin'];
export enum ResourceType {
APP = 'app',
DATA_SOURCE = 'data_source',
+ WORKFLOWS = 'workflow',
}
export const DEFAULT_GROUP_PERMISSIONS = {
@@ -26,6 +27,8 @@ export const DEFAULT_GROUP_PERMISSIONS = {
appCreate: true,
appDelete: true,
folderCRUD: true,
+ workflowCreate: true,
+ workflowDelete: true,
orgConstantCRUD: true,
dataSourceCreate: true,
dataSourceDelete: true,
@@ -37,6 +40,8 @@ export const DEFAULT_GROUP_PERMISSIONS = {
appCreate: true,
appDelete: true,
folderCRUD: true,
+ workflowCreate: true,
+ workflowDelete: true,
orgConstantCRUD: true,
dataSourceCreate: true,
dataSourceDelete: true,
@@ -47,6 +52,8 @@ export const DEFAULT_GROUP_PERMISSIONS = {
type: GROUP_PERMISSIONS_TYPE.DEFAULT,
appCreate: false,
appDelete: false,
+ workflowCreate: false,
+ workflowDelete: false,
folderCRUD: false,
orgConstantCRUD: false,
dataSourceCreate: false,
@@ -68,6 +75,10 @@ export const DEFAULT_RESOURCE_PERMISSIONS = {
canUse: false,
},
},
+ [ResourceType.WORKFLOWS]: {
+ canEdit: true,
+ canView: false,
+ },
},
[USER_ROLE.END_USER]: {
[ResourceType.APP]: {
@@ -75,6 +86,10 @@ export const DEFAULT_RESOURCE_PERMISSIONS = {
canView: true,
hideFromDashboard: false,
},
+ [ResourceType.WORKFLOWS]: {
+ canEdit: false,
+ canView: true,
+ },
},
[USER_ROLE.BUILDER]: {
[ResourceType.APP]: {
@@ -88,6 +103,10 @@ export const DEFAULT_RESOURCE_PERMISSIONS = {
canUse: false,
},
},
+ [ResourceType.WORKFLOWS]: {
+ canEdit: true,
+ canView: false,
+ },
},
} as Record>>;
diff --git a/server/src/modules/group-permissions/dto/index.ts b/server/src/modules/group-permissions/dto/index.ts
index 703fac73f5..651659df43 100644
--- a/server/src/modules/group-permissions/dto/index.ts
+++ b/server/src/modules/group-permissions/dto/index.ts
@@ -31,6 +31,14 @@ export class UpdateGroupPermissionDto {
@IsOptional()
orgConstantCRUD: boolean;
+ @IsBoolean()
+ @IsOptional()
+ workflowCreate: boolean;
+
+ @IsBoolean()
+ @IsOptional()
+ workflowDelete: boolean;
+
@IsBoolean()
@IsOptional()
dataSourceCreate: boolean;
diff --git a/server/src/modules/group-permissions/services/granular-permissions.service.ts b/server/src/modules/group-permissions/services/granular-permissions.service.ts
index 7f3d03ed28..a5f543b6b7 100644
--- a/server/src/modules/group-permissions/services/granular-permissions.service.ts
+++ b/server/src/modules/group-permissions/services/granular-permissions.service.ts
@@ -45,7 +45,6 @@ export class GranularPermissionsService implements IGranularPermissionsService {
return await dbTransactionWrap(async (manager: EntityManager) => {
const apps = await manager.find(AppBase, {
where: {
- type: 'front-end',
organizationId,
},
});
@@ -53,6 +52,7 @@ export class GranularPermissionsService implements IGranularPermissionsService {
return {
name: app.name,
id: app.id,
+ type: app.type,
};
});
});
diff --git a/server/src/modules/group-permissions/types/granular_permissions.ts b/server/src/modules/group-permissions/types/granular_permissions.ts
index ffaf1ccd35..5ed7d8ebfc 100644
--- a/server/src/modules/group-permissions/types/granular_permissions.ts
+++ b/server/src/modules/group-permissions/types/granular_permissions.ts
@@ -2,21 +2,39 @@ import { GranularPermissions } from '@entities/granular_permissions.entity';
import { ResourceType } from '../constants';
import { CreateGranularPermissionDto, UpdateGranularPermissionDto } from '../dto/granular-permissions';
import { GroupPermissions } from '@entities/group_permissions.entity';
+import { APP_TYPES } from '@modules/apps/constants';
export interface AddableResourceItem {
name: string;
id: string;
}
-export type CreateResourcePermissionObject =
- T extends ResourceType.APP ? CreateAppsPermissionsObject : CreateDataSourcePermissionsObject;
+type CreateResourcePermissionMap = {
+ [ResourceType.APP]: CreateAppsPermissionsObject;
+ [ResourceType.DATA_SOURCE]: CreateDataSourcePermissionsObject;
+ [ResourceType.WORKFLOWS]: CreateWorkflowPermissionsObject;
+};
-export interface CreateAppsPermissionsObject {
+export type CreateResourcePermissionObject = CreateResourcePermissionMap[T];
+
+export interface CreateBaseAppsPermissionsObject {
canEdit?: boolean;
canView?: boolean;
+ appType?: APP_TYPES;
+ resourcesToAdd?: GranularPermissionAddResourceItems;
+}
+export interface CreateAppsPermissionsObject extends CreateBaseAppsPermissionsObject {
hideFromDashboard?: boolean;
+}
+
+export interface CreateWorkflowPermissionsObject extends CreateBaseAppsPermissionsObject {}
+export interface CreateAppsPermissionsObject extends CreateBaseAppsPermissionsObject {
resourcesToAdd?: GranularPermissionAddResourceItems;
}
+export interface CreateWorkflowPermissionsObject extends CreateBaseAppsPermissionsObject {
+ resourcesToAdd?: GranularPermissionAddResourceItems;
+}
+
export interface CreateDataSourcePermissionsObject {
action?: DataSourcesGroupPermissionsActions;
resourcesToAdd?: GranularPermissionAddResourceItems;
@@ -32,10 +50,28 @@ export interface CreateGranularPermissionObject {
organizationId: string;
}
-export type GranularPermissionAddResourceItems =
- T extends ResourceType.APP ? AppsPermissionAddResourceItem[] : DataSourcesPermissionResourceItem[];
+type ResourceToPermissionItemMap = {
+ [ResourceType.APP]: AppsPermissionAddResourceItem[];
+ [ResourceType.DATA_SOURCE]: DataSourcesPermissionResourceItem[];
+ [ResourceType.WORKFLOWS]: WorkflowsPermissionAddResourceItem[];
+};
-export interface AppsPermissionAddResourceItem {
+export type GranularPermissionAddResourceItems = ResourceToPermissionItemMap[T];
+
+interface BaseAppsPermissionAddResourceItem {
+ appId: string;
+}
+
+export interface AppsPermissionAddResourceItem extends BaseAppsPermissionAddResourceItem {}
+
+export interface WorkflowsPermissionAddResourceItem extends BaseAppsPermissionAddResourceItem {}
+
+interface BaseAppsGroupPermissionsActions {
+ canEdit: boolean;
+ canView: boolean;
+}
+
+interface BaseAppsPermissionAddResourceItem {
appId: string;
}
@@ -43,12 +79,12 @@ export interface DataSourcesPermissionResourceItem {
dataSourceId: string;
}
-export interface AppsGroupPermissionsActions {
- canEdit: boolean;
- canView: boolean;
+export interface AppsGroupPermissionsActions extends BaseAppsGroupPermissionsActions {
hideFromDashboard: boolean;
}
+export interface WorkflowsGroupPermissionsActions extends BaseAppsGroupPermissionsActions {}
+
export interface ResourcePermissionMetaData {
granularPermissions: GranularPermissions;
organizationId: string;
@@ -66,7 +102,9 @@ export interface UpdateGranularPermissionObject {
updateGranularPermissionDto: UpdateGranularPermissionDto;
}
-export interface UpdateResourceGroupPermissionsObject {
+export interface UpdateResourceGroupPermissionsObject<
+ T extends ResourceType.APP | ResourceType.DATA_SOURCE | ResourceType.WORKFLOWS
+> {
group: GroupPermissions;
granularPermissions: GranularPermissions;
actions: ResourceGroupActions;
@@ -81,9 +119,13 @@ export interface GranularPermissionDeleteResourceItem {
id: string;
}
-export type ResourceGroupActions = T extends ResourceType.APP
- ? AppsGroupPermissionsActions
- : DataSourcesGroupPermissionsActions;
+type ResourceActionMap = {
+ [ResourceType.APP]: AppsGroupPermissionsActions;
+ [ResourceType.DATA_SOURCE]: DataSourcesGroupPermissionsActions;
+ [ResourceType.WORKFLOWS]: WorkflowsGroupPermissionsActions;
+};
+
+export type ResourceGroupActions = ResourceActionMap[T];
export interface ValidateResourceAction {
isBuilderPermissions: boolean;
diff --git a/server/src/modules/group-permissions/types/index.ts b/server/src/modules/group-permissions/types/index.ts
index ac338fa52e..e241de91fc 100644
--- a/server/src/modules/group-permissions/types/index.ts
+++ b/server/src/modules/group-permissions/types/index.ts
@@ -8,6 +8,8 @@ export interface CreateDefaultGroupObject {
name: string;
appCreate?: boolean;
appDelete?: boolean;
+ workflowCreate?: boolean;
+ workflowDelete?: boolean;
folderCRUD?: boolean;
orgConstantCRUD?: boolean;
dataSourceCreate?: boolean;
diff --git a/server/src/modules/group-permissions/util-services/granular-permissions.util.service.ts b/server/src/modules/group-permissions/util-services/granular-permissions.util.service.ts
index 2344490c6d..e17f6c3688 100644
--- a/server/src/modules/group-permissions/util-services/granular-permissions.util.service.ts
+++ b/server/src/modules/group-permissions/util-services/granular-permissions.util.service.ts
@@ -23,6 +23,7 @@ import * as _ from 'lodash';
import { DEFAULT_GRANULAR_PERMISSIONS_NAME } from '../constants/granular_permissions';
import { RolesRepository } from '@modules/roles/repository';
import { IGranularPermissionsUtilService } from '../interfaces/IUtilService';
+import { APP_TYPES } from '@modules/apps/constants';
@Injectable()
export class GranularPermissionsUtilService implements IGranularPermissionsUtilService {
@@ -52,7 +53,7 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
protected validateAppResourcePermissionUpdateOperation(
group: GroupPermissions,
- actions: ResourceGroupActions
+ actions: ResourceGroupActions
) {
if (group.name === USER_ROLE.END_USER && actions.canEdit) {
throw new BadRequestException(ERROR_HANDLER.EDITOR_LEVEL_PERMISSION_NOT_ALLOWED_END_USER);
@@ -120,7 +121,7 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
protected async createAppGroupPermission(
organizationId: string,
granularPermissions: GranularPermissions,
- createAppPermissionsObj?: CreateResourcePermissionObject,
+ createAppPermissionsObj?: CreateResourcePermissionObject,
manager?: EntityManager
): Promise {
const { resourcesToAdd, canEdit } = createAppPermissionsObj;
@@ -133,8 +134,8 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
},
manager
);
-
- const appGRoupPermissions = await manager.save(
+ createAppPermissionsObj.appType = this.getAppTypeFromResourceType(granularPermissions.type);
+ const appGroupPermissions = await manager.save(
manager.create(AppsGroupPermissions, {
...createAppPermissionsObj,
granularPermissionId: granularPermissions.id,
@@ -143,12 +144,23 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
if (resourcesToAdd?.length) {
await manager.insert(
GroupApps,
- resourcesToAdd.map((app) => ({ appId: app.appId, appsGroupPermissionsId: appGRoupPermissions.id }))
+ resourcesToAdd.map((app) => ({ appId: app.appId, appsGroupPermissionsId: appGroupPermissions.id }))
);
}
}, manager);
}
+ private getAppTypeFromResourceType(type: ResourceType) {
+ switch (type) {
+ case ResourceType.APP:
+ return APP_TYPES.FRONT_END;
+ case ResourceType.WORKFLOWS:
+ return APP_TYPES.WORKFLOW;
+ default:
+ throw new BadRequestException('Invalid resource type');
+ }
+ }
+
async validateResourceCreation(params: ResourceCreateValidation, manager: EntityManager) {
const { groupId, organizationId, isBuilderPermissions } = params;
if (!isBuilderPermissions) {
@@ -188,6 +200,8 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
appGranularPermission.isAll = true;
appGranularPermission.type = ResourceType.APP;
appGroupPermissions.canEdit = true;
+ appGroupPermissions.appType = APP_TYPES.FRONT_END;
+
return [appGranularPermission];
case USER_ROLE.END_USER:
@@ -195,6 +209,7 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
appGranularPermission.isAll = true;
appGranularPermission.type = ResourceType.APP;
appGroupPermissions.canView = true;
+
return [appGranularPermission];
default:
@@ -224,6 +239,7 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
resourcesToAdd,
allowRoleChange,
};
+
await catchDbException(async () => {
if (Object.keys(updateGranularPermission).length > 0)
await manager.update(GranularPermissions, id, updateGranularPermission);
@@ -251,7 +267,9 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
}
protected async updateAppsGroupPermission(
- UpdateResourceGroupPermissionsObject: UpdateResourceGroupPermissionsObject,
+ UpdateResourceGroupPermissionsObject: UpdateResourceGroupPermissionsObject<
+ ResourceType.APP | ResourceType.WORKFLOWS
+ >,
organizationId: string,
manager?: EntityManager
) {
@@ -259,7 +277,10 @@ export class GranularPermissionsUtilService implements IGranularPermissionsUtilS
const { granularPermissions, actions, resourcesToDelete, resourcesToAdd, group, allowRoleChange } =
UpdateResourceGroupPermissionsObject;
- this.validateAppResourcePermissionUpdateOperation(group, actions);
+ this.validateAppResourcePermissionUpdateOperation(
+ group,
+ actions as ResourceGroupActions
+ );
const { canEdit } = actions;
await this.validateResourceAction(
{
diff --git a/server/src/modules/group-permissions/util.service.ts b/server/src/modules/group-permissions/util.service.ts
index d8a27697ad..7af22ba987 100644
--- a/server/src/modules/group-permissions/util.service.ts
+++ b/server/src/modules/group-permissions/util.service.ts
@@ -30,6 +30,9 @@ import { UserRepository } from '@modules/users/repository';
import { USER_STATUS, WORKSPACE_USER_STATUS } from '@modules/users/constants/lifecycle';
import { IGroupPermissionsUtilService } from './interfaces/IUtilService';
import { GroupPermissionLicenseUtilService } from './util-services/license.util.service';
+import { getTooljetEdition } from '@helpers/utils.helper';
+import { TOOLJET_EDITIONS } from '@modules/app/constants';
+
@Injectable()
export class GroupPermissionsUtilService implements IGroupPermissionsUtilService {
constructor(
@@ -159,6 +162,7 @@ export class GroupPermissionsUtilService implements IGroupPermissionsUtilService
CreateResourcePermissionObject
> = DEFAULT_RESOURCE_PERMISSIONS[group.name];
for (const resource of Object.keys(groupGranularPermissions)) {
+ if (getTooljetEdition() === TOOLJET_EDITIONS.CE && resource == ResourceType.WORKFLOWS) continue;
const createResourcePermissionObj: CreateResourcePermissionObject = groupGranularPermissions[resource];
const dtoObject = {
diff --git a/server/src/modules/licensing/guards/app.guard.ts b/server/src/modules/licensing/guards/app.guard.ts
index e7c001b030..0bc1610525 100644
--- a/server/src/modules/licensing/guards/app.guard.ts
+++ b/server/src/modules/licensing/guards/app.guard.ts
@@ -4,6 +4,7 @@ import { LicenseTermsService } from '../interfaces/IService';
import { EntityManager } from 'typeorm';
import { dbTransactionWrap } from '@helpers/database.helper';
import { App } from '@entities/app.entity';
+import { APP_TYPES } from '@modules/apps/constants';
@Injectable()
export class AppCountGuard implements CanActivate {
@@ -13,7 +14,7 @@ export class AppCountGuard implements CanActivate {
async fetchTotalAppCount(manager: EntityManager): Promise {
const apps = await manager.find(App, {
where: {
- type: 'front-end',
+ type: APP_TYPES.FRONT_END,
organization: {
status: 'active',
},
diff --git a/server/src/modules/licensing/guards/webhook.guard.ts b/server/src/modules/licensing/guards/webhook.guard.ts
index f8ca2af0fa..1a5d084643 100644
--- a/server/src/modules/licensing/guards/webhook.guard.ts
+++ b/server/src/modules/licensing/guards/webhook.guard.ts
@@ -4,6 +4,7 @@ import { ConfigService } from '@nestjs/config';
import { LicenseTermsService } from '../interfaces/IService';
import { LICENSE_FIELD, LICENSE_LIMIT } from '../constants';
import { AppsRepository } from '@modules/apps/repository';
+import { APP_TYPES } from '@modules/apps/constants';
@Injectable()
export class WebhookGuard implements CanActivate {
@@ -21,7 +22,7 @@ export class WebhookGuard implements CanActivate {
const workflowApp = await this.appsRepository.findOne({
where: {
id: request?.params?.id,
- type: 'workflow',
+ type: APP_TYPES.WORKFLOW,
},
});
diff --git a/server/src/modules/licensing/guards/workflowcount.guard.ts b/server/src/modules/licensing/guards/workflowcount.guard.ts
index 44edf5965e..5dc723feae 100644
--- a/server/src/modules/licensing/guards/workflowcount.guard.ts
+++ b/server/src/modules/licensing/guards/workflowcount.guard.ts
@@ -2,6 +2,7 @@ import { CanActivate, ExecutionContext, HttpException, Injectable } from '@nestj
import { LicenseTermsService } from '../interfaces/IService';
import { LICENSE_FIELD, LICENSE_LIMIT } from '../constants';
import { AppsRepository } from '@modules/apps/repository';
+import { APP_TYPES } from '@modules/apps/constants';
@Injectable()
export class WorkflowCountGuard implements CanActivate {
@@ -24,7 +25,7 @@ export class WorkflowCountGuard implements CanActivate {
(await this.appsRepository.count({
where: {
organizationId: request?.headers['tj-workspace-id'] ?? '',
- type: 'workflow',
+ type: APP_TYPES.WORKFLOW,
},
})) >= workflowsLimit.workspace.total
) {
@@ -37,7 +38,7 @@ export class WorkflowCountGuard implements CanActivate {
workflowsLimit.instance.total !== LICENSE_LIMIT.UNLIMITED &&
(await this.appsRepository.count({
where: {
- type: 'workflow',
+ type: APP_TYPES.WORKFLOW,
},
})) >= workflowsLimit.instance.total
) {
diff --git a/server/src/modules/licensing/services/count.service.ts b/server/src/modules/licensing/services/count.service.ts
index d41c5f4a2e..14c2421a6e 100644
--- a/server/src/modules/licensing/services/count.service.ts
+++ b/server/src/modules/licensing/services/count.service.ts
@@ -8,6 +8,7 @@ import { Organization } from '@entities/organization.entity';
import { UserRepository } from '@modules/users/repository';
import { USER_ROLE } from '@modules/group-permissions/constants';
import { ILicenseCountsService } from '../interfaces/IService';
+import { APP_TYPES } from '@modules/apps/constants';
@Injectable()
export class LicenseCountsService implements ILicenseCountsService {
@@ -143,7 +144,7 @@ export class LicenseCountsService implements ILicenseCountsService {
fetchTotalWorkflowsCount(workspaceId: string, manager: EntityManager): Promise {
return manager.count(App, {
where: {
- type: 'workflow',
+ type: APP_TYPES.WORKFLOW,
...(workspaceId && { organizationId: workspaceId }),
},
});
@@ -188,7 +189,7 @@ export class LicenseCountsService implements ILicenseCountsService {
async fetchTotalAppCount(manager: EntityManager): Promise {
const apps = await manager.find(App, {
where: {
- type: 'front-end',
+ type: APP_TYPES.FRONT_END,
organization: {
status: 'active',
},
diff --git a/server/src/modules/versions/ability/app-version.ability.ts b/server/src/modules/versions/ability/app-version.ability.ts
new file mode 100644
index 0000000000..16ccb14ccd
--- /dev/null
+++ b/server/src/modules/versions/ability/app-version.ability.ts
@@ -0,0 +1,114 @@
+import { AbilityBuilder } from '@casl/ability';
+import { UserAllPermissions } from '@modules/app/types';
+import { FEATURE_KEY } from '../constants';
+import { App } from '@entities/app.entity';
+import { FeatureAbility } from './index';
+
+export function defineAppVersionAbility(
+ can: AbilityBuilder['can'],
+ UserAllPermissions: UserAllPermissions,
+ resourceId?: string
+): void {
+ const { superAdmin, isAdmin, userPermission, resource } = UserAllPermissions;
+ const userAppPermissions = userPermission?.[resource[0].resourceType];
+
+ if (isAdmin || superAdmin) {
+ can(
+ [
+ FEATURE_KEY.GET,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.CREATE,
+ FEATURE_KEY.GET_ONE,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.UPDATE_SETTINGS,
+ FEATURE_KEY.PROMOTE,
+ FEATURE_KEY.CREATE_COMPONENTS,
+ FEATURE_KEY.UPDATE_COMPONENTS,
+ FEATURE_KEY.UPDATE_COMPONENT_LAYOUT,
+ FEATURE_KEY.DELETE_COMPONENTS,
+ FEATURE_KEY.CREATE_PAGES,
+ FEATURE_KEY.CLONE_PAGES,
+ FEATURE_KEY.UPDATE_PAGES,
+ FEATURE_KEY.DELETE_PAGE,
+ FEATURE_KEY.REORDER_PAGES,
+ FEATURE_KEY.GET_EVENTS,
+ FEATURE_KEY.CREATE_EVENT,
+ FEATURE_KEY.UPDATE_EVENT,
+ FEATURE_KEY.DELETE_EVENT,
+ ],
+ App
+ );
+ return;
+ }
+
+ const isAllEditable = !!userAppPermissions?.isAllEditable;
+ const isAllViewable = !!userAppPermissions?.isAllViewable;
+
+ if (isAllEditable) {
+ can(
+ [
+ FEATURE_KEY.GET,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.CREATE,
+ FEATURE_KEY.GET_ONE,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.UPDATE_SETTINGS,
+ FEATURE_KEY.PROMOTE,
+ FEATURE_KEY.CREATE_COMPONENTS,
+ FEATURE_KEY.UPDATE_COMPONENTS,
+ FEATURE_KEY.UPDATE_COMPONENT_LAYOUT,
+ FEATURE_KEY.DELETE_COMPONENTS,
+ FEATURE_KEY.CREATE_PAGES,
+ FEATURE_KEY.CLONE_PAGES,
+ FEATURE_KEY.UPDATE_PAGES,
+ FEATURE_KEY.DELETE_PAGE,
+ FEATURE_KEY.REORDER_PAGES,
+ FEATURE_KEY.GET_EVENTS,
+ FEATURE_KEY.CREATE_EVENT,
+ FEATURE_KEY.UPDATE_EVENT,
+ FEATURE_KEY.DELETE_EVENT,
+ ],
+ App
+ );
+ } else if (
+ userAppPermissions?.editableAppsId?.length &&
+ resourceId &&
+ userAppPermissions.editableAppsId.includes(resourceId)
+ ) {
+ can(
+ [
+ FEATURE_KEY.GET,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.CREATE,
+ FEATURE_KEY.GET_ONE,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.UPDATE_SETTINGS,
+ FEATURE_KEY.PROMOTE,
+ FEATURE_KEY.CREATE_COMPONENTS,
+ FEATURE_KEY.UPDATE_COMPONENTS,
+ FEATURE_KEY.UPDATE_COMPONENT_LAYOUT,
+ FEATURE_KEY.DELETE_COMPONENTS,
+ FEATURE_KEY.CREATE_PAGES,
+ FEATURE_KEY.CLONE_PAGES,
+ FEATURE_KEY.UPDATE_PAGES,
+ FEATURE_KEY.DELETE_PAGE,
+ FEATURE_KEY.REORDER_PAGES,
+ FEATURE_KEY.GET_EVENTS,
+ FEATURE_KEY.CREATE_EVENT,
+ FEATURE_KEY.UPDATE_EVENT,
+ FEATURE_KEY.DELETE_EVENT,
+ ],
+ App
+ );
+ }
+
+ if (isAllViewable) {
+ can([FEATURE_KEY.GET_EVENTS], App);
+ } else if (
+ userAppPermissions?.viewableAppsId?.length &&
+ resourceId &&
+ userAppPermissions.viewableAppsId.includes(resourceId)
+ ) {
+ can([FEATURE_KEY.GET_EVENTS], App);
+ }
+}
diff --git a/server/src/modules/versions/ability/guard.ts b/server/src/modules/versions/ability/guard.ts
index 64740690c6..5a163c4a40 100644
--- a/server/src/modules/versions/ability/guard.ts
+++ b/server/src/modules/versions/ability/guard.ts
@@ -4,6 +4,7 @@ import { AbilityGuard } from '@modules/app/guards/ability.guard';
import { ResourceDetails } from '@modules/app/types';
import { MODULES } from '@modules/app/constants/modules';
import { App } from '@entities/app.entity';
+import { APP_TYPES } from '@modules/apps/constants';
@Injectable()
export class FeatureAbilityGuard extends AbilityGuard {
@@ -16,8 +17,18 @@ export class FeatureAbilityGuard extends AbilityGuard {
}
protected getResource(): ResourceDetails {
- return {
- resourceType: MODULES.APP,
- };
+ const resource: App = this.getResourceObject();
+ switch (resource?.type) {
+ case APP_TYPES.FRONT_END:
+ return {
+ resourceType: MODULES.APP,
+ };
+ case APP_TYPES.WORKFLOW:
+ return {
+ resourceType: MODULES.WORKFLOWS,
+ };
+ default:
+ return null;
+ }
}
}
diff --git a/server/src/modules/versions/ability/index.ts b/server/src/modules/versions/ability/index.ts
index 5237a97ec9..2026a67347 100644
--- a/server/src/modules/versions/ability/index.ts
+++ b/server/src/modules/versions/ability/index.ts
@@ -3,8 +3,8 @@ import { Ability, AbilityBuilder, InferSubjects } from '@casl/ability';
import { AbilityFactory } from '@modules/app/ability-factory';
import { UserAllPermissions } from '@modules/app/types';
import { FEATURE_KEY } from '../constants';
-import { MODULES } from '@modules/app/constants/modules';
import { App } from '@entities/app.entity';
+import { createVersionAbility } from './utility';
type Subjects = InferSubjects | 'all';
export type FeatureAbility = Ability<[FEATURE_KEY, Subjects]>;
@@ -21,80 +21,7 @@ export class FeatureAbilityFactory extends AbilityFactory
extractedMetadata: { moduleName: string; features: string[] },
request?: any
): void {
- const appId = request?.tj_resource_id;
- const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
-
- const userAppPermissions = userPermission?.[MODULES.APP];
- const isAllAppsEditable = !!userAppPermissions?.isAllEditable;
- const isAllAppsViewable = !!userAppPermissions?.isAllViewable;
-
- if (isAdmin || superAdmin || isAllAppsEditable) {
- // Admin or super admin and do all operations
- can(
- [
- FEATURE_KEY.GET,
- FEATURE_KEY.DELETE,
- FEATURE_KEY.CREATE,
- FEATURE_KEY.GET_ONE,
- FEATURE_KEY.UPDATE,
- FEATURE_KEY.UPDATE_SETTINGS,
- FEATURE_KEY.PROMOTE,
- FEATURE_KEY.CREATE_COMPONENTS,
- FEATURE_KEY.UPDATE_COMPONENTS,
- FEATURE_KEY.UPDATE_COMPONENT_LAYOUT,
- FEATURE_KEY.DELETE_COMPONENTS,
- FEATURE_KEY.CREATE_PAGES,
- FEATURE_KEY.CLONE_PAGES,
- FEATURE_KEY.UPDATE_PAGES,
- FEATURE_KEY.DELETE_PAGE,
- FEATURE_KEY.REORDER_PAGES,
- FEATURE_KEY.GET_EVENTS,
- FEATURE_KEY.CREATE_EVENT,
- FEATURE_KEY.UPDATE_EVENT,
- FEATURE_KEY.DELETE_EVENT,
- ],
- App
- );
- return;
- }
-
- if (userAppPermissions?.editableAppsId?.length && appId && userAppPermissions.editableAppsId.includes(appId)) {
- can(
- [
- FEATURE_KEY.GET,
- FEATURE_KEY.DELETE,
- FEATURE_KEY.CREATE,
- FEATURE_KEY.GET_ONE,
- FEATURE_KEY.UPDATE,
- FEATURE_KEY.UPDATE_SETTINGS,
- FEATURE_KEY.PROMOTE,
- FEATURE_KEY.CREATE_COMPONENTS,
- FEATURE_KEY.UPDATE_COMPONENTS,
- FEATURE_KEY.UPDATE_COMPONENT_LAYOUT,
- FEATURE_KEY.DELETE_COMPONENTS,
- FEATURE_KEY.CREATE_PAGES,
- FEATURE_KEY.CLONE_PAGES,
- FEATURE_KEY.UPDATE_PAGES,
- FEATURE_KEY.DELETE_PAGE,
- FEATURE_KEY.REORDER_PAGES,
- FEATURE_KEY.GET_EVENTS,
- FEATURE_KEY.CREATE_EVENT,
- FEATURE_KEY.UPDATE_EVENT,
- FEATURE_KEY.DELETE_EVENT,
- ],
- App
- );
- }
-
- if (isAllAppsViewable) {
- // add view permissions for all apps
- can([FEATURE_KEY.GET_EVENTS], App);
- } else if (
- userAppPermissions?.viewableAppsId?.length &&
- appId &&
- userAppPermissions.viewableAppsId.includes(appId)
- ) {
- can([FEATURE_KEY.GET_EVENTS], App);
- }
+ const resourceId = request?.tj_resource_id;
+ createVersionAbility(can, UserAllPermissions, resourceId);
}
}
diff --git a/server/src/modules/versions/ability/utility.ts b/server/src/modules/versions/ability/utility.ts
new file mode 100644
index 0000000000..545a3420df
--- /dev/null
+++ b/server/src/modules/versions/ability/utility.ts
@@ -0,0 +1,25 @@
+import { AbilityBuilder } from '@casl/ability';
+import { UserAllPermissions } from '@modules/app/types';
+import { MODULES } from '@modules/app/constants/modules';
+import { FeatureAbility } from './index';
+import { defineAppVersionAbility } from './app-version.ability';
+import { defineWorkflowVersionAbility } from './workflow-version.ability';
+
+export function createVersionAbility(
+ can: AbilityBuilder['can'],
+ UserAllPermissions: UserAllPermissions,
+ resourceId?: string
+): void {
+ const resourceType = UserAllPermissions.resource[0].resourceType ? UserAllPermissions.resource[0].resourceType : null;
+
+ switch (resourceType) {
+ case MODULES.APP:
+ defineAppVersionAbility(can, UserAllPermissions, resourceId);
+ break;
+ case MODULES.WORKFLOWS:
+ defineWorkflowVersionAbility(can, UserAllPermissions, resourceId);
+ break;
+ default:
+ throw new Error(`Unsupported resource type: ${resourceType}`);
+ }
+}
diff --git a/server/src/modules/versions/ability/workflow-version.ability.ts b/server/src/modules/versions/ability/workflow-version.ability.ts
new file mode 100644
index 0000000000..16c54f8e92
--- /dev/null
+++ b/server/src/modules/versions/ability/workflow-version.ability.ts
@@ -0,0 +1,75 @@
+import { AbilityBuilder } from '@casl/ability';
+import { UserAllPermissions } from '@modules/app/types';
+import { FEATURE_KEY } from '../constants';
+import { App } from '@entities/app.entity';
+import { FeatureAbility } from './index';
+
+export function defineWorkflowVersionAbility(
+ can: AbilityBuilder['can'],
+ UserAllPermissions: UserAllPermissions,
+ resourceId?: string
+): void {
+ const { superAdmin, isAdmin, userPermission, resource } = UserAllPermissions;
+ const userWorkflowPermissions = userPermission?.[resource[0].resourceType];
+
+ if (isAdmin || superAdmin) {
+ can(
+ [
+ FEATURE_KEY.GET,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.CREATE,
+ FEATURE_KEY.GET_ONE,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.UPDATE_SETTINGS,
+ FEATURE_KEY.PROMOTE,
+ ],
+ App
+ );
+ return;
+ }
+
+ const isAllEditable = !!userWorkflowPermissions?.isAllEditable;
+ const isAllExecutable = !!userWorkflowPermissions?.isAllExecutable;
+
+ if (isAllEditable) {
+ can(
+ [
+ FEATURE_KEY.GET,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.CREATE,
+ FEATURE_KEY.GET_ONE,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.UPDATE_SETTINGS,
+ FEATURE_KEY.PROMOTE,
+ ],
+ App
+ );
+ } else if (
+ userWorkflowPermissions?.editableWorkflowsId?.length &&
+ resourceId &&
+ userWorkflowPermissions.editableWorkflowsId.includes(resourceId)
+ ) {
+ can(
+ [
+ FEATURE_KEY.GET,
+ FEATURE_KEY.DELETE,
+ FEATURE_KEY.CREATE,
+ FEATURE_KEY.GET_ONE,
+ FEATURE_KEY.UPDATE,
+ FEATURE_KEY.UPDATE_SETTINGS,
+ FEATURE_KEY.PROMOTE,
+ ],
+ App
+ );
+ }
+
+ if (isAllExecutable) {
+ can([FEATURE_KEY.GET_EVENTS], App);
+ } else if (
+ userWorkflowPermissions?.executableWorkflowsId?.length &&
+ resourceId &&
+ userWorkflowPermissions.executableWorkflowsId.includes(resourceId)
+ ) {
+ can([FEATURE_KEY.GET_EVENTS], App);
+ }
+}
|