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
6 changes: 6 additions & 0 deletions backend/src/entities/cedar-authorization/cedar-action-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ export enum CedarAction {
TableAdd = 'table:add',
TableEdit = 'table:edit',
TableDelete = 'table:delete',
DashboardRead = 'dashboard:read',
DashboardCreate = 'dashboard:create',
DashboardEdit = 'dashboard:edit',
DashboardDelete = 'dashboard:delete',
}

export enum CedarResourceType {
Connection = 'RocketAdmin::Connection',
Group = 'RocketAdmin::Group',
Table = 'RocketAdmin::Table',
Dashboard = 'RocketAdmin::Dashboard',
}

export const CEDAR_ACTION_TYPE = 'RocketAdmin::Action';
Expand All @@ -25,4 +30,5 @@ export interface CedarValidationRequest {
connectionId?: string;
groupId?: string;
tableName?: string;
dashboardId?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
Body,
Controller,
Get,
HttpException,
HttpStatus,
Injectable,
Post,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { SlugUuid } from '../../decorators/index.js';
import { Timeout } from '../../decorators/timeout.decorator.js';
import { Messages } from '../../exceptions/text/messages.js';
import { ConnectionEditGuard } from '../../guards/connection-edit.guard.js';
import { ConnectionReadGuard } from '../../guards/connection-read.guard.js';
import { SentryInterceptor } from '../../interceptors/index.js';
import { IComplexPermission } from '../permission/permission.interface.js';
import { CedarAuthorizationService } from './cedar-authorization.service.js';
import { SaveCedarPolicyDto } from './dto/save-cedar-policy.dto.js';
import { ValidateCedarSchemaDto } from './dto/validate-cedar-schema.dto.js';

@UseInterceptors(SentryInterceptor)
@Timeout()
@Controller()
@ApiBearerAuth()
@ApiTags('Cedar Authorization')
@Injectable()
export class CedarAuthorizationController {
constructor(private readonly cedarAuthService: CedarAuthorizationService) {}

@ApiOperation({ summary: 'Get the current cedar schema used for authorization' })
@ApiResponse({
status: 200,
description: 'Cedar schema returned.',
})
@ApiParam({ name: 'connectionId', required: true })
@UseGuards(ConnectionReadGuard)
@Get('/connection/cedar-schema/:connectionId')
async getCedarSchema(
@SlugUuid('connectionId') connectionId: string,
): Promise<{ cedarSchema: Record<string, unknown> }> {
if (!connectionId) {
throw new HttpException({ message: Messages.CONNECTION_ID_MISSING }, HttpStatus.BAD_REQUEST);
}
return { cedarSchema: this.cedarAuthService.getSchema() };
}

@ApiOperation({ summary: 'Validate a cedar schema against the Cedar engine' })
@ApiResponse({
status: 200,
description: 'Cedar schema is valid.',
})
@ApiBody({ type: ValidateCedarSchemaDto })
@ApiParam({ name: 'connectionId', required: true })
@UseGuards(ConnectionReadGuard)
@Post('/connection/cedar-schema/validate/:connectionId')
async validateCedarSchema(
@SlugUuid('connectionId') connectionId: string,
@Body() dto: ValidateCedarSchemaDto,
): Promise<{ valid: boolean }> {
if (!connectionId) {
throw new HttpException({ message: Messages.CONNECTION_ID_MISSING }, HttpStatus.BAD_REQUEST);
}
this.cedarAuthService.validateCedarSchema(dto.cedarSchema);
return { valid: true };
}

@ApiOperation({ summary: 'Save a cedar policy for a group, generating classical permissions for backward compatibility' })
@ApiResponse({
status: 200,
description: 'Cedar policy saved and classical permissions generated.',
})
@ApiBody({ type: SaveCedarPolicyDto })
@ApiParam({ name: 'connectionId', required: true })
@UseGuards(ConnectionEditGuard)
@Post('/connection/cedar-policy/:connectionId')
Comment on lines +50 to +78
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swagger decorators declare @ApiResponse({ status: 200 }) for POST endpoints (/connection/cedar-schema/validate/:connectionId and /connection/cedar-policy/:connectionId), but Nest will respond with 201 by default for @Post() unless @HttpCode(200) is set. Update the documented status to 201 or set an explicit HttpCode to keep the OpenAPI spec consistent with actual responses (the e2e tests assert 201 for save policy).

Copilot uses AI. Check for mistakes.
async saveCedarPolicy(
@SlugUuid('connectionId') connectionId: string,
@Body() dto: SaveCedarPolicyDto,
): Promise<{ cedarPolicy: string; classicalPermissions: IComplexPermission }> {
if (!connectionId) {
throw new HttpException({ message: Messages.CONNECTION_ID_MISSING }, HttpStatus.BAD_REQUEST);
}
return this.cedarAuthService.saveCedarPolicy(connectionId, dto.groupId, dto.cedarPolicy);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
import { Global, Module } from '@nestjs/common';
import { Global, MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthMiddleware } from '../../authorization/auth.middleware.js';
import { GlobalDatabaseContext } from '../../common/application/global-database-context.js';
import { BaseType } from '../../common/data-injection.tokens.js';
import { LogOutEntity } from '../log-out/log-out.entity.js';
import { UserEntity } from '../user/user.entity.js';
import { CedarAuthorizationController } from './cedar-authorization.controller.js';
import { CedarAuthorizationService } from './cedar-authorization.service.js';

@Global()
@Module({
providers: [CedarAuthorizationService],
imports: [TypeOrmModule.forFeature([UserEntity, LogOutEntity])],
providers: [
{
provide: BaseType.GLOBAL_DB_CONTEXT,
useClass: GlobalDatabaseContext,
},
CedarAuthorizationService,
],
controllers: [CedarAuthorizationController],
exports: [CedarAuthorizationService],
})
export class CedarAuthorizationModule {}
export class CedarAuthorizationModule implements NestModule {
public configure(consumer: MiddlewareConsumer): void {
consumer
.apply(AuthMiddleware)
.forRoutes(
{ path: '/connection/cedar-schema/:connectionId', method: RequestMethod.GET },
{ path: '/connection/cedar-schema/validate/:connectionId', method: RequestMethod.POST },
{ path: '/connection/cedar-policy/:connectionId', method: RequestMethod.POST },
);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { IComplexPermission } from '../permission/permission.interface.js';
import { CedarValidationRequest } from './cedar-action-map.js';

export interface ICedarAuthorizationService {
isFeatureEnabled(): boolean;
validate(request: CedarValidationRequest): Promise<boolean>;
invalidatePolicyCacheForConnection(connectionId: string): void;
getSchema(): Record<string, unknown>;
validateCedarSchema(schema: Record<string, unknown>): void;
saveCedarPolicy(
connectionId: string,
groupId: string,
cedarPolicy: string,
): Promise<{ cedarPolicy: string; classicalPermissions: IComplexPermission }>;
}
Loading
Loading