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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { ICustomFieldsRepository } from '../../entities/custom-field/repository/
import { IEmailVerificationRepository } from '../../entities/email/repository/email-verification.repository.interface.js';
import { IGroupRepository } from '../../entities/group/repository/group.repository.interface.js';
import { ILogOutRepository } from '../../entities/log-out/repository/log-out-repository.interface.js';
import { IPermissionRepository } from '../../entities/permission/repository/permission.repository.interface.js';
import { ISecretAccessLogRepository } from '../../entities/secret-access-log/repository/secret-access-log-repository.interface.js';
import { SecretAccessLogEntity } from '../../entities/secret-access-log/secret-access-log.entity.js';
import { ActionEventsEntity } from '../../entities/table-actions/table-action-events-module/action-event.entity.js';
Expand Down Expand Up @@ -70,7 +69,6 @@ export interface IGlobalDatabaseContext extends IDatabaseContext {
userRepository: Repository<UserEntity> & IUserRepository;
connectionRepository: Repository<ConnectionEntity> & IConnectionRepository;
groupRepository: IGroupRepository;
permissionRepository: IPermissionRepository;
tableSettingsRepository: Repository<TableSettingsEntity> & ITableSettingsRepository;
agentRepository: IAgentRepository;
emailVerificationRepository: IEmailVerificationRepository;
Expand Down
11 changes: 0 additions & 11 deletions backend/src/common/application/global-database-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ import { groupCustomRepositoryExtension } from '../../entities/group/repository/
import { LogOutEntity } from '../../entities/log-out/log-out.entity.js';
import { logOutCustomRepositoryExtension } from '../../entities/log-out/repository/log-out-custom-repository-extension.js';
import { ILogOutRepository } from '../../entities/log-out/repository/log-out-repository.interface.js';
import { PermissionEntity } from '../../entities/permission/permission.entity.js';
import { IPermissionRepository } from '../../entities/permission/repository/permission.repository.interface.js';
import { permissionCustomRepositoryExtension } from '../../entities/permission/repository/permission-custom-repository-extension.js';
import { secretAccessLogRepositoryExtension } from '../../entities/secret-access-log/repository/secret-access-log-repository.extension.js';
import { ISecretAccessLogRepository } from '../../entities/secret-access-log/repository/secret-access-log-repository.interface.js';
import { SecretAccessLogEntity } from '../../entities/secret-access-log/secret-access-log.entity.js';
Expand Down Expand Up @@ -123,7 +120,6 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
private _userRepository: Repository<UserEntity> & IUserRepository;
private _connectionRepository: Repository<ConnectionEntity> & IConnectionRepository;
private _groupRepository: IGroupRepository;
private _permissionRepository: IPermissionRepository;
private _tableSettingsRepository: Repository<TableSettingsEntity> & ITableSettingsRepository;
private _agentRepository: IAgentRepository;
private _emailVerificationRepository: IEmailVerificationRepository;
Expand Down Expand Up @@ -175,9 +171,6 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
.getRepository(ConnectionEntity)
.extend(customConnectionRepositoryExtension);
this._groupRepository = this.appDataSource.getRepository(GroupEntity).extend(groupCustomRepositoryExtension);
this._permissionRepository = this.appDataSource
.getRepository(PermissionEntity)
.extend(permissionCustomRepositoryExtension);
this._tableSettingsRepository = this.appDataSource
.getRepository(TableSettingsEntity)
.extend(tableSettingsCustomRepositoryExtension);
Expand Down Expand Up @@ -285,10 +278,6 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
return this._groupRepository;
}

public get permissionRepository(): IPermissionRepository {
return this._permissionRepository;
}

