Browse Source

specs,tool,nextcloud,nextcloud_push_proxy: Remove push proxy implementation

pull/162/head
jld3103 2 years ago
parent
commit
302a3b51d3
No known key found for this signature in database
GPG Key ID: 9062417B9E8EB7B3
  1. 94
      .github/workflows/dart.yml
  2. 60
      packages/nextcloud/lib/src/helpers.dart
  3. 70
      packages/nextcloud/lib/src/nextcloud.openapi.dart
  4. 29
      packages/nextcloud/lib/src/nextcloud.openapi.g.dart
  5. 25
      packages/nextcloud/lib/src/nextcloud.openapi.json
  6. 4
      packages/nextcloud/pubspec.yaml
  7. 89
      packages/nextcloud/test/notifications.dart
  8. 12
      packages/nextcloud_push_proxy/.gitignore
  9. 17
      packages/nextcloud_push_proxy/Dockerfile
  10. 1
      packages/nextcloud_push_proxy/LICENSE
  11. 1
      packages/nextcloud_push_proxy/analysis_options.yaml
  12. 68
      packages/nextcloud_push_proxy/bin/unified_push.dart
  13. 163
      packages/nextcloud_push_proxy/lib/nextcloud_push_proxy.dart
  14. 7
      packages/nextcloud_push_proxy/mono_pkg.yaml
  15. 16
      packages/nextcloud_push_proxy/pubspec.yaml
  16. 25
      specs/notifications.json
  17. 4
      tool/run-dev-instance.sh

94
.github/workflows/dart.yml

