Skip to content

Commit

Permalink
feat(account_repository): Implement remote wipe
Browse files Browse the repository at this point in the history
Signed-off-by: provokateurin <[email protected]>
  • Loading branch information
provokateurin committed Nov 19, 2024
1 parent 7903cd3 commit 783daa2
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ final class DeleteCredentialsFailure extends AccountFailure {
const DeleteCredentialsFailure(super.error);
}

/// {@template get_remote_wipe_status_failure}
/// Thrown when getting the device remote wipe status fails.
/// {@endtemplate}
final class GetRemoteWipeStatusFailure extends AccountFailure {
/// {@macro get_remote_wipe_status_failure}
const GetRemoteWipeStatusFailure(super.error);
}

/// {@template post_remote_wipe_success_failure}
/// Thrown when posting the device remote wipe success fails.
/// {@endtemplate}
final class PostRemoteWipeSuccessFailure extends AccountFailure {
/// {@macro post_remote_wipe_success_failure}
const PostRemoteWipeSuccessFailure(super.error);
}

/// {@template account_repository}
/// A repository that manages the account data.
/// {@endtemplate}
Expand Down Expand Up @@ -321,4 +337,53 @@ class AccountRepository {
_accounts.add((active: accountID, accounts: value.accounts));
await _storage.saveLastAccount(accountID);
}

/// Gets the device remote wipe status.
///
/// May throw a [GetRemoteWipeStatusFailure].
Future<bool> getRemoteWipeStatus(Account account) async {
final client = buildUnauthenticatedClient(
httpClient: _httpClient,
userAgent: _userAgent,
serverURL: account.credentials.serverURL,
);

try {
final response = await client.authentication.wipe.checkWipe(
$body: core.WipeCheckWipeRequestApplicationJson(
(b) => b..token = account.credentials.appPassword,
),
);

// This is always true, as otherwise 404 is returned, but just to be safe in the future use the returned value.
return response.body.wipe;
} on http.ClientException catch (error, stackTrace) {
if (error case DynamiteStatusCodeException() when error.statusCode == 404) {
return false;
}

Error.throwWithStackTrace(GetRemoteWipeStatusFailure(error), stackTrace);
}
}

/// Posts the remote wipe success.
///
/// May throw a [PostRemoteWipeSuccessFailure].
Future<void> postRemoteWipeSuccess(Account account) async {
final client = buildUnauthenticatedClient(
httpClient: _httpClient,
userAgent: _userAgent,
serverURL: account.credentials.serverURL,
);

try {
await client.authentication.wipe.wipeDone(
$body: core.WipeWipeDoneRequestApplicationJson(
(b) => b..token = account.credentials.appPassword,
),
);
} on http.ClientException catch (error, stackTrace) {
Error.throwWithStackTrace(PostRemoteWipeSuccessFailure(error), stackTrace);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class AuthenticationClient {
required this.appPassword,
required this.clientFlowLoginV2,
required this.users,
required this.wipe,
});

final $core.$Client core;
Expand All @@ -25,6 +26,8 @@ class AuthenticationClient {
final $core.$ClientFlowLoginV2Client clientFlowLoginV2;

final $provisioning_api.$UsersClient users;

final $core.$WipeClient wipe;
}

/// Extension for getting the [AuthenticationClient].
Expand All @@ -38,5 +41,6 @@ extension AuthenticationClientExtension on NextcloudClient {
appPassword: core.appPassword,
clientFlowLoginV2: core.clientFlowLoginV2,
users: provisioningApi.users,
wipe: core.wipe,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:account_repository/account_repository.dart';
import 'package:account_repository/src/testing/testing.dart';
import 'package:account_repository/src/utils/authentication_client.dart';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/json_object.dart';
import 'package:built_value_test/matcher.dart';
import 'package:http/http.dart' as http;
import 'package:mocktail/mocktail.dart';
Expand Down Expand Up @@ -38,6 +39,14 @@ class _ClientFlowLoginV2ClientMock extends Mock implements core.$ClientFlowLogin

class _UsersClientMock extends Mock implements provisioning_api.$UsersClient {}

class _WipeClientMock extends Mock implements core.$WipeClient {}

class _WipeCheckResponseMock extends Mock implements core.WipeCheckWipeResponseApplicationJson {}

class _FakeWipeCheckRequest extends Fake implements core.WipeCheckWipeRequestApplicationJson {}

class _FakeWipeDoneRequest extends Fake implements core.WipeWipeDoneRequestApplicationJson {}

class _AccountStorageMock extends Mock implements AccountStorage {}

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

setUpAll(() {
registerFallbackValue(_FakeUri());
registerFallbackValue(_FakePollRequest());
registerFallbackValue(_FakeWipeCheckRequest());
registerFallbackValue(_FakeWipeDoneRequest());
MockNeonStorage();
});

Expand All @@ -62,12 +74,14 @@ void main() {
appPassword = _AppPasswordClientMock();
clientFlowLoginV2 = _ClientFlowLoginV2ClientMock();
users = _UsersClientMock();
wipe = _WipeClientMock();

mockedClient = AuthenticationClient(
core: coreClient,
appPassword: appPassword,
clientFlowLoginV2: clientFlowLoginV2,
users: users,
wipe: wipe,
);

storage = _AccountStorageMock();
Expand Down Expand Up @@ -587,5 +601,127 @@ void main() {
verify(() => storage.saveLastAccount(credentialsList[1].id)).called(1);
});
});

