Skip to content

Commit 783daa2

Browse files
committed
feat(account_repository): Implement remote wipe
Signed-off-by: provokateurin <kate@provokateurin.de>
1 parent 7903cd3 commit 783daa2

File tree

3 files changed

+205
-0
lines changed

3 files changed

+205
-0
lines changed

packages/neon_framework/packages/account_repository/lib/src/account_repository.dart

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,22 @@ final class DeleteCredentialsFailure extends AccountFailure {
7070
const DeleteCredentialsFailure(super.error);
7171
}
7272

73+
/// {@template get_remote_wipe_status_failure}
74+
/// Thrown when getting the device remote wipe status fails.
75+
/// {@endtemplate}
76+
final class GetRemoteWipeStatusFailure extends AccountFailure {
77+
/// {@macro get_remote_wipe_status_failure}
78+
const GetRemoteWipeStatusFailure(super.error);
79+
}
80+
81+
/// {@template post_remote_wipe_success_failure}
82+
/// Thrown when posting the device remote wipe success fails.
83+
/// {@endtemplate}
84+
final class PostRemoteWipeSuccessFailure extends AccountFailure {
85+
/// {@macro post_remote_wipe_success_failure}
86+
const PostRemoteWipeSuccessFailure(super.error);
87+
}
88+
7389
/// {@template account_repository}
7490
/// A repository that manages the account data.
7591
/// {@endtemplate}
@@ -321,4 +337,53 @@ class AccountRepository {
321337
_accounts.add((active: accountID, accounts: value.accounts));
322338
await _storage.saveLastAccount(accountID);
323339
}
340+
341+
/// Gets the device remote wipe status.
342+
///
343+
/// May throw a [GetRemoteWipeStatusFailure].
344+
Future<bool> getRemoteWipeStatus(Account account) async {
345+
final client = buildUnauthenticatedClient(
346+
httpClient: _httpClient,
347+
userAgent: _userAgent,
348+
serverURL: account.credentials.serverURL,
349+
);
350+
351+
try {
352+
final response = await client.authentication.wipe.checkWipe(
353+
$body: core.WipeCheckWipeRequestApplicationJson(
354+
(b) => b..token = account.credentials.appPassword,
355+
),
356+
);
357+
358+
// This is always true, as otherwise 404 is returned, but just to be safe in the future use the returned value.
359+
return response.body.wipe;
360+
} on http.ClientException catch (error, stackTrace) {
361+
if (error case DynamiteStatusCodeException() when error.statusCode == 404) {
362+
return false;
363+
}
364+
365+
Error.throwWithStackTrace(GetRemoteWipeStatusFailure(error), stackTrace);
366+
}
367+
}
368+
369+
/// Posts the remote wipe success.
370+
///
371+
/// May throw a [PostRemoteWipeSuccessFailure].
372+
Future<void> postRemoteWipeSuccess(Account account) async {
373+
final client = buildUnauthenticatedClient(
374+
httpClient: _httpClient,
375+
userAgent: _userAgent,
376+
serverURL: account.credentials.serverURL,
377+
);
378+
379+
try {
380+
await client.authentication.wipe.wipeDone(
381+
$body: core.WipeWipeDoneRequestApplicationJson(
382+
(b) => b..token = account.credentials.appPassword,
383+
),
384+
);
385+
} on http.ClientException catch (error, stackTrace) {
386+
Error.throwWithStackTrace(PostRemoteWipeSuccessFailure(error), stackTrace);
387+
}
388+
}
324389
}

packages/neon_framework/packages/account_repository/lib/src/utils/authentication_client.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class AuthenticationClient {
1616
required this.appPassword,
1717
required this.clientFlowLoginV2,
1818
required this.users,
19+
required this.wipe,
1920
});
2021

2122
final $core.$Client core;
@@ -25,6 +26,8 @@ class AuthenticationClient {
2526
final $core.$ClientFlowLoginV2Client clientFlowLoginV2;
2627

2728
final $provisioning_api.$UsersClient users;
29+
30+
final $core.$WipeClient wipe;
2831
}
2932

3033
/// Extension for getting the [AuthenticationClient].
@@ -38,5 +41,6 @@ extension AuthenticationClientExtension on NextcloudClient {
3841
appPassword: core.appPassword,
3942
clientFlowLoginV2: core.clientFlowLoginV2,
4043
users: provisioningApi.users,
44+
wipe: core.wipe,
4145
);
4246
}

packages/neon_framework/packages/account_repository/test/account_repository_test.dart

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:account_repository/account_repository.dart';
44
import 'package:account_repository/src/testing/testing.dart';
55
import 'package:account_repository/src/utils/authentication_client.dart';
66
import 'package:built_collection/built_collection.dart';
7+
import 'package:built_value/json_object.dart';
78
import 'package:built_value_test/matcher.dart';
89
import 'package:http/http.dart' as http;
910
import 'package:mocktail/mocktail.dart';
@@ -38,6 +39,14 @@ class _ClientFlowLoginV2ClientMock extends Mock implements core.$ClientFlowLogin
3839

3940
class _UsersClientMock extends Mock implements provisioning_api.$UsersClient {}
4041

42+
class _WipeClientMock extends Mock implements core.$WipeClient {}
43+
44+
class _WipeCheckResponseMock extends Mock implements core.WipeCheckWipeResponseApplicationJson {}
45+
46+
class _FakeWipeCheckRequest extends Fake implements core.WipeCheckWipeRequestApplicationJson {}
47+
48+
class _FakeWipeDoneRequest extends Fake implements core.WipeWipeDoneRequestApplicationJson {}
49+
4150
class _AccountStorageMock extends Mock implements AccountStorage {}
4251

