Skip to content

Commit 5bb5673

Browse files
Merge branch 'main' into signals-migration
2 parents 4f3aa7d + 85771eb commit 5bb5673

67 files changed

Lines changed: 7468 additions & 381 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/.development.env

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,6 @@ MYSQL_CONNECTION_SSH_PORT=22
109109
MYSQL_CONNECTION_SSH_USERNAME=TEST_MYSQL_CONNECTION_SSH_USERNAME
110110
MYSQL_CONNECTION_SSH_KEY=TEST_MYSQL_CONNECTION_SSH_KEY
111111

112-
TEST_CONNECTIONS=
112+
TEST_CONNECTIONS=
113+
114+
CEDAR_AUTHORIZATION_ENABLED='true'

backend/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@
2525
},
2626
"dependencies": {
2727
"@amplitude/node": "1.10.2",
28-
"@aws-sdk/client-bedrock-runtime": "^3.999.0",
29-
"@aws-sdk/client-s3": "^3.999.0",
30-
"@aws-sdk/lib-dynamodb": "^3.999.0",
31-
"@aws-sdk/s3-request-presigner": "^3.999.0",
28+
"@aws-sdk/client-bedrock-runtime": "^3.990.0",
29+
"@aws-sdk/client-s3": "^3.990.0",
30+
"@aws-sdk/lib-dynamodb": "^3.990.0",
31+
"@aws-sdk/s3-request-presigner": "^3.990.0",
32+
"@cedar-policy/cedar-wasm": "^4.9.0",
3233
"@electric-sql/pglite": "^0.3.15",
3334
"@faker-js/faker": "^10.3.0",
3435
"@langchain/aws": "^1.3.0",

backend/src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { DashboardModule } from './entities/visualizations/dashboard/dashboards.
4040
import { PanelPositionModule } from './entities/visualizations/panel-position/panel-position.module.js';
4141
import { PanelModule } from './entities/visualizations/panel/panel.module.js';
4242
import { TableWidgetModule } from './entities/widget/table-widget.module.js';
43+
import { CedarAuthorizationModule } from './entities/cedar-authorization/cedar-authorization.module.js';
4344
import { SaaSGatewayModule } from './microservices/gateways/saas-gateway.ts/saas-gateway.module.js';
4445
import { SaasModule } from './microservices/saas-microservice/saas.module.js';
4546
import { AppLoggerMiddleware } from './middlewares/logging-middleware/app-logger-middlewate.js';
@@ -59,6 +60,7 @@ import { GetHelloUseCase } from './use-cases-app/get-hello.use.case.js';
5960
},
6061
],
6162
}),
63+
CedarAuthorizationModule,
6264
AICoreModule,
6365
ConnectionModule,
6466
ConnectionPropertiesModule,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export enum CedarAction {
2+
ConnectionRead = 'connection:read',
3+
ConnectionEdit = 'connection:edit',
4+
GroupRead = 'group:read',
5+
GroupEdit = 'group:edit',
6+
TableRead = 'table:read',
7+
TableAdd = 'table:add',
8+
TableEdit = 'table:edit',
9+
TableDelete = 'table:delete',
10+
DashboardRead = 'dashboard:read',
11+
DashboardCreate = 'dashboard:create',
12+
DashboardEdit = 'dashboard:edit',
13+
DashboardDelete = 'dashboard:delete',
14+
}
15+
16+
export enum CedarResourceType {
17+
Connection = 'RocketAdmin::Connection',
18+
Group = 'RocketAdmin::Group',
19+
Table = 'RocketAdmin::Table',
20+
Dashboard = 'RocketAdmin::Dashboard',
21+
}
22+
23+
export const CEDAR_ACTION_TYPE = 'RocketAdmin::Action';
24+
export const CEDAR_USER_TYPE = 'RocketAdmin::User';
25+
export const CEDAR_GROUP_TYPE = 'RocketAdmin::Group';
26+
27+
export interface CedarValidationRequest {
28+
userId: string;
29+
action: CedarAction;
30+
connectionId?: string;
31+
groupId?: string;
32+
tableName?: string;
33+
dashboardId?: string;
34+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {
2+
Body,
3+
Controller,
4+
Get,
5+
HttpException,
6+
HttpStatus,
7+
Injectable,
8+
Post,
9+
UseGuards,
10+
UseInterceptors,
11+
} from '@nestjs/common';
12+
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
13+
import { SlugUuid } from '../../decorators/index.js';
14+
import { Timeout } from '../../decorators/timeout.decorator.js';
15+
import { Messages } from '../../exceptions/text/messages.js';
16+
import { ConnectionEditGuard } from '../../guards/connection-edit.guard.js';
17+
import { ConnectionReadGuard } from '../../guards/connection-read.guard.js';
18+
import { SentryInterceptor } from '../../interceptors/index.js';
19+
import { IComplexPermission } from '../permission/permission.interface.js';
20+
import { CedarAuthorizationService } from './cedar-authorization.service.js';
21+
import { SaveCedarPolicyDto } from './dto/save-cedar-policy.dto.js';
22+
import { ValidateCedarSchemaDto } from './dto/validate-cedar-schema.dto.js';
23+
24+
@UseInterceptors(SentryInterceptor)
25+
@Timeout()
26+
@Controller()
27+
@ApiBearerAuth()
28+
@ApiTags('Cedar Authorization')
29+
@Injectable()
30+
export class CedarAuthorizationController {
31+
constructor(private readonly cedarAuthService: CedarAuthorizationService) {}
32+
33+
@ApiOperation({ summary: 'Get the current cedar schema used for authorization' })
34+
@ApiResponse({
35+
status: 200,
36+
description: 'Cedar schema returned.',
37+
})
38+
@ApiParam({ name: 'connectionId', required: true })
39+
@UseGuards(ConnectionReadGuard)
40+
@Get('/connection/cedar-schema/:connectionId')
41+
async getCedarSchema(
42+
@SlugUuid('connectionId') connectionId: string,
43+
): Promise<{ cedarSchema: Record<string, unknown> }> {
44+
if (!connectionId) {
45+
throw new HttpException({ message: Messages.CONNECTION_ID_MISSING }, HttpStatus.BAD_REQUEST);
46+
}
47+
return { cedarSchema: this.cedarAuthService.getSchema() };
48+
}
49+
50+
@ApiOperation({ summary: 'Validate a cedar schema against the Cedar engine' })
51+
@ApiResponse({
52+
status: 200,
53+
description: 'Cedar schema is valid.',
54+
})
55+
@ApiBody({ type: ValidateCedarSchemaDto })
56+
@ApiParam({ name: 'connectionId', required: true })
57+
@UseGuards(ConnectionReadGuard)
58+
@Post('/connection/cedar-schema/validate/:connectionId')
59+
async validateCedarSchema(
60+
@SlugUuid('connectionId') connectionId: string,
61+
@Body() dto: ValidateCedarSchemaDto,
62+
): Promise<{ valid: boolean }> {
63+
if (!connectionId) {
64+
throw new HttpException({ message: Messages.CONNECTION_ID_MISSING }, HttpStatus.BAD_REQUEST);
65+
}
66+
this.cedarAuthService.validateCedarSchema(dto.cedarSchema);
67+
return { valid: true };
68+
}
69+
70+
@ApiOperation({ summary: 'Save a cedar policy for a group, generating classical permissions for backward compatibility' })
71+
@ApiResponse({
72+
status: 200,
73+
description: 'Cedar policy saved and classical permissions generated.',
74+
})
75+
@ApiBody({ type: SaveCedarPolicyDto })
76+
@ApiParam({ name: 'connectionId', required: true })
77+
@UseGuards(ConnectionEditGuard)
78+
@Post('/connection/cedar-policy/:connectionId')
79+
async saveCedarPolicy(
80+
@SlugUuid('connectionId') connectionId: string,
81+
@Body() dto: SaveCedarPolicyDto,
82+
): Promise<{ cedarPolicy: string; classicalPermissions: IComplexPermission }> {
83+
if (!connectionId) {
84+
throw new HttpException({ message: Messages.CONNECTION_ID_MISSING }, HttpStatus.BAD_REQUEST);
85+
}
86+
return this.cedarAuthService.saveCedarPolicy(connectionId, dto.groupId, dto.cedarPolicy);
87+
}
88+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Global, MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
2+
import { TypeOrmModule } from '@nestjs/typeorm';
3+
import { AuthMiddleware } from '../../authorization/auth.middleware.js';
4+
import { GlobalDatabaseContext } from '../../common/application/global-database-context.js';
5+
import { BaseType } from '../../common/data-injection.tokens.js';
6+
import { LogOutEntity } from '../log-out/log-out.entity.js';
7+
import { UserEntity } from '../user/user.entity.js';
8+
import { CedarAuthorizationController } from './cedar-authorization.controller.js';
9+
import { CedarAuthorizationService } from './cedar-authorization.service.js';
10+
11+
@Global()
12+
@Module({
13+
imports: [TypeOrmModule.forFeature([UserEntity, LogOutEntity])],
14+
providers: [
15+
{
16+
provide: BaseType.GLOBAL_DB_CONTEXT,
17+
useClass: GlobalDatabaseContext,
18+
},
19+
CedarAuthorizationService,
20+
],
21+
controllers: [CedarAuthorizationController],
22+
exports: [CedarAuthorizationService],
23+
})
24+
export class CedarAuthorizationModule implements NestModule {
25+
public configure(consumer: MiddlewareConsumer): void {
26+
consumer
27+
.apply(AuthMiddleware)
28+
.forRoutes(
29+
{ path: '/connection/cedar-schema/:connectionId', method: RequestMethod.GET },
30+
{ path: '/connection/cedar-schema/validate/:connectionId', method: RequestMethod.POST },
31+
{ path: '/connection/cedar-policy/:connectionId', method: RequestMethod.POST },
32+
);
33+
}
34+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { IComplexPermission } from '../permission/permission.interface.js';
2+
import { CedarValidationRequest } from './cedar-action-map.js';
3+
4+
export interface ICedarAuthorizationService {
5+
isFeatureEnabled(): boolean;
6+
validate(request: CedarValidationRequest): Promise<boolean>;
7+
invalidatePolicyCacheForConnection(connectionId: string): void;
8+
getSchema(): Record<string, unknown>;
9+
validateCedarSchema(schema: Record<string, unknown>): void;
10+
saveCedarPolicy(
11+
connectionId: string,
12+
groupId: string,
13+
cedarPolicy: string,
14+
): Promise<{ cedarPolicy: string; classicalPermissions: IComplexPermission }>;
15+
}

0 commit comments

Comments
 (0)