Browse Source

refactor(neon,nextcloud): cleanup PushNotification decryption

Signed-off-by: Nikolas Rimikis <leptopoda@users.noreply.github.com>
pull/959/head
Nikolas Rimikis 1 year ago
parent
commit
b4fb4108fe
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 2
      packages/neon/neon/lib/src/blocs/push_notifications.dart
  2. 49
      packages/neon/neon/lib/src/models/push_notification.dart
  3. 19
      packages/neon/neon/lib/src/utils/push_utils.dart

2
packages/neon/neon/lib/src/blocs/push_notifications.dart

@ -62,7 +62,7 @@ class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents,
Future<void> _setupUnifiedPush() async { Future<void> _setupUnifiedPush() async {
// We just use a single RSA keypair for all accounts // We just use a single RSA keypair for all accounts
final keypair = await PushUtils.loadRSAKeypair(); final keypair = PushUtils.loadRSAKeypair();
await UnifiedPush.initialize( await UnifiedPush.initialize(
onNewEndpoint: (final endpoint, final instance) async { onNewEndpoint: (final endpoint, final instance) async {

49
packages/neon/neon/lib/src/models/push_notification.dart

@ -1,13 +1,26 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:nextcloud/notifications.dart' show DecryptedSubject; import 'package:nextcloud/notifications.dart' as notifications;
part 'push_notification.g.dart'; part 'push_notification.g.dart';
/// The json key for [PushNotification.accountID].
const String _accountIDKey = 'accountID';
/// The json key for [PushNotification.priority].
const String _priorityKey = 'priority';
/// The json key for [PushNotification.type].
const String _typeKey = 'type';
/// The json key for [PushNotification.subject].
const String _subjectKey = 'subject';
/// Data for a push notification.
@JsonSerializable() @JsonSerializable()
@immutable
@internal @internal
class PushNotification { class PushNotification {
/// Creates a new push notification.
const PushNotification({ const PushNotification({
required this.accountID, required this.accountID,
required this.priority, required this.priority,
@ -15,14 +28,44 @@ class PushNotification {
required this.subject, required this.subject,
}); });
/// Creates a new PushNotification object from the given [json] data.
///
/// Use [PushNotification.fromEncrypted] when you the [subject] is still encrypted.
factory PushNotification.fromJson(final Map<String, dynamic> json) => _$PushNotificationFromJson(json); factory PushNotification.fromJson(final Map<String, dynamic> json) => _$PushNotificationFromJson(json);
/// Creates a new PushNotification object from the given [json] data containing an encrypted [subject].
///
/// Use [PushNotification.fromJson] when the [subject] is not encrypted.
factory PushNotification.fromEncrypted(
final Map<String, dynamic> json,
final notifications.RSAPrivateKey privateKey,
) {
final subject = notifications.decryptPushNotificationSubject(privateKey, json[_subjectKey] as String);
return PushNotification(
accountID: json[_accountIDKey] as String,
priority: json[_priorityKey] as String,
type: json[_typeKey] as String,
subject: subject,
);
}
/// Parses this object into a json like map.
Map<String, dynamic> toJson() => _$PushNotificationToJson(this); Map<String, dynamic> toJson() => _$PushNotificationToJson(this);
/// The account associated to this notification.
@JsonKey(name: _accountIDKey)
final String accountID; final String accountID;
/// The priority of the notification.
@JsonKey(name: _priorityKey)
final String priority; final String priority;
/// The type of the notification.
@JsonKey(name: _typeKey)
final String type; final String type;
final DecryptedSubject subject; /// The subject of this notification.
@JsonKey(name: _subjectKey)
final notifications.DecryptedSubject subject;
} }

19
packages/neon/neon/lib/src/utils/push_utils.dart

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:ui'; import 'dart:ui';
@ -23,17 +24,17 @@ import 'package:nextcloud/notifications.dart' as notifications;
class PushUtils { class PushUtils {
const PushUtils._(); const PushUtils._();
static Future<notifications.RSAKeypair> loadRSAKeypair() async { static notifications.RSAKeypair loadRSAKeypair() {
const storage = AppStorage(StorageKeys.notifications); const storage = AppStorage(StorageKeys.notifications);
const keyDevicePrivateKey = 'device-private-key'; const keyDevicePrivateKey = 'device-private-key';
late notifications.RSAKeypair keypair; final notifications.RSAKeypair keypair;
if (!storage.containsKey(keyDevicePrivateKey) || (storage.getString(keyDevicePrivateKey)?.isEmpty ?? true)) { if (!storage.containsKey(keyDevicePrivateKey) || (storage.getString(keyDevicePrivateKey)!.isEmpty)) {
debugPrint('Generating RSA keys for push notifications'); debugPrint('Generating RSA keys for push notifications');
// The key size has to be 2048, other sizes are not accepted by Nextcloud (at the moment at least) // The key size has to be 2048, other sizes are not accepted by Nextcloud (at the moment at least)
// ignore: avoid_redundant_argument_values // ignore: avoid_redundant_argument_values
keypair = notifications.RSAKeypair.fromRandom(keySize: 2048); keypair = notifications.RSAKeypair.fromRandom(keySize: 2048);
await storage.setString(keyDevicePrivateKey, keypair.privateKey.toPEM()); unawaited(storage.setString(keyDevicePrivateKey, keypair.privateKey.toPEM()));
} else { } else {
keypair = notifications.RSAKeypair(notifications.RSAPrivateKey.fromPEM(storage.getString(keyDevicePrivateKey)!)); keypair = notifications.RSAKeypair(notifications.RSAPrivateKey.fromPEM(storage.getString(keyDevicePrivateKey)!));
} }
@ -74,16 +75,10 @@ class PushUtils {
); );
await NeonStorage.init(); await NeonStorage.init();
final keypair = await loadRSAKeypair(); final keypair = loadRSAKeypair();
for (final message in Uri(query: utf8.decode(messages)).queryParameters.values) { for (final message in Uri(query: utf8.decode(messages)).queryParameters.values) {
final data = json.decode(message) as Map<String, dynamic>; final data = json.decode(message) as Map<String, dynamic>;
final pushNotification = PushNotification( final pushNotification = PushNotification.fromEncrypted(data, keypair.privateKey);
accountID: instance,
priority: data['priority']! as String,
type: data['type']! as String,
subject: notifications.decryptPushNotificationSubject(keypair.privateKey, data['subject']! as String),
);
if (pushNotification.subject.delete ?? false) { if (pushNotification.subject.delete ?? false) {
await localNotificationsPlugin.cancel(_getNotificationID(instance, pushNotification)); await localNotificationsPlugin.cancel(_getNotificationID(instance, pushNotification));

Loading…
Cancel
Save