From 964ad9d264891bc7c570bb29e2e200d049f761f1 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Mon, 17 Apr 2023 11:03:15 +0200 Subject: [PATCH 1/4] MDL-77893 airnotifier: Allow to set publickey when creating new device --- user/externallib.php | 11 ++++++++--- user/tests/externallib_test.php | 6 ++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/user/externallib.php b/user/externallib.php index 84127412756..54dfb0e032e 100644 --- a/user/externallib.php +++ b/user/externallib.php @@ -1257,7 +1257,8 @@ class core_user_external extends \core_external\external_api { 'platform' => new external_value(PARAM_NOTAGS, 'the device platform \'iOS\' or \'Android\' etc.'), 'version' => new external_value(PARAM_NOTAGS, 'the device version \'6.1.2\' or \'4.2.2\' etc.'), 'pushid' => new external_value(PARAM_RAW, 'the device PUSH token/key/identifier/registration id'), - 'uuid' => new external_value(PARAM_RAW, 'the device UUID') + 'uuid' => new external_value(PARAM_RAW, 'the device UUID'), + 'publickey' => new external_value(PARAM_RAW, 'the app generated public key', VALUE_DEFAULT, null), ) ); } @@ -1273,10 +1274,11 @@ class core_user_external extends \core_external\external_api { * @param string $version The device version 6.1.2 or 4.2.2 etc. * @param string $pushid The device PUSH token/key/identifier/registration id. * @param string $uuid The device UUID. + * @param string $publickey The app generated public key * @return array List of possible warnings. * @since Moodle 2.6 */ - public static function add_user_device($appid, $name, $model, $platform, $version, $pushid, $uuid) { + public static function add_user_device($appid, $name, $model, $platform, $version, $pushid, $uuid, $publickey = null) { global $CFG, $USER, $DB; require_once($CFG->dirroot . "/user/lib.php"); @@ -1287,7 +1289,8 @@ class core_user_external extends \core_external\external_api { 'platform' => $platform, 'version' => $version, 'pushid' => $pushid, - 'uuid' => $uuid + 'uuid' => $uuid, + 'publickey' => $publickey, )); $warnings = array(); @@ -1310,6 +1313,7 @@ class core_user_external extends \core_external\external_api { foreach ($userdevices as $userdevice) { $userdevice->version = $params['version']; // Maybe the user upgraded the device. $userdevice->pushid = $params['pushid']; + $userdevice->publickey = $params['publickey']; $userdevice->timemodified = time(); $DB->update_record('user_devices', $userdevice); } @@ -1324,6 +1328,7 @@ class core_user_external extends \core_external\external_api { $userdevice->version = $params['version']; $userdevice->pushid = $params['pushid']; $userdevice->uuid = $params['uuid']; + $userdevice->publickey = $params['publickey']; $userdevice->timecreated = time(); $userdevice->timemodified = $userdevice->timecreated; diff --git a/user/tests/externallib_test.php b/user/tests/externallib_test.php index 6e17be6504f..db87ef6352c 100644 --- a/user/tests/externallib_test.php +++ b/user/tests/externallib_test.php @@ -1108,7 +1108,8 @@ class externallib_test extends externallib_advanced_testcase { 'platform' => 'Android', 'version' => '4.2.2', 'pushid' => 'apushdkasdfj4835', - 'uuid' => 'asdnfl348qlksfaasef859' + 'uuid' => 'asdnfl348qlksfaasef859', + 'publickey' => null, ); // Call the external function. @@ -1129,8 +1130,9 @@ class externallib_test extends externallib_advanced_testcase { // Test update an existing device. $device['pushid'] = 'different than before'; + $device['publickey'] = 'MFsxCzAJBgNVBAYTAkZSMRMwEQYDVQQ'; $warnings = core_user_external::add_user_device($device['appid'], $device['name'], $device['model'], $device['platform'], - $device['version'], $device['pushid'], $device['uuid']); + $device['version'], $device['pushid'], $device['uuid'], $device['publickey']); $warnings = external_api::clean_returnvalue(core_user_external::add_user_device_returns(), $warnings); $this->assertEquals(1, $DB->count_records('user_devices')); From bdf525a43e249340c453aeceb36ab62e22f3de61 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Mon, 17 Apr 2023 11:21:52 +0200 Subject: [PATCH 2/4] MDL-77893 airnotifier: Leave only message data processed by Airnotifier --- .../message_output_airnotifier.php | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/message/output/airnotifier/message_output_airnotifier.php b/message/output/airnotifier/message_output_airnotifier.php index 6e30f3d2bc4..de973e154e6 100644 --- a/message/output/airnotifier/message_output_airnotifier.php +++ b/message/output/airnotifier/message_output_airnotifier.php @@ -92,16 +92,15 @@ class message_output_airnotifier extends message_output { $extra->sitefullname = clean_param(format_string($site->fullname), PARAM_NOTAGS); $extra->siteshortname = clean_param(format_string($site->shortname), PARAM_NOTAGS); - // Clean HTML, push notifications must arrive clean. - if (!empty($extra->smallmessage)) { - $extra->smallmessage = clean_param($extra->smallmessage, PARAM_NOTAGS); - } - if (!empty($extra->fullmessage)) { - $extra->fullmessage = clean_param($extra->fullmessage, PARAM_NOTAGS); - } - if (!empty($extra->fullmessagehtml)) { - $extra->fullmessagehtml = clean_param($extra->fullmessagehtml, PARAM_NOTAGS); + // Clean HTML and ony allow data not to be ignored by Airnotifier to reduce the payload size. + if (empty($extra->smallmessage)) { + $extra->smallmessage = $extra->fullmessage; } + $extra->smallmessage = clean_param($extra->smallmessage, PARAM_NOTAGS); + unset($extra->fullmessage); + unset($extra->fullmessagehtml); + unset($extra->fullmessageformat); + unset($extra->fullmessagetrust); // Send wwwroot to airnotifier. $extra->wwwroot = $CFG->wwwroot; @@ -169,8 +168,6 @@ class message_output_airnotifier extends message_output { 'userfromid', 'sitefullname', 'smallmessage', - 'fullmessage', - 'fullmessagehtml', 'subject', 'contexturl', ]; @@ -196,16 +193,12 @@ class message_output_airnotifier extends message_output { unset($payload->replytoname); unset($payload->attachment); unset($payload->attachname); - unset($payload->fullmessageformat); - unset($payload->fullmessagetrust); // We use Firebase to deliver all Push Notifications, and for all device types. // Firebase has a 4KB payload limit. // https://firebase.google.com/docs/cloud-messaging/concept-options#notifications_and_data_messages // If the message is over that limit we remove unneeded fields and replace the title with a simple message. if (\core_text::strlen(json_encode($payload), '8bit') > 4000) { - unset($payload->fullmessage); - unset($payload->fullmessagehtml); $payload->smallmessage = get_string('view_notification', 'message_airnotifier'); } From 862a9fb24ce96ed47fbbe4ea5da0030abc69a3f3 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Mon, 17 Apr 2023 12:38:18 +0200 Subject: [PATCH 3/4] MDL-77893 airnotifier: Allow configuring how to process encrypted notifs --- message/output/airnotifier/classes/manager.php | 6 ++++++ .../airnotifier/lang/en/message_airnotifier.php | 4 ++++ .../airnotifier/message_output_airnotifier.php | 11 ++++++++++- message/output/airnotifier/settings.php | 13 +++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/message/output/airnotifier/classes/manager.php b/message/output/airnotifier/classes/manager.php index 80638488607..ed9adf5692c 100644 --- a/message/output/airnotifier/classes/manager.php +++ b/message/output/airnotifier/classes/manager.php @@ -37,6 +37,12 @@ class message_airnotifier_manager { /** @var string The Airnotifier public instance URL */ const AIRNOTIFIER_PUBLICURL = 'https://messages.moodle.net'; + /** @var int Avoid sending notifications to devices not supporting encryption */ + const ENCRYPT_UNSUPPORTED_NOT_SEND = 0; + + /** @var int Send notifications to devices not supporting encryption */ + const ENCRYPT_UNSUPPORTED_SEND = 1; + /** * Include the relevant javascript and language strings for the device * toolbox YUI module diff --git a/message/output/airnotifier/lang/en/message_airnotifier.php b/message/output/airnotifier/lang/en/message_airnotifier.php index 80b1ac53f9c..35de2534e7c 100644 --- a/message/output/airnotifier/lang/en/message_airnotifier.php +++ b/message/output/airnotifier/lang/en/message_airnotifier.php @@ -38,9 +38,12 @@ $string['configured'] = 'Configured'; $string['deletecheckdevicename'] = 'Delete your device: {$a->name}'; $string['deletedevice'] = 'Delete the device. Note that an app can register the device again. If the device keeps reappearing, disable it.'; $string['devicetoken'] = 'Device token'; +$string['donotsendnotification'] = 'Do not send notifications at all'; $string['enableprocessor'] = 'Enable mobile notifications'; $string['encryptnotifications'] = 'Encrypt notifications'; $string['encryptnotifications_help'] = 'Enable end-to-end encryption of app notifications where possible. Only personal data is encrypted, some data may be removed from notification payload if it can\'t be encrypted.'; +$string['encryptprocessing'] = 'For devices not supporting encryption'; +$string['encryptprocessing_desc'] = 'Please indicate what to do when the target device does not support encryption (supported only Android 6 and iOS 13 onward).'; $string['errorretrievingkey'] = 'An error occurred while retrieving the access key. Your site must be registered to use this service. If your site is already registered, please try updating your registration. Alternatively, you can obtain an access key by creating an account on the Moodle Apps Portal.'; $string['keyretrievedsuccessfully'] = 'The access key was retrieved successfully. To access Moodle app usage statistics, please create an account on the Moodle Apps Portal.'; $string['messageprovidersempty'] = 'There are no mobile notifications enabled in default notification preferences.'; @@ -73,6 +76,7 @@ $string['privacy:subcontext'] = 'Message Airnotifier'; $string['sitemustberegistered'] = 'In order to use the public Airnotifier instance, your site must be registered. Alternatively, you can obtain an access key by creating an account on the Moodle Apps Portal.'; $string['showhide'] = 'Enable/disable the device.'; $string['requestaccesskey'] = 'Request access key'; +$string['sendnotificationnotenc'] = 'Send notifications without encryption'; $string['sendtest'] = 'Send test push notification to my devices'; $string['sendtestconfirmation'] = 'A test push notification will be sent to the devices you use to connect to this site. Please ensure that your devices are connected to the Internet and that the mobile app is not open (since push notifications are only displayed when received in the background).'; $string['serverconnectivityerror'] = 'This site is not able to connect to the notifications server {$a}'; diff --git a/message/output/airnotifier/message_output_airnotifier.php b/message/output/airnotifier/message_output_airnotifier.php index de973e154e6..1873326b7d2 100644 --- a/message/output/airnotifier/message_output_airnotifier.php +++ b/message/output/airnotifier/message_output_airnotifier.php @@ -85,7 +85,8 @@ class message_output_airnotifier extends message_output { $extra->site = $siteid; $extra->date = (!empty($eventdata->timecreated)) ? $eventdata->timecreated : time(); $extra->notification = (!empty($eventdata->notification)) ? 1 : 0; - $extra->encrypted = get_config('message_airnotifier', 'encryptnotifications') == 1; + $encryptnotifications = get_config('message_airnotifier', 'encryptnotifications') == 1; + $encryptprocessing = get_config('message_airnotifier', 'encryptprocessing'); // Site name. $site = get_site(); @@ -114,6 +115,13 @@ class message_output_airnotifier extends message_output { continue; } + // Check if we should skip sending the notification. + if ($encryptnotifications && empty($devicetoken->publickey) && + $encryptprocessing == message_airnotifier_manager::ENCRYPT_UNSUPPORTED_NOT_SEND) { + + continue; // Avoid sending notifications to devices not supporting encryption. + } + // Sending the message to the device. $serverurl = $CFG->airnotifierurl . ':' . $CFG->airnotifierport . '/api/v2/push/'; $header = array('Accept: application/json', 'X-AN-APP-NAME: ' . $CFG->airnotifierappname, @@ -123,6 +131,7 @@ class message_output_airnotifier extends message_output { $curl->setopt(array('CURLOPT_TIMEOUT' => 2, 'CURLOPT_CONNECTTIMEOUT' => 2)); $curl->setHeader($header); + $extra->encrypted = $encryptnotifications; $extra = $this->encrypt_payload($extra, $devicetoken); $params = array( 'device' => $devicetoken->platform, diff --git a/message/output/airnotifier/settings.php b/message/output/airnotifier/settings.php index ab5ebd8f3ed..c6f37272e4e 100644 --- a/message/output/airnotifier/settings.php +++ b/message/output/airnotifier/settings.php @@ -55,6 +55,19 @@ if ($ADMIN->fulltree) { false )); + $options = [ + message_airnotifier_manager::ENCRYPT_UNSUPPORTED_NOT_SEND => new lang_string('donotsendnotification', 'message_airnotifier'), + message_airnotifier_manager::ENCRYPT_UNSUPPORTED_SEND => new lang_string('sendnotificationnotenc', 'message_airnotifier'), + ]; + $settings->add(new admin_setting_configselect('message_airnotifier/encryptprocessing', + new lang_string('encryptprocessing', 'message_airnotifier'), + new lang_string('encryptprocessing_desc', 'message_airnotifier'), + message_airnotifier_manager::ENCRYPT_UNSUPPORTED_NOT_SEND, + $options + )); + $settings->hide_if('message_airnotifier/encryptprocessing', 'message_airnotifier/encryptnotifications', + 'neq', 1); + $url = new moodle_url('/message/output/airnotifier/requestaccesskey.php', array('sesskey' => sesskey())); $link = html_writer::link($url, get_string('requestaccesskey', 'message_airnotifier')); $settings->add(new admin_setting_heading('requestaccesskey', '', $link)); From cf76d1bf9fea780ba5793db2cad547e3bdd9fbcc Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Mon, 17 Apr 2023 13:23:36 +0200 Subject: [PATCH 4/4] MDL-77893 airnotifier: Apply payload size optimisation to all push --- .../airnotifier/message_output_airnotifier.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/message/output/airnotifier/message_output_airnotifier.php b/message/output/airnotifier/message_output_airnotifier.php index 1873326b7d2..677dbaf1ff2 100644 --- a/message/output/airnotifier/message_output_airnotifier.php +++ b/message/output/airnotifier/message_output_airnotifier.php @@ -133,6 +133,15 @@ class message_output_airnotifier extends message_output { $extra->encrypted = $encryptnotifications; $extra = $this->encrypt_payload($extra, $devicetoken); + + // We use Firebase to deliver all Push Notifications, and for all device types. + // Firebase has a 4KB payload limit. + // https://firebase.google.com/docs/cloud-messaging/concept-options#notifications_and_data_messages + // If the message is over that limit we remove unneeded fields and replace the title with a simple message. + if (\core_text::strlen(json_encode($extra), '8bit') > 4000) { + $extra->smallmessage = get_string('view_notification', 'message_airnotifier'); + } + $params = array( 'device' => $devicetoken->platform, 'token' => $devicetoken->pushid, @@ -203,14 +212,6 @@ class message_output_airnotifier extends message_output { unset($payload->attachment); unset($payload->attachname); - // We use Firebase to deliver all Push Notifications, and for all device types. - // Firebase has a 4KB payload limit. - // https://firebase.google.com/docs/cloud-messaging/concept-options#notifications_and_data_messages - // If the message is over that limit we remove unneeded fields and replace the title with a simple message. - if (\core_text::strlen(json_encode($payload), '8bit') > 4000) { - $payload->smallmessage = get_string('view_notification', 'message_airnotifier'); - } - return $payload; }