🪪 fix: Filter ACL Principal Details (#13524)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions

* fix: filter ACL principal details

* test: type ACL permission pipeline assertions

* test: add ACL permissions e2e coverage
This commit is contained in:
Danny Avila 2026-06-05 19:06:41 -04:00 • committed by GitHub
parent da5876331e
commit c374d08b64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 694 additions and 13 deletions

View file

@ -1,12 +1,16 @@
const mongoose = require('mongoose');
const mockLogger = { error: jest.fn(), warn: jest.fn(), info: jest.fn(), debug: jest.fn() };
const mockGetTenantId = jest.fn();
jest.mock('@librechat/data-schemas', () => ({
logger: mockLogger,
getTenantId: mockGetTenantId,
SYSTEM_TENANT_ID: '__SYSTEM__',
}));
const { ResourceType, PrincipalType } = jest.requireActual('librechat-data-provider');
const { AccessRoleIds, ResourceType, PrincipalType } =
jest.requireActual('librechat-data-provider');
jest.mock('librechat-data-provider', () => ({
...jest.requireActual('librechat-data-provider'),
@ -32,6 +36,7 @@ jest.mock('~/server/services/PermissionService', () => ({
const mockRemoveAgentFromUserFavorites = jest.fn();
jest.mock('~/models', () => ({
aggregateAclEntries: jest.fn(),
searchPrincipals: jest.fn(),
sortPrincipalsByRelevance: jest.fn(),
calculateRelevanceScore: jest.fn(),
@ -44,7 +49,11 @@ jest.mock('~/server/services/GraphApiService', () => ({
}));
const db = require('~/models');
const { updateResourcePermissions, searchPrincipals } = require('../PermissionsController');
const {
updateResourcePermissions,
searchPrincipals,
getResourcePermissions,
} = require('../PermissionsController');
const createMockReq = (overrides = {}) => ({
params: { resourceType: ResourceType.AGENT, resourceId: '507f1f77bcf86cd799439011' },
@ -66,6 +75,7 @@ const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
describe('PermissionsController', () => {
beforeEach(() => {
jest.clearAllMocks();
mockGetTenantId.mockReturnValue(undefined);
});
describe('searchPrincipals', () => {
@ -139,6 +149,108 @@ describe('PermissionsController', () => {
});
});
describe('getResourcePermissions — principal details', () => {
const currentTenantId = 'tenant-a';
const otherTenantId = 'tenant-b';
const userId = new mongoose.Types.ObjectId();
const groupId = new mongoose.Types.ObjectId();
it('omits joined user and group details outside the current request context', async () => {
mockGetTenantId.mockReturnValue(currentTenantId);
db.aggregateAclEntries.mockResolvedValue([
{
principalType: PrincipalType.USER,
accessRoleId: AccessRoleIds.AGENT_VIEWER,
userInfo: {
_id: userId,
tenantId: otherTenantId,
name: 'Outside User',
email: 'outside-user@example.com',
avatar: 'outside-user.png',
},
},
{
principalType: PrincipalType.GROUP,
accessRoleId: AccessRoleIds.AGENT_VIEWER,
groupInfo: {
_id: groupId,
tenantId: otherTenantId,
name: 'Outside Group',
email: 'outside-group@example.com',
avatar: 'outside-group.png',
},
},
{
principalType: PrincipalType.PUBLIC,
accessRoleId: AccessRoleIds.AGENT_VIEWER,
},
]);
const req = createMockReq();
const res = createMockRes();
await getResourcePermissions(req, res);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
resourceType: ResourceType.AGENT,
resourceId: req.params.resourceId,
principals: [],
public: true,
publicAccessRoleId: AccessRoleIds.AGENT_VIEWER,
});
expect(JSON.stringify(res.json.mock.calls[0][0])).not.toContain('outside-user@example.com');
expect(JSON.stringify(res.json.mock.calls[0][0])).not.toContain('outside-group@example.com');
});
it('includes joined user and group details in the current request context', async () => {
mockGetTenantId.mockReturnValue(currentTenantId);
db.aggregateAclEntries.mockResolvedValue([
{
principalType: PrincipalType.USER,
accessRoleId: AccessRoleIds.AGENT_VIEWER,
userInfo: {
_id: userId,
tenantId: currentTenantId,
name: 'Current User',
email: 'current-user@example.com',
avatar: 'current-user.png',
},
},
{
principalType: PrincipalType.GROUP,
accessRoleId: AccessRoleIds.AGENT_VIEWER,
groupInfo: {
_id: groupId,
tenantId: currentTenantId,
name: 'Current Group',
email: 'current-group@example.com',
avatar: 'current-group.png',
},
},
]);
const req = createMockReq();
const res = createMockRes();
await getResourcePermissions(req, res);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json.mock.calls[0][0].principals).toEqual([
expect.objectContaining({
type: PrincipalType.USER,
id: userId.toString(),
email: 'current-user@example.com',
}),
expect.objectContaining({
type: PrincipalType.GROUP,
id: groupId.toString(),
email: 'current-group@example.com',
}),
]);
});
});
describe('updateResourcePermissions — favorites cleanup', () => {
const agentObjectId = new mongoose.Types.ObjectId().toString();
const revokedUserId = new mongoose.Types.ObjectId().toString();