Skip to content

Commit

Permalink
feat: Add option to disable end-to-end encryption to allow legacy cli…
Browse files Browse the repository at this point in the history
…ents.

Signed-off-by: Joachim Bauch <bauch@struktur.de>
  • Loading branch information
fancycode committed Dec 16, 2024
1 parent 22f2374 commit 4fb8481
Show file tree
Hide file tree
Showing 17 changed files with 251 additions and 4 deletions.
2 changes: 2 additions & 0 deletions appinfo/routes/routesRoomController.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@
['name' => 'Room#setLobby', 'url' => '/api/{apiVersion}/room/{token}/webinar/lobby', 'verb' => 'PUT', 'requirements' => $requirementsWithToken],
/** @see \OCA\Talk\Controller\RoomController::setSIPEnabled() */
['name' => 'Room#setSIPEnabled', 'url' => '/api/{apiVersion}/room/{token}/webinar/sip', 'verb' => 'PUT', 'requirements' => $requirementsWithToken],
/** @see \OCA\Talk\Controller\RoomController::setEncryptionEnabled() */
['name' => 'Room#setEncryptionEnabled', 'url' => '/api/{apiVersion}/room/{token}/webinar/encryption', 'verb' => 'PUT', 'requirements' => $requirementsWithToken],
/** @see \OCA\Talk\Controller\RoomController::setRecordingConsent() */
['name' => 'Room#setRecordingConsent', 'url' => '/api/{apiVersion}/room/{token}/recording-consent', 'verb' => 'PUT', 'requirements' => $requirementsWithToken],
/** @see \OCA\Talk\Controller\RoomController::setMessageExpiration() */
Expand Down
29 changes: 29 additions & 0 deletions lib/Controller/RoomController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2369,6 +2369,35 @@ public function setSIPEnabled(int $state): DataResponse {
return new DataResponse($this->formatRoom($this->room, $this->participant));
}

/**
* Update end-to-end encryption enabled state
*
* @param bool $state New state
* @psalm-param Webinary::SIP_* $state
* @return DataResponse<Http::STATUS_OK, TalkRoom, array{}>|DataResponse<Http::STATUS_UNAUTHORIZED|Http::STATUS_FORBIDDEN|Http::STATUS_PRECONDITION_FAILED, array{error: 'config'}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: 'breakout-room'|'token'|'type'|'value'}, array{}>
*
* 200: End-to-end encryption enabled state updated successfully
* 400: Updating end-to-end encryption enabled state is not possible
* 401: User not found
* 403: Missing permissions to update end-to-end encryption enabled state
*/
#[NoAdminRequired]
#[RequireModeratorParticipant]
public function setEncryptionEnabled(bool $enabled): DataResponse {
$user = $this->userManager->get($this->userId);
if (!$user instanceof IUser) {
return new DataResponse(['error' => 'config'], Http::STATUS_UNAUTHORIZED);
}

try {
$this->roomService->setEncryptionEnabled($this->room, $enabled);
} catch (SipConfigurationException $e) {
return new DataResponse(['error' => $e->getReason()], Http::STATUS_BAD_REQUEST);
}

return new DataResponse($this->formatRoom($this->room, $this->participant));
}

