Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion lib/Controller/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,18 @@ public function personal(int $batchSetting, string $soundNotification, string $s
* @param int $batchSetting How often E-mails about missed notifications should be sent (hourly: 1; every three hours: 2; daily: 3; weekly: 4)
* @param string $soundNotification Enable sound for notifications ('yes' or 'no')
* @param string $soundTalk Enable sound for Talk notifications ('yes' or 'no')
* @param string $webpushEnabled Enable web push notifications ('yes' or 'no')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param string $webpushEnabled Enable web push notifications ('yes' or 'no')
* @param string $webpushEnabled Enable external web push notifications ('yes' or 'no')

Make sure the admins understand it's external web push services

* @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
*
* 200: Admin settings updated
*/
#[OpenAPI(scope: OpenAPI::SCOPE_ADMINISTRATION)]
#[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/settings/admin', requirements: ['apiVersion' => '(v2)'])]
public function admin(int $batchSetting, string $soundNotification, string $soundTalk): DataResponse {
public function admin(int $batchSetting, string $soundNotification, string $soundTalk, string $webpushEnabled): DataResponse {
$this->appConfig->setAppValueInt('setting_batchtime', $batchSetting);
$this->appConfig->setAppValueBool('sound_notification', $soundNotification === 'yes');
$this->appConfig->setAppValueBool('sound_talk', $soundTalk === 'yes');
$this->appConfig->setAppValueBool('webpush_enabled', $webpushEnabled === 'yes');

return new DataResponse();
}
Expand Down
14 changes: 12 additions & 2 deletions lib/Controller/WebPushController.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,21 @@ public function getVapid(): DataResponse {
* @param string $uaPublicKey Public key of the device, uncompress base64url encoded (RFC8291)
* @param string $auth Authentication tag, base64url encoded (RFC8291)
* @param string $appTypes comma seperated list of types used to filter incoming notifications - appTypes are alphanum - use "all" to get all notifications, prefix with `-` to exclude (eg. 'all,-talk')
* @return DataResponse<Http::STATUS_OK|Http::STATUS_CREATED|Http::STATUS_UNAUTHORIZED, list<empty>, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{message: string}, array{}>
* @return DataResponse<Http::STATUS_OK|Http::STATUS_CREATED|Http::STATUS_UNAUTHORIZED, list<empty>, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN, array{message: string}, array{}>
*
* 200: A subscription was already registered and activated
* 201: New subscription registered successfully
* 400: Registering is not possible
* 401: Missing permissions to register
* 403: Web push is disabled by the administrator
*/
#[NoAdminRequired]
#[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/webpush', requirements: ['apiVersion' => '(v2)'])]
public function registerWP(string $endpoint, string $uaPublicKey, string $auth, string $appTypes): DataResponse {
if (!$this->appConfig->getAppValueBool('webpush_enabled')) {
return new DataResponse(['message' => 'WEBPUSH_DISABLED'], Http::STATUS_FORBIDDEN);
}

$user = $this->userSession->getUser();
if (!$user instanceof IUser) {
return new DataResponse([], Http::STATUS_UNAUTHORIZED);
Expand Down Expand Up @@ -142,17 +147,22 @@ public function registerWP(string $endpoint, string $uaPublicKey, string $auth,
* Activate subscription for push notifications
*
* @param string $activationToken Random token sent via a push notification during registration to enable the subscription
* @return DataResponse<Http::STATUS_OK|Http::STATUS_ACCEPTED|Http::STATUS_UNAUTHORIZED, list<empty>, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
* @return DataResponse<Http::STATUS_OK|Http::STATUS_ACCEPTED|Http::STATUS_UNAUTHORIZED, list<empty>, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Subscription was already activated
* 202: Subscription activated successfully
* 400: Activating subscription is not possible, may be because of a wrong activation token
* 401: Missing permissions to activate subscription
* 403: Web push is disabled by the administrator
* 404: No subscription found for the device
*/
#[NoAdminRequired]
#[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/webpush/activate', requirements: ['apiVersion' => '(v2)'])]
public function activateWP(string $activationToken): DataResponse {
if (!$this->appConfig->getAppValueBool('webpush_enabled')) {
return new DataResponse(['message' => 'WEBPUSH_DISABLED'], Http::STATUS_FORBIDDEN);
}

$user = $this->userSession->getUser();
if (!$user instanceof IUser) {
return new DataResponse([], Http::STATUS_UNAUTHORIZED);
Expand Down
8 changes: 8 additions & 0 deletions lib/Push.php
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,10 @@ protected function getProxyDevicesForUsers(array $userIds): array {
* @return list<array{id: int, uid: string, token: int, endpoint: string, ua_public: string, auth: string, app_types: string, activated: bool, activation_token: string}>
*/
protected function getWebPushDevicesForUser(string $uid): array {
if (!$this->appConfig->getAppValueBool('webpush_enabled')) {
return [];
}

$query = $this->db->getQueryBuilder();
$query->select('*')
->from('notifications_webpush')
Expand All @@ -1066,6 +1070,10 @@ protected function getWebPushDevicesForUser(string $uid): array {
* @return array<string, list<array{id: int, uid: string, token: int, endpoint: string, ua_public: string, auth: string, app_types: string, activated: bool, activation_token: string}>>
*/
protected function getWebPushDevicesForUsers(array $userIds): array {
if (!$this->appConfig->getAppValueBool('webpush_enabled')) {
return [];
}

$query = $this->db->getQueryBuilder();
$query->select('*')
->from('notifications_webpush')
Expand Down
2 changes: 2 additions & 0 deletions lib/Settings/Admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public function getForm(): TemplateResponse {
$defaultSoundNotification = $this->appConfig->getAppValueBool('sound_notification');
$defaultSoundTalk = $this->appConfig->getAppValueBool('sound_talk');
$defaultBatchtime = $this->appConfig->getAppValueInt('setting_batchtime');
$webpushEnabled = $this->appConfig->getAppValueBool('webpush_enabled');

if ($defaultBatchtime !== Settings::EMAIL_SEND_WEEKLY
&& $defaultBatchtime !== Settings::EMAIL_SEND_DAILY
Expand All @@ -45,6 +46,7 @@ public function getForm(): TemplateResponse {
'setting_batchtime' => $defaultBatchtime,
'sound_notification' => $defaultSoundNotification,
'sound_talk' => $defaultSoundTalk,
'webpush_enabled' => $webpushEnabled,
]);

return new TemplateResponse('notifications', 'settings/admin');
Expand Down
30 changes: 28 additions & 2 deletions src/views/AdminSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,33 @@

<NcCheckboxRadioSwitch
v-model="config.sound_notification"
type="switch"
@update:modelValue="updateSettings">
{{ t('notifications', 'Play sound when a new notification arrives') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
v-model="config.sound_talk"
type="switch"
@update:modelValue="updateSettings">
{{ t('notifications', 'Play sound when a call started (requires Nextcloud Talk)') }}
</NcCheckboxRadioSwitch>

<h3>{{ t('notifications', 'Web push settings') }}</h3>
<NcNoteCard v-if="showWebpushSwitch" type="warning">
{{ t('notifications', 'Web push notifications are encrypted but routed through services provided by Microsoft, Apple, and Google. While the content is protected, metadata such as timing and frequency of notifications may be exposed to these providers.') }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{{ t('notifications', 'Web push notifications are encrypted but routed through services provided by Microsoft, Apple, and Google. While the content is protected, metadata such as timing and frequency of notifications may be exposed to these providers.') }}
{{ t('notifications', 'Web push notifications are encrypted but routed through services provided by Microsoft, Apple, Mozilla and Google. While the content is protected, metadata such as timing and frequency of notifications may be exposed to these providers.') }}

Mozilla is one of the big providers too

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I thought M… is already in the list. But we have 2 M…

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

metadata such as timing and frequency of notifications may be exposed to these providers

This is also true with the proxy push to APNS/FCM

</NcNoteCard>
<NcButton
v-if="!showWebpushSwitch"
@click="showWebpushSwitch = true">
{{ t('notifications', 'Enable web push notifications') }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{{ t('notifications', 'Enable web push notifications') }}
{{ t('notifications', 'Enable external web push notifications') }}

Same as above

</NcButton>
<NcCheckboxRadioSwitch
v-else
v-model="config.webpush_enabled"
type="switch"
@update:modelValue="updateSettings">
{{ t('notifications', 'Enable web push notifications') }}
</NcCheckboxRadioSwitch>
</NcSettingsSection>
</template>

Expand All @@ -40,8 +59,10 @@ import { showError, showSuccess } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
import { computed, reactive } from 'vue'
import { computed, reactive, ref } from 'vue'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
import NcSelect from '@nextcloud/vue/components/NcSelect'
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'

Expand All @@ -64,13 +85,16 @@ const BATCHTIME_OPTIONS = [
export default {
name: 'AdminSettings',
components: {
NcSelect,
NcButton,
NcCheckboxRadioSwitch,
NcNoteCard,
NcSelect,
NcSettingsSection,
},

setup() {
const config = reactive(loadState('notifications', 'config', {}))
const showWebpushSwitch = ref(config.webpush_enabled)

const currentBatchTime = computed({
get() {
Expand All @@ -85,6 +109,7 @@ export default {
BATCHTIME_OPTIONS,
config,
currentBatchTime,
showWebpushSwitch,
}
},

Expand All @@ -97,6 +122,7 @@ export default {
form.append('batchSetting', this.config.setting_batchtime)
form.append('soundNotification', this.config.sound_notification ? 'yes' : 'no')
form.append('soundTalk', this.config.sound_talk ? 'yes' : 'no')
form.append('webpushEnabled', this.config.webpush_enabled ? 'yes' : 'no')
await axios.post(generateOcsUrl('apps/notifications/api/v2/settings/admin'), form)
showSuccess(t('notifications', 'Your settings have been updated.'))
} catch (error) {
Expand Down
8 changes: 3 additions & 5 deletions src/views/UserSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,21 @@

<NcCheckboxRadioSwitch
v-model="config.sound_notification"
type="switch"
@update:modelValue="updateSettings">
{{ t('notifications', 'Play sound when a new notification arrives') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
v-model="config.sound_talk"
type="switch"
@update:modelValue="updateSettings">
{{ t('notifications', 'Play sound when a call started (requires Nextcloud Talk)') }}
</NcCheckboxRadioSwitch>

<template v-if="config.sound_talk">
<NcCheckboxRadioSwitch
v-model="storage.secondary_speaker"
class="additional-margin-top"
type="switch"
:disabled="isSafari"
@update:modelValue="updateLocalSettings">
{{ t('notifications', 'Also repeat sound on a secondary speaker') }}
Expand Down Expand Up @@ -192,10 +194,6 @@ export default {
</script>

<style lang="scss" scoped>
.additional-margin-top {
margin-top: 12px;
}

.notification-frequency__wrapper {
display: flex;
flex-direction: column;
Expand Down
4 changes: 4 additions & 0 deletions tests/Unit/Controller/WebPushControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ protected function setUp(): void {
$this->userSession = $this->createMock(IUserSession::class);
$this->tokenProvider = $this->createMock(IProvider::class);
$this->identityProof = $this->createMock(Manager::class);

$this->appConfig->method('getAppValueBool')
->with('webpush_enabled')
->willReturn(true);
}

protected function getController(array $methods = []): WebPushController|MockObject {
Expand Down
4 changes: 4 additions & 0 deletions tests/Unit/PushTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ protected function setUp(): void {
$this->cacheFactory->method('createDistributed')
->with('pushtokens')
->willReturn($this->cache);

$this->appConfig->method('getAppValueBool')
->with('webpush_enabled')
->willReturn(true);
}

/**
Expand Down
Loading