fix: honor agent rolePermissionConfig in record CRUD (#23248)

## Summary
- Agent tools were built with the agent’s `rolePermissionConfig`, but
record CRUD ignored it and re-resolved permissions from `authContext`
(app `defaultRoleId`)
- CRUD services now pass `rolePermissionConfig` through
`CommonApiContextBuilder` and the common query runner, so repository
access matches the agent role
- Workflow/chat paths already use the same role for auth and
`rolePermissionConfig`, so their behavior should be unchanged

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23248?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Abdul Rahman
2026-07-24 20:51:27 +05:30
committed by GitHub
parent 70e1e94e55
commit 7067f6ef88
18 changed files with 193 additions and 60 deletions
@@ -0,0 +1,83 @@
import { type ObjectsPermissionsByRoleId } from 'twenty-shared/types';
import { getObjectsPermissionsFromRolePermissionConfig } from 'src/engine/twenty-orm/utils/get-objects-permissions-from-role-permission-config.util';
const OBJECT_ID = 'object-1';
const agentRolePermissions = {
[OBJECT_ID]: {
canReadObjectRecords: true,
canUpdateObjectRecords: false,
canSoftDeleteObjectRecords: false,
canDestroyObjectRecords: false,
restrictedFields: {},
rowLevelPermissionPredicates: [],
rowLevelPermissionPredicateGroups: [],
},
};
const defaultRolePermissions = {
[OBJECT_ID]: {
canReadObjectRecords: false,
canUpdateObjectRecords: false,
canSoftDeleteObjectRecords: false,
canDestroyObjectRecords: false,
restrictedFields: {},
rowLevelPermissionPredicates: [],
rowLevelPermissionPredicateGroups: [],
},
};
const rolesPermissions: ObjectsPermissionsByRoleId = {
'agent-role-id': agentRolePermissions,
'default-role-id': defaultRolePermissions,
};
describe('getObjectsPermissionsFromRolePermissionConfig', () => {
it('should resolve a single union role', () => {
expect(
getObjectsPermissionsFromRolePermissionConfig({
rolesPermissions,
rolePermissionConfig: { unionOf: ['agent-role-id'] },
}),
).toEqual(agentRolePermissions);
});
it('should resolve a single intersection role', () => {
expect(
getObjectsPermissionsFromRolePermissionConfig({
rolesPermissions,
rolePermissionConfig: { intersectionOf: ['default-role-id'] },
}),
).toEqual(defaultRolePermissions);
});
it('should use the first role when multiple are provided', () => {
expect(
getObjectsPermissionsFromRolePermissionConfig({
rolesPermissions,
rolePermissionConfig: {
intersectionOf: ['agent-role-id', 'default-role-id'],
},
}),
).toEqual(agentRolePermissions);
});
it('should return empty permissions when bypassing checks', () => {
expect(
getObjectsPermissionsFromRolePermissionConfig({
rolesPermissions,
rolePermissionConfig: { shouldBypassPermissionChecks: true },
}),
).toEqual({});
});
it('should return empty permissions when the role is missing from the cache', () => {
expect(
getObjectsPermissionsFromRolePermissionConfig({
rolesPermissions,
rolePermissionConfig: { unionOf: ['missing-role-id'] },
}),
).toEqual({});
});
});
@@ -0,0 +1,33 @@
import {
type ObjectsPermissions,
type ObjectsPermissionsByRoleId,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
// Multi-role union/intersection is not ready — use the first assigned role only.
export const getObjectsPermissionsFromRolePermissionConfig = ({
rolesPermissions,
rolePermissionConfig,
}: {
rolesPermissions: ObjectsPermissionsByRoleId;
rolePermissionConfig: RolePermissionConfig;
}): ObjectsPermissions => {
if ('shouldBypassPermissionChecks' in rolePermissionConfig) {
return {};
}
const roleId =
'intersectionOf' in rolePermissionConfig
? rolePermissionConfig.intersectionOf[0]
: 'unionOf' in rolePermissionConfig
? rolePermissionConfig.unionOf[0]
: undefined;
if (!isDefined(roleId)) {
return {};
}
return rolesPermissions[roleId] ?? {};
};