diff --git a/server/src/modules/data-queries/ability/app/guard.ts b/server/src/modules/data-queries/ability/app/guard.ts index 42e817796a..87d8c2d92b 100644 --- a/server/src/modules/data-queries/ability/app/guard.ts +++ b/server/src/modules/data-queries/ability/app/guard.ts @@ -19,7 +19,9 @@ export class FeatureAbilityGuard extends AbilityGuard { { resourceType: MODULES.APP, }, - { resourceType: MODULES.GLOBAL_DATA_SOURCE }, + { + 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 998c26ea7a..b388171e39 100644 --- a/server/src/modules/data-queries/ability/app/index.ts +++ b/server/src/modules/data-queries/ability/app/index.ts @@ -1,9 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { Ability, AbilityBuilder, InferSubjects, SubjectType } from '@casl/ability'; +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 { DataSource } from '@entities/data_source.entity'; import { MODULES } from '@modules/app/constants/modules'; import { App } from '@entities/app.entity'; @@ -16,8 +15,13 @@ export class FeatureAbilityFactory extends AbilityFactory return App; } - protected defineAbilityFor(can: AbilityBuilder['can'], UserAllPermissions: UserAllPermissions): void { - const { superAdmin, isAdmin, userPermission, isBuilder } = UserAllPermissions; + protected defineAbilityFor( + can: AbilityBuilder['can'], + UserAllPermissions: UserAllPermissions, + extractedMetadata: { moduleName: string; features: string[] }, + request?: any + ): void { + const { superAdmin, isAdmin, userPermission } = UserAllPermissions; const resourcePermissions = userPermission?.[MODULES.APP]; const isAllEditable = !!resourcePermissions?.isAllEditable; @@ -25,23 +29,69 @@ export class FeatureAbilityFactory extends AbilityFactory const isCanDelete = userPermission.appDelete; const isAllViewable = !!resourcePermissions?.isAllViewable; - //if (isAdmin || superAdmin) { + const appId = request?.tj_resource_id; + // Admin or super admin and do all operations - 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 (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], App); + return; + } + if (resourcePermissions?.viewableAppsId?.length && appId && resourcePermissions?.viewableAppsId?.includes(appId)) { + can([FEATURE_KEY.GET, FEATURE_KEY.PREVIEW, FEATURE_KEY.RUN_VIEWER], App); + return; + } } } diff --git a/server/src/modules/data-queries/ability/data-source/guard.ts b/server/src/modules/data-queries/ability/data-source/guard.ts index 8186194790..9838920e71 100644 --- a/server/src/modules/data-queries/ability/data-source/guard.ts +++ b/server/src/modules/data-queries/ability/data-source/guard.ts @@ -1,6 +1,8 @@ import { Injectable } from '@nestjs/common'; import { FeatureAbilityFactory } from '.'; import { AbilityGuard } from '@modules/app/guards/ability.guard'; +import { MODULES } from '@modules/app/constants/modules'; +import { ResourceDetails } from '@modules/app/types'; import { DataSource } from '@entities/data_source.entity'; @Injectable() @@ -13,6 +15,14 @@ export class FeatureAbilityGuard extends AbilityGuard { return DataSource; } + protected getResource(): ResourceDetails | ResourceDetails[] { + return [ + { + resourceType: MODULES.GLOBAL_DATA_SOURCE, + }, + ]; + } + protected forwardAbility(): boolean { return true; } diff --git a/server/src/modules/data-queries/ability/data-source/index.ts b/server/src/modules/data-queries/ability/data-source/index.ts index 3f6e44c89e..b93f513950 100644 --- a/server/src/modules/data-queries/ability/data-source/index.ts +++ b/server/src/modules/data-queries/ability/data-source/index.ts @@ -3,56 +3,108 @@ 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 { DataSource } from '@entities/data_source.entity'; -// import { MODULES } from '@modules/app/constants/modules'; -import { DataSourcesRepository } from '@modules/data-sources/repository'; -import { AbilityService } from '@modules/ability/interfaces/IService'; type Subjects = InferSubjects | 'all'; export type FeatureAbility = Ability<[FEATURE_KEY, Subjects]>; @Injectable() export class FeatureAbilityFactory extends AbilityFactory { - constructor(private readonly dataSourceRepository: DataSourcesRepository, protected abilityService: AbilityService) { - super(abilityService); - } protected getSubjectType() { return DataSource; } - protected async defineAbilityFor( + protected defineAbilityFor( can: AbilityBuilder['can'], UserAllPermissions: UserAllPermissions, extractedMetadata: { moduleName: string; features: string[] }, request?: any - ): Promise { - // Data source permissions - // EE - data source create/delete -> full access - // CE - Admin - full access. builder -> use access + ): void { + const { superAdmin, isAdmin, userPermission } = UserAllPermissions; - // const { userPermission } = UserAllPermissions; - // const staticDataSources = await this.dataSourceRepository.getAllStaticDataSources(request.params.versionId); + const resourcePermissions = userPermission?.[MODULES.GLOBAL_DATA_SOURCE]; + const isAllEditable = !!resourcePermissions?.isAllConfigurable; + const isCanCreate = userPermission.dataSourceCreate; + const isCanDelete = userPermission.dataSourceDelete; + const isAllViewable = !!resourcePermissions?.isAllUsable; - // const resourcePermissions = userPermission?.[MODULES.GLOBAL_DATA_SOURCE]; - // const isAllEditable = !!resourcePermissions?.isAllConfigurable; - // const isCanCreate = userPermission.dataSourceCreate; - // const isCanDelete = userPermission.dataSourceDelete; - // const isAllViewable = !!resourcePermissions?.isAllUsable; + const dataSourceId = request?.tj_resource_id; - 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, - ], - DataSource - ); - return; + // Define permissions for data queries + + if (isAdmin || superAdmin || isAllEditable || isCanCreate || isCanDelete) { + can( + [ + FEATURE_KEY.CREATE, + FEATURE_KEY.GET, + FEATURE_KEY.UPDATE, + FEATURE_KEY.DELETE, + FEATURE_KEY.RUN_VIEWER, + FEATURE_KEY.PREVIEW, + FEATURE_KEY.UPDATE_DATA_SOURCE, + FEATURE_KEY.UPDATE_ONE, + FEATURE_KEY.RUN_EDITOR, + ], + DataSource + ); + return; + } + + if ( + resourcePermissions?.configurableDataSourceId?.length && + dataSourceId && + resourcePermissions?.configurableDataSourceId?.includes(dataSourceId) + ) { + can( + [ + FEATURE_KEY.CREATE, + FEATURE_KEY.GET, + FEATURE_KEY.UPDATE, + FEATURE_KEY.DELETE, + FEATURE_KEY.RUN_VIEWER, + FEATURE_KEY.PREVIEW, + FEATURE_KEY.UPDATE_DATA_SOURCE, + FEATURE_KEY.UPDATE_ONE, + FEATURE_KEY.RUN_EDITOR, + ], + DataSource + ); + } + if (isAllViewable) { + 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, + ], + DataSource + ); + return; + } + if ( + resourcePermissions.usableDataSourcesId?.length && + dataSourceId && + resourcePermissions?.usableDataSourcesId?.includes(dataSourceId) + ) { + can( + [ + FEATURE_KEY.GET, + FEATURE_KEY.CREATE, + FEATURE_KEY.UPDATE, + FEATURE_KEY.DELETE, + FEATURE_KEY.RUN_VIEWER, + FEATURE_KEY.PREVIEW, + FEATURE_KEY.UPDATE_ONE, + FEATURE_KEY.RUN_EDITOR, + ], + DataSource + ); + } } } diff --git a/server/src/modules/data-queries/controller.ts b/server/src/modules/data-queries/controller.ts index ad149c407a..6ee4183d8f 100644 --- a/server/src/modules/data-queries/controller.ts +++ b/server/src/modules/data-queries/controller.ts @@ -15,7 +15,6 @@ import { FeatureAbilityGuard as AppFeatureAbilityGuard } from './ability/app/gua import { FeatureAbilityGuard as DataSourceFeatureAbilityGuard } from './ability/data-source/guard'; import { ValidateQuerySourceGuard } from './guards/validate-query-source.guard'; import { ValidateAppVersionGuard } from '@modules/versions/guards/validate-app-version.guard'; -import { QueryAuthGuard } from './guards/query-auth.guard'; import { AbilityDecorator as Ability } from '@modules/app/decorators/ability.decorator'; import { AppAbility } from '@modules/casl/casl-ability.factory'; import { AppDecorator } from '@modules/app/decorators/app.decorator'; @@ -26,19 +25,18 @@ import { IDataQueriesController } from './interfaces/IController'; export class DataQueriesController implements IDataQueriesController { constructor(protected dataQueriesService: DataQueriesService) {} - // Add ability check - App editable @InitFeature(FEATURE_KEY.GET) - @UseGuards(JwtAuthGuard, ValidateAppVersionGuard, AppFeatureAbilityGuard) + @UseGuards(JwtAuthGuard, ValidateAppVersionGuard, ValidateQueryAppGuard, AppFeatureAbilityGuard) @Get(':versionId') index(@Param('versionId') versionId: string) { return this.dataQueriesService.getAll(versionId); } @InitFeature(FEATURE_KEY.CREATE) - // Add ability check - App editable and data source configurable @UseGuards( JwtAuthGuard, ValidateAppVersionGuard, + ValidateQueryAppGuard, AppFeatureAbilityGuard, ValidateQuerySourceGuard, DataSourceFeatureAbilityGuard @@ -55,7 +53,6 @@ export class DataQueriesController implements IDataQueriesController { } @InitFeature(FEATURE_KEY.UPDATE_ONE) - // Add ability check - App editable and data source editable @UseGuards( JwtAuthGuard, ValidateQueryAppGuard, @@ -64,8 +61,9 @@ export class DataQueriesController implements IDataQueriesController { DataSourceFeatureAbilityGuard ) @Patch(':id/versions/:versionId') - async updateDataSource( + async updateDataQuery( @User() user: UserEntity, + @AppDecorator() app: App, @Param('id') dataQueryId, @Param('versionId') versionId, @Body() updateDataQueryDto: UpdateDataQueryDto @@ -76,7 +74,7 @@ export class DataQueriesController implements IDataQueriesController { @InitFeature(FEATURE_KEY.UPDATE) //* On Updating references, need update the options of multiple queries - @UseGuards(JwtAuthGuard, ValidateAppVersionGuard, AppFeatureAbilityGuard) + @UseGuards(JwtAuthGuard, ValidateAppVersionGuard, ValidateQueryAppGuard, AppFeatureAbilityGuard) @Patch('versions/:versionId') async bulkUpdate(@User() user: UserEntity, @Body() updatingReferencesOptions: UpdatingReferencesOptionsDto) { return await this.dataQueriesService.bulkUpdateQueryOptions(user, updatingReferencesOptions.data_queries_options); @@ -97,7 +95,6 @@ export class DataQueriesController implements IDataQueriesController { } @InitFeature(FEATURE_KEY.RUN_EDITOR) - // TODO: Validate against app edit access @UseGuards( JwtAuthGuard, ValidateQueryAppGuard, @@ -108,6 +105,7 @@ export class DataQueriesController implements IDataQueriesController { @Post(':id/versions/:versionId/run/:environmentId') runQueryOnBuilder( @User() user: UserEntity, + @AppDecorator() app: App, @Param('id') dataQueryId, @Param('environmentId') environmentId, @Body() updateDataQueryDto: UpdateDataQueryDto, @@ -127,11 +125,17 @@ export class DataQueriesController implements IDataQueriesController { } @InitFeature(FEATURE_KEY.RUN_VIEWER) - // TODO: Validate against app view access - @UseGuards(QueryAuthGuard, AppFeatureAbilityGuard) + @UseGuards( + JwtAuthGuard, + ValidateQueryAppGuard, + AppFeatureAbilityGuard, + ValidateQuerySourceGuard, + DataSourceFeatureAbilityGuard + ) @Post(':id/run') async runQuery( @User() user: UserEntity, + @AppDecorator() app: App, @Param('id') dataQueryId, @Body() updateDataQueryDto: UpdateDataQueryDto, @Res({ passthrough: true }) response: Response diff --git a/server/src/modules/data-queries/guards/validate-query-app.guard.ts b/server/src/modules/data-queries/guards/validate-query-app.guard.ts index be6a28c306..a5e3029460 100644 --- a/server/src/modules/data-queries/guards/validate-query-app.guard.ts +++ b/server/src/modules/data-queries/guards/validate-query-app.guard.ts @@ -17,10 +17,10 @@ export class ValidateQueryAppGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); const { id, versionId } = request.params; + const appId = request.body?.app_id; const user: User = request.user; - // Check if either id is provided, otherwise throw BadRequestException - if (!id) { + if (!versionId) { throw new BadRequestException(); } @@ -28,8 +28,16 @@ export class ValidateQueryAppGuard implements CanActivate { if (!user) { throw new ForbiddenException(); } - - const app = await this.appsRepository.findByDataQuery(id, user.organizationId, versionId); + let app; + if (id) { + app = await this.appsRepository.findByDataQuery(id, user.organizationId, versionId); + } + if (appId) { + app = await this.appsRepository.findById(appId, user.organizationId, versionId); + } + if (versionId) { + app = await this.versionRepository.findAppFromVersion(versionId, user.organizationId); + } // If app is not found, throw NotFoundException if (!app) { diff --git a/server/src/modules/data-queries/interfaces/IController.ts b/server/src/modules/data-queries/interfaces/IController.ts index e188b0c219..d68f28b382 100644 --- a/server/src/modules/data-queries/interfaces/IController.ts +++ b/server/src/modules/data-queries/interfaces/IController.ts @@ -16,8 +16,9 @@ export interface IDataQueriesController { dataQueryDto: CreateDataQueryDto ): Promise; - updateDataSource( + updateDataQuery( user: UserEntity, + app: App, dataQueryId: string, versionId: string, updateDataQueryDto: UpdateDataQueryDto @@ -29,6 +30,7 @@ export interface IDataQueriesController { runQueryOnBuilder( user: UserEntity, + app: App, dataQueryId: string, environmentId: string, updateDataQueryDto: UpdateDataQueryDto, @@ -39,6 +41,7 @@ export interface IDataQueriesController { runQuery( user: UserEntity, + app: App, dataQueryId: string, updateDataQueryDto: UpdateDataQueryDto, response: Response diff --git a/server/src/modules/data-sources/ability/index.ts b/server/src/modules/data-sources/ability/index.ts index d3d09cabfa..4e960170bb 100644 --- a/server/src/modules/data-sources/ability/index.ts +++ b/server/src/modules/data-sources/ability/index.ts @@ -108,6 +108,7 @@ export class FeatureAbilityFactory extends AbilityFactory resourcePermissions?.usableDataSourcesId?.includes(dataSourceId) ) { can([FEATURE_KEY.GET, FEATURE_KEY.GET_BY_ENVIRONMENT], DataSource); + return; } } }