/**
* Set recording consent requirement for this conversation
*
Expand Down
1 change: 1 addition & 0 deletions lib/Controller/SignalingController.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ public function getSettings(string $token = ''): DataResponse {
'stunservers' => $stun,
'turnservers' => $turn,
'sipDialinInfo' => $this->talkConfig->isSIPConfigured() ? $this->talkConfig->getDialInInfo() : '',
'encrypted' => $room->getEncryptionEnabled(),
];

Check failure on line 223 in lib/Controller/SignalingController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

InvalidReturnStatement

lib/Controller/SignalingController.php:223:10: InvalidReturnStatement: The inferred type 'OCP\AppFramework\Http\DataResponse<200, array{encrypted: bool, federation: array{helloAuthParams: array{token: string}, nextcloudServer: string, roomId: string, server: string}|null, helloAuthParams: array{'1.0': array{ticket: string, userid: null|string}, '2.0': array{token: string}}, hideWarning: bool, server: string, signalingMode: string, sipDialinInfo: string, stunservers: list{array{urls: list<non-falsy-string>}}, ticket: string, turnservers: list<array{credential: string, urls: non-empty-list<non-falsy-string>, username: string}>, userId: null|string}, array<never, never>>' does not match the declared return type 'OCP\AppFramework\Http\DataResponse<200, array{federation: array{helloAuthParams: array{token: string}, nextcloudServer: string, roomId: string, server: string}|null, helloAuthParams: array{'1.0': array{ticket: string, userid: null|string}, '2.0': array{token: string}}, hideWarning: bool, server: string, signalingMode: string, sipDialinInfo: string, stunservers: list<array{urls: list<string>}>, ticket: string, turnservers: list<array{credential: mixed, urls: list<string>, username: string}>, userId: null|string}, array<never, never>>|OCP\AppFramework\Http\DataResponse<401|404, null, array<never, never>>' for OCA\Talk\Controller\SignalingController::getSettings (see https://psalm.dev/128)
return new DataResponse($data);
Expand Down
9 changes: 5 additions & 4 deletions lib/Events/ARoomModifiedEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ abstract class ARoomModifiedEvent extends ARoomEvent {
public const PROPERTY_READ_ONLY = 'readOnly';
public const PROPERTY_RECORDING_CONSENT = 'recordingConsent';
public const PROPERTY_SIP_ENABLED = 'sipEnabled';
public const PROPERTY_ENCRYPTION_ENABLED = 'encryptionEnabled';
public const PROPERTY_TYPE = 'type';

/**
Expand All @@ -39,8 +40,8 @@ abstract class ARoomModifiedEvent extends ARoomEvent {
public function __construct(
Room $room,
protected string $property,
protected \DateTime|string|int|null $newValue,
protected \DateTime|string|int|null $oldValue = null,
protected \DateTime|string|int|bool|null $newValue,
protected \DateTime|string|int|bool|null $oldValue = null,
protected ?Participant $actor = null,
) {
parent::__construct($room);
Expand All @@ -50,11 +51,11 @@ public function getProperty(): string {
return $this->property;
}

public function getNewValue(): \DateTime|string|int|null {
public function getNewValue(): \DateTime|string|int|bool|null {
return $this->newValue;
}

public function getOldValue(): \DateTime|string|int|null {
public function getOldValue(): \DateTime|string|int|bool|null {
return $this->oldValue;
}

Expand Down
2 changes: 2 additions & 0 deletions lib/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ public function createRoomObjectFromData(array $data): Room {
'message_expiration' => 0,
'lobby_state' => 0,
'sip_enabled' => 0,
'encrypted' => false,
'assigned_hpb' => null,
'token' => '',
'name' => '',
Expand Down Expand Up @@ -167,6 +168,7 @@ public function createRoomObject(array $row): Room {
(int)$row['message_expiration'],
(int)$row['lobby_state'],
(int)$row['sip_enabled'],
(bool)$row['encrypted'],
$assignedSignalingServer,
(string)$row['token'],
(string)$row['name'],
Expand Down
39 changes: 39 additions & 0 deletions lib/Migration/Version21000Date20241212134329.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Talk\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

class Version21000Date20241212134329 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
*
* @return ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

$table = $schema->getTable('talk_rooms');
if (!$table->hasColumn('encrypted')) {
$table->addColumn('encrypted', Types::BOOLEAN, [
'notnull' => false,
'default' => true,
]);
}

return $schema;
}
}
1 change: 1 addition & 0 deletions lib/Model/SelectHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public function selectRoomsTable(IQueryBuilder $query, string $alias = 'r'): voi
->addSelect($alias . 'read_only')
->addSelect($alias . 'lobby_state')
->addSelect($alias . 'sip_enabled')
->addSelect($alias . 'encrypted')
->addSelect($alias . 'assigned_hpb')
->addSelect($alias . 'token')
->addSelect($alias . 'name')
Expand Down
10 changes: 10 additions & 0 deletions lib/Room.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public function __construct(
private int $messageExpiration,
private int $lobbyState,
private int $sipEnabled,
private bool $encryptionEnabled,
private ?int $assignedSignalingServer,
private string $token,
private string $name,
Expand Down Expand Up @@ -223,6 +224,14 @@ public function setSIPEnabled(int $sipEnabled): void {
$this->sipEnabled = $sipEnabled;
}

public function getEncryptionEnabled(): bool {
return $this->encryptionEnabled;
}

public function setEncryptionEnabled(bool $encryptionEnabled): void {
$this->encryptionEnabled = $encryptionEnabled;
}

public function getAssignedSignalingServer(): ?int {
return $this->assignedSignalingServer;
}
Expand Down Expand Up @@ -432,6 +441,7 @@ public function getPropertiesForSignaling(string $userId, bool $roomModified = t
'listable' => $this->getListable(),
'active-since' => $this->getActiveSince(),
'sip-enabled' => $this->getSIPEnabled(),
'encrypted' => $this->getEncryptionEnabled(),
];

if ($roomModified) {
Expand Down
3 changes: 3 additions & 0 deletions lib/Service/RoomFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public function formatRoomV4(
'lastPing' => 0,
'sessionId' => '0',
'sipEnabled' => Webinary::SIP_DISABLED,
'encrypted' => false,
'actorType' => '',
'actorId' => '',
'attendeeId' => 0,
Expand Down Expand Up @@ -177,6 +178,7 @@ public function formatRoomV4(
'lobbyState' => $room->getLobbyState(),
'lobbyTimer' => $lobbyTimer,
'sipEnabled' => $room->getSIPEnabled(),
'encrypted' => $room->getEncryptionEnabled(),
'listable' => $room->getListable(),
'breakoutRoomMode' => $room->getBreakoutRoomMode(),
'breakoutRoomStatus' => $room->getBreakoutRoomStatus(),
Expand Down Expand Up @@ -210,6 +212,7 @@ public function formatRoomV4(
'notificationCalls' => $attendee->getNotificationCalls(),
'lobbyState' => $room->getLobbyState(),
'lobbyTimer' => $lobbyTimer,
'encrypted' => $room->getEncryptionEnabled(),
'actorType' => $attendee->getActorType(),
'actorId' => $attendee->getActorId(),
'attendeeId' => $attendee->getId(),
Expand Down
22 changes: 22 additions & 0 deletions lib/Service/RoomService.php
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,28 @@ public function setSIPEnabled(Room $room, int $newSipEnabled): void {
$this->dispatcher->dispatchTyped($event);
}

public function setEncryptionEnabled(Room $room, bool $newEncryptionEnabled): void {
$oldEncryptionEnabled = $room->getEncryptionEnabled();

if ($newEncryptionEnabled === $oldEncryptionEnabled) {
return;
}

$event = new BeforeRoomModifiedEvent($room, ARoomModifiedEvent::PROPERTY_ENCRYPTION_ENABLED, $newEncryptionEnabled, $oldEncryptionEnabled);
$this->dispatcher->dispatchTyped($event);

$update = $this->db->getQueryBuilder();
$update->update('talk_rooms')
->set('encrypted', $update->createNamedParameter($newEncryptionEnabled, IQueryBuilder::PARAM_BOOL))
->where($update->expr()->eq('id', $update->createNamedParameter($room->getId(), IQueryBuilder::PARAM_INT)));
$update->executeStatement();

$room->setEncryptionEnabled($newEncryptionEnabled);

$event = new RoomModifiedEvent($room, ARoomModifiedEvent::PROPERTY_ENCRYPTION_ENABLED, $newEncryptionEnabled, $oldEncryptionEnabled);
$this->dispatcher->dispatchTyped($event);
}

/**
* @psalm-param RecordingService::CONSENT_REQUIRED_* $recordingConsent
* @throws RecordingConsentException When the room has an active call or the value is invalid
Expand Down
1 change: 1 addition & 0 deletions lib/Signaling/Listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class Listener implements IEventListener {
ARoomModifiedEvent::PROPERTY_PASSWORD,
ARoomModifiedEvent::PROPERTY_READ_ONLY,
ARoomModifiedEvent::PROPERTY_SIP_ENABLED,
ARoomModifiedEvent::PROPERTY_ENCRYPTION_ENABLED,
ARoomModifiedEvent::PROPERTY_TYPE,
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
:name="t('spreed', 'Meeting')">
<LobbySettings :token="token" />
<SipSettings v-if="canUserEnableSIP" />
<SecuritySettings />
</NcAppSettingsSection>

<!-- Conversation permissions -->
Expand Down Expand Up @@ -129,6 +130,7 @@ import MatterbridgeSettings from './Matterbridge/MatterbridgeSettings.vue'
import MentionsSettings from './MentionsSettings.vue'
import NotificationsSettings from './NotificationsSettings.vue'
import RecordingConsentSettings from './RecordingConsentSettings.vue'
import SecuritySettings from './SecuritySettings.vue'
import SipSettings from './SipSettings.vue'

import { CALL, CONFIG, PARTICIPANT, CONVERSATION } from '../../constants.js'
Expand Down Expand Up @@ -159,6 +161,7 @@ export default {
NcCheckboxRadioSwitch,
NotificationsSettings,
RecordingConsentSettings,
SecuritySettings,
SipSettings,
},

Expand Down
84 changes: 84 additions & 0 deletions src/components/ConversationSettings/SecuritySettings.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<!--
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<template>
<div class="app-settings-subsection">
<h4 class="app-settings-section__subtitle">
{{ t('spreed', 'Security') }}
</h4>

<div>
<NcCheckboxRadioSwitch :checked="!hasEncryptionEnabled"
type="switch"
aria-describedby="encryption_settings_hint"
:disabled="isEncryptionLoading"
@update:checked="toggleSetting()">
{{ t('spreed', 'Disable end-to-end encryption to allow legacy clients.') }}
</NcCheckboxRadioSwitch>
</div>
</div>
</template>

<script>
import { showError, showSuccess } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'

import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'

export default {
name: 'SecuritySettings',

components: {
NcCheckboxRadioSwitch,
},

data() {
return {
isEncryptionLoading: false,
}
},

computed: {
token() {
return this.$store.getters.getToken()
},

conversation() {
return this.$store.getters.conversation(this.token) || this.$store.getters.dummyConversation
},

hasEncryptionEnabled() {
return this.conversation.encrypted || false
},
},

methods: {
t,
async toggleSetting() {
const enabled = !this.conversation.encrypted
try {
await this.$store.dispatch('setEncryptionEnabled', {
token: this.token,
enabled,
})
if (this.conversation.encrypted) {
showSuccess(t('spreed', 'End-to-end encryption is now enabled'))
} else {
showSuccess(t('spreed', 'End-to-end encryption is now disabled'))
}
} catch (e) {
// TODO check "precondition failed"
if (!this.conversation.encrypted) {
console.error('Error occurred when enabling end-to-end encryption', e)
showError(t('spreed', 'Error occurred when enabling end-to-end encryption'))
} else {
console.error('Error occurred when disabling end-to-end encryption', e)
showError(t('spreed', 'Error occurred when disabling end-to-end encryption'))
}
}
},
},
}
</script>
13 changes: 13 additions & 0 deletions src/services/conversationsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,18 @@ const setSIPEnabled = async function(token, newState) {
})
}

/**
* Change the end-to-end encryption enabled
*
* @param {string} token The token of the conversation to be modified
* @param {boolean} newState The new enabled state to set
*/
const setEncryptionEnabled = async function(token, newState) {
return axios.put(generateOcsUrl('apps/spreed/api/v4/room/{token}/webinar/encryption', { token }), {
enabled: newState,
})
}

/**
* Change the recording consent per conversation
*
Expand Down Expand Up @@ -372,6 +384,7 @@ export {
makeConversationPublic,
makeConversationPrivate,
setSIPEnabled,
setEncryptionEnabled,
setRecordingConsent,
changeLobbyState,
changeReadOnlyState,
Expand Down
Loading

0 comments on commit 4fb8481

Please sign in to comment.