group('getRemoteWipeStatus', () {
group('retrieves remote wipe status from server', () {
test('should wipe', () async {
final wipeCheckResponse = _WipeCheckResponseMock();
when(() => wipeCheckResponse.wipe).thenReturn(true);
final response = _DynamiteResponseMock<_WipeCheckResponseMock, void>();
when(() => response.body).thenReturn(wipeCheckResponse);

when(() => wipe.checkWipe($body: any(named: r'$body'))).thenAnswer((_) async => response);

await expectLater(
repository.getRemoteWipeStatus(accountsList.first),
completion(true),
);

verify(
() => wipe.checkWipe(
$body: any(
named: r'$body',
that: isA<core.WipeCheckWipeRequestApplicationJson>().having(
(b) => b.token,
'token',
'appPassword',
),
),
),
).called(1);
});

test('should not wipe', () async {
when(() => wipe.checkWipe($body: any(named: r'$body')))
.thenThrow(DynamiteStatusCodeException(http.Response('', 404)));

await expectLater(
repository.getRemoteWipeStatus(accountsList.first),
completion(false),
);

verify(
() => wipe.checkWipe(
$body: any(
named: r'$body',
that: isA<core.WipeCheckWipeRequestApplicationJson>().having(
(b) => b.token,
'token',
'appPassword',
),
),
),
).called(1);
});
});

test('rethrows http exceptions as `GetRemoteWipeStatusFailure`', () async {
when(() => wipe.checkWipe($body: any(named: r'$body'))).thenThrow(http.ClientException(''));

await expectLater(
repository.getRemoteWipeStatus(accountsList.first),
throwsA(isA<GetRemoteWipeStatusFailure>().having((e) => e.error, 'error', isA<http.ClientException>())),
);

verify(
() => wipe.checkWipe(
$body: any(
named: r'$body',
that: isA<core.WipeCheckWipeRequestApplicationJson>().having(
(b) => b.token,
'token',
'appPassword',
),
),
),
).called(1);
});
});

group('postRemoteWipeSuccess', () {
test('posts remote wipe success server', () async {
final response = _DynamiteResponseMock<JsonObject, void>();
when(() => response.body).thenReturn(JsonObject(''));

when(() => wipe.wipeDone($body: any(named: r'$body'))).thenAnswer((_) async => response);

await repository.postRemoteWipeSuccess(accountsList.first);

verify(
() => wipe.wipeDone(
$body: any(
named: r'$body',
that: isA<core.WipeWipeDoneRequestApplicationJson>().having(
(b) => b.token,
'token',
'appPassword',
),
),
),
).called(1);
});

test('rethrows http exceptions as `PostRemoteWipeSuccessFailure`', () async {
when(() => wipe.wipeDone($body: any(named: r'$body'))).thenThrow(http.ClientException(''));

await expectLater(
repository.postRemoteWipeSuccess(accountsList.first),
throwsA(isA<PostRemoteWipeSuccessFailure>().having((e) => e.error, 'error', isA<http.ClientException>())),
);

verify(
() => wipe.wipeDone(
$body: any(
named: r'$body',
that: isA<core.WipeWipeDoneRequestApplicationJson>().having(
(b) => b.token,
'token',
'appPassword',
),
),
),
).called(1);
});
});
});
}

0 comments on commit 783daa2

Please sign in to comment.