@ -101,38 +101,6 @@ jobs:
needs: needs:
- job_001 - job_001
job_004: job_004:
name: "all; PKG: packages/nextcloud_push_proxy; `dart analyze --fatal-infos .`"
runs-on: ubuntu-latest
steps:
- name: Cache Pub hosted dependencies
uses: actions/cache@627f0f41f6904a5b1efbaed9f96d9eb58e92e920
with:
path: "~/.pub-cache/hosted"
key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/nextcloud_push_proxy;commands:analyze_0"
restore-keys: |
os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/nextcloud_push_proxy
os:ubuntu-latest;pub-cache-hosted;sdk:stable
os:ubuntu-latest;pub-cache-hosted
os:ubuntu-latest
- name: Setup Dart SDK
uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46
with:
sdk: stable
- id: checkout
name: Checkout repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- id: packages_nextcloud_push_proxy_pub_upgrade
name: packages/nextcloud_push_proxy; dart pub upgrade
run: dart pub upgrade
if: "always() && steps.checkout.conclusion == 'success'"
working-directory: packages/nextcloud_push_proxy
- name: "packages/nextcloud_push_proxy; dart analyze --fatal-infos ."
run: dart analyze --fatal-infos .
if: "always() && steps.packages_nextcloud_push_proxy_pub_upgrade.conclusion == 'success'"
working-directory: packages/nextcloud_push_proxy
needs:
- job_001
job_005:
name: "all; PKG: packages/nextcloud_test; `dart analyze --fatal-infos .`" name: "all; PKG: packages/nextcloud_test; `dart analyze --fatal-infos .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -164,7 +132,7 @@ jobs:
working-directory: packages/nextcloud_test working-directory: packages/nextcloud_test
needs: needs:
- job_001 - job_001
job_006: job_005:
name: "all; PKG: packages/sort_box; `dart analyze --fatal-infos .`" name: "all; PKG: packages/sort_box; `dart analyze --fatal-infos .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -196,7 +164,7 @@ jobs:
working-directory: packages/sort_box working-directory: packages/sort_box
needs: needs:
- job_001 - job_001
job_007: job_006:
name: "all; PKG: packages/spec_templates; `dart analyze --fatal-infos .`" name: "all; PKG: packages/spec_templates; `dart analyze --fatal-infos .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -228,7 +196,7 @@ jobs:
working-directory: packages/spec_templates working-directory: packages/spec_templates
needs: needs:
- job_001 - job_001
job_008: job_007:
name: "all; PKG: packages/dynamite; `dart format --output=none --set-exit-if-changed --line-length 120 .`" name: "all; PKG: packages/dynamite; `dart format --output=none --set-exit-if-changed --line-length 120 .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -260,7 +228,7 @@ jobs:
working-directory: packages/dynamite working-directory: packages/dynamite
needs: needs:
- job_001 - job_001
job_009: job_008:
name: "all; PKG: packages/file_icons; `dart format --output=none --set-exit-if-changed --line-length 120 .`" name: "all; PKG: packages/file_icons; `dart format --output=none --set-exit-if-changed --line-length 120 .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -292,7 +260,7 @@ jobs:
working-directory: packages/file_icons working-directory: packages/file_icons
needs: needs:
- job_001 - job_001
job_010: job_009:
name: "all; PKG: packages/neon; `dart format --output=none --set-exit-if-changed --line-length 120 .`" name: "all; PKG: packages/neon; `dart format --output=none --set-exit-if-changed --line-length 120 .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -324,7 +292,7 @@ jobs:
working-directory: packages/neon working-directory: packages/neon
needs: needs:
- job_001 - job_001
job_011: job_010:
name: "all; PKG: packages/nextcloud; `dart format --output=none --set-exit-if-changed --line-length 120 .`" name: "all; PKG: packages/nextcloud; `dart format --output=none --set-exit-if-changed --line-length 120 .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -356,39 +324,7 @@ jobs:
working-directory: packages/nextcloud working-directory: packages/nextcloud
needs: needs:
- job_001 - job_001
job_012: job_011:
name: "all; PKG: packages/nextcloud_push_proxy; `dart format --output=none --set-exit-if-changed --line-length 120 .`"
runs-on: ubuntu-latest
steps:
- name: Cache Pub hosted dependencies
uses: actions/cache@627f0f41f6904a5b1efbaed9f96d9eb58e92e920
with:
path: "~/.pub-cache/hosted"
key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/nextcloud_push_proxy;commands:format"
restore-keys: |
os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/nextcloud_push_proxy
os:ubuntu-latest;pub-cache-hosted;sdk:stable
os:ubuntu-latest;pub-cache-hosted
os:ubuntu-latest
- name: Setup Dart SDK
uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46
with:
sdk: stable
- id: checkout
name: Checkout repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
- id: packages_nextcloud_push_proxy_pub_upgrade
name: packages/nextcloud_push_proxy; dart pub upgrade
run: dart pub upgrade
if: "always() && steps.checkout.conclusion == 'success'"
working-directory: packages/nextcloud_push_proxy
- name: "packages/nextcloud_push_proxy; dart format --output=none --set-exit-if-changed --line-length 120 ."
run: "dart format --output=none --set-exit-if-changed --line-length 120 ."
if: "always() && steps.packages_nextcloud_push_proxy_pub_upgrade.conclusion == 'success'"
working-directory: packages/nextcloud_push_proxy
needs:
- job_001
job_013:
name: "all; PKG: packages/nextcloud_test; `dart format --output=none --set-exit-if-changed --line-length 120 .`" name: "all; PKG: packages/nextcloud_test; `dart format --output=none --set-exit-if-changed --line-length 120 .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -420,7 +356,7 @@ jobs:
working-directory: packages/nextcloud_test working-directory: packages/nextcloud_test
needs: needs:
- job_001 - job_001
job_014: job_012:
name: "all; PKG: packages/settings; `dart format --output=none --set-exit-if-changed --line-length 120 .`" name: "all; PKG: packages/settings; `dart format --output=none --set-exit-if-changed --line-length 120 .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -452,7 +388,7 @@ jobs:
working-directory: packages/settings working-directory: packages/settings
needs: needs:
- job_001 - job_001
job_015: job_013:
name: "all; PKG: packages/sort_box; `dart format --output=none --set-exit-if-changed --line-length 120 .`" name: "all; PKG: packages/sort_box; `dart format --output=none --set-exit-if-changed --line-length 120 .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -484,7 +420,7 @@ jobs:
working-directory: packages/sort_box working-directory: packages/sort_box
needs: needs:
- job_001 - job_001
job_016: job_014:
name: "all; PKG: packages/spec_templates; `dart format --output=none --set-exit-if-changed --line-length 120 .`" name: "all; PKG: packages/spec_templates; `dart format --output=none --set-exit-if-changed --line-length 120 .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -516,7 +452,7 @@ jobs:
working-directory: packages/spec_templates working-directory: packages/spec_templates
needs: needs:
- job_001 - job_001
job_017: job_015:
name: "all; PKG: packages/file_icons; `flutter analyze --fatal-infos .`" name: "all; PKG: packages/file_icons; `flutter analyze --fatal-infos .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -548,7 +484,7 @@ jobs:
working-directory: packages/file_icons working-directory: packages/file_icons
needs: needs:
- job_001 - job_001
job_018: job_016:
name: "all; PKG: packages/neon; `flutter analyze --fatal-infos .`" name: "all; PKG: packages/neon; `flutter analyze --fatal-infos .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -580,7 +516,7 @@ jobs:
working-directory: packages/neon working-directory: packages/neon
needs: needs:
- job_001 - job_001
job_019: job_017:
name: "all; PKG: packages/settings; `flutter analyze --fatal-infos .`" name: "all; PKG: packages/settings; `flutter analyze --fatal-infos .`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -612,7 +548,7 @@ jobs:
working-directory: packages/settings working-directory: packages/settings
needs: needs:
- job_001 - job_001
job_020: job_018:
name: "all; PKG: packages/nextcloud; `dart test`" name: "all; PKG: packages/nextcloud; `dart test`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -644,7 +580,7 @@ jobs:
working-directory: packages/nextcloud working-directory: packages/nextcloud
needs: needs:
- job_001 - job_001
job_021: job_019:
name: "all; PKG: packages/sort_box; `dart test`" name: "all; PKG: packages/sort_box; `dart test`"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

