Skip to content

Commit

Permalink
MDL-76722 message_airnotifier: Add encrypted notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
alexmorrisnz authored and andrewnicols committed Apr 11, 2023
1 parent 9bc236d commit 38160a6
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 2 deletions.
3 changes: 3 additions & 0 deletions message/output/airnotifier/lang/en/message_airnotifier.php
Expand Up @@ -39,6 +39,8 @@
$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['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['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 <a href="https://apps.moodle.com">Moodle Apps Portal</a>.';
$string['keyretrievedsuccessfully'] = 'The access key was retrieved successfully. To access Moodle app usage statistics, please create an account on the <a href="https://apps.moodle.com">Moodle Apps Portal</a>.';
$string['messageprovidersempty'] = 'There are no mobile notifications enabled in default notification preferences.';
Expand Down Expand Up @@ -77,3 +79,4 @@
$string['unknowndevice'] = 'Unknown device';
$string['userdevices'] = 'User devices';
$string['airnotifier:managedevice'] = 'Manage devices';
$string['view_notification'] = 'Tap to view';
78 changes: 76 additions & 2 deletions message/output/airnotifier/message_output_airnotifier.php
Expand Up @@ -85,6 +85,7 @@ public function send_message($eventdata) {
$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;

// Site name.
$site = get_site();
Expand All @@ -110,7 +111,6 @@ public function send_message($eventdata) {
$devicetokens = $airnotifiermanager->get_user_devices($CFG->airnotifiermobileappname, $eventdata->userto->id);

foreach ($devicetokens as $devicetoken) {

if (!$devicetoken->enable) {
continue;
}
Expand All @@ -124,11 +124,18 @@ public function send_message($eventdata) {
$curl->setopt(array('CURLOPT_TIMEOUT' => 2, 'CURLOPT_CONNECTTIMEOUT' => 2));
$curl->setHeader($header);

$extra = $this->encrypt_payload($extra, $devicetoken);
$params = array(
'device' => $devicetoken->platform,
'token' => $devicetoken->pushid,
'extra' => $extra
);
if ($extra->encrypted) {
// Setting alert to null makes air notifier send the notification as a data payload,
// this forces Android phones to call the app onMessageReceived function to decrypt the notification.
// Otherwise notifications are created by the Android system and will not be decrypted.
$params['alert'] = null;
}

// JSON POST raw body request.
$resp = $curl->post($serverurl, json_encode($params));
Expand All @@ -137,6 +144,74 @@ public function send_message($eventdata) {
return true;
}

/**
* Encrypt the notification payload.
*
* @param stdClass $payload The notification payload.
* @param stdClass $devicetoken The device token record
* @return stdClass
*/
protected function encrypt_payload(stdClass $payload, stdClass $devicetoken): stdClass {
if (empty($payload->encrypted)) {
return $payload;
}

if (empty($devicetoken->publickey)) {
$payload->encrypted = false;
return $payload;
}

// Clone the data to avoid modifying the original.
$payload = clone $payload;
$publickey = sodium_base642bin($devicetoken->publickey, SODIUM_BASE64_VARIANT_ORIGINAL);
$fields = [
'userfromfullname',
'userfromid',
'sitefullname',
'smallmessage',
'fullmessage',
'fullmessagehtml',
'subject',
'contexturl',
];
foreach ($fields as $field) {
if (!isset($payload->$field)) {
continue;
}
$payload->$field = sodium_bin2base64(sodium_crypto_box_seal(
$payload->$field,
$publickey
), SODIUM_BASE64_VARIANT_ORIGINAL);
}

// Remove extra fields which may contain personal data.
// They cannot be encrypted otherwise we would go over the 4KB payload size limit.
unset($payload->usertoid);
unset($payload->replyto);
unset($payload->replytoname);
unset($payload->name);
unset($payload->siteshortname);
unset($payload->customdata);
unset($payload->contexturlname);
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');
}

return $payload;
}

/**
* Creates necessary fields in the messaging config form.
*
Expand Down Expand Up @@ -221,4 +296,3 @@ public function is_system_configured() {
return $airnotifiermanager->is_system_configured();
}
}

6 changes: 6 additions & 0 deletions message/output/airnotifier/settings.php
Expand Up @@ -49,6 +49,12 @@
get_string('airnotifieraccesskey', 'message_airnotifier'),
get_string('configairnotifieraccesskey', 'message_airnotifier'), '', PARAM_ALPHANUMEXT));

$settings->add(new admin_setting_configcheckbox('message_airnotifier/encryptnotifications',
new lang_string('encryptnotifications', 'message_airnotifier'),
new lang_string('encryptnotifications_help', 'message_airnotifier'),
false
));

$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));
Expand Down

0 comments on commit 38160a6

Please sign in to comment.