Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9085f89
poc - external decision delegation
KevLehman Mar 23, 2026
dfca7a1
smol
KevLehman Mar 23, 2026
79e7a0c
remove param
KevLehman Mar 23, 2026
9dc5d19
caching
KevLehman Mar 23, 2026
95f4f30
enable cache setting
KevLehman Mar 23, 2026
17448a9
cron for continus check
KevLehman Mar 23, 2026
da988e6
change how pdp is started
KevLehman Mar 24, 2026
d7925ff
use tools
KevLehman Mar 24, 2026
f891a50
clearing up
KevLehman Mar 24, 2026
e1a169b
fix
KevLehman Mar 24, 2026
3635526
missing pkg
KevLehman Mar 24, 2026
7975ce4
argh
KevLehman Mar 24, 2026
212ab1d
sec
KevLehman Mar 25, 2026
91bc3b0
virtru
KevLehman Mar 25, 2026
c33351b
pdp
KevLehman Mar 25, 2026
419cfee
plain field
KevLehman Mar 25, 2026
d9e1845
virtru
KevLehman Mar 25, 2026
375aa55
remove trailing slashes
KevLehman Mar 25, 2026
0e49080
fix types
KevLehman Mar 26, 2026
dd21ef3
types again
KevLehman Mar 26, 2026
b390b19
types & promise queue
KevLehman Mar 26, 2026
e27da52
availability
KevLehman Mar 26, 2026
a84cb10
Revert "fix: Calendar status event status switch (#39491)"
KevLehman Mar 26, 2026
359d88f
right type
KevLehman Mar 27, 2026
73193dc
thanks calendar
KevLehman Mar 27, 2026
534e8c8
ouch
KevLehman Mar 27, 2026
aa9222e
shame
KevLehman Mar 27, 2026
ab77362
test: External PDP (#39913)
KevLehman Mar 30, 2026
819d1e7
fix: Prevent attr update when external pdp is down (#39978)
KevLehman Mar 30, 2026
ebb075b
chore: Detailed connection checks for Virtru PDP (#40013)
KevLehman Apr 1, 2026
858eec8
test: ExternalPDP (2) (#40055)
KevLehman Apr 7, 2026
7a2813a
Create tall-singers-roll.md
KevLehman Apr 7, 2026
1d2aba3
fix: Review fixes (#40065)
KevLehman Apr 7, 2026
902a547
fix: Revert caching for availability check
KevLehman Apr 7, 2026
58e281b
fix: Duplicated health checks
KevLehman Apr 7, 2026
6c5a4ba
fix accordionitem
KevLehman Apr 14, 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
5 changes: 5 additions & 0 deletions .changeset/red-windows-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Fixes calendar events modifying the wrong status property when attempting to sync `busy` status.
10 changes: 10 additions & 0 deletions .changeset/tall-singers-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/core-services": minor
"@rocket.chat/core-typings": minor
"@rocket.chat/i18n": minor
"@rocket.chat/authorization-service": minor
"@rocket.chat/abac": minor
---

Adds support for setting up Virtru as a PDP (Policy Decision Point) for ABAC.
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,4 +1,5 @@
import { Accordion, AccordionItem, Box, Callout, FieldGroup } from '@rocket.chat/fuselage';
import { useSetting } from '@rocket.chat/ui-contexts';
import { useTranslation, Trans } from 'react-i18next';

import AbacEnabledToggle from './AbacEnabledToggle';
Expand All @@ -9,13 +10,27 @@ 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' overflow='auto' mb={24}>
<FieldGroup>
<AbacEnabledToggle hasABAC={hasABAC} />
<SettingField settingId='ABAC_PDP_Type' />
<SettingField settingId='ABAC_ShowAttributesInRooms' />
<SettingField settingId='Abac_Cache_Decision_Time_Seconds' />

{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>
)}

<Accordion>
<AccordionItem title={t('LDAP_DataSync_ABAC')}>
<FieldGroup>
Expand All @@ -24,16 +39,18 @@ const SettingsPage = () => {
<SettingField settingId='LDAP_ABAC_AttributeMap' />
</FieldGroup>
</AccordionItem>
</Accordion>

<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>
<AccordionItem title={t('ABAC_Virtru_PDP_Configuration')}>
<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' />
</AccordionItem>
</Accordion>
</FieldGroup>
</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: ['success', '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: ['success', 'available', 'message'],
additionalProperties: false,
});

const GETAbacRoomsListQuerySchema = {
type: 'object',
properties: {
Expand Down
42 changes: 40 additions & 2 deletions apps/meteor/ee/server/configuration/abac.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
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 { isValidCron } from 'cron-validator';
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 +25,44 @@ 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');

if (!cronValue || !isValidCron(cronValue)) {
return;
}

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

stopCronWatcher = settings.watchMultiple(
['ABAC_Enabled', 'ABAC_PDP_Type', 'ABAC_Virtru_Sync_Interval'],
() => void 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