selfprivacy.org.app/lib/logic/api_maps/rest_maps/backblaze.dart

226 lines
6.8 KiB
Dart
Raw Normal View History

2021-02-03 19:51:07 +00:00
import 'dart:io';
2022-02-16 07:28:29 +00:00
2021-02-03 19:51:07 +00:00
import 'package:dio/dio.dart';
2021-03-25 23:30:34 +00:00
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/models/backup.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
2021-12-06 18:31:19 +00:00
export 'package:selfprivacy/logic/api_maps/generic_result.dart';
2021-12-06 18:31:19 +00:00
class BackblazeApiAuth {
BackblazeApiAuth({required this.authorizationToken, required this.apiUrl});
final String authorizationToken;
final String apiUrl;
}
class BackblazeApplicationKey {
BackblazeApplicationKey({
required this.applicationKeyId,
required this.applicationKey,
});
2021-12-06 18:31:19 +00:00
final String applicationKeyId;
final String applicationKey;
}
2021-02-03 19:51:07 +00:00
class BackblazeApi extends RestApiMap {
2022-02-16 07:28:29 +00:00
BackblazeApi({this.hasLogger = false, this.isWithToken = true});
2021-03-25 23:30:34 +00:00
2022-05-24 18:55:39 +00:00
@override
2021-03-25 23:30:34 +00:00
BaseOptions get options {
final BaseOptions options = BaseOptions(
baseUrl: rootAddress,
contentType: Headers.jsonContentType,
responseType: ResponseType.json,
);
2021-03-25 23:30:34 +00:00
if (isWithToken) {
final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential;
2022-06-05 19:36:32 +00:00
final String token = backblazeCredential!.applicationKey;
2021-03-25 23:30:34 +00:00
options.headers = {'Authorization': 'Basic $token'};
}
if (validateStatus != null) {
options.validateStatus = validateStatus!;
2021-02-03 19:51:07 +00:00
}
2021-03-25 23:30:34 +00:00
return options;
2021-02-03 19:51:07 +00:00
}
@override
2021-03-25 23:30:34 +00:00
String rootAddress = 'https://api.backblazeb2.com/b2api/v2/';
2021-02-03 19:51:07 +00:00
2021-12-06 18:31:19 +00:00
String apiPrefix = '/b2api/v2';
Future<BackblazeApiAuth> getAuthorizationToken() async {
2022-06-05 19:36:32 +00:00
final Dio client = await getClient();
final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential;
2021-12-06 18:31:19 +00:00
if (backblazeCredential == null) {
throw Exception('Backblaze credential is null');
}
final String encodedApiKey = encodedBackblazeKey(
backblazeCredential.keyId,
backblazeCredential.applicationKey,
);
2022-06-05 19:36:32 +00:00
final Response response = await client.get(
2021-12-06 18:31:19 +00:00
'b2_authorize_account',
options: Options(headers: {'Authorization': 'Basic $encodedApiKey'}),
);
if (response.statusCode != 200) {
throw Exception('code: ${response.statusCode}');
}
return BackblazeApiAuth(
authorizationToken: response.data['authorizationToken'],
apiUrl: response.data['apiUrl'],
);
}
Future<GenericResult<bool>> isApiTokenValid(
final String encodedApiKey,
) async {
2022-06-05 19:36:32 +00:00
final Dio client = await getClient();
bool isTokenValid = false;
try {
final Response response = await client.get(
'b2_authorize_account',
options: Options(
followRedirects: false,
validateStatus: (final status) =>
status != null && (status >= 200 || status == 401),
headers: {'Authorization': 'Basic $encodedApiKey'},
),
);
if (response.statusCode == HttpStatus.ok) {
isTokenValid =
response.data['allowed']['capabilities'].contains('listBuckets');
} else if (response.statusCode == HttpStatus.unauthorized) {
isTokenValid = false;
} else {
throw Exception('code: ${response.statusCode}');
2021-12-06 18:31:19 +00:00
}
} on DioError catch (e) {
print(e);
return GenericResult(
data: false,
success: false,
message: e.toString(),
);
} finally {
close(client);
2021-02-03 19:51:07 +00:00
}
return GenericResult(
data: isTokenValid,
success: true,
);
2021-02-03 19:51:07 +00:00
}
2021-03-25 23:30:34 +00:00
2021-12-06 18:31:19 +00:00
// Create bucket
2022-06-05 19:36:32 +00:00
Future<String> createBucket(final String bucketName) async {
final BackblazeApiAuth auth = await getAuthorizationToken();
final BackupsCredential? backblazeCredential =
getIt<ApiConfigModel>().backblazeCredential;
2022-06-05 19:36:32 +00:00
final Dio client = await getClient();
2021-12-06 18:31:19 +00:00
client.options.baseUrl = auth.apiUrl;
2022-06-05 19:36:32 +00:00
final Response response = await client.post(
2021-12-06 18:31:19 +00:00
'$apiPrefix/b2_create_bucket',
data: {
'accountId': backblazeCredential!.keyId,
'bucketName': bucketName,
'bucketType': 'allPrivate',
'lifecycleRules': [
{
2022-05-24 18:55:39 +00:00
'daysFromHidingToDeleting': 30,
'daysFromUploadingToHiding': null,
'fileNamePrefix': '',
2021-12-06 18:31:19 +00:00
}
],
},
options: Options(
headers: {'Authorization': auth.authorizationToken},
),
);
close(client);
if (response.statusCode == HttpStatus.ok) {
return response.data['bucketId'];
} else {
throw Exception('code: ${response.statusCode}');
}
}
// Create a limited capability key with access to the given bucket
2022-06-05 19:36:32 +00:00
Future<BackblazeApplicationKey> createKey(final String bucketId) async {
final BackblazeApiAuth auth = await getAuthorizationToken();
final Dio client = await getClient();
2021-12-06 18:31:19 +00:00
client.options.baseUrl = auth.apiUrl;
2022-06-05 19:36:32 +00:00
final Response response = await client.post(
2021-12-06 18:31:19 +00:00
'$apiPrefix/b2_create_key',
data: {
'accountId': getIt<ApiConfigModel>().backblazeCredential!.keyId,
'bucketId': bucketId,
'capabilities': ['listBuckets', 'listFiles', 'readFiles', 'writeFiles'],
'keyName': 'selfprivacy-restricted-server-key',
},
options: Options(
headers: {'Authorization': auth.authorizationToken},
),
);
close(client);
if (response.statusCode == HttpStatus.ok) {
return BackblazeApplicationKey(
applicationKeyId: response.data['applicationKeyId'],
applicationKey: response.data['applicationKey'],
);
2021-12-06 18:31:19 +00:00
} else {
throw Exception('code: ${response.statusCode}');
}
}
Future<BackblazeBucket?> fetchBucket(
final BackupsCredential credentials,
final BackupConfiguration configuration,
) async {
BackblazeBucket? bucket;
final BackblazeApiAuth auth = await getAuthorizationToken();
final Dio client = await getClient();
client.options.baseUrl = auth.apiUrl;
final Response response = await client.get(
'$apiPrefix/b2_list_buckets',
queryParameters: {
'accountId': getIt<ApiConfigModel>().backblazeCredential!.keyId,
},
options: Options(
headers: {'Authorization': auth.authorizationToken},
),
);
close(client);
if (response.statusCode == HttpStatus.ok) {
for (final rawBucket in response.data['buckets']) {
if (rawBucket['bucketId'] == configuration.locationId) {
bucket = BackblazeBucket(
bucketId: rawBucket['bucketId'],
bucketName: rawBucket['bucketName'],
encryptionKey: configuration.encryptionKey,
applicationKeyId: '',
applicationKey: '',
);
}
}
return bucket;
} else {
throw Exception('code: ${response.statusCode}');
}
}
2021-03-25 23:30:34 +00:00
@override
2022-02-16 07:28:29 +00:00
bool hasLogger;
2021-03-25 23:30:34 +00:00
@override
bool isWithToken;
}