Skip to content

Commit

Permalink
fix(notifications_push_repository): Delete push subscription when acc…
Browse files Browse the repository at this point in the history
…ount is deleted

Signed-off-by: provokateurin <[email protected]>
  • Loading branch information
provokateurin committed Nov 20, 2024
1 parent 3a7c457 commit 20c0c0d
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ class NotificationsPushRepository {
required OnMessageCallback onMessage,
}) : _accountRepository = accountRepository,
_storage = storage,
_onMessage = onMessage;
_onMessage = onMessage {
_accountRepository.registerBeforeLogOutCallback(_beforeLogOutCallback);
}

final AccountRepository _accountRepository;
final NotificationsPushStorage _storage;
Expand All @@ -48,6 +50,28 @@ class NotificationsPushRepository {
/// Closes all open resources of the repository.
void close() {
unawaited(_accountsListener?.cancel());
_accountRepository.unregisterBeforeLogOutCallback(_beforeLogOutCallback);
}

Future<void> _beforeLogOutCallback(Account account) async {
final subscriptions = await _storage.readSubscriptions();
var subscription = subscriptions[account.id];
if (subscription == null) {
return;
}

final pushDevice = subscription.pushDevice;
if (pushDevice != null) {
await _unregisterNextcloud(account.id, account, pushDevice);
subscription = subscription.rebuild((b) => b.pushDevice = null);
}

if (subscription.endpoint != null) {
await _unregisterUnifiedPush(account.id);
subscription = subscription.rebuild((b) => b.endpoint = null);
}

await _storage.updateSubscription(account.id, subscription);
}

/// Changes the used distributor to the new [distributor].
Expand Down Expand Up @@ -146,21 +170,21 @@ class NotificationsPushRepository {
if (_selectedDistributor == null) {
_log.fine('Push notifications disabled, removing all subscriptions');

await _unregisterUnifiedPush();
await _unregisterAllUnifiedPush();
return;
}

if (distributorChanged) {
_log.finer('UnifiedPush distributor changed to $_selectedDistributor');

await _unregisterUnifiedPush();
await _unregisterAllUnifiedPush();
await UnifiedPush.saveDistributor(_selectedDistributor!);
}

await _registerUnifiedPush();
await _registerAllUnifiedPush();
}

Future<void> _registerUnifiedPush() async {
Future<void> _registerAllUnifiedPush() async {
// Notifications will only work on accounts with app password
final accounts = (await _accountRepository.accounts.first).accounts;
for (final account in accounts.where((a) => a.credentials.appPassword != null)) {
Expand All @@ -170,7 +194,7 @@ class NotificationsPushRepository {
}
}

Future<void> _unregisterUnifiedPush() async {
Future<void> _unregisterAllUnifiedPush() async {
final subscriptions = await _storage.readSubscriptions();
for (final entry in subscriptions.entries) {
final accountID = entry.key;
Expand All @@ -184,10 +208,7 @@ class NotificationsPushRepository {
}

if (subscription.endpoint != null) {
_log.finer('Unregistering $accountID from UnifiedPush');

await UnifiedPush.unregister(accountID);

await _unregisterUnifiedPush(accountID);
subscription = subscription.rebuild((b) => b.endpoint = null);
}

Expand Down Expand Up @@ -229,4 +250,10 @@ class NotificationsPushRepository {
_log.warning('Failed to unregister $accountID at Nextcloud', error);
}
}

Future<void> _unregisterUnifiedPush(String accountID) async {
_log.finer('Unregistering $accountID from UnifiedPush');

await UnifiedPush.unregister(accountID);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ void main() {
void Function(String, String)? unifiedPushOnNewEndpoint;
void Function(String)? unifiedPushOnUnregistered;
void Function(Uint8List, String)? unifiedPushOnMessage;
late List<BeforeLogOutCallback> beforeLogOutCallbacks;

setUpAll(() {
registerFallbackValue(RSAPrivateKey(BigInt.zero, BigInt.zero, BigInt.zero, BigInt.zero));
Expand All @@ -72,6 +73,7 @@ void main() {
});

setUp(() {
beforeLogOutCallbacks = [];
accountsSubject = BehaviorSubject();
accountRepository = _AccountRepositoryMock();
when(() => accountRepository.accounts).thenAnswer((_) => accountsSubject.map((e) => (active: null, accounts: e)));
Expand All @@ -81,6 +83,12 @@ void main() {
return accountsSubject.value.singleWhereOrNull((account) => account.id == accountID);
},
);
when(() => accountRepository.registerBeforeLogOutCallback(any())).thenAnswer((invocation) {
beforeLogOutCallbacks.add(invocation.positionalArguments.first as BeforeLogOutCallback);
});
when(() => accountRepository.unregisterBeforeLogOutCallback(any())).thenAnswer((invocation) {
beforeLogOutCallbacks.remove(invocation.positionalArguments.first as BeforeLogOutCallback);
});

storage = _StorageMock();

Expand All @@ -104,6 +112,7 @@ void main() {

tearDown(() {
repository.close();
expect(beforeLogOutCallbacks, isEmpty);
unawaited(accountsSubject.close());
});

Expand Down Expand Up @@ -1728,6 +1737,122 @@ void main() {
});
});
});

group('Removes subscription when account is deleted', () {
test('Success at Nextcloud', () async {
when(
() => httpRequest(
'DELETE',
Uri.parse('https://cloud.example.com:8443/nextcloud/ocs/v2.php/apps/notifications/api/v2/push'),
any(),
any(),
),
).thenAnswer(
(_) => http.StreamedResponse(
Stream.value(
utf8.encode(
json.encode(
{
'ocs': {
'meta': {
'status': '',
'statuscode': 0,
},
'data': <dynamic, dynamic>{},
},
},
),
),
),
200,
headers: {
'content-type': 'application/json; charset=utf-8',
},
),
);

repository = NotificationsPushRepository(
accountRepository: accountRepository,
storage: storage,
onMessage: onMessageCallback,
);
await repository.initialize();

verify(() => unifiedPushPlatform.registerApp(account.id, [])).called(1);
verifyNever(() => unifiedPushPlatform.registerApp(any(), any()));

for (final callback in beforeLogOutCallbacks) {
await callback(account);
}

verify(() => unifiedPushPlatform.unregister(account.id)).called(1);
verifyNever(() => unifiedPushPlatform.unregister(any()));
verify(
() => httpRequest(
'DELETE',
Uri.parse('https://cloud.example.com:8443/nextcloud/ocs/v2.php/apps/notifications/api/v2/push'),
BuiltMap(
{
'Accept': 'application/json',
'Authorization': 'Bearer user1',
'OCS-APIRequest': 'true',
'user-agent': 'neon',
},
),
Uint8List(0),
),
).called(1);
verifyNever(() => httpRequest(any(), any(), any(), any()));
verify(() => storage.updateSubscription(account.id, PushSubscription())).called(1);
verifyNever(() => storage.updateSubscription(any(), any()));
});

test('Failure at Nextcloud', () async {
when(
() => httpRequest(
'DELETE',
Uri.parse('https://cloud.example.com:8443/nextcloud/ocs/v2.php/apps/notifications/api/v2/push'),
any(),
any(),
),
).thenAnswer((_) => http.StreamedResponse(const Stream.empty(), 500));

repository = NotificationsPushRepository(
accountRepository: accountRepository,
storage: storage,
onMessage: onMessageCallback,
);
await repository.initialize();

verify(() => unifiedPushPlatform.registerApp(account.id, [])).called(1);
verifyNever(() => unifiedPushPlatform.registerApp(any(), any()));

for (final callback in beforeLogOutCallbacks) {
await callback(account);
}

verify(() => unifiedPushPlatform.unregister(account.id)).called(1);
verifyNever(() => unifiedPushPlatform.unregister(any()));
verify(
() => httpRequest(
'DELETE',
Uri.parse('https://cloud.example.com:8443/nextcloud/ocs/v2.php/apps/notifications/api/v2/push'),
BuiltMap(
{
'Accept': 'application/json',
'Authorization': 'Bearer user1',
'OCS-APIRequest': 'true',
'user-agent': 'neon',
},
),
Uint8List(0),
),
).called(1);
verifyNever(() => httpRequest(any(), any(), any(), any()));
verify(() => storage.updateSubscription(account.id, PushSubscription())).called(1);
verifyNever(() => storage.updateSubscription(any(), any()));
});
});
});
});
});
Expand Down

0 comments on commit 20c0c0d

Please sign in to comment.