feat(federation): add access-federation permission (#37377)

This commit is contained in:
Ricardo Garim 2025-11-07 13:26:55 -03:00 committed by GitHub
parent cd0f72faa5
commit 22c6e0b907
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 44 additions and 5 deletions

View File

@ -244,6 +244,7 @@ export const permissions = [
{ _id: 'mobile-upload-file', roles: ['user', 'admin'] },
{ _id: 'send-mail', roles: ['admin'] },
{ _id: 'view-federation-data', roles: ['admin'] },
{ _id: 'access-federation', roles: ['admin', 'user'] },
{ _id: 'add-all-to-room', roles: ['admin'] },
{ _id: 'get-server-info', roles: ['admin'] },
{ _id: 'register-on-cloud', roles: ['admin'] },

View File

@ -3,6 +3,7 @@ import { AppsEngineException } from '@rocket.chat/apps-engine/definition/excepti
import { Message, Team } from '@rocket.chat/core-services';
import type { ICreateRoomParams, ISubscriptionExtraData } from '@rocket.chat/core-services';
import type { ICreatedRoom, IUser, IRoom, RoomType } from '@rocket.chat/core-typings';
import { isRoomNativeFederated } from '@rocket.chat/core-typings';
import { Rooms, Subscriptions, Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
@ -13,6 +14,7 @@ import { beforeCreateRoomCallback, prepareCreateRoomCallback } from '../../../..
import { calculateRoomRolePriorityFromRoles } from '../../../../lib/roles/calculateRoomRolePriorityFromRoles';
import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig';
import { syncRoomRolePriorityForUserAndRoom } from '../../../../server/lib/roles/syncRoomRolePriority';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref';
import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName';
import { notifyOnRoomChanged, notifyOnSubscriptionChangedById } from '../lib/notifyListener';
@ -162,6 +164,13 @@ export const createRoom = async <T extends RoomType>(
// options,
});
const shouldBeHandledByFederation = isRoomNativeFederated(extraData);
if (shouldBeHandledByFederation && owner && !(await hasPermissionAsync(owner._id, 'access-federation'))) {
throw new Meteor.Error('error-not-authorized-federation', 'Not authorized to access federation', {
method: 'createRoom',
});
}
if (type === 'd') {
return createDirectRoom(members as IUser[], extraData, { ...options, creator: options?.creator || owner?.username });
}
@ -255,8 +264,6 @@ export const createRoom = async <T extends RoomType>(
Object.assign(roomProps, eventResult);
}
const shouldBeHandledByFederation = roomProps.federated === true || owner.username.includes(':');
await beforeCreateRoomCallback.run({
owner,
room: roomProps,

View File

@ -1,4 +1,4 @@
import { FederationMatrix } from '@rocket.chat/core-services';
import { FederationMatrix, Authorization, MeteorError } from '@rocket.chat/core-services';
import { isEditedMessage, type IMessage, type IRoom, type IUser } from '@rocket.chat/core-typings';
import { Rooms } from '@rocket.chat/models';
@ -83,6 +83,9 @@ beforeAddUserToRoom.add(
}
if (FederationActions.shouldPerformFederationAction(room)) {
if (!(await Authorization.hasPermission(user._id, 'access-federation'))) {
throw new MeteorError('error-not-authorized-federation', 'Not authorized to access federation');
}
await FederationMatrix.inviteUsersToRoom(room, [user.username], inviter);
}
},

View File

@ -114,6 +114,10 @@ export class RoomService extends ServiceClassInternal implements IRoomService {
throw new MeteorError('error-not-allowed', 'Not allowed', { method: 'joinRoom' });
}
if (FederationActions.shouldPerformFederationAction(room) && !(await Authorization.hasPermission(user._id, 'access-federation'))) {
throw new MeteorError('error-not-authorized-federation', 'Not authorized to access federation', { method: 'joinRoom' });
}
if (isRoomWithJoinCode(room) && !(await Authorization.hasPermission(user._id, 'join-without-join-code'))) {
if (!joinCode) {
throw new MeteorError('error-code-required', 'Code required', { method: 'joinRoom' });

View File

@ -1,4 +1,4 @@
import { Room } from '@rocket.chat/core-services';
import { Authorization, Room } from '@rocket.chat/core-services';
import { isUserNativeFederated, type IUser } from '@rocket.chat/core-typings';
import type { PduMembershipEventContent, PersistentEventBase, RoomVersion } from '@rocket.chat/federation-sdk';
import { eventIdSchema, roomIdSchema, NotAllowedError, federationSDK } from '@rocket.chat/federation-sdk';
@ -10,6 +10,8 @@ import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv';
import { createOrUpdateFederatedUser, getUsernameServername } from '../../FederationMatrix';
import { isAuthenticatedMiddleware } from '../middlewares/isAuthenticated';
const logger = new Logger('federation-matrix:invite');
const EventBaseSchema = {
type: 'object',
properties: {
@ -139,8 +141,14 @@ async function runWithBackoff(fn: () => Promise<void>, delaySec = 5) {
try {
await fn();
} catch (e) {
// don't retry on authorization/validation errors - they won't succeed on retry
if (e instanceof NotAllowedError) {
logger.error('Authorization error, not retrying:', e);
return;
}
const delay = Math.min(625, delaySec ** 2);
console.error(`error occurred, retrying in ${delay}s`, e);
logger.error(`error occurred, retrying in ${delay}s`, e);
setTimeout(() => {
runWithBackoff(fn, delay);
}, delay * 1000);
@ -346,6 +354,19 @@ export const getMatrixInviteRoutes = () => {
throw new Error('user not found not processing invite');
}
// check federation permission before processing the invite
if (!(await Authorization.hasPermission(ourUser._id, 'access-federation'))) {
logger.info(`User ${userToCheck} denied federation access, rejecting invite to room ${roomId}`);
return {
body: {
errcode: 'M_FORBIDDEN',
error: 'User does not have permission to access federation',
},
statusCode: 403,
};
}
try {
const inviteEvent = await federationSDK.processInvite(
event,

View File

@ -5957,6 +5957,8 @@
"access-permissions_description": "Modify permissions for various roles.",
"access-setting-permissions": "Modify Setting-Based Permissions",
"access-setting-permissions_description": "Permission to modify setting-based permissions",
"access-federation": "Access Federation",
"access-federation_description": "Permission to access federation features, create and join federated rooms",
"active": "active",
"add-all-to-room": "Add all users to a room",
"add-all-to-room_description": "Permission to add all users to a room",
@ -6255,6 +6257,7 @@
"error-no-tokens-for-this-user": "There are no tokens for this user",
"error-not-allowed": "Not allowed",
"error-not-authorized": "Not authorized",
"error-not-authorized-federation": "Not authorized to access federation",
"error-office-hours-are-closed": "The office hours are closed.",
"error-password-in-history": "Entered password has been previously used",
"error-password-policy-not-met": "Password does not meet the server's policy",