public get tableSettingsRepository(): Repository<TableSettingsEntity> & ITableSettingsRepository {
return this._tableSettingsRepository;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { HttpException, HttpStatus, Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { AccessLevelEnum, PermissionTypeEnum } from '../../enums/index.js';
import { Messages } from '../../exceptions/text/messages.js';
import { Cacher } from '../../helpers/cache/cacher.js';
import { IGlobalDatabaseContext } from '../../common/application/global-database-context.interface.js';
import { BaseType } from '../../common/data-injection.tokens.js';
import { GroupEntity } from '../group/group.entity.js';
import { IComplexPermission } from '../permission/permission.interface.js';
import { PermissionEntity } from '../permission/permission.entity.js';
import {
CedarAction,
CedarResourceType,
Expand Down Expand Up @@ -87,14 +85,12 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On
): Promise<{ cedarPolicy: string; classicalPermissions: IComplexPermission }> {
this.validateCedarPolicyText(cedarPolicy);

const group = await this.globalDbContext.groupRepository.findGroupWithPermissionsById(groupId);
const group = await this.globalDbContext.groupRepository.findGroupByIdWithConnectionAndUsers(groupId);
if (!group) {
throw new HttpException({ message: Messages.GROUP_NOT_FOUND }, HttpStatus.BAD_REQUEST);
}

const groupWithConnection = await this.globalDbContext.groupRepository.findGroupByIdWithConnectionAndUsers(groupId);

if (groupWithConnection?.connection?.id !== connectionId) {
if (group.connection?.id !== connectionId) {
throw new HttpException({ message: Messages.GROUP_NOT_FROM_THIS_CONNECTION }, HttpStatus.BAD_REQUEST);
}

Expand All @@ -106,8 +102,6 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On

const classicalPermissions = parseCedarPolicyToClassicalPermissions(cedarPolicy, connectionId, groupId);

await this.syncClassicalPermissions(group, classicalPermissions);

group.cedarPolicy = cedarPolicy;
await this.globalDbContext.groupRepository.saveNewOrUpdatedGroup(group);
Cacher.invalidateCedarPolicyCache(connectionId);
Comment on lines 103 to 107
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

saveCedarPolicy no longer calls syncClassicalPermissions, which means any features still querying group.permissions / PermissionEntity will become stale or stop working after a Cedar policy update. For example, groupRepository.findAllUsersInGroupsWhereUserIsAdmin (used in table log filtering) currently checks group.permissions for PermissionTypeEnum.Group + AccessLevelEnum.edit. If the intent is to fully deprecate classical permissions, those remaining queries should be migrated to Cedar evaluation before removing synchronization.

Copilot uses AI. Check for mistakes.
Expand Down Expand Up @@ -181,7 +175,7 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On
const userGroups = await this.globalDbContext.groupRepository.findAllUserGroupsInConnection(connectionId, userId);
if (userGroups.length === 0) return false;

const groupPolicies = await this.loadPoliciesPerGroup(connectionId, userGroups);
const groupPolicies = this.loadPoliciesPerGroup(userGroups);
if (groupPolicies.length === 0) return false;

const entities = buildCedarEntities(userId, userGroups, connectionId, tableName, dashboardId);
Expand Down Expand Up @@ -210,13 +204,8 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On
return false;
}

private async loadPoliciesPerGroup(connectionId: string, userGroups: Array<GroupEntity>): Promise<string[]> {
const groups = await this.globalDbContext.groupRepository.findAllGroupsInConnection(connectionId);
const userGroupIdSet = new Set(userGroups.map((g) => g.id));
return groups
.filter((g) => userGroupIdSet.has(g.id))
.map((g) => g.cedarPolicy)
.filter(Boolean);
private loadPoliciesPerGroup(userGroups: Array<GroupEntity>): string[] {
return userGroups.map((g) => g.cedarPolicy).filter(Boolean);
}

private async assertUserNotSuspended(userId: string): Promise<void> {
Expand Down Expand Up @@ -316,74 +305,4 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On
}
}

private async syncClassicalPermissions(group: GroupEntity, permissions: IComplexPermission): Promise<void> {
if (group.permissions && group.permissions.length > 0) {
for (const perm of group.permissions) {
await this.globalDbContext.permissionRepository.removePermissionEntity(perm);
}
}
group.permissions = [];

if (permissions.connection.accessLevel !== AccessLevelEnum.none) {
const connPerm = new PermissionEntity();
connPerm.type = PermissionTypeEnum.Connection;
connPerm.accessLevel = permissions.connection.accessLevel;
const saved = await this.globalDbContext.permissionRepository.saveNewOrUpdatedPermission(connPerm);
group.permissions.push(saved);
}

if (permissions.group.accessLevel !== AccessLevelEnum.none) {
const groupPerm = new PermissionEntity();
groupPerm.type = PermissionTypeEnum.Group;
groupPerm.accessLevel = permissions.group.accessLevel;
const saved = await this.globalDbContext.permissionRepository.saveNewOrUpdatedPermission(groupPerm);
group.permissions.push(saved);
}

for (const table of permissions.tables) {
const access = table.accessLevel;
if (access.visibility) {
const perm = new PermissionEntity();
perm.type = PermissionTypeEnum.Table;
perm.accessLevel = AccessLevelEnum.visibility;
perm.tableName = table.tableName;
const saved = await this.globalDbContext.permissionRepository.saveNewOrUpdatedPermission(perm);
group.permissions.push(saved);
}
if (access.readonly) {
const perm = new PermissionEntity();
perm.type = PermissionTypeEnum.Table;
perm.accessLevel = AccessLevelEnum.readonly;
perm.tableName = table.tableName;
const saved = await this.globalDbContext.permissionRepository.saveNewOrUpdatedPermission(perm);
group.permissions.push(saved);
}
if (access.add) {
const perm = new PermissionEntity();
perm.type = PermissionTypeEnum.Table;
perm.accessLevel = AccessLevelEnum.add;
perm.tableName = table.tableName;
const saved = await this.globalDbContext.permissionRepository.saveNewOrUpdatedPermission(perm);
group.permissions.push(saved);
}
if (access.edit) {
const perm = new PermissionEntity();
perm.type = PermissionTypeEnum.Table;
perm.accessLevel = AccessLevelEnum.edit;
perm.tableName = table.tableName;
const saved = await this.globalDbContext.permissionRepository.saveNewOrUpdatedPermission(perm);
group.permissions.push(saved);
}
if (access.delete) {
const perm = new PermissionEntity();
perm.type = PermissionTypeEnum.Table;
perm.accessLevel = AccessLevelEnum.delete;
perm.tableName = table.tableName;
const saved = await this.globalDbContext.permissionRepository.saveNewOrUpdatedPermission(perm);
group.permissions.push(saved);
}
}

await this.globalDbContext.groupRepository.saveNewOrUpdatedGroup(group);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ export function parseCedarPolicyToClassicalPermissions(
}

result.tables = Array.from(tableMap.values());
for (const table of result.tables) {
const a = table.accessLevel;
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

parseCedarPolicyToClassicalPermissions currently sets visibility only when it sees table:read. If a Cedar policy permits table:add/table:edit/table:delete without an explicit table:read (which is syntactically valid and passes validatePolicyReferences), the parser will return add/edit/delete: true but visibility: false, producing an inconsistent classical permission shape. To keep parity with CedarPermissionsService (where visibility = canRead || canAdd || canEdit || canDelete), consider deriving visibility from any of the table actions and then computing readonly from the derived booleans.

Suggested change
const a = table.accessLevel;
const a = table.accessLevel;
// Ensure visibility matches any allowed table action, in parity with CedarPermissionsService
if (!a.visibility && (a.add || a.edit || a.delete)) {
a.visibility = true;
}

Copilot uses AI. Check for mistakes.
a.readonly = a.visibility && !a.add && !a.edit && !a.delete;
Comment on lines +83 to +84
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

The new post-processing loop uses a non-descriptive variable name a for table.accessLevel, which makes the logic harder to read/maintain. Consider renaming it to something explicit like access/accessLevel to match surrounding conventions.

Suggested change
const a = table.accessLevel;
a.readonly = a.visibility && !a.add && !a.edit && !a.delete;
const accessLevel = table.accessLevel;
accessLevel.readonly =
accessLevel.visibility && !accessLevel.add && !accessLevel.edit && !accessLevel.delete;

Copilot uses AI. Check for mistakes.
}
result.dashboards = Array.from(dashboardMap.values());

return result;
Expand Down Expand Up @@ -215,7 +219,6 @@ function applyTableAction(entry: ITablePermissionData, action: string): void {
switch (action) {
case 'table:read':
entry.accessLevel.visibility = true;
entry.accessLevel.readonly = true;
break;
case 'table:add':
entry.accessLevel.add = true;
Expand Down

This file was deleted.

2 changes: 0 additions & 2 deletions backend/src/entities/company-info/company-info.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { ConnectionPropertiesEntity } from '../connection-properties/connection-
import { CustomFieldsEntity } from '../custom-field/custom-fields.entity.js';
import { GroupEntity } from '../group/group.entity.js';
import { LogOutEntity } from '../log-out/log-out.entity.js';
import { PermissionEntity } from '../permission/permission.entity.js';
import { TableLogsEntity } from '../table-logs/table-logs.entity.js';
import { TableSettingsEntity } from '../table-settings/common-table-settings/table-settings.entity.js';
import { UserEntity } from '../user/user.entity.js';
Expand Down Expand Up @@ -50,7 +49,6 @@ import { VerifyInviteUserInCompanyAndConnectionGroupUseCase } from './use-cases/
ConnectionEntity,
UserEntity,
GroupEntity,
PermissionEntity,
TableSettingsEntity,
TableLogsEntity,
CustomFieldsEntity,
Expand Down
2 changes: 0 additions & 2 deletions backend/src/entities/connection/connection.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { ConnectionPropertiesEntity } from '../connection-properties/connection-
import { CustomFieldsEntity } from '../custom-field/custom-fields.entity.js';
import { GroupEntity } from '../group/group.entity.js';
import { LogOutEntity } from '../log-out/log-out.entity.js';
import { PermissionEntity } from '../permission/permission.entity.js';
import { TableLogsEntity } from '../table-logs/table-logs.entity.js';
import { TableSettingsEntity } from '../table-settings/common-table-settings/table-settings.entity.js';
import { UserEntity } from '../user/user.entity.js';
Expand Down Expand Up @@ -43,7 +42,6 @@ import { ValidateConnectionTokenUseCase } from './use-cases/validate-connection-
ConnectionEntity,
UserEntity,
GroupEntity,
PermissionEntity,
TableSettingsEntity,
TableLogsEntity,
CustomFieldsEntity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ export class CreateConnectionUseCase
savedConnection,
connectionAuthor,
);
await this._dbContext.permissionRepository.createdDefaultAdminPermissionsInGroup(createdAdminGroup);
createdAdminGroup.cedarPolicy = generateCedarPolicyForGroup(
savedConnection.id,
true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ import { isSaaS } from '../../../helpers/app/is-saas.js';
import { Constants } from '../../../helpers/constants/constants.js';
import { AmplitudeService } from '../../amplitude/amplitude.service.js';
import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js';
import { generateCedarPolicyForGroup } from '../../cedar-authorization/cedar-policy-generator.js';
import { GroupEntity } from '../../group/group.entity.js';
import { PermissionEntity } from '../../permission/permission.entity.js';
import { CreateUserDs } from '../../user/application/data-structures/create-user.ds.js';
import { FindUserDs } from '../../user/application/data-structures/find-user.ds.js';
import { UserRoleEnum } from '../../user/enums/user-role.enum.js';
import { buildConnectionEntitiesFromTestDtos } from '../../user/utils/build-connection-entities-from-test-dtos.js';
import { buildDefaultAdminGroups } from '../../user/utils/build-default-admin-groups.js';
import { buildDefaultAdminPermissions } from '../../user/utils/build-default-admin-permissions.js';
import { FoundConnectionsDs } from '../application/data-structures/found-connections.ds.js';
import { ConnectionEntity } from '../connection.entity.js';
import { buildFoundConnectionDs } from '../utils/build-found-connection.ds.js';
Expand Down Expand Up @@ -76,10 +75,16 @@ export class FindAllConnectionsUseCase
return await this._dbContext.groupRepository.saveNewOrUpdatedGroup(group);
}),
);
const testPermissionsEntities = buildDefaultAdminPermissions(createdTestGroups);
await Promise.all(
testPermissionsEntities.map(async (permission: PermissionEntity) => {
await this._dbContext.permissionRepository.saveNewOrUpdatedPermission(permission);
createdTestGroups.map(async (group: GroupEntity) => {
const connectionId = group.connection?.id;
if (!connectionId) return;
group.cedarPolicy = generateCedarPolicyForGroup(connectionId, group.isMain, {
connection: { connectionId, accessLevel: AccessLevelEnum.edit },
group: { groupId: group.id, accessLevel: AccessLevelEnum.edit },
tables: [],
});
await this._dbContext.groupRepository.saveNewOrUpdatedGroup(group);
}),
);
allFoundUserTestConnections.push(...createdTestConnections);
Expand Down
Loading
Loading