60
packages/nextcloud/lib/src/helpers.dart

@ -2,64 +2,24 @@
part of '../nextcloud.dart'; part of '../nextcloud.dart';
extension UserDetailsDisplayName on openapi.NextcloudProvisioningApiUserDetails { /// Generates the push token hash which is just sha512
/// This is used to work around an API change that wasn't made for every endpoint String generatePushTokenHash(final String pushToken) => sha512.convert(utf8.encode(pushToken)).toString();
/// See https://github.com/nextcloud/server/commit/5086335643b6181284ee50f57b95525002842992
String? getDisplayName() => displayname ?? displayName;
}
extension NextcloudNotificationsPushProxy on NextcloudNotificationsClient {
/// Registers a device at the push proxy server
Future registerDeviceAtPushProxy(
final String pushToken,
final NextcloudNotificationsPushServerSubscription subscription,
final String proxyServer,
) async {
final request = await HttpClient().postUrl(Uri.parse('${proxyServer}devices'))
..followRedirects = false
..persistentConnection = true;
request.headers.add(HttpHeaders.contentTypeHeader, 'application/x-www-form-urlencoded');
request.add(
utf8.encode(
Uri(
queryParameters: {
'pushToken': pushToken,
'deviceIdentifier': subscription.deviceIdentifier,
'deviceIdentifierSignature': subscription.signature,
'userPublicKey': subscription.publicKey,
},
).query,
),
);
final response = await request.close();
if (response.statusCode != 200) {
// coverage:ignore-start
throw NextcloudApiException(
response.statusCode,
{}, // TODO
await response.bodyBytes,
);
// coverage:ignore-end
}
}
/// Generates the push token hash which is just sha512
String generatePushTokenHash(final String pushToken) => sha512.convert(utf8.encode(pushToken)).toString();
}
/// Decrypts the subject of a push notification /// Decrypts the subject of a push notification
NextcloudNotificationsPushNotificationDecryptedSubject decryptPushNotificationSubject( NextcloudNotificationsNotificationDecryptedSubject decryptPushNotificationSubject(
final RSAPrivateKey privateKey, final RSAPrivateKey privateKey,
final String subject, final String subject,
) => ) =>
NextcloudNotificationsPushNotificationDecryptedSubject.fromJson( NextcloudNotificationsNotificationDecryptedSubject.fromJson(
json.decode(privateKey.decrypt(subject)) as Map<String, dynamic>, json.decode(privateKey.decrypt(subject)) as Map<String, dynamic>,
); );
extension UserDetailsDisplayName on openapi.NextcloudProvisioningApiUserDetails {
/// This is used to work around an API change that wasn't made for every endpoint
/// See https://github.com/nextcloud/server/commit/5086335643b6181284ee50f57b95525002842992
String? getDisplayName() => displayname ?? displayName;
}
/// See https://github.com/nextcloud/news/blob/4a107b3d53c4fe651ac704251b99e04a53cd587f/lib/Db/ListType.php /// See https://github.com/nextcloud/news/blob/4a107b3d53c4fe651ac704251b99e04a53cd587f/lib/Db/ListType.php
enum NewsListType { enum NewsListType {
feed(0), feed(0),

70
packages/nextcloud/lib/src/nextcloud.openapi.dart

@ -5178,8 +5178,8 @@ class NextcloudUserStatusPredefinedStatuses {
} }
@JsonSerializable() @JsonSerializable()
class NextcloudNotificationsPushNotificationDecryptedSubject { class NextcloudNotificationsNotificationDecryptedSubject {
NextcloudNotificationsPushNotificationDecryptedSubject({ NextcloudNotificationsNotificationDecryptedSubject({
this.nid, this.nid,
this.app, this.app,
this.subject, this.subject,
@ -5190,13 +5190,13 @@ class NextcloudNotificationsPushNotificationDecryptedSubject {
}); });
// coverage:ignore-start // coverage:ignore-start
factory NextcloudNotificationsPushNotificationDecryptedSubject.fromJson(Map<String, dynamic> json) => factory NextcloudNotificationsNotificationDecryptedSubject.fromJson(Map<String, dynamic> json) =>
_$NextcloudNotificationsPushNotificationDecryptedSubjectFromJson(json); _$NextcloudNotificationsNotificationDecryptedSubjectFromJson(json);
// coverage:ignore-end // coverage:ignore-end
// coverage:ignore-start // coverage:ignore-start
factory NextcloudNotificationsPushNotificationDecryptedSubject.fromJsonString(String data) => factory NextcloudNotificationsNotificationDecryptedSubject.fromJsonString(String data) =>
NextcloudNotificationsPushNotificationDecryptedSubject.fromJson(json.decode(data) as Map<String, dynamic>); NextcloudNotificationsNotificationDecryptedSubject.fromJson(json.decode(data) as Map<String, dynamic>);
// coverage:ignore-end // coverage:ignore-end
final int? nid; final int? nid;
@ -5215,43 +5215,9 @@ class NextcloudNotificationsPushNotificationDecryptedSubject {
final bool? deleteAll; final bool? deleteAll;
// coverage:ignore-start // coverage:ignore-start
Map<String, dynamic> toJson() => _$NextcloudNotificationsPushNotificationDecryptedSubjectToJson(this); Map<String, dynamic> toJson() => _$NextcloudNotificationsNotificationDecryptedSubjectToJson(this);
// coverage:ignore-end // coverage:ignore-end
static String? toJsonString(NextcloudNotificationsPushNotificationDecryptedSubject? data) => static String? toJsonString(NextcloudNotificationsNotificationDecryptedSubject? data) =>
data == null ? null : json.encode(data.toJson());
}
@JsonSerializable()
class NextcloudNotificationsPushNotification {
NextcloudNotificationsPushNotification({
required this.accountID,
required this.priority,
required this.type,
required this.subject,
});
// coverage:ignore-start
factory NextcloudNotificationsPushNotification.fromJson(Map<String, dynamic> json) =>
_$NextcloudNotificationsPushNotificationFromJson(json);
// coverage:ignore-end
// coverage:ignore-start
factory NextcloudNotificationsPushNotification.fromJsonString(String data) =>
NextcloudNotificationsPushNotification.fromJson(json.decode(data) as Map<String, dynamic>);
// coverage:ignore-end
final String accountID;
final String priority;
final String type;
final NextcloudNotificationsPushNotificationDecryptedSubject subject;
// coverage:ignore-start
Map<String, dynamic> toJson() => _$NextcloudNotificationsPushNotificationToJson(this);
// coverage:ignore-end
static String? toJsonString(NextcloudNotificationsPushNotification? data) =>
data == null ? null : json.encode(data.toJson()); data == null ? null : json.encode(data.toJson());
} }
@ -5773,15 +5739,10 @@ final _deserializers = <Type, dynamic Function(dynamic)>{
NextcloudUserStatusPredefinedStatus.fromJson(data as Map<String, dynamic>), NextcloudUserStatusPredefinedStatus.fromJson(data as Map<String, dynamic>),
List<NextcloudUserStatusPredefinedStatus>: (final data) => List<NextcloudUserStatusPredefinedStatus>: (final data) =>
(data as List).map((final e) => NextcloudUserStatusPredefinedStatus.fromJson(e as Map<String, dynamic>)).toList(), (data as List).map((final e) => NextcloudUserStatusPredefinedStatus.fromJson(e as Map<String, dynamic>)).toList(),
NextcloudNotificationsPushNotification: (final data) => NextcloudNotificationsNotificationDecryptedSubject: (final data) =>
NextcloudNotificationsPushNotification.fromJson(data as Map<String, dynamic>), NextcloudNotificationsNotificationDecryptedSubject.fromJson(data as Map<String, dynamic>),
List<NextcloudNotificationsPushNotification>: (final data) => (data as List) List<NextcloudNotificationsNotificationDecryptedSubject>: (final data) => (data as List)
.map((final e) => NextcloudNotificationsPushNotification.fromJson(e as Map<String, dynamic>)) .map((final e) => NextcloudNotificationsNotificationDecryptedSubject.fromJson(e as Map<String, dynamic>))
.toList(),
NextcloudNotificationsPushNotificationDecryptedSubject: (final data) =>
NextcloudNotificationsPushNotificationDecryptedSubject.fromJson(data as Map<String, dynamic>),
List<NextcloudNotificationsPushNotificationDecryptedSubject>: (final data) => (data as List)
.map((final e) => NextcloudNotificationsPushNotificationDecryptedSubject.fromJson(e as Map<String, dynamic>))
.toList(), .toList(),
}; };
@ -6036,11 +5997,8 @@ final _serializers = <Type, dynamic Function(dynamic)>{
List<NextcloudUserStatusPredefinedStatuses_Ocs>: (final data) => data.map((final e) => e.toJson()).toList(), List<NextcloudUserStatusPredefinedStatuses_Ocs>: (final data) => data.map((final e) => e.toJson()).toList(),
NextcloudUserStatusPredefinedStatus: (final data) => data.toJson(), NextcloudUserStatusPredefinedStatus: (final data) => data.toJson(),
List<NextcloudUserStatusPredefinedStatus>: (final data) => data.map((final e) => e.toJson()).toList(), List<NextcloudUserStatusPredefinedStatus>: (final data) => data.map((final e) => e.toJson()).toList(),
NextcloudNotificationsPushNotification: (final data) => data.toJson(), NextcloudNotificationsNotificationDecryptedSubject: (final data) => data.toJson(),
List<NextcloudNotificationsPushNotification>: (final data) => data.map((final e) => e.toJson()).toList(), List<NextcloudNotificationsNotificationDecryptedSubject>: (final data) => data.map((final e) => e.toJson()).toList(),
NextcloudNotificationsPushNotificationDecryptedSubject: (final data) => data.toJson(),
List<NextcloudNotificationsPushNotificationDecryptedSubject>: (final data) =>
data.map((final e) => e.toJson()).toList(),
}; };
T deserializeNextcloud<T>(final dynamic data) => _deserializers[T]!(data) as T; T deserializeNextcloud<T>(final dynamic data) => _deserializers[T]!(data) as T;

29
packages/nextcloud/lib/src/nextcloud.openapi.g.dart

@ -2193,13 +2193,13 @@ Map<String, dynamic> _$NextcloudUserStatusPredefinedStatusesToJson(NextcloudUser
'ocs': instance.ocs.toJson(), 'ocs': instance.ocs.toJson(),
}; };
NextcloudNotificationsPushNotificationDecryptedSubject _$NextcloudNotificationsPushNotificationDecryptedSubjectFromJson( NextcloudNotificationsNotificationDecryptedSubject _$NextcloudNotificationsNotificationDecryptedSubjectFromJson(
Map<String, dynamic> json) { Map<String, dynamic> json) {
$checkKeys( $checkKeys(
json, json,
allowedKeys: const ['nid', 'app', 'subject', 'type', 'id', 'delete', 'delete-all'], allowedKeys: const ['nid', 'app', 'subject', 'type', 'id', 'delete', 'delete-all'],
); );
return NextcloudNotificationsPushNotificationDecryptedSubject( return NextcloudNotificationsNotificationDecryptedSubject(
nid: json['nid'] as int?, nid: json['nid'] as int?,
app: json['app'] as String?, app: json['app'] as String?,
subject: json['subject'] as String?, subject: json['subject'] as String?,
@ -2210,8 +2210,8 @@ NextcloudNotificationsPushNotificationDecryptedSubject _$NextcloudNotificationsP
); );
} }
Map<String, dynamic> _$NextcloudNotificationsPushNotificationDecryptedSubjectToJson( Map<String, dynamic> _$NextcloudNotificationsNotificationDecryptedSubjectToJson(
NextcloudNotificationsPushNotificationDecryptedSubject instance) => NextcloudNotificationsNotificationDecryptedSubject instance) =>
<String, dynamic>{ <String, dynamic>{
'nid': instance.nid, 'nid': instance.nid,
'app': instance.app, 'app': instance.app,
@ -2221,24 +2221,3 @@ Map<String, dynamic> _$NextcloudNotificationsPushNotificationDecryptedSubjectToJ
'delete': instance.delete, 'delete': instance.delete,
'delete-all': instance.deleteAll, 'delete-all': instance.deleteAll,
}; };
NextcloudNotificationsPushNotification _$NextcloudNotificationsPushNotificationFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
allowedKeys: const ['accountID', 'priority', 'type', 'subject'],
);
return NextcloudNotificationsPushNotification(
accountID: json['accountID'] as String,
priority: json['priority'] as String,
type: json['type'] as String,
subject: NextcloudNotificationsPushNotificationDecryptedSubject.fromJson(json['subject'] as Map<String, dynamic>),
);
}
Map<String, dynamic> _$NextcloudNotificationsPushNotificationToJson(NextcloudNotificationsPushNotification instance) =>
<String, dynamic>{
'accountID': instance.accountID,
'priority': instance.priority,
'type': instance.type,
'subject': instance.subject.toJson(),
};

