Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/src/entities/ai/user-ai-requests-v2.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { Timeout, TimeoutDefaults } from '../../decorators/timeout.decorator.js'
import { UserId } from '../../decorators/user-id.decorator.js';
import { InTransactionEnum } from '../../enums/in-transaction.enum.js';
import { ConnectionEditGuard } from '../../guards/connection-edit.guard.js';
import { TableReadGuard } from '../../guards/table-read.guard.js';
import { TableAiRequestGuard } from '../../guards/table-ai-request.guard.js';
import { ValidationHelper } from '../../helpers/validators/validation-helper.js';
import { SentryInterceptor } from '../../interceptors/sentry.interceptor.js';
import { IAISettingsAndWidgetsCreation, IRequestInfoFromTableV2 } from './ai-use-cases.interface.js';
Expand All @@ -47,7 +47,7 @@ export class UserAIRequestsControllerV2 {
status: 201,
description: 'Returned info with conversation history saved.',
})
@UseGuards(TableReadGuard)
@UseGuards(TableAiRequestGuard)
@ApiBody({ type: RequestInfoFromTableBodyDTO })
@ApiQuery({ name: 'tableName', required: true, type: String })
@ApiQuery({ name: 'threadId', required: false, type: String })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export enum CedarAction {
TableAdd = 'table:add',
TableEdit = 'table:edit',
TableDelete = 'table:delete',
TableAiRequest = 'table:ai-request',
DashboardRead = 'dashboard:read',
DashboardCreate = 'dashboard:create',
DashboardEdit = 'dashboard:edit',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,17 @@ export class CedarPermissionsService implements IUserAccessRepository {
): Promise<ITablePermissionData> {
const ctx = await this.loadContext(connectionId, cognitoUserName);
if (!ctx) {
return { tableName, accessLevel: { visibility: false, readonly: false, add: false, delete: false, edit: false } };
return {
tableName,
accessLevel: {
visibility: false,
readonly: false,
add: false,
delete: false,
edit: false,
aiRequest: false,
},
};
}

return this.evaluateTablePermissions(cognitoUserName, connectionId, tableName, ctx);
Expand Down Expand Up @@ -386,6 +396,14 @@ export class CedarPermissionsService implements IUserAccessRepository {
ctx.policies,
entities,
);
const canAiRequest = this.evaluatePolicies(
userId,
CedarAction.TableAiRequest,
CedarResourceType.Table,
resourceId,
ctx.policies,
entities,
);

return {
tableName,
Expand All @@ -395,6 +413,7 @@ export class CedarPermissionsService implements IUserAccessRepository {
add: canAdd,
delete: canDelete,
edit: canEdit,
aiRequest: canAiRequest,
},
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ export function generateCedarPolicyForGroup(
`permit(\n principal,\n action == RocketAdmin::Action::"table:delete",\n resource == ${tableRef}\n);`,
);
}
if (access.aiRequest) {
policies.push(
`permit(\n principal,\n action == RocketAdmin::Action::"table:ai-request",\n resource == ${tableRef}\n);`,
);
}
}

return policies.join('\n\n');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ export function parseCedarPolicyToClassicalPermissions(
case 'table:read':
case 'table:add':
case 'table:edit':
case 'table:delete': {
case 'table:delete':
case 'table:ai-request': {
const tableName = extractTableName(permit.resourceId, connectionId);
if (!tableName) break;
const tableEntry = getOrCreateTableEntry(tableMap, tableName);
Expand Down Expand Up @@ -227,6 +228,7 @@ function getOrCreateTableEntry(map: Map<string, ITablePermissionData>, tableName
add: false,
delete: false,
edit: false,
aiRequest: false,
},
};
map.set(tableName, entry);
Expand All @@ -248,6 +250,9 @@ function applyTableAction(entry: ITablePermissionData, action: string): void {
case 'table:delete':
entry.accessLevel.delete = true;
break;
case 'table:ai-request':
entry.accessLevel.aiRequest = true;
break;
}
}

Expand Down
6 changes: 6 additions & 0 deletions backend/src/entities/cedar-authorization/cedar-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@
"resourceTypes": ["Table"]
}
},
"table:ai-request": {
"appliesTo": {
"principalTypes": ["User"],
"resourceTypes": ["Table"]
}
},
"dashboard:read": {
"appliesTo": {
"principalTypes": ["User"],
Expand Down
6 changes: 6 additions & 0 deletions backend/src/entities/cedar-authorization/cedar-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ export const CEDAR_SCHEMA = {
resourceTypes: ['Table'],
},
},
'table:ai-request': {
appliesTo: {
principalTypes: ['User'],
resourceTypes: ['Table'],
},
},
'dashboard:read': {
appliesTo: {
principalTypes: ['User'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import AbstractUseCase from '../../../common/abstract-use.case.js';
import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
import { BaseType } from '../../../common/data-injection.tokens.js';
import { AccessLevelEnum } from '../../../enums/access-level.enum.js';
import { TablePermissionDs } from '../../permission/application/data-structures/create-permissions.ds.js';
import { parseCedarPolicyToClassicalPermissions } from '../../cedar-authorization/cedar-policy-parser.js';
import { TablePermissionDs } from '../../permission/application/data-structures/create-permissions.ds.js';
import { FoundPermissionsInConnectionDs } from '../application/data-structures/found-permissions-in-connection.ds.js';
import { GetPermissionsInConnectionDs } from '../application/data-structures/get-permissions-in-connection.ds.js';
import { IGetPermissionsForGroupInConnection } from './use-cases.interfaces.js';
Expand Down Expand Up @@ -62,6 +62,7 @@ export class GetPermissionsForGroupInConnectionUseCase
edit: false,
readonly: false,
visibility: false,
aiRequest: false,
},
};
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, IsBoolean, IsEnum, IsString, IsUUID, ValidateNested } from 'class-validator';
import { IsArray, IsBoolean, IsEnum, IsOptional, IsString, IsUUID, ValidateNested } from 'class-validator';
import { AccessLevelEnum } from '../../../../enums/access-level.enum.js';

export class CreatePermissionsDs {
Expand Down Expand Up @@ -41,6 +41,11 @@ export class TableAccessLevelsDs {
@ApiProperty()
@IsBoolean()
visibility: boolean;

@ApiProperty({ required: false })
@IsOptional()
@IsBoolean()
aiRequest?: boolean;
}

export class TablePermissionDs {
Expand Down
1 change: 1 addition & 0 deletions backend/src/entities/permission/permission.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface ITableAccessLevel {
add: boolean;
delete: boolean;
edit: boolean;
aiRequest?: boolean;
}

export interface ITablePermissionData {
Expand Down
46 changes: 46 additions & 0 deletions backend/src/guards/table-ai-request.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { BadRequestException, CanActivate, ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { IRequestWithCognitoInfo } from '../authorization/cognito-decoded.interface.js';
import { CedarAction } from '../entities/cedar-authorization/cedar-action-map.js';
import { CedarAuthorizationService } from '../entities/cedar-authorization/cedar-authorization.service.js';
import { Messages } from '../exceptions/text/messages.js';
import { ValidationHelper } from '../helpers/validators/validation-helper.js';
import { validateUuidByRegex } from './utils/validate-uuid-by-regex.js';

@Injectable()
export class TableAiRequestGuard implements CanActivate {
constructor(private readonly cedarAuthService: CedarAuthorizationService) {}

canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
return new Promise(async (resolve, reject) => {
const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest();
const cognitoUserName = request.decoded.sub;
const connectionId: string = request.params?.slug || request.params?.connectionId;
const tableName: string = request.query?.tableName;
if (!tableName) {
reject(new BadRequestException(Messages.TABLE_NAME_MISSING));
return;
}
if (!connectionId || (!validateUuidByRegex(connectionId) && !ValidationHelper.isValidNanoId(connectionId))) {
reject(new BadRequestException(Messages.CONNECTION_ID_MISSING));
return;
}

try {
const allowed = await this.cedarAuthService.validate({
userId: cognitoUserName,
action: CedarAction.TableAiRequest,
connectionId,
tableName,
});
if (allowed) {
resolve(true);
return;
}
reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS));
} catch (e) {
reject(e);
}
});
}
}
Loading
Loading