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

254 lines
7.3 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';
2024-01-31 05:14:23 +00:00
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/get_it/resources_model.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';
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 {
2024-07-29 22:18:54 +00:00
BackblazeApi({
this.token = '',
this.tokenId = '',
this.hasLogger = false,
this.isWithToken = true,
}) : assert(isWithToken ? token.isNotEmpty && tokenId.isNotEmpty : true);
@override
bool hasLogger;
@override
bool isWithToken;
final String token;
final String tokenId;
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) {
2024-07-29 22:18:54 +00:00
final encodedApiKey = encodedBackblazeKey(tokenId, token);
options.headers = {'Authorization': 'Basic $encodedApiKey'};
2021-03-25 23:30:34 +00:00
}
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();
2024-07-29 22:18:54 +00:00
if (token.isEmpty || tokenId.isEmpty) {
2021-12-06 18:31:19 +00:00
throw Exception('Backblaze credential is null');
}
final String encodedApiKey = encodedBackblazeKey(
2024-07-29 22:18:54 +00:00
tokenId,
token,
);
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 DioException 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
2024-07-29 22:18:54 +00:00
Future<GenericResult<String>> createBucket(final String bucketName) async {
2022-06-05 19:36:32 +00:00
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_bucket',
data: {
2024-07-29 22:18:54 +00:00
'accountId': tokenId,
2021-12-06 18:31:19 +00:00
'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) {
2024-07-29 22:18:54 +00:00
return GenericResult(
data: response.data['bucketId'],
success: true,
);
2021-12-06 18:31:19 +00:00
} else {
2024-07-29 22:18:54 +00:00
return GenericResult(
data: '',
success: false,
message: 'code: ${response.statusCode}, ${response.data}',
);
2021-12-06 18:31:19 +00:00
}
}
// Create a limited capability key with access to the given bucket
2024-07-29 22:18:54 +00:00
Future<GenericResult<BackblazeApplicationKey>> createKey(
final String bucketId,
) async {
2022-06-05 19:36:32 +00:00
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<ResourcesModel>().backblazeCredential!.keyId,
2021-12-06 18:31:19 +00:00
'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) {
2024-07-29 22:18:54 +00:00
return GenericResult(
success: true,
data: BackblazeApplicationKey(
applicationKeyId: response.data['applicationKeyId'],
applicationKey: response.data['applicationKey'],
),
);
2021-12-06 18:31:19 +00:00
} else {
2024-07-29 22:18:54 +00:00
return GenericResult(
success: false,
data: BackblazeApplicationKey(
applicationKeyId: '',
applicationKey: '',
),
message: 'code: ${response.statusCode}, ${response.data}',
);
2021-12-06 18:31:19 +00:00
}
}
2024-07-29 22:18:54 +00:00
Future<GenericResult<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<ResourcesModel>().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: '',
);
}
}
2024-07-29 22:18:54 +00:00
return GenericResult(
success: bucket != null,
data: bucket,
);
} else {
2024-07-29 22:18:54 +00:00
return GenericResult(
success: false,
data: null,
message: 'code: ${response.statusCode}, ${response.data}',
);
}
}
2021-03-25 23:30:34 +00:00
}