diff --git a/.github/workflows/flutter_build.yml b/.github/workflows/flutter_build.yml index dd19244aa..67ae4b7c1 100644 --- a/.github/workflows/flutter_build.yml +++ b/.github/workflows/flutter_build.yml @@ -27,7 +27,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: 'stable' - flutter-version: '3.16.5' + flutter-version: '3.19.6' - run: "flutter upgrade" - run: "flutter --version" - run: "flutter pub get" @@ -48,14 +48,14 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: - distribution: 'zulu' - java-version: '17.0.7' + distribution: 'oracle' + java-version: '17' - uses: subosito/flutter-action@v2 with: channel: 'stable' - flutter-version: '3.19.0' + flutter-version: '3.19.6' + - run: 'flutter clean' - run: "flutter upgrade" - run: "flutter --version" - run: "flutter pub get" - - run: 'flutter clean' - - run: "flutter build apk -t 'lib/mains/main_netknights.dart' --debug --flavor netknights" \ No newline at end of file + - run: "flutter build apk -t 'lib/mains/main_netknights.dart' --debug --flavor netknights" diff --git a/.gitignore b/.gitignore index c7c2122a5..2fae39ee8 100644 --- a/.gitignore +++ b/.gitignore @@ -890,3 +890,4 @@ DerivedData/ .flutter-plugins-dependencies lib/l10n/untranslated.txt *.jks +ios/Flutter/flutter_export_environment.sh diff --git a/android/app/src/main/res/drawable/app_icon.png b/android/app/src/main/res/drawable/app_icon.png deleted file mode 100644 index f1142776d..000000000 Binary files a/android/app/src/main/res/drawable/app_icon.png and /dev/null differ diff --git a/android/app/src/netknights/res/drawable-hdpi/hw_background.png b/android/app/src/netknights/res/drawable-hdpi/hw_background.png new file mode 100644 index 000000000..ac9ba78be Binary files /dev/null and b/android/app/src/netknights/res/drawable-hdpi/hw_background.png differ diff --git a/android/app/src/netknights/res/drawable-mdpi/hw_background.png b/android/app/src/netknights/res/drawable-mdpi/hw_background.png new file mode 100644 index 000000000..ac9ba78be Binary files /dev/null and b/android/app/src/netknights/res/drawable-mdpi/hw_background.png differ diff --git a/android/app/src/netknights/res/drawable-xhdpi/hw_background.png b/android/app/src/netknights/res/drawable-xhdpi/hw_background.png new file mode 100644 index 000000000..ac9ba78be Binary files /dev/null and b/android/app/src/netknights/res/drawable-xhdpi/hw_background.png differ diff --git a/android/app/src/netknights/res/drawable-xxhdpi/hw_background.png b/android/app/src/netknights/res/drawable-xxhdpi/hw_background.png new file mode 100644 index 000000000..ac9ba78be Binary files /dev/null and b/android/app/src/netknights/res/drawable-xxhdpi/hw_background.png differ diff --git a/android/app/src/netknights/res/drawable-xxxhdpi/hw_background.png b/android/app/src/netknights/res/drawable-xxxhdpi/hw_background.png new file mode 100644 index 000000000..ac9ba78be Binary files /dev/null and b/android/app/src/netknights/res/drawable-xxxhdpi/hw_background.png differ diff --git a/android/app/src/netknights/res/drawable/app_icon.png b/android/app/src/netknights/res/drawable/app_icon.png deleted file mode 100644 index f1142776d..000000000 Binary files a/android/app/src/netknights/res/drawable/app_icon.png and /dev/null differ diff --git a/android/app/src/netknights/res/drawable/hw_background.png b/android/app/src/netknights/res/drawable/hw_background.png new file mode 100644 index 000000000..ac9ba78be Binary files /dev/null and b/android/app/src/netknights/res/drawable/hw_background.png differ diff --git a/integration_test/add_tokens_test.dart b/integration_test/add_tokens_test.dart index e00bd5972..30e224114 100644 --- a/integration_test/add_tokens_test.dart +++ b/integration_test/add_tokens_test.dart @@ -14,7 +14,7 @@ import 'package:privacyidea_authenticator/state_notifiers/completed_introduction import 'package:privacyidea_authenticator/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_notifier.dart'; -import 'package:privacyidea_authenticator/utils/app_customizer.dart'; +import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; import 'package:privacyidea_authenticator/views/add_token_manually_view/add_token_manually_view.dart'; import 'package:privacyidea_authenticator/views/add_token_manually_view/add_token_manually_view_widgets/labeled_dropdown_button.dart'; diff --git a/integration_test/copy_to_clipboard_test.dart b/integration_test/copy_to_clipboard_test.dart index 88eb2bd81..a020a2c29 100644 --- a/integration_test/copy_to_clipboard_test.dart +++ b/integration_test/copy_to_clipboard_test.dart @@ -12,7 +12,7 @@ import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_notifier.dart'; -import 'package:privacyidea_authenticator/utils/app_customizer.dart'; +import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; import 'package:privacyidea_authenticator/model/version.dart'; diff --git a/integration_test/rename_and_delete_test.dart b/integration_test/rename_and_delete_test.dart index 17a438071..c33f191bb 100644 --- a/integration_test/rename_and_delete_test.dart +++ b/integration_test/rename_and_delete_test.dart @@ -13,7 +13,7 @@ import 'package:privacyidea_authenticator/state_notifiers/completed_introduction import 'package:privacyidea_authenticator/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_notifier.dart'; -import 'package:privacyidea_authenticator/utils/app_customizer.dart'; +import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_delete_action.dart'; diff --git a/integration_test/two_step_rollout_test.dart b/integration_test/two_step_rollout_test.dart index 2b84ba31a..bb1a400b4 100644 --- a/integration_test/two_step_rollout_test.dart +++ b/integration_test/two_step_rollout_test.dart @@ -10,7 +10,7 @@ import 'package:privacyidea_authenticator/model/states/settings_state.dart'; import 'package:privacyidea_authenticator/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_notifier.dart'; -import 'package:privacyidea_authenticator/utils/app_customizer.dart'; +import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; import 'package:privacyidea_authenticator/model/version.dart'; diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index b3752ff03..35015e40b 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -12,7 +12,7 @@ import 'package:privacyidea_authenticator/model/states/settings_state.dart'; import 'package:privacyidea_authenticator/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/state_notifiers/token_notifier.dart'; -import 'package:privacyidea_authenticator/utils/app_customizer.dart'; +import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; import 'package:privacyidea_authenticator/model/version.dart'; diff --git a/lib/mains/main_netknights.dart b/lib/mains/main_netknights.dart index 49a14958c..82661d394 100644 --- a/lib/mains/main_netknights.dart +++ b/lib/mains/main_netknights.dart @@ -23,7 +23,7 @@ import 'package:easy_dynamic_theme/easy_dynamic_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; -import 'package:privacyidea_authenticator/utils/app_customizer.dart'; +import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; diff --git a/lib/model/encryption/token_encryption.dart b/lib/model/encryption/token_encryption.dart index 898d6b442..ffc585675 100644 --- a/lib/model/encryption/token_encryption.dart +++ b/lib/model/encryption/token_encryption.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:io'; import '../../processors/scheme_processors/token_import_scheme_processors/privacyidea_authenticator_qr_processor.dart'; import '../tokens/token.dart'; @@ -23,16 +22,16 @@ class TokenEncryption { static Uri generateQrCodeUri({required Token token}) { final tokenJson = token.toJson(); final encoded = json.encode(tokenJson); - final zip = gzip.encode(utf8.encode(encoded)); - final base64 = base64Url.encode(zip); + final bytes = utf8.encode(encoded); + final base64 = base64Url.encode(bytes); final uri = Uri.parse('${PrivacyIDEAAuthenticatorQrProcessor.scheme}://${PrivacyIDEAAuthenticatorQrProcessor.host}?data=$base64'); return uri; } static Token fromQrCodeUri(Uri uri) { final base64String = uri.queryParameters['data']; - final zip = base64Url.decode(base64String!); - final jsonString = utf8.decode(gzip.decode(zip)); + final bytes = base64Url.decode(base64String!); + final jsonString = utf8.decode(bytes); final tokenJson = json.decode(jsonString) as Map; return Token.fromJson(tokenJson); } diff --git a/lib/model/states/introduction_state.dart b/lib/model/states/introduction_state.dart index 3e75fc77d..016254314 100644 --- a/lib/model/states/introduction_state.dart +++ b/lib/model/states/introduction_state.dart @@ -1,8 +1,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/introduction_extension.dart'; import '../enums/introduction.dart'; +import '../extensions/enums/introduction_extension.dart'; part 'introduction_state.g.dart'; diff --git a/lib/model/token_import/token_import_source.dart b/lib/model/token_import/token_import_source.dart index 10c720d6d..9e15f1b71 100644 --- a/lib/model/token_import/token_import_source.dart +++ b/lib/model/token_import/token_import_source.dart @@ -1,5 +1,4 @@ -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; - +import '../../l10n/app_localizations.dart'; import '../../processors/mixins/token_import_processor.dart'; import '../enums/token_import_type.dart'; diff --git a/lib/model/token_import/token_origin_data.dart b/lib/model/token_import/token_origin_data.dart index 280614fff..8f02a54fa 100644 --- a/lib/model/token_import/token_origin_data.dart +++ b/lib/model/token_import/token_origin_data.dart @@ -1,7 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; -import '../version.dart'; import '../enums/token_origin_source_type.dart'; +import '../version.dart'; part 'token_origin_data.g.dart'; diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index 8f0d36a9e..df5c5165a 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/algorithms_extension.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; -import 'package:privacyidea_authenticator/utils/errors.dart'; import 'package:uuid/uuid.dart'; +import '../../utils/errors.dart'; import '../../utils/identifiers.dart'; import '../enums/algorithms.dart'; import '../enums/day_password_token_view_mode.dart'; import '../enums/encodings.dart'; import '../enums/token_types.dart'; +import '../extensions/enums/algorithms_extension.dart'; +import '../extensions/enums/encodings_extension.dart'; import '../token_import/token_origin_data.dart'; import 'otp_token.dart'; import 'token.dart'; @@ -131,7 +131,7 @@ class DayPasswordToken extends OTPToken { name: URI_SECRET, ); } - if (uriMap[URI_PERIOD] < 1) { + if (uriMap[URI_PERIOD] != null && uriMap[URI_PERIOD] < 1) { throw LocalizedArgumentError( localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), unlocalizedMessage: 'Period must be greater than 0', @@ -139,7 +139,7 @@ class DayPasswordToken extends OTPToken { name: URI_PERIOD, ); } - if (uriMap[URI_DIGITS] < 1) { + if (uriMap[URI_DIGITS] != null && uriMap[URI_DIGITS] < 1) { throw LocalizedArgumentError( localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), unlocalizedMessage: 'Digits must be greater than 0', diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index 7cb6faf57..75f092f02 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -1,12 +1,12 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/algorithms_extension.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import 'package:uuid/uuid.dart'; import '../../utils/identifiers.dart'; import '../enums/algorithms.dart'; import '../enums/encodings.dart'; import '../enums/token_types.dart'; +import '../extensions/enums/algorithms_extension.dart'; +import '../extensions/enums/encodings_extension.dart'; import '../token_import/token_origin_data.dart'; import 'otp_token.dart'; import 'token.dart'; diff --git a/lib/model/tokens/steam_token.dart b/lib/model/tokens/steam_token.dart index 6e1d2eb24..4800e9b84 100644 --- a/lib/model/tokens/steam_token.dart +++ b/lib/model/tokens/steam_token.dart @@ -1,14 +1,14 @@ import 'package:base32/base32.dart'; import 'package:crypto/crypto.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; -import 'package:privacyidea_authenticator/utils/errors.dart'; import 'package:uuid/uuid.dart'; +import '../../utils/errors.dart'; import '../../utils/identifiers.dart'; import '../enums/algorithms.dart'; import '../enums/encodings.dart'; import '../enums/token_types.dart'; +import '../extensions/enums/encodings_extension.dart'; import '../extensions/int_extension.dart'; import '../token_import/token_origin_data.dart'; import 'token.dart'; diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index c09665871..75f0db286 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -1,6 +1,4 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/algorithms_extension.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import 'package:uuid/uuid.dart'; import '../../utils/identifiers.dart'; @@ -8,6 +6,8 @@ import '../../utils/logger.dart'; import '../enums/algorithms.dart'; import '../enums/encodings.dart'; import '../enums/token_types.dart'; +import '../extensions/enums/algorithms_extension.dart'; +import '../extensions/enums/encodings_extension.dart'; import '../token_import/token_origin_data.dart'; import 'otp_token.dart'; import 'token.dart'; @@ -29,16 +29,17 @@ class TOTPToken extends OTPToken { } final int period; - - @override - String get otpValue => algorithm.generateTOTPCodeString( + String otpFromTime(DateTime time) => algorithm.generateTOTPCodeString( secret: secret, - time: DateTime.now(), + time: time, length: digits, interval: Duration(seconds: period), isGoogle: true, ); + @override + String get otpValue => otpFromTime(DateTime.now()); + TOTPToken({ required int period, required super.id, diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor.dart b/lib/processors/scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor.dart index c2ea1ff5c..6183f4ccb 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor.dart @@ -1,6 +1,5 @@ -import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; - import '../../../model/enums/token_origin_source_type.dart'; +import '../../../model/extensions/enums/token_origin_source_type.dart'; import '../../../model/processor_result.dart'; import '../../../model/tokens/token.dart'; import '../../../utils/token_import_origins.dart'; diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart b/lib/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart index 0a53482af..84895051f 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart @@ -1,13 +1,15 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; +import '../../../model/enums/token_origin_source_type.dart'; +import '../../../model/token_import/token_origin_data.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/algorithms.dart'; import '../../../model/enums/encodings.dart'; import '../../../model/enums/token_types.dart'; import '../../../model/extensions/enum_extension.dart'; +import '../../../model/extensions/enums/encodings_extension.dart'; import '../../../model/processor_result.dart'; import '../../../model/tokens/token.dart'; import '../../../utils/errors.dart'; @@ -33,7 +35,7 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { } catch (e, s) { if (e is LocalizedException) { Logger.warning('Error while parsing otpAuth.', name: 'token_notifier.dart#addTokenFromOtpAuth', error: e.unlocalizedMessage, stackTrace: s); - final message = e.localizedMessage(AppLocalizations.of(await globalContext)!); + final message = globalContextSync != null ? e.localizedMessage(AppLocalizations.of(globalContextSync!)!) : e.unlocalizedMessage; return [ProcessorResult.failed(message)]; } String? message; @@ -65,13 +67,14 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { } Token newToken; try { - newToken = Token.fromUriMap(uriMap); + newToken = + Token.fromUriMap(uriMap).copyWith(origin: TokenOriginData(source: TokenOriginSourceType.link, data: uri.toString(), createdAt: DateTime.now())); } on FormatException catch (e) { Logger.warning('Error while parsing otpAuth.', name: 'token_notifier.dart#addTokenFromOtpAuth', error: e); return [ProcessorResultFailed(e.message)]; } catch (e, s) { Logger.warning('Error while parsing otpAuth.', name: 'token_notifier.dart#addTokenFromOtpAuth', error: e, stackTrace: s); - showMessage(message: 'An error occurred while parsing the QR code.', duration: const Duration(seconds: 3)); + // showMessage(message: 'An error occurred while parsing the QR code.', duration: const Duration(seconds: 3)); return [const ProcessorResultFailed('An error occurred while parsing the QR code.')]; } return [ProcessorResultSuccess(newToken)]; @@ -177,82 +180,82 @@ Map _parseOtpAuth(Uri uri) { uriMap[URI_SECRET] = secret; - if (uriMap[URI_TYPE] == 'hotp') { - // Parse counter. - String? counterAsString = uri.queryParameters['counter']; - if (counterAsString == null) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, name) => localizations.missingRequiredParameter(name), - unlocalizedMessage: 'Value for parameter [counter] is required and is missing.', - invalidValue: counterAsString, - name: 'counter', - ); - } - try { - uriMap[URI_COUNTER] = int.parse(counterAsString); - } on FormatException { + // Parse counter. + String? counterString = uri.queryParameters['counter']; + if (counterString != null) { + uriMap[URI_COUNTER] = int.tryParse(counterString); + if (uriMap[URI_COUNTER] == null) { throw LocalizedArgumentError( localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: '[$counterAsString] is not a valid value for uri parameter [counter].', - invalidValue: counterAsString, + unlocalizedMessage: '[$counterString] is not a valid value for uri parameter [counter].', + invalidValue: counterString, name: 'counter', ); } } - if (uriMap[URI_TYPE] == 'totp' || uriMap[URI_TYPE] == 'daypassword') { - // Parse period. - String periodAsString = uri.queryParameters['period'] ?? '30'; - - int? period = int.tryParse(periodAsString); - if (period == null) { - throw ArgumentError('Value [$periodAsString] for parameter [period] is invalid.'); - } - uriMap[URI_PERIOD] = period; - } - - if (_is2StepURI(uri)) { - // Parse for 2 step roll out. - String saltLengthAsString = uri.queryParameters['2step_salt'] ?? '10'; - String outputLengthInByteAsString = uri.queryParameters['2step_output'] ?? '20'; - String iterationsAsString = uri.queryParameters['2step_difficulty'] ?? '10000'; - - // Parse parameters - try { - uriMap[URI_SALT_LENGTH] = int.parse(saltLengthAsString); - } on FormatException { + // Parse period. + String? periodString = uri.queryParameters['period']; + if (periodString != null) { + uriMap[URI_PERIOD] = int.tryParse(periodString); + if (uriMap[URI_PERIOD] == null) { throw LocalizedArgumentError( localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: '[$saltLengthAsString] is not a valid value for parameter [2step_salt].', - invalidValue: saltLengthAsString, - name: '2step_salt', - ); - } - try { - uriMap[URI_OUTPUT_LENGTH_IN_BYTES] = int.parse(outputLengthInByteAsString); - } on FormatException { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: '[$outputLengthInByteAsString] is not a valid value for parameter [2step_output].', - invalidValue: outputLengthInByteAsString, - name: '2step_output', - ); - } - try { - uriMap[URI_ITERATIONS] = int.parse(iterationsAsString); - } on FormatException { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: '[$iterationsAsString] is not a valid value for parameter [2step_difficulty].', - invalidValue: iterationsAsString, - name: '2step_difficulty', + unlocalizedMessage: 'Value [$periodString] for parameter [period] is invalid.', + invalidValue: periodString, + name: 'period', ); } } + if (_is2StepURI(uri)) { + uriMap.addAll(_parse2StepURI(uri)); + } + return uriMap; } +Map _parse2StepURI(Uri uri) { + Map uriMap2Step = {}; + // Parse for 2 step roll out. + String saltLengthAsString = uri.queryParameters['2step_salt'] ?? '10'; + String outputLengthInByteAsString = uri.queryParameters['2step_output'] ?? '20'; + String iterationsAsString = uri.queryParameters['2step_difficulty'] ?? '10000'; + + // Parse parameters + try { + uriMap2Step[URI_SALT_LENGTH] = int.parse(saltLengthAsString); + } on FormatException { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: '[$saltLengthAsString] is not a valid value for parameter [2step_salt].', + invalidValue: saltLengthAsString, + name: '2step_salt', + ); + } + try { + uriMap2Step[URI_OUTPUT_LENGTH_IN_BYTES] = int.parse(outputLengthInByteAsString); + } on FormatException { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: '[$outputLengthInByteAsString] is not a valid value for parameter [2step_output].', + invalidValue: outputLengthInByteAsString, + name: '2step_output', + ); + } + try { + uriMap2Step[URI_ITERATIONS] = int.parse(iterationsAsString); + } on FormatException { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: '[$iterationsAsString] is not a valid value for parameter [2step_difficulty].', + invalidValue: iterationsAsString, + name: '2step_difficulty', + ); + } + return uriMap2Step; +} + Map _parsePiPushToken(Uri uri) { // otpauth://pipush/LABELTEXT? // url=https://privacyidea.org/enroll/this/token diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/privacyidea_authenticator_qr_processor.dart b/lib/processors/scheme_processors/token_import_scheme_processors/privacyidea_authenticator_qr_processor.dart index e2c13b161..73c5f980c 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/privacyidea_authenticator_qr_processor.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/privacyidea_authenticator_qr_processor.dart @@ -1,9 +1,7 @@ -import 'package:privacyidea_authenticator/model/processor_result.dart'; - -import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; - import '../../../model/encryption/token_encryption.dart'; +import '../../../model/processor_result.dart'; +import '../../../model/tokens/token.dart'; +import '../../../utils/logger.dart'; import 'token_import_scheme_processor_interface.dart'; class PrivacyIDEAAuthenticatorQrProcessor extends TokenImportSchemeProcessor { diff --git a/lib/processors/token_import_file_processor/aegis_import_file_processor.dart b/lib/processors/token_import_file_processor/aegis_import_file_processor.dart index f1cfc9977..f8a093ba5 100644 --- a/lib/processors/token_import_file_processor/aegis_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/aegis_import_file_processor.dart @@ -76,7 +76,7 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { } @override - Future fileIsValid({required XFile file}) async { + Future fileIsValid(XFile file) async { final Map json; try { final String fileContent = await file.readAsString(); @@ -88,7 +88,7 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { } @override - Future fileNeedsPassword({required XFile file}) async { + Future fileNeedsPassword(XFile file) async { Map json; try { final String fileContent = await file.readAsString(); @@ -100,7 +100,7 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { } @override - Future>> processFile({required XFile file, String? password}) async { + Future>> processFile(XFile file, {String? password}) async { final String fileContent = await file.readAsString(); final Map json; try { @@ -136,15 +136,15 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { } } - Future>> _processPlainV2(Map json) async { + Future>> _processPlainV2(Map json) { final results = >[]; - final localization = AppLocalizations.of(await globalContext)!; + final localization = globalContextSync != null ? AppLocalizations.of(globalContextSync!)! : null; for (Map entry in json['db']['entries']) { try { if (entry['type'] != 'totp' && entry['type'] != 'hotp') { // TODO: support other token types Logger.warning('Unsupported token type: ${entry['type']}', name: '_processPlain#OtpAuthImportFileProcessor'); - results.add(ProcessorResult.failed(localization.unsupported('token type', entry['type']))); + results.add(ProcessorResult.failed(localization?.unsupported('token type', entry['type']) ?? 'Unsupported token type: ${entry['type']}')); continue; } Map info = entry['info']; @@ -167,25 +167,25 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { final token = Token.fromUriMap(entryUriMap); results.add(ProcessorResult.success(token.copyWith(id: entry[AEGIS_ID]))); } on LocalizedException catch (e) { - results.add(ProcessorResult.failed(e.localizedMessage(localization))); + results.add(ProcessorResult.failed(localization != null ? e.localizedMessage(localization) : e.unlocalizedMessage)); } catch (e) { Logger.error('Failed to parse token.', name: 'AegisImportFileProcessor#_processPlain', error: e, stackTrace: StackTrace.current); results.add(ProcessorResult.failed(e.toString())); } } - return results; + return Future.value(results); } - Future>> _processPlainV3(Map json) async { + Future>> _processPlainV3(Map json) { final results = >[]; - final localization = AppLocalizations.of(await globalContext)!; + final localization = globalContextSync != null ? AppLocalizations.of(globalContextSync!)! : null; final entries = json['db']['entries'] as List; for (Map entry in entries) { try { if (doesThrow(() => TokenTypes.values.byName((entry['type'] as String).toUpperCase()))) { // TODO: support other token types Logger.warning('Unsupported token type: ${entry['type']}', name: '_processPlain#OtpAuthImportFileProcessor'); - results.add(ProcessorResult.failed(localization.unsupported('token type', entry['type']))); + results.add(ProcessorResult.failed(localization?.unsupported('token type', entry['type']) ?? 'Unsupported token type: ${entry['type']}')); continue; } Map info = entry['info']; @@ -207,14 +207,14 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { }; results.add(ProcessorResult.success(Token.fromUriMap(entryUriMap))); } on LocalizedException catch (e) { - results.add(ProcessorResultFailed(e.localizedMessage(localization))); + results.add(ProcessorResultFailed(localization != null ? e.localizedMessage(localization) : e.unlocalizedMessage)); } catch (e) { Logger.error('Failed to parse token.', name: 'AegisImportFileProcessor#_processPlain', error: e, stackTrace: StackTrace.current); results.add(ProcessorResultFailed(e.toString())); } } - return results; + return Future.value(results); } Future runIsolatedKdf(ScryptParameters scryptParameters, String password) async { diff --git a/lib/processors/token_import_file_processor/authenticator_pro_import_file_processor.dart b/lib/processors/token_import_file_processor/authenticator_pro_import_file_processor.dart index bfce3aa69..5ddbe17c8 100644 --- a/lib/processors/token_import_file_processor/authenticator_pro_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/authenticator_pro_import_file_processor.dart @@ -5,7 +5,9 @@ import 'dart:convert'; import 'package:cryptography/cryptography.dart'; import 'package:file_selector/file_selector.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; +import 'package:privacyidea_authenticator/model/enums/encodings.dart'; import 'package:privacyidea_authenticator/model/enums/token_types.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart'; @@ -36,13 +38,6 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { static const String _AUTHENTICATOR_PRO_COUNTER = "Counter"; static const String _AUTHENTICATOR_PRO_ALGORITHM = "Algorithm"; - /* - // final copyCount = tokenMap['CopyCount'] as int; - // final ranking = tokenMap['Ranking'] as int; - // final pin = tokenMap['Pin'] as String?; - // final icon = tokenMap['Icon'] as String?; - */ - static final typeMap = { 1: TokenTypes.HOTP.name, 2: TokenTypes.TOTP.name, @@ -60,21 +55,32 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { const AuthenticatorProImportFileProcessor(); @override - Future fileIsValid({required XFile file}) async { + Future fileIsValid(XFile file) async { final contentBytes = await file.readAsBytes(); try { final contentString = utf8.decode(contentBytes); try { + // Check if it's a JSON with plain tokens if (json.decode(contentString)['Authenticators'] != null) return true; } catch (e) {} try { + // Check if it's the HTML export if (contentString.startsWith('')) return true; } catch (e) {} try { - if (Uri.tryParse(contentString.split('\n').first) != null) return true; - return false; + // Check if it's a list of URIs + List lines = contentString.split('\n')..removeWhere((element) => element.isEmpty); + if (lines.every((line) => line.isEmpty || line.startsWith('otpauth://'))) return true; } catch (e) {} + // Its utf8 encoded, but not a JSON, HTML or URI list, so it's not valid -> return false + Logger.warning( + 'File is not a valid Authenticator Pro backup file', + error: 'Invalid content: $contentString', + name: 'authenticator_pro_import_file_processor#fileIsValid', + ); + return false; } catch (e) { + // When utf8 decoding fails, it's may be encrypted try { final headerByteLength = utf8.encode(header).length; final importedHeader = contentBytes.sublist(0, headerByteLength); @@ -83,14 +89,20 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { return true; } } catch (e) {} + Logger.warning( + 'File is not a valid Authenticator Pro backup file', + error: 'Content Bytes: $contentBytes', + name: 'authenticator_pro_import_file_processor#fileIsValid', + ); } - + // It's not utf8 encoded and not encrypted, so it's not valid -> return false return false; } @override - Future fileNeedsPassword({required XFile file}) async { + Future fileNeedsPassword(XFile file) async { final contentBytes = await file.readAsBytes(); + try { utf8.decode(contentBytes); @@ -108,9 +120,8 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { } @override - Future>> processFile({required XFile file, String? password}) async { + Future>> processFile(XFile file, {String? password}) async { var results = >[]; - final bytes = await file.readAsBytes(); Uint8Buffer uint8buffer = Uint8Buffer(data: bytes); final headerByteLength = utf8.encode(header).length; @@ -199,7 +210,7 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { Future>> _processPlain({required String fileContent}) async { try { final tokensMap = (json.decode(fileContent)['Authenticators'].cast>()) as List>; - return _processAuthPro(tokensMap: tokensMap); + return _processJson(tokensMap: tokensMap); } catch (e) { try { final lines = fileContent.split('\n').where((e) => e.isNotEmpty).map((e) => Uri.parse(e)).toList(); @@ -263,7 +274,7 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { return results; } - Future>> _processAuthPro({required List> tokensMap}) async { + Future>> _processJson({required List> tokensMap}) async { Logger.info('Processing plain file', name: 'authenticator_pro_import_file_processor#_processAuthPro'); final result = >[]; for (var tokenMap in tokensMap) { @@ -278,7 +289,7 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { URI_TYPE: tokenType, URI_ISSUER: tokenMap[_AUTHENTICATOR_PRO_ISSUER] as String, URI_LABEL: tokenMap[_AUTHENTICATOR_PRO_LABEL] as String, - URI_SECRET: utf8.encode(tokenMap[_AUTHENTICATOR_PRO_SECRET] as String), + URI_SECRET: Encodings.base32.decode(tokenMap[_AUTHENTICATOR_PRO_SECRET] as String), URI_DIGITS: tokenMap[_AUTHENTICATOR_PRO_DIGITS] as int, URI_PERIOD: tokenMap[_AUTHENTICATOR_PRO_PERIOD] as int, URI_ALGORITHM: algorithmMap[tokenMap[_AUTHENTICATOR_PRO_ALGORITHM] as int], diff --git a/lib/processors/token_import_file_processor/free_otp_plus_file_processor.dart b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart similarity index 85% rename from lib/processors/token_import_file_processor/free_otp_plus_file_processor.dart rename to lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart index 8c3d277cb..ef3c9a6ef 100644 --- a/lib/processors/token_import_file_processor/free_otp_plus_file_processor.dart +++ b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart @@ -18,7 +18,7 @@ import '../../utils/token_import_origins.dart'; import '../scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor.dart'; import 'token_import_file_processor_interface.dart'; -class FreeOtpPlusFileProcessor extends TokenImportFileProcessor { +class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { static const String _FREE_OTP_PLUS_ALGORITHM = 'algo'; // String: "MD5", "SHA1", "SHA256", "SHA512" static const String _FREE_OTP_PLUS_COUNTER = 'counter'; static const String _FREE_OTP_PLUS_DIGITS = 'digits'; @@ -30,35 +30,31 @@ class FreeOtpPlusFileProcessor extends TokenImportFileProcessor { static const String _steamTokenIssuer = "Steam"; static const String _steamTokenType = "steam"; - const FreeOtpPlusFileProcessor(); + const FreeOtpPlusImportFileProcessor(); @override - Future fileIsValid({required XFile file}) async { - String content; + Future fileIsValid(XFile file) async { + String contentString; try { - content = await file.readAsString(); + contentString = await file.readAsString(); } catch (e) { return false; } try { - final json = jsonDecode(content) as Map; + final json = jsonDecode(contentString) as Map; return json['tokens'] != null; // ignore: empty_catches } catch (e) {} - List lines = content.split('\n')..removeWhere((element) => element.isEmpty); - for (var line in lines) { - if (line.startsWith('otpauth://') == false) { - return false; - } - } - return true; + List lines = contentString.split('\n')..removeWhere((element) => element.isEmpty); + if (lines.every((line) => line.isEmpty || line.startsWith('otpauth://'))) return true; + return false; } @override - Future fileNeedsPassword({required XFile file}) async => false; + Future fileNeedsPassword(XFile file) async => false; @override - Future>> processFile({required XFile file, String? password}) async { + Future>> processFile(XFile file, {String? password}) async { String content = await file.readAsString(); try { final json = jsonDecode(content) as Map; @@ -119,7 +115,7 @@ class FreeOtpPlusFileProcessor extends TokenImportFileProcessor { URI_ISSUER: tokenJson[_FREE_OTP_PLUS_ISSUER], URI_ALGORITHM: tokenJson[_FREE_OTP_PLUS_ALGORITHM], URI_DIGITS: tokenJson[_FREE_OTP_PLUS_DIGITS], - URI_COUNTER: tokenJson[_FREE_OTP_PLUS_COUNTER], + URI_COUNTER: tokenJson[_FREE_OTP_PLUS_COUNTER] + 1, // FreeOTP+ saves only in JSON as 0-based counter URI_PERIOD: tokenJson[_FREE_OTP_PLUS_PERIOD], URI_ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( appName: TokenImportOrigins.freeOtpPlus.appName, diff --git a/lib/processors/token_import_file_processor/privacyidea_authenticator_import_file_processor.dart b/lib/processors/token_import_file_processor/privacyidea_authenticator_import_file_processor.dart index 0e04f5b84..9e85c6bd7 100644 --- a/lib/processors/token_import_file_processor/privacyidea_authenticator_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/privacyidea_authenticator_import_file_processor.dart @@ -12,7 +12,7 @@ import 'two_fas_import_file_processor.dart'; class PrivacyIDEAAuthenticatorImportFileProcessor extends TokenImportFileProcessor { const PrivacyIDEAAuthenticatorImportFileProcessor(); @override - Future fileIsValid({required XFile file}) async { + Future fileIsValid(XFile file) async { try { final content = await file.readAsString(); final json = jsonDecode(content); @@ -26,10 +26,10 @@ class PrivacyIDEAAuthenticatorImportFileProcessor extends TokenImportFileProcess } @override - Future fileNeedsPassword({required XFile file}) => Future.value(true); + Future fileNeedsPassword(XFile file) => Future.value(true); @override - Future>> processFile({required XFile file, String? password}) async { + Future>> processFile(XFile file, {String? password}) async { assert(password != null); try { diff --git a/lib/processors/token_import_file_processor/token_import_file_processor_interface.dart b/lib/processors/token_import_file_processor/token_import_file_processor_interface.dart index 4fd9ce2a8..75a0bdc85 100644 --- a/lib/processors/token_import_file_processor/token_import_file_processor_interface.dart +++ b/lib/processors/token_import_file_processor/token_import_file_processor_interface.dart @@ -12,21 +12,21 @@ abstract class TokenImportFileProcessor with TokenImportProcessor>> processTokenMigrate(XFile data, {String? args}) async { - return processFile(file: data, password: args); + return processFile(data, password: args); } - Future>> processFile({required XFile file, String? password}); + Future>> processFile(XFile file, {String? password}); static final List implementations = [ const AegisImportFileProcessor(), - const TwoFasFileImportProcessor(), + const TwoFasAuthenticatorImportFileProcessor(), ]; static Future>> processFileByAny({required XFile file, String? password}) async { final tokens = >[]; for (TokenImportFileProcessor processor in implementations) { try { - tokens.addAll(await processor.processFile(file: file, password: password)); + tokens.addAll(await processor.processFile(file, password: password)); return tokens; } catch (e) { Logger.warning('Failed to process file with processor ${processor.runtimeType}', @@ -37,8 +37,8 @@ abstract class TokenImportFileProcessor with TokenImportProcessor fileIsValid({required XFile file}); + Future fileIsValid(XFile file); /// Returns true if a password is required to decrypt the Tokens - Future fileNeedsPassword({required XFile file}); + Future fileNeedsPassword(XFile file); } diff --git a/lib/processors/token_import_file_processor/two_fas_import_file_processor.dart b/lib/processors/token_import_file_processor/two_fas_import_file_processor.dart index 0f2a863b2..2b09f42f4 100644 --- a/lib/processors/token_import_file_processor/two_fas_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/two_fas_import_file_processor.dart @@ -20,17 +20,19 @@ import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; import 'token_import_file_processor_interface.dart'; -class TwoFasFileImportProcessor extends TokenImportFileProcessor { - const TwoFasFileImportProcessor(); +class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { + const TwoFasAuthenticatorImportFileProcessor(); static const String TWOFAS_TYPE = 'tokenType'; static const String TWOFAS_ISSUER = 'name'; static const String TWOFAS_SECRET = 'secret'; + static const String TWOFAS_ALGORITHM = 'algorithm'; static const String TWOFAS_LABEL = 'label'; static const String TWOFAS_DIGITS = 'digits'; + static const String TWOFAS_PERIOD = 'period'; static const String TWOFAS_COUNTER = 'counter'; @override - Future>> processFile({required XFile file, String? password}) async { + Future>> processFile(XFile file, {String? password}) async { final String fileContent = await file.readAsString(); final Map json; try { @@ -43,20 +45,20 @@ class TwoFasFileImportProcessor extends TokenImportFileProcessor { } @override - Future fileNeedsPassword({required XFile file}) async { + Future fileIsValid(XFile file) async { try { final Map json = jsonDecode(await file.readAsString()) as Map; - return json['servicesEncrypted'] != null; + return json['servicesEncrypted'] != null || json['services'] != null; } catch (e) { return false; } } @override - Future fileIsValid({required XFile file}) async { + Future fileNeedsPassword(XFile file) async { try { final Map json = jsonDecode(await file.readAsString()) as Map; - return json['servicesEncrypted'] != null || json['services'] != null; + return json['servicesEncrypted'] != null; } catch (e) { return false; } @@ -141,9 +143,11 @@ class TwoFasFileImportProcessor extends TokenImportFileProcessor { return { URI_TYPE: twoFasOTP[TWOFAS_TYPE], URI_ISSUER: twoFasToken[TWOFAS_ISSUER], - URI_SECRET: Encodings.none.decode(twoFasToken[TWOFAS_SECRET]), + URI_SECRET: Encodings.base32.decode(twoFasToken[TWOFAS_SECRET]), + URI_ALGORITHM: twoFasOTP[TWOFAS_ALGORITHM], URI_LABEL: twoFasOTP[TWOFAS_LABEL], URI_DIGITS: twoFasOTP[TWOFAS_DIGITS], + URI_PERIOD: twoFasOTP[TWOFAS_PERIOD], URI_COUNTER: twoFasOTP[TWOFAS_COUNTER], URI_ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( appName: TokenImportOrigins.twoFasAuthenticator.appName, diff --git a/lib/repo/secure_token_repository.dart b/lib/repo/secure_token_repository.dart index f12cf2696..eb878bfe7 100644 --- a/lib/repo/secure_token_repository.dart +++ b/lib/repo/secure_token_repository.dart @@ -214,7 +214,7 @@ class SecureTokenRepository implements TokenRepository { Logger.info('Sending error report', name: 'secure_token_repository.dart#_decryptErrorDialog'); await showDialog( context: context, - builder: (context) => SendErrorDialog(), + builder: (context) => const SendErrorDialog(), useRootNavigator: false, ); }), diff --git a/lib/state_notifiers/deeplink_notifier.dart b/lib/state_notifiers/deeplink_notifier.dart index 43e8f509b..8929ddb21 100644 --- a/lib/state_notifiers/deeplink_notifier.dart +++ b/lib/state_notifiers/deeplink_notifier.dart @@ -54,7 +54,7 @@ class DeeplinkNotifier extends StateNotifier { if (!mounted) return; state = DeepLink(initialUri, fromInit: true); Logger.info('Got initial uri from ${source.name}'); - return; // There can only be one initial uri + return; // There should be only one initial uri } } } diff --git a/lib/state_notifiers/settings_notifier.dart b/lib/state_notifiers/settings_notifier.dart index a6a960179..6f960429b 100644 --- a/lib/state_notifiers/settings_notifier.dart +++ b/lib/state_notifiers/settings_notifier.dart @@ -4,9 +4,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../interfaces/repo/settings_repository.dart'; import '../model/states/settings_state.dart'; +import '../model/version.dart'; import '../utils/logger.dart'; import '../utils/push_provider.dart'; -import '../model/version.dart'; /// This class provies access to the device specific settings. /// It also ensures that the settings are saved to the device. diff --git a/lib/state_notifiers/token_notifier.dart b/lib/state_notifiers/token_notifier.dart index 8ec4c0690..d48ff3387 100644 --- a/lib/state_notifiers/token_notifier.dart +++ b/lib/state_notifiers/token_notifier.dart @@ -11,13 +11,13 @@ import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; import 'package:pi_authenticator_legacy/pi_authenticator_legacy.dart'; import 'package:pointycastle/asymmetric/api.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/push_token_rollout_state_extension.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; import '../interfaces/repo/token_repository.dart'; import '../l10n/app_localizations.dart'; import '../model/enums/push_token_rollout_state.dart'; import '../model/enums/token_origin_source_type.dart'; +import '../model/extensions/enums/push_token_rollout_state_extension.dart'; +import '../model/extensions/enums/token_origin_source_type.dart'; import '../model/processor_result.dart'; import '../model/states/token_state.dart'; import '../model/tokens/hotp_token.dart'; diff --git a/lib/utils/app_info_utils.dart b/lib/utils/app_info_utils.dart index ca2bcb1ef..b063843cf 100644 --- a/lib/utils/app_info_utils.dart +++ b/lib/utils/app_info_utils.dart @@ -9,13 +9,14 @@ import '../model/version.dart'; class AppInfoUtils { static bool isInitialized = false; static final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin(); - static final packageInfo = PackageInfo.fromPlatform(); + static final _packageInfo = PackageInfo.fromPlatform(); static Future init() async { - _appName = (await packageInfo).appName; - _packageName = (await packageInfo).packageName; - _appVersion = Version.parse((await packageInfo).version); - _appBuildNumber = (await packageInfo).buildNumber; + final packageInfo = await _packageInfo; + _appName = packageInfo.appName; + _packageName = packageInfo.packageName; + _appVersion = Version.parse(packageInfo.version); + _appBuildNumber = packageInfo.buildNumber; _androidInfo = !kIsWeb && Platform.isAndroid ? await _deviceInfo.androidInfo : null; _iosInfo = !kIsWeb && Platform.isIOS ? await _deviceInfo.iosInfo : null; diff --git a/lib/utils/customization/action_theme.dart b/lib/utils/customization/action_theme.dart new file mode 100644 index 000000000..589b64856 --- /dev/null +++ b/lib/utils/customization/action_theme.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class ActionTheme extends ThemeExtension { + final Color deleteColor; + final Color editColor; + final Color lockColor; + final Color foregroundColor; + const ActionTheme({ + required this.deleteColor, + required this.editColor, + required this.lockColor, + required this.foregroundColor, + }); + + @override + ThemeExtension lerp(covariant ActionTheme? other, double t) => ActionTheme( + deleteColor: Color.lerp(deleteColor, other?.deleteColor, t) ?? deleteColor, + editColor: Color.lerp(editColor, other?.editColor, t) ?? editColor, + lockColor: Color.lerp(lockColor, other?.lockColor, t) ?? lockColor, + foregroundColor: Color.lerp(foregroundColor, other?.foregroundColor, t) ?? foregroundColor, + ); + + @override + ThemeExtension copyWith({Color? deleteColor, Color? editColor, Color? lockColor, Color? foregroundColor}) => ActionTheme( + deleteColor: deleteColor ?? this.deleteColor, + editColor: editColor ?? this.editColor, + lockColor: lockColor ?? this.lockColor, + foregroundColor: foregroundColor ?? this.foregroundColor, + ); +} diff --git a/lib/utils/app_customizer.dart b/lib/utils/customization/application_customization.dart similarity index 75% rename from lib/utils/app_customizer.dart rename to lib/utils/customization/application_customization.dart index 9c4b82e2d..ee57f2f16 100644 --- a/lib/utils/app_customizer.dart +++ b/lib/utils/customization/application_customization.dart @@ -1,426 +1,10 @@ import 'dart:convert'; -import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:privacyidea_authenticator/utils/customization/theme_customization.dart'; -import '../model/enums/app_feature.dart'; - -class ThemeCustomization { - static const ThemeCustomization defaultLightTheme = ThemeCustomization.defaultLightWith(); - static const ThemeCustomization defaultDarkTheme = ThemeCustomization.defaultDarkWith(); - - const ThemeCustomization({ - required this.brightness, - required this.primaryColor, - required this.onPrimary, - required this.subtitleColor, - required this.backgroundColor, - required this.foregroundColor, - required this.shadowColor, - required this.deleteColor, - required this.renameColor, - required this.lockColor, - required this.tileIconColor, - required this.navigationBarColor, - Color? actionButtonsForegroundColor, - Color? tilePrimaryColor, - Color? tileSubtitleColor, - Color? navigationBarIconColor, - Color? qrButtonBackgroundColor, - Color? qrButtonIconColor, - }) : _actionButtonsForegroundColor = actionButtonsForegroundColor, - _tilePrimaryColor = tilePrimaryColor, - _tileSubtitleColor = tileSubtitleColor, - _navigationBarIconColor = navigationBarIconColor, - _qrButtonBackgroundColor = qrButtonBackgroundColor, - _qrButtonIconColor = qrButtonIconColor; - - const ThemeCustomization.defaultLightWith({ - Color? primaryColor, - Color? onPrimary, - Color? subtitleColor, - Color? backgroundColor, - Color? foregroundColor, - Color? shadowColor, - Color? deleteColor, - Color? renameColor, - Color? lockColor, - Color? tileIconColor, - Color? navigationBarColor, - // From here on the colors have a default value based on another given color - Color? actionButtonsForegroundColor, // Default: foregroundColor - Color? tilePrimaryColor, // Default: primaryColor - Color? tileSubtitleColor, // Default: subtitleColor - Color? navigationBarIconColor, // Default: foregroundColor - Color? qrButtonBackgroundColor, // Default: primaryColor - Color? qrButtonIconColor, // Default: onPrimary - }) : brightness = Brightness.light, - primaryColor = primaryColor ?? Colors.lightBlue, - onPrimary = onPrimary ?? const Color(0xff282828), - subtitleColor = subtitleColor ?? const Color(0xff9E9E9E), - backgroundColor = backgroundColor ?? const Color(0xffEFEFEF), - foregroundColor = foregroundColor ?? const Color(0xff282828), - shadowColor = shadowColor ?? const Color(0xff303030), - deleteColor = deleteColor ?? const Color(0xffE04D2D), - renameColor = renameColor ?? const Color(0xff6A8FE5), - lockColor = lockColor ?? const Color(0xffFFD633), - tileIconColor = tileIconColor ?? const Color(0xff757575), - navigationBarColor = navigationBarColor ?? Colors.white, - // From here on the colors have a default value based on another given color - _actionButtonsForegroundColor = actionButtonsForegroundColor, - _tilePrimaryColor = tilePrimaryColor, - _tileSubtitleColor = tileSubtitleColor, - _navigationBarIconColor = navigationBarIconColor, - _qrButtonBackgroundColor = qrButtonBackgroundColor, - _qrButtonIconColor = qrButtonIconColor; - - const ThemeCustomization.defaultDarkWith({ - Color? primaryColor, - Color? onPrimary, - Color? subtitleColor, - Color? backgroundColor, - Color? foregroundColor, - Color? shadowColor, - Color? deleteColor, - Color? renameColor, - Color? lockColor, - Color? tileIconColor, - Color? navigationBarColor, - // From here on the colors have a default value based on another given color - Color? actionButtonsForegroundColor, // Default: foregroundColor - Color? tilePrimaryColor, // Default: primaryColor - Color? tileSubtitleColor, // Default: subtitleColor - Color? navigationBarIconColor, // Default: foregroundColor - Color? qrButtonBackgroundColor, // Default: primaryColor - Color? qrButtonIconColor, // Default: onPrimary - }) : brightness = Brightness.dark, - primaryColor = primaryColor ?? Colors.lightBlue, - onPrimary = onPrimary ?? const Color(0xFF282828), - subtitleColor = subtitleColor ?? const Color(0xFF9E9E9E), - backgroundColor = backgroundColor ?? const Color(0xFF303030), - foregroundColor = foregroundColor ?? const Color(0xffF5F5F5), - shadowColor = shadowColor ?? const Color(0xFFEFEFEF), - deleteColor = deleteColor ?? const Color(0xffCD3C14), - renameColor = renameColor ?? const Color(0xff527EDB), - lockColor = lockColor ?? const Color(0xffFFCC00), - tileIconColor = tileIconColor ?? const Color(0xffF5F5F5), - navigationBarColor = navigationBarColor ?? const Color(0xFF282828), - // From here on the colors have a default value based on another given color - _actionButtonsForegroundColor = actionButtonsForegroundColor, - _tilePrimaryColor = tilePrimaryColor, - _tileSubtitleColor = tileSubtitleColor, - _navigationBarIconColor = navigationBarIconColor, - _qrButtonBackgroundColor = qrButtonBackgroundColor, - _qrButtonIconColor = qrButtonIconColor; - - final Brightness brightness; - - // Basic colors - final Color primaryColor; - final Color onPrimary; - final Color subtitleColor; - final Color backgroundColor; - final Color foregroundColor; - final Color shadowColor; - - // Slide action - final Color deleteColor; - final Color renameColor; - final Color lockColor; - final Color? _actionButtonsForegroundColor; // Default: foregroundColor - Color get actionButtonsForegroundColor => _actionButtonsForegroundColor ?? foregroundColor; - - // List tile - final Color? _tilePrimaryColor; // Default: primaryColor - Color get tilePrimaryColor => _tilePrimaryColor ?? primaryColor; - final Color tileIconColor; - final Color? _tileSubtitleColor; // Default: subtitleColor - Color get tileSubtitleColor => _tileSubtitleColor ?? subtitleColor; - - // Navigation bar - final Color navigationBarColor; - final Color? _navigationBarIconColor; // Default: foregroundColor - Color get navigationBarIconColor => _navigationBarIconColor ?? foregroundColor; - final Color? _qrButtonBackgroundColor; // Default: primaryColor - Color get qrButtonBackgroundColor => _qrButtonBackgroundColor ?? primaryColor; - final Color? _qrButtonIconColor; // Default: onPrimary - Color get qrButtonIconColor => _qrButtonIconColor ?? onPrimary; - - ThemeCustomization copyWith({ - Brightness? brightness, - Color? primaryColor, - Color? onPrimary, - Color? subtitleColor, - Color? backgroundColor, - Color? foregroundColor, - Color? shadowColor, - Color? deleteColor, - Color? renameColor, - Color? lockColor, - Color? tileIconColor, - Color? navigationBarColor, - // From here on the colors have a default value based on another given color - Color? Function()? actionButtonsForegroundColor, // Default: foregroundColor - Color? Function()? tilePrimaryColor, // Default: primaryColor - Color? Function()? tileSubtitleColor, // Default: subtitleColor - Color? Function()? navigationBarIconColor, // Default: foregroundColor - Color? Function()? qrButtonBackgroundColor, // Default: primaryColor - Color? Function()? qrButtonIconColor, // Default: onPrimary - }) => - ThemeCustomization( - brightness: brightness ?? this.brightness, - primaryColor: primaryColor ?? this.primaryColor, - onPrimary: onPrimary ?? this.onPrimary, - subtitleColor: subtitleColor ?? this.subtitleColor, - backgroundColor: backgroundColor ?? this.backgroundColor, - foregroundColor: foregroundColor ?? this.foregroundColor, - shadowColor: shadowColor ?? this.shadowColor, - deleteColor: deleteColor ?? this.deleteColor, - renameColor: renameColor ?? this.renameColor, - lockColor: lockColor ?? this.lockColor, - actionButtonsForegroundColor: actionButtonsForegroundColor != null ? actionButtonsForegroundColor() : this.actionButtonsForegroundColor, - tilePrimaryColor: tilePrimaryColor != null ? tilePrimaryColor() : this.tilePrimaryColor, - tileIconColor: tileIconColor ?? this.tileIconColor, - tileSubtitleColor: tileSubtitleColor != null ? tileSubtitleColor() : this.tileSubtitleColor, - navigationBarColor: navigationBarColor ?? this.navigationBarColor, - navigationBarIconColor: navigationBarIconColor != null ? navigationBarIconColor() : this.navigationBarIconColor, - qrButtonBackgroundColor: qrButtonBackgroundColor != null ? qrButtonBackgroundColor() : this.qrButtonBackgroundColor, - qrButtonIconColor: qrButtonIconColor != null ? qrButtonIconColor() : this.qrButtonIconColor, - ); - - factory ThemeCustomization.fromJson(Map json) { - bool isLightTheme = json['brightness'] == 'light'; - bool isDarkTheme = json['brightness'] == 'dark'; - if (json['brightness'] == null && json['primaryColor'] != null) { - isLightTheme = _isColorBright(Color(json['primaryColor'] as int)); - } - if (isLightTheme) { - return ThemeCustomization.defaultLightWith( - primaryColor: json['primaryColor'] != null ? Color(json['primaryColor'] as int) : null, - onPrimary: json['onPrimary'] != null ? Color(json['onPrimary'] as int) : null, - subtitleColor: json['subtitleColor'] != null ? Color(json['subtitleColor'] as int) : null, - backgroundColor: json['backgroundColor'] != null ? Color(json['backgroundColor'] as int) : null, - foregroundColor: json['foregroundColor'] != null ? Color(json['foregroundColor'] as int) : null, - shadowColor: json['shadowColor'] != null ? Color(json['shadowColor'] as int) : null, - deleteColor: json['deleteColor'] != null ? Color(json['deleteColor'] as int) : null, - renameColor: json['renameColor'] != null ? Color(json['renameColor'] as int) : null, - lockColor: json['lockColor'] != null ? Color(json['lockColor'] as int) : null, - actionButtonsForegroundColor: json['actionButtonsForegroundColor'] != null ? Color(json['actionButtonsForegroundColor'] as int) : null, - tilePrimaryColor: json['tilePrimaryColor'] != null ? Color(json['tilePrimaryColor'] as int) : null, - tileIconColor: json['tileIconColor'] != null ? Color(json['tileIconColor'] as int) : null, - tileSubtitleColor: json['tileSubtitleColor'] != null ? Color(json['tileSubtitleColor'] as int) : null, - navigationBarColor: json['navigationBarColor'] != null ? Color(json['navigationBarColor'] as int) : null, - navigationBarIconColor: json['navigationBarIconColor'] != null ? Color(json['navigationBarIconColor'] as int) : null, - qrButtonBackgroundColor: json['qrButtonBackgroundColor'] != null ? Color(json['qrButtonBackgroundColor'] as int) : null, - qrButtonIconColor: json['qrButtonIconColor'] != null ? Color(json['qrButtonIconColor'] as int) : null, - ); - } - if (isDarkTheme) { - return ThemeCustomization.defaultDarkWith( - primaryColor: json['primaryColor'] != null ? Color(json['primaryColor'] as int) : null, - onPrimary: json['onPrimary'] != null ? Color(json['onPrimary'] as int) : null, - subtitleColor: json['subtitleColor'] != null ? Color(json['subtitleColor'] as int) : null, - backgroundColor: json['backgroundColor'] != null ? Color(json['backgroundColor'] as int) : null, - foregroundColor: json['foregroundColor'] != null ? Color(json['foregroundColor'] as int) : null, - shadowColor: json['shadowColor'] != null ? Color(json['shadowColor'] as int) : null, - deleteColor: json['deleteColor'] != null ? Color(json['deleteColor'] as int) : null, - renameColor: json['renameColor'] != null ? Color(json['renameColor'] as int) : null, - lockColor: json['lockColor'] != null ? Color(json['lockColor'] as int) : null, - actionButtonsForegroundColor: json['actionButtonsForegroundColor'] != null ? Color(json['actionButtonsForegroundColor'] as int) : null, - tilePrimaryColor: json['tilePrimaryColor'] != null ? Color(json['tilePrimaryColor'] as int) : null, - tileIconColor: json['tileIconColor'] != null ? Color(json['tileIconColor'] as int) : null, - tileSubtitleColor: json['tileSubtitleColor'] != null ? Color(json['tileSubtitleColor'] as int) : null, - navigationBarColor: json['navigationBarColor'] != null ? Color(json['navigationBarColor'] as int) : null, - navigationBarIconColor: json['navigationBarIconColor'] != null ? Color(json['navigationBarIconColor'] as int) : null, - qrButtonBackgroundColor: json['qrButtonBackgroundColor'] != null ? Color(json['qrButtonBackgroundColor'] as int) : null, - qrButtonIconColor: json['qrButtonIconColor'] != null ? Color(json['qrButtonIconColor'] as int) : null, - ); - } - throw Exception('Invalid brightness value: ${json['brightness']}'); - } - - Map toJson() => { - 'brightness': brightness == Brightness.light ? 'light' : 'dark', - 'primaryColor': primaryColor.value, - 'onPrimary': onPrimary.value, - 'subtitleColor': subtitleColor.value, - 'backgroundColor': backgroundColor.value, - 'foregroundColor': foregroundColor.value, - 'shadowColor': shadowColor.value, - 'deleteColor': deleteColor.value, - 'renameColor': renameColor.value, - 'lockColor': lockColor.value, - 'tileIconColor': tileIconColor.value, - 'navigationBarColor': navigationBarColor.value, - '_actionButtonsForegroundColor': _actionButtonsForegroundColor?.value, - '_tilePrimaryColor': _tilePrimaryColor?.value, - '_tileSubtitleColor': _tileSubtitleColor?.value, - '_navigationBarIconColor': _navigationBarIconColor?.value, - '_qrButtonBackgroundColor': _qrButtonBackgroundColor?.value, - }; - - ThemeData generateTheme() => ThemeData( - useMaterial3: false, - brightness: brightness, - primaryColor: primaryColor, - canvasColor: backgroundColor, - textTheme: const TextTheme().copyWith( - bodyLarge: TextStyle(color: foregroundColor), - bodyMedium: TextStyle(color: foregroundColor), - titleMedium: TextStyle(color: foregroundColor), - titleSmall: TextStyle(color: foregroundColor), - displayLarge: TextStyle(color: foregroundColor), - displayMedium: TextStyle(color: foregroundColor), - displaySmall: TextStyle(color: foregroundColor), - headlineMedium: TextStyle(color: foregroundColor), - headlineSmall: TextStyle(color: foregroundColor), - titleLarge: TextStyle(color: primaryColor), - bodySmall: TextStyle(color: subtitleColor), - labelLarge: TextStyle(color: foregroundColor), - labelSmall: TextStyle(color: foregroundColor), - ), - iconButtonTheme: IconButtonThemeData( - style: ButtonStyle( - foregroundColor: MaterialStateProperty.all(foregroundColor), - ), - ), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - foregroundColor: onPrimary, - backgroundColor: primaryColor, - padding: const EdgeInsets.all(6), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - shadowColor: shadowColor, - elevation: 1.5, - ), - ), - scaffoldBackgroundColor: backgroundColor, - cardColor: backgroundColor, - shadowColor: shadowColor, - // shadowColor: Colors.transparent, - appBarTheme: const AppBarTheme().copyWith( - backgroundColor: backgroundColor, - shadowColor: shadowColor, - foregroundColor: foregroundColor, - elevation: 0, - titleSpacing: 6, - ), - inputDecorationTheme: InputDecorationTheme( - labelStyle: TextStyle(color: foregroundColor), - hintStyle: TextStyle(color: primaryColor), - errorStyle: TextStyle(color: deleteColor), - border: UnderlineInputBorder( - borderSide: BorderSide(color: shadowColor), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: subtitleColor), - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: primaryColor), - ), - ), - primaryIconTheme: IconThemeData(color: onPrimary), - iconTheme: IconThemeData(color: foregroundColor), - navigationBarTheme: const NavigationBarThemeData().copyWith( - backgroundColor: navigationBarColor, - shadowColor: shadowColor, - iconTheme: MaterialStatePropertyAll(IconThemeData(color: navigationBarIconColor)), - elevation: 3, - ), - floatingActionButtonTheme: FloatingActionButtonThemeData( - backgroundColor: qrButtonBackgroundColor, - foregroundColor: qrButtonIconColor, - elevation: 0, - ), - textButtonTheme: TextButtonThemeData( - style: ButtonStyle( - overlayColor: MaterialStateColor.resolveWith((states) => foregroundColor.withOpacity(0.1)), - ), - ), - listTileTheme: ListTileThemeData( - tileColor: Colors.transparent, - titleTextStyle: TextStyle(color: tilePrimaryColor), - subtitleTextStyle: TextStyle(color: tileSubtitleColor), - iconColor: tileIconColor, - ), - colorScheme: brightness == Brightness.light - ? ColorScheme.light( - primary: primaryColor, - secondary: primaryColor, - onPrimary: onPrimary, - onSecondary: onPrimary, - error: deleteColor, - errorContainer: deleteColor, - ) - : ColorScheme.dark( - primary: primaryColor, - secondary: primaryColor, - onPrimary: onPrimary, - onSecondary: onPrimary, - error: deleteColor, - errorContainer: deleteColor, - ), - checkboxTheme: CheckboxThemeData( - checkColor: MaterialStateProperty.resolveWith((_) => onPrimary), - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { - return null; - } - if (states.contains(MaterialState.selected)) { - return primaryColor; - } - return null; - }), - ), - radioTheme: RadioThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { - return null; - } - if (states.contains(MaterialState.selected)) { - return primaryColor; - } - return null; - }), - ), - switchTheme: SwitchThemeData( - thumbColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { - return null; - } - if (states.contains(MaterialState.selected)) { - return primaryColor; - } - return null; - }), - trackColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { - return null; - } - if (states.contains(MaterialState.selected)) { - return primaryColor; - } - return null; - }), - ), - extensions: [ - ActionTheme( - deleteColor: deleteColor, - editColor: renameColor, - lockColor: lockColor, - foregroundColor: actionButtonsForegroundColor, - ), - ExtendedTextTheme( - tokenTile: TextStyle( - color: primaryColor, - ), - tokenTileSubtitle: TextStyle( - color: tileSubtitleColor, - ), - ), - ]); -} +import '../../model/enums/app_feature.dart'; class ApplicationCustomization { // Edit in android/app/src/main/AndroidManifest.xml file @@ -529,76 +113,6 @@ class ApplicationCustomization { } } -class ExtendedTextTheme extends ThemeExtension { - final TextStyle tokenTile; - final TextStyle tokenTileSubtitle; - final String veilingCharacter; - - ExtendedTextTheme({ - this.veilingCharacter = '●', - TextStyle? tokenTile, - TextStyle? tokenTileSubtitle, - }) : tokenTile = const TextStyle( - fontFamily: 'monospace', - fontWeight: FontWeight.bold, - ).merge(tokenTile), - tokenTileSubtitle = const TextStyle( - fontFamily: 'monospace', - fontWeight: FontWeight.bold, - ).merge(tokenTileSubtitle); - - @override - ThemeExtension copyWith({ - TextStyle? otpTextStyle, - TextStyle? otpSubtitleTextStyle, - }) => - ExtendedTextTheme( - tokenTile: otpTextStyle ?? tokenTile, - tokenTileSubtitle: otpSubtitleTextStyle ?? tokenTileSubtitle, - ); - - @override - ThemeExtension lerp(ExtendedTextTheme? other, double t) => ExtendedTextTheme( - tokenTile: TextStyle.lerp(tokenTile, other?.tokenTile, t) ?? tokenTile, - ); -} - -class ActionTheme extends ThemeExtension { - final Color deleteColor; - final Color editColor; - final Color lockColor; - final Color foregroundColor; - const ActionTheme({ - required this.deleteColor, - required this.editColor, - required this.lockColor, - required this.foregroundColor, - }); - - @override - ThemeExtension lerp(covariant ActionTheme? other, double t) => ActionTheme( - deleteColor: Color.lerp(deleteColor, other?.deleteColor, t) ?? deleteColor, - editColor: Color.lerp(editColor, other?.editColor, t) ?? editColor, - lockColor: Color.lerp(lockColor, other?.lockColor, t) ?? lockColor, - foregroundColor: Color.lerp(foregroundColor, other?.foregroundColor, t) ?? foregroundColor, - ); - - @override - ThemeExtension copyWith({Color? deleteColor, Color? editColor, Color? lockColor, Color? foregroundColor}) => ActionTheme( - deleteColor: deleteColor ?? this.deleteColor, - editColor: editColor ?? this.editColor, - lockColor: lockColor ?? this.lockColor, - foregroundColor: foregroundColor ?? this.foregroundColor, - ); -} - -// /// Calculate HSP and check if the primary color is bright or dark -// /// brightness = sqrt( .299 R^2 + .587 G^2 + .114 B^2 ) -// /// c.f., http://alienryderflex.com/hsp.html -bool _isColorBright(Color color) { - return sqrt(0.299 * pow(color.red, 2) + 0.587 * pow(color.green, 2) + 0.114 * pow(color.blue, 2)) > 150; -} - final Uint8List defaultIconUint8List = base64Decode( "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADr8AAA6/ATgFUyQAAB7TSURBVHhe7X0JuF1Vefa79z77THeeM9yEYMKgEALEBhmMIFADiK1WqOPvw////WsLTyu/Px0stFpbq7Y+aC1K0TIIte2PUgUqRhEtgojBiAGChARIcpM7j2ee+777nHOb5E7nnnv2uecm933ud9fe65yz91rre9e3vrX22msZOI7Qff2tAQarKRsop1JOoZxMWUPpojRSvBSLMhMylAQlROmn7Ke8SnmZsoeyl3K457Yb4wyXPJY0AajwBgZnUN5EOZ+ykSJl11PcwgTlAGUX5acF2U1CRBguOSw5AlDpqxi8hXIF5QKKarhJWSzIYuyjPEF5hPJjkkGWY0lgSRCASm9hcDnlGoqU30GpVUj5j1H+v0KSQRajZlHTBKDiNzH4H5TfopykuCUG+Qv3U+4jEXY7MTWGmiNA9/WfZ5pyl/Hweso2ik/xSxxRykOUL5EIjzsxNYKaIgBr/FUMPkq5xIk4/pCjfJfyORLhB07MIqMmCEDFb2XwZ5RfdyJODDxI+WsS4Wf508XBohKAin8dg7+gfICymJ78YiFJuZPyVyTCISemylgUAlDxHgY3UG6mtCnuBMdhysdJgq/kT6uHqhOAyj+bwecp6s4t42hoHOFGEuGl/Kn7mG1ItOKg8v+Qwb0UDdMuYyo0dP3exi3bhid2bP9FPspdVMUCUPEah7+Nov78MkrDPZSP0BqM5U/dgesEoPI1Rn83ZbnWzx/PUj5EEui5gytw1fOm8j/I4HuUZeWXB/lLj7EcfzN/Wnm45gMw0fLwv0jR49dllI8g5Rr6BaP0Cyo+ZlDxJoCK1zX/nqJu3jIqCw0cqWJVDBW1AN3Xf179+7sov+NELKPS2EpL0EZLoO5iRVAxArDm2wy+RtGo3jLcw3kkwQqS4D8K5wtCRQhA5cuZVM1/vxOxDLfxRpKgvRKWoFK9APXx5fEvo3q4gRXvU4XjsrFgC8BE/CUDPcJdRvXxZlqCCVoCzUssCwvqBVD5/5tB1R9gLOMoaI7BtewdfCN/Oj+UTQAq/2IGaoP8TsQyFhMaLr6UJNiZPy0dZRGAytfU6x9TluI8veMVz1MuJgmG86elYd5OYPcNt8pvuINSlvLFuFg2h554Fj0JSiqLsUwOqVzOsWUnApRP5XeU+Vb+nXJgeSQYt4A2+UyKRl7nhXnfj7VfI1GfzJ/NHxNU/kVtdbji7A4MjSXQP5rGofEkDkwk8Hw8hUxG0+xZRKSmTao1mwZsw1iS04WylCSVOsY8p5UtRbDIPZYHZwZsrG3worvZh84WC+2NPvz7M33YORFHHfO8AFxPK/ClwvGcmNedqPyLGGgyY9nj+2L8h0/twtWXHkAo8SyVuw5Grh2ZVBMS8SBCERsj48DgWAaHR5M4OJHEy9EkhtJp8oIlKCZQWlhIfhLDWlBZVQasyIirRlPRjpIlholOjwcb6rxY0+jFymYvOpsttDQBDfUp+PwRWJ5x5MwhpLKvodl3Lv7lm/W4ZzCJbs+C6K73EC4iCZ7Ln86OkouPyq9joCnN5zoRZUIE+N1TunD51ufQH/4MRHbq0Qkt02Kt3wyvdRJscxXMXAey6RYkEnWIRHwYCxm0Gln0jqbQQ6vxWiSJV5IpFjhL3MhbjQYyok7X4kVLzlwJkNlOU8kRHoSlcSk5p4SbWO+1sa6etbnJixXNNjqaTTQ3ZlEXTMLrD8O0xpAxBpDOHkYysx+pzA5k+HvxhZd0LrWq7k/xwP0NuLc3ju6W1vwH5eOHlMtJAtmdWVFyGZEA6u/fkj8rH/9NgBcwGPkbR/FHwsm2CsY5c8rXqeW2dQblZBJjNSy955ltRSrZiGjUh4mQheHxHPqc5iSB/aGk05ywPeEVeCVew8vmpIk3m6050T0lap/HqZ3kEWbbsCxs9Mts+7Catbmr2YO2ZgNNDRkEAnHY3hDvM4wM+lmjD/G3r9Ls755UdP4q+X/HFro+X1H3ZyRAPe59qQerOlbADDYwMYUflgdNJvlC4XhGHJuWaUHl67n0kxQ9mlwQ5iLATHCKgv+KRSKrYVGTHnMtibGBCl4Lj4hRaE7i8QBCITYnNIgDY2k2JykcDCXwWiyFfjXIjlaOLGBekGxbRfO7lu3zGrbJq1ibO6nootn2+6OwbJltKjrXRyUfYG1+mTW7x1F0UV9OlvivxKwdQ4BD6CRbPS1dMP0s7vJJoN7AFpLglfzp9CgpjSTAwwz00saCUS4BZoPKqFhMxebEYwZJjE0UEsRcyRaiE7l0M5LJAJsUG6mUiWTaw9Yj51gZrycDrzdLSbE2R2B6xmjhB1mbewtm+xdUcnLSbDv3KtxvoZhCAJsJUnPY2gXD618ICe4lAfRq3YyYM/lU/jsZPJA/WzjcIMBMULFNlh3vo3upOTHNLoYdVF4jo01+L8PvjbNWD1LJg45CJEVWSckuJnNaC6CEGx7bsQSGTZ+7PBKoAdMA0Y/yp1MxU3PogMrXe3kVnYBQTUhpUrojimAZylSn0v2IpZ5HNPkTRJJPMHyK57sZT+WryPg9fb/4W12n6iDrcukU0mMDyGXYAyrP1Cgbt3Tf4EzSmRazEoDQ490Fef21CpXIsVJzEAlSSWTGBlmX5ZGWlcq3ktDvKBxPwYwEYO2Xw3dj/mwZiwaSIJuIIj0un07mqSz8P+pzWl3PZgHeTdHwYsVhMlOGyXaXx8X2VscnEo7Nu2lmnXKZFoaJbCyEzMRoIWLe0ACeVlSZgmnnA5Atmtv3ZR06ERVEmM7M+oAXm05aiaB3E+p858Bnr4dltTKjaRZGyPF3HCn8przmrzZQzIeTF+ZDPQ7b6obfPhf13ovQ4L0MjfZVMJLr8eTOIewJx6YfClZzkIw7FzB9Wgtr3mid2LH9nwvHk5i2aEmAtzHQe+yuIMnSWMluzslWjH1t9t47/GhttdHI/nagLgmPN0xqjiINDZMeYhfsNcqvnCFXjQYLTsL5b9oMVBmOcpU2hUxQ0Xm0rddT1jmjmh6tapNpRjpRj1jEi4lxdtRHUugfSuDwUBR7hkI4lIzBP53yj0IOnqZ2mMFGHjp3LBX0JHEhewRHTS2f9m4kgNa30Xo8riFFGU+lEB8aoFnQAls0Rh4PTgn6cHJLEGs6guhqD6C9zUZzk4FgQxq2PwrDYnfNGEJ6ciBG/fOsY0rzGmCmmKu5irEcOLc48h4Uj8mumrUJXmsNbGMlzFw7vfcmpOIBRMIejI/lMDicpKLjODgUwSujMeyLJqgO6sPkxWSDPSaa2D/1zcPUeZo7YQbq5kuC20mA3yscO5hyRypfa+xpYoGWYHMXyjC92/TYELLxMHJs6zTerieGmTQzpiov55fWoivgw6lNAaxpC2JlRwCdbT40t5iob8jCF4jD9LLpMIaRymkotofE2IN0tt/p1k0qTTJHGTtf5b/CT5zvy2x7zJWszaewNndT0eybZ1uRTTUgEfUjHDIxOprFwHACvYNRHBxmjR6PYSCedPLnKJkK9ngMNLKGL/w5BVPHsvK0rGBzMK+Boj7K2STB5CpmU9JBAtzE4LP5s2qASaBdT08MIxudcDI2HcSFKDMaU0feIYdiWQheG5saAlhLYqymxehs96Ot1YOGxhz8RzUnGtVTc3KIFmMfLUZ8UsmCCsI066mck6no1c7ooQedvE8L0sk6xB2zbWBkJI3+4TgOUdH7R6L4ZSiOrB5IyUN3arOBgGUiSCVr0Mk1SOmWB3brivkOFP1PEkAzuB0clUQqX1nQmP95TkSV4ZAgIhKUXnJyCRK0GCFVdRFDLZ2e0rE5Ob3Oj3WttBoddVhBYrS22CSGAX8gC4+docLVE1FtzNFSWEglLcRjJkITWQw57XMcPWyfXxuN4qVIwWzrqaNcZJpt1WYvZbaulKug0qV8WQKD+S2RBN8lASZ7BMcSQIM+mmGqlzwWBZnwKDKhsXmRYDqoKNSc6KleNi1yMEJsYXPCRhsrbQ/qWFNljjP8XpiWpS/FL6VoWnL/bbatgtn2LNhsuwSRgL0Cu4XWylCi5ySBlsA9hyTQ4pZTyCtmLJryBau+BVbjgp+HO8rSo992KrnTR2NeR2lQaKDDl0XSSGI4y3Y6E3XCFM8V31lv5L8XpPgstPH3uk5NKl9g2nKJGNLjQywzMXxOyLfTMnwOJgnQff0XlEd1/xYdVn0zLHZ18lgYEaaDMqr2WYotis5rVslzgenPxsJIh0YKEXPiykJ4pAXIasUuPfdffLD2W3WN7OpoRVgmcYHW4ISARgvpP2XCmiE+J5W3sLlnm3HENxnxPgZTRorcxlg6gzCl3nHHjgGZnY5FkBztY7PsuP3HDUTpHP2RoM+Lekttd4WgykPrqQo0R8XZRj9g+5EE0NDvh/Nn7sPx3pNJbO3uwLr162EESQF578eafJIAGXaz0lpS73gBHc9sBqGhAezb9Qs8fngYHUF/RZsgWU8zUD8bCbQ24S3OPal8dWzk/W/WudtQkjLpFK44/zx0bdqCCduP+Gz+SyVLpoagqdUd0RGMPP4gbnvsKTqeZY3xTwOWMJsEW5NJ9NxgehI8SgJcXiSAXvLQgkTNOncbkWQKW88+C10XvQ1hpk0josepjmeFUxHo4mw249j7wFdw+89eRKe/QivqSOnOQJFmFPmmI0EP5eyiE6hFnKqifPW5uwM2Vm4811lC2zpBlS8o3xYt3wvw4w1bfx0nmVlnxLMicJrONNKjg87MIuf8aKygnFwkgLZdqQqi2SzWtLXAV99UYrf1+IbUEqN/G2lahTNWtWO8ks4ulZ6j75TWjCLnukeRQM3+6UUCvL4Qug49tfPYHufBzzLyUBM4Ahsef8CxkBWFSJCMzzRQ9PqiFtYXQtfhcLDCeTweIL27ViwkgZ626lnLMTjFpANY3GptGYuJKU10haGBomgImZCmlU3ebIMsgJy/Vud0Gcc3aAk0UpiJjDvHxOoiAdyf/LGM2VGtZlEkCI04zw543CQCaEu2ml3mJasULmHJSXg4G5zKSNH33G4J8sg5TmE2FvEa9AH0ZKgiiw6Wgol0BhevXYF1V/42htkTUTlNBxWGl6XRHu5HMhZlIVWnaCqJHD07rz+IoYYuZyLssTnQubKlUdDXZeN4+Z8/h2/s60Une0nugwkyLYgA1/Ls3/KR7mM+BGiwWDjfuw/f+d6jaAgu+MXkqiMUjWHbpRej7ooPYYLd8CIBilxOpIHhGPCjCHBTMI5DD30OX/sVCeCtBgHyUPnX9CpfaSax3/Rh1LSXnPRbXqTofYvMgmZ863giAeyhM/7oAGUs/5yr+J1qQwSoHt3KBQtOU7eWmhSMvPOXopnvY03fOQh8i/JT+mDDGpcpfm2RMJMFXkaFUMcSDoWAh/uB7SPAbm1MLyyi0o/EMgFcRgv9GCsORDQpdZFr+3QQAZYfybgIte2TrUENQgQoGqVlnIAQAfRi3jJOUIgAWlhwGScoRAA9HjqeZlwuo3RoRppDgOVm4ESEYYRFAL1JIFnGCQbDtPpEAL0sqPfGl3GiwfK8YvbcdqO6qq/mY5ZxIsGw7L2yAELV9qtfRo1Ai03Z3heLBNB2IzWHxXpCdvwjp/Zf6xBPEuBXlFj+sDqYS7n6XK9s+7V5wlJlQk4LUZv51WxqCSxPw2OPmIH6ySZgP+Vg/tBd6IYprdpRglZFgPaGOuQX8F2CIAFsnx/REghs5LJIaf2jajwzMAwSwLevYdPWXocAdAQ1DvBLHbsNTYrQi6HGHEpVOcRZcK3teo19qZqALPz1LRguwQR4chnnbenJ6UJuglbJ8Hqf2X3dmc5AUBFPFEJXkWUGPckEOo0UjwuR00AJc6ZRNa/Bea1BxPVK0RKCUrvaa8LbsgIvFR8FzwR+ZudSiMQS81orsCzQKmkZetP2Ofo+kgBaHUxJdRVeZnA8GkdjJj5tmSjOsRIswQHapYNmF87fdAYm4olZy7DWMJTOYvPqNsTrVqF3LgvAjNnpKIbDUQSUeZdh2L6w4Q04K4YeSYDdlD35Q/eg9Xj2x5IwouNgBZmEiC/RPj2HQ8DTg8BjI8C3whbWnnMJ3hDQi6VLwwpIhblYDJs2bcaeXNOcLdhai4qIj+FAJOmUj6vg9U3bv6vtsvc5W8lMqoB+gHoB2m3KVSh7PekcIkMDaGbGtSiI4qJs/l4ZYwIGgO8z3KvHU/xgP23Sc/5T8f6r345QOFR7HvU06Gftv2RFPZpOuxD36R342XRKcpzmIfFH+zCQmmaZnIoib/4N2/v9Z9/e7DhhR1oAQXsDuQ/LwoEDB1GXzWCItHueNf0h1vgnWPP7lSyVQqEkVCHuYnxow9tw8zsuxfBEyNlhsxahJIdppZqSEVx19W/hx7mVJc232mBnMXSYFdJyeX4ui419/4zp9WvPZwfHEuApyqy7TFUCnbaF5w4cRnBkFN8dBnayliSk02no76ia8Z8et5E5+xp88revRl0yioGk6+7KvKCk9zNNXZkYPvaB9+LlzgvxbeZrziadn6/OjWPP3n3wel1eotEx/75d3hXrflGIcdbDnMTEju2Jxi3btFi0q0vFasr0C9EELlvZCG9DN/bO5SUX8ETcwqmrTsc7zlyD5rED+NmhYURMy1nxczExkM4gQgfumg2dePe1H8Lz7Rfgn0LsazNPc9mqc73ApvEX8JUfPYFmn6+UYigTMv9ercF4+/7PXPdYIfJoAggkgGYIXUdxLy1EjManPT6Oc049HT+Ms10q5W78zs8TNLOBlbho42ZcdlIzWiID2Nk/jHAqiyT7t1pv39WEE7LqI5kswuyZRFNxvHNtOz545TasvPBafNs4CQ+x5s+lfCeN/MJ76jMYe+ZB/LRnGPXaLNkt8F6mPxjz1LfcOPH0d9jg5jGlrLrzC0arj/gmJ8JF9CQS+MxVl+I/G9+I75RQaIIS7LgAPLiaPYNfs0PwD7+E/r278OKel/DLvlEc0ot4JttTjwU/rYO6VrI6+u2UDE+DYhrU6dDmz+FsFjlnlXKaqlwG6/wWzlzRitM2nIKu9Wch3nYKdmaa8C05fLo1b1JC049uJvFGdrzu+MdbMWj4tNC4izDgaWr7zuE7bzlq/8dpb0kS/C6D2/Nn7mGUhXpJiw9vufK9uGlC6wMXPigVhe+f4wO2+HLozo3BF+5FfPggRvt7MDTYj4GRMQyG2MeOpzBG71yLR+d/N13W+QGjG6hB7UHc4rXQGvChrbEe7a2tzqhkY/sqeFtXIx7sQo/RhGcSBnaqx6JrlqhANVhKxh83pxH9zzvwxZ88h07exz3I/JNgLZ3XHLr9pm8UIh3MRIA2BnIU1jgRLkE3P5hI4o9/7TRkz3g7/nbUKskKTIuCAlbTfm1ku7rGyqHVSLCnEYU3FQFSrJ6UbDKObDqJjPbi02YOrOFar8hkz8SkF27SctBLpvWgeIPIeAJIWEGEDT+GcjYO8me7UsgP785D6UXo67JgVwSBS0efwsfuvBONgbop3nhlkYMZaHgh8LqNW179y/fITk1ixuSTBBXZLHouyFSOJhP4+LaL8ULbefgaPRDt0FGKCZ0RRzJIOaS08JodlGYe67VzW8KPVPDyQSXshjtbDoxTxpiAPl1HcmxidM0yoJ9J+d288Ue9PXj43s/jByNJdC5su/i5QYJbDS0f6b3rz6dsJj1jVkiAtQy0dYysgavQ3vudRga/c9U2/CRwJu5nv1+LiKnsXcN0Fy9TsaWgqHy53Z9tGMULD9+Oe547iK6A1918EobH3m93dG/u+YePTFklaka3k13CcfYItGb7hfkY96Dhz4GsgUOv7MVV6xqxoqETz+p9JZaaazrRhY8Vl6D6LeW30un7eMMYXvvBPfjqz/eiK+h3XfmCYXo+zdr//cLpUZjL9nyRMpA/dA8qhGY6XnvSJu54+Lt4/eBP8Ed0kKQUFZyLuqkK5PBdRJfiZn8f9jxyB7780xfRVReoivLpVL1GL+erhbMpmLXjSSswQSsg9/St+Rh3EaAlGKe6H3npVZyHw3hXdwe8nnrs0Z5MhBzEpQInqdIwq9iHG4E3R3bhhw98Ff+6p69qNV+g+f/YwL/93eOF0ymYlQACCbCLwbsorvsCgvbmqfN4sL1nBNbeHdjaksGbuzpZYF68Jk9NqHUiSLtM4zvqgP/lH4bnhYfwlfvvx9OhNLp87rf5RRiWZ4d3xUl/MPH0IzM+QyupKOkQvofBv+TPqgMlTOME4YEevH9dCzZdcBlinRvxTKoeD8X5YdEzrxUyFLXKKvXuAHCuOcY+7jN48vFH8R/7R9BMk6+5EFWDuraBum199/7V9kLMtCg5RSTBtxnMuA25a6AT0DPYy/ZoGNeevgZnnXs+7O6z0GN3YmfCwpMahFksMhyh9LdqTN+bxqpkL6L7n8XPdzyFB18dBPx+dHmsqtX6Ikxf4O6+r39aQ/qzYj4EOI2BnhZqXcEqgknMZZAaHUB/mP3DZBIXtAex+YzTsXrDWWyYXochu43Ngxe/oq/wvJqJCvXbHRyrOV7LZrv+Rnr0G9if7zZjaE4OIT24Dz0v78KO3XvwzBi7MFR8JxW/GDA83h6rueNNh//xjw4VombEvIqGJLiewT/kz6oIms6c1r4f6UMulUSMSgklWPWzKZzfWoc3nLwGq9auR13HWqChC1G7ERNGECM5jzNiN0IZIimG+LtRKXSm6lgojVaGjlDRWkqzjXpsMrNoRhIN2Qjs+BgyE32Y6D+AngOvYPeBXuwcZ7vk8aLJZ7s/r282mCasYOP7eu/5RElN9rxTShJ8i8Fv5M+qCJGAyk+P9h+1AYImi044Gz6m4DVz2Njgw9qOZnS2d6CppR3BxhZ4g00w/fTIbDbOlo2cxPCQB7ymLs3/WszNyKZhkGg5EgspbQkbQSoWRiI8jvDECEZHhtE3PIr9oyHsicnUkBleGy2s6a5P5SoRbPfv7rvvU3Oa/iLKIYCeD+hpoUYKqwuRIJlgc9CX3wBhmkJP0WeQ84g0P9d3tEa+kXN2526yTPYwTASoMB+P8z/XP20dm9+XOE6JpbOYYDjm7FfPzzUsqfFpK/90sU61bN4l5z4Mr2+3t6P7zQf//g9K3kCwrGyQBNph9CFK9Rs5ai2rnTJpCfIbIJSWhaLl18CSXkrRL3Xu/JOOGSgzemgsYui8tCvXAmi9LDtqNbRcdvifbpafVjLKUuDEju17G7ds02Dt5Bak1YQzsZFmPBs/6sHWrCgqVM/qNTdA4w3HiunIUlM+YVps9xtu6L3rL9RTmxfKrsEkwZMkwToeLspuo86W6cx4TiSg4k5YiLT+ulv77v3kpwox8wIbtgXh9yk/yB9WGbTl2h2zEhtNL2WYXv8DLVvfdVPhdN5YEAEK7xJoy1ntOVh9OCRoglWvly9OPBIYtu/Hdkf3dS/+n830dsvDQi2ASKCnhXpW4PpbRTPBamiFGWwgCeTanRigH7ST1u+ag1+4YUHL/C2YAAJJoCVmNDbg+jsFM8HT2Ma2UHvlHv8kMCzPLlq+3zx8x5+wK7QwVIQAAkmgRSY04/RlJ6LaYF/d09QOwxc8vpsD03rWDNRfffjOWyqynkPFCCAUSLCNUpW1Bo4GlW5Zzq7ZM+yVu/RhmE+ZvsCVvfd84kAhZsGoKAEEkkDNgEjwIyeimqDSaR7zJPDYxxcJDOMRevxX9d33172FmIqg4gQQSAKtO/h2yn1ORDUhEthekqDTsQjHBwmMu+D1vbPv63+jVV0rCtdHULqvv/VmBppiXt3RGqO8IeMaxMcHvvmFTxSOKw7Xx/Indmx/vHHLNr1kcjGlqhtUljNkXEPQ8r3XUfm35U/dgesEEEiCl0iCf+fh6RS9fVw1qDnQmnjZhEiwZKzAzynvovJdH2WtCgEEkmC08bxtX+dhmHI+xc2X4Y6CoVe92CTk2CQorHF8mfIBKr9inv5sWJTSoF9wFoO/o1zuRFQJ2jM3E57cOLnWoLUaP0rFfzN/Wh1UzQIcCVqDfjYJ6iFoMGMTRRtYuw72oekPZpBLxWuJBOqm6MWN91P5O5yYKmLRS4HWQCtB/l/KhylNinMV7BamxwbpGGr3bFd6wfPB05SbqfhH86fVR81UAxLhFAYiwgco9YpzB8wyrUBqbGAxfYJ9lM9S7qbyNbF90VAzBCiCRFBP4fcoesysl1MrDzmEzizjfjYHiWqSQA/NvkT5KhVfE7u01BwBiiARNPlUJPgg5QzFVRQiwTSzjF2CXq+7g/J1Kr7io3kLQc0SoAgSIcBAL6e+l6Jeg3yGykAkmGOW8QIQoqgffzdlOxWvF9pqDjVPgCNBMqxkcAlFr6hp3YJuysJApVdwyFgzpDSIo3cnHqTSF+fR+DywpAhwJEgGva18LuUtlAsob6B0UeYPkSAWYe9g3kshqAvXQ5HStQCDnoC+SMUvmSdQS5YAx4KEkPJPpWyknFk4lh8hR1LPIGZfhlMkiIaQHh8qREyBvHW9cCGFv0iR0iW7qfCSX8SoNRw3BJgOJEUdg1aKSFAUDTqJEH5KcZ0o2f4USRBNDfd62T1s4LEUrvl2WldHj7cPF8JhKrzsSZi1BeC/ABatIOvf+x1NAAAAAElFTkSuQmCC", ); diff --git a/lib/utils/customization/extended_text_theme.dart b/lib/utils/customization/extended_text_theme.dart new file mode 100644 index 000000000..289e38395 --- /dev/null +++ b/lib/utils/customization/extended_text_theme.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +class ExtendedTextTheme extends ThemeExtension { + final TextStyle tokenTile; + final TextStyle tokenTileSubtitle; + final String veilingCharacter; + + ExtendedTextTheme({ + this.veilingCharacter = '●', + TextStyle? tokenTile, + TextStyle? tokenTileSubtitle, + }) : tokenTile = const TextStyle( + fontFamily: 'monospace', + fontWeight: FontWeight.bold, + ).merge(tokenTile), + tokenTileSubtitle = const TextStyle( + fontFamily: 'monospace', + fontWeight: FontWeight.bold, + ).merge(tokenTileSubtitle); + + @override + ThemeExtension copyWith({ + TextStyle? otpTextStyle, + TextStyle? otpSubtitleTextStyle, + }) => + ExtendedTextTheme( + tokenTile: otpTextStyle ?? tokenTile, + tokenTileSubtitle: otpSubtitleTextStyle ?? tokenTileSubtitle, + ); + + @override + ThemeExtension lerp(ExtendedTextTheme? other, double t) => ExtendedTextTheme( + tokenTile: TextStyle.lerp(tokenTile, other?.tokenTile, t) ?? tokenTile, + ); +} diff --git a/lib/utils/customization/theme_customization.dart b/lib/utils/customization/theme_customization.dart new file mode 100644 index 000000000..93818c883 --- /dev/null +++ b/lib/utils/customization/theme_customization.dart @@ -0,0 +1,498 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:privacyidea_authenticator/utils/customization/action_theme.dart'; +import 'package:privacyidea_authenticator/utils/customization/extended_text_theme.dart'; + +class ThemeCustomization { + static const ThemeCustomization defaultLightTheme = ThemeCustomization.defaultLightWith(); + static const ThemeCustomization defaultDarkTheme = ThemeCustomization.defaultDarkWith(); + + const ThemeCustomization({ + required this.brightness, + required this.primaryColor, + required this.onPrimary, + required this.subtitleColor, + required this.backgroundColor, + required this.foregroundColor, + required this.shadowColor, + required this.deleteColor, + required this.renameColor, + required this.lockColor, + required this.tileIconColor, + required this.navigationBarColor, + Color? actionButtonsForegroundColor, + Color? tilePrimaryColor, + Color? tileSubtitleColor, + Color? navigationBarIconColor, + Color? qrButtonBackgroundColor, + Color? qrButtonIconColor, + }) : _actionButtonsForegroundColor = actionButtonsForegroundColor, + _tilePrimaryColor = tilePrimaryColor, + _tileSubtitleColor = tileSubtitleColor, + _navigationBarIconColor = navigationBarIconColor, + _qrButtonBackgroundColor = qrButtonBackgroundColor, + _qrButtonIconColor = qrButtonIconColor; + + const ThemeCustomization.defaultLightWith({ + Color? primaryColor, + Color? onPrimary, + Color? subtitleColor, + Color? backgroundColor, + Color? foregroundColor, + Color? shadowColor, + Color? deleteColor, + Color? renameColor, + Color? lockColor, + Color? tileIconColor, + Color? navigationBarColor, + // From here on the colors have a default value based on another given color + Color? actionButtonsForegroundColor, // Default: foregroundColor + Color? tilePrimaryColor, // Default: primaryColor + Color? tileSubtitleColor, // Default: subtitleColor + Color? navigationBarIconColor, // Default: foregroundColor + Color? qrButtonBackgroundColor, // Default: primaryColor + Color? qrButtonIconColor, // Default: onPrimary + }) : brightness = Brightness.light, + primaryColor = primaryColor ?? const Color(0xff03A9F4), + onPrimary = onPrimary ?? const Color(0xff282828), + subtitleColor = subtitleColor ?? const Color(0xff9E9E9E), + backgroundColor = backgroundColor ?? const Color(0xffEFEFEF), + foregroundColor = foregroundColor ?? const Color(0xff282828), + shadowColor = shadowColor ?? const Color(0xff303030), + deleteColor = deleteColor ?? const Color(0xffE04D2D), + renameColor = renameColor ?? const Color(0xff6A8FE5), + lockColor = lockColor ?? const Color(0xffFFD633), + tileIconColor = tileIconColor ?? const Color(0xff757575), + navigationBarColor = navigationBarColor ?? const Color(0xFFFFFFFF), + // From here on the colors have a default value based on another given color + _actionButtonsForegroundColor = actionButtonsForegroundColor, + _tilePrimaryColor = tilePrimaryColor, + _tileSubtitleColor = tileSubtitleColor, + _navigationBarIconColor = navigationBarIconColor, + _qrButtonBackgroundColor = qrButtonBackgroundColor, + _qrButtonIconColor = qrButtonIconColor; + + const ThemeCustomization.defaultDarkWith({ + Color? primaryColor, + Color? onPrimary, + Color? subtitleColor, + Color? backgroundColor, + Color? foregroundColor, + Color? shadowColor, + Color? deleteColor, + Color? renameColor, + Color? lockColor, + Color? tileIconColor, + Color? navigationBarColor, + // From here on the colors have a default value based on another given color + Color? actionButtonsForegroundColor, // Default: foregroundColor + Color? tilePrimaryColor, // Default: primaryColor + Color? tileSubtitleColor, // Default: subtitleColor + Color? navigationBarIconColor, // Default: foregroundColor + Color? qrButtonBackgroundColor, // Default: primaryColor + Color? qrButtonIconColor, // Default: onPrimary + }) : brightness = Brightness.dark, + primaryColor = primaryColor ?? const Color(0xff03A9F4), + onPrimary = onPrimary ?? const Color(0xFF282828), + subtitleColor = subtitleColor ?? const Color(0xFF9E9E9E), + backgroundColor = backgroundColor ?? const Color(0xFF303030), + foregroundColor = foregroundColor ?? const Color(0xffF5F5F5), + shadowColor = shadowColor ?? const Color(0xFFEFEFEF), + deleteColor = deleteColor ?? const Color(0xffCD3C14), + renameColor = renameColor ?? const Color(0xff527EDB), + lockColor = lockColor ?? const Color(0xffFFCC00), + tileIconColor = tileIconColor ?? const Color(0xffF5F5F5), + navigationBarColor = navigationBarColor ?? const Color(0xFF282828), + // From here on the colors have a default value based on another given color + _actionButtonsForegroundColor = actionButtonsForegroundColor, + _tilePrimaryColor = tilePrimaryColor, + _tileSubtitleColor = tileSubtitleColor, + _navigationBarIconColor = navigationBarIconColor, + _qrButtonBackgroundColor = qrButtonBackgroundColor, + _qrButtonIconColor = qrButtonIconColor; + + final Brightness brightness; + + // Basic colors + final Color primaryColor; + final Color onPrimary; + final Color subtitleColor; + final Color backgroundColor; + final Color foregroundColor; + final Color shadowColor; + + // Slide action + final Color deleteColor; + final Color renameColor; + final Color lockColor; + final Color? _actionButtonsForegroundColor; // Default: foregroundColor + Color get actionButtonsForegroundColor => _actionButtonsForegroundColor ?? foregroundColor; + + // List tile + final Color? _tilePrimaryColor; // Default: primaryColor + Color get tilePrimaryColor => _tilePrimaryColor ?? primaryColor; + final Color tileIconColor; + final Color? _tileSubtitleColor; // Default: subtitleColor + Color get tileSubtitleColor => _tileSubtitleColor ?? subtitleColor; + + // Navigation bar + final Color navigationBarColor; + final Color? _navigationBarIconColor; // Default: foregroundColor + Color get navigationBarIconColor => _navigationBarIconColor ?? foregroundColor; + final Color? _qrButtonBackgroundColor; // Default: primaryColor + Color get qrButtonBackgroundColor => _qrButtonBackgroundColor ?? primaryColor; + final Color? _qrButtonIconColor; // Default: onPrimary + Color get qrButtonIconColor => _qrButtonIconColor ?? onPrimary; + + ThemeCustomization copyWith({ + Brightness? brightness, + Color? primaryColor, + Color? onPrimary, + Color? subtitleColor, + Color? backgroundColor, + Color? foregroundColor, + Color? shadowColor, + Color? deleteColor, + Color? renameColor, + Color? lockColor, + Color? tileIconColor, + Color? navigationBarColor, + // From here on the colors have a default value based on another given color + Color? Function()? actionButtonsForegroundColor, // Default: foregroundColor + Color? Function()? tilePrimaryColor, // Default: primaryColor + Color? Function()? tileSubtitleColor, // Default: subtitleColor + Color? Function()? navigationBarIconColor, // Default: foregroundColor + Color? Function()? qrButtonBackgroundColor, // Default: primaryColor + Color? Function()? qrButtonIconColor, // Default: onPrimary + }) => + ThemeCustomization( + brightness: brightness ?? this.brightness, + primaryColor: primaryColor ?? this.primaryColor, + onPrimary: onPrimary ?? this.onPrimary, + subtitleColor: subtitleColor ?? this.subtitleColor, + backgroundColor: backgroundColor ?? this.backgroundColor, + foregroundColor: foregroundColor ?? this.foregroundColor, + shadowColor: shadowColor ?? this.shadowColor, + deleteColor: deleteColor ?? this.deleteColor, + renameColor: renameColor ?? this.renameColor, + lockColor: lockColor ?? this.lockColor, + actionButtonsForegroundColor: actionButtonsForegroundColor != null ? actionButtonsForegroundColor() : this.actionButtonsForegroundColor, + tilePrimaryColor: tilePrimaryColor != null ? tilePrimaryColor() : this.tilePrimaryColor, + tileIconColor: tileIconColor ?? this.tileIconColor, + tileSubtitleColor: tileSubtitleColor != null ? tileSubtitleColor() : this.tileSubtitleColor, + navigationBarColor: navigationBarColor ?? this.navigationBarColor, + navigationBarIconColor: navigationBarIconColor != null ? navigationBarIconColor() : this.navigationBarIconColor, + qrButtonBackgroundColor: qrButtonBackgroundColor != null ? qrButtonBackgroundColor() : this.qrButtonBackgroundColor, + qrButtonIconColor: qrButtonIconColor != null ? qrButtonIconColor() : this.qrButtonIconColor, + ); + + factory ThemeCustomization.fromJson(Map json) { + bool isLightTheme = json['brightness'] == 'light'; + bool isDarkTheme = json['brightness'] == 'dark'; + if (json['brightness'] == null && json['primaryColor'] != null) { + isLightTheme = _isColorBright(Color(json['primaryColor'] as int)); + } + if (isLightTheme) { + return ThemeCustomization.defaultLightWith( + primaryColor: json['primaryColor'] != null ? Color(json['primaryColor'] as int) : null, + onPrimary: json['onPrimary'] != null ? Color(json['onPrimary'] as int) : null, + subtitleColor: json['subtitleColor'] != null ? Color(json['subtitleColor'] as int) : null, + backgroundColor: json['backgroundColor'] != null ? Color(json['backgroundColor'] as int) : null, + foregroundColor: json['foregroundColor'] != null ? Color(json['foregroundColor'] as int) : null, + shadowColor: json['shadowColor'] != null ? Color(json['shadowColor'] as int) : null, + deleteColor: json['deleteColor'] != null ? Color(json['deleteColor'] as int) : null, + renameColor: json['renameColor'] != null ? Color(json['renameColor'] as int) : null, + lockColor: json['lockColor'] != null ? Color(json['lockColor'] as int) : null, + tileIconColor: json['tileIconColor'] != null ? Color(json['tileIconColor'] as int) : null, + navigationBarColor: json['navigationBarColor'] != null ? Color(json['navigationBarColor'] as int) : null, + actionButtonsForegroundColor: json['_actionButtonsForegroundColor'] != null ? Color(json['_actionButtonsForegroundColor'] as int) : null, + tilePrimaryColor: json['_tilePrimaryColor'] != null ? Color(json['_tilePrimaryColor'] as int) : null, + tileSubtitleColor: json['_tileSubtitleColor'] != null ? Color(json['_tileSubtitleColor'] as int) : null, + navigationBarIconColor: json['_navigationBarIconColor'] != null ? Color(json['_navigationBarIconColor'] as int) : null, + qrButtonBackgroundColor: json['_qrButtonBackgroundColor'] != null ? Color(json['_qrButtonBackgroundColor'] as int) : null, + qrButtonIconColor: json['_qrButtonIconColor'] != null ? Color(json['_qrButtonIconColor'] as int) : null, + ); + } + if (isDarkTheme) { + return ThemeCustomization.defaultDarkWith( + primaryColor: json['primaryColor'] != null ? Color(json['primaryColor'] as int) : null, + onPrimary: json['onPrimary'] != null ? Color(json['onPrimary'] as int) : null, + subtitleColor: json['subtitleColor'] != null ? Color(json['subtitleColor'] as int) : null, + backgroundColor: json['backgroundColor'] != null ? Color(json['backgroundColor'] as int) : null, + foregroundColor: json['foregroundColor'] != null ? Color(json['foregroundColor'] as int) : null, + shadowColor: json['shadowColor'] != null ? Color(json['shadowColor'] as int) : null, + deleteColor: json['deleteColor'] != null ? Color(json['deleteColor'] as int) : null, + renameColor: json['renameColor'] != null ? Color(json['renameColor'] as int) : null, + lockColor: json['lockColor'] != null ? Color(json['lockColor'] as int) : null, + tileIconColor: json['tileIconColor'] != null ? Color(json['tileIconColor'] as int) : null, + navigationBarColor: json['navigationBarColor'] != null ? Color(json['navigationBarColor'] as int) : null, + actionButtonsForegroundColor: json['_actionButtonsForegroundColor'] != null ? Color(json['_actionButtonsForegroundColor'] as int) : null, + tilePrimaryColor: json['_tilePrimaryColor'] != null ? Color(json['_tilePrimaryColor'] as int) : null, + tileSubtitleColor: json['_tileSubtitleColor'] != null ? Color(json['_tileSubtitleColor'] as int) : null, + navigationBarIconColor: json['_navigationBarIconColor'] != null ? Color(json['_navigationBarIconColor'] as int) : null, + qrButtonBackgroundColor: json['_qrButtonBackgroundColor'] != null ? Color(json['_qrButtonBackgroundColor'] as int) : null, + qrButtonIconColor: json['_qrButtonIconColor'] != null ? Color(json['_qrButtonIconColor'] as int) : null, + ); + } + throw Exception('Invalid brightness value: ${json['brightness']}'); + } + + Map toJson() => { + 'brightness': brightness == Brightness.light ? 'light' : 'dark', + 'primaryColor': primaryColor.value, + 'onPrimary': onPrimary.value, + 'subtitleColor': subtitleColor.value, + 'backgroundColor': backgroundColor.value, + 'foregroundColor': foregroundColor.value, + 'shadowColor': shadowColor.value, + 'deleteColor': deleteColor.value, + 'renameColor': renameColor.value, + 'lockColor': lockColor.value, + 'tileIconColor': tileIconColor.value, + 'navigationBarColor': navigationBarColor.value, + '_actionButtonsForegroundColor': _actionButtonsForegroundColor?.value, + '_tilePrimaryColor': _tilePrimaryColor?.value, + '_tileSubtitleColor': _tileSubtitleColor?.value, + '_navigationBarIconColor': _navigationBarIconColor?.value, + '_qrButtonBackgroundColor': _qrButtonBackgroundColor?.value, + '_qrButtonIconColor': _qrButtonIconColor?.value, + }; + + ThemeData generateTheme() => ThemeData( + useMaterial3: false, + brightness: brightness, + primaryColor: primaryColor, + canvasColor: backgroundColor, + textTheme: const TextTheme().copyWith( + bodyLarge: TextStyle(color: foregroundColor), + bodyMedium: TextStyle(color: foregroundColor), + titleMedium: TextStyle(color: foregroundColor), + titleSmall: TextStyle(color: foregroundColor), + displayLarge: TextStyle(color: foregroundColor), + displayMedium: TextStyle(color: foregroundColor), + displaySmall: TextStyle(color: foregroundColor), + headlineMedium: TextStyle(color: foregroundColor), + headlineSmall: TextStyle(color: foregroundColor), + titleLarge: TextStyle(color: primaryColor), + bodySmall: TextStyle(color: subtitleColor), + labelLarge: TextStyle(color: foregroundColor), + labelSmall: TextStyle(color: foregroundColor), + ), + iconButtonTheme: IconButtonThemeData( + style: ButtonStyle( + foregroundColor: MaterialStateProperty.all(foregroundColor), + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + foregroundColor: onPrimary, + backgroundColor: primaryColor, + padding: const EdgeInsets.all(6), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + shadowColor: shadowColor, + elevation: 1.5, + ), + ), + scaffoldBackgroundColor: backgroundColor, + cardColor: backgroundColor, + shadowColor: shadowColor, + // shadowColor: Colors.transparent, + appBarTheme: const AppBarTheme().copyWith( + backgroundColor: backgroundColor, + shadowColor: shadowColor, + foregroundColor: foregroundColor, + elevation: 0, + titleSpacing: 6, + ), + inputDecorationTheme: InputDecorationTheme( + labelStyle: TextStyle(color: foregroundColor), + hintStyle: TextStyle(color: primaryColor), + errorStyle: TextStyle(color: deleteColor), + border: UnderlineInputBorder( + borderSide: BorderSide(color: shadowColor), + ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: subtitleColor), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: primaryColor), + ), + ), + primaryIconTheme: IconThemeData(color: onPrimary), + iconTheme: IconThemeData(color: foregroundColor), + navigationBarTheme: const NavigationBarThemeData().copyWith( + backgroundColor: navigationBarColor, + shadowColor: shadowColor, + iconTheme: MaterialStatePropertyAll(IconThemeData(color: navigationBarIconColor)), + elevation: 3, + ), + floatingActionButtonTheme: FloatingActionButtonThemeData( + backgroundColor: qrButtonBackgroundColor, + foregroundColor: qrButtonIconColor, + elevation: 0, + ), + textButtonTheme: TextButtonThemeData( + style: ButtonStyle( + overlayColor: MaterialStateColor.resolveWith((states) => foregroundColor.withOpacity(0.1)), + ), + ), + listTileTheme: ListTileThemeData( + tileColor: Colors.transparent, + titleTextStyle: TextStyle(color: tilePrimaryColor), + subtitleTextStyle: TextStyle(color: tileSubtitleColor), + iconColor: tileIconColor, + ), + colorScheme: brightness == Brightness.light + ? ColorScheme.light( + primary: primaryColor, + secondary: primaryColor, + onPrimary: onPrimary, + onSecondary: onPrimary, + error: deleteColor, + errorContainer: deleteColor, + ) + : ColorScheme.dark( + primary: primaryColor, + secondary: primaryColor, + onPrimary: onPrimary, + onSecondary: onPrimary, + error: deleteColor, + errorContainer: deleteColor, + ), + checkboxTheme: CheckboxThemeData( + checkColor: MaterialStateProperty.resolveWith((_) => onPrimary), + fillColor: MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return primaryColor; + } + return null; + }), + ), + radioTheme: RadioThemeData( + fillColor: MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return primaryColor; + } + return null; + }), + ), + switchTheme: SwitchThemeData( + thumbColor: MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return primaryColor; + } + return null; + }), + trackColor: MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return primaryColor; + } + return null; + }), + ), + extensions: [ + ActionTheme( + deleteColor: deleteColor, + editColor: renameColor, + lockColor: lockColor, + foregroundColor: actionButtonsForegroundColor, + ), + ExtendedTextTheme( + tokenTile: TextStyle( + color: primaryColor, + ), + tokenTileSubtitle: TextStyle( + color: tileSubtitleColor, + ), + ), + ]); + + @override + String toString() => 'ThemeCustomization(' + 'brightness: $brightness, ' + 'primaryColor: $primaryColor, ' + 'onPrimary: $onPrimary, ' + 'subtitleColor: $subtitleColor, ' + 'backgroundColor: $backgroundColor, ' + 'foregroundColor: $foregroundColor, ' + 'shadowColor: $shadowColor, ' + 'deleteColor: $deleteColor, ' + 'renameColor: $renameColor, ' + 'lockColor: $lockColor, ' + 'actionButtonsForegroundColor: $actionButtonsForegroundColor, ' + 'tilePrimaryColor: $tilePrimaryColor, ' + 'tileIconColor: $tileIconColor, ' + 'tileSubtitleColor: $tileSubtitleColor, ' + 'navigationBarColor: $navigationBarColor, ' + 'navigationBarIconColor: $navigationBarIconColor, ' + 'qrButtonBackgroundColor: $qrButtonBackgroundColor, ' + 'qrButtonIconColor: $qrButtonIconColor' + ')'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is ThemeCustomization && + other.brightness == brightness && + other.primaryColor == primaryColor && + other.onPrimary == onPrimary && + other.subtitleColor == subtitleColor && + other.backgroundColor == backgroundColor && + other.foregroundColor == foregroundColor && + other.shadowColor == shadowColor && + other.deleteColor == deleteColor && + other.renameColor == renameColor && + other.lockColor == lockColor && + other.actionButtonsForegroundColor == actionButtonsForegroundColor && + other.tilePrimaryColor == tilePrimaryColor && + other.tileIconColor == tileIconColor && + other.tileSubtitleColor == tileSubtitleColor && + other.navigationBarColor == navigationBarColor && + other.navigationBarIconColor == navigationBarIconColor && + other.qrButtonBackgroundColor == qrButtonBackgroundColor && + other.qrButtonIconColor == qrButtonIconColor; + } + + @override + int get hashCode => Object.hashAll([ + brightness, + primaryColor, + onPrimary, + subtitleColor, + backgroundColor, + foregroundColor, + shadowColor, + deleteColor, + renameColor, + lockColor, + actionButtonsForegroundColor, + tilePrimaryColor, + tileIconColor, + tileSubtitleColor, + navigationBarColor, + navigationBarIconColor, + qrButtonBackgroundColor, + qrButtonIconColor, + ]); +} + +// /// Calculate HSP and check if the primary color is bright or dark +// /// brightness = sqrt( .299 R^2 + .587 G^2 + .114 B^2 ) +// /// c.f., http://alienryderflex.com/hsp.html +bool _isColorBright(Color color) { + return sqrt(0.299 * pow(color.red, 2) + 0.587 * pow(color.green, 2) + 0.114 * pow(color.blue, 2)) > 150; +} diff --git a/lib/utils/home_widget_utils.dart b/lib/utils/home_widget_utils.dart index 3aa064e08..0319e0653 100644 --- a/lib/utils/home_widget_utils.dart +++ b/lib/utils/home_widget_utils.dart @@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:home_widget/home_widget.dart'; import 'package:mutex/mutex.dart'; +import 'package:privacyidea_authenticator/utils/customization/theme_customization.dart'; import '../interfaces/repo/token_folder_repository.dart'; import '../interfaces/repo/token_repository.dart'; @@ -27,7 +28,6 @@ import '../widgets/home_widgets/home_widget_copied.dart'; import '../widgets/home_widgets/home_widget_hidden.dart'; import '../widgets/home_widgets/home_widget_otp.dart'; import '../widgets/home_widgets/home_widget_unlinked.dart'; -import 'app_customizer.dart'; import 'logger.dart'; import 'riverpod_providers.dart'; diff --git a/lib/utils/license_utils.dart b/lib/utils/license_utils.dart deleted file mode 100644 index 684f134ca..000000000 --- a/lib/utils/license_utils.dart +++ /dev/null @@ -1,629 +0,0 @@ -// ignore_for_file: constant_identifier_names - -/* - privacyIDEA Authenticator - - Authors: Timo Sturm - Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH - - Licensed under the Apache License, Version 2.0 (the 'License'); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an 'AS IS' BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -import 'package:flutter/foundation.dart'; - -/// This method removes all licenses from the LicenseRegistry. -/// It can be used for testing purposes, if one wishes to inspect a specifically -/// added license. -clearLicenses() { - // ignore: invalid_use_of_visible_for_testing_member - LicenseRegistry.reset(); -} - -addAllLicenses() { - _addNewLicense('privacyIDEA Authenticator', _PI_AUTHENTICATOR_LICENSE); - _addNewLicense('dart-hex', _DART_HEX_LICENSE); - _addNewLicense('dart-base32', _DART_BASE32_LICENSE); - _addNewLicense('otp', _DART_OTP_LICENSE); - _addNewLicense('dart-uuid', _DART_UUID_LICENSE); - _addNewLicense('json_serializabel', _JSON_SERIALIZABLE_LICENSE); - _addNewLicense('flutter_secure_storage', _FLUTTER_SECURE_STORAGE_LICENSE); - _addNewLicense('flutter_slidable', _FLUTTER_SLIDABLE_LICENSE); - _addNewLicense('intl', _INTL_LICENSE); - _addNewLicense('package_info', _PACKAGE_INFO_LICENSE); - _addNewLicense('pointycastle', _POINTYCASTLE_LICENSE); - _addNewLicense('dynamic_theme', _DYNAMIC_THEME_LICENSE); - _addNewLicense('flutterfire', _FLUTTERFIRE_LICENSE); - _addNewLicense('firebase_core', _FIREBASE_CORE_LICENSE); - _addNewLicense('asn1lib', _ASN1LIB_LICENSE); - _addNewLicense('http', _HTTP_LICENSE); - _addNewLicense('flutter_local_notifications#', _FLUTTER_LOCAL_NOTIFICATIONS); - _addNewLicense('dart-mutex', _DART_MUTEX_LICENSE); -} - -_addNewLicense(String packageName, String licenseText) { - LicenseRegistry.addLicense(() async* { - yield LicenseEntryWithLineBreaks([packageName], licenseText); - }); -} - -const String _DART_MUTEX_LICENSE = ''' -Copyright (c) 2016, Hoylen Sue. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - '''; - -const String _FLUTTER_LOCAL_NOTIFICATIONS = ''' -Copyright 2018 Michael Bui. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of the copyright holder nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; - -const String _HTTP_LICENSE = ''' -Copyright 2014, the Dart project authors. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; - -const String _ASN1LIB_LICENSE = ''' -http://opensource.org/licenses/BSD-3-Clause -Copyright (c) 2015, Warren Strange -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - - Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - - Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; - -const String _FIREBASE_CORE_LICENSE = ''' -// Copyright 2017 The Chromium Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; -const String _FLUTTERFIRE_LICENSE = ''' -Copyright 2017 The Chromium Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; -const String _DYNAMIC_THEME_LICENSE = ''' -MIT License - -Copyright (c) 2019 Norbert Kozsir - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the 'Software'), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'''; -const String _PI_AUTHENTICATOR_LICENSE = ''' - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - 'License' shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - 'Licensor' shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - 'Legal Entity' shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - 'control' means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - 'You' (or 'Your') shall mean an individual or Legal Entity - exercising permissions granted by this License. - - 'Source' form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - 'Object' form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - 'Work' shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - 'Derivative Works' shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - 'Contribution' shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, 'submitted' - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as 'Not a Contribution.' - - 'Contributor' shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a 'NOTICE' text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an 'AS IS' BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS'''; -const String _DART_HEX_LICENSE = ''' - -The MIT License (MIT) - -Copyright (c) 2016 Dartcoin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the 'Software'), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'''; -const String _DART_BASE32_LICENSE = ''' Copyright (c) 2012 Yulian Kuncheff - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.'''; -const String _DART_OTP_LICENSE = ''' Copyright (c) 2012 Yulian Kuncheff - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.'''; -const String _DART_UUID_LICENSE = ''' Copyright (c) 2012 Yulian Kuncheff - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.'''; -const String _JSON_SERIALIZABLE_LICENSE = ''' -Copyright 2017, the Dart project authors. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'''; -const String _FLUTTER_SECURE_STORAGE_LICENSE = ''' -// Copyright 2017 Your Company. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Your Company nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'''; -const String _FLUTTER_SLIDABLE_LICENSE = ''' -MIT License - -Copyright (c) 2018 Romain Rastel - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the 'Software'), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE.'''; -const String _INTL_LICENSE = ''' -Copyright 2013, the Dart project authors. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; -const String _PACKAGE_INFO_LICENSE = ''' Copyright 2017 The Chromium Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'''; -const String _POINTYCASTLE_LICENSE = ''' -Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the 'Software'), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -'''; diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index ea728348b..c5e75431e 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -322,7 +322,7 @@ Device Parameters $deviceInfo"""; if (_context == null) return; showDialog( context: _context!, - builder: (context) => SendErrorDialog(), + builder: (context) => const SendErrorDialog(), useRootNavigator: false, ); } diff --git a/lib/utils/patch_notes_utils.dart b/lib/utils/patch_notes_utils.dart index fbf33db0c..5dbe0ca0f 100644 --- a/lib/utils/patch_notes_utils.dart +++ b/lib/utils/patch_notes_utils.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import '../l10n/app_localizations.dart'; import '../model/enums/patch_note_type.dart'; +import '../model/version.dart'; import '../widgets/dialog_widgets/patch_notes_dialog.dart'; import 'app_info_utils.dart'; import 'globals.dart'; import 'logger.dart'; -import '../model/version.dart'; class PatchNotesUtils { static Map>> _getNewPatchNotes({required BuildContext context, required Version latestStartedVersion}) { diff --git a/lib/utils/push_provider.dart b/lib/utils/push_provider.dart index c863cbd3c..2e62c5473 100644 --- a/lib/utils/push_provider.dart +++ b/lib/utils/push_provider.dart @@ -20,7 +20,6 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:developer'; import 'package:collection/collection.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; @@ -205,8 +204,6 @@ class PushProvider { Logger.warning('No token found for serial ${pushRequest.serial}.', name: 'push_provider.dart#_handleIncomingRequestForeground'); return; } - log('token:' + jsonEncode(pushToken.toJson())); - log('pushRequest:' + jsonEncode(pushRequest.toJson())); if (!await pushRequest.verifySignature(pushToken, rsaUtils: _rsaUtils, legacyUtils: _legacyUtils)) { Logger.warning('Signature verification failed.', name: 'push_provider.dart#_handleIncomingRequestForeground'); return; @@ -277,7 +274,7 @@ class PushProvider { } final connectivityResult = await (Connectivity().checkConnectivity()); - if (connectivityResult == ConnectivityResult.none) { + if (connectivityResult.contains(ConnectivityResult.none)) { if (isManually) { Logger.info('Tried to poll without any internet connection available.', name: 'push_provider.dart#pollForChallenges'); globalRef?.read(statusMessageProvider.notifier).state = ( diff --git a/lib/utils/riverpod_providers.dart b/lib/utils/riverpod_providers.dart index 5fb4936e1..f8ff04328 100644 --- a/lib/utils/riverpod_providers.dart +++ b/lib/utils/riverpod_providers.dart @@ -3,11 +3,10 @@ import 'dart:developer'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/model/extensions/sortable_list.dart'; -import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:uni_links/uni_links.dart'; import '../l10n/app_localizations.dart'; +import '../model/extensions/sortable_list.dart'; import '../model/mixins/sortable_mixin.dart'; import '../model/states/introduction_state.dart'; import '../model/states/push_request_state.dart'; @@ -17,6 +16,7 @@ import '../model/states/token_folder_state.dart'; import '../model/states/token_state.dart'; import '../model/token_folder.dart'; import '../model/tokens/otp_token.dart'; +import '../model/tokens/token.dart'; import '../repo/preference_introduction_repository.dart'; import '../repo/preference_settings_repository.dart'; import '../repo/preference_token_folder_repository.dart'; @@ -26,7 +26,7 @@ import '../state_notifiers/push_request_notifier.dart'; import '../state_notifiers/settings_notifier.dart'; import '../state_notifiers/token_folder_notifier.dart'; import '../state_notifiers/token_notifier.dart'; -import 'app_customizer.dart'; +import 'customization/application_customization.dart'; import 'firebase_utils.dart'; import 'globals.dart'; import 'home_widget_utils.dart'; @@ -115,14 +115,6 @@ final deeplinkProvider = StateNotifierProvider( name: 'deeplinkProvider', ); -// final appStateProvider = StateProvider( -// (ref) { -// Logger.info("New AppStateNotifier created", name: 'appStateProvider'); -// return null; -// }, -// name: 'appStateProvider', -// ); - final tokenFolderProvider = StateNotifierProvider( (ref) { Logger.info("New TokenFolderNotifier created", name: 'tokenFolderProvider'); diff --git a/lib/utils/riverpod_state_listener.dart b/lib/utils/riverpod_state_listener.dart index bdd90301d..848b91f67 100644 --- a/lib/utils/riverpod_state_listener.dart +++ b/lib/utils/riverpod_state_listener.dart @@ -73,4 +73,13 @@ class DeepLink { final Uri uri; final bool fromInit; const DeepLink(this.uri, {this.fromInit = false}); + + @override + bool operator ==(Object other) => other is DeepLink && other.uri == uri && other.fromInit == fromInit; + + @override + int get hashCode => Object.hash(uri, fromInit); + + @override + String toString() => 'DeepLink(uri: $uri, fromInit: $fromInit)'; } diff --git a/lib/utils/token_import_origins.dart b/lib/utils/token_import_origins.dart index 0454f3d76..a2d138975 100644 --- a/lib/utils/token_import_origins.dart +++ b/lib/utils/token_import_origins.dart @@ -8,7 +8,7 @@ import '../processors/scheme_processors/token_import_scheme_processors/otp_auth_ import '../processors/scheme_processors/token_import_scheme_processors/privacyidea_authenticator_qr_processor.dart'; import '../processors/token_import_file_processor/aegis_import_file_processor.dart'; import '../processors/token_import_file_processor/authenticator_pro_import_file_processor.dart'; -import '../processors/token_import_file_processor/free_otp_plus_file_processor.dart'; +import '../processors/token_import_file_processor/free_otp_plus_import_file_processor.dart'; import '../processors/token_import_file_processor/privacyidea_authenticator_import_file_processor.dart'; import '../processors/token_import_file_processor/two_fas_import_file_processor.dart'; @@ -82,7 +82,7 @@ class TokenImportOrigins { iconPath: '${_importSourceIconFolder}2fas.png', importSources: [ TokenImportSource( - processor: const TwoFasFileImportProcessor(), + processor: const TwoFasAuthenticatorImportFileProcessor(), type: TokenImportType.backupFile, importHint: (localizations) => localizations.importHint2FAS, ), @@ -109,7 +109,7 @@ class TokenImportOrigins { importHint: (localizations) => localizations.importHintFreeOtpPlusQrScan, ), TokenImportSource( - processor: const FreeOtpPlusFileProcessor(), + processor: const FreeOtpPlusImportFileProcessor(), type: TokenImportType.backupFile, importHint: (localizations) => localizations.importHintFreeOtpPlusFile, ), diff --git a/lib/views/add_token_manually_view/add_token_manually_view.dart b/lib/views/add_token_manually_view/add_token_manually_view.dart index 538dce2af..b5861de54 100644 --- a/lib/views/add_token_manually_view/add_token_manually_view.dart +++ b/lib/views/add_token_manually_view/add_token_manually_view.dart @@ -2,8 +2,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; import '../../l10n/app_localizations.dart'; import '../../mains/main_netknights.dart'; @@ -11,6 +9,8 @@ import '../../model/enums/algorithms.dart'; import '../../model/enums/encodings.dart'; import '../../model/enums/token_origin_source_type.dart'; import '../../model/enums/token_types.dart'; +import '../../model/extensions/enums/encodings_extension.dart'; +import '../../model/extensions/enums/token_origin_source_type.dart'; import '../../model/tokens/token.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/labeled_dropdown_button.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/labeled_dropdown_button.dart index 58cfd8625..10a387e23 100644 --- a/lib/views/add_token_manually_view/add_token_manually_view_widgets/labeled_dropdown_button.dart +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/labeled_dropdown_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; + import '../../../utils/logger.dart'; class LabeledDropdownButton extends StatefulWidget { diff --git a/lib/views/import_tokens_view/pages/import_plain_tokens_page.dart b/lib/views/import_tokens_view/pages/import_plain_tokens_page.dart index 33f3ed41d..5771f61a8 100644 --- a/lib/views/import_tokens_view/pages/import_plain_tokens_page.dart +++ b/lib/views/import_tokens_view/pages/import_plain_tokens_page.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/token_import_type_extension.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/token_import_type.dart'; +import '../../../model/extensions/enums/token_import_type_extension.dart'; import '../../../model/processor_result.dart'; import '../../../model/tokens/token.dart'; import '../../../utils/riverpod_providers.dart'; diff --git a/lib/views/import_tokens_view/pages/import_start_page.dart b/lib/views/import_tokens_view/pages/import_start_page.dart index f5b5e6022..d53879944 100644 --- a/lib/views/import_tokens_view/pages/import_start_page.dart +++ b/lib/views/import_tokens_view/pages/import_start_page.dart @@ -157,17 +157,17 @@ class _ImportStartPageState extends State { Logger.warning("No file selected", name: "_pickAFile#ImportSelectFilePage"); return; } - if (await fileProcessor.fileIsValid(file: file) == false) { + if (await fileProcessor.fileIsValid(file) == false) { if (mounted == false) return; setState(() => _errorText = localizations.invalidBackupFile(widget.appName)); return; } setState(() => _errorText = null); - if (await fileProcessor.fileNeedsPassword(file: file)) { + if (await fileProcessor.fileNeedsPassword(file)) { _routeEncryptedData(data: file, processor: fileProcessor); return; } - var importResults = await fileProcessor.processFile(file: file); + var importResults = await fileProcessor.processFile(file); if (importResults.isEmpty) { if (mounted == false) return; setState(() => _errorText = localizations.invalidBackupFile(widget.appName)); diff --git a/lib/views/import_tokens_view/pages/select_import_type_page.dart b/lib/views/import_tokens_view/pages/select_import_type_page.dart index 0a2729385..979bf28d7 100644 --- a/lib/views/import_tokens_view/pages/select_import_type_page.dart +++ b/lib/views/import_tokens_view/pages/select_import_type_page.dart @@ -1,9 +1,9 @@ import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/token_import_type_extension.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/token_import_type.dart'; +import '../../../model/extensions/enums/token_import_type_extension.dart'; import '../../../model/token_import/token_import_origin.dart'; import '../../../model/token_import/token_import_source.dart'; import '../import_tokens_view.dart'; diff --git a/lib/views/link_home_widget_view/link_home_widget_view.dart b/lib/views/link_home_widget_view/link_home_widget_view.dart index 67c689577..4c3ef75b4 100644 --- a/lib/views/link_home_widget_view/link_home_widget_view.dart +++ b/lib/views/link_home_widget_view/link_home_widget_view.dart @@ -2,7 +2,7 @@ import 'package:app_minimizer/app_minimizer.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../utils/app_customizer.dart'; +import '../../utils/customization/extended_text_theme.dart'; import '../../utils/home_widget_utils.dart'; import '../../utils/riverpod_providers.dart'; import '../../utils/utils.dart'; diff --git a/lib/views/main_view/main_view_widgets/connectivity_listener.dart b/lib/views/main_view/main_view_widgets/connectivity_listener.dart index 13303f49d..98e87ac66 100644 --- a/lib/views/main_view/main_view_widgets/connectivity_listener.dart +++ b/lib/views/main_view/main_view_widgets/connectivity_listener.dart @@ -13,7 +13,7 @@ class ConnectivityListener extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final connectivity = ref.watch(connectivityProvider).asData?.value; - if (connectivity != null && connectivity == ConnectivityResult.none) { + if (connectivity != null && connectivity.contains(ConnectivityResult.none)) { ref.read(tokenProvider.notifier).initState.then((newState) { if (newState.hasPushTokens) { Logger.info("Connectivity changed: $connectivity"); diff --git a/lib/views/main_view/main_view_widgets/drag_target_divider.dart b/lib/views/main_view/main_view_widgets/drag_target_divider.dart index a1f039a00..f07fbb7e5 100644 --- a/lib/views/main_view/main_view_widgets/drag_target_divider.dart +++ b/lib/views/main_view/main_view_widgets/drag_target_divider.dart @@ -2,8 +2,8 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/model/extensions/sortable_list.dart'; +import '../../../model/extensions/sortable_list.dart'; import '../../../model/mixins/sortable_mixin.dart'; import '../../../model/token_folder.dart'; import '../../../model/tokens/token.dart'; diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_actions.dart/delete_token_folder_action.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_actions.dart/delete_token_folder_action.dart index 4da714246..12ab76272 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_actions.dart/delete_token_folder_action.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_actions.dart/delete_token_folder_action.dart @@ -3,7 +3,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/token_folder.dart'; -import '../../../../../utils/app_customizer.dart'; +import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/riverpod_providers.dart'; diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_actions.dart/lock_token_folder_action.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_actions.dart/lock_token_folder_action.dart index 835e34024..763d708df 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_actions.dart/lock_token_folder_action.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_actions.dart/lock_token_folder_action.dart @@ -3,7 +3,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/token_folder.dart'; -import '../../../../../utils/app_customizer.dart'; +import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/riverpod_providers.dart'; diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_actions.dart/rename_token_folder_action.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_actions.dart/rename_token_folder_action.dart index 2e927c822..1646c70d1 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_actions.dart/rename_token_folder_action.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_actions.dart/rename_token_folder_action.dart @@ -3,7 +3,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/token_folder.dart'; -import '../../../../../utils/app_customizer.dart'; +import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable.dart index 4d191bf97..4bfabfab5 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable.dart @@ -12,7 +12,7 @@ import '../../../../model/states/token_filter.dart'; import '../../../../model/token_folder.dart'; import '../../../../model/tokens/push_token.dart'; import '../../../../model/tokens/token.dart'; -import '../../../../utils/app_customizer.dart'; +import '../../../../utils/customization/action_theme.dart'; import '../../../../utils/lock_auth.dart'; import '../../../../utils/riverpod_providers.dart'; import '../../../../widgets/custom_trailing.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart b/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart index 17a1c5050..7327fb948 100644 --- a/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart +++ b/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -import 'package:privacyidea_authenticator/model/tokens/token.dart'; import '../../../model/mixins/sortable_mixin.dart'; import '../../../model/token_folder.dart'; import '../../../model/tokens/push_token.dart'; +import '../../../model/tokens/token.dart'; import '../../../utils/push_provider.dart'; import '../../../utils/riverpod_providers.dart'; import '../../../widgets/deactivateable_refresh_indicator.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/actions/edit_day_password_token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/actions/edit_day_password_token_action.dart index d9acac56e..1daf7cff2 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/actions/edit_day_password_token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/actions/edit_day_password_token_action.dart @@ -6,7 +6,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../../l10n/app_localizations.dart'; import '../../../../../../model/enums/introduction.dart'; import '../../../../../../model/tokens/day_password_token.dart'; -import '../../../../../../utils/app_customizer.dart'; +import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod_providers.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_delete_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_delete_action.dart index b317abf62..730d20408 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_delete_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_delete_action.dart @@ -3,7 +3,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; -import '../../../../../utils/app_customizer.dart'; +import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/riverpod_providers.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action.dart index 184529d9b..eceac546e 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action.dart @@ -5,7 +5,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/enums/introduction.dart'; import '../../../../../model/tokens/token.dart'; -import '../../../../../utils/app_customizer.dart'; +import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_lock_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_lock_action.dart index d4a604e5c..3fcae56e9 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_lock_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_lock_action.dart @@ -4,7 +4,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/enums/introduction.dart'; import '../../../../../model/tokens/token.dart'; -import '../../../../../utils/app_customizer.dart'; +import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; import '../../../../../utils/riverpod_providers.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart index fd530a7cd..fc1a65bf1 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart @@ -7,7 +7,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../../l10n/app_localizations.dart'; import '../../../../../../model/enums/introduction.dart'; import '../../../../../../model/tokens/hotp_token.dart'; -import '../../../../../../utils/app_customizer.dart'; +import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod_providers.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart index d619c2672..ee6f49281 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart @@ -5,7 +5,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../../l10n/app_localizations.dart'; import '../../../../../../model/enums/introduction.dart'; import '../../../../../../model/tokens/push_token.dart'; -import '../../../../../../utils/app_customizer.dart'; +import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod_providers.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/rollout_failed_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/rollout_failed_widget.dart index 852705fda..1d8109319 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/rollout_failed_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/rollout_failed_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/push_token_rollout_state_extension.dart'; import '../../../../../l10n/app_localizations.dart'; +import '../../../../../model/extensions/enums/push_token_rollout_state_extension.dart'; import '../../../../../model/tokens/push_token.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/riverpod_providers.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/rollout_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/rollout_widget.dart index d9fd029e6..bcc76fa5d 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/rollout_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/rollout_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/push_token_rollout_state_extension.dart'; import '../../../../../l10n/app_localizations.dart'; +import '../../../../../model/extensions/enums/push_token_rollout_state_extension.dart'; import '../../../../../model/tokens/push_token.dart'; class RolloutWidget extends StatelessWidget { diff --git a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/actions/edit_totp_token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/actions/edit_totp_token_action.dart index b4da8dab3..81b31d1b8 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/actions/edit_totp_token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/actions/edit_totp_token_action.dart @@ -4,7 +4,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../../l10n/app_localizations.dart'; import '../../../../../../model/enums/introduction.dart'; import '../../../../../../model/tokens/totp_token.dart'; -import '../../../../../../utils/app_customizer.dart'; +import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod_providers.dart'; diff --git a/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart b/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart index 51f97410c..587a79a62 100644 --- a/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart +++ b/lib/views/qr_scanner_view/qr_scanner_view_widgets/qr_scanner_widget.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:isolate'; import 'dart:math'; + import 'package:camera/camera.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_export_type_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_export_type_dialog.dart index 1c1ffec9e..556e37fb0 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_export_type_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_export_type_dialog.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/show_qr_code_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/show_qr_code_dialog.dart index ca9b40117..746063f9f 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/show_qr_code_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/show_qr_code_dialog.dart @@ -3,13 +3,12 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:zxing2/qrcode.dart'; import 'package:image/image.dart' as img; +import 'package:zxing2/qrcode.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/encryption/token_encryption.dart'; import '../../../../../model/tokens/token.dart'; -import '../../../../../utils/logger.dart'; import '../../../../../utils/riverpod_providers.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; @@ -63,8 +62,11 @@ class ShowQrCodeDialog extends ConsumerWidget { } static Uint8List _generateQrCodeImage({required String data}) { - Logger.info('$data'); - final qrcode = Encoder.encode(data, ErrorCorrectionLevel.m); + final qrcode = Encoder.encode( + data, + ErrorCorrectionLevel.q, + hints: EncodeHints()..put(EncodeHintType.characterSet, CharacterSetECI.ASCII), + ); final matrix = qrcode.matrix!; const scale = 4; const padding = 1; diff --git a/lib/views/settings_view/settings_groups/settings_group_push_token.dart b/lib/views/settings_view/settings_groups/settings_group_push_token.dart index c7031214e..4ca61c79f 100644 --- a/lib/views/settings_view/settings_groups/settings_group_push_token.dart +++ b/lib/views/settings_view/settings_groups/settings_group_push_token.dart @@ -121,14 +121,14 @@ class SettingsGroupPushToken extends ConsumerWidget { shrinkWrap: true, itemCount: unsupported.length, itemBuilder: (context, index) => Text('${unsupported[index].label}'), - separatorBuilder: (context, index) => Divider(), + separatorBuilder: (context, index) => const Divider(), ), ), actions: [ TextButton( child: Text( AppLocalizations.of(context)!.dismiss, - style: Theme.of(context).textTheme.headline6, + style: Theme.of(context).textTheme.titleLarge, ), onPressed: () => Navigator.of(context).pop(), ), diff --git a/lib/views/settings_view/settings_view_widgets/errorlog_buttons/send_errorlog_button.dart b/lib/views/settings_view/settings_view_widgets/errorlog_buttons/send_errorlog_button.dart index bb6e19c1f..cea48a1a8 100644 --- a/lib/views/settings_view/settings_view_widgets/errorlog_buttons/send_errorlog_button.dart +++ b/lib/views/settings_view/settings_view_widgets/errorlog_buttons/send_errorlog_button.dart @@ -20,7 +20,7 @@ void _pressSendErrorLog(BuildContext context) { showDialog( useRootNavigator: false, context: context, - builder: (context) => SendErrorDialog(), + builder: (context) => const SendErrorDialog(), ); } else { showDialog( diff --git a/lib/views/splash_screen/splash_screen.dart b/lib/views/splash_screen/splash_screen.dart index e52deba96..c0b2545c5 100644 --- a/lib/views/splash_screen/splash_screen.dart +++ b/lib/views/splash_screen/splash_screen.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../model/enums/app_feature.dart'; -import '../../utils/app_customizer.dart'; +import '../../utils/customization/application_customization.dart'; import '../../utils/app_info_utils.dart'; import '../../utils/home_widget_utils.dart'; import '../../utils/logger.dart'; diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index c27016be8..893670bb1 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -5,9 +5,9 @@ import 'package:easy_dynamic_theme/easy_dynamic_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; + import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; - import '../utils/riverpod_providers.dart'; import '../utils/riverpod_state_listener.dart'; import 'app_wrappers/single_touch_recognizer.dart'; diff --git a/lib/widgets/dialog_widgets/patch_notes_dialog.dart b/lib/widgets/dialog_widgets/patch_notes_dialog.dart index 4aa216ff9..288bbc726 100644 --- a/lib/widgets/dialog_widgets/patch_notes_dialog.dart +++ b/lib/widgets/dialog_widgets/patch_notes_dialog.dart @@ -1,13 +1,13 @@ import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/patch_note_type_extension.dart'; import '../../l10n/app_localizations.dart'; import '../../model/enums/patch_note_type.dart'; +import '../../model/extensions/enums/patch_note_type_extension.dart'; +import '../../model/version.dart'; import '../../utils/app_info_utils.dart'; import '../../utils/riverpod_providers.dart'; -import '../../model/version.dart'; import 'default_dialog.dart'; class PatchNotesDialog extends StatelessWidget { diff --git a/lib/widgets/home_widgets/home_widget_copied.dart b/lib/widgets/home_widgets/home_widget_copied.dart index faed851c8..6aa9ea7d9 100644 --- a/lib/widgets/home_widgets/home_widget_copied.dart +++ b/lib/widgets/home_widgets/home_widget_copied.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../../utils/app_customizer.dart'; +import '../../utils/customization/extended_text_theme.dart'; import 'interfaces/flutter_home_widget_base.dart'; import 'interfaces/flutter_home_widget_builder.dart'; diff --git a/lib/widgets/home_widgets/home_widget_hidden.dart b/lib/widgets/home_widgets/home_widget_hidden.dart index 40074003f..256d3bdc6 100644 --- a/lib/widgets/home_widgets/home_widget_hidden.dart +++ b/lib/widgets/home_widgets/home_widget_hidden.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../../utils/app_customizer.dart'; +import '../../utils/customization/extended_text_theme.dart'; import 'home_widget_otp.dart'; import 'interfaces/flutter_home_widget_base.dart'; import 'interfaces/flutter_home_widget_builder.dart'; diff --git a/lib/widgets/home_widgets/home_widget_otp.dart b/lib/widgets/home_widgets/home_widget_otp.dart index 653cdcab3..eee6d78bb 100644 --- a/lib/widgets/home_widgets/home_widget_otp.dart +++ b/lib/widgets/home_widgets/home_widget_otp.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../../utils/app_customizer.dart'; +import '../../utils/customization/extended_text_theme.dart'; import '../../utils/utils.dart'; import 'interfaces/flutter_home_widget_base.dart'; import 'interfaces/flutter_home_widget_builder.dart'; diff --git a/lib/widgets/home_widgets/home_widget_unlinked.dart b/lib/widgets/home_widgets/home_widget_unlinked.dart index b6687cb41..af3b17b0d 100644 --- a/lib/widgets/home_widgets/home_widget_unlinked.dart +++ b/lib/widgets/home_widgets/home_widget_unlinked.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../../utils/app_customizer.dart'; +import '../../utils/customization/extended_text_theme.dart'; import 'interfaces/flutter_home_widget_base.dart'; import 'interfaces/flutter_home_widget_builder.dart'; diff --git a/pubspec.lock b/pubspec.lock index cfd6bd7a7..59b3a6f5b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "0cb43f83f36ba8cb20502dee0c205e3f3aafb751732d724aeac3f2e044212cc2" + sha256: "3dee3db3468c5f4640a4e8aa9c1e22561c298976d8c39ed2fdd456a9a3db26e1" url: "https://pub.dev" source: hosted - version: "1.3.29" + version: "1.3.32" analyzer: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: archive - sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" + sha256: "0763b45fa9294197a2885c8567927e2830ade852e5c896fd4ab7e0e348d0f373" url: "https://pub.dev" source: hosted - version: "3.4.10" + version: "3.5.0" args: dependency: transitive description: @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: camera_avfoundation - sha256: "5d009ae48de1c8ab621b1c4496dadb6e2a83f3223b76c6e6a4a252414105f561" + sha256: "7d021e8cd30d9b71b8b92b4ad669e80af432d722d18d6aac338572754a786c15" url: "https://pub.dev" source: hosted - version: "0.9.15" + version: "0.9.16" camera_platform_interface: dependency: transitive description: @@ -237,10 +237,10 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: ebe15d94de9dd7c31dc2ac54e42780acdf3384b1497c69290c9f3c5b0279fc57 + sha256: db7a4e143dc72cc3cb2044ef9b052a7ebfe729513e6a82943bc3526f784365b8 url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.0.3" connectivity_plus_platform_interface: dependency: transitive description: @@ -445,10 +445,10 @@ packages: dependency: "direct main" description: name: firebase_core - sha256: a864d1b6afd25497a3b57b016886d1763df52baaa69758a46723164de8d187fe + sha256: "4aef2a23d0f3265545807d68fbc2f76a6b994ca3c778d88453b99325abd63284" url: "https://pub.dev" source: hosted - version: "2.29.0" + version: "2.30.1" firebase_core_platform_interface: dependency: transitive description: @@ -461,34 +461,34 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: c8b02226e548f35aace298e2bb2e6c24e34e8a203d614e742bb1146e5a4ad3c8 + sha256: "67f2fcc600fc78c2f731c370a3a5e6c87ee862e3a2fba6f951eca6d5dafe5c29" url: "https://pub.dev" source: hosted - version: "2.15.0" + version: "2.16.0" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: "87e3eda0ecdfeadb5fd1cf0dc5153aea5307a0cfca751c4b1ac97bfdd805660e" + sha256: "73a43445a7f8c6e6327f0ec3922b1c99a9f4a0e4896197bfe10a88259f775aad" url: "https://pub.dev" source: hosted - version: "14.8.1" + version: "14.9.1" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: "80b4ccf20066b0579ebc88d4678230a5f53ab282fe040e31671af745db1588f9" + sha256: "675527aadccb679c9dfd43a4558690427123ac1e99f03eef5bbce9dc216edc91" url: "https://pub.dev" source: hosted - version: "4.5.31" + version: "4.5.34" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: "9224aa4db1ce6f08d96a82978453d37e9980204a20e410a11d9b774b24c6841c" + sha256: "66deff69307f54fc7a20732b4278af327ae378b86607d5ac877d8f2201fe881e" url: "https://pub.dev" source: hosted - version: "3.8.1" + version: "3.8.4" fixnum: dependency: transitive description: @@ -501,10 +501,10 @@ packages: dependency: "direct main" description: name: fluentui_system_icons - sha256: "1c860f10a0e74c5788ff8a650ae6074d9a544463ae269714f1044b32df52b978" + sha256: "9bb6c46ff0351f0b20a79bbe5afcaf533af5f06fd8ac22407b57eaee8025004c" url: "https://pub.dev" source: hosted - version: "1.1.234" + version: "1.1.236" flutter: dependency: "direct main" description: flutter @@ -527,10 +527,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: a701df4866f9a38bb8e4450a54c143bbeeb0ce2381e7df5a36e1006f3b43bb28 + sha256: "8cdc719114ab1c86c64bb7a86d3a679674c3637edd229e3a994797d4a1504ce4" url: "https://pub.dev" source: hosted - version: "17.0.1" + version: "17.1.0" flutter_local_notifications_linux: dependency: transitive description: @@ -543,10 +543,10 @@ packages: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "7cf643d6d5022f3baed0be777b0662cce5919c0a7b86e700299f22dc4ae660ef" + sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7" url: "https://pub.dev" source: hosted - version: "7.0.0+1" + version: "7.1.0" flutter_localizations: dependency: "direct main" description: flutter @@ -760,18 +760,18 @@ packages: dependency: "direct main" description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" json_serializable: dependency: "direct dev" description: name: json_serializable - sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 + sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b url: "https://pub.dev" source: hosted - version: "6.7.1" + version: "6.8.0" leak_tracker: dependency: transitive description: @@ -960,18 +960,18 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: cb44f49b6e690fa766f023d5b22cac6b9affe741dd792b6ac7ad4fabe0d7b097 + sha256: "2c582551839386fa7ddbc7770658be7c0f87f388a4bff72066478f597c34d17f" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "7.0.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" path: dependency: transitive description: @@ -1111,10 +1111,10 @@ packages: dependency: "direct main" description: name: pointycastle - sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333" + sha256: "79fbafed02cfdbe85ef3fd06c7f4bc2cbcba0177e61b765264853d4253b21744" url: "https://pub.dev" source: hosted - version: "3.8.0" + version: "3.9.0" pool: dependency: transitive description: @@ -1412,10 +1412,10 @@ packages: dependency: transitive description: name: timezone - sha256: "1cfd8ddc2d1cfd836bc93e67b9be88c3adaeca6f40a00ca999104c30693cdca0" + sha256: a6ccda4a69a442098b602c44e61a1e2b4bf6f5516e875bbf0f427d5df14745d5 url: "https://pub.dev" source: hosted - version: "0.9.2" + version: "0.9.3" timing: dependency: transitive description: @@ -1588,10 +1588,10 @@ packages: dependency: transitive description: name: win32 - sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.5.0" win32_registry: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ffd1a86ce..2fccb2787 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,7 +56,7 @@ dependencies: shared_preferences: ^2.2.0 flutter_secure_storage: ^9.0.0 # Info - package_info_plus: ^6.0.0 + package_info_plus: ^7.0.0 device_info_plus: ^10.0.1 # URI uni_links: ^0.5.1 diff --git a/test/unit_test/model/encryption/aes_encrypted_test.dart b/test/unit_test/model/encryption/aes_encrypted_test.dart index 981e2daec..4db9484ff 100644 --- a/test/unit_test/model/encryption/aes_encrypted_test.dart +++ b/test/unit_test/model/encryption/aes_encrypted_test.dart @@ -83,8 +83,6 @@ void _testAesEncrypted() { cypher: AesGcm.with256bits(), ); final decrypted = await aesEncrypted.decryptToString("password"); - final jsonEncoded = jsonEncode(aesEncrypted.toJson()); - print(jsonEncoded); expect(decrypted, "test"); }); test('toJson', () { diff --git a/test/unit_test/model/encryption/token_encryption_test.dart b/test/unit_test/model/encryption/token_encryption_test.dart index 4e7c5d4ae..774ebfa5f 100644 --- a/test/unit_test/model/encryption/token_encryption_test.dart +++ b/test/unit_test/model/encryption/token_encryption_test.dart @@ -48,11 +48,11 @@ void _testTokenEncryption() { PushToken(serial: 'serial', id: 'id5'), ]; const compressedTokensBase64 = [ - 'H4sIAAAAAAAACk1PMQ7CMAz8i-cOsDBkY2slJJDgA2nshqhuUiWuBEL8HUelgsnnu7PPfgHbnhgMQAOhlIXyF6PWgHuFc4hgBsuFquWU3Ej4R7QBkX4OSSPFbrKewMSFuYEhMVLucOtLytJFpMdGpBx8zVg7ec46Cu35dtFwy15luU9KXdtjvQfVLQXMQVeRyyQqraCqLi1R6he79wc_WDvI3QAAAA==', - 'H4sIAAAAAAAACk1OzQrCMAx-l5x3kIkivXlbQVDYXqAuWS3L2tF2oIjvbuYcesr3l3x5ApsrMSiAAlxKE8UvRpkOS4Gj86A6w4nmyCm0PeGfUDlE-iVy6MnrwVgC5SfmArrASFHjylOIWXuk-yqE6OzcsbD8GGUVmnNzkXLDVux8G0Sqq2O524uIks8J1EGOURspi7mAz78UXZC27eb1Bgi4xPffAAAA', - 'H4sIAAAAAAAACk2NwQrCMBBE_2XPvfWWmwfBgJ70B2J3WkLXbElSsIj_7pZa9LRvHrPMiyTcIeSIGoqlzMhfZruRW8MpJnJ9kIK1ctZuBP-JU2TGr1F1RPKPMIBcmkUa6lUY2fOei-bqE-O5C81xWDe2VJfJXul6Ox4utl7QZVQTG7T0_gDM0h9FtAAAAA==', - 'H4sIAAAAAAAACk1PTQvCMAz9Lznv4GSK7DYY4mAy2UDxOE2cZV072s4PxP9uyhyaS15eXpKXF8j6RBJigACEtQOZL0bOAiOGvVAQX2ppyUtyfW4J_4iNQKSfwumWVNbVDUGsBikDuGiJZDKcaquNyxTSYyK0EY2_MVbu2fMopMlxl1TVoShT9lDLhlXu2nGn2iSLcM4k8pizEIczXkpnQ467I_C-b4LuW41-2T7Js3RdlP4bMkKzl9Uymn3j_QHdQYgrBgEAAA==', - 'H4sIAAAAAAAAClVQwW7CMAz9F5-57tIrO1ANMbSy3VPiIguTVI6DqKb9-5xCOnbK88t7fk_-BnY9MjQAK6CUMsoDe3vJvxgcKUAzOE5YJNt4PKN_IjbkPf4pNJ4xtBd3QmhCZl7BENmjtL7OKYq2weOtElHoVDLuk06jWWHf7j-7jcXjbSRxSjG8Ol2WJhRypfcDWEx_KNGLIPGXfQ3T0gyDROYLBl0LWmU1X6ryLFwhpQ_ToX_PuniLM2btdK5Qx10sjKjdw86Ue6Zjh3JFecOpbhuFrmaauz3Ts_o_-_ML-WVvco4BAAA=', + 'eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQxIiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IkhPVFAiLCJhbGdvcml0aG0iOiJTSEExIiwiZGlnaXRzIjo2LCJzZWNyZXQiOiJzZWNyZXQxIiwiY291bnRlciI6MH0=', + 'eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQyIiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IlRPVFAiLCJhbGdvcml0aG0iOiJTSEEyNTYiLCJkaWdpdHMiOjgsInNlY3JldCI6InNlY3JldDIiLCJwZXJpb2QiOjMwfQ==', + 'eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQzIiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IlNURUFNIiwic2VjcmV0Ijoic2VjcmV0MyJ9', + 'eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQ0IiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IkRBWVBBU1NXT1JEIiwiYWxnb3JpdGhtIjoiU0hBNTEyIiwiZGlnaXRzIjoxMCwic2VjcmV0Ijoic2VjcmV0NCIsInZpZXdNb2RlIjoiVkFMSURGT1IiLCJwZXJpb2QiOjg2NDAwMDAwMDAwfQ==', + 'eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQ1IiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IlBJUFVTSCIsImV4cGlyYXRpb25EYXRlIjpudWxsLCJzZXJpYWwiOiJzZXJpYWwiLCJmYlRva2VuIjpudWxsLCJzc2xWZXJpZnkiOmZhbHNlLCJlbnJvbGxtZW50Q3JlZGVudGlhbHMiOm51bGwsInVybCI6bnVsbCwiaXNSb2xsZWRPdXQiOmZhbHNlLCJyb2xsb3V0U3RhdGUiOiJyb2xsb3V0Tm90U3RhcnRlZCIsInB1YmxpY1NlcnZlcktleSI6bnVsbCwicHJpdmF0ZVRva2VuS2V5IjpudWxsLCJwdWJsaWNUb2tlbktleSI6bnVsbH0=', ]; for (var i = 0; tokensList.length > i; i++) { final token = tokensList[i]; @@ -72,11 +72,11 @@ void _testTokenEncryption() { PushToken(serial: 'serial', id: 'id5'), ]; const uriStrings = [ - 'pia://qrbackup?data=H4sIAAAAAAAACk1PMQ7CMAz8i-cOsDBkY2slJJDgA2nshqhuUiWuBEL8HUelgsnnu7PPfgHbnhgMQAOhlIXyF6PWgHuFc4hgBsuFquWU3Ej4R7QBkX4OSSPFbrKewMSFuYEhMVLucOtLytJFpMdGpBx8zVg7ec46Cu35dtFwy15luU9KXdtjvQfVLQXMQVeRyyQqraCqLi1R6he79wc_WDvI3QAAAA==', - 'pia://qrbackup?data=H4sIAAAAAAAACk1OzQrCMAx-l5x3kIkivXlbQVDYXqAuWS3L2tF2oIjvbuYcesr3l3x5ApsrMSiAAlxKE8UvRpkOS4Gj86A6w4nmyCm0PeGfUDlE-iVy6MnrwVgC5SfmArrASFHjylOIWXuk-yqE6OzcsbD8GGUVmnNzkXLDVux8G0Sqq2O524uIks8J1EGOURspi7mAz78UXZC27eb1Bgi4xPffAAAA', - 'pia://qrbackup?data=H4sIAAAAAAAACk2OsQ7CMAxE_8VzB6BiydYBqZFggh8ItRuiukmVpBII8e84lAom3z2fdX4CmysxKIAKXEozxa9GmQ5rkZPzoHrDiUrkGLqB8A-0DpF-iRwG8no0lkD5mbmCPjBS1Lj6FGLWHum-ghCdLR2Ly49JTuF8OTQnaTdsZZ9vY2Fts9_uBCbqImUhi_h8SdEF6ag3rzcEg5Vm1QAAAA==', - 'pia://qrbackup?data=H4sIAAAAAAAACk1PTQvCMAz9Lznv4GSK7DYY4mAy2UDxOE2cZV072s4PxP9uyhyaS15eXpKXF8j6RBJigACEtQOZL0bOAiOGvVAQX2ppyUtyfW4J_4iNQKSfwumWVNbVDUGsBikDuGiJZDKcaquNyxTSYyK0EY2_MVbu2fMopMlxl1TVoShT9lDLhlXu2nGn2iSLcM4k8pizEIczXkpnQ467I_C-b4LuW41-2T7Js3RdlP4bMkKzl9Uymn3j_QHdQYgrBgEAAA==', - 'pia://qrbackup?data=H4sIAAAAAAAAClVQwW7CMAz9F5-57tIrO1ANMbSy3VPiIguTVI6DqKb9-5xCOnbK88t7fk_-BnY9MjQAK6CUMsoDe3vJvxgcKUAzOE5YJNt4PKN_IjbkPf4pNJ4xtBd3QmhCZl7BENmjtL7OKYq2weOtElHoVDLuk06jWWHf7j-7jcXjbSRxSjG8Ol2WJhRypfcDWEx_KNGLIPGXfQ3T0gyDROYLBl0LWmU1X6ryLFwhpQ_ToX_PuniLM2btdK5Qx10sjKjdw86Ue6Zjh3JFecOpbhuFrmaauz3Ts_o_-_ML-WVvco4BAAA=', + 'pia://qrbackup?data=eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQxIiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IkhPVFAiLCJhbGdvcml0aG0iOiJTSEExIiwiZGlnaXRzIjo2LCJzZWNyZXQiOiJzZWNyZXQxIiwiY291bnRlciI6MH0=', + 'pia://qrbackup?data=eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQyIiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IlRPVFAiLCJhbGdvcml0aG0iOiJTSEEyNTYiLCJkaWdpdHMiOjgsInNlY3JldCI6InNlY3JldDIiLCJwZXJpb2QiOjMwfQ==', + 'pia://qrbackup?data=eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQzIiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IlNURUFNIiwic2VjcmV0Ijoic2VjcmV0MyJ9', + 'pia://qrbackup?data=eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQ0IiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IkRBWVBBU1NXT1JEIiwiYWxnb3JpdGhtIjoiU0hBNTEyIiwiZGlnaXRzIjoxMCwic2VjcmV0Ijoic2VjcmV0NCIsInZpZXdNb2RlIjoiVkFMSURGT1IiLCJwZXJpb2QiOjg2NDAwMDAwMDAwfQ==', + 'pia://qrbackup?data=eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQ1IiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IlBJUFVTSCIsImV4cGlyYXRpb25EYXRlIjpudWxsLCJzZXJpYWwiOiJzZXJpYWwiLCJmYlRva2VuIjpudWxsLCJzc2xWZXJpZnkiOmZhbHNlLCJlbnJvbGxtZW50Q3JlZGVudGlhbHMiOm51bGwsInVybCI6bnVsbCwiaXNSb2xsZWRPdXQiOmZhbHNlLCJyb2xsb3V0U3RhdGUiOiJyb2xsb3V0Tm90U3RhcnRlZCIsInB1YmxpY1NlcnZlcktleSI6bnVsbCwicHJpdmF0ZVRva2VuS2V5IjpudWxsLCJwdWJsaWNUb2tlbktleSI6bnVsbH0=', ]; for (var i = 0; uriStrings.length > i; i++) { final uri = Uri.parse(uriStrings[i]); diff --git a/test/unit_test/model/push_request_test.dart b/test/unit_test/model/push_request_test.dart index 950ea7a80..91990c500 100644 --- a/test/unit_test/model/push_request_test.dart +++ b/test/unit_test/model/push_request_test.dart @@ -1,8 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:privacyidea_authenticator/model/push_request.dart'; import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; - -import '../utils/identifiers_test.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; void main() { _testPushRequest(); diff --git a/test/unit_test/processors/scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor_test.dart b/test/unit_test/processors/scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor_test.dart index 714a7045a..8ea234d60 100644 --- a/test/unit_test/processors/scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor_test.dart +++ b/test/unit_test/processors/scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor_test.dart @@ -1,5 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:privacyidea_authenticator/model/processor_result.dart'; +import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; import 'package:privacyidea_authenticator/processors/scheme_processors/token_import_scheme_processors/free_otp_plus_qr_processor.dart'; @@ -44,9 +45,15 @@ void _testFreeOtpPlusQrProcessor() { final results = await const FreeOtpPlusQrProcessor().processUri(normalOtpAuthUri); // Assert expect(results.length, equals(1)); - expect(results.first, isA>()); - final firstResult = results.first as ProcessorResultFailed; - expect(firstResult.message.isNotEmpty, equals(true)); + expect(results.first, isA>()); + final firstResult = results.first.asSuccess!; + expect(firstResult.resultData, isA()); + expect(firstResult.resultData.issuer, equals('FreeOTP+')); + expect(firstResult.resultData.label, equals('alice')); + expect(firstResult.resultData, isA()); + expect(firstResult.resultData.origin!.appName, equals('FreeOTP+')); + final hotpToken = firstResult.resultData as HOTPToken; + expect(hotpToken.counter, equals(0)); }); }); } diff --git a/test/unit_test/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor_test.dart b/test/unit_test/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor_test.dart index 128dc1d4f..3e111d643 100644 --- a/test/unit_test/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor_test.dart +++ b/test/unit_test/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor_test.dart @@ -28,9 +28,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type, equals('TOTP')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final totpToken = token0 as TOTPToken; - expect(totpToken.origin, isNull); expect(totpToken.period, equals(45)); expect(totpToken.digits, equals(8)); expect(totpToken.algorithm.name, equals('SHA256')); @@ -50,9 +52,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type, equals('TOTP')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final totpToken = token0 as TOTPToken; - expect(totpToken.origin, isNull); expect(totpToken.period, equals(30)); expect(totpToken.digits, equals(6)); expect(totpToken.algorithm.name, equals('SHA1')); @@ -72,9 +76,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type, equals('TOTP')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final totpToken = token0 as TOTPToken; - expect(totpToken.origin, isNull); expect(totpToken.period, equals(30)); expect(totpToken.digits, equals(6)); expect(totpToken.algorithm.name, equals('SHA1')); @@ -94,9 +100,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type, equals('TOTP')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final totpToken = token0 as TOTPToken; - expect(totpToken.origin, isNull); expect(totpToken.period, equals(30)); expect(totpToken.digits, equals(6)); expect(totpToken.algorithm.name, equals('SHA1')); @@ -130,8 +138,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type, equals('TOTP')); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final totpToken = token0 as TOTPToken; - expect(totpToken.origin, isNull); expect(totpToken.period, equals(30)); expect(totpToken.digits, equals(6)); expect(totpToken.algorithm.name, equals('SHA1')); @@ -154,9 +165,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type, equals('HOTP')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final hotpToken = token0 as HOTPToken; - expect(hotpToken.origin, isNull); expect(hotpToken.counter, equals(5)); expect(hotpToken.digits, equals(8)); expect(hotpToken.algorithm.name, equals('SHA256')); @@ -176,9 +189,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type, equals('HOTP')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final hotpToken = token0 as HOTPToken; - expect(hotpToken.origin, isNull); expect(hotpToken.counter, equals(5)); expect(hotpToken.digits, equals(8)); expect(hotpToken.algorithm.name, equals('SHA1')); @@ -198,9 +213,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type, equals('HOTP')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final hotpToken = token0 as HOTPToken; - expect(hotpToken.origin, isNull); expect(hotpToken.counter, equals(5)); expect(hotpToken.digits, equals(6)); expect(hotpToken.algorithm.name, equals('SHA256')); @@ -220,9 +237,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type, equals('HOTP')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final hotpToken = token0 as HOTPToken; - expect(hotpToken.origin, isNull); expect(hotpToken.counter, equals(0)); expect(hotpToken.digits, equals(8)); expect(hotpToken.algorithm.name, equals('SHA256')); @@ -256,9 +275,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type, equals('HOTP')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final hotpToken = token0 as HOTPToken; - expect(hotpToken.origin, isNull); expect(hotpToken.counter, equals(5)); expect(hotpToken.digits, equals(8)); expect(hotpToken.algorithm.name, equals('SHA256')); @@ -294,9 +315,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type.toLowerCase(), equals('daypassword')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final dayPasswordToken = token0 as DayPasswordToken; - expect(dayPasswordToken.origin, isNull); expect(dayPasswordToken.period, equals(const Duration(days: 1))); expect(dayPasswordToken.digits, equals(8)); expect(dayPasswordToken.algorithm.name, equals('SHA256')); @@ -317,9 +340,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type.toLowerCase(), equals('daypassword')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final dayPasswordToken = token0 as DayPasswordToken; - expect(dayPasswordToken.origin, isNull); expect(dayPasswordToken.period, equals(const Duration(days: 1))); expect(dayPasswordToken.digits, equals(8)); expect(dayPasswordToken.algorithm.name, equals('SHA1')); @@ -340,9 +365,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type.toLowerCase(), equals('daypassword')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final dayPasswordToken = token0 as DayPasswordToken; - expect(dayPasswordToken.origin, isNull); expect(dayPasswordToken.period, equals(const Duration(days: 2))); expect(dayPasswordToken.digits, equals(6)); expect(dayPasswordToken.algorithm.name, equals('SHA256')); @@ -363,9 +390,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('issuer')); expect(token0.label, equals('account')); expect(token0.type.toLowerCase(), equals('daypassword')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final dayPasswordToken = token0 as DayPasswordToken; - expect(dayPasswordToken.origin, isNull); expect(dayPasswordToken.period, equals(const Duration(days: 1))); expect(dayPasswordToken.digits, equals(8)); expect(dayPasswordToken.algorithm.name, equals('SHA256')); @@ -403,9 +432,11 @@ void _testOtpAuthProcessor() { expect(token0.issuer, equals('privacyIDEA')); expect(token0.label, equals('PIPU0000D79E')); expect(token0.type.toLowerCase(), equals('pipush')); - expect(token0.origin, isNull); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, isNull); + expect(token0.origin!.isPrivacyIdeaToken, isNull); + expect(token0.origin!.data, equals(uriString)); final pushToken = token0 as PushToken; - expect(pushToken.origin, isNull); expect(pushToken.url, equals(Uri.parse('https://123.456.78.9/ttype/push'))); expect(pushToken.expirationDate, isNotNull); // DateTimes.now() are never the same diff --git a/test/unit_test/processors/token_import_file_processor/aegis_import_file_processor_test.dart b/test/unit_test/processors/token_import_file_processor/aegis_import_file_processor_test.dart index 10d7760a6..987b4ceaa 100644 --- a/test/unit_test/processors/token_import_file_processor/aegis_import_file_processor_test.dart +++ b/test/unit_test/processors/token_import_file_processor/aegis_import_file_processor_test.dart @@ -4,7 +4,10 @@ import 'package:camera/camera.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:privacyidea_authenticator/model/processor_result.dart'; +import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; +import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; import 'package:privacyidea_authenticator/processors/token_import_file_processor/aegis_import_file_processor.dart'; +import 'package:privacyidea_authenticator/processors/token_import_file_processor/two_fas_import_file_processor.dart'; void main() { _testAegisImportFileProcessor(); @@ -34,8 +37,8 @@ void _testAegisImportFileProcessor() { const aegisImportFileProcessor = AegisImportFileProcessor(); final XFile file = XFile.fromData(byteData.buffer.asUint8List(), name: 'aegis_plain.json'); // Act - final isValid = await aegisImportFileProcessor.fileIsValid(file: file); - final results = await aegisImportFileProcessor.processFile(file: file); + final isValid = await aegisImportFileProcessor.fileIsValid(file); + final results = await aegisImportFileProcessor.processFile(file); // Assert expect(isValid, isTrue); expect(results.length, equals(2)); @@ -45,18 +48,135 @@ void _testAegisImportFileProcessor() { expect(token0.label, equals('Test1')); expect(token0.type, equals('TOTP')); expect(token0.origin, isNotNull); + final totpToken = token0 as TOTPToken; + expect(totpToken.digits, equals(6)); + expect(totpToken.algorithm.name, equals('SHA1')); + expect(totpToken.period, equals(30)); + expect(totpToken.secret, equals('AAAAAAAA')); + expect(totpToken.issuer, equals('Testing')); + expect(totpToken.otpFromTime(DateTime.fromMillisecondsSinceEpoch(1713352639317)), equals('220975')); final result1 = results[1]; expect(result1, isA()); final token1 = result1.asSuccess!.resultData; expect(token1.label, equals('Test2')); expect(token1.type, equals('HOTP')); expect(token1.origin, isNotNull); + final hotpToken = token1 as HOTPToken; + expect(hotpToken.digits, equals(6)); + expect(hotpToken.algorithm.name, equals('SHA1')); + expect(hotpToken.counter, equals(0)); + expect(hotpToken.secret, equals('AAAAAAAA')); + expect(hotpToken.issuer, equals('Testing')); + expect(hotpToken.otpValue, equals('328482')); }); - }); - group('import HTML', () {}); + test('encrypted', () async { + // Arrange + const encryptedBytesString = + '[123, 10, 32, 32, 32, 32, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 32, 49, 44, 10, 32, 32, 32, 32, 34, 104, 101, 97, 100, 101, 114, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 115, 108, 111, 116, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 116, 121, 112, 101, 34, 58, 32, 49, 44, 10, 32, 32, 32, 32,' + '32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 117, 117, 105, 100, 34, 58, 32, 34, 99, 101, 53, 50, 101, 98, 99, 56, 45, 53, 56, 53, 54, 45, 52, 52, 100, 52, 45, 97, 99, 49, 49, 45, 102, 102, 54, 48, 49, 57, 57, 53, 48, 101, 98, 102, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 107, 101, 121, 34, 58, 32, 34, 54, 48, 102, 51, 101, 99, 102, 101, 57, 57, 54, 53, 55, 54, 55, 98,' + '97, 49, 53, 51, 53, 50, 49, 49, 48, 99, 50, 54, 56, 101, 53, 56, 48, 50, 53, 53, 56, 53, 101, 54, 52, 98, 57, 99, 54, 99, 53, 98, 53, 99, 97, 97, 54, 98, 101, 52, 56, 98, 53, 98, 101, 98, 57, 50, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 107, 101, 121, 95, 112, 97, 114, 97, 109, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,' + '32, 32, 32, 34, 110, 111, 110, 99, 101, 34, 58, 32, 34, 98, 56, 100, 50, 54, 97, 48, 53, 99, 57, 51, 49, 56, 54, 99, 57, 55, 52, 98, 55, 49, 54, 99, 51, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 116, 97, 103, 34, 58, 32, 34, 51, 56, 99, 49, 102, 48, 50, 100, 57, 48, 56, 51, 49, 50, 55, 56, 99, 53, 55, 56, 48, 100, 54, 53, 54, 97, 56, 99, 53, 50, 48, 97,' + '34, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, 34, 58, 32, 51, 50, 55, 54, 56, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 114, 34, 58, 32, 56, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 112, 34, 58, 32, 49, 44, 10, 32, 32, 32,' + '32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 115, 97, 108, 116, 34, 58, 32, 34, 99, 51, 49, 57, 49, 57, 100, 53, 97, 50, 57, 48, 54, 102, 50, 54, 51, 57, 97, 51, 52, 50, 50, 99, 49, 53, 102, 102, 52, 52, 100, 51, 101, 55, 50, 50, 56, 53, 101, 97, 52, 54, 98, 48, 48, 97, 51, 48, 48, 57, 55, 52, 100, 57, 102, 98, 54, 99, 100, 97, 97, 48, 101, 100, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,' + '32, 32, 32, 32, 32, 34, 114, 101, 112, 97, 105, 114, 101, 100, 34, 58, 32, 116, 114, 117, 101, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 105, 115, 95, 98, 97, 99, 107, 117, 112, 34, 58, 32, 102, 97, 108, 115, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 93, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 112, 97, 114, 97, 109, 115, 34, 58,' + '32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, 111, 110, 99, 101, 34, 58, 32, 34, 48, 57, 102, 48, 53, 54, 52, 49, 48, 50, 55, 49, 102, 50, 99, 50, 52, 97, 51, 51, 100, 52, 99, 54, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 116, 97, 103, 34, 58, 32, 34, 51, 48, 54, 53, 54, 100, 102, 50, 102, 54, 97, 49, 97, 100, 99, 48, 99, 56, 51, 98, 99, 97, 55, 57, 99, 101, 101, 97,' + '57, 99, 100, 54, 34, 10, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 100, 98, 34, 58, 32, 34, 82, 89, 75, 89, 119, 107, 119, 100, 71, 121, 54, 76, 49, 54, 85, 87, 119, 116, 68, 84, 57, 48, 67, 102, 100, 81, 77, 119, 97, 105, 115, 72, 78, 70, 99, 51, 116, 103, 103, 87, 87, 56, 65, 79, 84, 113, 118, 51, 118, 106, 121, 52, 74, 56, 50, 52, 107, 87, 66, 108, 108, 105, 77, 50, 65, 85,' + '48, 92, 47, 53, 98, 116, 108, 108, 86, 106, 78, 77, 121, 74, 100, 97, 79, 86, 79, 98, 98, 121, 85, 90, 52, 106, 78, 117, 118, 72, 106, 114, 79, 97, 99, 73, 92, 47, 121, 97, 51, 88, 70, 54, 85, 48, 113, 43, 87, 89, 68, 111, 70, 97, 110, 57, 116, 110, 78, 56, 83, 48, 78, 78, 97, 66, 67, 102, 82, 87, 72, 66, 66, 118, 119, 83, 118, 77, 118, 112, 92, 47, 98, 109, 43, 66, 76, 113, 69, 56, 92, 47, 108, 82, 72, 74, 81, 119, 102, 54,' + '71, 49, 83, 49, 106, 103, 56, 110, 84, 122, 108, 120, 113, 54, 53, 52, 109, 121, 103, 115, 122, 118, 111, 72, 80, 65, 97, 52, 76, 99, 83, 99, 48, 77, 103, 50, 87, 48, 109, 53, 99, 103, 81, 112, 69, 73, 111, 51, 73, 111, 88, 113, 104, 66, 87, 101, 67, 85, 55, 70, 70, 68, 87, 102, 88, 120, 86, 104, 117, 56, 99, 72, 69, 105, 112, 114, 86, 73, 103, 43, 73, 48, 83, 74, 82, 119, 98, 117, 78, 102, 88, 107, 106, 105, 75, 122, 67, 116, 52, 111,' + '74, 119, 67, 85, 113, 111, 48, 88, 84, 57, 84, 121, 88, 87, 110, 75, 89, 51, 108, 70, 73, 107, 101, 77, 111, 101, 90, 114, 84, 100, 103, 122, 105, 118, 79, 111, 52, 54, 87, 80, 52, 76, 52, 67, 99, 74, 92, 47, 69, 110, 101, 73, 85, 83, 107, 100, 100, 73, 90, 109, 55, 106, 106, 88, 112, 70, 92, 47, 82, 112, 120, 106, 53, 107, 112, 67, 89, 75, 74, 54, 50, 69, 89, 54, 89, 108, 79, 88, 99, 110, 74, 78, 88, 81, 92, 47, 100, 86, 51, 75,' + '66, 101, 112, 119, 51, 104, 69, 97, 113, 109, 116, 81, 43, 82, 75, 98, 102, 108, 111, 108, 69, 77, 106, 50, 80, 65, 120, 112, 114, 43, 104, 118, 79, 107, 112, 105, 69, 90, 86, 48, 113, 120, 79, 48, 43, 98, 70, 67, 66, 108, 120, 107, 78, 74, 50, 110, 99, 49, 110, 117, 82, 99, 88, 102, 73, 49, 56, 121, 75, 112, 56, 82, 51, 99, 69, 75, 43, 105, 99, 90, 119, 67, 54, 65, 114, 100, 120, 107, 101, 116, 76, 49, 122, 114, 80, 103, 68, 117, 67, 100,' + '122, 54, 53, 108, 111, 43, 90, 69, 112, 118, 81, 116, 73, 78, 92, 47, 67, 115, 80, 69, 113, 118, 73, 89, 72, 55, 81, 55, 114, 111, 112, 112, 87, 101, 79, 105, 122, 80, 80, 120, 84, 118, 99, 98, 72, 57, 99, 80, 122, 50, 105, 76, 53, 104, 107, 105, 55, 48, 116, 97, 106, 85, 120, 113, 88, 103, 80, 120, 68, 116, 68, 113, 43, 78, 50, 120, 48, 92, 47, 103, 71, 100, 99, 105, 65, 118, 99, 98, 116, 97, 52, 55, 99, 74, 119, 88, 108, 76, 50, 67,' + '97, 67, 69, 87, 116, 97, 89, 98, 54, 87, 54, 108, 112, 120, 67, 103, 57, 66, 54, 82, 56, 77, 43, 90, 73, 79, 115, 89, 83, 81, 114, 77, 117, 97, 99, 80, 51, 51, 90, 101, 76, 118, 97, 113, 99, 83, 80, 69, 115, 67, 78, 78, 107, 107, 122, 108, 71, 75, 90, 75, 100, 49, 54, 115, 90, 49, 43, 55, 112, 99, 69, 121, 76, 119, 68, 57, 74, 55, 107, 85, 115, 85, 80, 72, 88, 69, 53, 56, 101, 92, 47, 111, 97, 77, 68, 53, 109, 104, 85, 100,' + '81, 66, 99, 66, 78, 112, 107, 101, 49, 50, 56, 73, 89, 109, 49, 51, 67, 85, 83, 119, 82, 120, 116, 85, 66, 48, 111, 98, 87, 87, 106, 120, 111, 102, 80, 115, 55, 54, 114, 89, 112, 84, 112, 75, 87, 103, 98, 99, 48, 118, 118, 104, 57, 66, 99, 43, 84, 100, 82, 101, 117, 51, 79, 88, 105, 80, 121, 92, 47, 111, 73, 73, 79, 117, 122, 53, 115, 92, 47, 54, 109, 56, 89, 106, 48, 48, 70, 74, 79, 67, 113, 110, 89, 108, 78, 100, 69, 99, 88, 107,' + '90, 113, 89, 74, 97, 92, 47, 50, 54, 76, 85, 121, 54, 120, 69, 89, 97, 84, 78, 111, 109, 99, 89, 67, 73, 89, 56, 84, 92, 47, 112, 87, 116, 54, 97, 92, 47, 109, 111, 108, 89, 69, 52, 122, 82, 108, 80, 55, 71, 68, 88, 48, 104, 76, 51, 114, 51, 98, 84, 100, 97, 52, 120, 106, 57, 81, 112, 109, 110, 49, 100, 110, 116, 92, 47, 118, 101, 119, 116, 84, 103, 72, 100, 118, 100, 86, 97, 108, 120, 52, 75, 116, 105, 97, 86, 54, 107, 120, 80, 98,' + '109, 110, 81, 121, 76, 79, 111, 86, 73, 121, 106, 70, 57, 92, 47, 56, 67, 48, 67, 106, 75, 109, 111, 85, 70, 100, 78, 89, 102, 68, 76, 66, 114, 122, 74, 56, 43, 80, 67, 103, 84, 71, 51, 88, 100, 75, 101, 51, 43, 110, 100, 51, 119, 50, 100, 121, 77, 121, 78, 69, 70, 104, 114, 50, 120, 99, 112, 74, 121, 116, 120, 69, 92, 47, 65, 57, 101, 108, 77, 77, 121, 74, 82, 103, 77, 51, 82, 120, 83, 98, 82, 84, 98, 89, 112, 109, 99, 102, 101, 54,' + '72, 50, 100, 48, 110, 113, 118, 83, 97, 82, 110, 51, 98, 49, 57, 84, 48, 121, 49, 43, 57, 68, 67, 49, 113, 86, 78, 79, 102, 109, 109, 67, 66, 90, 43, 101, 87, 86, 68, 50, 78, 110, 107, 88, 81, 68, 110, 115, 67, 78, 71, 50, 104, 76, 52, 101, 73, 75, 117, 81, 97, 122, 68, 81, 112, 53, 86, 74, 104, 114, 100, 122, 105, 121, 106, 79, 68, 105, 85, 57, 78, 86, 77, 67, 70, 65, 68, 52, 76, 106, 102, 74, 79, 100, 116, 98, 122, 87, 78, 112,' + '67, 100, 79, 109, 56, 66, 108, 86, 74, 121, 97, 56, 86, 48, 84, 81, 65, 100, 118, 54, 104, 70, 50, 119, 112, 65, 83, 54, 86, 92, 47, 104, 107, 109, 71, 101, 106, 103, 65, 57, 101, 108, 118, 118, 50, 82, 113, 97, 78, 90, 84, 79, 102, 103, 102, 80, 108, 110, 113, 74, 113, 81, 51, 102, 98, 81, 99, 116, 122, 72, 66, 118, 98, 114, 107, 100, 77, 87, 86, 76, 102, 108, 102, 49, 103, 92, 47, 90, 121, 115, 69, 97, 106, 66, 75, 121, 108, 43, 107, 57,' + '115, 82, 102, 85, 76, 120, 112, 72, 116, 77, 70, 54, 86, 109, 117, 120, 52, 87, 51, 115, 43, 77, 88, 102, 67, 72, 66, 66, 71, 81, 112, 90, 105, 117, 48, 51, 98, 121, 86, 116, 101, 86, 111, 104, 68, 78, 97, 108, 52, 77, 70, 81, 117, 72, 52, 72, 66, 52, 106, 55, 71, 118, 99, 98, 113, 120, 56, 122, 56, 120, 73, 90, 113, 90, 104, 75, 73, 50, 111, 82, 97, 86, 81, 106, 53, 69, 48, 68, 56, 71, 85, 110, 74, 84, 49, 81, 100, 86, 65, 69,' + '43, 77, 86, 75, 65, 78, 74, 68, 109, 104, 84, 56, 86, 111, 34, 10, 125]'; + + final byteData = ByteData.view(Uint8List.fromList((jsonDecode(encryptedBytesString) as List).cast()).buffer); - group('import TXT', () {}); + const aegisImportFileProcessor = AegisImportFileProcessor(); + final XFile file = XFile.fromData(byteData.buffer.asUint8List(), name: 'aegis_encrypted.json'); + // Act + final isValid = await aegisImportFileProcessor.fileIsValid(file); + final fileNeedsPassword = await aegisImportFileProcessor.fileNeedsPassword(file); + final results = await aegisImportFileProcessor.processFile(file, password: 'test123'); + // Assert + expect(isValid, isTrue); + expect(fileNeedsPassword, isTrue); + expect(results.length, equals(2)); + final result0 = results[0]; + expect(result0, isA()); + final token0 = result0.asSuccess!.resultData; + expect(token0.label, equals('Test1')); + expect(token0.type, equals('TOTP')); + expect(token0.origin, isNotNull); + final totpToken = token0 as TOTPToken; + expect(totpToken.digits, equals(6)); + expect(totpToken.algorithm.name, equals('SHA1')); + expect(totpToken.period, equals(30)); + expect(totpToken.secret, equals('AAAAAAAA')); + expect(totpToken.issuer, equals('Testing')); + expect(totpToken.otpFromTime(DateTime.fromMillisecondsSinceEpoch(1713352639317)), equals('220975')); + final result1 = results[1]; + expect(result1, isA()); + final token1 = result1.asSuccess!.resultData; + expect(token1.label, equals('Test2')); + expect(token1.type, equals('HOTP')); + expect(token1.origin, isNotNull); + final hotpToken = token1 as HOTPToken; + expect(hotpToken.digits, equals(6)); + expect(hotpToken.algorithm.name, equals('SHA1')); + expect(hotpToken.counter, equals(0)); + expect(hotpToken.secret, equals('AAAAAAAA')); + expect(hotpToken.issuer, equals('Testing')); + expect(hotpToken.otpValue, equals('328482')); + }); + + test('exncrypted bad password', () async { + // Arrange + const encryptedBytesString = + '[123, 10, 32, 32, 32, 32, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 32, 49, 44, 10, 32, 32, 32, 32, 34, 104, 101, 97, 100, 101, 114, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 115, 108, 111, 116, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 116, 121, 112, 101, 34, 58, 32, 49, 44, 10, 32, 32, 32, 32,' + '32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 117, 117, 105, 100, 34, 58, 32, 34, 99, 101, 53, 50, 101, 98, 99, 56, 45, 53, 56, 53, 54, 45, 52, 52, 100, 52, 45, 97, 99, 49, 49, 45, 102, 102, 54, 48, 49, 57, 57, 53, 48, 101, 98, 102, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 107, 101, 121, 34, 58, 32, 34, 54, 48, 102, 51, 101, 99, 102, 101, 57, 57, 54, 53, 55, 54, 55, 98,' + '97, 49, 53, 51, 53, 50, 49, 49, 48, 99, 50, 54, 56, 101, 53, 56, 48, 50, 53, 53, 56, 53, 101, 54, 52, 98, 57, 99, 54, 99, 53, 98, 53, 99, 97, 97, 54, 98, 101, 52, 56, 98, 53, 98, 101, 98, 57, 50, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 107, 101, 121, 95, 112, 97, 114, 97, 109, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,' + '32, 32, 32, 34, 110, 111, 110, 99, 101, 34, 58, 32, 34, 98, 56, 100, 50, 54, 97, 48, 53, 99, 57, 51, 49, 56, 54, 99, 57, 55, 52, 98, 55, 49, 54, 99, 51, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 116, 97, 103, 34, 58, 32, 34, 51, 56, 99, 49, 102, 48, 50, 100, 57, 48, 56, 51, 49, 50, 55, 56, 99, 53, 55, 56, 48, 100, 54, 53, 54, 97, 56, 99, 53, 50, 48, 97,' + '34, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, 34, 58, 32, 51, 50, 55, 54, 56, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 114, 34, 58, 32, 56, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 112, 34, 58, 32, 49, 44, 10, 32, 32, 32,' + '32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 115, 97, 108, 116, 34, 58, 32, 34, 99, 51, 49, 57, 49, 57, 100, 53, 97, 50, 57, 48, 54, 102, 50, 54, 51, 57, 97, 51, 52, 50, 50, 99, 49, 53, 102, 102, 52, 52, 100, 51, 101, 55, 50, 50, 56, 53, 101, 97, 52, 54, 98, 48, 48, 97, 51, 48, 48, 57, 55, 52, 100, 57, 102, 98, 54, 99, 100, 97, 97, 48, 101, 100, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,' + '32, 32, 32, 32, 32, 34, 114, 101, 112, 97, 105, 114, 101, 100, 34, 58, 32, 116, 114, 117, 101, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 105, 115, 95, 98, 97, 99, 107, 117, 112, 34, 58, 32, 102, 97, 108, 115, 101, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 93, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 112, 97, 114, 97, 109, 115, 34, 58,' + '32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, 111, 110, 99, 101, 34, 58, 32, 34, 48, 57, 102, 48, 53, 54, 52, 49, 48, 50, 55, 49, 102, 50, 99, 50, 52, 97, 51, 51, 100, 52, 99, 54, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 116, 97, 103, 34, 58, 32, 34, 51, 48, 54, 53, 54, 100, 102, 50, 102, 54, 97, 49, 97, 100, 99, 48, 99, 56, 51, 98, 99, 97, 55, 57, 99, 101, 101, 97,' + '57, 99, 100, 54, 34, 10, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 100, 98, 34, 58, 32, 34, 82, 89, 75, 89, 119, 107, 119, 100, 71, 121, 54, 76, 49, 54, 85, 87, 119, 116, 68, 84, 57, 48, 67, 102, 100, 81, 77, 119, 97, 105, 115, 72, 78, 70, 99, 51, 116, 103, 103, 87, 87, 56, 65, 79, 84, 113, 118, 51, 118, 106, 121, 52, 74, 56, 50, 52, 107, 87, 66, 108, 108, 105, 77, 50, 65, 85,' + '48, 92, 47, 53, 98, 116, 108, 108, 86, 106, 78, 77, 121, 74, 100, 97, 79, 86, 79, 98, 98, 121, 85, 90, 52, 106, 78, 117, 118, 72, 106, 114, 79, 97, 99, 73, 92, 47, 121, 97, 51, 88, 70, 54, 85, 48, 113, 43, 87, 89, 68, 111, 70, 97, 110, 57, 116, 110, 78, 56, 83, 48, 78, 78, 97, 66, 67, 102, 82, 87, 72, 66, 66, 118, 119, 83, 118, 77, 118, 112, 92, 47, 98, 109, 43, 66, 76, 113, 69, 56, 92, 47, 108, 82, 72, 74, 81, 119, 102, 54,' + '71, 49, 83, 49, 106, 103, 56, 110, 84, 122, 108, 120, 113, 54, 53, 52, 109, 121, 103, 115, 122, 118, 111, 72, 80, 65, 97, 52, 76, 99, 83, 99, 48, 77, 103, 50, 87, 48, 109, 53, 99, 103, 81, 112, 69, 73, 111, 51, 73, 111, 88, 113, 104, 66, 87, 101, 67, 85, 55, 70, 70, 68, 87, 102, 88, 120, 86, 104, 117, 56, 99, 72, 69, 105, 112, 114, 86, 73, 103, 43, 73, 48, 83, 74, 82, 119, 98, 117, 78, 102, 88, 107, 106, 105, 75, 122, 67, 116, 52, 111,' + '74, 119, 67, 85, 113, 111, 48, 88, 84, 57, 84, 121, 88, 87, 110, 75, 89, 51, 108, 70, 73, 107, 101, 77, 111, 101, 90, 114, 84, 100, 103, 122, 105, 118, 79, 111, 52, 54, 87, 80, 52, 76, 52, 67, 99, 74, 92, 47, 69, 110, 101, 73, 85, 83, 107, 100, 100, 73, 90, 109, 55, 106, 106, 88, 112, 70, 92, 47, 82, 112, 120, 106, 53, 107, 112, 67, 89, 75, 74, 54, 50, 69, 89, 54, 89, 108, 79, 88, 99, 110, 74, 78, 88, 81, 92, 47, 100, 86, 51, 75,' + '66, 101, 112, 119, 51, 104, 69, 97, 113, 109, 116, 81, 43, 82, 75, 98, 102, 108, 111, 108, 69, 77, 106, 50, 80, 65, 120, 112, 114, 43, 104, 118, 79, 107, 112, 105, 69, 90, 86, 48, 113, 120, 79, 48, 43, 98, 70, 67, 66, 108, 120, 107, 78, 74, 50, 110, 99, 49, 110, 117, 82, 99, 88, 102, 73, 49, 56, 121, 75, 112, 56, 82, 51, 99, 69, 75, 43, 105, 99, 90, 119, 67, 54, 65, 114, 100, 120, 107, 101, 116, 76, 49, 122, 114, 80, 103, 68, 117, 67, 100,' + '122, 54, 53, 108, 111, 43, 90, 69, 112, 118, 81, 116, 73, 78, 92, 47, 67, 115, 80, 69, 113, 118, 73, 89, 72, 55, 81, 55, 114, 111, 112, 112, 87, 101, 79, 105, 122, 80, 80, 120, 84, 118, 99, 98, 72, 57, 99, 80, 122, 50, 105, 76, 53, 104, 107, 105, 55, 48, 116, 97, 106, 85, 120, 113, 88, 103, 80, 120, 68, 116, 68, 113, 43, 78, 50, 120, 48, 92, 47, 103, 71, 100, 99, 105, 65, 118, 99, 98, 116, 97, 52, 55, 99, 74, 119, 88, 108, 76, 50, 67,' + '97, 67, 69, 87, 116, 97, 89, 98, 54, 87, 54, 108, 112, 120, 67, 103, 57, 66, 54, 82, 56, 77, 43, 90, 73, 79, 115, 89, 83, 81, 114, 77, 117, 97, 99, 80, 51, 51, 90, 101, 76, 118, 97, 113, 99, 83, 80, 69, 115, 67, 78, 78, 107, 107, 122, 108, 71, 75, 90, 75, 100, 49, 54, 115, 90, 49, 43, 55, 112, 99, 69, 121, 76, 119, 68, 57, 74, 55, 107, 85, 115, 85, 80, 72, 88, 69, 53, 56, 101, 92, 47, 111, 97, 77, 68, 53, 109, 104, 85, 100,' + '81, 66, 99, 66, 78, 112, 107, 101, 49, 50, 56, 73, 89, 109, 49, 51, 67, 85, 83, 119, 82, 120, 116, 85, 66, 48, 111, 98, 87, 87, 106, 120, 111, 102, 80, 115, 55, 54, 114, 89, 112, 84, 112, 75, 87, 103, 98, 99, 48, 118, 118, 104, 57, 66, 99, 43, 84, 100, 82, 101, 117, 51, 79, 88, 105, 80, 121, 92, 47, 111, 73, 73, 79, 117, 122, 53, 115, 92, 47, 54, 109, 56, 89, 106, 48, 48, 70, 74, 79, 67, 113, 110, 89, 108, 78, 100, 69, 99, 88, 107,' + '90, 113, 89, 74, 97, 92, 47, 50, 54, 76, 85, 121, 54, 120, 69, 89, 97, 84, 78, 111, 109, 99, 89, 67, 73, 89, 56, 84, 92, 47, 112, 87, 116, 54, 97, 92, 47, 109, 111, 108, 89, 69, 52, 122, 82, 108, 80, 55, 71, 68, 88, 48, 104, 76, 51, 114, 51, 98, 84, 100, 97, 52, 120, 106, 57, 81, 112, 109, 110, 49, 100, 110, 116, 92, 47, 118, 101, 119, 116, 84, 103, 72, 100, 118, 100, 86, 97, 108, 120, 52, 75, 116, 105, 97, 86, 54, 107, 120, 80, 98,' + '109, 110, 81, 121, 76, 79, 111, 86, 73, 121, 106, 70, 57, 92, 47, 56, 67, 48, 67, 106, 75, 109, 111, 85, 70, 100, 78, 89, 102, 68, 76, 66, 114, 122, 74, 56, 43, 80, 67, 103, 84, 71, 51, 88, 100, 75, 101, 51, 43, 110, 100, 51, 119, 50, 100, 121, 77, 121, 78, 69, 70, 104, 114, 50, 120, 99, 112, 74, 121, 116, 120, 69, 92, 47, 65, 57, 101, 108, 77, 77, 121, 74, 82, 103, 77, 51, 82, 120, 83, 98, 82, 84, 98, 89, 112, 109, 99, 102, 101, 54,' + '72, 50, 100, 48, 110, 113, 118, 83, 97, 82, 110, 51, 98, 49, 57, 84, 48, 121, 49, 43, 57, 68, 67, 49, 113, 86, 78, 79, 102, 109, 109, 67, 66, 90, 43, 101, 87, 86, 68, 50, 78, 110, 107, 88, 81, 68, 110, 115, 67, 78, 71, 50, 104, 76, 52, 101, 73, 75, 117, 81, 97, 122, 68, 81, 112, 53, 86, 74, 104, 114, 100, 122, 105, 121, 106, 79, 68, 105, 85, 57, 78, 86, 77, 67, 70, 65, 68, 52, 76, 106, 102, 74, 79, 100, 116, 98, 122, 87, 78, 112,' + '67, 100, 79, 109, 56, 66, 108, 86, 74, 121, 97, 56, 86, 48, 84, 81, 65, 100, 118, 54, 104, 70, 50, 119, 112, 65, 83, 54, 86, 92, 47, 104, 107, 109, 71, 101, 106, 103, 65, 57, 101, 108, 118, 118, 50, 82, 113, 97, 78, 90, 84, 79, 102, 103, 102, 80, 108, 110, 113, 74, 113, 81, 51, 102, 98, 81, 99, 116, 122, 72, 66, 118, 98, 114, 107, 100, 77, 87, 86, 76, 102, 108, 102, 49, 103, 92, 47, 90, 121, 115, 69, 97, 106, 66, 75, 121, 108, 43, 107, 57,' + '115, 82, 102, 85, 76, 120, 112, 72, 116, 77, 70, 54, 86, 109, 117, 120, 52, 87, 51, 115, 43, 77, 88, 102, 67, 72, 66, 66, 71, 81, 112, 90, 105, 117, 48, 51, 98, 121, 86, 116, 101, 86, 111, 104, 68, 78, 97, 108, 52, 77, 70, 81, 117, 72, 52, 72, 66, 52, 106, 55, 71, 118, 99, 98, 113, 120, 56, 122, 56, 120, 73, 90, 113, 90, 104, 75, 73, 50, 111, 82, 97, 86, 81, 106, 53, 69, 48, 68, 56, 71, 85, 110, 74, 84, 49, 81, 100, 86, 65, 69,' + '43, 77, 86, 75, 65, 78, 74, 68, 109, 104, 84, 56, 86, 111, 34, 10, 125]'; + + final byteData = ByteData.view(Uint8List.fromList((jsonDecode(encryptedBytesString) as List).cast()).buffer); + + const aegisImportFileProcessor = AegisImportFileProcessor(); + final XFile file = XFile.fromData(byteData.buffer.asUint8List(), name: 'aegis_encrypted.json'); + // Act/Assert + expect(() async => await aegisImportFileProcessor.processFile(file, password: 'wrongPassword'), throwsA(isA())); + }); + }); + group('import HTML', () { + // Unimplemented + }); + + group('import TXT', () { + // Unimplemented + }); }); }); } diff --git a/test/unit_test/processors/token_import_file_processor/authenticator_pro_import_file_processor_test.dart b/test/unit_test/processors/token_import_file_processor/authenticator_pro_import_file_processor_test.dart index 8b1378917..bdedc5ef2 100644 --- a/test/unit_test/processors/token_import_file_processor/authenticator_pro_import_file_processor_test.dart +++ b/test/unit_test/processors/token_import_file_processor/authenticator_pro_import_file_processor_test.dart @@ -1 +1,209 @@ +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:file_selector/file_selector.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; +import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; +import 'package:privacyidea_authenticator/model/enums/token_types.dart'; +import 'package:privacyidea_authenticator/model/processor_result.dart'; +import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; +import 'package:privacyidea_authenticator/model/tokens/token.dart'; +import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; +import 'package:privacyidea_authenticator/processors/token_import_file_processor/authenticator_pro_import_file_processor.dart'; +import 'package:privacyidea_authenticator/utils/token_import_origins.dart'; + +void main() { + _testAuthenticatorProImportFileProcessor(); +} + +void _assertSuccessResults(List> results) { + expect(results.length, equals(2)); + final result0 = results[0]; + expect(result0, isA()); + final token0 = result0.asSuccess!.resultData; + expect(token0.label, 'Test1'); + expect(token0.issuer, 'Test1'); + expect(token0.type, TokenTypes.TOTP.name); + expect(token0, isA()); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, TokenImportOrigins.authenticatorPro.appName); + expect(token0.origin!.source, TokenOriginSourceType.backupFile); + final totpToken = token0 as TOTPToken; + expect(totpToken.secret, 'AAAAAAAA'); + expect(totpToken.algorithm, Algorithms.SHA1); + expect(totpToken.digits, 6); + expect(totpToken.period, 30); + final result1 = results[1]; + expect(result1, isA()); + final token1 = result1.asSuccess!.resultData; + expect(token1.label, 'Test2'); + expect(token1.issuer, 'Test2'); + expect(token1.type, TokenTypes.HOTP.name); + expect(token1, isA()); + expect(token1.origin, isNotNull); + expect(token1.origin!.appName, TokenImportOrigins.authenticatorPro.appName); + expect(token1.origin!.source, TokenOriginSourceType.backupFile); + final hotpToken = token1 as HOTPToken; + expect(hotpToken.secret, 'BBBBBBBB'); + expect(hotpToken.algorithm, Algorithms.SHA1); + expect(hotpToken.digits, 6); + expect(hotpToken.counter, 0); +} + +void _testAuthenticatorProImportFileProcessor() { + group('Authenticator ProImport File Processor Test', () { + const processor = AuthenticatorProImportFileProcessor(); + group('json', () { + group('encryped', () {}); + group('plain', () { + // Arrange + const byteDataString = + '[123, 34, 65, 117, 116, 104, 101, 110, 116, 105, 99, 97, 116, 111, 114, 115, 34, 58, 91, 123, 34, 84, 121, 112, 101, 34, 58, 50, 44, 34, 73, 99, 111, 110, 34, 58, 110, 117, 108, 108, 44, 34, 73, 115, 115, 117, 101, 114, 34, 58, 34, 84, 101, 115, 116, 49, 34, 44, 34, 85, 115, 101, 114, 110, 97, 109, 101, 34, 58, 34, 84, 101, 115, 116, 49, 34, 44, 34, 83, 101, 99, 114, 101, 116, 34, 58, 34, 65, 65, 65, 65, 65, 65, 65, 65, 34, 44, 34, 80, 105,' + '110, 34, 58, 110, 117, 108, 108, 44, 34, 65, 108, 103, 111, 114, 105, 116, 104, 109, 34, 58, 48, 44, 34, 68, 105, 103, 105, 116, 115, 34, 58, 54, 44, 34, 80, 101, 114, 105, 111, 100, 34, 58, 51, 48, 44, 34, 67, 111, 117, 110, 116, 101, 114, 34, 58, 48, 44, 34, 67, 111, 112, 121, 67, 111, 117, 110, 116, 34, 58, 48, 44, 34, 82, 97, 110, 107, 105, 110, 103, 34, 58, 48, 125, 44, 123, 34, 84, 121, 112, 101, 34, 58, 49, 44, 34, 73, 99, 111, 110, 34,' + '58, 110, 117, 108, 108, 44, 34, 73, 115, 115, 117, 101, 114, 34, 58, 34, 84, 101, 115, 116, 50, 34, 44, 34, 85, 115, 101, 114, 110, 97, 109, 101, 34, 58, 34, 84, 101, 115, 116, 50, 34, 44, 34, 83, 101, 99, 114, 101, 116, 34, 58, 34, 66, 66, 66, 66, 66, 66, 66, 66, 34, 44, 34, 80, 105, 110, 34, 58, 110, 117, 108, 108, 44, 34, 65, 108, 103, 111, 114, 105, 116, 104, 109, 34, 58, 48, 44, 34, 68, 105, 103, 105, 116, 115, 34, 58, 54, 44, 34, 80,' + '101, 114, 105, 111, 100, 34, 58, 51, 48, 44, 34, 67, 111, 117, 110, 116, 101, 114, 34, 58, 48, 44, 34, 67, 111, 112, 121, 67, 111, 117, 110, 116, 34, 58, 48, 44, 34, 82, 97, 110, 107, 105, 110, 103, 34, 58, 48, 125, 93, 44, 34, 67, 97, 116, 101, 103, 111, 114, 105, 101, 115, 34, 58, 91, 93, 44, 34, 65, 117, 116, 104, 101, 110, 116, 105, 99, 97, 116, 111, 114, 67, 97, 116, 101, 103, 111, 114, 105, 101, 115, 34, 58, 91, 93, 44, 34, 67, 117, 115, 116,' + '111, 109, 73, 99, 111, 110, 115, 34, 58, 91, 93, 125]'; + final byteData = Uint8List.fromList((jsonDecode(byteDataString) as List).cast()); + final XFile file = XFile.fromData(byteData, name: 'auth_pro_plain.json'); + group('fileIsValid', () { + test('isTrue', () async { + // Act + final result = await processor.fileIsValid(file); + // Assert + expect(result, isTrue); + }); + test('isFalse', () async { + // Arrange + final byteData = Uint8List.fromList((jsonDecode(byteDataString) as List).cast()..removeLast()); + final XFile file = XFile.fromData(byteData, name: 'auth_pro_plain_invalid.json'); + + // Act + final result = await processor.fileIsValid(file); + // Assert + expect(result, isFalse); + }); + }); + + test('fileNeedsPassword', () async { + // Act + final result = await processor.fileNeedsPassword(file); + // Assert + expect(result, isFalse); + }); + test('processFile', () async { + // Act + final results = await processor.processFile(file); + // Assert + _assertSuccessResults(results); + }); + }); + }); + group('html plain', () { + // Arrange + const htmlFileByteString = + '[60, 33, 100, 111, 99, 116, 121, 112, 101, 32, 104, 116, 109, 108, 62, 10, 60, 104, 116, 109, 108, 62, 10, 32, 32, 32, 32, 60, 104, 101, 97, 100, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 109, 101, 116, 97, 32, 99, 104, 97, 114, 115, 101, 116, 61, 34, 117, 116, 102, 45, 56, 34, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 105, 116, 108, 101, 62, 65, 117, 116, 104, 101, 110, 116, 105, 99, 97, 116, 111, 114, 32, 80, 114, 111, 32, 66, 97,' + '99, 107, 117, 112, 60, 47, 116, 105, 116, 108, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 115, 116, 121, 108, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 100, 121, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 110, 116, 45, 102, 97, 109, 105, 108, 121, 58, 32, 115, 97, 110, 115, 45, 115, 101, 114, 105, 102, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,' + '32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 97, 98, 108, 101, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 119, 105, 100, 116, 104, 58, 32, 49, 48, 48, 37, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 108, 97, 112, 115, 101, 58, 32, 99, 111, 108, 108, 97, 112,' + '115, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 97, 98, 108, 101, 32, 116, 104, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 101, 120, 116, 45, 97, 108, 105, 103, 110, 58, 32, 108, 101, 102, 116, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,' + '125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 97, 98, 108, 101, 32, 116, 114, 58, 110, 111, 116, 40, 58, 108, 97, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 41, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 98, 111, 116, 116, 111, 109, 58, 32, 115, 111, 108, 105, 100, 32, 49, 112, 120, 32, 35, 101,' + '101, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 97, 98, 108, 101, 32, 116, 114, 58, 102, 105, 114, 115, 116, 45, 111, 102, 45, 116, 121, 112, 101, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 98, 111, 114, 100, 101, 114, 45, 99, 111, 108, 111, 114, 58, 32, 35, 48,' + '48, 48, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 116, 97, 98, 108, 101, 32, 116, 104, 44, 32, 116, 97, 98, 108, 101, 32, 116, 100, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 112, 97, 100, 100, 105, 110, 103, 58, 32, 56, 112, 120, 59, 10, 32, 32, 32, 32, 32, 32,' + '32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 115, 116, 121, 108, 101, 62, 10, 32, 32, 32, 32, 60, 47, 104, 101, 97, 100, 62, 10, 32, 32, 32, 32, 60, 98, 111, 100, 121, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 97, 98, 108, 101, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 114, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,' + '32, 32, 32, 32, 32, 32, 60, 116, 104, 62, 73, 115, 115, 117, 101, 114, 60, 47, 116, 104, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 104, 62, 85, 115, 101, 114, 110, 97, 109, 101, 60, 47, 116, 104, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 104, 62, 79, 84, 80, 32, 65, 117, 116, 104, 32, 85, 82, 73, 60, 47, 116, 104, 62, 10, 32, 32, 32, 32, 32, 32,' + '32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 104, 62, 81, 82, 32, 67, 111, 100, 101, 60, 47, 116, 104, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 116, 114, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 116, 114, 62, 10, 32, 32, 32, 32, 60, 116, 100, 62, 84, 101, 115, 116, 49, 60, 47, 116, 100, 62, 10, 32, 32, 32, 32, 60, 116,' + '100, 62, 84, 101, 115, 116, 49, 60, 47, 116, 100, 62, 10, 32, 32, 32, 32, 60, 116, 100, 62, 60, 99, 111, 100, 101, 62, 111, 116, 112, 97, 117, 116, 104, 58, 47, 47, 116, 111, 116, 112, 47, 84, 101, 115, 116, 49, 37, 51, 65, 84, 101, 115, 116, 49, 63, 115, 101, 99, 114, 101, 116, 61, 65, 65, 65, 65, 65, 65, 65, 65, 38, 105, 115, 115, 117, 101, 114, 61, 84, 101, 115, 116, 49, 60, 47, 99, 111, 100, 101, 62, 60, 47, 116, 100, 62, 10, 32, 32, 32,' + '32, 60, 116, 100, 62, 60, 105, 109, 103, 32, 115, 114, 99, 61, 34, 100, 97, 116, 97, 58, 105, 109, 97, 103, 101, 47, 112, 110, 103, 59, 98, 97, 115, 101, 54, 52, 44, 105, 86, 66, 79, 82, 119, 48, 75, 71, 103, 111, 65, 65, 65, 65, 78, 83, 85, 104, 69, 85, 103, 65, 65, 65, 76, 81, 65, 65, 65, 67, 48, 65, 81, 65, 65, 65, 65, 65, 86, 116, 106, 117, 102, 65, 65, 65, 66, 99, 107, 108, 69, 81, 86, 82, 52, 110, 79, 50, 88, 85, 90, 76,' + '67, 77, 65, 120, 68, 100, 81, 80, 102, 47, 53, 97, 54, 103, 98, 72, 107, 108, 116, 73, 100, 47, 108, 98, 119, 82, 101, 105, 85, 53, 115, 70, 77, 107, 79, 48, 111, 66, 118, 49, 50, 69, 68, 47, 43, 76, 81, 54, 103, 109, 113, 88, 82, 120, 100, 112, 112, 107, 78, 100, 99, 71, 68, 65, 118, 43, 77, 77, 79, 56, 53, 107, 78, 66, 54, 105, 53, 110, 118, 78, 99, 54, 111, 112, 100, 57, 83, 70, 79, 72, 69, 47, 111, 80, 74, 43, 76, 67, 116, 114,' + '107, 71, 51, 47, 105, 109, 101, 66, 75, 57, 77, 117, 52, 49, 85, 79, 67, 98, 54, 70, 121, 118, 116, 68, 85, 106, 55, 106, 86, 99, 52, 68, 84, 67, 87, 100, 118, 48, 105, 85, 117, 121, 52, 87, 116, 82, 113, 86, 70, 104, 84, 68, 76, 116, 101, 88, 109, 112, 113, 113, 97, 108, 86, 48, 65, 85, 81, 55, 72, 114, 67, 89, 47, 85, 107, 102, 74, 105, 47, 74, 90, 97, 108, 89, 99, 114, 70, 88, 88, 81, 75, 74, 99, 43, 90, 89, 100, 114, 98, 82,' + '83, 87, 85, 85, 53, 54, 88, 116, 98, 50, 77, 122, 65, 77, 66, 56, 49, 74, 114, 85, 98, 114, 56, 79, 56, 108, 87, 52, 118, 51, 54, 113, 116, 48, 86, 104, 104, 118, 115, 98, 82, 76, 105, 111, 53, 101, 74, 104, 106, 99, 52, 69, 49, 119, 77, 79, 47, 99, 49, 121, 110, 50, 99, 122, 109, 114, 116, 66, 86, 110, 47, 115, 105, 120, 88, 87, 86, 107, 54, 79, 113, 57, 83, 54, 80, 99, 114, 49, 82, 53, 48, 70, 98, 72, 90, 57, 43, 109, 79, 74,' + '110, 118, 118, 115, 111, 114, 84, 66, 110, 55, 82, 80, 87, 110, 112, 53, 49, 108, 101, 76, 108, 90, 111, 87, 55, 74, 89, 67, 114, 98, 119, 108, 120, 90, 49, 55, 74, 116, 48, 85, 112, 101, 70, 108, 117, 73, 87, 52, 115, 102, 71, 122, 50, 53, 83, 99, 90, 98, 104, 43, 49, 110, 56, 75, 78, 88, 89, 102, 53, 72, 118, 120, 87, 66, 80, 105, 65, 113, 121, 120, 88, 53, 71, 66, 84, 57, 90, 53, 79, 99, 120, 47, 74, 48, 113, 79, 65, 79, 88, 74,' + '90, 55, 114, 53, 88, 75, 53, 118, 87, 108, 90, 99, 85, 120, 49, 98, 86, 108, 113, 52, 69, 53, 114, 107, 83, 119, 106, 51, 53, 49, 102, 57, 43, 103, 72, 80, 47, 72, 77, 106, 57, 48, 116, 120, 120, 107, 54, 122, 116, 117, 99, 53, 122, 80, 56, 88, 88, 75, 75, 122, 75, 80, 85, 122, 102, 47, 107, 47, 57, 110, 55, 56, 98, 80, 47, 52, 108, 47, 103, 65, 80, 48, 119, 89, 80, 99, 50, 108, 73, 67, 103, 65, 65, 65, 65, 66, 74, 82, 85, 53,' + '69, 114, 107, 74, 103, 103, 103, 61, 61, 34, 62, 60, 47, 116, 100, 62, 10, 60, 47, 116, 114, 62, 60, 116, 114, 62, 10, 32, 32, 32, 32, 60, 116, 100, 62, 84, 101, 115, 116, 50, 60, 47, 116, 100, 62, 10, 32, 32, 32, 32, 60, 116, 100, 62, 84, 101, 115, 116, 50, 60, 47, 116, 100, 62, 10, 32, 32, 32, 32, 60, 116, 100, 62, 60, 99, 111, 100, 101, 62, 111, 116, 112, 97, 117, 116, 104, 58, 47, 47, 104, 111, 116, 112, 47, 84, 101, 115, 116, 50, 37,' + '51, 65, 84, 101, 115, 116, 50, 63, 115, 101, 99, 114, 101, 116, 61, 66, 66, 66, 66, 66, 66, 66, 66, 38, 105, 115, 115, 117, 101, 114, 61, 84, 101, 115, 116, 50, 38, 99, 111, 117, 110, 116, 101, 114, 61, 48, 60, 47, 99, 111, 100, 101, 62, 60, 47, 116, 100, 62, 10, 32, 32, 32, 32, 60, 116, 100, 62, 60, 105, 109, 103, 32, 115, 114, 99, 61, 34, 100, 97, 116, 97, 58, 105, 109, 97, 103, 101, 47, 112, 110, 103, 59, 98, 97, 115, 101, 54, 52, 44, 105,' + '86, 66, 79, 82, 119, 48, 75, 71, 103, 111, 65, 65, 65, 65, 78, 83, 85, 104, 69, 85, 103, 65, 65, 65, 77, 81, 65, 65, 65, 68, 69, 65, 81, 65, 65, 65, 65, 68, 111, 51, 98, 80, 71, 65, 65, 65, 66, 118, 85, 108, 69, 81, 86, 82, 52, 110, 79, 50, 88, 87, 50, 52, 68, 81, 81, 103, 69, 117, 81, 72, 51, 118, 50, 88, 102, 103, 70, 68, 78, 98, 71, 120, 76, 121, 100, 56, 103, 53, 83, 78, 106, 120, 52, 56, 112, 83, 55, 115, 119, 84,' + '85, 79, 105, 102, 108, 109, 75, 102, 47, 73, 51, 83, 69, 83, 107, 112, 72, 52, 116, 47, 112, 84, 104, 116, 43, 115, 107, 43, 53, 107, 90, 121, 115, 113, 53, 73, 84, 89, 50, 83, 80, 82, 43, 104, 102, 113, 76, 115, 104, 47, 101, 87, 67, 73, 100, 90, 52, 100, 52, 111, 116, 119, 107, 118, 102, 113, 57, 48, 53, 116, 55, 112, 69, 103, 108, 112, 53, 102, 99, 82, 51, 122, 109, 43, 104, 53, 120, 99, 79, 47, 114, 81, 122, 118, 51, 105, 74, 99, 47, 111,' + '56, 88, 79, 54, 50, 99, 116, 88, 67, 77, 106, 106, 69, 81, 97, 82, 67, 104, 43, 116, 48, 70, 81, 117, 116, 103, 74, 55, 113, 79, 108, 114, 49, 100, 108, 51, 83, 82, 57, 90, 118, 49, 48, 100, 67, 76, 79, 99, 80, 106, 51, 67, 100, 99, 116, 82, 67, 56, 113, 114, 71, 112, 121, 101, 112, 43, 107, 110, 97, 104, 108, 51, 107, 85, 99, 116, 113, 101, 73, 68, 100, 73, 112, 76, 67, 52, 116, 89, 107, 119, 114, 80, 106, 100, 73, 79, 85, 82, 120, 72,' + '50, 105, 108, 114, 54, 57, 72, 56, 86, 101, 74, 120, 108, 118, 108, 113, 120, 55, 70, 98, 66, 68, 98, 69, 73, 82, 102, 70, 75, 101, 88, 51, 122, 86, 51, 107, 49, 103, 88, 52, 118, 122, 107, 87, 68, 118, 68, 50, 105, 68, 67, 89, 90, 79, 57, 72, 66, 107, 113, 89, 52, 79, 77, 76, 43, 65, 84, 114, 113, 116, 119, 105, 104, 99, 73, 47, 107, 112, 119, 97, 70, 66, 80, 117, 67, 116, 69, 122, 121, 89, 82, 114, 120, 71, 70, 106, 117, 89, 112, 51,' + '88, 83, 106, 113, 103, 88, 105, 111, 115, 76, 57, 69, 73, 110, 78, 102, 73, 102, 85, 101, 69, 80, 118, 89, 66, 98, 43, 69, 107, 118, 69, 48, 119, 80, 57, 110, 69, 109, 73, 98, 79, 89, 67, 56, 82, 90, 70, 106, 77, 110, 97, 77, 113, 73, 50, 121, 74, 115, 116, 89, 66, 81, 85, 49, 112, 80, 82, 113, 52, 81, 100, 55, 105, 72, 111, 115, 101, 84, 48, 53, 83, 70, 88, 121, 97, 69, 101, 117, 101, 83, 87, 47, 107, 120, 99, 100, 119, 110, 104, 87,' + '88, 122, 101, 48, 104, 108, 83, 55, 104, 77, 71, 104, 110, 66, 76, 112, 52, 111, 82, 82, 53, 49, 74, 57, 83, 54, 104, 122, 52, 53, 65, 47, 71, 107, 71, 121, 103, 88, 105, 51, 106, 83, 122, 107, 72, 83, 75, 98, 73, 78, 52, 65, 67, 55, 76, 65, 113, 72, 77, 77, 76, 108, 65, 99, 115, 82, 73, 111, 122, 105, 57, 97, 89, 109, 69, 76, 87, 47, 71, 73, 97, 116, 47, 106, 57, 105, 43, 100, 83, 98, 57, 97, 98, 107, 114, 53, 74, 105, 115, 57,' + '89, 55, 80, 53, 103, 89, 120, 53, 80, 119, 52, 119, 102, 75, 47, 76, 98, 86, 65, 80, 65, 84, 84, 66, 56, 102, 121, 103, 116, 108, 114, 103, 47, 121, 56, 47, 115, 109, 102, 73, 70, 57, 110, 49, 118, 57, 117, 48, 80, 48, 100, 72, 119, 65, 65, 65, 65, 66, 74, 82, 85, 53, 69, 114, 107, 74, 103, 103, 103, 61, 61, 34, 62, 60, 47, 116, 100, 62, 10, 60, 47, 116, 114, 62, 10, 32, 32, 32, 32, 32, 32, 32, 32, 60, 47, 116, 97, 98, 108, 101,' + '62, 10, 32, 32, 32, 32, 10, 32, 32, 32, 32, 60, 47, 98, 111, 100, 121, 62, 10, 60, 47, 104, 116, 109, 108, 62]'; + final byteData = Uint8List.fromList((jsonDecode(htmlFileByteString) as List).cast().toList()); + + final file = XFile.fromData(byteData, name: 'auth_pro_plain.html'); + + group('fileIsValid', () { + test('isTrue', () async { + // Act + final fileIsValid = await processor.fileIsValid(file); + // Assert + expect(fileIsValid, isTrue); + }); + test('isFalse', () async { + // Arrange + final byteData = Uint8List.fromList((jsonDecode(htmlFileByteString) as List).cast().toList()..removeAt(0)); + + final file = XFile.fromData(byteData, name: 'auth_pro_plain_invalid.html'); + // Act + final fileIsValid = await processor.fileIsValid(file); + // Assert + expect(fileIsValid, isFalse); + }); + }); + + test('fileNeedsPassword', () async { + // Act + final result = await processor.fileNeedsPassword(file); + // Assert + expect(result, false); + }); + + test('processFile', () async { + // Act + final results = await processor.processFile(file); + // Assert + _assertSuccessResults(results); + }); + }); + group('otpauth link list plain', () { + const uriListBytes = + '[111, 116, 112, 97, 117, 116, 104, 58, 47, 47, 116, 111, 116, 112, 47, 84, 101, 115, 116, 49, 37, 51, 65, 84, 101, 115, 116, 49, 63, 115, 101, 99, 114, 101, 116, 61, 65, 65, 65, 65, 65, 65, 65, 65, 38, 105, 115, 115, 117, 101, 114, 61, 84, 101, 115, 116, 49, 10, 111, 116, 112, 97, 117, 116, 104, 58, 47, 47, 104, 111, 116, 112, 47, 84, 101, 115, 116, 50, 37, 51, 65, 84, 101, 115, 116, 50, 63, 115, 101, 99, 114, 101, 116, 61, 66, 66, 66, 66, 66, 66,' + '66, 66, 38, 105, 115, 115, 117, 101, 114, 61, 84, 101, 115, 116, 50, 38, 99, 111, 117, 110, 116, 101, 114, 61, 48, 10]'; + final byteData = Uint8List.fromList((jsonDecode(uriListBytes) as List).cast().toList()); + + final file = XFile.fromData(byteData, name: 'auth_pro_plain.txt'); + + group('fileIsValid', () { + test('isTrue', () async { + // Act + final fileIsValid = await processor.fileIsValid(file); + // Assert + expect(fileIsValid, isTrue); + }); + test('isFalse', () async { + // Act + final byteData = Uint8List.fromList((jsonDecode(uriListBytes) as List).cast().toList()..removeWhere((uint) => uint == 58)); // 58 is ':' + + final file = XFile.fromData(byteData, name: 'auth_pro_plain_invalid.txt'); + final fileIsValid = await processor.fileIsValid(file); + // Assert + expect(fileIsValid, isFalse); + }); + }); + + test('fileNeedsPassword', () async { + // Act + final result = await processor.fileNeedsPassword(file); + // Assert + expect(result, isFalse); + }); + test('processFile', () async { + // Act + final results = await processor.processFile(file); + // Assert + _assertSuccessResults(results); + }); + }); + }); +} diff --git a/test/unit_test/processors/token_import_file_processor/free_otp_plus_file_processor_test.dart b/test/unit_test/processors/token_import_file_processor/free_otp_plus_file_processor_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/processors/token_import_file_processor/free_otp_plus_file_processor_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/processors/token_import_file_processor/free_otp_plus_import_file_processor_test.dart b/test/unit_test/processors/token_import_file_processor/free_otp_plus_import_file_processor_test.dart new file mode 100644 index 000000000..c3e4a2582 --- /dev/null +++ b/test/unit_test/processors/token_import_file_processor/free_otp_plus_import_file_processor_test.dart @@ -0,0 +1,138 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:file_selector/file_selector.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; +import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; +import 'package:privacyidea_authenticator/model/enums/token_types.dart'; +import 'package:privacyidea_authenticator/model/processor_result.dart'; +import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; +import 'package:privacyidea_authenticator/model/tokens/token.dart'; +import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; +import 'package:privacyidea_authenticator/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart'; +import 'package:privacyidea_authenticator/utils/token_import_origins.dart'; + +void main() { + _testFreeOtpPlusImportFileProcessor(); +} + +void _assertSuccessResults(List> results) { + expect(results.length, equals(2)); + + final result0 = results[0]; + expect(result0, isA()); + final token0 = result0.asSuccess!.resultData; + expect(token0.label, 'Test2'); + expect(token0.issuer, 'Test2'); + expect(token0.type, TokenTypes.HOTP.name); + expect(token0, isA()); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, TokenImportOrigins.freeOtpPlus.appName); + expect(token0.origin!.source, TokenOriginSourceType.backupFile); + final hotpToken = token0 as HOTPToken; + expect(hotpToken.secret, 'BBBBBBBB'); + expect(hotpToken.algorithm, Algorithms.SHA1); + expect(hotpToken.digits, 8); + expect(hotpToken.counter, 5); + expect(hotpToken.otpValue, equals('83718223')); + final result1 = results[1]; + expect(result1, isA()); + final token1 = result1.asSuccess!.resultData; + expect(token1.label, 'Test1'); + expect(token1.issuer, 'Test1'); + expect(token1.type, TokenTypes.TOTP.name); + expect(token1, isA()); + expect(token1.origin, isNotNull); + expect(token1.origin!.appName, TokenImportOrigins.freeOtpPlus.appName); + expect(token1.origin!.source, TokenOriginSourceType.backupFile); + final totpToken = token1 as TOTPToken; + expect(totpToken.secret, 'AAAAAAAA'); + expect(totpToken.algorithm, Algorithms.SHA256); + expect(totpToken.digits, 8); + expect(totpToken.period, 60); + expect(totpToken.otpFromTime(DateTime.fromMillisecondsSinceEpoch(1713519600602)), equals('46107496')); +} + +void _testFreeOtpPlusImportFileProcessor() { + group('Free Otp Plus File Processor Test', () { + const processor = FreeOtpPlusImportFileProcessor(); + // No encryption or password protection is used in the FreeOTP+ App at all. + group('JSON plain', () { + // Arrange + const jsonFileBytesString = + '[123, 34, 116, 111, 107, 101, 110, 79, 114, 100, 101, 114, 34, 58, 91, 34, 84, 101, 115, 116, 50, 58, 84, 101, 115, 116, 50, 34, 44, 34, 84, 101, 115, 116, 49, 58, 84, 101, 115, 116, 49, 34, 93, 44, 34, 116, 111, 107, 101, 110, 115, 34, 58, 91, 123, 34, 97, 108, 103, 111, 34, 58, 34, 83, 72, 65, 49, 34, 44, 34, 99, 111, 117, 110, 116, 101, 114, 34, 58, 52, 44, 34, 100, 105, 103, 105, 116, 115, 34, 58, 56, 44, 34, 105, 115, 115, 117, 101, 114, 69,' + '120, 116, 34, 58, 34, 84, 101, 115, 116, 50, 34, 44, 34, 108, 97, 98, 101, 108, 34, 58, 34, 84, 101, 115, 116, 50, 34, 44, 34, 112, 101, 114, 105, 111, 100, 34, 58, 51, 48, 44, 34, 115, 101, 99, 114, 101, 116, 34, 58, 91, 56, 44, 54, 54, 44, 49, 54, 44, 45, 49, 50, 52, 44, 51, 51, 93, 44, 34, 116, 121, 112, 101, 34, 58, 34, 72, 79, 84, 80, 34, 125, 44, 123, 34, 97, 108, 103, 111, 34, 58, 34, 83, 72, 65, 50, 53, 54, 34, 44, 34,' + '99, 111, 117, 110, 116, 101, 114, 34, 58, 48, 44, 34, 100, 105, 103, 105, 116, 115, 34, 58, 56, 44, 34, 105, 115, 115, 117, 101, 114, 69, 120, 116, 34, 58, 34, 84, 101, 115, 116, 49, 34, 44, 34, 108, 97, 98, 101, 108, 34, 58, 34, 84, 101, 115, 116, 49, 34, 44, 34, 112, 101, 114, 105, 111, 100, 34, 58, 54, 48, 44, 34, 115, 101, 99, 114, 101, 116, 34, 58, 91, 48, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 116, 121, 112, 101, 34, 58, 34, 84,' + '79, 84, 80, 34, 125, 93, 125]'; + final jsonFileBytes = (jsonDecode(jsonFileBytesString) as List).cast(); + final jsonFile = XFile.fromData(Uint8List.fromList(jsonFileBytes), name: 'Free_OTP_Plus_plain.json'); + + group('fileIsValid', () { + test('isTrue', () async { + // Act + final fileIsValid = await processor.fileIsValid(jsonFile); + // Assert + expect(fileIsValid, isTrue); + }); + test('isFalse', () async { + // Arrange + final jsonFileBytes = (jsonDecode(jsonFileBytesString) as List).cast()..removeLast(); + final jsonFileInvalid = XFile.fromData(Uint8List.fromList(jsonFileBytes), name: 'Free_OTP_Plus_plain_invalid.json'); + // Act + final fileIsValid = await processor.fileIsValid(jsonFileInvalid); + // Assert + expect(fileIsValid, isFalse); + }); + }); + test('fileNeedsPassword', () async { + // Act + final fileNeedsPassword = await processor.fileNeedsPassword(jsonFile); + // Assert + expect(fileNeedsPassword, isFalse); + }); + test('processFile', () async { + // Act + final results = await processor.processFile(jsonFile); + // Assert + _assertSuccessResults(results); + }); + }); + group('Uri List plain', () { + const uriListByteString = + '[111, 116, 112, 97, 117, 116, 104, 58, 47, 47, 104, 111, 116, 112, 47, 84, 101, 115, 116, 50, 37, 51, 65, 84, 101, 115, 116, 50, 63, 115, 101, 99, 114, 101, 116, 61, 66, 66, 66, 66, 66, 66, 66, 66, 38, 97, 108, 103, 111, 114, 105, 116, 104, 109, 61, 83, 72, 65, 49, 38, 100, 105, 103, 105, 116, 115, 61, 56, 38, 112, 101, 114, 105, 111, 100, 61, 51, 48, 38, 99, 111, 117, 110, 116, 101, 114, 61, 53, 10, 111, 116, 112, 97, 117, 116, 104, 58, 47, 47, 116,' + '111, 116, 112, 47, 84, 101, 115, 116, 49, 37, 51, 65, 84, 101, 115, 116, 49, 63, 115, 101, 99, 114, 101, 116, 61, 65, 65, 65, 65, 65, 65, 65, 65, 38, 97, 108, 103, 111, 114, 105, 116, 104, 109, 61, 83, 72, 65, 50, 53, 54, 38, 100, 105, 103, 105, 116, 115, 61, 56, 38, 112, 101, 114, 105, 111, 100, 61, 54, 48, 10]'; + final uriListBytes = (jsonDecode(uriListByteString) as List).cast(); + final uriListFile = XFile.fromData(Uint8List.fromList(uriListBytes), name: 'Free_OTP_Plus_uri_list_plain.txt'); + group('fileIsValid', () { + test('isTrue', () async { + // Act + final fileIsValid = await processor.fileIsValid(uriListFile); + // Assert + expect(fileIsValid, isTrue); + }); + test('isFalse', () async { + // Arrange + final uriListBytes = (jsonDecode(uriListByteString) as List).cast()..removeAt(0); + final uriListFileInvalid = XFile.fromData(Uint8List.fromList(uriListBytes), name: 'Free_OTP_Plus_uri_list_plain_invalid.txt'); + // Act + final fileIsValid = await processor.fileIsValid(uriListFileInvalid); + // Assert + expect(fileIsValid, isFalse); + }); + }); + test('fileNeedsPassword', () async { + // Act + final fileNeedsPassword = await processor.fileNeedsPassword(uriListFile); + // Assert + expect(fileNeedsPassword, isFalse); + }); + test('processFile', () async { + // Act + final results = await processor.processFile(uriListFile); + // Assert + _assertSuccessResults(results); + }); + }); + }); +} diff --git a/test/unit_test/processors/token_import_file_processor/privacyidea_authenticator_import_file_processor_test.dart b/test/unit_test/processors/token_import_file_processor/privacyidea_authenticator_import_file_processor_test.dart index 8b1378917..283240836 100644 --- a/test/unit_test/processors/token_import_file_processor/privacyidea_authenticator_import_file_processor_test.dart +++ b/test/unit_test/processors/token_import_file_processor/privacyidea_authenticator_import_file_processor_test.dart @@ -1 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +void main() { + _testPrivacyideaAuthenticatorImportFileProcessor(); +} + +void _testPrivacyideaAuthenticatorImportFileProcessor() { + group('Privacyidea Authenticator Import File Processor', () { + test('', () {}); + }); +} diff --git a/test/unit_test/processors/token_import_file_processor/token_import_file_processor_interface_test.dart b/test/unit_test/processors/token_import_file_processor/token_import_file_processor_interface_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/processors/token_import_file_processor/token_import_file_processor_interface_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/processors/token_import_file_processor/two_fas_authenticator_import_file_processor_test.dart b/test/unit_test/processors/token_import_file_processor/two_fas_authenticator_import_file_processor_test.dart new file mode 100644 index 000000000..aa8fe6077 --- /dev/null +++ b/test/unit_test/processors/token_import_file_processor/two_fas_authenticator_import_file_processor_test.dart @@ -0,0 +1,186 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:file_selector/file_selector.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; +import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; +import 'package:privacyidea_authenticator/model/enums/token_types.dart'; +import 'package:privacyidea_authenticator/model/processor_result.dart'; +import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; +import 'package:privacyidea_authenticator/model/tokens/steam_token.dart'; +import 'package:privacyidea_authenticator/model/tokens/token.dart'; +import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; +import 'package:privacyidea_authenticator/processors/token_import_file_processor/two_fas_import_file_processor.dart'; +import 'package:privacyidea_authenticator/utils/token_import_origins.dart'; + +void main() { + _testTwoFasImportFileProcessor(); +} + +void _assertSuccessResults(List> results) { + expect(results.length, equals(3)); + + final result0 = results[0]; + expect(result0, isA()); + final token0 = result0.asSuccess!.resultData; + expect(token0.label, ''); + expect(token0.issuer, 'Test1'); + expect(token0.type, TokenTypes.TOTP.name); + expect(token0, isA()); + expect(token0.origin, isNotNull); + expect(token0.origin!.appName, TokenImportOrigins.twoFasAuthenticator.appName); + expect(token0.origin!.source, TokenOriginSourceType.backupFile); + final totpToken = token0 as TOTPToken; + expect(totpToken.secret, equals('AAAAAAAA')); + expect(totpToken.algorithm, Algorithms.SHA256); + expect(totpToken.digits, 8); + expect(totpToken.period, 60); + expect(totpToken.otpFromTime(DateTime.fromMillisecondsSinceEpoch(1713519600602)), equals('46107496')); + final result1 = results[1]; + expect(result1, isA()); + final token1 = result1.asSuccess!.resultData; + expect(token1.label, ''); + expect(token1.issuer, 'Test2'); + expect(token1.type, TokenTypes.HOTP.name); + expect(token1, isA()); + expect(token1.origin, isNotNull); + expect(token1.origin!.appName, TokenImportOrigins.twoFasAuthenticator.appName); + expect(token1.origin!.source, TokenOriginSourceType.backupFile); + final hotpToken = token1 as HOTPToken; + expect(hotpToken.secret, equals('BBBBBBBB')); + expect(hotpToken.algorithm, Algorithms.SHA1); + expect(hotpToken.digits, 6); + expect(hotpToken.counter, 5); + expect(hotpToken.otpValue, equals('718223')); + final result2 = results[2]; + expect(result2, isA()); + final token2 = result2.asSuccess!.resultData; + expect(token2.label, ''); + expect(token2.issuer, 'SteamTest'); + expect(token2.type, TokenTypes.STEAM.name); + expect(token2, isA()); + expect(token2.origin, isNotNull); + expect(token2.origin!.appName, TokenImportOrigins.twoFasAuthenticator.appName); + expect(token2.origin!.source, TokenOriginSourceType.backupFile); + final steamToken = token2 as SteamToken; + expect(steamToken.secret, equals('CCCCCCCC')); +} + +void _testTwoFasImportFileProcessor() { + group('Two Fas Import File Processor Test', () { + // Arrange + const processor = TwoFasAuthenticatorImportFileProcessor(); + group('JSON', () { + const jsonFileBytesString = + '[123, 34, 115, 101, 114, 118, 105, 99, 101, 115, 34, 58, 91, 123, 34, 110, 97, 109, 101, 34, 58, 34, 84, 101, 115, 116, 49, 34, 44, 34, 115, 101, 99, 114, 101, 116, 34, 58, 34, 65, 65, 65, 65, 65, 65, 65, 65, 34, 44, 34, 117, 112, 100, 97, 116, 101, 100, 65, 116, 34, 58, 49, 55, 49, 51, 53, 50, 54, 55, 55, 48, 56, 55, 57, 44, 34, 111, 116, 112, 34, 58, 123, 34, 108, 97, 98, 101, 108, 34, 58, 34, 34, 44, 34, 97, 99, 99, 111, 117, 110,' + '116, 34, 58, 34, 34, 44, 34, 100, 105, 103, 105, 116, 115, 34, 58, 56, 44, 34, 112, 101, 114, 105, 111, 100, 34, 58, 54, 48, 44, 34, 97, 108, 103, 111, 114, 105, 116, 104, 109, 34, 58, 34, 83, 72, 65, 50, 53, 54, 34, 44, 34, 116, 111, 107, 101, 110, 84, 121, 112, 101, 34, 58, 34, 84, 79, 84, 80, 34, 44, 34, 115, 111, 117, 114, 99, 101, 34, 58, 34, 77, 97, 110, 117, 97, 108, 34, 125, 44, 34, 111, 114, 100, 101, 114, 34, 58, 123, 34, 112, 111,' + '115, 105, 116, 105, 111, 110, 34, 58, 48, 125, 44, 34, 105, 99, 111, 110, 34, 58, 123, 34, 115, 101, 108, 101, 99, 116, 101, 100, 34, 58, 34, 76, 97, 98, 101, 108, 34, 44, 34, 108, 97, 98, 101, 108, 34, 58, 123, 34, 116, 101, 120, 116, 34, 58, 34, 84, 69, 34, 44, 34, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 67, 111, 108, 111, 114, 34, 58, 34, 66, 114, 111, 119, 110, 34, 125, 44, 34, 105, 99, 111, 110, 67, 111, 108, 108, 101, 99, 116, 105, 111,' + '110, 34, 58, 123, 34, 105, 100, 34, 58, 34, 97, 53, 98, 51, 102, 98, 54, 53, 45, 52, 101, 99, 53, 45, 52, 51, 101, 54, 45, 56, 101, 99, 49, 45, 52, 57, 101, 50, 52, 99, 97, 57, 101, 55, 97, 100, 34, 125, 125, 125, 44, 123, 34, 110, 97, 109, 101, 34, 58, 34, 84, 101, 115, 116, 50, 34, 44, 34, 115, 101, 99, 114, 101, 116, 34, 58, 34, 66, 66, 66, 66, 66, 66, 66, 66, 34, 44, 34, 117, 112, 100, 97, 116, 101, 100, 65, 116, 34, 58, 49,' + '55, 49, 51, 53, 50, 54, 57, 57, 54, 48, 55, 57, 44, 34, 111, 116, 112, 34, 58, 123, 34, 108, 97, 98, 101, 108, 34, 58, 34, 34, 44, 34, 97, 99, 99, 111, 117, 110, 116, 34, 58, 34, 34, 44, 34, 100, 105, 103, 105, 116, 115, 34, 58, 54, 44, 34, 112, 101, 114, 105, 111, 100, 34, 58, 51, 48, 44, 34, 97, 108, 103, 111, 114, 105, 116, 104, 109, 34, 58, 34, 83, 72, 65, 49, 34, 44, 34, 99, 111, 117, 110, 116, 101, 114, 34, 58, 53, 44, 34, 116,' + '111, 107, 101, 110, 84, 121, 112, 101, 34, 58, 34, 72, 79, 84, 80, 34, 44, 34, 115, 111, 117, 114, 99, 101, 34, 58, 34, 77, 97, 110, 117, 97, 108, 34, 125, 44, 34, 111, 114, 100, 101, 114, 34, 58, 123, 34, 112, 111, 115, 105, 116, 105, 111, 110, 34, 58, 49, 125, 44, 34, 105, 99, 111, 110, 34, 58, 123, 34, 115, 101, 108, 101, 99, 116, 101, 100, 34, 58, 34, 76, 97, 98, 101, 108, 34, 44, 34, 108, 97, 98, 101, 108, 34, 58, 123, 34, 116, 101, 120, 116,' + '34, 58, 34, 84, 69, 34, 44, 34, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 67, 111, 108, 111, 114, 34, 58, 34, 79, 114, 97, 110, 103, 101, 34, 125, 44, 34, 105, 99, 111, 110, 67, 111, 108, 108, 101, 99, 116, 105, 111, 110, 34, 58, 123, 34, 105, 100, 34, 58, 34, 97, 53, 98, 51, 102, 98, 54, 53, 45, 52, 101, 99, 53, 45, 52, 51, 101, 54, 45, 56, 101, 99, 49, 45, 52, 57, 101, 50, 52, 99, 97, 57, 101, 55, 97, 100, 34, 125, 125, 125, 44,' + '123, 34, 110, 97, 109, 101, 34, 58, 34, 83, 116, 101, 97, 109, 84, 101, 115, 116, 34, 44, 34, 115, 101, 99, 114, 101, 116, 34, 58, 34, 67, 67, 67, 67, 67, 67, 67, 67, 34, 44, 34, 117, 112, 100, 97, 116, 101, 100, 65, 116, 34, 58, 49, 55, 49, 51, 53, 50, 56, 55, 52, 56, 56, 48, 53, 44, 34, 111, 116, 112, 34, 58, 123, 34, 108, 97, 98, 101, 108, 34, 58, 34, 34, 44, 34, 97, 99, 99, 111, 117, 110, 116, 34, 58, 34, 34, 44, 34, 105, 115,' + '115, 117, 101, 114, 34, 58, 34, 83, 116, 101, 97, 109, 34, 44, 34, 100, 105, 103, 105, 116, 115, 34, 58, 53, 44, 34, 112, 101, 114, 105, 111, 100, 34, 58, 51, 48, 44, 34, 97, 108, 103, 111, 114, 105, 116, 104, 109, 34, 58, 34, 83, 72, 65, 49, 34, 44, 34, 116, 111, 107, 101, 110, 84, 121, 112, 101, 34, 58, 34, 83, 84, 69, 65, 77, 34, 44, 34, 115, 111, 117, 114, 99, 101, 34, 58, 34, 77, 97, 110, 117, 97, 108, 34, 125, 44, 34, 111, 114, 100, 101,' + '114, 34, 58, 123, 34, 112, 111, 115, 105, 116, 105, 111, 110, 34, 58, 50, 125, 44, 34, 105, 99, 111, 110, 34, 58, 123, 34, 115, 101, 108, 101, 99, 116, 101, 100, 34, 58, 34, 76, 97, 98, 101, 108, 34, 44, 34, 108, 97, 98, 101, 108, 34, 58, 123, 34, 116, 101, 120, 116, 34, 58, 34, 83, 84, 34, 44, 34, 98, 97, 99, 107, 103, 114, 111, 117, 110, 100, 67, 111, 108, 111, 114, 34, 58, 34, 76, 105, 103, 104, 116, 66, 108, 117, 101, 34, 125, 44, 34, 105, 99,' + '111, 110, 67, 111, 108, 108, 101, 99, 116, 105, 111, 110, 34, 58, 123, 34, 105, 100, 34, 58, 34, 97, 53, 98, 51, 102, 98, 54, 53, 45, 52, 101, 99, 53, 45, 52, 51, 101, 54, 45, 56, 101, 99, 49, 45, 52, 57, 101, 50, 52, 99, 97, 57, 101, 55, 97, 100, 34, 125, 125, 125, 93, 44, 34, 103, 114, 111, 117, 112, 115, 34, 58, 91, 93, 44, 34, 117, 112, 100, 97, 116, 101, 100, 65, 116, 34, 58, 49, 55, 49, 51, 53, 50, 56, 56, 49, 57, 54, 54, 48,' + '44, 34, 115, 99, 104, 101, 109, 97, 86, 101, 114, 115, 105, 111, 110, 34, 58, 52, 44, 34, 97, 112, 112, 86, 101, 114, 115, 105, 111, 110, 67, 111, 100, 101, 34, 58, 53, 48, 48, 48, 48, 49, 57, 44, 34, 97, 112, 112, 86, 101, 114, 115, 105, 111, 110, 78, 97, 109, 101, 34, 58, 34, 53, 46, 52, 46, 48, 34, 44, 34, 97, 112, 112, 79, 114, 105, 103, 105, 110, 34, 58, 34, 97, 110, 100, 114, 111, 105, 100, 34, 125]'; + final jsonFileBytes = (jsonDecode(jsonFileBytesString) as List).cast(); + final jsonFile = XFile.fromData(Uint8List.fromList(jsonFileBytes), name: 'Two_Fas_plain.json'); + group('plain', () { + group('fileIsValid', () { + test('isTrue', () async { + // Act + final fileIsValid = await processor.fileIsValid(jsonFile); + // Assert + expect(fileIsValid, isTrue); + }); + test('isFalse', () async { + // Arrange + final jsonFileBytes = (jsonDecode(jsonFileBytesString) as List).cast()..removeLast(); + final jsonFileInvalid = XFile.fromData(Uint8List.fromList(jsonFileBytes), name: 'Two_Fas_plain_invalid.json'); + // Act + final fileIsValid = await processor.fileIsValid(jsonFileInvalid); + // Assert + expect(fileIsValid, isFalse); + }); + }); + test('fileNeedsPassword', () async { + // Act + final fileNeedsPassword = await processor.fileNeedsPassword(jsonFile); + // Assert + expect(fileNeedsPassword, isFalse); + }); + test('processFile', () async { + // Act + final results = await processor.processFile(jsonFile); + // Assert + _assertSuccessResults(results); + }); + }); + group('encrypted', () { + const jsonFileBytesString = + '[123, 34, 115, 101, 114, 118, 105, 99, 101, 115, 34, 58, 91, 93, 44, 34, 103, 114, 111, 117, 112, 115, 34, 58, 91, 93, 44, 34, 117, 112, 100, 97, 116, 101, 100, 65, 116, 34, 58, 49, 55, 49, 51, 53, 50, 56, 56, 49, 50, 55, 48, 51, 44, 34, 115, 99, 104, 101, 109, 97, 86, 101, 114, 115, 105, 111, 110, 34, 58, 52, 44, 34, 97, 112, 112, 86, 101, 114, 115, 105, 111, 110, 67, 111, 100, 101, 34, 58, 53, 48, 48, 48, 48, 49, 57, 44, 34, 97, 112, 112,' + '86, 101, 114, 115, 105, 111, 110, 78, 97, 109, 101, 34, 58, 34, 53, 46, 52, 46, 48, 34, 44, 34, 97, 112, 112, 79, 114, 105, 103, 105, 110, 34, 58, 34, 97, 110, 100, 114, 111, 105, 100, 34, 44, 34, 115, 101, 114, 118, 105, 99, 101, 115, 69, 110, 99, 114, 121, 112, 116, 101, 100, 34, 58, 34, 110, 121, 115, 88, 84, 84, 50, 86, 51, 78, 122, 111, 54, 48, 110, 113, 108, 118, 90, 99, 89, 83, 80, 108, 102, 65, 54, 57, 81, 54, 52, 86, 78, 105, 101, 113,' + '76, 100, 107, 53, 112, 53, 75, 88, 104, 66, 104, 106, 65, 68, 99, 80, 109, 71, 120, 102, 99, 83, 71, 122, 66, 100, 107, 116, 109, 52, 114, 53, 71, 114, 107, 103, 67, 116, 107, 113, 115, 86, 97, 121, 71, 48, 116, 78, 116, 122, 54, 67, 79, 106, 70, 71, 57, 119, 43, 108, 43, 70, 119, 88, 84, 78, 89, 108, 71, 115, 55, 56, 73, 113, 67, 51, 86, 102, 68, 81, 72, 52, 72, 75, 104, 76, 48, 97, 83, 107, 106, 51, 86, 55, 100, 76, 90, 76, 101, 70,' + '86, 78, 106, 75, 69, 76, 68, 56, 90, 111, 97, 109, 89, 49, 106, 107, 75, 52, 65, 79, 86, 121, 57, 97, 107, 49, 98, 122, 56, 106, 82, 117, 119, 115, 53, 100, 54, 66, 103, 103, 109, 47, 121, 122, 105, 119, 49, 102, 111, 57, 77, 72, 56, 101, 90, 101, 108, 47, 74, 111, 78, 68, 72, 74, 117, 68, 78, 43, 105, 78, 86, 83, 99, 66, 119, 120, 80, 119, 53, 55, 67, 52, 105, 113, 68, 79, 48, 70, 83, 103, 76, 85, 121, 69, 68, 103, 70, 56, 48, 80,' + '121, 43, 114, 57, 66, 52, 54, 83, 51, 85, 72, 101, 116, 66, 43, 99, 113, 57, 76, 68, 122, 69, 55, 55, 122, 68, 67, 111, 48, 111, 66, 114, 112, 48, 86, 80, 70, 113, 50, 72, 68, 120, 43, 85, 81, 108, 68, 55, 65, 50, 120, 114, 115, 72, 90, 108, 54, 90, 80, 66, 89, 83, 75, 108, 90, 117, 70, 83, 100, 55, 65, 99, 54, 89, 105, 108, 98, 98, 113, 82, 69, 57, 80, 108, 110, 113, 89, 112, 103, 116, 69, 117, 50, 108, 51, 43, 43, 78, 56, 56,' + '112, 55, 116, 101, 74, 109, 50, 120, 114, 89, 110, 90, 102, 67, 103, 107, 72, 48, 104, 57, 99, 116, 51, 55, 55, 116, 74, 70, 73, 76, 100, 111, 88, 54, 119, 68, 108, 112, 113, 117, 56, 43, 113, 47, 119, 102, 121, 121, 48, 112, 107, 105, 102, 109, 101, 48, 80, 77, 109, 76, 122, 69, 52, 87, 101, 80, 122, 121, 122, 85, 82, 105, 84, 83, 101, 83, 99, 121, 76, 71, 66, 100, 107, 119, 66, 48, 70, 110, 100, 66, 98, 122, 111, 122, 109, 82, 87, 52, 75, 108,' + '54, 112, 71, 50, 121, 82, 107, 120, 72, 53, 55, 77, 75, 103, 109, 120, 70, 72, 114, 103, 114, 49, 54, 49, 53, 119, 83, 72, 67, 106, 119, 122, 56, 74, 89, 73, 118, 120, 43, 99, 49, 77, 47, 119, 106, 85, 70, 56, 108, 71, 85, 74, 113, 48, 89, 82, 109, 97, 84, 47, 87, 108, 74, 77, 121, 50, 73, 86, 108, 97, 43, 83, 121, 72, 52, 80, 50, 80, 116, 100, 47, 113, 77, 85, 51, 106, 107, 47, 80, 115, 69, 52, 77, 55, 113, 90, 55, 107, 98, 104,' + '98, 98, 112, 47, 100, 100, 48, 89, 113, 50, 99, 73, 90, 75, 87, 102, 85, 72, 75, 66, 72, 52, 48, 110, 74, 67, 107, 72, 81, 100, 51, 56, 43, 103, 73, 112, 114, 48, 67, 104, 77, 88, 85, 65, 120, 114, 115, 80, 104, 74, 83, 71, 89, 119, 97, 105, 52, 122, 105, 97, 83, 98, 49, 54, 52, 117, 85, 114, 85, 104, 66, 43, 97, 105, 73, 51, 80, 48, 47, 74, 76, 99, 82, 116, 78, 121, 99, 79, 100, 97, 105, 71, 102, 85, 53, 72, 98, 105, 47, 81,' + '108, 72, 121, 51, 121, 82, 111, 74, 50, 107, 118, 102, 69, 52, 66, 47, 80, 54, 70, 57, 78, 106, 71, 89, 57, 105, 113, 83, 107, 97, 114, 54, 114, 121, 76, 75, 110, 104, 116, 87, 68, 74, 86, 81, 69, 99, 105, 78, 111, 52, 68, 117, 76, 72, 75, 49, 86, 88, 118, 101, 67, 106, 72, 76, 50, 76, 97, 43, 87, 73, 84, 65, 83, 78, 84, 110, 100, 70, 85, 112, 43, 52, 79, 87, 111, 118, 43, 98, 75, 70, 85, 113, 115, 106, 43, 100, 85, 75, 81, 106,' + '116, 83, 99, 102, 110, 70, 111, 77, 86, 90, 118, 86, 71, 69, 98, 121, 113, 53, 100, 76, 74, 90, 102, 72, 90, 105, 90, 72, 111, 111, 116, 87, 47, 103, 87, 82, 57, 65, 107, 52, 122, 65, 66, 54, 51, 87, 120, 104, 80, 79, 105, 51, 81, 82, 79, 68, 109, 101, 112, 115, 56, 86, 122, 70, 79, 50, 105, 81, 83, 98, 75, 80, 101, 90, 118, 112, 79, 88, 89, 102, 43, 89, 84, 84, 99, 73, 57, 84, 57, 109, 98, 70, 109, 99, 49, 102, 72, 87, 87, 114,' + '55, 56, 66, 50, 119, 76, 119, 88, 110, 107, 102, 107, 83, 49, 116, 52, 114, 78, 57, 75, 100, 116, 120, 118, 97, 67, 48, 120, 88, 68, 99, 65, 89, 101, 103, 121, 85, 121, 110, 101, 119, 48, 110, 120, 120, 52, 53, 111, 52, 78, 121, 121, 82, 55, 115, 88, 85, 111, 112, 71, 47, 57, 48, 119, 110, 55, 57, 87, 77, 66, 112, 104, 119, 86, 109, 116, 56, 117, 88, 118, 108, 47, 51, 79, 83, 73, 56, 51, 120, 122, 114, 71, 48, 111, 90, 66, 52, 71, 103, 99,' + '85, 74, 101, 47, 84, 118, 115, 47, 56, 43, 66, 73, 56, 106, 115, 43, 68, 106, 50, 83, 55, 99, 68, 49, 98, 83, 78, 48, 121, 86, 117, 85, 53, 48, 56, 55, 120, 104, 54, 100, 107, 48, 79, 115, 87, 110, 121, 102, 88, 54, 119, 111, 43, 71, 71, 119, 89, 116, 84, 86, 106, 104, 67, 55, 82, 53, 115, 74, 65, 53, 88, 52, 112, 89, 114, 117, 51, 72, 88, 56, 72, 50, 68, 116, 71, 76, 112, 108, 112, 80, 107, 72, 90, 54, 119, 74, 49, 75, 56, 72,' + '72, 103, 53, 111, 108, 119, 102, 68, 48, 122, 87, 101, 102, 107, 53, 99, 47, 77, 51, 54, 110, 90, 85, 73, 100, 51, 52, 86, 121, 86, 80, 88, 74, 67, 76, 119, 72, 99, 47, 122, 106, 71, 68, 89, 77, 83, 77, 117, 75, 72, 122, 69, 43, 103, 107, 71, 53, 81, 86, 55, 76, 54, 53, 100, 99, 89, 111, 75, 116, 83, 87, 84, 51, 83, 116, 87, 101, 88, 49, 47, 107, 88, 67, 115, 101, 100, 50, 48, 108, 106, 102, 87, 101, 82, 109, 109, 52, 97, 112, 51,' + '68, 119, 43, 88, 100, 69, 47, 68, 81, 101, 75, 99, 43, 119, 83, 81, 76, 114, 104, 101, 122, 65, 72, 119, 84, 57, 53, 120, 109, 70, 108, 76, 89, 110, 74, 52, 50, 82, 90, 54, 74, 89, 97, 87, 68, 76, 68, 54, 57, 69, 108, 82, 90, 86, 104, 121, 75, 75, 84, 112, 99, 116, 71, 103, 85, 114, 83, 87, 119, 77, 50, 54, 114, 77, 72, 52, 83, 68, 117, 103, 65, 77, 56, 118, 76, 87, 118, 114, 74, 55, 50, 51, 108, 75, 100, 83, 109, 121, 47, 88,' + '99, 86, 112, 67, 119, 69, 107, 78, 65, 100, 77, 51, 87, 109, 77, 84, 100, 84, 47, 98, 89, 106, 97, 68, 103, 86, 117, 56, 77, 88, 86, 89, 74, 98, 49, 122, 70, 50, 57, 66, 106, 65, 117, 66, 110, 113, 99, 81, 50, 48, 85, 106, 108, 120, 50, 100, 52, 115, 99, 70, 56, 57, 105, 114, 101, 84, 87, 77, 52, 65, 112, 57, 47, 119, 101, 114, 66, 119, 104, 43, 119, 100, 43, 53, 57, 68, 99, 112, 117, 84, 71, 66, 79, 86, 110, 97, 109, 48, 90, 77,' + '99, 67, 111, 105, 118, 88, 105, 103, 75, 65, 52, 100, 87, 51, 107, 100, 69, 65, 50, 71, 43, 81, 113, 110, 65, 108, 50, 65, 78, 84, 117, 97, 79, 104, 90, 43, 100, 83, 68, 70, 105, 116, 83, 90, 49, 103, 104, 55, 47, 114, 90, 97, 112, 68, 106, 73, 119, 49, 53, 106, 81, 52, 117, 103, 55, 106, 107, 88, 48, 118, 99, 52, 108, 97, 116, 113, 90, 66, 65, 79, 110, 114, 118, 68, 72, 119, 61, 61, 58, 54, 68, 114, 79, 56, 88, 72, 77, 111, 87, 57,' + '117, 77, 120, 75, 57, 122, 112, 122, 57, 103, 122, 53, 81, 86, 78, 122, 104, 78, 90, 83, 90, 77, 106, 89, 50, 81, 75, 72, 113, 114, 118, 109, 57, 47, 69, 57, 67, 119, 101, 54, 104, 81, 121, 108, 55, 73, 70, 51, 109, 43, 109, 52, 66, 87, 53, 87, 49, 101, 117, 102, 108, 105, 66, 81, 109, 119, 97, 115, 98, 76, 51, 77, 86, 65, 84, 79, 65, 88, 109, 57, 97, 84, 98, 111, 89, 119, 102, 86, 86, 105, 50, 71, 87, 98, 74, 119, 105, 120, 54, 47,' + '99, 116, 70, 48, 52, 101, 43, 102, 120, 90, 87, 119, 77, 80, 115, 82, 109, 53, 82, 51, 56, 102, 67, 122, 68, 54, 56, 82, 78, 67, 54, 86, 68, 51, 101, 122, 114, 54, 43, 52, 56, 85, 68, 120, 116, 112, 66, 72, 117, 69, 71, 47, 78, 78, 101, 77, 66, 70, 51, 43, 122, 69, 86, 89, 74, 98, 112, 114, 116, 79, 79, 75, 113, 102, 101, 43, 101, 50, 84, 66, 85, 100, 106, 77, 84, 74, 72, 108, 52, 57, 70, 70, 83, 89, 83, 50, 73, 102, 68, 48,' + '105, 90, 73, 117, 77, 102, 105, 68, 53, 87, 76, 54, 67, 99, 69, 114, 76, 113, 54, 75, 98, 86, 73, 49, 65, 43, 74, 115, 98, 115, 80, 87, 108, 86, 109, 111, 79, 84, 112, 53, 43, 57, 116, 98, 67, 101, 83, 99, 56, 112, 52, 87, 110, 72, 49, 57, 70, 55, 81, 56, 105, 84, 103, 72, 89, 77, 88, 52, 89, 83, 108, 111, 121, 47, 68, 90, 70, 108, 119, 52, 88, 79, 70, 100, 110, 102, 77, 98, 53, 70, 70, 57, 69, 79, 49, 115, 54, 48, 87, 77,' + '116, 76, 56, 65, 72, 109, 98, 107, 121, 53, 56, 57, 116, 97, 105, 43, 108, 78, 72, 88, 52, 77, 102, 55, 112, 80, 57, 107, 121, 89, 119, 61, 61, 58, 97, 54, 111, 108, 99, 101, 88, 80, 56, 110, 84, 52, 87, 71, 48, 84, 34, 44, 34, 114, 101, 102, 101, 114, 101, 110, 99, 101, 34, 58, 34, 113, 100, 49, 49, 78, 83, 79, 65, 54, 115, 72, 52, 106, 70, 67, 66, 78, 75, 122, 48, 117, 43, 86, 48, 82, 120, 85, 86, 86, 56, 90, 57, 73, 102, 113,' + '55, 54, 65, 97, 100, 121, 77, 89, 104, 69, 121, 101, 121, 90, 109, 100, 73, 119, 110, 48, 99, 50, 101, 103, 87, 43, 86, 55, 73, 66, 73, 114, 76, 75, 57, 66, 43, 117, 98, 90, 76, 117, 53, 86, 83, 68, 77, 100, 103, 66, 90, 108, 97, 73, 47, 52, 54, 103, 98, 48, 84, 79, 53, 67, 88, 102, 79, 118, 113, 55, 47, 66, 97, 112, 76, 87, 107, 71, 68, 111, 114, 65, 108, 110, 71, 75, 116, 113, 100, 84, 97, 70, 109, 109, 70, 86, 65, 80, 47, 79,' + '48, 118, 57, 84, 122, 73, 53, 78, 56, 113, 98, 102, 98, 79, 120, 110, 84, 113, 86, 66, 48, 65, 43, 82, 73, 106, 50, 85, 50, 99, 79, 121, 66, 80, 108, 90, 52, 89, 82, 97, 105, 113, 88, 79, 89, 86, 65, 68, 57, 105, 76, 74, 73, 113, 68, 43, 66, 104, 100, 103, 121, 108, 83, 82, 105, 50, 89, 100, 118, 113, 84, 48, 106, 84, 51, 71, 88, 52, 113, 90, 65, 116, 104, 111, 70, 78, 76, 54, 55, 120, 86, 83, 112, 71, 106, 118, 53, 85, 57, 80,' + '110, 50, 114, 76, 80, 85, 111, 73, 112, 66, 49, 75, 113, 117, 121, 101, 104, 104, 106, 66, 86, 70, 67, 121, 99, 67, 72, 83, 115, 66, 56, 119, 55, 80, 83, 76, 115, 118, 51, 82, 89, 100, 77, 71, 82, 55, 110, 106, 72, 79, 103, 57, 114, 86, 71, 86, 79, 89, 71, 77, 51, 109, 52, 76, 80, 112, 89, 118, 88, 50, 103, 76, 73, 66, 55, 118, 99, 98, 100, 49, 65, 104, 111, 55, 54, 100, 106, 119, 111, 89, 79, 122, 49, 66, 101, 99, 102, 67, 114, 105,' + '87, 47, 101, 80, 113, 78, 52, 101, 84, 79, 77, 51, 51, 107, 43, 106, 69, 101, 52, 43, 86, 76, 90, 89, 77, 101, 43, 56, 61, 58, 54, 68, 114, 79, 56, 88, 72, 77, 111, 87, 57, 117, 77, 120, 75, 57, 122, 112, 122, 57, 103, 122, 53, 81, 86, 78, 122, 104, 78, 90, 83, 90, 77, 106, 89, 50, 81, 75, 72, 113, 114, 118, 109, 57, 47, 69, 57, 67, 119, 101, 54, 104, 81, 121, 108, 55, 73, 70, 51, 109, 43, 109, 52, 66, 87, 53, 87, 49, 101, 117,' + '102, 108, 105, 66, 81, 109, 119, 97, 115, 98, 76, 51, 77, 86, 65, 84, 79, 65, 88, 109, 57, 97, 84, 98, 111, 89, 119, 102, 86, 86, 105, 50, 71, 87, 98, 74, 119, 105, 120, 54, 47, 99, 116, 70, 48, 52, 101, 43, 102, 120, 90, 87, 119, 77, 80, 115, 82, 109, 53, 82, 51, 56, 102, 67, 122, 68, 54, 56, 82, 78, 67, 54, 86, 68, 51, 101, 122, 114, 54, 43, 52, 56, 85, 68, 120, 116, 112, 66, 72, 117, 69, 71, 47, 78, 78, 101, 77, 66, 70, 51,' + '43, 122, 69, 86, 89, 74, 98, 112, 114, 116, 79, 79, 75, 113, 102, 101, 43, 101, 50, 84, 66, 85, 100, 106, 77, 84, 74, 72, 108, 52, 57, 70, 70, 83, 89, 83, 50, 73, 102, 68, 48, 105, 90, 73, 117, 77, 102, 105, 68, 53, 87, 76, 54, 67, 99, 69, 114, 76, 113, 54, 75, 98, 86, 73, 49, 65, 43, 74, 115, 98, 115, 80, 87, 108, 86, 109, 111, 79, 84, 112, 53, 43, 57, 116, 98, 67, 101, 83, 99, 56, 112, 52, 87, 110, 72, 49, 57, 70, 55, 81,' + '56, 105, 84, 103, 72, 89, 77, 88, 52, 89, 83, 108, 111, 121, 47, 68, 90, 70, 108, 119, 52, 88, 79, 70, 100, 110, 102, 77, 98, 53, 70, 70, 57, 69, 79, 49, 115, 54, 48, 87, 77, 116, 76, 56, 65, 72, 109, 98, 107, 121, 53, 56, 57, 116, 97, 105, 43, 108, 78, 72, 88, 52, 77, 102, 55, 112, 80, 57, 107, 121, 89, 119, 61, 61, 58, 71, 103, 119, 105, 68, 99, 73, 79, 86, 84, 117, 68, 76, 80, 75, 75, 34, 125]'; + final jsonFileBytes = (jsonDecode(jsonFileBytesString) as List).cast(); + final jsonFile = XFile.fromData(Uint8List.fromList(jsonFileBytes), name: 'Two_Fas_encrypted.json'); + group('fileIsValid', () { + test('isTrue', () async { + // Act + final fileIsValid = await processor.fileIsValid(jsonFile); + // Assert + expect(fileIsValid, isTrue); + }); + test('isFalse', () async { + // Arrange + final jsonFileBytes = (jsonDecode(jsonFileBytesString) as List).cast()..removeLast(); + final jsonFileInvalid = XFile.fromData(Uint8List.fromList(jsonFileBytes), name: 'Two_Fas_encrypted_invalid.json'); + // Act + final fileIsValid = await processor.fileIsValid(jsonFileInvalid); + // Assert + expect(fileIsValid, isFalse); + }); + }); + test('fileNeedsPassword', () async { + // Act + final fileNeedsPassword = await processor.fileNeedsPassword(jsonFile); + // Assert + expect(fileNeedsPassword, isTrue); + }); + test('processFile', () async { + // Arrange + const password = 'test123'; + // Act + final results = await processor.processFile(jsonFile, password: password); + // Assert + _assertSuccessResults(results); + }); + }); + }); + }); +} diff --git a/test/unit_test/processors/token_import_file_processor/two_fas_import_file_processor_test.dart b/test/unit_test/processors/token_import_file_processor/two_fas_import_file_processor_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/processors/token_import_file_processor/two_fas_import_file_processor_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/repo/preference_introduction_repository_test.dart b/test/unit_test/repo/preference_introduction_repository_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/repo/preference_introduction_repository_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/repo/preference_settings_repository_test.dart b/test/unit_test/repo/preference_settings_repository_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/repo/preference_settings_repository_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/repo/preference_token_folder_repository_test.dart b/test/unit_test/repo/preference_token_folder_repository_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/repo/preference_token_folder_repository_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/repo/secure_push_request_repository_test.dart b/test/unit_test/repo/secure_push_request_repository_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/repo/secure_push_request_repository_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/repo/secure_token_repository_test.dart b/test/unit_test/repo/secure_token_repository_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/repo/secure_token_repository_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/state_notifiers/completed_introduction_notifier_test.dart b/test/unit_test/state_notifiers/completed_introduction_notifier_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/state_notifiers/completed_introduction_notifier_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/state_notifiers/deeplink_notifier_test.dart b/test/unit_test/state_notifiers/deeplink_notifier_test.dart index 8b1378917..1ffb2a5f9 100644 --- a/test/unit_test/state_notifiers/deeplink_notifier_test.dart +++ b/test/unit_test/state_notifiers/deeplink_notifier_test.dart @@ -1 +1,91 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:privacyidea_authenticator/state_notifiers/deeplink_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod_state_listener.dart'; +void main() { + _testDeeplinkNotifier(); +} + +void _testDeeplinkNotifier() { + group('Deeplink Notifier Test', () { + test('initUri', () { + final container = ProviderContainer(); + final initUri = Uri.parse('otpauth://hotp/issuer?secret=secret&counter=0&digits=6&algorithm=SHA1'); + final deeplinkProvider = StateNotifierProvider( + (ref) => DeeplinkNotifier(sources: [DeeplinkSource(name: 'test', stream: const Stream.empty(), initialUri: Future.value(initUri))]), + ); + container.listen(deeplinkProvider, (prev, next) { + expect(prev, isNull); + expect(next, isNotNull); + expect(next!.uri, equals(initUri)); + }); + }); + test('initUri multible', () async { + final container = ProviderContainer(); + final initUri = Uri.parse('otpauth://hotp/issuer?secret=secret&counter=0&digits=6&algorithm=SHA1'); + final initUri2 = Uri.parse('otpauth://totp/issuer?secret=secret&period=30&digits=6&algorithm=SHA1'); + final deeplinkProvider = StateNotifierProvider( + (ref) => DeeplinkNotifier(sources: [ + DeeplinkSource(name: 'test', stream: const Stream.empty(), initialUri: Future.value(initUri)), + DeeplinkSource(name: 'test2', stream: const Stream.empty(), initialUri: Future.value(initUri2)), + ]), + ); + container.listen(deeplinkProvider, (prev, next) { + // There should be only one initial uri, others will be ignored + expect(prev, isNull); + expect(next, isNotNull); + expect(next!.uri, equals(initUri)); + }); + }); + test('stream uri', () { + final container = ProviderContainer(); + final uri1 = Uri.parse('otpauth://hotp/issuer?secret=secret&counter=0&digits=6&algorithm=SHA1'); + final uri2 = Uri.parse('otpauth://totp/issuer?secret=secret&period=30&digits=6&algorithm=SHA1'); + final uri3 = Uri.parse('otpauth://totp/issuer?secret=secret&period=60&digits=6&algorithm=SHA1'); + final uri4 = Uri.parse('otpauth://totp/issuer?secret=secret&period=90&digits=6&algorithm=SHA1'); + final list = [uri1, uri2, uri3, uri4]; + Stream stream = Stream.fromIterable([...list]); + final deeplinkProvider = StateNotifierProvider( + (ref) => DeeplinkNotifier(sources: [ + DeeplinkSource(name: 'test', stream: stream, initialUri: Future.value(null)), + ]), + ); + container.listen(deeplinkProvider, (prev, next) { + print('prev: $prev, next: $next'); + expect(next?.uri, equals(list.removeAt(0))); + expect(next?.fromInit, isFalse); + }); + }); + test('stream uri multible', () { + final container = ProviderContainer(); + final hotp1 = Uri.parse('otpauth://hotp/issuer?secret=secret&counter=1&digits=6&algorithm=SHA1'); + final hotp2 = Uri.parse('otpauth://hotp/issuer?secret=secret&counter=2&digits=6&algorithm=SHA1'); + final hotp3 = Uri.parse('otpauth://hotp/issuer?secret=secret&counter=3&digits=6&algorithm=SHA1'); + final hotp4 = Uri.parse('otpauth://hotp/issuer?secret=secret&counter=4&digits=6&algorithm=SHA1'); + final totp1 = Uri.parse('otpauth://totp/issuer?secret=secret&period=15&digits=6&algorithm=SHA1'); + final totp2 = Uri.parse('otpauth://totp/issuer?secret=secret&period=30&digits=6&algorithm=SHA1'); + final totp3 = Uri.parse('otpauth://totp/issuer?secret=secret&period=60&digits=6&algorithm=SHA1'); + final totp4 = Uri.parse('otpauth://totp/issuer?secret=secret&period=90&digits=6&algorithm=SHA1'); + final hotpList = [hotp1, hotp2, hotp3, hotp4]; + final totpList = [totp1, totp2, totp3, totp4]; + + Stream hotpStream = Stream.fromIterable([...hotpList]); + Stream totpStream = Stream.fromIterable([...totpList]); + final deeplinkProvider = StateNotifierProvider( + (ref) => DeeplinkNotifier(sources: [ + DeeplinkSource(name: 'HOTPs', stream: hotpStream, initialUri: Future.value(null)), + DeeplinkSource(name: 'TOTPs', stream: totpStream, initialUri: Future.value(null)), + ]), + ); + container.listen(deeplinkProvider, (prev, next) { + if (hotpList.length == totpList.length) { + expect(next?.uri, equals(hotpList.removeAt(0))); + } else { + expect(next?.uri, equals(totpList.removeAt(0))); + } + expect(next?.fromInit, isFalse); + }); + }); + }); +} diff --git a/test/unit_test/utils/app_customizer_test.dart b/test/unit_test/utils/app_customizer_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/app_customizer_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/utils/app_info_utils_test.dart b/test/unit_test/utils/app_info_utils_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/app_info_utils_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/utils/customization/application_customization_test.dart b/test/unit_test/utils/customization/application_customization_test.dart new file mode 100644 index 000000000..d3c99c563 --- /dev/null +++ b/test/unit_test/utils/customization/application_customization_test.dart @@ -0,0 +1,86 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:privacyidea_authenticator/model/enums/app_feature.dart'; +import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; + +void main() { + _testAppCustomizer(); +} + +void _testAppCustomizer() { + group('App Customizer Test', () { + // Arrange + final customization = ApplicationCustomization( + appName: 'test', + websiteLink: 'https://test', + appIconUint8List: defaultIconUint8List, + appImageUint8List: defaultImageUint8List, + lightTheme: ApplicationCustomization.defaultCustomization.lightTheme, + darkTheme: ApplicationCustomization.defaultCustomization.darkTheme, + disabledFeatures: {AppFeature.patchNotes}, + ); + test('constructor', () { + // Assert + expect(customization.appName, equals('test')); + expect(customization.websiteLink, equals('https://test')); + expect(customization.appIconUint8List, equals(defaultIconUint8List)); + expect(customization.appImageUint8List, equals(defaultImageUint8List)); + expect(customization.lightTheme, equals(ApplicationCustomization.defaultCustomization.lightTheme)); + expect(customization.darkTheme, equals(ApplicationCustomization.defaultCustomization.darkTheme)); + expect(customization.disabledFeatures, equals({AppFeature.patchNotes})); + }); + test('copyWith', () { + // Act + final newCustomization = customization.copyWith( + appName: 'test2', + websiteLink: 'https://test2', + appIconUint8List: defaultImageUint8List, + appImageUint8List: defaultIconUint8List, + lightTheme: ApplicationCustomization.defaultCustomization.darkTheme, + darkTheme: ApplicationCustomization.defaultCustomization.lightTheme, + disabledFeatures: {}, + ); + // Assert + expect(newCustomization.appName, equals('test2')); + expect(newCustomization.websiteLink, equals('https://test2')); + expect(newCustomization.appIconUint8List, equals(defaultImageUint8List)); + expect(newCustomization.appImageUint8List, equals(defaultIconUint8List)); + }); + group('serialization', () { + test('toJson', () { + // Act + final json = customization.toJson(); + // Assert + expect(json['appName'], equals('test')); + expect(json['websiteLink'], equals('https://test')); + expect(json['appIconBASE64'], equals(base64Encode(defaultIconUint8List))); + expect(json['appImageBASE64'], equals(base64Encode(defaultImageUint8List))); + expect(json['lightTheme'], equals(ApplicationCustomization.defaultCustomization.lightTheme.toJson())); + expect(json['darkTheme'], equals(ApplicationCustomization.defaultCustomization.darkTheme.toJson())); + expect(json['disabledFeatures'], equals({AppFeature.patchNotes.name})); + }); + test('fromJson', () { + // Act + final newCustomization = ApplicationCustomization.fromJson({ + 'appName': 'test2', + 'websiteLink': 'https://test2', + 'appIconBASE64': base64Encode(defaultImageUint8List), + 'appImageBASE64': base64Encode(defaultIconUint8List), + 'lightTheme': ApplicationCustomization.defaultCustomization.lightTheme.toJson(), + 'darkTheme': ApplicationCustomization.defaultCustomization.darkTheme.toJson(), + 'disabledFeatures': [], + }); + // Assert + expect(newCustomization.appName, equals('test2')); + expect(newCustomization.websiteLink, equals('https://test2')); + expect(newCustomization.appIconUint8List, equals(defaultImageUint8List)); + expect(newCustomization.appImageUint8List, equals(defaultIconUint8List)); + expect(newCustomization.lightTheme, equals(ApplicationCustomization.defaultCustomization.lightTheme)); + expect(newCustomization.darkTheme, equals(ApplicationCustomization.defaultCustomization.darkTheme)); + expect(newCustomization.disabledFeatures, isA()); + expect(newCustomization.disabledFeatures, isEmpty); + }); + }); + }); +} diff --git a/test/unit_test/utils/customization/theme_customization_test.dart b/test/unit_test/utils/customization/theme_customization_test.dart new file mode 100644 index 000000000..a23dc69b1 --- /dev/null +++ b/test/unit_test/utils/customization/theme_customization_test.dart @@ -0,0 +1,248 @@ +import 'dart:ui'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:privacyidea_authenticator/utils/customization/theme_customization.dart'; + +void main() { + _testThemeCustomization(); +} + +void _testThemeCustomization() { + group('Theme Customization Test', () { + // Arrange + const customization = ThemeCustomization( + brightness: Brightness.dark, + primaryColor: Color(0xFF000000), + onPrimary: Color(0xFF000001), + subtitleColor: Color(0xFF000002), + backgroundColor: Color(0xFF000003), + foregroundColor: Color(0xFF000004), + shadowColor: Color(0xFF000005), + deleteColor: Color(0xFF000006), + renameColor: Color(0xFF000007), + lockColor: Color(0xFF000008), + tileIconColor: Color(0xFF000009), + navigationBarColor: Color(0xFF00000A), + actionButtonsForegroundColor: Color(0xFF00000B), + tilePrimaryColor: Color(0xFF00000C), + tileSubtitleColor: Color(0xFF00000D), + navigationBarIconColor: Color(0xFF00000E), + qrButtonBackgroundColor: Color(0xFF00000F), + qrButtonIconColor: Color(0xFF000010), + ); + test('constructor', () { + // Assert + expect(customization.brightness, equals(Brightness.dark)); + expect(customization.primaryColor, equals(const Color(0xFF000000))); + expect(customization.onPrimary, equals(const Color(0xFF000001))); + expect(customization.subtitleColor, equals(const Color(0xFF000002))); + expect(customization.backgroundColor, equals(const Color(0xFF000003))); + expect(customization.foregroundColor, equals(const Color(0xFF000004))); + expect(customization.shadowColor, equals(const Color(0xFF000005))); + expect(customization.deleteColor, equals(const Color(0xFF000006))); + expect(customization.renameColor, equals(const Color(0xFF000007))); + expect(customization.lockColor, equals(const Color(0xFF000008))); + expect(customization.tileIconColor, equals(const Color(0xFF000009))); + expect(customization.navigationBarColor, equals(const Color(0xFF00000A))); + expect(customization.actionButtonsForegroundColor, equals(const Color(0xFF00000B))); + expect(customization.tilePrimaryColor, equals(const Color(0xFF00000C))); + expect(customization.tileSubtitleColor, equals(const Color(0xFF00000D))); + expect(customization.navigationBarIconColor, equals(const Color(0xFF00000E))); + expect(customization.qrButtonBackgroundColor, equals(const Color(0xFF00000F))); + expect(customization.qrButtonIconColor, equals(const Color(0xFF000010))); + }); + test('copyWith', () { + // Act + final newCustomization = customization.copyWith( + brightness: Brightness.light, + primaryColor: const Color(0xFFFFFFFF), + onPrimary: const Color(0xFFFFFFFE), + subtitleColor: const Color(0xFFFFFFFD), + backgroundColor: const Color(0xFFFFFFFC), + foregroundColor: const Color(0xFFFFFFFB), + shadowColor: const Color(0xFFFFFFFA), + deleteColor: const Color(0xFFFFFFF9), + renameColor: const Color(0xFFFFFFF8), + lockColor: const Color(0xFFFFFFF7), + tileIconColor: const Color(0xFFFFFFF6), + navigationBarColor: const Color(0xFFFFFFF5), + actionButtonsForegroundColor: () => const Color(0xFFFFFFF4), + tilePrimaryColor: () => const Color(0xFFFFFFF3), + tileSubtitleColor: () => const Color(0xFFFFFFF2), + navigationBarIconColor: () => const Color(0xFFFFFFF1), + qrButtonBackgroundColor: () => const Color(0xFFFFFFF0), + qrButtonIconColor: () => const Color(0xFFFFFFEF), + ); + // Assert + expect(newCustomization.brightness, equals(Brightness.light)); + expect(newCustomization.primaryColor, equals(const Color(0xFFFFFFFF))); + expect(newCustomization.onPrimary, equals(const Color(0xFFFFFFFE))); + expect(newCustomization.subtitleColor, equals(const Color(0xFFFFFFFD))); + expect(newCustomization.backgroundColor, equals(const Color(0xFFFFFFFC))); + expect(newCustomization.foregroundColor, equals(const Color(0xFFFFFFFB))); + expect(newCustomization.shadowColor, equals(const Color(0xFFFFFFFA))); + expect(newCustomization.deleteColor, equals(const Color(0xFFFFFFF9))); + expect(newCustomization.renameColor, equals(const Color(0xFFFFFFF8))); + expect(newCustomization.lockColor, equals(const Color(0xFFFFFFF7))); + expect(newCustomization.tileIconColor, equals(const Color(0xFFFFFFF6))); + expect(newCustomization.navigationBarColor, equals(const Color(0xFFFFFFF5))); + expect(newCustomization.actionButtonsForegroundColor, equals(const Color(0xFFFFFFF4))); + expect(newCustomization.tilePrimaryColor, equals(const Color(0xFFFFFFF3))); + expect(newCustomization.tileSubtitleColor, equals(const Color(0xFFFFFFF2))); + expect(newCustomization.navigationBarIconColor, equals(const Color(0xFFFFFFF1))); + expect(newCustomization.qrButtonBackgroundColor, equals(const Color(0xFFFFFFF0))); + expect(newCustomization.qrButtonIconColor, equals(const Color(0xFFFFFFEF))); + }); + group('default themes', () { + test('defaultLightWith', () { + // Act + const newCustomization = ThemeCustomization.defaultLightWith( + primaryColor: Color(0xFFFFFFFF), + onPrimary: Color(0xFFFFFFFE), + subtitleColor: Color(0xFFFFFFFD), + backgroundColor: Color(0xFFFFFFFC), + foregroundColor: Color(0xFFFFFFFB), + shadowColor: Color(0xFFFFFFFA), + deleteColor: Color(0xFFFFFFF9), + renameColor: Color(0xFFFFFFF8), + lockColor: Color(0xFFFFFFF7), + tileIconColor: Color(0xFFFFFFF6), + navigationBarColor: Color(0xFFFFFFF5), + actionButtonsForegroundColor: Color(0xFFFFFFF4), + tilePrimaryColor: Color(0xFFFFFFF3), + tileSubtitleColor: Color(0xFFFFFFF2), + navigationBarIconColor: Color(0xFFFFFFF1), + qrButtonBackgroundColor: Color(0xFFFFFFF0), + qrButtonIconColor: Color(0xFFFFFFEF), + ); + // Assert + expect(newCustomization.brightness, equals(Brightness.light)); + expect(newCustomization.primaryColor, equals(const Color(0xFFFFFFFF))); + expect(newCustomization.onPrimary, equals(const Color(0xFFFFFFFE))); + expect(newCustomization.subtitleColor, equals(const Color(0xFFFFFFFD))); + expect(newCustomization.backgroundColor, equals(const Color(0xFFFFFFFC))); + expect(newCustomization.foregroundColor, equals(const Color(0xFFFFFFFB))); + expect(newCustomization.shadowColor, equals(const Color(0xFFFFFFFA))); + expect(newCustomization.deleteColor, equals(const Color(0xFFFFFFF9))); + expect(newCustomization.renameColor, equals(const Color(0xFFFFFFF8))); + expect(newCustomization.lockColor, equals(const Color(0xFFFFFFF7))); + expect(newCustomization.tileIconColor, equals(const Color(0xFFFFFFF6))); + expect(newCustomization.navigationBarColor, equals(const Color(0xFFFFFFF5))); + expect(newCustomization.actionButtonsForegroundColor, equals(const Color(0xFFFFFFF4))); + expect(newCustomization.tilePrimaryColor, equals(const Color(0xFFFFFFF3))); + expect(newCustomization.tileSubtitleColor, equals(const Color(0xFFFFFFF2))); + expect(newCustomization.navigationBarIconColor, equals(const Color(0xFFFFFFF1))); + expect(newCustomization.qrButtonBackgroundColor, equals(const Color(0xFFFFFFF0))); + expect(newCustomization.qrButtonIconColor, equals(const Color(0xFFFFFFEF))); + }); + test('defaultDarkWith', () { + // Act + const newCustomization = ThemeCustomization.defaultDarkWith( + primaryColor: Color(0xFFFFFFFF), + onPrimary: Color(0xFFFFFFFE), + subtitleColor: Color(0xFFFFFFFD), + backgroundColor: Color(0xFFFFFFFC), + foregroundColor: Color(0xFFFFFFFB), + shadowColor: Color(0xFFFFFFFA), + deleteColor: Color(0xFFFFFFF9), + renameColor: Color(0xFFFFFFF8), + lockColor: Color(0xFFFFFFF7), + tileIconColor: Color(0xFFFFFFF6), + navigationBarColor: Color(0xFFFFFFF5), + actionButtonsForegroundColor: Color(0xFFFFFFF4), + tilePrimaryColor: Color(0xFFFFFFF3), + tileSubtitleColor: Color(0xFFFFFFF2), + navigationBarIconColor: Color(0xFFFFFFF1), + qrButtonBackgroundColor: Color(0xFFFFFFF0), + qrButtonIconColor: Color(0xFFFFFFEF), + ); + // Assert + expect(newCustomization.brightness, equals(Brightness.dark)); + expect(newCustomization.primaryColor, equals(const Color(0xFFFFFFFF))); + expect(newCustomization.onPrimary, equals(const Color(0xFFFFFFFE))); + expect(newCustomization.subtitleColor, equals(const Color(0xFFFFFFFD))); + expect(newCustomization.backgroundColor, equals(const Color(0xFFFFFFFC))); + expect(newCustomization.foregroundColor, equals(const Color(0xFFFFFFFB))); + expect(newCustomization.shadowColor, equals(const Color(0xFFFFFFFA))); + expect(newCustomization.deleteColor, equals(const Color(0xFFFFFFF9))); + expect(newCustomization.renameColor, equals(const Color(0xFFFFFFF8))); + expect(newCustomization.lockColor, equals(const Color(0xFFFFFFF7))); + expect(newCustomization.tileIconColor, equals(const Color(0xFFFFFFF6))); + expect(newCustomization.navigationBarColor, equals(const Color(0xFFFFFFF5))); + expect(newCustomization.actionButtonsForegroundColor, equals(const Color(0xFFFFFFF4))); + expect(newCustomization.tilePrimaryColor, equals(const Color(0xFFFFFFF3))); + expect(newCustomization.tileSubtitleColor, equals(const Color(0xFFFFFFF2))); + expect(newCustomization.navigationBarIconColor, equals(const Color(0xFFFFFFF1))); + expect(newCustomization.qrButtonBackgroundColor, equals(const Color(0xFFFFFFF0))); + expect(newCustomization.qrButtonIconColor, equals(const Color(0xFFFFFFEF))); + }); + }); + group('serialization', () { + test('fromJson', () { + // Act + final newCustomization = ThemeCustomization.fromJson({ + 'brightness': 'light', + 'primaryColor': 0xFFFFFFFF, + 'onPrimary': 0xFFFFFFFE, + 'subtitleColor': 0xFFFFFFFD, + 'backgroundColor': 0xFFFFFFFC, + 'foregroundColor': 0xFFFFFFFB, + 'shadowColor': 0xFFFFFFFA, + 'deleteColor': 0xFFFFFFF9, + 'renameColor': 0xFFFFFFF8, + 'lockColor': 0xFFFFFFF7, + 'tileIconColor': 0xFFFFFFF6, + 'navigationBarColor': 0xFFFFFFF5, + '_actionButtonsForegroundColor': 0xFFFFFFF4, + '_tilePrimaryColor': 0xFFFFFFF3, + '_tileSubtitleColor': 0xFFFFFFF2, + '_navigationBarIconColor': 0xFFFFFFF1, + '_qrButtonBackgroundColor': 0xFFFFFFF0, + '_qrButtonIconColor': 0xFFFFFFEF, + }); + // Assert + expect(newCustomization.brightness, equals(Brightness.light)); + expect(newCustomization.primaryColor, equals(const Color(0xFFFFFFFF))); + expect(newCustomization.onPrimary, equals(const Color(0xFFFFFFFE))); + expect(newCustomization.subtitleColor, equals(const Color(0xFFFFFFFD))); + expect(newCustomization.backgroundColor, equals(const Color(0xFFFFFFFC))); + expect(newCustomization.foregroundColor, equals(const Color(0xFFFFFFFB))); + expect(newCustomization.shadowColor, equals(const Color(0xFFFFFFFA))); + expect(newCustomization.deleteColor, equals(const Color(0xFFFFFFF9))); + expect(newCustomization.renameColor, equals(const Color(0xFFFFFFF8))); + expect(newCustomization.lockColor, equals(const Color(0xFFFFFFF7))); + expect(newCustomization.tileIconColor, equals(const Color(0xFFFFFFF6))); + expect(newCustomization.navigationBarColor, equals(const Color(0xFFFFFFF5))); + expect(newCustomization.actionButtonsForegroundColor, equals(const Color(0xFFFFFFF4))); + expect(newCustomization.tilePrimaryColor, equals(const Color(0xFFFFFFF3))); + expect(newCustomization.tileSubtitleColor, equals(const Color(0xFFFFFFF2))); + expect(newCustomization.navigationBarIconColor, equals(const Color(0xFFFFFFF1))); + expect(newCustomization.qrButtonBackgroundColor, equals(const Color(0xFFFFFFF0))); + expect(newCustomization.qrButtonIconColor, equals(const Color(0xFFFFFFEF))); + }); + test('toJson', () { + // Act + final json = customization.toJson(); + // Assert + expect(json['brightness'], equals('dark')); + expect(json['primaryColor'], equals(0xFF000000)); + expect(json['onPrimary'], equals(0xFF000001)); + expect(json['subtitleColor'], equals(0xFF000002)); + expect(json['backgroundColor'], equals(0xFF000003)); + expect(json['foregroundColor'], equals(0xFF000004)); + expect(json['shadowColor'], equals(0xFF000005)); + expect(json['deleteColor'], equals(0xFF000006)); + expect(json['renameColor'], equals(0xFF000007)); + expect(json['lockColor'], equals(0xFF000008)); + expect(json['tileIconColor'], equals(0xFF000009)); + expect(json['navigationBarColor'], equals(0xFF00000A)); + expect(json['_actionButtonsForegroundColor'], equals(0xFF00000B)); + expect(json['_tilePrimaryColor'], equals(0xFF00000C)); + expect(json['_tileSubtitleColor'], equals(0xFF00000D)); + expect(json['_navigationBarIconColor'], equals(0xFF00000E)); + expect(json['_qrButtonBackgroundColor'], equals(0xFF00000F)); + expect(json['_qrButtonIconColor'], equals(0xFF000010)); + }); + }); + }); +} diff --git a/test/unit_test/utils/firebase_utils_test.dart b/test/unit_test/utils/firebase_utils_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/firebase_utils_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/utils/globals_test.dart b/test/unit_test/utils/globals_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/globals_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/utils/home_widget_utils_test.dart b/test/unit_test/utils/home_widget_utils_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/home_widget_utils_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/utils/identifiers_test.dart b/test/unit_test/utils/identifiers_test.dart deleted file mode 100644 index e56679f64..000000000 --- a/test/unit_test/utils/identifiers_test.dart +++ /dev/null @@ -1,76 +0,0 @@ -// ignore_for_file: constant_identifier_names - -/* - privacyIDEA Authenticator - - Authors: Timo Sturm - Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH - - Licensed under the Apache License, Version 2.0 (the 'License'); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an 'AS IS' BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -// default email address for crash reports - -const defaultCrashReportRecipient = 'app-crash@netknights.it'; - -// qr codes: -const String URI_TYPE = 'URI_TYPE'; -const String URI_LABEL = 'URI_LABEL'; -const String URI_ALGORITHM = 'URI_ALGORITHM'; -const String URI_DIGITS = 'URI_DIGITS'; -const String URI_SECRET = 'URI_SECRET'; // Should be base32 encoded -const String URI_COUNTER = 'URI_COUNTER'; -const String URI_PERIOD = 'URI_PERIOD'; -const String URI_ISSUER = 'URI_ISSUER'; -const String URI_PIN = 'URI_PIN'; -const String URI_IMAGE = 'URI_IMAGE'; -const String URI_ORIGIN = 'URI_ORIGIN'; - -// 2 step: -const String URI_SALT_LENGTH = 'URI_SALT_LENGTH'; -const String URI_OUTPUT_LENGTH_IN_BYTES = 'URI_OUTPUT_LENGTH_IN_BYTES'; -const String URI_ITERATIONS = 'URI_ITERATIONS'; - -// push token: -const String URI_SERIAL = 'URI_SERIAL'; -const String URI_ROLLOUT_URL = 'URI_ROLLOUT_URL'; -const String URI_TTL = 'URI_TTL'; -const String URI_ENROLLMENT_CREDENTIAL = 'URI_ENROLLMENT_CREDENTIAL'; -const String URI_SSL_VERIFY = 'URI_SSL_VERIFY'; - -// Crypto stuff: -const String SIGNING_ALGORITHM = 'SHA-256/RSA'; - -// Custom error identifiers -const String FIREBASE_TOKEN_ERROR_CODE = 'FIREBASE_TOKEN_ERROR_CODE'; - -// Push request: -const String PUSH_REQUEST_NONCE = 'nonce'; // 1. -const String PUSH_REQUEST_URL = 'url'; // 2. -const String PUSH_REQUEST_SERIAL = 'serial'; // 3. -const String PUSH_REQUEST_QUESTION = 'question'; // 4. -const String PUSH_REQUEST_TITLE = 'title'; // 5. -const String PUSH_REQUEST_SSL_VERIFY = 'sslverify'; // 6. -const String PUSH_REQUEST_SIGNATURE = 'signature'; // 7. - -const String GLOBAL_SECURE_REPO_PREFIX = 'app_v3_'; - -bool validateMap(Map map, List keys) { - for (String key in keys) { - if (!map.containsKey(key)) { - return false; - } - } - return true; -} diff --git a/test/unit_test/utils/image_converter_test.dart b/test/unit_test/utils/image_converter_test.dart deleted file mode 100644 index 2639ea5c5..000000000 --- a/test/unit_test/utils/image_converter_test.dart +++ /dev/null @@ -1,268 +0,0 @@ -import 'dart:io'; -import 'dart:typed_data'; -import 'dart:ui'; - -import 'package:camera/camera.dart'; -import 'package:flutter/material.dart'; -import 'package:image/image.dart' as imglib; - -class ImageConverter { - final imglib.Image image; - final Size size; - - ImageConverter({ - required this.image, - }) : size = Size(image.width.toDouble(), image.height.toDouble()); - - factory ImageConverter.fromCameraImage(CameraImage image, int rotation, - {bool isFrontCamera = false, int? chropLeft, int? chropRight, int? chropTop, int? chropBottom}) { - return switch (image.format.group) { - ImageFormatGroup.yuv420 => ImageConverter._fromYUV420(image, rotation, isFrontCamera, chropLeft ?? 0, chropRight ?? 0, chropTop ?? 0, chropBottom ?? 0), - ImageFormatGroup.bgra8888 => - ImageConverter._fromBGRA8888(image, rotation, isFrontCamera, chropLeft ?? 0, chropRight ?? 0, chropTop ?? 0, chropBottom ?? 0), - ImageFormatGroup.jpeg => ImageConverter._fromJPEG(image), - ImageFormatGroup.nv21 => ImageConverter._fromNV21(image), - ImageFormatGroup.unknown => throw ArgumentError('Unknown image format'), - }; - } - - factory ImageConverter._fromNV21(CameraImage image) { - final width = image.width.toInt(); - final height = image.height.toInt(); - Uint8List yuv420sp = image.planes[0].bytes; - final convertedImage = imglib.Image(height: height, width: width); - final int frameSize = width * height; - - for (int j = 0, yp = 0; j < height; j++) { - int uvp = frameSize + (j >> 1) * width, u = 0, v = 0; - for (int i = 0; i < width; i++, yp++) { - int y = (0xff & yuv420sp[yp]) - 16; - if (y < 0) y = 0; - if ((i & 1) == 0) { - v = (0xff & yuv420sp[uvp++]) - 128; - u = (0xff & yuv420sp[uvp++]) - 128; - } - int y1192 = 1192 * y; - int r = (y1192 + 1634 * v).clamp(0, 262143); - int g = (y1192 - 833 * v - 400 * u).clamp(0, 262143); - int b = (y1192 + 2066 * u).clamp(0, 262143); - - // getting their 8-bit values. - convertedImage.setPixelRgba( - i, - j, - ((r << 6) & 0xff0000) >> 16, - ((g >> 2) & 0xff00) >> 8, - (b >> 10) & 0xff, - 0xff, - ); - } - } - - return ImageConverter( - image: convertedImage, - ); - } - - factory ImageConverter._fromJPEG(CameraImage image) { - return ImageConverter(image: imglib.decodeJpg(image.planes[0].bytes)!); - } - - factory ImageConverter._fromBGRA8888(CameraImage image, int rotation, bool mirror, int cropLeft, int cropRight, int cropTop, int cropBottom) { - rotation = 360 - (rotation % 360); // if the image is rotated by 90, we need to rotate by another 270 to get the correct rotation (0/360) - const numChannels = 4; // 1 for alpha, 3 for RGB - var img = imglib.Image.fromBytes( - width: image.width, - height: image.height, - rowStride: image.planes[0].bytesPerRow, - numChannels: numChannels, - bytesOffset: numChannels * 7, // i don't know why 7 pixels, but it works - bytes: (image.planes[0].bytes).buffer, - ); - img = imglib.copyRotate(img, angle: rotation); - if (mirror) { - img = imglib.flip(img, direction: imglib.FlipDirection.horizontal); - } - img = imglib.copyCrop( - img, - x: cropLeft, - y: cropTop, - width: img.width - cropLeft - cropRight, - height: img.height - cropTop - cropBottom, - ); - return ImageConverter(image: img); - } - - factory ImageConverter._fromYUV420( - CameraImage image, - int rotation, - bool mirror, [ - int chropLeft = 0, - int chropRight = 0, - int chropTop = 0, - int chropBottom = 0, - ]) { - rotation = 360 - (rotation % 360); // if the rotation is 90, we need to rotate by 270 to get the correct rotation - - const alpha = 0xFF; - final height = image.height; - final width = image.width; - final yPlane = image.planes[0]; - final uPlane = image.planes[1]; - final vPlane = image.planes[2]; - - final int outputWidth; - final int outputHeight; - final int rotatedChropLeft; - final int rotatedChropRight; - final int rotatedChropTop; - final int rotatedChropBottom; - - final int uvRowStride = uPlane.bytesPerRow; - final int uvPixelStride = uPlane.bytesPerPixel!; - Function(int x, int y) getNewX; - Function(int x, int y) getNewY; - - switch (rotation) { - case 90: - outputWidth = height; - outputHeight = width; - if (mirror) { - // rotate by 90 and flip horizontally - getNewX = (x, y) => height - y - 1; - getNewY = (x, y) => width - x - 1; - rotatedChropRight = chropBottom; - rotatedChropBottom = chropRight; - rotatedChropLeft = chropTop; - rotatedChropTop = chropLeft; - } else { - getNewX = (x, y) => y; - getNewY = (x, y) => width - x - 1; - rotatedChropRight = chropTop; - rotatedChropBottom = chropRight; - rotatedChropLeft = chropBottom; - rotatedChropTop = chropLeft; - } - break; - case 180: - outputWidth = width; - outputHeight = height; - if (mirror) { - // rotate by 180 and flip horizontally - getNewX = (x, y) => x; - getNewY = (x, y) => height - y - 1; - - rotatedChropBottom = chropTop; - rotatedChropLeft = chropLeft; - rotatedChropTop = chropBottom; - rotatedChropRight = chropRight; - } else { - getNewX = (x, y) => width - x - 1; - getNewY = (x, y) => height - y - 1; - rotatedChropBottom = chropTop; - rotatedChropLeft = chropRight; - rotatedChropTop = chropBottom; - rotatedChropRight = chropLeft; - } - break; - case 270: - outputWidth = height; - outputHeight = width; - if (mirror) { - // rotate by 270 and flip horizontally - getNewX = (x, y) => y; - getNewY = (x, y) => height - x; - - rotatedChropLeft = chropBottom; - rotatedChropTop = chropRight; - rotatedChropRight = chropTop; - rotatedChropBottom = chropLeft; - } else { - getNewX = (x, y) => height - y - 1; - getNewY = (x, y) => x; - rotatedChropLeft = chropTop; - rotatedChropTop = chropRight; - rotatedChropRight = chropBottom; - rotatedChropBottom = chropLeft; - } - break; - - default: - outputWidth = width; - outputHeight = height; - if (mirror) { - // flip horizontally - getNewX = (x, y) => x; - getNewY = (x, y) => height - y - 1; - rotatedChropTop = chropTop; - rotatedChropRight = chropLeft; - rotatedChropBottom = chropBottom; - rotatedChropLeft = chropRight; - } else { - getNewX = (x, y) => x; - getNewY = (x, y) => y; - rotatedChropTop = chropTop; - rotatedChropRight = chropRight; - rotatedChropBottom = chropBottom; - rotatedChropLeft = chropLeft; - } - break; - } - - // imgLib -> Image package from https://pub.dartlang.org/packages/image - var img = imglib.Image(width: outputWidth, height: outputHeight); // Create Image buffer - - // Fill image buffer with plane[0] from YUV420_888 - - for (int y = chropTop; y < height - chropBottom; y++) { - for (int x = chropLeft; x < width - chropRight; x++) { - // if (x % 100 == 0) log("x: $x, y: $y"); - final int uvIndex = uvPixelStride * (x / 2).floor() + uvRowStride * (y / 2).floor(); - final int index = (y * width + x); - - final yp = yPlane.bytes[index]; - final up = uPlane.bytes[uvIndex]; - final vp = vPlane.bytes[uvIndex]; - // Calculate pixel color - - final int r = (yp + vp * 1436 / 1024 - 179).round().clamp(0, 255); - final int g = (yp - up * 46549 / 131072 + 44 - vp * 93604 / 131072 + 91).round().clamp(0, 255); - final int b = (yp + up * 1814 / 1024 - 227).round().clamp(0, 255); - // color: 0x FF FF FF FF - // A B G R - final newX = getNewX(x, y); - final newY = getNewY(x, y); - - if ((img.isBoundsSafe(newX, newY))) { - img.setPixelRgba(newX, newY, r, g, b, alpha); - } - } - } - final chropedImg = imglib.copyCrop( - img, - x: rotatedChropLeft, - y: rotatedChropTop, - width: img.width - rotatedChropLeft - rotatedChropRight, - height: img.height - rotatedChropTop - rotatedChropBottom, - ); - return ImageConverter(image: chropedImg); - } - - factory ImageConverter.fromFile(String path) { - final img = imglib.decodeImage(File(path).readAsBytesSync())!; - return ImageConverter(image: img); - } - - factory ImageConverter.fromBytes(Uint8List bytes) { - final img = imglib.decodeImage(bytes)!; - return ImageConverter(image: img); - } - - Uint8List toBytes() { - return Uint8List.fromList(imglib.encodePng(image)); - } - - imglib.Image toImage() { - return image; - } -} diff --git a/test/unit_test/utils/license_utils_test.dart b/test/unit_test/utils/license_utils_test.dart deleted file mode 100644 index 684f134ca..000000000 --- a/test/unit_test/utils/license_utils_test.dart +++ /dev/null @@ -1,629 +0,0 @@ -// ignore_for_file: constant_identifier_names - -/* - privacyIDEA Authenticator - - Authors: Timo Sturm - Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH - - Licensed under the Apache License, Version 2.0 (the 'License'); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an 'AS IS' BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -import 'package:flutter/foundation.dart'; - -/// This method removes all licenses from the LicenseRegistry. -/// It can be used for testing purposes, if one wishes to inspect a specifically -/// added license. -clearLicenses() { - // ignore: invalid_use_of_visible_for_testing_member - LicenseRegistry.reset(); -} - -addAllLicenses() { - _addNewLicense('privacyIDEA Authenticator', _PI_AUTHENTICATOR_LICENSE); - _addNewLicense('dart-hex', _DART_HEX_LICENSE); - _addNewLicense('dart-base32', _DART_BASE32_LICENSE); - _addNewLicense('otp', _DART_OTP_LICENSE); - _addNewLicense('dart-uuid', _DART_UUID_LICENSE); - _addNewLicense('json_serializabel', _JSON_SERIALIZABLE_LICENSE); - _addNewLicense('flutter_secure_storage', _FLUTTER_SECURE_STORAGE_LICENSE); - _addNewLicense('flutter_slidable', _FLUTTER_SLIDABLE_LICENSE); - _addNewLicense('intl', _INTL_LICENSE); - _addNewLicense('package_info', _PACKAGE_INFO_LICENSE); - _addNewLicense('pointycastle', _POINTYCASTLE_LICENSE); - _addNewLicense('dynamic_theme', _DYNAMIC_THEME_LICENSE); - _addNewLicense('flutterfire', _FLUTTERFIRE_LICENSE); - _addNewLicense('firebase_core', _FIREBASE_CORE_LICENSE); - _addNewLicense('asn1lib', _ASN1LIB_LICENSE); - _addNewLicense('http', _HTTP_LICENSE); - _addNewLicense('flutter_local_notifications#', _FLUTTER_LOCAL_NOTIFICATIONS); - _addNewLicense('dart-mutex', _DART_MUTEX_LICENSE); -} - -_addNewLicense(String packageName, String licenseText) { - LicenseRegistry.addLicense(() async* { - yield LicenseEntryWithLineBreaks([packageName], licenseText); - }); -} - -const String _DART_MUTEX_LICENSE = ''' -Copyright (c) 2016, Hoylen Sue. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - '''; - -const String _FLUTTER_LOCAL_NOTIFICATIONS = ''' -Copyright 2018 Michael Bui. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of the copyright holder nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; - -const String _HTTP_LICENSE = ''' -Copyright 2014, the Dart project authors. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; - -const String _ASN1LIB_LICENSE = ''' -http://opensource.org/licenses/BSD-3-Clause -Copyright (c) 2015, Warren Strange -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - - Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - - Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; - -const String _FIREBASE_CORE_LICENSE = ''' -// Copyright 2017 The Chromium Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; -const String _FLUTTERFIRE_LICENSE = ''' -Copyright 2017 The Chromium Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; -const String _DYNAMIC_THEME_LICENSE = ''' -MIT License - -Copyright (c) 2019 Norbert Kozsir - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the 'Software'), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'''; -const String _PI_AUTHENTICATOR_LICENSE = ''' - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - 'License' shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - 'Licensor' shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - 'Legal Entity' shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - 'control' means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - 'You' (or 'Your') shall mean an individual or Legal Entity - exercising permissions granted by this License. - - 'Source' form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - 'Object' form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - 'Work' shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - 'Derivative Works' shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - 'Contribution' shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, 'submitted' - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as 'Not a Contribution.' - - 'Contributor' shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a 'NOTICE' text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an 'AS IS' BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS'''; -const String _DART_HEX_LICENSE = ''' - -The MIT License (MIT) - -Copyright (c) 2016 Dartcoin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the 'Software'), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -'''; -const String _DART_BASE32_LICENSE = ''' Copyright (c) 2012 Yulian Kuncheff - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.'''; -const String _DART_OTP_LICENSE = ''' Copyright (c) 2012 Yulian Kuncheff - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.'''; -const String _DART_UUID_LICENSE = ''' Copyright (c) 2012 Yulian Kuncheff - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.'''; -const String _JSON_SERIALIZABLE_LICENSE = ''' -Copyright 2017, the Dart project authors. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'''; -const String _FLUTTER_SECURE_STORAGE_LICENSE = ''' -// Copyright 2017 Your Company. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Your Company nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'''; -const String _FLUTTER_SLIDABLE_LICENSE = ''' -MIT License - -Copyright (c) 2018 Romain Rastel - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the 'Software'), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE.'''; -const String _INTL_LICENSE = ''' -Copyright 2013, the Dart project authors. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; -const String _PACKAGE_INFO_LICENSE = ''' Copyright 2017 The Chromium Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'''; -const String _POINTYCASTLE_LICENSE = ''' -Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the 'Software'), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -'''; diff --git a/test/unit_test/utils/lock_auth_test.dart b/test/unit_test/utils/lock_auth_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/lock_auth_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/utils/logger_test.dart b/test/unit_test/utils/logger_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/logger_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/utils/network_utils_test.dart b/test/unit_test/utils/network_utils_test.dart deleted file mode 100644 index 9d63c4103..000000000 --- a/test/unit_test/utils/network_utils_test.dart +++ /dev/null @@ -1,182 +0,0 @@ -/* - privacyIDEA Authenticator - - Authors: Timo Sturm - Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH - - Licensed under the Apache License, Version 2.0 (the 'License'); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an 'AS IS' BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -import 'dart:async'; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:http/http.dart'; -import 'package:http/io_client.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; -import 'package:privacyidea_authenticator/utils/globals.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; -import 'package:privacyidea_authenticator/utils/view_utils.dart'; - -class PrivacyIdeaIOClient { - const PrivacyIdeaIOClient(); - - /// Dummy network request can be used to trigger the network access permission - /// on iOS devices. Doing this at an appropriate place in the code can prevent - /// SocketExceptions. - Future triggerNetworkAccessPermission({required Uri url, bool sslVerify = true, bool isRetry = false}) async { - if (kIsWeb) return false; - HttpClient httpClient = HttpClient(); - httpClient.badCertificateCallback = ((X509Certificate cert, String host, int port) => !sslVerify); - httpClient.userAgent = 'privacyIDEA-App' - '/${(await PackageInfo.fromPlatform()).version}' - ' ${Platform.operatingSystem}' - '/${Platform.operatingSystemVersion}'; - - IOClient ioClient = IOClient(httpClient); - - try { - await ioClient.post(url, body: '').timeout(const Duration(seconds: 15)); - } on ClientException { - Logger.warning('ClientException', name: 'utils.dart#triggerNetworkAccessPermission'); - ioClient.close(); - if (globalNavigatorKey.currentState?.context == null) return false; - globalRef?.read(statusMessageProvider.notifier).state = ( - AppLocalizations.of(await globalContext)!.connectionFailed, - AppLocalizations.of(await globalContext)!.checkYourNetwork, - ); - return false; - } catch (e, _) { - if (e is! SocketException && e is! TimeoutException) { - rethrow; - } - if (isRetry) { - Logger.warning('SocketException while retrying', name: 'utils.dart#triggerNetworkAccessPermission'); - if (globalNavigatorKey.currentState?.context != null) { - globalRef?.read(statusMessageProvider.notifier).state = ( - AppLocalizations.of(await globalContext)!.connectionFailed, - AppLocalizations.of(await globalContext)!.checkYourNetwork, - ); - } - ioClient.close(); - return false; - } - ioClient.close(); - return Future.delayed( - const Duration(seconds: 10), - () => triggerNetworkAccessPermission(url: url, sslVerify: sslVerify, isRetry: true), - ); - } finally { - ioClient.close(); - } - return true; - } - - /// Custom POST request allows to not verify certificates. - Future doPost({required Uri url, required Map body, bool sslVerify = true}) async { - if (kIsWeb) return Response('Platform not supported', 405); - Logger.info('Sending post request (SSLVerify: $sslVerify)', name: 'utils.dart#doPost'); - - List entries = body.entries.where((element) => element.value == null).toList(); - if (entries.isNotEmpty) { - List nullEntries = []; - for (MapEntry entry in entries) { - nullEntries.add(entry.key); - } - throw ArgumentError('Can not send request because the argument [body] contains a null values' - ' at entries $nullEntries, this is not permitted.'); - } - - HttpClient httpClient = HttpClient(); - httpClient.badCertificateCallback = ((_, __, ___) => !sslVerify); - httpClient.userAgent = 'privacyIDEA-App' - '/${(await PackageInfo.fromPlatform()).version}' - ' ${Platform.operatingSystem}' - '/${Platform.operatingSystemVersion}'; - - IOClient ioClient = IOClient(httpClient); - - Response response; - try { - response = await ioClient.post(url, body: body).timeout(const Duration(seconds: 15)); - } on HandshakeException catch (e, s) { - response = Response('${e.runtimeType} : $s', 525); - } catch (e, s) { - if (e is! TimeoutException && e is! SocketException) rethrow; - response = Response('${e.runtimeType} : $s', 404); - } - - if (response.statusCode != 200) { - Logger.warning( - 'Received unexpected response', - name: 'utils.dart#doPost', - error: 'Status code: ${response.statusCode}' '\nPosted body: $body' '\nResponse: ${response.body}\n', - ); - } - ioClient.close(); - - return response; - } - - Future doGet({required Uri url, required Map parameters, bool sslVerify = true}) async { - if (kIsWeb) return Response('', 405); - Logger.info('Sending get request (SSLVerify: $sslVerify)', name: 'utils.dart#doGet'); - List entries = parameters.entries.where((element) => element.value == null).toList(); - if (entries.isNotEmpty) { - List nullEntries = []; - for (MapEntry entry in entries) { - nullEntries.add(entry.key); - } - throw ArgumentError("Can not send request because the argument [parameters] contains " - "null values at entries $nullEntries, this is not permitted."); - } - - HttpClient httpClient = HttpClient(); - httpClient.badCertificateCallback = ((X509Certificate cert, String host, int port) => !sslVerify); - httpClient.userAgent = 'privacyIDEA-App /' - ' ${Platform.operatingSystem}' - ' ${(await PackageInfo.fromPlatform()).version}'; - - IOClient ioClient = IOClient(httpClient); - - StringBuffer buffer = StringBuffer(url); - - if (parameters.isNotEmpty) { - buffer.write('?'); - buffer.writeAll(parameters.entries.map((e) => '${e.key}=${e.value}'), '&'); - } - - Response response; - Uri uri = Uri.parse(buffer.toString()); - try { - response = await ioClient.get(uri).timeout(const Duration(seconds: 15)); - } on HandshakeException catch (e, s) { - Logger.warning('Handshake failed. sslVerify: $sslVerify', name: 'utils.dart#doGet', error: e, stackTrace: s); - showMessage(message: 'Handshake failed, please check the server certificate and try again.'); - rethrow; - } catch (e, s) { - if (e is! TimeoutException && e is! SocketException) rethrow; - response = Response('${e.runtimeType} : $s', 404); - } - - if (response.statusCode != 200) { - Logger.warning('Received unexpected response: ${response.statusCode}', name: 'utils.dart#doGet'); - } - - ioClient.close(); - return response; - } -} diff --git a/test/unit_test/utils/patch_notes_utils_test.dart b/test/unit_test/utils/patch_notes_utils_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/patch_notes_utils_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/utils/pi_mailer_test.dart b/test/unit_test/utils/pi_mailer_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/pi_mailer_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/utils/pi_notifications_test.dart b/test/unit_test/utils/pi_notifications_test.dart deleted file mode 100644 index 8a98113a7..000000000 --- a/test/unit_test/utils/pi_notifications_test.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; - -class PiNotifications { - static PiNotifications? _instance; - int id = 0; - FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - late NotificationDetails notificationDetails; - - PiNotifications._(); - - static Future show(String title, String body) async => (await _getInstance)._show(title, body); - - static Future get _getInstance async { - if (_instance == null) { - _instance = PiNotifications._(); - await _instance!._initialize(); - } - return _instance!; - } - - Future _initialize() async { - var initializationSettingsAndroid = const AndroidInitializationSettings('@mipmap/ic_launcher'); // <- default icon name is @mipmap/ic_launcher - // var initializationSettingsIOS = IOSInitializationSettings(onDidReceiveLocalNotification: onDidReceiveLocalNotification); - var initializationSettings = InitializationSettings(android: initializationSettingsAndroid); - flutterLocalNotificationsPlugin.initialize(initializationSettings); - AndroidNotificationDetails androidNotificationDetails = const AndroidNotificationDetails( - 'PiNotifications', - 'PiNotifications', - importance: Importance.max, - priority: Priority.high, - ticker: 'ticker', - ); - - notificationDetails = NotificationDetails(android: androidNotificationDetails); - } - - Future _show(String title, String body) async { - final id = this.id++; - await flutterLocalNotificationsPlugin.show(id, title, body, notificationDetails); - return id; - } -} diff --git a/test/unit_test/utils/push_provider_test.dart b/test/unit_test/utils/push_provider_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/push_provider_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/utils/riverpod_providers_test.dart b/test/unit_test/utils/riverpod_providers_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/riverpod_providers_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/utils/riverpod_state_listener_test.dart b/test/unit_test/utils/riverpod_state_listener_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/riverpod_state_listener_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/utils/token_import_origins_test.dart b/test/unit_test/utils/token_import_origins_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/token_import_origins_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/unit_test/utils/utils_test.dart b/test/unit_test/utils/utils_test.dart deleted file mode 100644 index 53f83c4bb..000000000 --- a/test/unit_test/utils/utils_test.dart +++ /dev/null @@ -1,52 +0,0 @@ -/* - privacyIDEA Authenticator - - Authors: Timo Sturm - Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH - - Licensed under the Apache License, Version 2.0 (the 'License'); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an 'AS IS' BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -import 'package:flutter_test/flutter_test.dart'; -import 'package:privacyidea_authenticator/utils/utils.dart'; - -void main() { - _testInsertCharAt(); - _testSplitPeriodically(); -} - -void _testInsertCharAt() { - const String str = 'ABCD'; - - group('insertCharAt', () { - test('Insert at start', () => expect('XABCD', insertCharAt(str, 'X', 0))); - - test('Insert at end', () => expect('ABCDX', insertCharAt(str, 'X', str.length))); - - test('Insert at end', () => expect('ABXCD', insertCharAt(str, 'X', 2))); - }); -} - -void _testSplitPeriodically() { - const String str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - - group('splitPeriodically', () { - test('Split every -1', () => expect(splitPeriodically(str, -1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')); - test('Split every 0', () => expect(splitPeriodically(str, 0), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')); - test('Split every 1', () => expect(splitPeriodically(str, 1), 'A B C D E F G H I J K L M N O P Q R S T U V W X Y Z')); - test('Split every 2', () => expect('AB CD EF GH IJ KL MN OP QR ST UV WX YZ', splitPeriodically(str, 2))); - test('Split every 3', () => expect('ABC DEF GHI JKL MNO PQR STU VWX YZ', splitPeriodically(str, 3))); - test('Split every 7', () => expect('ABCDEFG HIJKLMN OPQRSTU VWXYZ', splitPeriodically(str, 7))); - test('Split every 12', () => expect('ABCDEFGHIJKL MNOPQRSTUVWX YZ', splitPeriodically(str, 12))); - }); -} diff --git a/test/unit_test/utils/view_utils.dart b/test/unit_test/utils/view_utils.dart deleted file mode 100644 index 8b1378917..000000000 --- a/test/unit_test/utils/view_utils.dart +++ /dev/null @@ -1 +0,0 @@ -