Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
731530e
poc - external decision delegation
KevLehman Mar 23, 2026
2ae612e
smol
KevLehman Mar 23, 2026
81e02c6
remove param
KevLehman Mar 23, 2026
22ac232
caching
KevLehman Mar 23, 2026
57e8220
enable cache setting
KevLehman Mar 23, 2026
bdce013
cron for continus check
KevLehman Mar 23, 2026
2bb48be
change how pdp is started
KevLehman Mar 24, 2026
fc7aaed
use tools
KevLehman Mar 24, 2026
81063f5
clearing up
KevLehman Mar 24, 2026
59a47db
fix
KevLehman Mar 24, 2026
1aca084
missing pkg
KevLehman Mar 24, 2026
394d2a0
argh
KevLehman Mar 24, 2026
ae3695f
sec
KevLehman Mar 25, 2026
129205c
virtru
KevLehman Mar 25, 2026
b2941fa
pdp
KevLehman Mar 25, 2026
1346c07
plain field
KevLehman Mar 25, 2026
9b84ddf
virtru
KevLehman Mar 25, 2026
138aab4
remove trailing slashes
KevLehman Mar 25, 2026
ae5c6c4
fix types
KevLehman Mar 26, 2026
c75b287
types again
KevLehman Mar 26, 2026
1ac6ca7
types & promise queue
KevLehman Mar 26, 2026
deb328f
availability
KevLehman Mar 26, 2026
92299c3
Revert "fix: Calendar status event status switch (#39491)"
KevLehman Mar 26, 2026
207897d
right type
KevLehman Mar 27, 2026
85c674e
thanks calendar
KevLehman Mar 27, 2026
baca975
ouch
KevLehman Mar 27, 2026
cdf6655
shame
KevLehman Mar 27, 2026
bc70f36
test: External PDP (#39913)
KevLehman Mar 30, 2026
7d82278
fix: Prevent attr update when external pdp is down (#39978)
KevLehman Mar 30, 2026
e339562
chore: Detailed connection checks for Virtru PDP (#40013)
KevLehman Apr 1, 2026
e8a0abb
test: ExternalPDP (2) (#40055)
KevLehman Apr 7, 2026
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
1 change: 1 addition & 0 deletions .github/workflows/ci-test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ jobs:
env:
ENTERPRISE_LICENSE: ${{ inputs.enterprise-license }}
TRANSPORTER: ${{ inputs.transporter }}
COMPOSE_PROFILES: ${{ inputs.type == 'api' && 'api' || '' }}
run: |
DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml up -d --wait

Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,51 @@
import { Box, Callout, Margins } from '@rocket.chat/fuselage';
import { Trans } from 'react-i18next';
import { Accordion, AccordionItem, Box, Callout, Margins } from '@rocket.chat/fuselage';
import { useSetting } from '@rocket.chat/ui-contexts';
import { useTranslation, Trans } from 'react-i18next';

import AbacEnabledToggle from './AbacEnabledToggle';
import SettingField from './SettingField';
import { useHasLicenseModule } from '../../../../hooks/useHasLicenseModule';
import { links } from '../../../../lib/links';

const SettingsPage = () => {
const { t } = useTranslation();
const { data: hasABAC = false } = useHasLicenseModule('abac');
const pdpType = useSetting('ABAC_PDP_Type', 'local');

return (
<Box maxWidth='x600' w='full' alignSelf='center'>
<Box maxWidth='x600' w='full' alignSelf='center' overflow='auto'>
<Box>
<Margins block={24}>
<AbacEnabledToggle hasABAC={hasABAC} />
<SettingField settingId='ABAC_PDP_Type' />
<SettingField settingId='ABAC_ShowAttributesInRooms' />
<SettingField settingId='Abac_Cache_Decision_Time_Seconds' />

<Callout>
<Trans i18nKey='ABAC_Enabled_callout'>
User attributes are synchronized via LDAP
<a href={links.go.abacLDAPDocs} rel='noopener noreferrer' target='_blank'>
Learn more
</a>
</Trans>
</Callout>
<Accordion>
<AccordionItem title={t('ABAC_Virtru_PDP_Configuration')}>
<Margins block={16}>
<SettingField settingId='ABAC_Virtru_Base_URL' />
<SettingField settingId='ABAC_Virtru_Client_ID' />
<SettingField settingId='ABAC_Virtru_Client_Secret' />
<SettingField settingId='ABAC_Virtru_OIDC_Endpoint' />
<SettingField settingId='ABAC_Virtru_Default_Entity_Key' />
<SettingField settingId='ABAC_Virtru_Attribute_Namespace' />
<SettingField settingId='ABAC_Virtru_Sync_Interval' />
<SettingField settingId='ABAC_Virtru_Test_Connection' />
</Margins>
</AccordionItem>
</Accordion>

{pdpType === 'local' && (
<Callout>
<Trans i18nKey='ABAC_Enabled_callout'>
User attributes are synchronized via LDAP
<a href={links.go.abacLDAPDocs} rel='noopener noreferrer' target='_blank'>
Learn more
</a>
</Trans>
</Callout>
)}
</Margins>
</Box>
</Box>
Expand Down
27 changes: 27 additions & 0 deletions apps/meteor/ee/server/api/abac/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
GETAbacRoomsResponseValidator,
GETAbacAuditEventsQuerySchema,
GETAbacAuditEventsResponseSchema,
GETAbacPdpHealthResponseSchema,
GETAbacPdpHealthErrorResponseSchema,
} from './schemas';
import { API } from '../../../../app/api/server';
import type { ExtractRoutesFromAPI } from '../../../../app/api/server/ApiClass';
Expand Down Expand Up @@ -357,6 +359,31 @@ const abacEndpoints = API.v1
return API.v1.success(result);
},
)
.get(
'abac/pdp/health',
{
authRequired: true,
permissionsRequired: ['abac-management'],
rateLimiterOptions: {
numRequestsAllowed: 5,
intervalTimeInMS: 60000,
},
response: {
200: GETAbacPdpHealthResponseSchema,
400: GETAbacPdpHealthErrorResponseSchema,
401: validateUnauthorizedErrorResponse,
403: validateUnauthorizedErrorResponse,
},
},
async function action() {
try {
await Abac.getPDPHealth();
return API.v1.success({ available: true, message: 'ABAC_PDP_Health_OK' });
} catch (err) {
return API.v1.failure({ available: false, message: (err as Error).message });
}
},
)
.get(
'abac/audit',
{
Expand Down
25 changes: 25 additions & 0 deletions apps/meteor/ee/server/api/abac/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,31 @@ export const POSTAbacUsersSyncBodySchema = ajv.compile<{

export const GenericErrorSchema = ajv.compile<{ success: boolean; message: string }>(GenericError);

export const GETAbacPdpHealthResponseSchema = ajv.compile<{
available: boolean;
message: string;
}>({
type: 'object',
properties: {
success: { type: 'boolean', enum: [true] },
available: { type: 'boolean' },
message: { type: 'string' },
},
required: ['available', 'message'],
additionalProperties: false,
});

export const GETAbacPdpHealthErrorResponseSchema = ajv.compile<{ available: boolean; message: string }>({
type: 'object',
properties: {
success: { type: 'boolean', enum: [false] },
available: { type: 'boolean', enum: [false] },
message: { type: 'string' },
},
required: ['available', 'message'],
additionalProperties: false,
});

const GETAbacRoomsListQuerySchema = {
type: 'object',
properties: {
Expand Down
34 changes: 32 additions & 2 deletions apps/meteor/ee/server/configuration/abac.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { Abac } from '@rocket.chat/core-services';
import { cronJobs } from '@rocket.chat/cron';
import { License } from '@rocket.chat/license';
import { Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';

import { settings } from '../../../app/settings/server';
import { LDAPEE } from '../sdk';

const VIRTRU_PDP_SYNC_JOB = 'ABAC_Virtru_PDP_Sync';

Meteor.startup(async () => {
let stopWatcher: () => void;
let stopCronWatcher: () => void;

License.onToggledFeature('abac', {
up: async () => {
const { addSettings } = await import('../settings/abac');
Expand All @@ -18,13 +24,37 @@ Meteor.startup(async () => {
await import('../hooks/abac');

stopWatcher = settings.watch('ABAC_Enabled', async (value) => {
if (value) {
if (value && settings.get<string>('ABAC_PDP_Type') !== 'virtru') {
await LDAPEE.syncUsersAbacAttributes(Users.findLDAPUsers());
}
});

async function configureVirtruPdpSync(): Promise<void> {
if (await cronJobs.has(VIRTRU_PDP_SYNC_JOB)) {
await cronJobs.remove(VIRTRU_PDP_SYNC_JOB);
}

const abacEnabled = settings.get('ABAC_Enabled');
const pdpType = settings.get<string>('ABAC_PDP_Type');

if (!abacEnabled || pdpType !== 'virtru') {
return;
}

const cronValue = settings.get<string>('ABAC_Virtru_Sync_Interval');

await cronJobs.add(VIRTRU_PDP_SYNC_JOB, cronValue, () => Abac.evaluateRoomMembership());
}

stopCronWatcher = settings.watchMultiple(['ABAC_PDP_Type', 'ABAC_Virtru_Sync_Interval'], () => configureVirtruPdpSync());
},
down: () => {
down: async () => {
stopWatcher?.();
stopCronWatcher?.();

if (await cronJobs.has(VIRTRU_PDP_SYNC_JOB)) {
await cronJobs.remove(VIRTRU_PDP_SYNC_JOB);
}
},
});
});
10 changes: 8 additions & 2 deletions apps/meteor/ee/server/lib/ldap/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ export class LDAPEEManager extends LDAPManager {
!settings.get('LDAP_Enable') ||
!settings.get('LDAP_Background_Sync_ABAC_Attributes') ||
!License.hasModule('abac') ||
!settings.get('ABAC_Enabled')
!settings.get('ABAC_Enabled') ||
settings.get('ABAC_PDP_Type') === 'virtru'
) {
return;
}
Expand All @@ -129,7 +130,12 @@ export class LDAPEEManager extends LDAPManager {
}

public static async syncUsersAbacAttributes(users: FindCursor<IUser>): Promise<void> {
if (!settings.get('LDAP_Enable') || !License.hasModule('abac') || !settings.get('ABAC_Enabled')) {
if (
!settings.get('LDAP_Enable') ||
!License.hasModule('abac') ||
!settings.get('ABAC_Enabled') ||
settings.get('ABAC_PDP_Type') === 'virtru'
) {
return;
}

Expand Down
88 changes: 86 additions & 2 deletions apps/meteor/ee/server/settings/abac.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { settingsRegistry } from '../../../app/settings/server';

const abacEnabledQuery = { _id: 'ABAC_Enabled', value: true };
const virtruPdpQuery = [abacEnabledQuery, { _id: 'ABAC_PDP_Type', value: 'virtru' }];

export function addSettings(): Promise<void> {
return settingsRegistry.addGroup('General', async function () {
await this.with(
Expand All @@ -15,20 +18,101 @@ export function addSettings(): Promise<void> {
section: 'ABAC',
i18nDescription: 'ABAC_Enabled_Description',
});
await this.add('ABAC_PDP_Type', 'local', {
type: 'select',
public: true,
section: 'ABAC',
invalidValue: 'local',
values: [
{ key: 'local', i18nLabel: 'ABAC_PDP_Type_Local' },
{ key: 'virtru', i18nLabel: 'ABAC_PDP_Type_Virtru' },
],
enableQuery: abacEnabledQuery,
});
await this.add('ABAC_ShowAttributesInRooms', false, {
type: 'boolean',
public: true,
invalidValue: false,
section: 'ABAC',
enableQuery: { _id: 'ABAC_Enabled', value: true },
enableQuery: abacEnabledQuery,
});
await this.add('Abac_Cache_Decision_Time_Seconds', 300, {
type: 'int',
public: true,
section: 'ABAC',
invalidValue: 0,
enableQuery: { _id: 'ABAC_Enabled', value: true },
enableQuery: [abacEnabledQuery],
});

// Virtru PDP Configuration
await this.add('ABAC_Virtru_Base_URL', '', {
type: 'string',
public: false,
invalidValue: '',
section: 'ABAC_Virtru_PDP_Configuration',
enableQuery: virtruPdpQuery,
});
await this.add('ABAC_Virtru_Client_ID', '', {
type: 'string',
public: false,
invalidValue: '',
section: 'ABAC_Virtru_PDP_Configuration',
enableQuery: virtruPdpQuery,
});
await this.add('ABAC_Virtru_Client_Secret', '', {
type: 'password',
public: false,
invalidValue: '',
section: 'ABAC_Virtru_PDP_Configuration',
enableQuery: virtruPdpQuery,
});
await this.add('ABAC_Virtru_OIDC_Endpoint', '', {
type: 'string',
public: false,
invalidValue: '',
section: 'ABAC_Virtru_PDP_Configuration',
i18nDescription: 'ABAC_Virtru_OIDC_Endpoint_Description',
enableQuery: virtruPdpQuery,
});
await this.add('ABAC_Virtru_Default_Entity_Key', 'emailAddress', {
type: 'select',
public: false,
invalidValue: 'emailAddress',
section: 'ABAC_Virtru_PDP_Configuration',
i18nDescription: 'ABAC_Virtru_Default_Entity_Key_Description',
values: [
{ key: 'emailAddress', i18nLabel: 'ABAC_Virtru_Entity_Key_Email' },
{ key: 'oidcIdentifier', i18nLabel: 'ABAC_Virtru_Entity_Key_OIDC' },
],
enableQuery: virtruPdpQuery,
});
await this.add('ABAC_Virtru_Attribute_Namespace', 'example.com', {
type: 'string',
public: false,
invalidValue: 'example.com',
section: 'ABAC_Virtru_PDP_Configuration',
i18nDescription: 'ABAC_Virtru_Attribute_Namespace_Description',
enableQuery: virtruPdpQuery,
});
await this.add('ABAC_Virtru_Sync_Interval', '*/5 * * * *', {
type: 'string',
public: false,
invalidValue: '*/5 * * * *',
section: 'ABAC_Virtru_PDP_Configuration',
i18nDescription: 'ABAC_Virtru_Sync_Interval_Description',
enableQuery: virtruPdpQuery,
});
await this.add(
'ABAC_Virtru_Test_Connection',
{ method: 'GET', path: '/v1/abac/pdp/health' },
{
type: 'action',
actionText: 'ABAC_Virtru_Test_Connection_Action',
invalidValue: '',
section: 'ABAC_Virtru_PDP_Configuration',
enableQuery: virtruPdpQuery,
},
);
},
);
});
Expand Down
Loading
Loading