4352
typedef _AccountStream = ({BuiltList<Account> accounts, Account? active});
@@ -50,10 +59,13 @@ void main() {
5059
late core.$AppPasswordClient appPassword;
5160
late core.$ClientFlowLoginV2Client clientFlowLoginV2;
5261
late provisioning_api.$UsersClient users;
62+
late core.$WipeClient wipe;
5363

5464
setUpAll(() {
5565
registerFallbackValue(_FakeUri());
5666
registerFallbackValue(_FakePollRequest());
67+
registerFallbackValue(_FakeWipeCheckRequest());
68+
registerFallbackValue(_FakeWipeDoneRequest());
5769
MockNeonStorage();
5870
});
5971

@@ -62,12 +74,14 @@ void main() {
6274
appPassword = _AppPasswordClientMock();
6375
clientFlowLoginV2 = _ClientFlowLoginV2ClientMock();
6476
users = _UsersClientMock();
77+
wipe = _WipeClientMock();
6578

6679
mockedClient = AuthenticationClient(
6780
core: coreClient,
6881
appPassword: appPassword,
6982
clientFlowLoginV2: clientFlowLoginV2,
7083
users: users,
84+
wipe: wipe,
7185
);
7286

7387
storage = _AccountStorageMock();
@@ -587,5 +601,127 @@ void main() {
587601
verify(() => storage.saveLastAccount(credentialsList[1].id)).called(1);
588602
});
589603
});
604+
605+
group('getRemoteWipeStatus', () {
606+
group('retrieves remote wipe status from server', () {
607+
test('should wipe', () async {
608+
final wipeCheckResponse = _WipeCheckResponseMock();
609+
when(() => wipeCheckResponse.wipe).thenReturn(true);
610+
final response = _DynamiteResponseMock<_WipeCheckResponseMock, void>();
611+
when(() => response.body).thenReturn(wipeCheckResponse);
612+
613+
when(() => wipe.checkWipe($body: any(named: r'$body'))).thenAnswer((_) async => response);
614+
615+
await expectLater(
616+
repository.getRemoteWipeStatus(accountsList.first),
617+
completion(true),
618+
);
619+
620+
verify(
621+
() => wipe.checkWipe(
622+
$body: any(
623+
named: r'$body',
624+
that: isA<core.WipeCheckWipeRequestApplicationJson>().having(
625+
(b) => b.token,
626+
'token',
627+
'appPassword',
628+
),
629+
),
630+
),
631+
).called(1);
632+
});
633+
634+
test('should not wipe', () async {
635+
when(() => wipe.checkWipe($body: any(named: r'$body')))
636+
.thenThrow(DynamiteStatusCodeException(http.Response('', 404)));
637+
638+
await expectLater(
639+
repository.getRemoteWipeStatus(accountsList.first),
640+
completion(false),
641+
);
642+
643+
verify(
644+
() => wipe.checkWipe(
645+
$body: any(
646+
named: r'$body',
647+
that: isA<core.WipeCheckWipeRequestApplicationJson>().having(
648+
(b) => b.token,
649+
'token',
650+
'appPassword',
651+
),
652+
),
653+
),
654+
).called(1);
655+
});
656+
});
657+
658+
test('rethrows http exceptions as `GetRemoteWipeStatusFailure`', () async {
659+
when(() => wipe.checkWipe($body: any(named: r'$body'))).thenThrow(http.ClientException(''));
660+
661+
await expectLater(
662+
repository.getRemoteWipeStatus(accountsList.first),
663+
throwsA(isA<GetRemoteWipeStatusFailure>().having((e) => e.error, 'error', isA<http.ClientException>())),
664+
);
665+
666+
verify(
667+
() => wipe.checkWipe(
668+
$body: any(
669+
named: r'$body',
670+
that: isA<core.WipeCheckWipeRequestApplicationJson>().having(
671+
(b) => b.token,
672+
'token',
673+
'appPassword',
674+
),
675+
),
676+
),
677+
).called(1);
678+
});
679+
});
680+
681+
group('postRemoteWipeSuccess', () {
682+
test('posts remote wipe success server', () async {
683+
final response = _DynamiteResponseMock<JsonObject, void>();
684+
when(() => response.body).thenReturn(JsonObject(''));
685+
686+
when(() => wipe.wipeDone($body: any(named: r'$body'))).thenAnswer((_) async => response);
687+
688+
await repository.postRemoteWipeSuccess(accountsList.first);
689+
690+
verify(
691+
() => wipe.wipeDone(
692+
$body: any(
693+
named: r'$body',
694+
that: isA<core.WipeWipeDoneRequestApplicationJson>().having(
695+
(b) => b.token,
696+
'token',
697+
'appPassword',
698+
),
699+
),
700+
),
701+
).called(1);
702+
});
703+
704+
test('rethrows http exceptions as `PostRemoteWipeSuccessFailure`', () async {
705+
when(() => wipe.wipeDone($body: any(named: r'$body'))).thenThrow(http.ClientException(''));
706+
707+
await expectLater(
708+
repository.postRemoteWipeSuccess(accountsList.first),
709+
throwsA(isA<PostRemoteWipeSuccessFailure>().having((e) => e.error, 'error', isA<http.ClientException>())),
710+
);
711+
712+
verify(
713+
() => wipe.wipeDone(
714+
$body: any(
715+
named: r'$body',
716+
that: isA<core.WipeWipeDoneRequestApplicationJson>().having(
717+
(b) => b.token,
718+
'token',
719+
'appPassword',
720+
),
721+
),
722+
),
723+
).called(1);
724+
});
725+
});
590726
});
591727
}

0 commit comments

Comments
 (0)