Permissions refactor (#12624)

* permissions refactor

* update

* updates
This commit is contained in:
Anantshree Chandola 2025-04-18 18:06:09 +05:30 committed by GitHub
parent 6f12ec47bb
commit 4fdc36a99e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 200 additions and 70 deletions

View file

@ -19,7 +19,9 @@ export class FeatureAbilityGuard extends AbilityGuard {
{
resourceType: MODULES.APP,
},
{ resourceType: MODULES.GLOBAL_DATA_SOURCE },
{
resourceType: MODULES.GLOBAL_DATA_SOURCE,
},
];
}

View file

@ -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<FEATURE_KEY, Subjects>
return App;
}
protected defineAbilityFor(can: AbilityBuilder<FeatureAbility>['can'], UserAllPermissions: UserAllPermissions): void {
const { superAdmin, isAdmin, userPermission, isBuilder } = UserAllPermissions;
protected defineAbilityFor(
can: AbilityBuilder<FeatureAbility>['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<FEATURE_KEY, Subjects>
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;
}
}
}

View file

@ -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;
}

View file

@ -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<typeof DataSource> | 'all';
export type FeatureAbility = Ability<[FEATURE_KEY, Subjects]>;
@Injectable()
export class FeatureAbilityFactory extends AbilityFactory<FEATURE_KEY, Subjects> {
constructor(private readonly dataSourceRepository: DataSourcesRepository, protected abilityService: AbilityService) {
super(abilityService);
}
protected getSubjectType() {
return DataSource;
}
protected async defineAbilityFor(
protected defineAbilityFor(
can: AbilityBuilder<FeatureAbility>['can'],
UserAllPermissions: UserAllPermissions,
extractedMetadata: { moduleName: string; features: string[] },
request?: any
): Promise<void> {
// 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
);
}
}
}

View file

@ -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

View file

@ -17,10 +17,10 @@ export class ValidateQueryAppGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
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) {

View file

@ -16,8 +16,9 @@ export interface IDataQueriesController {
dataQueryDto: CreateDataQueryDto
): Promise<object>;
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

View file

@ -108,6 +108,7 @@ export class FeatureAbilityFactory extends AbilityFactory<FEATURE_KEY, Subjects>
resourcePermissions?.usableDataSourcesId?.includes(dataSourceId)
) {
can([FEATURE_KEY.GET, FEATURE_KEY.GET_BY_ENVIRONMENT], DataSource);
return;
}
}
}