Skip to content
Merged
6 changes: 6 additions & 0 deletions .changeset/orange-paws-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/i18n': patch
'@rocket.chat/meteor': patch
---

Deprecates `Anonymous write`. Feature will be removed in version 9.0.0.
5 changes: 5 additions & 0 deletions .changeset/tame-tables-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Fixes "Join" button on Outlook Calendar bubbling click event, also opening the calendar event details.
35 changes: 2 additions & 33 deletions apps/meteor/app/api/server/v1/custom-sounds.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { ICustomSound } from '@rocket.chat/core-typings';
import { CustomSounds } from '@rocket.chat/models';
import type { PaginatedRequest, PaginatedResult } from '@rocket.chat/rest-typings';
import type { PaginatedResult } from '@rocket.chat/rest-typings';
import {
isCustomSoundsGetOneProps,
isCustomSoundsListProps,
ajv,
validateBadRequestErrorResponse,
validateNotFoundErrorResponse,
Expand All @@ -15,38 +16,6 @@ import type { ExtractRoutesFromAPI } from '../ApiClass';
import { API } from '../api';
import { getPaginationItems } from '../helpers/getPaginationItems';

type CustomSoundsList = PaginatedRequest<{ name?: string }>;

const CustomSoundsListSchema = {
type: 'object',
properties: {
count: {
type: 'number',
nullable: true,
},
offset: {
type: 'number',
nullable: true,
},
sort: {
type: 'string',
nullable: true,
},
name: {
type: 'string',
nullable: true,
},
query: {
type: 'string',
nullable: true,
},
},
required: [],
additionalProperties: false,
};

export const isCustomSoundsListProps = ajv.compile<CustomSoundsList>(CustomSoundsListSchema);

const customSoundsEndpoints = API.v1
.get(
'custom-sounds.list',
Expand Down
34 changes: 17 additions & 17 deletions apps/meteor/app/authorization/server/constant/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ export const permissions = [
{ _id: 'ban-user', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'bulk-register-user', roles: ['admin'] },
{ _id: 'change-livechat-room-visitor', roles: ['admin', 'livechat-manager', 'livechat-agent'] },
{ _id: 'create-c', roles: ['admin', 'user', 'bot', 'app'] },
{ _id: 'create-d', roles: ['admin', 'user', 'bot', 'app'] },
{ _id: 'create-p', roles: ['admin', 'user', 'bot', 'app'] },
{ _id: 'create-c', roles: ['admin', 'user', 'federated-external', 'bot', 'app'] },
{ _id: 'create-d', roles: ['admin', 'user', 'federated-external', 'bot', 'app'] },
{ _id: 'create-p', roles: ['admin', 'user', 'federated-external', 'bot', 'app'] },
{ _id: 'create-personal-access-tokens', roles: ['admin', 'user'] },
{ _id: 'create-user', roles: ['admin'] },
{ _id: 'clean-channel-history', roles: ['admin'] },
{ _id: 'delete-c', roles: ['admin', 'owner'] },
{ _id: 'delete-d', roles: ['admin'] },
{ _id: 'delete-message', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'delete-own-message', roles: ['admin', 'user'] },
{ _id: 'delete-own-message', roles: ['admin', 'user', 'federated-external'] },
{ _id: 'delete-p', roles: ['admin', 'owner'] },
{ _id: 'delete-user', roles: ['admin'] },
{ _id: 'edit-message', roles: ['admin', 'owner', 'moderator'] },
Expand All @@ -44,8 +44,8 @@ export const permissions = [
{ _id: 'edit-room-retention-policy', roles: ['admin'] },
{ _id: 'force-delete-message', roles: ['admin', 'owner'] },
{ _id: 'join-without-join-code', roles: ['admin', 'bot', 'app'] },
{ _id: 'leave-c', roles: ['admin', 'user', 'bot', 'anonymous', 'app'] },
{ _id: 'leave-p', roles: ['admin', 'user', 'bot', 'anonymous', 'app'] },
{ _id: 'leave-c', roles: ['admin', 'user', 'federated-external', 'bot', 'anonymous', 'app'] },
{ _id: 'leave-p', roles: ['admin', 'user', 'federated-external', 'bot', 'anonymous', 'app'] },
{ _id: 'logout-other-user', roles: ['admin'] },
{ _id: 'manage-assets', roles: ['admin'] },
{ _id: 'manage-email-inbox', roles: ['admin'] },
Expand All @@ -57,8 +57,8 @@ export const permissions = [
{ _id: 'manage-own-incoming-integrations', roles: ['admin'] },
{ _id: 'manage-oauth-apps', roles: ['admin'] },
{ _id: 'manage-selected-settings', roles: ['admin'] },
{ _id: 'mention-all', roles: ['admin', 'owner', 'moderator', 'user'] },
{ _id: 'mention-here', roles: ['admin', 'owner', 'moderator', 'user'] },
{ _id: 'mention-all', roles: ['admin', 'owner', 'moderator', 'user', 'federated-external'] },
{ _id: 'mention-here', roles: ['admin', 'owner', 'moderator', 'user', 'federated-external'] },
{ _id: 'mute-user', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'remove-user', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'run-import', roles: ['admin'] },
Expand All @@ -67,26 +67,26 @@ export const permissions = [
{ _id: 'set-owner', roles: ['admin', 'owner'] },
{ _id: 'send-many-messages', roles: ['admin', 'bot', 'app'] },
{ _id: 'set-leader', roles: ['admin', 'owner'] },
{ _id: 'start-discussion', roles: ['admin', 'user', 'guest', 'app'] },
{ _id: 'start-discussion-other-user', roles: ['admin', 'user', 'owner', 'app'] },
{ _id: 'start-discussion', roles: ['admin', 'user', 'federated-external', 'guest', 'app'] },
{ _id: 'start-discussion-other-user', roles: ['admin', 'user', 'federated-external', 'owner', 'app'] },
{ _id: 'unarchive-room', roles: ['admin'] },
{ _id: 'view-c-room', roles: ['admin', 'user', 'bot', 'app', 'anonymous'] },
{ _id: 'view-c-room', roles: ['admin', 'user', 'federated-external', 'bot', 'app', 'anonymous'] },
{ _id: 'user-generate-access-token', roles: ['admin'] },
{ _id: 'view-d-room', roles: ['admin', 'user', 'bot', 'app', 'guest'] },
{ _id: 'view-d-room', roles: ['admin', 'user', 'federated-external', 'bot', 'app', 'guest'] },
{ _id: 'view-device-management', roles: ['admin'] },
{ _id: 'view-engagement-dashboard', roles: ['admin'] },
{ _id: 'view-full-other-user-info', roles: ['admin'] },
{ _id: 'view-joined-room', roles: ['guest', 'bot', 'app', 'anonymous'] },
{ _id: 'view-join-code', roles: ['admin'] },
{ _id: 'view-logs', roles: ['admin'] },
{ _id: 'view-other-user-channels', roles: ['admin'] },
{ _id: 'view-p-room', roles: ['admin', 'user', 'anonymous', 'guest'] },
{ _id: 'view-p-room', roles: ['admin', 'user', 'federated-external', 'anonymous', 'guest'] },
{ _id: 'view-privileged-setting', roles: ['admin'] },
{ _id: 'view-room-administration', roles: ['admin'] },
{ _id: 'view-statistics', roles: ['admin'] },
{ _id: 'view-user-administration', roles: ['admin'] },
{ _id: 'preview-c-room', roles: ['admin', 'user', 'anonymous'] },
{ _id: 'view-outside-room', roles: ['admin', 'owner', 'moderator', 'user'] },
{ _id: 'preview-c-room', roles: ['admin', 'user', 'federated-external', 'anonymous'] },
{ _id: 'view-outside-room', roles: ['admin', 'owner', 'moderator', 'user', 'federated-external'] },
{ _id: 'view-broadcast-member-list', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'call-management', roles: ['admin', 'owner', 'moderator', 'user'] },
{ _id: 'create-invite-links', roles: ['admin', 'owner', 'moderator'] },
Expand Down Expand Up @@ -220,10 +220,10 @@ export const permissions = [
{ _id: 'manage-sounds', roles: ['admin'] },
{ _id: 'access-mailer', roles: ['admin'] },
{ _id: 'pin-message', roles: ['owner', 'moderator', 'admin'] },
{ _id: 'mobile-upload-file', roles: ['user', 'admin'] },
{ _id: 'mobile-upload-file', roles: ['user', 'federated-external', 'admin'] },
{ _id: 'send-mail', roles: ['admin'] },
{ _id: 'view-federation-data', roles: ['admin'] },
{ _id: 'access-federation', roles: ['admin', 'user'] },
{ _id: 'access-federation', roles: ['admin', 'user', 'federated-external'] },
{ _id: 'add-all-to-room', roles: ['admin'] },
{ _id: 'get-server-info', roles: ['admin'] },
{ _id: 'register-on-cloud', roles: ['admin'] },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const upsertPermissions = async (): Promise<void> => {
{ name: 'leader', scope: 'Subscriptions', description: 'Leader' },
{ name: 'owner', scope: 'Subscriptions', description: 'Owner' },
{ name: 'user', scope: 'Users', description: '' },
{ name: 'federated-external', scope: 'Users', description: 'External Federated User' },
{ name: 'bot', scope: 'Users', description: '' },
{ name: 'app', scope: 'Users', description: '' },
{ name: 'guest', scope: 'Users', description: '' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,9 @@ const CreateChannelModal = ({ teamId = '', mainRoom, onClose, reload }: CreateCh

dispatchToastMessage({ type: 'success', message: t('Room_has_been_created') });
reload?.();
onClose();
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
} finally {
onClose();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,9 @@ const CreateTeamModal = ({ onClose }: CreateTeamModalProps) => {
const { team } = await createTeamAction(params);
dispatchToastMessage({ type: 'success', message: t('Team_has_been_created') });
goToRoomById(team.roomId);
onClose();
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
} finally {
onClose();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useSetModal } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';

import { useFormatDateAndTime } from '../../../hooks/useFormatDateAndTime';
import { usePreventPropagation } from '../../../hooks/usePreventPropagation';
import OutlookCalendarEventModal from '../OutlookCalendarEventModal';
import { useOutlookOpenCall } from '../hooks/useOutlookOpenCall';

Expand All @@ -15,7 +16,7 @@ const OutlookEventItem = ({ subject, description, startTime, meetingUrl }: Outlo
const setModal = useSetModal();
const formatDateAndTime = useFormatDateAndTime();
const openCall = useOutlookOpenCall(meetingUrl);

const handleMeetingClick = usePreventPropagation(openCall);
const hovered = css`
&:hover {
cursor: pointer;
Expand Down Expand Up @@ -57,7 +58,7 @@ const OutlookEventItem = ({ subject, description, startTime, meetingUrl }: Outlo
</Box>
<Box>
{meetingUrl && (
<Button onClick={openCall} small>
<Button onClick={handleMeetingClick} small>
{t('Join')}
</Button>
)}
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/public/fonts/InterVariable.woff2
1 change: 1 addition & 0 deletions apps/meteor/server/settings/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export const createAccountSettings = () =>
await this.add('Accounts_AllowAnonymousWrite', false, {
type: 'boolean',
public: true,
alert: 'Accounts_AllowAnonymousWrite_Deprecation_Alert',
enableQuery: {
_id: 'Accounts_AllowAnonymousRead',
value: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { UserStatus } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';

import { createOrUpdateFederatedUser } from './createOrUpdateFederatedUser';

jest.mock('@rocket.chat/models', () => ({
Users: {
findOneAndUpdate: jest.fn(),
},
}));

const mockFindOneAndUpdate = Users.findOneAndUpdate as jest.MockedFunction<typeof Users.findOneAndUpdate>;

describe('createOrUpdateFederatedUser', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should assign the "federated" role to the new user', async () => {
const fakeUser = { _id: 'user123', username: '@alice:example.com' };
mockFindOneAndUpdate.mockResolvedValueOnce(fakeUser as any);

await createOrUpdateFederatedUser({ username: '@alice:example.com', origin: 'example.com' });

expect(mockFindOneAndUpdate).toHaveBeenCalledTimes(1);
const [, updateDoc] = mockFindOneAndUpdate.mock.calls[0];
expect((updateDoc as any).$set.roles).toEqual(['federated-external']);
});

it('should not assign the "user" role to federated users', async () => {
const fakeUser = { _id: 'user123', username: '@alice:example.com' };
mockFindOneAndUpdate.mockResolvedValueOnce(fakeUser as any);

await createOrUpdateFederatedUser({ username: '@alice:example.com', origin: 'example.com' });

const [, updateDoc] = mockFindOneAndUpdate.mock.calls[0];
expect((updateDoc as any).$set.roles).not.toContain('user');
});

it('should set federated=true on the created/updated user', async () => {
const fakeUser = { _id: 'user123', username: '@alice:example.com' };
mockFindOneAndUpdate.mockResolvedValueOnce(fakeUser as any);

await createOrUpdateFederatedUser({ username: '@alice:example.com', origin: 'example.com' });

const [, updateDoc] = mockFindOneAndUpdate.mock.calls[0];
expect((updateDoc as any).$set.federated).toBe(true);
});

it('should use the provided name when supplied', async () => {
const fakeUser = { _id: 'user123', username: '@alice:example.com' };
mockFindOneAndUpdate.mockResolvedValueOnce(fakeUser as any);

await createOrUpdateFederatedUser({ username: '@alice:example.com', name: 'Alice', origin: 'example.com' });

const [, updateDoc] = mockFindOneAndUpdate.mock.calls[0];
expect((updateDoc as any).$set.name).toBe('Alice');
});

it('should default name to username when name is not provided', async () => {
const fakeUser = { _id: 'user123', username: '@alice:example.com' };
mockFindOneAndUpdate.mockResolvedValueOnce(fakeUser as any);

await createOrUpdateFederatedUser({ username: '@alice:example.com', origin: 'example.com' });

const [, updateDoc] = mockFindOneAndUpdate.mock.calls[0];
expect((updateDoc as any).$set.name).toBe('@alice:example.com');
});

it('should set initial status to OFFLINE', async () => {
const fakeUser = { _id: 'user123', username: '@alice:example.com' };
mockFindOneAndUpdate.mockResolvedValueOnce(fakeUser as any);

await createOrUpdateFederatedUser({ username: '@alice:example.com', origin: 'example.com' });

const [, updateDoc] = mockFindOneAndUpdate.mock.calls[0];
expect((updateDoc as any).$set.status).toBe(UserStatus.OFFLINE);
});

it('should store the origin server in the federation object', async () => {
const fakeUser = { _id: 'user123', username: '@alice:example.com' };
mockFindOneAndUpdate.mockResolvedValueOnce(fakeUser as any);

await createOrUpdateFederatedUser({ username: '@alice:example.com', origin: 'example.com' });

const [, updateDoc] = mockFindOneAndUpdate.mock.calls[0];
expect((updateDoc as any).$set.federation.origin).toBe('example.com');
});

it('should use upsert so the user is created if not found', async () => {
const fakeUser = { _id: 'user123', username: '@alice:example.com' };
mockFindOneAndUpdate.mockResolvedValueOnce(fakeUser as any);

await createOrUpdateFederatedUser({ username: '@alice:example.com', origin: 'example.com' });

const [, , options] = mockFindOneAndUpdate.mock.calls[0];
expect((options as any).upsert).toBe(true);
});

it('should throw when findOneAndUpdate returns null', async () => {
mockFindOneAndUpdate.mockResolvedValueOnce(null as any);

await expect(createOrUpdateFederatedUser({ username: '@alice:example.com', origin: 'example.com' })).rejects.toThrow(
'Failed to create or update federated user: @alice:example.com',
);
});

it('should return the user returned by findOneAndUpdate', async () => {
const fakeUser = { _id: 'user123', username: '@alice:example.com' };
mockFindOneAndUpdate.mockResolvedValueOnce(fakeUser as any);

const result = await createOrUpdateFederatedUser({ username: '@alice:example.com', origin: 'example.com' });

expect(result).toBe(fakeUser);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import { Users } from '@rocket.chat/models';
export async function createOrUpdateFederatedUser(options: { username: string; name?: string; origin: string }): Promise<IUser> {
const { username, name = username, origin } = options;

console.log('createOrUpdateFederatedUser ->', options);

// TODO: Have a specific method to handle this upsert
const user = await Users.findOneAndUpdate(
{
Expand All @@ -25,7 +23,7 @@ export async function createOrUpdateFederatedUser(options: { username: string; n
type: 'user' as const,
status: UserStatus.OFFLINE,
active: true,
roles: ['user'],
roles: ['federated-external'],
requirePasswordChange: false,
federated: true,
federation: {
Expand Down
4 changes: 4 additions & 0 deletions ee/packages/media-calls/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { CallFeature } from '@rocket.chat/media-signaling';

export const DEFAULT_CALL_FEATURES: CallFeature[] = ['audio'];
export const SIP_CALL_FEATURES: CallFeature[] = ['audio', 'transfer', 'hold'];
2 changes: 1 addition & 1 deletion ee/packages/media-calls/src/definition/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type InternalCallParams = {
requestedService?: CallService;
parentCallId?: string;
requestedBy?: MediaCallSignedContact;
features?: CallFeature[];
features: CallFeature[];
};

export type MediaCallHeader = AtLeast<IMediaCall, '_id' | 'caller' | 'callee'>;
Expand Down
8 changes: 4 additions & 4 deletions ee/packages/media-calls/src/internal/SignalProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
} from '@rocket.chat/media-signaling';
import { MediaCalls } from '@rocket.chat/models';

import { DEFAULT_CALL_FEATURES } from '../constants';
import type { InternalCallParams } from '../definition/common';
import { logger } from '../logger';
import { mediaCallDirector } from '../server/CallDirector';
Expand Down Expand Up @@ -137,9 +138,8 @@ export class GlobalSignalProcessor {

// If this user's side of the call has already been signed
if (actor.contractId) {
// If it's signed to the same session that is now registering
// Or it was signed by a session that the current session is replacing (as in a browser refresh)
if (actor.contractId === signal.contractId || actor.contractId === signal.oldContractId) {
// If it was signed by a session that the current session is replacing (as in a browser refresh)
if (actor.contractId === signal.oldContractId) {
logger.info({ msg: 'Server detected a client refresh for a session with an active call.', callId: call._id });
await mediaCallDirector.hangupDetachedCall(call, { endedBy: { ...actor, contractId: signal.contractId }, reason: 'unknown' });
return;
Expand Down Expand Up @@ -181,7 +181,7 @@ export class GlobalSignalProcessor {

const services = signal.supportedServices ?? [];
const requestedService = services.includes('webrtc') ? 'webrtc' : services[0];
const features = signal.supportedFeatures ?? ['audio'];
const features = signal.supportedFeatures ?? DEFAULT_CALL_FEATURES;

const params: InternalCallParams = {
caller: {
Expand Down
Loading
Loading