25
packages/nextcloud/lib/src/nextcloud.openapi.json

@ -1471,30 +1471,7 @@
} }
} }
}, },
"NotificationsPushNotification": { "NotificationsNotificationDecryptedSubject": {
"type": "object",
"required": [
"accountID",
"priority",
"type",
"subject"
],
"properties": {
"accountID": {
"type": "string"
},
"priority": {
"type": "string"
},
"type": {
"type": "string"
},
"subject": {
"$ref": "#/components/schemas/NotificationsPushNotificationDecryptedSubject"
}
}
},
"NotificationsPushNotificationDecryptedSubject": {
"type": "object", "type": "object",
"properties": { "properties": {
"nid": { "nid": {

4
packages/nextcloud/pubspec.yaml

@ -6,7 +6,7 @@ environment:
dependencies: dependencies:
cookie_jar: ^3.0.1 cookie_jar: ^3.0.1
crypto: ^3.0.1 crypto: ^3.0.2
crypton: ^2.0.5 crypton: ^2.0.5
intl: ^0.17.0 intl: ^0.17.0
json_annotation: ^4.7.0 json_annotation: ^4.7.0
@ -19,8 +19,6 @@ dev_dependencies:
dynamite: dynamite:
path: ../dynamite path: ../dynamite
json_serializable: ^6.3.2 json_serializable: ^6.3.2
nextcloud_push_proxy:
path: ../nextcloud_push_proxy
nextcloud_test: nextcloud_test:
path: ../nextcloud_test path: ../nextcloud_test
nit_picking: nit_picking:

89
packages/nextcloud/test/notifications.dart

@ -1,8 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud_push_proxy/nextcloud_push_proxy.dart';
import 'package:nextcloud_test/nextcloud_test.dart'; import 'package:nextcloud_test/nextcloud_test.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
@ -117,95 +115,12 @@ Future run(final DockerImage image) async {
// ignore: avoid_redundant_argument_values // ignore: avoid_redundant_argument_values
RSAKeypair generateKeypair() => RSAKeypair.fromRandom(keySize: 2048); RSAKeypair generateKeypair() => RSAKeypair.fromRandom(keySize: 2048);
test('Register device and receive notification', () async { test('Register and remove push device', () async {
const pushToken = '789';
final keypair = generateKeypair();
final pushProxy = NextcloudPushProxy();
late int port;
while (true) {
port = randomPort();
try {
await pushProxy.create(
logging: false,
port: port,
);
break;
} on SocketException catch (e) {
if (e.osError?.errorCode != 98) {
rethrow;
}
}
}
final subscription = (await client.notifications.registerDevice(
pushTokenHash: client.notifications.generatePushTokenHash(pushToken),
devicePublicKey: keypair.publicKey.toFormattedPEM(),
proxyServer: 'http://host.docker.internal:$port/',
))
.ocs
.data;
expect(subscription.publicKey, hasLength(451));
RSAPublicKey.fromPEM(subscription.publicKey);
expect(subscription.deviceIdentifier, isNotEmpty);
expect(subscription.signature, isNotEmpty);
expect(subscription.message, isNull);
final deviceCompleter = Completer();
final notificationCompleter = Completer();
pushProxy.onNewDevice.listen((final device) async {
expect(device.pushToken, pushToken);
expect(device.deviceIdentifier, isNotEmpty);
expect(device.deviceIdentifierSignature, isNotEmpty);
expect(device.userPublicKey, isNotEmpty);
deviceCompleter.complete();
});
pushProxy.onNewNotification.listen((final notification) async {
expect(notification.deviceIdentifier, subscription.deviceIdentifier);
expect(notification.pushTokenHash, client.notifications.generatePushTokenHash(pushToken));
expect(notification.subject, isNotEmpty);
expect(notification.signature, isNotEmpty);
expect(notification.priority, 'normal');
expect(notification.type, 'alert');
final decryptedSubject = decryptPushNotificationSubject(
keypair.privateKey,
notification.subject,
);
expect(decryptedSubject.nid, isNotNull);
expect(decryptedSubject.app, 'admin_notifications');
expect(decryptedSubject.subject, '123');
expect(decryptedSubject.type, 'admin_notifications');
expect(decryptedSubject.id, isNotEmpty);
notificationCompleter.complete();
});
await client.notifications.registerDeviceAtPushProxy(
pushToken,
subscription,
'http://localhost:$port/',
);
await client.notifications.sendAdminNotification(
userId: 'admin',
shortMessage: '123',
longMessage: '456',
);
await deviceCompleter.future;
await notificationCompleter.future;
await pushProxy.close();
});
test('Remove push device', () async {
const pushToken = '789'; const pushToken = '789';
final keypair = generateKeypair(); final keypair = generateKeypair();
final subscription = (await client.notifications.registerDevice( final subscription = (await client.notifications.registerDevice(
pushTokenHash: client.notifications.generatePushTokenHash(pushToken), pushTokenHash: generatePushTokenHash(pushToken),
devicePublicKey: keypair.publicKey.toFormattedPEM(), devicePublicKey: keypair.publicKey.toFormattedPEM(),
proxyServer: 'https://example.com/', proxyServer: 'https://example.com/',
)) ))

12
packages/nextcloud_push_proxy/.gitignore vendored

@ -1,12 +0,0 @@
# Files and directories created by pub.
.dart_tool/
.packages
# Conventional directory for build outputs.
build/
# Omit committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock
tmp/

17
packages/nextcloud_push_proxy/Dockerfile

@ -1,17 +0,0 @@
FROM dart:stable as builder
WORKDIR /app
ADD pubspec.yaml .
RUN dart pub get
ADD lib ./lib
ADD bin ./bin
RUN dart compile exe bin/unified_push.dart -o bin/nextcloud-push-proxy
FROM debian:bullseye-slim
COPY --from=builder /app/bin/nextcloud-push-proxy /usr/local/bin/
CMD ["nextcloud-push-proxy", "/data/devices.json"]

1
packages/nextcloud_push_proxy/LICENSE

@ -1 +0,0 @@
../../LICENSE

1
packages/nextcloud_push_proxy/analysis_options.yaml

@ -1 +0,0 @@
include: package:nit_picking/dart.yaml

68
packages/nextcloud_push_proxy/bin/unified_push.dart

@ -1,68 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:nextcloud_push_proxy/nextcloud_push_proxy.dart';
Future main(final List<String> args) async {
if (args.length != 1) {
throw Exception('Provide the file where to store devices');
}
final devices = <PushProxyDevice>[];
final devicesFile = File(args[0]);
if (devicesFile.existsSync()) {
devices.addAll(
(json.decode(devicesFile.readAsStringSync()) as List)
.map((final d) => PushProxyDevice.fromJson(d as Map<String, dynamic>)),
);
}
final server = NextcloudPushProxy();
watchSignals((final signal) async {
print('Got exit signal, shutting down');
await server.close();
exit(1);
});
await server.create();
server.onNewDevice.listen((final device) {
if (!devices.map((final d) => d.pushToken).contains(device.pushToken)) {
devices.add(device);
devicesFile
..createSync(recursive: true)
..writeAsString(json.encode(devices.map((final d) => d.toJson()).toList()));
}
});
server.onNewNotification.listen((final notification) async {
for (final device in devices) {
if (notification.pushTokenHash == sha512.convert(utf8.encode(device.pushToken)).toString()) {
final request = await HttpClient().postUrl(Uri.parse(device.pushToken))
..followRedirects = false
..persistentConnection = true
..add(utf8.encode(json.encode(notification.toPushNotificationData())));
final response = await request.close();
if (response.statusCode > 299) {
print('Failed to send notification');
}
}
}
});
print('Listening on *:8080');
}
void watchSignals(final Function(ProcessSignal signal) callback) {
for (final signal in [
ProcessSignal.sighup,
ProcessSignal.sigint,
ProcessSignal.sigterm,
ProcessSignal.sigusr1,
ProcessSignal.sigusr2,
]) {
signal.watch().listen(callback);
}
}

163
packages/nextcloud_push_proxy/lib/nextcloud_push_proxy.dart

@ -1,163 +0,0 @@
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart';
import 'package:shelf_router/shelf_router.dart';
/// Implements the listening part of a Nextcloud push proxy
class NextcloudPushProxy {
HttpServer? _server;
late StreamController<PushProxyDevice> _onNewDeviceController;
late StreamController<PushProxyNotification> _onNewNotificationController;
Stream<PushProxyDevice>? _onNewDeviceStream;
Stream<PushProxyNotification>? _onNewNotificationStream;
/// Listens for new devices
Stream<PushProxyDevice> get onNewDevice {
if (_onNewDeviceStream == null) {
throw Exception('Server not created');
}
return _onNewDeviceStream!;
}
/// Listens for new notifications
Stream<PushProxyNotification> get onNewNotification {
if (_onNewNotificationStream == null) {
throw Exception('Server not created');
}
return _onNewNotificationStream!;
}
late final _router = Router()
..post('/devices', _devicesHandler)
..post('/notifications', _notificationsHandler)
..get('/health', (final _) async => Response.ok(''));
Future<Response> _devicesHandler(final Request request) async {
final data = Uri(query: await request.readAsString()).queryParameters;
_onNewDeviceController.add(
PushProxyDevice(
pushToken: data['pushToken']!,
deviceIdentifier: data['deviceIdentifier']!,
deviceIdentifierSignature: data['deviceIdentifierSignature']!,
userPublicKey: data['userPublicKey']!,
),
);
return Response.ok('');
}
Future<Response> _notificationsHandler(final Request request) async {
final data = Uri(query: await request.readAsString()).queryParameters;
for (final notification in data.values) {
final notificationData = json.decode(notification) as Map<String, dynamic>;
_onNewNotificationController.add(
PushProxyNotification(
deviceIdentifier: notificationData['deviceIdentifier']! as String,
pushTokenHash: notificationData['pushTokenHash']! as String,
subject: notificationData['subject']! as String,
signature: notificationData['signature']! as String,
priority: notificationData['priority']! as String,
type: notificationData['type']! as String,
),
);
}
return Response.ok('');
}
/// Creates a server listening on the [port]
Future create({
final bool logging = true,
final int port = 8080,
}) async {
if (_server != null) {
throw Exception('Server already created');
}
_onNewDeviceController = StreamController<PushProxyDevice>();
_onNewNotificationController = StreamController<PushProxyNotification>();
_onNewDeviceStream = _onNewDeviceController.stream.asBroadcastStream();
_onNewNotificationStream = _onNewNotificationController.stream.asBroadcastStream();
var handler = Cascade().add(_router.call).handler;
if (logging) {
handler = logRequests().addHandler(handler);
}
final server = await serve(
handler,
InternetAddress.anyIPv4,
port,
);
server.autoCompress = true;
_server = server;
}
/// Closes the server
Future close() async {
if (_server != null) {
await _server!.close();
_server = null;
await _onNewDeviceController.close();
await _onNewNotificationController.close();
}
}
}
class PushProxyDevice {
PushProxyDevice({
required this.pushToken,
required this.deviceIdentifier,
required this.deviceIdentifierSignature,
required this.userPublicKey,
});
factory PushProxyDevice.fromJson(final Map<String, dynamic> data) => PushProxyDevice(
pushToken: data['pushToken'] as String,
deviceIdentifier: data['deviceIdentifier'] as String,
deviceIdentifierSignature: data['deviceIdentifierSignature'] as String,
userPublicKey: data['userPublicKey'] as String,
);
Map<String, dynamic> toJson() => {
'pushToken': pushToken,
'deviceIdentifier': deviceIdentifier,
'deviceIdentifierSignature': deviceIdentifierSignature,
'userPublicKey': userPublicKey,
};
final String pushToken;
final String deviceIdentifier;
final String deviceIdentifierSignature;
final String userPublicKey;
}
class PushProxyNotification {
PushProxyNotification({
required this.deviceIdentifier,
required this.pushTokenHash,
required this.subject,
required this.signature,
required this.priority,
required this.type,
});
final String deviceIdentifier;
final String pushTokenHash;
final String subject;
final String signature;
final String priority;
final String type;
Map<String, String> toPushNotificationData() => {
'subject': subject,
'signature': signature,
'priority': priority,
'type': type,
};
}

7
packages/nextcloud_push_proxy/mono_pkg.yaml

@ -1,7 +0,0 @@
sdk:
- stable
stages:
- all:
- analyze: --fatal-infos .
- format: --output=none --set-exit-if-changed --line-length 120 .

16
packages/nextcloud_push_proxy/pubspec.yaml

@ -1,16 +0,0 @@
name: nextcloud_push_proxy
version: 1.0.0
environment:
sdk: '>=2.19.0 <3.0.0'
dependencies:
crypto: ^3.0.2
shelf: ^1.3.1
shelf_router: ^1.1.3
dev_dependencies:
nit_picking:
git:
url: https://github.com/stack11/dart_nit_picking
ref: 0b2ee0d

25
specs/notifications.json

@ -277,30 +277,7 @@
} }
} }
}, },
"PushNotification": { "NotificationDecryptedSubject": {
"type": "object",
"required": [
"accountID",
"priority",
"type",
"subject"
],
"properties": {
"accountID": {
"type": "string"
},
"priority": {
"type": "string"
},
"type": {
"type": "string"
},
"subject": {
"$ref": "#/components/schemas/PushNotificationDecryptedSubject"
}
}
},
"PushNotificationDecryptedSubject": {
"type": "object", "type": "object",
"properties": { "properties": {
"nid": { "nid": {

4
tool/run-dev-instance.sh

@ -20,10 +20,6 @@ fi
echo "TEST_HOST=$ip echo "TEST_HOST=$ip
TEST_USER=user1 TEST_USER=user1
TEST_PASSWORD=user1" > packages/neon/assets/.env TEST_PASSWORD=user1" > packages/neon/assets/.env
(
cd packages/nextcloud_push_proxy
fvm dart run bin/unified_push.dart ./tmp/devices.json
) &
function cleanup() { function cleanup() {
rm packages/neon/assets/.env rm packages/neon/assets/.env

Loading…
Cancel
Save