From d219746c7edac0cf34105c1736e2ed419b9d38d3 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 16 Jul 2024 13:55:02 +0200 Subject: [PATCH 001/285] refactoring --- integration_test/add_tokens_test.dart | 5 +- integration_test/copy_to_clipboard_test.dart | 5 +- integration_test/rename_and_delete_test.dart | 5 +- integration_test/two_step_rollout_test.dart | 6 +- integration_test/views_test.dart | 7 +- lib/api/token_container_api_endpoint.dart | 14 + lib/interfaces/api_endpoint.dart | 4 + lib/interfaces/repo/container_repository.dart | 6 + .../state_notifier_provider_listener.dart | 11 + .../deep_link_listener.dart | 12 + .../token_state_listener.dart | 12 + lib/mains/main_customizer.dart | 4 +- lib/mains/main_netknights.dart | 3 +- lib/model/deeplink.dart | 14 + .../enums/app_feature_extension.dart | 2 +- .../enums/introduction_extension.dart | 3 +- .../enums/token_origin_source_type.dart | 3 +- lib/model/push_request.dart | 7 +- lib/model/states/token_container_state.dart | 245 ++++++++++++++++++ lib/model/states/token_container_state.g.dart | 189 ++++++++++++++ lib/model/token_container.dart | 37 +++ lib/model/token_container.g.dart | 25 ++ .../home_widget_navigate_processor.dart | 4 +- .../google_authenticator_qr_processor.dart | 4 +- .../otp_auth_processor.dart | 8 +- ...thenticator_pro_import_file_processor.dart | 16 +- ...google_authenticator_qrfile_processor.dart | 13 +- .../two_fas_import_file_processor.dart | 2 +- lib/repo/secure_token_repository.dart | 8 +- ...brid_token_container_state_repository.dart | 46 ++++ ...token_container_state_repository.dart.dart | 39 +++ ...mote_token_container_state_repository.dart | 20 ++ lib/state_notifiers/deeplink_notifier.dart | 2 +- .../push_request_notifier.dart | 2 +- lib/state_notifiers/sortable_notifier.dart | 24 +- .../token_container_notifier.dart | 37 +++ lib/state_notifiers/token_notifier.dart | 7 +- .../application_customization.dart | 2 +- lib/utils/encryption/token_encryption.dart | 3 +- lib/utils/globals.dart | 3 + lib/utils/home_widget_utils.dart | 7 +- lib/utils/logger.dart | 2 +- lib/utils/privacyidea_io_client.dart | 2 +- lib/utils/push_provider.dart | 4 +- .../deeplink_provider.dart | 22 ++ .../introduction_provider.dart | 13 + .../progress_state_provider.dart | 10 + .../push_request_provider.dart | 44 ++++ .../settings_provider.dart | 13 + .../sortable_provider.dart | 32 +++ .../token_container_state_provider.dart | 23 ++ .../token_folder_provider.dart | 14 + .../token_provider.dart | 25 ++ .../app_constraints_provider.dart | 11 + .../application_customizer_provider.dart | 8 + .../dragging_sortable_provider.dart | 12 + .../state_providers/home_widget_provider.dart | 16 ++ .../status_message_provider.dart | 10 + .../token_filter_provider.dart | 5 + .../connectivity_provider.dart | 26 ++ .../home_widget_deep_link_listener.dart | 16 ++ .../home_widget_token_state_listener.dart | 9 + .../navigation_deep_link_listener.dart | 22 ++ .../token_container_token_state_listener.dart | 16 ++ lib/utils/riverpod_providers.dart | 210 --------------- lib/utils/riverpod_state_listener.dart | 85 ------ lib/utils/rsa_utils.dart | 3 +- lib/utils/validators.dart | 2 +- .../add_token_manually_view.dart | 2 +- .../import_tokens_view.dart | 2 +- .../pages/import_plain_tokens_page.dart | 2 +- .../pages/import_start_page.dart | 5 +- .../pages/select_import_type_page.dart | 2 +- .../widgets/dialogs/qr_not_found_dialog.dart | 4 +- .../link_home_widget_view.dart | 3 +- lib/views/main_view/main_view.dart | 4 +- .../connectivity_listener.dart | 4 +- .../drag_target_divider.dart | 3 +- .../filter_token_widget.dart | 3 +- .../add_token_folder_dialog.dart | 3 +- .../delete_token_folder_action.dart | 3 +- .../lock_token_folder_action.dart | 3 +- .../rename_token_folder_action.dart | 2 +- .../token_folder_expandable.dart | 6 +- .../folder_widgets/token_folder_widget.dart | 4 +- .../main_view_navigation_bar.dart | 3 +- .../license_push_view_button.dart | 3 +- .../qr_scanner_button.dart | 2 +- .../main_view_tokens_list.dart | 4 +- .../main_view_tokens_list_filtered.dart | 4 +- .../edit_day_password_token_action.dart | 2 +- .../day_password_token_widget_tile.dart | 13 +- .../default_delete_action.dart | 2 +- .../default_edit_action.dart | 5 +- .../default_edit_action_dialog.dart | 5 +- .../default_lock_action.dart | 4 +- .../actions/edit_hotp_token_action.dart | 2 +- .../hotp_token_widget_tile.dart | 3 +- .../actions/edit_push_token_action.dart | 5 +- .../push_token_widget_tile.dart | 2 +- .../rollout_failed_widget.dart | 3 +- .../token_widgets/token_widget_base.dart | 5 +- .../actions/edit_totp_token_action.dart | 2 +- .../totp_token_widget_tile.dart | 3 +- .../widgets/push_tokens_view_list.dart | 3 +- .../dialogs/export_tokens_to_file_dialog.dart | 2 +- .../dialogs/select_export_type_dialog.dart | 2 +- .../dialogs/select_tokens_dialog.dart | 3 +- .../dialogs/show_qr_code_dialog.dart | 2 +- .../settings_group_import_export_tokens.dart | 6 +- .../settings_group_language.dart | 2 +- .../settings_group_push_token.dart | 3 +- lib/views/settings_view/settings_view.dart | 2 +- .../settings_view_widgets/logging_menu.dart | 2 +- .../update_firebase_token_dialog.dart | 2 +- lib/views/splash_screen/splash_screen.dart | 5 +- lib/widgets/app_wrapper.dart | 12 +- lib/widgets/app_wrappers/state_observer.dart | 3 +- lib/widgets/countdown_button.dart | 3 +- .../dialog_widgets/patch_notes_dialog.dart | 3 +- .../dialog_widgets/push_request_dialog.dart | 3 +- lib/widgets/drag_item_scroller.dart | 2 +- ...nable_text_form_field_after_many_taps.dart | 1 + lib/widgets/focused_item_as_overlay.dart | 2 +- lib/widgets/hideable_widget_.dart | 2 +- .../token_introduction.dart | 2 +- lib/widgets/push_request_listener.dart | 2 +- lib/widgets/status_bar.dart | 2 +- pubspec.yaml | 10 +- .../deeplink_notifier_test.dart | 3 +- .../sortable_notifier_test.dart | 5 +- .../state_notifiers/token_notifier_test.dart | 2 +- 132 files changed, 1299 insertions(+), 440 deletions(-) create mode 100644 lib/api/token_container_api_endpoint.dart create mode 100644 lib/interfaces/api_endpoint.dart create mode 100644 lib/interfaces/repo/container_repository.dart create mode 100644 lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart create mode 100644 lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart create mode 100644 lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart create mode 100644 lib/model/deeplink.dart create mode 100644 lib/model/states/token_container_state.dart create mode 100644 lib/model/states/token_container_state.g.dart create mode 100644 lib/model/token_container.dart create mode 100644 lib/model/token_container.g.dart create mode 100644 lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart create mode 100644 lib/repo/token_container_state_repositorys/preference_token_container_state_repository.dart.dart create mode 100644 lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart create mode 100644 lib/state_notifiers/token_container_notifier.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart create mode 100644 lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart create mode 100644 lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart create mode 100644 lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart create mode 100644 lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart delete mode 100644 lib/utils/riverpod_providers.dart delete mode 100644 lib/utils/riverpod_state_listener.dart diff --git a/integration_test/add_tokens_test.dart b/integration_test/add_tokens_test.dart index fc0756d37..62ac65652 100644 --- a/integration_test/add_tokens_test.dart +++ b/integration_test/add_tokens_test.dart @@ -17,7 +17,10 @@ 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/customization/application_customization.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.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'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/app_bar_item.dart'; diff --git a/integration_test/copy_to_clipboard_test.dart b/integration_test/copy_to_clipboard_test.dart index f470af938..71a3c2054 100644 --- a/integration_test/copy_to_clipboard_test.dart +++ b/integration_test/copy_to_clipboard_test.dart @@ -14,8 +14,11 @@ 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/customization/application_customization.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; import 'package:privacyidea_authenticator/model/version.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../test/tests_app_wrapper.dart'; import '../test/tests_app_wrapper.mocks.dart'; diff --git a/integration_test/rename_and_delete_test.dart b/integration_test/rename_and_delete_test.dart index 2ca4b8a5d..bfdcdd9f9 100644 --- a/integration_test/rename_and_delete_test.dart +++ b/integration_test/rename_and_delete_test.dart @@ -15,8 +15,11 @@ 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/customization/application_customization.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; import 'package:privacyidea_authenticator/model/version.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_delete_action.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart'; diff --git a/integration_test/two_step_rollout_test.dart b/integration_test/two_step_rollout_test.dart index c12b5fc09..6b1f7b115 100644 --- a/integration_test/two_step_rollout_test.dart +++ b/integration_test/two_step_rollout_test.dart @@ -12,9 +12,13 @@ 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/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'; import 'package:privacyidea_authenticator/model/version.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart'; diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index 4aa7623c3..5dcc8e288 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -16,8 +16,13 @@ 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/customization/application_customization.dart'; +import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/push_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/views/settings_view/settings_view_widgets/settings_groups.dart'; diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart new file mode 100644 index 000000000..bce3983f5 --- /dev/null +++ b/lib/api/token_container_api_endpoint.dart @@ -0,0 +1,14 @@ +import '../interfaces/api_endpoint.dart'; +import '../model/states/token_container_state.dart'; + +class TokenContainerApiEndpoint implements ApiEndpioint { + @override + Future fetch(String id) { + throw UnimplementedError(); + } + + @override + Future save(TokenContainerState data) { + throw UnimplementedError(); + } +} diff --git a/lib/interfaces/api_endpoint.dart b/lib/interfaces/api_endpoint.dart new file mode 100644 index 000000000..ab4fa8f95 --- /dev/null +++ b/lib/interfaces/api_endpoint.dart @@ -0,0 +1,4 @@ +abstract class ApiEndpioint { + Future fetch(Ref ref); + Future save(Data data); +} diff --git a/lib/interfaces/repo/container_repository.dart b/lib/interfaces/repo/container_repository.dart new file mode 100644 index 000000000..978cbe013 --- /dev/null +++ b/lib/interfaces/repo/container_repository.dart @@ -0,0 +1,6 @@ +import '../../model/states/token_container_state.dart'; + +abstract class TokenContainerStateRepository { + Future saveContainerState(TokenContainerState containerState); + Future loadContainer(); +} diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart new file mode 100644 index 000000000..b8d46c45d --- /dev/null +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart @@ -0,0 +1,11 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +abstract class StateNotifierProviderListener, S> { + final StateNotifierProvider? provider; + final void Function(S? previous, S next)? onNewState; + const StateNotifierProviderListener({this.provider, this.onNewState}); + void buildListen(WidgetRef ref) { + if (provider == null || onNewState == null) return; + ref.listen(provider!, onNewState!); + } +} diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart new file mode 100644 index 000000000..107fcbe4e --- /dev/null +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart @@ -0,0 +1,12 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../model/deeplink.dart'; +import '../../../../state_notifiers/deeplink_notifier.dart'; +import '../state_notifier_provider_listener.dart'; + +abstract class DeepLinkListener extends StateNotifierProviderListener { + const DeepLinkListener({ + required StateNotifierProvider deeplinkProvider, + required super.onNewState, + }) : super(provider: deeplinkProvider); +} diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart new file mode 100644 index 000000000..a245ad1d1 --- /dev/null +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart @@ -0,0 +1,12 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../model/states/token_state.dart'; +import '../../../../state_notifiers/token_notifier.dart'; +import '../state_notifier_provider_listener.dart'; + +abstract class TokenStateListener extends StateNotifierProviderListener { + const TokenStateListener({ + required StateNotifierProvider tokenProvider, + required super.onNewState, + }) : super(provider: tokenProvider); +} diff --git a/lib/mains/main_customizer.dart b/lib/mains/main_customizer.dart index 347a0e33d..0e409afc8 100644 --- a/lib/mains/main_customizer.dart +++ b/lib/mains/main_customizer.dart @@ -27,7 +27,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../l10n/app_localizations.dart'; import '../model/enums/app_feature.dart'; import '../utils/globals.dart'; -import '../utils/riverpod_providers.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart'; import '../views/add_token_manually_view/add_token_manually_view.dart'; import '../views/feedback_view/feedback_view.dart'; import '../views/import_tokens_view/import_tokens_view.dart'; diff --git a/lib/mains/main_netknights.dart b/lib/mains/main_netknights.dart index 79fa9a5f3..3ef10c8d7 100644 --- a/lib/mains/main_netknights.dart +++ b/lib/mains/main_netknights.dart @@ -31,7 +31,8 @@ import '../utils/customization/application_customization.dart'; import '../utils/globals.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; -import '../utils/riverpod_providers.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; import '../views/add_token_manually_view/add_token_manually_view.dart'; import '../views/feedback_view/feedback_view.dart'; import '../views/import_tokens_view/import_tokens_view.dart'; diff --git a/lib/model/deeplink.dart b/lib/model/deeplink.dart new file mode 100644 index 000000000..c28203370 --- /dev/null +++ b/lib/model/deeplink.dart @@ -0,0 +1,14 @@ +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/model/extensions/enums/app_feature_extension.dart b/lib/model/extensions/enums/app_feature_extension.dart index 6a1727504..7d3fee097 100644 --- a/lib/model/extensions/enums/app_feature_extension.dart +++ b/lib/model/extensions/enums/app_feature_extension.dart @@ -1,4 +1,4 @@ -import 'package:privacyidea_authenticator/model/enums/app_feature.dart'; +import '../../enums/app_feature.dart'; extension AppFeatureX on AppFeature { bool isDisabled(Set disabledFeatures) => disabledFeatures.contains(this); diff --git a/lib/model/extensions/enums/introduction_extension.dart b/lib/model/extensions/enums/introduction_extension.dart index fe43b0fbe..35690875d 100644 --- a/lib/model/extensions/enums/introduction_extension.dart +++ b/lib/model/extensions/enums/introduction_extension.dart @@ -1,7 +1,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../enums/introduction.dart'; import '../../states/introduction_state.dart'; diff --git a/lib/model/extensions/enums/token_origin_source_type.dart b/lib/model/extensions/enums/token_origin_source_type.dart index 9454d5a48..bd4521d5e 100644 --- a/lib/model/extensions/enums/token_origin_source_type.dart +++ b/lib/model/extensions/enums/token_origin_source_type.dart @@ -1,5 +1,4 @@ -import 'package:privacyidea_authenticator/utils/utils.dart'; - +import '../../../utils/utils.dart'; import '../../enums/token_origin_source_type.dart'; import '../../token_import/token_origin_data.dart'; import '../../tokens/token.dart'; diff --git a/lib/model/push_request.dart b/lib/model/push_request.dart index 4a711c5ce..68e78b9b7 100644 --- a/lib/model/push_request.dart +++ b/lib/model/push_request.dart @@ -4,9 +4,10 @@ import 'package:base32/base32.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:pi_authenticator_legacy/pi_authenticator_legacy.dart'; +import '../utils/globals.dart'; import '../utils/identifiers.dart'; import '../utils/logger.dart'; -import '../utils/riverpod_providers.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../utils/rsa_utils.dart'; import 'tokens/push_token.dart'; @@ -53,7 +54,7 @@ class PushRequest { String? serial, String? signature, bool? accepted, - List Function()? answers, + List Function()? possibleAnswers, String? Function()? selectedAnswer, }) { return PushRequest( @@ -67,7 +68,7 @@ class PushRequest { serial: serial ?? this.serial, signature: signature ?? this.signature, accepted: accepted ?? this.accepted, - possibleAnswers: answers != null ? answers() : this.possibleAnswers, + possibleAnswers: possibleAnswers != null ? possibleAnswers() : this.possibleAnswers, selectedAnswer: selectedAnswer != null ? selectedAnswer() : this.selectedAnswer, ); } diff --git a/lib/model/states/token_container_state.dart b/lib/model/states/token_container_state.dart new file mode 100644 index 000000000..7c301545b --- /dev/null +++ b/lib/model/states/token_container_state.dart @@ -0,0 +1,245 @@ +import 'package:json_annotation/json_annotation.dart'; + +import '../token_container.dart'; +import '../tokens/token.dart'; + +part 'token_container_state.g.dart'; + +sealed class TokenContainerState extends TokenContainer { + final DateTime? lastSyncedAt; + + const TokenContainerState({ + required this.lastSyncedAt, + // TokenContainer + required super.containerId, + required super.description, + required super.type, + required super.tokens, + }); + + factory TokenContainerState.uninitialized() => const TokenContainerStateUninitialized( + containerId: '', + description: '', + type: '', + tokens: [], + ); + + factory TokenContainerState.fromJson(Map json) => switch (json['type']) { + const ('TokenContainerStateUninitialized') => _$TokenContainerStateUninitializedFromJson(json), + const ('TokenContainerStateSynced') => _$TokenContainerStateSyncedFromJson(json), + const ('TokenContainerStateSyncing') => _$TokenContainerStateSyncingFromJson(json), + const ('TokenContainerStateUnsynced') => _$TokenContainerStateUnsyncedFromJson(json), + const ('TokenContainerStateError') => _$TokenContainerStateErrorFromJson(json), + const ('TokenContainerStateDeactivated') => _$TokenContainerStateDeactivatedFromJson(json), + const ('TokenContainerStateDeleted') => _$TokenContainerStateDeletedFromJson(json), + _ => throw UnimplementedError(json['type']), + }; + + @override + Map toJson() { + final json = switch (this) { + TokenContainerStateUninitialized() => _$TokenContainerStateUninitializedToJson(this as TokenContainerStateUninitialized), + TokenContainerStateSynced() => _$TokenContainerStateSyncedToJson(this as TokenContainerStateSynced), + TokenContainerStateSyncing() => _$TokenContainerStateSyncingToJson(this as TokenContainerStateSyncing), + TokenContainerStateUnsynced() => _$TokenContainerStateUnsyncedToJson(this as TokenContainerStateUnsynced), + TokenContainerStateError() => _$TokenContainerStateErrorToJson(this as TokenContainerStateError), + TokenContainerStateDeactivated() => _$TokenContainerStateDeactivatedToJson(this as TokenContainerStateDeactivated), + TokenContainerStateDeleted() => _$TokenContainerStateDeletedToJson(this as TokenContainerStateDeleted), + }; + json['type'] = runtimeType.toString(); + return json; + } + + T copyStateWith({ + dynamic data, + DateTime? dateTime, + DateTime? lastSyncedAt, + String? containerId, + String? description, + String? type, + List? tokens, + }) { + final copied = copyWith( + containerId: containerId, + description: description, + type: type, + tokens: tokens, + ) as TokenContainerState; + return copied.as(data: data, dateTime: dateTime); + } + + T as({dynamic data, DateTime? dateTime}) => switch (T) { + const (TokenContainerStateUninitialized) => TokenContainerStateUninitialized( + containerId: containerId, + description: description, + type: type, + tokens: tokens, + ) as T, + const (TokenContainerStateSynced) => TokenContainerStateSynced( + lastSyncedAt: lastSyncedAt ?? DateTime.now(), + containerId: containerId, + description: description, + type: type, + tokens: tokens, + ) as T, + const (TokenContainerStateSyncing) => TokenContainerStateSyncing( + lastSyncedAt: lastSyncedAt, + syncStartedAt: dateTime ?? DateTime.now(), + containerId: containerId, + description: description, + type: type, + tokens: tokens, + ) as T, + const (TokenContainerStateUnsynced) => this is TokenContainerStateUnsynced + ? (this as TokenContainerStateUnsynced).withIncrementedSyncAttempts() as T + : TokenContainerStateUnsynced( + lastSyncedAt: lastSyncedAt, + syncAttempts: data is num ? data.floor() : 1, + containerId: containerId, + description: description, + type: type, + tokens: tokens, + ) as T, + const (TokenContainerStateError) => TokenContainerStateError( + error: data, + lastSyncedAt: lastSyncedAt, + containerId: containerId, + description: description, + type: type, + tokens: tokens, + ) as T, + const (TokenContainerStateDeactivated) => TokenContainerStateDeactivated( + reason: data, + deactivatedAt: dateTime ?? DateTime.now(), + lastSyncedAt: lastSyncedAt, + containerId: containerId, + description: description, + type: type, + tokens: tokens, + ) as T, + const (TokenContainerStateDeleted) => TokenContainerStateDeleted( + reason: data, + deletedAt: dateTime ?? DateTime.now(), + lastSyncedAt: lastSyncedAt, + containerId: containerId, + description: description, + type: type, + tokens: tokens, + ) as T, + _ => throw UnimplementedError(), + }; +} + +/// ContainerState is not initialized +@JsonSerializable() +class TokenContainerStateUninitialized extends TokenContainerState { + const TokenContainerStateUninitialized({ + required super.containerId, + required super.description, + required super.type, + required super.tokens, + }) : super(lastSyncedAt: null); +} + +/// ContainerState is successfully synced with repo +@JsonSerializable() +class TokenContainerStateSynced extends TokenContainerState { + TokenContainerStateSynced({ + required DateTime lastSyncedAt, + required super.containerId, + required super.description, + required super.type, + required super.tokens, + }) : super(lastSyncedAt: lastSyncedAt); +} + +/// ContainerState is currently syncing +@JsonSerializable() +class TokenContainerStateSyncing extends TokenContainerState { + final DateTime syncStartedAt; + final Duration timeOut; + get isSyncing => DateTime.now().difference(syncStartedAt) < timeOut; + get isTimedOut => DateTime.now().difference(syncStartedAt) > timeOut; + const TokenContainerStateSyncing({ + required this.syncStartedAt, + this.timeOut = const Duration(seconds: 30), + required super.lastSyncedAt, + required super.containerId, + required super.description, + required super.type, + required super.tokens, + }); +} + +/// ContainerState is failed last sync attempt +@JsonSerializable() +class TokenContainerStateUnsynced extends TokenContainerState { + final int syncAttempts; + final dynamic lastError; + + TokenContainerStateUnsynced({ + this.syncAttempts = 1, + this.lastError, + required super.lastSyncedAt, + required super.containerId, + required super.description, + required super.type, + required super.tokens, + }); + + TokenContainerStateUnsynced withIncrementedSyncAttempts() => withSyncAttempts(syncAttempts + 1); + TokenContainerStateUnsynced withSyncAttempts(int syncAttempts) => TokenContainerStateUnsynced( + syncAttempts: syncAttempts, + lastSyncedAt: lastSyncedAt, + containerId: containerId, + description: description, + type: type, + tokens: tokens, + ); +} + +/// ContainerState is in error state +@JsonSerializable() +class TokenContainerStateError extends TokenContainerState { + final dynamic error; + TokenContainerStateError({ + required this.error, + required super.lastSyncedAt, + required super.containerId, + required super.description, + required super.type, + required super.tokens, + }); +} + +/// ContainerState is deactivated +@JsonSerializable() +class TokenContainerStateDeactivated extends TokenContainerState { + final DateTime deactivatedAt; + final dynamic reason; + TokenContainerStateDeactivated({ + required this.reason, + required this.deactivatedAt, + required super.lastSyncedAt, + required super.containerId, + required super.description, + required super.type, + required super.tokens, + }); +} + +/// ContainerState is deleted repo-side +@JsonSerializable() +class TokenContainerStateDeleted extends TokenContainerState { + final DateTime deletedAt; + final dynamic reason; + TokenContainerStateDeleted({ + required this.reason, + required this.deletedAt, + required super.lastSyncedAt, + required super.containerId, + required super.description, + required super.type, + required super.tokens, + }); +} diff --git a/lib/model/states/token_container_state.g.dart b/lib/model/states/token_container_state.g.dart new file mode 100644 index 000000000..a62228da9 --- /dev/null +++ b/lib/model/states/token_container_state.g.dart @@ -0,0 +1,189 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'token_container_state.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +TokenContainerStateUninitialized _$TokenContainerStateUninitializedFromJson( + Map json) => + TokenContainerStateUninitialized( + containerId: json['containerId'] as String, + description: json['description'] as String, + type: json['type'] as String, + tokens: (json['tokens'] as List) + .map((e) => Token.fromJson(e as Map)) + .toList(), + ); + +Map _$TokenContainerStateUninitializedToJson( + TokenContainerStateUninitialized instance) => + { + 'containerId': instance.containerId, + 'description': instance.description, + 'type': instance.type, + 'tokens': instance.tokens, + }; + +TokenContainerStateSynced _$TokenContainerStateSyncedFromJson( + Map json) => + TokenContainerStateSynced( + lastSyncedAt: DateTime.parse(json['lastSyncedAt'] as String), + containerId: json['containerId'] as String, + description: json['description'] as String, + type: json['type'] as String, + tokens: (json['tokens'] as List) + .map((e) => Token.fromJson(e as Map)) + .toList(), + ); + +Map _$TokenContainerStateSyncedToJson( + TokenContainerStateSynced instance) => + { + 'containerId': instance.containerId, + 'description': instance.description, + 'type': instance.type, + 'tokens': instance.tokens, + 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), + }; + +TokenContainerStateSyncing _$TokenContainerStateSyncingFromJson( + Map json) => + TokenContainerStateSyncing( + syncStartedAt: DateTime.parse(json['syncStartedAt'] as String), + timeOut: json['timeOut'] == null + ? const Duration(seconds: 30) + : Duration(microseconds: (json['timeOut'] as num).toInt()), + lastSyncedAt: json['lastSyncedAt'] == null + ? null + : DateTime.parse(json['lastSyncedAt'] as String), + containerId: json['containerId'] as String, + description: json['description'] as String, + type: json['type'] as String, + tokens: (json['tokens'] as List) + .map((e) => Token.fromJson(e as Map)) + .toList(), + ); + +Map _$TokenContainerStateSyncingToJson( + TokenContainerStateSyncing instance) => + { + 'containerId': instance.containerId, + 'description': instance.description, + 'type': instance.type, + 'tokens': instance.tokens, + 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), + 'syncStartedAt': instance.syncStartedAt.toIso8601String(), + 'timeOut': instance.timeOut.inMicroseconds, + }; + +TokenContainerStateUnsynced _$TokenContainerStateUnsyncedFromJson( + Map json) => + TokenContainerStateUnsynced( + syncAttempts: (json['syncAttempts'] as num?)?.toInt() ?? 1, + lastError: json['lastError'], + lastSyncedAt: json['lastSyncedAt'] == null + ? null + : DateTime.parse(json['lastSyncedAt'] as String), + containerId: json['containerId'] as String, + description: json['description'] as String, + type: json['type'] as String, + tokens: (json['tokens'] as List) + .map((e) => Token.fromJson(e as Map)) + .toList(), + ); + +Map _$TokenContainerStateUnsyncedToJson( + TokenContainerStateUnsynced instance) => + { + 'containerId': instance.containerId, + 'description': instance.description, + 'type': instance.type, + 'tokens': instance.tokens, + 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), + 'syncAttempts': instance.syncAttempts, + 'lastError': instance.lastError, + }; + +TokenContainerStateError _$TokenContainerStateErrorFromJson( + Map json) => + TokenContainerStateError( + error: json['error'], + lastSyncedAt: json['lastSyncedAt'] == null + ? null + : DateTime.parse(json['lastSyncedAt'] as String), + containerId: json['containerId'] as String, + description: json['description'] as String, + type: json['type'] as String, + tokens: (json['tokens'] as List) + .map((e) => Token.fromJson(e as Map)) + .toList(), + ); + +Map _$TokenContainerStateErrorToJson( + TokenContainerStateError instance) => + { + 'containerId': instance.containerId, + 'description': instance.description, + 'type': instance.type, + 'tokens': instance.tokens, + 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), + 'error': instance.error, + }; + +TokenContainerStateDeactivated _$TokenContainerStateDeactivatedFromJson( + Map json) => + TokenContainerStateDeactivated( + reason: json['reason'], + deactivatedAt: DateTime.parse(json['deactivatedAt'] as String), + lastSyncedAt: json['lastSyncedAt'] == null + ? null + : DateTime.parse(json['lastSyncedAt'] as String), + containerId: json['containerId'] as String, + description: json['description'] as String, + type: json['type'] as String, + tokens: (json['tokens'] as List) + .map((e) => Token.fromJson(e as Map)) + .toList(), + ); + +Map _$TokenContainerStateDeactivatedToJson( + TokenContainerStateDeactivated instance) => + { + 'containerId': instance.containerId, + 'description': instance.description, + 'type': instance.type, + 'tokens': instance.tokens, + 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), + 'deactivatedAt': instance.deactivatedAt.toIso8601String(), + 'reason': instance.reason, + }; + +TokenContainerStateDeleted _$TokenContainerStateDeletedFromJson( + Map json) => + TokenContainerStateDeleted( + reason: json['reason'], + deletedAt: DateTime.parse(json['deletedAt'] as String), + lastSyncedAt: json['lastSyncedAt'] == null + ? null + : DateTime.parse(json['lastSyncedAt'] as String), + containerId: json['containerId'] as String, + description: json['description'] as String, + type: json['type'] as String, + tokens: (json['tokens'] as List) + .map((e) => Token.fromJson(e as Map)) + .toList(), + ); + +Map _$TokenContainerStateDeletedToJson( + TokenContainerStateDeleted instance) => + { + 'containerId': instance.containerId, + 'description': instance.description, + 'type': instance.type, + 'tokens': instance.tokens, + 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), + 'deletedAt': instance.deletedAt.toIso8601String(), + 'reason': instance.reason, + }; diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart new file mode 100644 index 000000000..1ec626e6d --- /dev/null +++ b/lib/model/token_container.dart @@ -0,0 +1,37 @@ +import 'package:json_annotation/json_annotation.dart'; + +import 'tokens/token.dart'; + +part 'token_container.g.dart'; + +@JsonSerializable() +class TokenContainer { + final String containerId; + final String description; + final String type; + final List tokens; + + const TokenContainer({ + required this.containerId, + required this.description, + required this.type, + required this.tokens, + }); + + factory TokenContainer.fromJson(Map json) => _$TokenContainerFromJson(json); + Map toJson() => _$TokenContainerToJson(this); + + TokenContainer copyWith({ + String? containerId, + String? description, + String? type, + List? tokens, + }) { + return TokenContainer( + containerId: containerId ?? this.containerId, + description: description ?? this.description, + type: type ?? this.type, + tokens: tokens ?? this.tokens, + ); + } +} diff --git a/lib/model/token_container.g.dart b/lib/model/token_container.g.dart new file mode 100644 index 000000000..012ef32db --- /dev/null +++ b/lib/model/token_container.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'token_container.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +TokenContainer _$TokenContainerFromJson(Map json) => + TokenContainer( + containerId: json['containerId'] as String, + description: json['description'] as String, + type: json['type'] as String, + tokens: (json['tokens'] as List) + .map((e) => Token.fromJson(e as Map)) + .toList(), + ); + +Map _$TokenContainerToJson(TokenContainer instance) => + { + 'containerId': instance.containerId, + 'description': instance.description, + 'type': instance.type, + 'tokens': instance.tokens, + }; diff --git a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart index ee2279cbc..69f862f85 100644 --- a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart +++ b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; +import '../../../utils/globals.dart'; import '../../../utils/home_widget_utils.dart'; import '../../../utils/logger.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../views/link_home_widget_view/link_home_widget_view.dart'; import 'navigation_scheme_processor_interface.dart'; diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart b/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart index b1b3e24df..0c1d2e876 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart @@ -5,13 +5,13 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:base32/base32.dart'; -import '../../../model/extensions/enums/token_origin_source_type.dart'; -import '../../../utils/logger.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 '../../../proto/generated/GoogleAuthenticatorImport.pb.dart'; +import '../../../utils/logger.dart'; import '../../../utils/token_import_origins.dart'; import 'otp_auth_processor.dart'; import 'token_import_scheme_processor_interface.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 abe016941..d0780e178 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,16 +1,17 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.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_origin_source_type.dart'; import '../../../model/enums/token_types.dart'; import '../../../model/extensions/enum_extension.dart'; import '../../../model/extensions/enums/encodings_extension.dart'; +import '../../../model/extensions/enums/token_origin_source_type.dart'; import '../../../model/processor_result.dart'; +import '../../../model/token_import/token_origin_data.dart'; import '../../../model/tokens/token.dart'; import '../../../utils/errors.dart'; import '../../../utils/globals.dart'; @@ -19,7 +20,6 @@ import '../../../utils/logger.dart'; import '../../../utils/utils.dart' show getCurrentAppName; import '../../../utils/view_utils.dart'; import '../../../widgets/dialog_widgets/two_step_dialog.dart'; - import 'token_import_scheme_processor_interface.dart'; class OtpAuthProcessor extends TokenImportSchemeProcessor { 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 395cf0e6d..2388dc38d 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 @@ -4,25 +4,25 @@ import 'dart:convert'; import 'package:cryptography/cryptography.dart'; import 'package:file_selector/file_selector.dart'; + +import '../../l10n/app_localizations.dart'; +import '../../model/encryption/uint_8_buffer.dart'; 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/processor_result.dart'; import '../../model/tokens/token.dart'; import '../../processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart'; import '../../processors/token_import_file_processor/two_fas_import_file_processor.dart'; -import '../../utils/identifiers.dart'; -import '../../utils/logger.dart'; -import '../../utils/token_import_origins.dart'; - -import '../../l10n/app_localizations.dart'; import '../../utils/encryption/aes_encrypted.dart'; -import '../../model/encryption/uint_8_buffer.dart'; -import '../../model/enums/token_origin_source_type.dart'; -import '../../model/processor_result.dart'; import '../../utils/errors.dart'; import '../../utils/globals.dart'; +import '../../utils/identifiers.dart'; +import '../../utils/logger.dart'; +import '../../utils/token_import_origins.dart'; import 'token_import_file_processor_interface.dart'; class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { diff --git a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart index 678e288c1..543b0b332 100644 --- a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart +++ b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart @@ -4,18 +4,19 @@ import 'dart:math'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/foundation.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; -import 'package:privacyidea_authenticator/model/processor_result.dart'; -import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/processors/token_import_file_processor/token_import_file_processor_interface.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; -import 'package:zxing2/qrcode.dart'; import 'package:image/image.dart' as img_lib; +import 'package:zxing2/qrcode.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/globals.dart'; import '../../utils/logger.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart'; import '../../utils/token_import_origins.dart'; import '../scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart'; +import 'token_import_file_processor_interface.dart'; class GoogleAuthenticatorQrfileProcessor extends TokenImportFileProcessor { const GoogleAuthenticatorQrfileProcessor(); 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 c2fd2602e..083dbc654 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 @@ -6,13 +6,13 @@ import 'package:cryptography/cryptography.dart'; import 'package:file_selector/file_selector.dart'; import '../../l10n/app_localizations.dart'; -import '../../utils/encryption/aes_encrypted.dart'; import '../../model/enums/encodings.dart'; import '../../model/enums/token_origin_source_type.dart'; import '../../model/extensions/enums/encodings_extension.dart'; import '../../model/extensions/enums/token_origin_source_type.dart'; import '../../model/processor_result.dart'; import '../../model/tokens/token.dart'; +import '../../utils/encryption/aes_encrypted.dart'; import '../../utils/errors.dart'; import '../../utils/globals.dart'; import '../../utils/identifiers.dart'; diff --git a/lib/repo/secure_token_repository.dart b/lib/repo/secure_token_repository.dart index 716014990..35cd253b8 100644 --- a/lib/repo/secure_token_repository.dart +++ b/lib/repo/secure_token_repository.dart @@ -31,9 +31,10 @@ import 'package:mutex/mutex.dart'; import '../interfaces/repo/token_repository.dart'; import '../l10n/app_localizations.dart'; import '../model/tokens/token.dart'; +import '../utils/globals.dart'; import '../utils/identifiers.dart'; import '../utils/logger.dart'; -import '../utils/riverpod_providers.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../utils/view_utils.dart'; import '../views/settings_view/settings_view_widgets/send_error_dialog.dart'; import '../widgets/dialog_widgets/default_dialog.dart'; @@ -133,7 +134,7 @@ class SecureTokenRepository implements TokenRepository { } } if (failedTokens.isNotEmpty) { - Logger.error( + Logger.warning( 'Could not save all tokens (${tokens.length - failedTokens.length}/${tokens.length}) to secure storage', name: 'secure_token_repository.dart#saveOrReplaceTokens', stackTrace: StackTrace.current, @@ -149,7 +150,8 @@ class SecureTokenRepository implements TokenRepository { Future _saveOrReplaceToken(Token token) async { try { await _storage.write(key: _TOKEN_PREFIX + token.id, value: jsonEncode(token)); - } catch (_) { + } catch (e, s) { + Logger.error('Could not save token to secure storage', name: 'secure_token_repository.dart#saveOrReplaceToken', error: e, stackTrace: s); return false; } return true; diff --git a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart new file mode 100644 index 000000000..ff1cdd689 --- /dev/null +++ b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart @@ -0,0 +1,46 @@ +import '../../interfaces/repo/container_repository.dart'; +import '../../model/states/token_container_state.dart'; + +class HybridTokenContainerStateRepository + implements TokenContainerStateRepository { + final RemoteRepo _remoteRepository; + final LocalRepo _localRepository; + + HybridTokenContainerStateRepository({ + required RemoteRepo remoteRepository, + required LocalRepo localRepository, + }) : _remoteRepository = remoteRepository, + _localRepository = localRepository; + + @override + Future loadContainer({bool isInitial = false}) async { + final remoteState = await _remoteRepository.loadContainer(); + if (isInitial) return remoteState; + final localState = await _localRepository.loadContainer(); + return _merge(localState, remoteState); + } + + @override + Future saveContainerState(TokenContainerState containerState) async { + TokenContainerState remoteState; + TokenContainerState newState; + try { + remoteState = await _remoteRepository.loadContainer(); + } catch (e) { + newState = containerState.copyStateWith(); + return _localRepository.saveContainerState(newState); + } + newState = _merge(containerState, remoteState); + try { + await _remoteRepository.saveContainerState(newState); + } catch (e) { + newState = newState.copyStateWith(); + } + await _localRepository.saveContainerState(newState); + return newState; + } + + TokenContainerState _merge(TokenContainerState localState, TokenContainerState remoteState) { + throw UnimplementedError(); + } +} diff --git a/lib/repo/token_container_state_repositorys/preference_token_container_state_repository.dart.dart b/lib/repo/token_container_state_repositorys/preference_token_container_state_repository.dart.dart new file mode 100644 index 000000000..1dee7b247 --- /dev/null +++ b/lib/repo/token_container_state_repositorys/preference_token_container_state_repository.dart.dart @@ -0,0 +1,39 @@ +import 'dart:convert'; + +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../interfaces/repo/container_repository.dart'; +import '../../model/states/token_container_state.dart'; + +class PreferenceTokenContainerStateRepository implements TokenContainerStateRepository { + static String prefix = 'token_container_state_'; + final String containerId; + final Future _prefs = SharedPreferences.getInstance(); + + PreferenceTokenContainerStateRepository(this.containerId); + + @override + + /// Save the container state to the shared preferences + /// Returns the state that was actually written to the shared preferences + Future saveContainerState(TokenContainerState containerState) async { + (await _prefs).setString(prefix + containerId, jsonEncode(containerState.toJson())); + return containerState; + } + + @override + + /// Load the container state from the shared preferences + Future loadContainer() async { + final jsonString = (await _prefs).getString(prefix + containerId); + if (jsonString == null) { + return TokenContainerStateUninitialized( + containerId: containerId, + description: '', + type: '', + tokens: [], + ); + } + return TokenContainerState.fromJson(jsonDecode(jsonString)); + } +} diff --git a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart new file mode 100644 index 000000000..dbfea4cf4 --- /dev/null +++ b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart @@ -0,0 +1,20 @@ +import '../../api/token_container_api_endpoint.dart'; +import '../../interfaces/repo/container_repository.dart'; +import '../../model/states/token_container_state.dart'; + +class RemoteTokenContainerStateRepository implements TokenContainerStateRepository { + final TokenContainerApiEndpoint apiEndpoint; + final String containerId; + + RemoteTokenContainerStateRepository({required this.apiEndpoint, required this.containerId}); + + @override + Future saveContainerState(TokenContainerState containerState) { + throw UnimplementedError(); + } + + @override + Future loadContainer() => _fetchContainerState(); + + Future _fetchContainerState() async => apiEndpoint.fetch(containerId); +} diff --git a/lib/state_notifiers/deeplink_notifier.dart b/lib/state_notifiers/deeplink_notifier.dart index 8929ddb21..bc8521d70 100644 --- a/lib/state_notifiers/deeplink_notifier.dart +++ b/lib/state_notifiers/deeplink_notifier.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../model/deeplink.dart'; import '../utils/logger.dart'; -import '../utils/riverpod_state_listener.dart'; bool _initialUriIsHandled = false; diff --git a/lib/state_notifiers/push_request_notifier.dart b/lib/state_notifiers/push_request_notifier.dart index ea1d921ac..a87c429be 100644 --- a/lib/state_notifiers/push_request_notifier.dart +++ b/lib/state_notifiers/push_request_notifier.dart @@ -35,7 +35,7 @@ import '../utils/globals.dart'; import '../utils/logger.dart'; import '../utils/privacyidea_io_client.dart'; import '../utils/push_provider.dart'; -import '../utils/riverpod_providers.dart'; +import '../utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import '../utils/rsa_utils.dart'; import '../utils/utils.dart'; diff --git a/lib/state_notifiers/sortable_notifier.dart b/lib/state_notifiers/sortable_notifier.dart index c1d1cb22b..87e3803ee 100644 --- a/lib/state_notifiers/sortable_notifier.dart +++ b/lib/state_notifiers/sortable_notifier.dart @@ -1,15 +1,21 @@ 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'; -import '../utils/riverpod_providers.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; class SortableNotifier extends StateNotifier> { - final StateNotifierProviderRef ref; + final StateNotifierProviderRef _ref; Future>? initState; - SortableNotifier(this.ref, {List initState = const []}) : super(initState); + SortableNotifier({ + required StateNotifierProviderRef ref, + List initState = const [], + }) : _ref = ref, + super(initState); Future _waitInit() async { if (initState != null) { await initState; @@ -17,8 +23,8 @@ class SortableNotifier extends StateNotifier> { } initState = Future(() async { final newSortables = []; - newSortables.addAll((await ref.read(tokenProvider.notifier).initState).tokens.cast()); - newSortables.addAll((await ref.read(tokenFolderProvider.notifier).initState).folders.cast()); + newSortables.addAll((await _ref.read(tokenProvider.notifier).initState).tokens.cast()); + newSortables.addAll((await _ref.read(tokenFolderProvider.notifier).initState).folders.cast()); state = newSortables.sorted.fillNullIndices(); return state; }); @@ -37,12 +43,12 @@ class SortableNotifier extends StateNotifier> { await Future.wait([ Future.delayed(const Duration(milliseconds: 50)), if (newList.any((e) => e is Token) && newList.any((element) => element.sortIndex == null)) - ref.read(tokenProvider.notifier).addOrReplaceTokens(state.whereType().toList()), + _ref.read(tokenProvider.notifier).addOrReplaceTokens(state.whereType().toList()), if (newList.any((e) => e is TokenFolder) && newList.any((element) => element.sortIndex == null)) - ref.read(tokenFolderProvider.notifier).addOrReplaceFolders(state.whereType().toList()), + _ref.read(tokenFolderProvider.notifier).addOrReplaceFolders(state.whereType().toList()), ]); - ref.read(draggingSortableProvider.notifier).state = null; + _ref.read(draggingSortableProvider.notifier).state = null; return newState; } } diff --git a/lib/state_notifiers/token_container_notifier.dart b/lib/state_notifiers/token_container_notifier.dart new file mode 100644 index 000000000..858aabef2 --- /dev/null +++ b/lib/state_notifiers/token_container_notifier.dart @@ -0,0 +1,37 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../interfaces/repo/container_repository.dart'; +import '../model/states/token_container_state.dart'; +import '../model/tokens/token.dart'; + +class TokenContainerNotifier extends StateNotifier { + final TokenContainerStateRepository _repository; + final StateNotifierProviderRef _ref; + + TokenContainerNotifier({ + required StateNotifierProviderRef ref, + required TokenContainerStateRepository repository, + TokenContainerState? initState, + }) : _repository = repository, + _ref = ref, + super(initState ?? TokenContainerState.uninitialized()) { + _init(); + } + + Future _init() async { + throw UnimplementedError(); + } + + Future loadContainerState() async { + final containerState = await _repository.loadContainer(); + state = containerState; + } + + Future saveContainerState() async { + await _repository.saveContainerState(state); + } + + Future updatedTokensIfContains(List lastlyUpdatedTokens) async { + throw UnimplementedError(); + } +} diff --git a/lib/state_notifiers/token_notifier.dart b/lib/state_notifiers/token_notifier.dart index 008c9a250..f17c60521 100644 --- a/lib/state_notifiers/token_notifier.dart +++ b/lib/state_notifiers/token_notifier.dart @@ -11,11 +11,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; import 'package:pointycastle/asymmetric/api.dart'; -import '../model/enums/token_import_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_import_type.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'; @@ -33,7 +33,8 @@ import '../utils/identifiers.dart'; import '../utils/lock_auth.dart'; import '../utils/logger.dart'; import '../utils/privacyidea_io_client.dart'; -import '../utils/riverpod_providers.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import '../utils/rsa_utils.dart'; import '../utils/utils.dart'; import '../utils/view_utils.dart'; @@ -647,7 +648,7 @@ class TokenNotifier extends StateNotifier { /* //////////////////////////////////////////////////////////////////////////// ///////////////////////// Add New Tokens Methods ////////////////////////////// - /////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////// /// Does not need to wait for updating functions because they doesn't depend on any state */ /// The return value of a qrCode could be any object. In this case should be a String that is a valid URI. diff --git a/lib/utils/customization/application_customization.dart b/lib/utils/customization/application_customization.dart index af96ab9b0..21df5bcea 100644 --- a/lib/utils/customization/application_customization.dart +++ b/lib/utils/customization/application_customization.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/material.dart'; -import '../../../utils/customization/theme_customization.dart'; +import '../../../utils/customization/theme_customization.dart'; import '../../model/enums/app_feature.dart'; class ApplicationCustomization { diff --git a/lib/utils/encryption/token_encryption.dart b/lib/utils/encryption/token_encryption.dart index df142d5d8..0925aab44 100644 --- a/lib/utils/encryption/token_encryption.dart +++ b/lib/utils/encryption/token_encryption.dart @@ -1,10 +1,11 @@ import 'dart:convert'; + import 'package:zxing2/qrcode.dart'; import '../../model/tokens/token.dart'; import '../../processors/scheme_processors/token_import_scheme_processors/privacyidea_authenticator_qr_processor.dart'; -import '../../utils/logger.dart'; import '../../utils/encryption/aes_encrypted.dart'; +import '../../utils/logger.dart'; class TokenEncryption { static Future encrypt({required Iterable tokens, required String password}) async { diff --git a/lib/utils/globals.dart b/lib/utils/globals.dart index 451144099..86ae3bca7 100644 --- a/lib/utils/globals.dart +++ b/lib/utils/globals.dart @@ -21,6 +21,7 @@ */ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../l10n/app_localizations.dart'; import '../model/enums/patch_note_type.dart'; @@ -82,3 +83,5 @@ final piAuthenticatorGitHubUri = Uri.parse("https://github.com/privacyidea/pi-au // The highest version of the pipush Tokentype that this client supports. const maxPushTokenVersion = 1; + +WidgetRef? globalRef; diff --git a/lib/utils/home_widget_utils.dart b/lib/utils/home_widget_utils.dart index d2f244c81..af5b51f40 100644 --- a/lib/utils/home_widget_utils.dart +++ b/lib/utils/home_widget_utils.dart @@ -7,7 +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 '../utils/customization/theme_customization.dart'; + import '../interfaces/repo/token_folder_repository.dart'; import '../interfaces/repo/token_repository.dart'; import '../mains/main_netknights.dart'; @@ -20,6 +20,7 @@ import '../model/tokens/totp_token.dart'; import '../processors/scheme_processors/home_widget_processor.dart'; import '../repo/preference_token_folder_repository.dart'; import '../repo/secure_token_repository.dart'; +import '../utils/customization/theme_customization.dart'; import '../widgets/home_widgets/home_widget_action.dart'; import '../widgets/home_widgets/home_widget_background.dart'; import '../widgets/home_widgets/home_widget_configure.dart'; @@ -27,8 +28,10 @@ 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 'globals.dart'; import 'logger.dart'; -import 'riverpod_providers.dart'; +import 'riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import 'riverpod/riverpod_providers/state_providers/home_widget_provider.dart'; const appGroupId = 'group.authenticator_home_widget_group'; diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index c017ca7ef..6da325588 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -17,7 +17,7 @@ import '../utils/app_info_utils.dart'; import '../utils/pi_mailer.dart'; import '../views/settings_view/settings_view_widgets/send_error_dialog.dart'; import 'globals.dart'; -import 'riverpod_providers.dart'; +import 'riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; final provider = Provider((ref) => 0); diff --git a/lib/utils/privacyidea_io_client.dart b/lib/utils/privacyidea_io_client.dart index 63ad96cd0..6ec0d8e4f 100644 --- a/lib/utils/privacyidea_io_client.dart +++ b/lib/utils/privacyidea_io_client.dart @@ -29,8 +29,8 @@ import 'package:package_info_plus/package_info_plus.dart'; import '../l10n/app_localizations.dart'; import '../utils/globals.dart'; import '../utils/logger.dart'; -import '../utils/riverpod_providers.dart'; import '../utils/view_utils.dart'; +import 'riverpod/riverpod_providers/state_providers/status_message_provider.dart'; class PrivacyideaIOClient { const PrivacyideaIOClient(); diff --git a/lib/utils/push_provider.dart b/lib/utils/push_provider.dart index 3ecc8cf5c..9eb49d542 100644 --- a/lib/utils/push_provider.dart +++ b/lib/utils/push_provider.dart @@ -38,7 +38,9 @@ import 'firebase_utils.dart'; import 'globals.dart'; import 'logger.dart'; import 'privacyidea_io_client.dart'; -import 'riverpod_providers.dart'; +import 'riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import 'riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import 'rsa_utils.dart'; import 'utils.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart new file mode 100644 index 000000000..4312c9e44 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart @@ -0,0 +1,22 @@ +import 'package:app_links/app_links.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../model/deeplink.dart'; +import '../../../../state_notifiers/deeplink_notifier.dart'; +import '../../../home_widget_utils.dart'; +import '../../../logger.dart'; + +final deeplinkProvider = StateNotifierProvider( + (ref) { + Logger.info("New DeeplinkNotifier created", name: 'deeplinkProvider'); + return DeeplinkNotifier(sources: [ + DeeplinkSource(name: 'uni_links', stream: AppLinks().uriLinkStream, initialUri: AppLinks().getInitialLink()), + DeeplinkSource( + name: 'home_widget', + stream: HomeWidgetUtils().widgetClicked, + initialUri: HomeWidgetUtils().initiallyLaunchedFromHomeWidget(), + ), + ]); + }, + name: 'deeplinkProvider', +); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart new file mode 100644 index 000000000..a7893b673 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart @@ -0,0 +1,13 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../model/states/introduction_state.dart'; +import '../../../../repo/preference_introduction_repository.dart'; +import '../../../../state_notifiers/completed_introduction_notifier.dart'; +import '../../../logger.dart'; + +final introductionProvider = StateNotifierProvider( + (ref) { + Logger.info("New introductionProvider created", name: 'introductionProvider'); + return IntroductionNotifier(repository: PreferenceIntroductionRepository()); + }, +); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart new file mode 100644 index 000000000..ddec052d0 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart @@ -0,0 +1,10 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../model/states/progress_state.dart'; +import '../../../../state_notifiers/progress_state_notifier.dart'; +import '../../../logger.dart'; + +final progressStateProvider = StateNotifierProvider((ref) { + Logger.info("New ProgressStateNotifier created", name: 'progressStateProvider'); + return ProgressStateNotifier(); +}); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart new file mode 100644 index 000000000..c7f8458ed --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart @@ -0,0 +1,44 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../model/states/push_request_state.dart'; +import '../../../../state_notifiers/push_request_notifier.dart'; +import '../../../logger.dart'; +import '../../../push_provider.dart'; +import 'settings_provider.dart'; +import 'token_provider.dart'; + +final pushRequestProvider = StateNotifierProvider( + (ref) { + Logger.info("New PushRequestNotifier created", name: 'pushRequestProvider'); + final tokenState = ref.read(tokenProvider); + PushProvider pushProvider = tokenState.hasPushTokens ? PushProvider() : PlaceholderPushProvider(); // Until the state is loaded from the repo + final pushRequestNotifier = PushRequestNotifier( + ref: ref, + pushProvider: pushProvider, + ); + + ref.listen(tokenProvider, (previous, next) { + if (previous?.hasPushTokens == true && next.hasPushTokens == false) { + /// Last push token was deleted + Logger.info('Last push token was deleted. Deactivating push provider and deleting firebase token.', name: 'pushRequestProvider#tokenProvider'); + pushRequestNotifier.swapPushProvider(PlaceholderPushProvider()); + pushProvider.firebaseUtils.deleteFirebaseToken(); + } + if (previous?.hasPushTokens != true && next.hasPushTokens == true) { + /// First push token was added + Logger.info('First push token was added. Activating push provider.', name: 'pushRequestProvider#tokenProvider'); + pushRequestNotifier.swapPushProvider(PushProvider()); + } + }); + + ref.listen(settingsProvider, (previous, next) { + if (previous?.enablePolling != next.enablePolling) { + Logger.info("Polling enabled changed from ${previous?.enablePolling} to ${next.enablePolling}", name: 'pushRequestProvider#settingsProvider'); + pushRequestNotifier.pushProvider.setPollingEnabled(next.enablePolling); + } + }); + + return pushRequestNotifier; + }, + name: 'pushRequestProvider', +); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart new file mode 100644 index 000000000..e0b25b5a7 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart @@ -0,0 +1,13 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../model/states/settings_state.dart'; +import '../../../../repo/preference_settings_repository.dart'; +import '../../../../state_notifiers/settings_notifier.dart'; + +final settingsProvider = StateNotifierProvider( + (ref) { + // Using Logger here will cause a circular dependency because Logger uses settingsProvider (logging verbosity) + return SettingsNotifier(repository: PreferenceSettingsRepository()); + }, + name: 'settingsProvider', +); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart new file mode 100644 index 000000000..d5269dc93 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart @@ -0,0 +1,32 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../model/mixins/sortable_mixin.dart'; +import '../../../../model/states/token_folder_state.dart'; +import '../../../../model/states/token_state.dart'; +import '../../../../state_notifiers/sortable_notifier.dart'; +import '../../../logger.dart'; +import 'token_folder_provider.dart'; +import 'token_provider.dart'; + +final sortableProvider = StateNotifierProvider>( + (ref) { + final SortableNotifier notifier = SortableNotifier(ref: ref); + Logger.info("New sortableProvider created", name: 'sortableProvider'); + ref.listen(tokenProvider, (previous, next) => notifier.handleNewStateList(next.tokens)); + ref.listen(tokenFolderProvider, (previous, next) => notifier.handleNewStateList(next.folders)); + Future.wait( + [ref.read(tokenProvider.notifier).initState, ref.read(tokenFolderProvider.notifier).initState], + ).then((values) { + final sortables = []; + for (final v in values) { + if (v is TokenState) { + sortables.addAll(v.tokens); + } else if (v is TokenFolderState) { + sortables.addAll(v.folders); + } + } + notifier.handleNewStateList(sortables); + }); + return notifier; + }, +); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart new file mode 100644 index 000000000..4f048570b --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart @@ -0,0 +1,23 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../api/token_container_api_endpoint.dart'; +import '../../../../model/states/token_container_state.dart'; +import '../../../../repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; +import '../../../../repo/token_container_state_repositorys/preference_token_container_state_repository.dart.dart'; +import '../../../../repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; +import '../../../../state_notifiers/token_container_notifier.dart'; +import '../../../logger.dart'; + +final tokenContainerStateProvider = StateNotifierProvider((ref) { + Logger.info("New tokenContainerStateProvider created", name: 'tokenContainerStateProvider'); + return TokenContainerNotifier( + ref: ref, + repository: HybridTokenContainerStateRepository( + localRepository: PreferenceTokenContainerStateRepository('placeholder'), // TODO: Implement containerId + remoteRepository: RemoteTokenContainerStateRepository( + apiEndpoint: TokenContainerApiEndpoint(), // TODO: Nochmal anschauen + containerId: 'placeholder', // TODO: Implement containerId + ), + ), + ); +}); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart new file mode 100644 index 000000000..3762b8692 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart @@ -0,0 +1,14 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../model/states/token_folder_state.dart'; +import '../../../../repo/preference_token_folder_repository.dart'; +import '../../../../state_notifiers/token_folder_notifier.dart'; +import '../../../logger.dart'; + +final tokenFolderProvider = StateNotifierProvider( + (ref) { + Logger.info("New TokenFolderNotifier created", name: 'tokenFolderProvider'); + return TokenFolderNotifier(repository: PreferenceTokenFolderRepository()); + }, + name: 'tokenFolderProvider', +); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart new file mode 100644 index 000000000..e410c15ba --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart @@ -0,0 +1,25 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../model/states/token_state.dart'; +import '../../../../state_notifiers/token_notifier.dart'; +import '../../../logger.dart'; +import 'deeplink_provider.dart'; + +final tokenProvider = StateNotifierProvider( + (ref) { + Logger.info("New TokenNotifier created"); + final newTokenNotifier = TokenNotifier(ref: ref); + + ref.listen(deeplinkProvider, (previous, newLink) { + if (newLink == null) { + Logger.info("Received null deeplink", name: 'tokenProvider#deeplinkProvider'); + return; + } + Logger.info("Received new deeplink", name: 'tokenProvider#deeplinkProvider'); + newTokenNotifier.handleLink(newLink.uri); + }); + + return newTokenNotifier; + }, + name: 'tokenProvider', +); diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart new file mode 100644 index 000000000..fee317907 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart @@ -0,0 +1,11 @@ +import 'package:flutter/rendering.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../logger.dart'; + +final appConstraintsProvider = StateProvider( + (ref) { + Logger.info("New constraintsProvider created", name: 'appConstraintsProvider'); + return null; + }, +); diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart new file mode 100644 index 000000000..270dc4264 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart @@ -0,0 +1,8 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../customization/application_customization.dart'; + +/// Only used for the app customizer +final applicationCustomizerProvider = StateProvider((ref) { + return ApplicationCustomization.defaultCustomization; +}); diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart new file mode 100644 index 000000000..527529afd --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart @@ -0,0 +1,12 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../model/mixins/sortable_mixin.dart'; +import '../../../logger.dart'; + +final draggingSortableProvider = StateProvider( + (ref) { + Logger.info("New draggingSortableProvider created", name: 'draggingSortableProvider'); + return null; + }, + name: 'draggingSortableProvider', +); diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart new file mode 100644 index 000000000..033b3297f --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart @@ -0,0 +1,16 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../model/tokens/otp_token.dart'; +import '../../../home_widget_utils.dart'; +import '../../../logger.dart'; +import '../state_notifier_providers/token_provider.dart'; + +final homeWidgetProvider = StateProvider>( + (ref) { + Logger.info("New homeWidgetProvider created", name: 'homeWidgetProvider'); + ref.listen(tokenProvider, (previous, next) { + HomeWidgetUtils().updateTokensIfLinked(next.lastlyUpdatedTokens); + }); + return {}; + }, +); diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart new file mode 100644 index 000000000..9a93308d4 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart @@ -0,0 +1,10 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../logger.dart'; + +final statusMessageProvider = StateProvider<(String, String?)?>( + (ref) { + Logger.info("New statusMessageProvider created", name: 'statusMessageProvider'); + return null; + }, +); diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart new file mode 100644 index 000000000..4cee3e0a7 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart @@ -0,0 +1,5 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../model/states/token_filter.dart'; + +final tokenFilterProvider = StateProvider((ref) => null); diff --git a/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart b/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart new file mode 100644 index 000000000..5625d1b84 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart @@ -0,0 +1,26 @@ +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../l10n/app_localizations.dart'; +import '../../../globals.dart'; +import '../../../logger.dart'; +import '../state_notifier_providers/token_provider.dart'; +import '../state_providers/status_message_provider.dart'; + +final connectivityProvider = StreamProvider>( + (ref) { + Logger.info("New connectivityProvider created", name: 'connectivityProvider'); + ref.read(tokenProvider.notifier).initState.then( + (newState) { + Connectivity().checkConnectivity().then((connectivity) { + Logger.info("First connectivity check: $connectivity", name: 'connectivityProvider#initialCheck'); + final hasNoConnection = connectivity.contains(ConnectivityResult.none); + if (hasNoConnection && newState.hasPushTokens && globalNavigatorKey.currentContext != null) { + ref.read(statusMessageProvider.notifier).state = (AppLocalizations.of(globalNavigatorKey.currentContext!)!.noNetworkConnection, null); + } + }); + }, + ); + return Connectivity().onConnectivityChanged; + }, +); diff --git a/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart b/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart new file mode 100644 index 000000000..60f9f8148 --- /dev/null +++ b/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart @@ -0,0 +1,16 @@ +import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart'; +import '../../../model/deeplink.dart'; +import '../../../processors/scheme_processors/home_widget_processor.dart'; + +class HomeWidgetDeepLinkListener extends DeepLinkListener { + const HomeWidgetDeepLinkListener({ + required super.deeplinkProvider, + }) : super( + onNewState: _onNewState, + ); + + static void _onNewState(DeepLink? previous, DeepLink? next) { + if (next == null) return; + const HomeWidgetProcessor().processUri(next.uri, fromInit: next.fromInit); + } +} diff --git a/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart new file mode 100644 index 000000000..7bafd1844 --- /dev/null +++ b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart @@ -0,0 +1,9 @@ +import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; +import '../../../model/states/token_state.dart'; +import '../../home_widget_utils.dart'; + +class HomeWidgetTokenStateListener extends TokenStateListener { + const HomeWidgetTokenStateListener({required super.tokenProvider}) : super(onNewState: _onNewState); + + static void _onNewState(TokenState? previous, TokenState next) => HomeWidgetUtils().updateTokensIfLinked(next.lastlyUpdatedTokens); +} diff --git a/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart b/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart new file mode 100644 index 000000000..8abda01f1 --- /dev/null +++ b/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart'; +import '../../../model/deeplink.dart'; +import '../../../processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart'; + +class NavigationDeepLinkListener extends DeepLinkListener { + static BuildContext? _context; + NavigationDeepLinkListener({required super.deeplinkProvider, BuildContext? context}) + : super( + onNewState: (DeepLink? previous, DeepLink? next) { + _onNewState(previous, next); + }, + ) { + _context = context; + } + + static void _onNewState(DeepLink? previous, DeepLink? next) { + if (next == null) return; + NavigationSchemeProcessor.processUriByAny(next.uri, context: _context, fromInit: next.fromInit); + } +} diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart new file mode 100644 index 000000000..b5e3a9d47 --- /dev/null +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -0,0 +1,16 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; +import '../../../model/states/token_state.dart'; +import '../riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; + +class TokenContainerTokenStateListener extends TokenStateListener { + TokenContainerTokenStateListener({ + required super.tokenProvider, + required WidgetRef ref, + }) : super(onNewState: (TokenState? previous, TokenState next) => _onNewState(previous, next, ref)); + + static Future _onNewState(TokenState? previous, TokenState next, WidgetRef ref) async { + ref.read(tokenContainerStateProvider.notifier).updatedTokensIfContains(next.lastlyUpdatedTokens); + } +} diff --git a/lib/utils/riverpod_providers.dart b/lib/utils/riverpod_providers.dart deleted file mode 100644 index c82122843..000000000 --- a/lib/utils/riverpod_providers.dart +++ /dev/null @@ -1,210 +0,0 @@ -import 'package:app_links/app_links.dart'; -import 'package:connectivity_plus/connectivity_plus.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import '../l10n/app_localizations.dart'; -import '../model/mixins/sortable_mixin.dart'; -import '../model/states/introduction_state.dart'; -import '../model/states/progress_state.dart'; -import '../model/states/push_request_state.dart'; -import '../model/states/settings_state.dart'; -import '../model/states/token_filter.dart'; -import '../model/states/token_folder_state.dart'; -import '../model/states/token_state.dart'; -import '../model/tokens/otp_token.dart'; -import '../repo/preference_introduction_repository.dart'; -import '../repo/preference_settings_repository.dart'; -import '../repo/preference_token_folder_repository.dart'; -import '../state_notifiers/completed_introduction_notifier.dart'; -import '../state_notifiers/deeplink_notifier.dart'; -import '../state_notifiers/progress_state_notifier.dart'; -import '../state_notifiers/push_request_notifier.dart'; -import '../state_notifiers/settings_notifier.dart'; -import '../state_notifiers/sortable_notifier.dart'; -import '../state_notifiers/token_folder_notifier.dart'; -import '../state_notifiers/token_notifier.dart'; -import 'customization/application_customization.dart'; -import 'globals.dart'; -import 'home_widget_utils.dart'; -import 'logger.dart'; -import 'push_provider.dart'; -import 'riverpod_state_listener.dart'; - -/// Never use globalRef to .watch() a provider. only use it to .read() a provider -/// -/// Otherwise the whole app will rebuild on every state change of the provider -WidgetRef? globalRef; - -final tokenProvider = StateNotifierProvider( - (ref) { - Logger.info("New TokenNotifier created"); - final newTokenNotifier = TokenNotifier(ref: ref); - - ref.listen(deeplinkProvider, (previous, newLink) { - if (newLink == null) { - Logger.info("Received null deeplink", name: 'tokenProvider#deeplinkProvider'); - return; - } - Logger.info("Received new deeplink", name: 'tokenProvider#deeplinkProvider'); - newTokenNotifier.handleLink(newLink.uri); - }); - - return newTokenNotifier; - }, - name: 'tokenProvider', -); - -final settingsProvider = StateNotifierProvider( - (ref) { - // Using Logger here will cause a circular dependency because Logger uses settingsProvider (logging verbosity) - return SettingsNotifier(repository: PreferenceSettingsRepository()); - }, - name: 'settingsProvider', -); - -final pushRequestProvider = StateNotifierProvider( - (ref) { - Logger.info("New PushRequestNotifier created", name: 'pushRequestProvider'); - final tokenState = ref.read(tokenProvider); - PushProvider pushProvider = tokenState.hasPushTokens ? PushProvider() : PlaceholderPushProvider(); // Until the state is loaded from the repo - final pushRequestNotifier = PushRequestNotifier( - ref: ref, - pushProvider: pushProvider, - ); - - ref.listen(tokenProvider, (previous, next) { - if (previous?.hasPushTokens == true && next.hasPushTokens == false) { - /// Last push token was deleted - Logger.info('Last push token was deleted. Deactivating push provider and deleting firebase token.', name: 'pushRequestProvider#tokenProvider'); - pushRequestNotifier.swapPushProvider(PlaceholderPushProvider()); - pushProvider.firebaseUtils.deleteFirebaseToken(); - } - if (previous?.hasPushTokens != true && next.hasPushTokens == true) { - /// First push token was added - Logger.info('First push token was added. Activating push provider.', name: 'pushRequestProvider#tokenProvider'); - pushRequestNotifier.swapPushProvider(PushProvider()); - } - }); - - ref.listen(settingsProvider, (previous, next) { - if (previous?.enablePolling != next.enablePolling) { - Logger.info("Polling enabled changed from ${previous?.enablePolling} to ${next.enablePolling}", name: 'pushRequestProvider#settingsProvider'); - pushRequestNotifier.pushProvider.setPollingEnabled(next.enablePolling); - } - }); - - return pushRequestNotifier; - }, - name: 'pushRequestProvider', -); - -final deeplinkProvider = StateNotifierProvider( - (ref) { - Logger.info("New DeeplinkNotifier created", name: 'deeplinkProvider'); - return DeeplinkNotifier(sources: [ - DeeplinkSource(name: 'uni_links', stream: AppLinks().uriLinkStream, initialUri: AppLinks().getInitialLink()), - DeeplinkSource( - name: 'home_widget', - stream: HomeWidgetUtils().widgetClicked, - initialUri: HomeWidgetUtils().initiallyLaunchedFromHomeWidget(), - ), - ]); - }, - name: 'deeplinkProvider', -); - -final tokenFolderProvider = StateNotifierProvider( - (ref) { - Logger.info("New TokenFolderNotifier created", name: 'tokenFolderProvider'); - return TokenFolderNotifier(repository: PreferenceTokenFolderRepository()); - }, - name: 'tokenFolderProvider', -); - -final draggingSortableProvider = StateProvider( - (ref) { - Logger.info("New draggingSortableProvider created", name: 'draggingSortableProvider'); - return null; - }, - name: 'draggingSortableProvider', -); - -final tokenFilterProvider = StateProvider((ref) => null); - -final connectivityProvider = StreamProvider>( - (ref) { - Logger.info("New connectivityProvider created", name: 'connectivityProvider'); - ref.read(tokenProvider.notifier).initState.then( - (newState) { - Connectivity().checkConnectivity().then((connectivity) { - Logger.info("First connectivity check: $connectivity", name: 'connectivityProvider#initialCheck'); - final hasNoConnection = connectivity.contains(ConnectivityResult.none); - if (hasNoConnection && newState.hasPushTokens && globalNavigatorKey.currentContext != null) { - ref.read(statusMessageProvider.notifier).state = (AppLocalizations.of(globalNavigatorKey.currentContext!)!.noNetworkConnection, null); - } - }); - }, - ); - return Connectivity().onConnectivityChanged; - }, -); - -final statusMessageProvider = StateProvider<(String, String?)?>( - (ref) { - Logger.info("New statusMessageProvider created", name: 'statusMessageProvider'); - return null; - }, -); - -final introductionProvider = StateNotifierProvider( - (ref) { - Logger.info("New introductionProvider created", name: 'introductionProvider'); - return IntroductionNotifier(repository: PreferenceIntroductionRepository()); - }, -); - -final appConstraintsProvider = StateProvider( - (ref) { - Logger.info("New constraintsProvider created", name: 'appConstraintsProvider'); - return null; - }, -); - -final homeWidgetProvider = StateProvider>( - (ref) { - Logger.info("New homeWidgetProvider created", name: 'homeWidgetProvider'); - ref.listen(tokenProvider, (previous, next) { - HomeWidgetUtils().updateTokensIfLinked(next.lastlyUpdatedTokens); - }); - return {}; - }, -); - -final sortableProvider = StateNotifierProvider>( - (ref) { - final SortableNotifier notifier = SortableNotifier(ref); - Logger.info("New sortableProvider created", name: 'sortableProvider'); - ref.listen(tokenProvider, (previous, next) => notifier.handleNewStateList(next.tokens)); - ref.listen(tokenFolderProvider, (previous, next) => notifier.handleNewStateList(next.folders)); - Future.wait( - [ref.read(tokenProvider.notifier).initState, ref.read(tokenFolderProvider.notifier).initState], - ).then((values) { - final sortables = []; - for (final v in values) { - if (v is TokenState) { - sortables.addAll(v.tokens); - } else if (v is TokenFolderState) { - sortables.addAll(v.folders); - } - } - notifier.handleNewStateList(sortables); - }); - return notifier; - }, -); - -final progressStateProvider = StateNotifierProvider((ref) => ProgressStateNotifier()); - -/// Only used for the app customizer -final applicationCustomizerProvider = StateProvider((ref) => ApplicationCustomization.defaultCustomization); diff --git a/lib/utils/riverpod_state_listener.dart b/lib/utils/riverpod_state_listener.dart deleted file mode 100644 index 848b91f67..000000000 --- a/lib/utils/riverpod_state_listener.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import '../model/states/token_state.dart'; -import '../processors/scheme_processors/home_widget_processor.dart'; -import '../processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart'; -import '../state_notifiers/deeplink_notifier.dart'; -import '../state_notifiers/token_notifier.dart'; -import 'home_widget_utils.dart'; - -abstract class StateNotifierProviderListener, S> { - final StateNotifierProvider? provider; - final void Function(S? previous, S next)? onNewState; - const StateNotifierProviderListener({this.provider, this.onNewState}); - void buildListen(WidgetRef ref) { - if (provider == null || onNewState == null) return; - ref.listen(provider!, onNewState!); - } -} - -abstract class DeepLinkListener extends StateNotifierProviderListener { - const DeepLinkListener({ - required StateNotifierProvider deeplinkProvider, - required super.onNewState, - }) : super(provider: deeplinkProvider); -} - -class NavigationDeepLinkListener extends DeepLinkListener { - static BuildContext? _context; - NavigationDeepLinkListener({required super.deeplinkProvider, BuildContext? context}) - : super( - onNewState: (DeepLink? previous, DeepLink? next) { - _onNewState(previous, next); - }, - ) { - _context = context; - } - - static void _onNewState(DeepLink? previous, DeepLink? next) { - if (next == null) return; - NavigationSchemeProcessor.processUriByAny(next.uri, context: _context, fromInit: next.fromInit); - } -} - -class HomeWidgetDeepLinkListener extends DeepLinkListener { - const HomeWidgetDeepLinkListener({ - required StateNotifierProvider provider, - }) : super( - deeplinkProvider: provider, - onNewState: _onNewState, - ); - - static void _onNewState(DeepLink? previous, DeepLink? next) { - if (next == null) return; - const HomeWidgetProcessor().processUri(next.uri, fromInit: next.fromInit); - } -} - -abstract class TokenStateListener extends StateNotifierProviderListener { - const TokenStateListener({ - required StateNotifierProvider tokenProvider, - required super.onNewState, - }) : super(provider: tokenProvider); -} - -class HomeWidgetTokenStateListener extends TokenStateListener { - const HomeWidgetTokenStateListener({required super.tokenProvider}) : super(onNewState: _onNewState); - - static void _onNewState(TokenState? previous, TokenState next) => HomeWidgetUtils().updateTokensIfLinked(next.lastlyUpdatedTokens); -} - -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/rsa_utils.dart b/lib/utils/rsa_utils.dart index 820f63ca0..294807163 100644 --- a/lib/utils/rsa_utils.dart +++ b/lib/utils/rsa_utils.dart @@ -26,12 +26,13 @@ import 'package:flutter/foundation.dart'; import 'package:pi_authenticator_legacy/pi_authenticator_legacy.dart'; import 'package:pointycastle/export.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; + import '../l10n/app_localizations.dart'; import '../model/tokens/push_token.dart'; import '../utils/crypto_utils.dart'; import '../utils/identifiers.dart'; import '../utils/logger.dart'; -import 'riverpod_providers.dart'; +import 'riverpod/riverpod_providers/state_providers/status_message_provider.dart'; class RsaUtils { const RsaUtils(); diff --git a/lib/utils/validators.dart b/lib/utils/validators.dart index e8846bc44..794e81819 100644 --- a/lib/utils/validators.dart +++ b/lib/utils/validators.dart @@ -1,4 +1,4 @@ -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; +import '../l10n/app_localizations.dart'; class Validators { final AppLocalizations appLocalizations; 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 f2215b0d0..2d67575ea 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 @@ -14,7 +14,7 @@ import '../../model/extensions/enums/token_origin_source_type.dart'; import '../../model/tokens/token.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; -import '../../utils/riverpod_providers.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import 'add_token_manually_view_widgets/labeled_dropdown_button.dart'; class AddTokenManuallyView extends ConsumerStatefulWidget { diff --git a/lib/views/import_tokens_view/import_tokens_view.dart b/lib/views/import_tokens_view/import_tokens_view.dart index 93bf8297d..f6d48f154 100644 --- a/lib/views/import_tokens_view/import_tokens_view.dart +++ b/lib/views/import_tokens_view/import_tokens_view.dart @@ -4,7 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../l10n/app_localizations.dart'; import '../../model/token_import/token_import_origin.dart'; import '../../model/tokens/token.dart'; -import '../../utils/riverpod_providers.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../utils/token_import_origins.dart'; import '../view_interface.dart'; import 'pages/import_start_page.dart'; 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 2081f1d50..7ad7cffd6 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 @@ -7,7 +7,7 @@ import '../../../model/extensions/enums/token_import_type_extension.dart'; import '../../../model/processor_result.dart'; import '../../../model/token_import/token_import_entry.dart'; import '../../../model/tokens/token.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../import_tokens_view.dart'; import '../widgets/conflicted_import_tokens_list.dart'; import '../widgets/failed_imports_list.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 4898e26e0..1ce486842 100644 --- a/lib/views/import_tokens_view/pages/import_start_page.dart +++ b/lib/views/import_tokens_view/pages/import_start_page.dart @@ -4,7 +4,6 @@ import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:privacyidea_authenticator/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart'; import 'package:zxing2/zxing2.dart'; import '../../../l10n/app_localizations.dart'; @@ -18,10 +17,12 @@ import '../../../model/tokens/token.dart'; import '../../../processors/mixins/token_import_processor.dart'; import '../../../processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart'; import '../../../processors/token_import_file_processor/token_import_file_processor_interface.dart'; +import '../../../utils/globals.dart'; import '../../../utils/logger.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart'; import '../../qr_scanner_view/qr_scanner_view.dart'; import '../import_tokens_view.dart'; +import '../widgets/dialogs/qr_not_found_dialog.dart'; import 'import_encrypted_data_page.dart'; import 'import_plain_tokens_page.dart'; 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 7083e7c28..849b17187 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,12 +1,12 @@ import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; -import '../../../model/tokens/token.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 '../../../model/tokens/token.dart'; import '../import_tokens_view.dart'; import 'import_start_page.dart'; diff --git a/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart b/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart index 410a93e94..2cf43c18a 100644 --- a/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart +++ b/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart @@ -1,9 +1,9 @@ import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:image_cropper/image_cropper.dart'; -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; -import 'package:privacyidea_authenticator/utils/globals.dart'; +import '../../../../l10n/app_localizations.dart'; +import '../../../../utils/globals.dart'; import '../../../../utils/logger.dart'; import '../../../../widgets/dialog_widgets/default_dialog.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 4c3ef75b4..00b2d0860 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 @@ -4,7 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../utils/customization/extended_text_theme.dart'; import '../../utils/home_widget_utils.dart'; -import '../../utils/riverpod_providers.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../utils/utils.dart'; import '../view_interface.dart'; diff --git a/lib/views/main_view/main_view.dart b/lib/views/main_view/main_view.dart index d7c7a862a..a7c5a42f2 100644 --- a/lib/views/main_view/main_view.dart +++ b/lib/views/main_view/main_view.dart @@ -3,8 +3,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../l10n/app_localizations.dart'; import '../../model/states/token_filter.dart'; +import '../../utils/globals.dart'; import '../../utils/patch_notes_utils.dart'; -import '../../utils/riverpod_providers.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart'; import '../../widgets/push_request_listener.dart'; import '../../widgets/status_bar.dart'; import '../view_interface.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 98e87ac66..b078f832b 100644 --- a/lib/views/main_view/main_view_widgets/connectivity_listener.dart +++ b/lib/views/main_view/main_view_widgets/connectivity_listener.dart @@ -4,7 +4,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; import '../../../utils/logger.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart'; class ConnectivityListener extends ConsumerWidget { final Widget child; 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 f07fbb7e5..a6926d381 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 @@ -7,7 +7,8 @@ import '../../../model/extensions/sortable_list.dart'; import '../../../model/mixins/sortable_mixin.dart'; import '../../../model/token_folder.dart'; import '../../../model/tokens/token.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../widgets/drag_item_scroller.dart'; /// DragTargetDivider is used to create a divider that can be used to move a sortable up or down in the list diff --git a/lib/views/main_view/main_view_widgets/filter_token_widget.dart b/lib/views/main_view/main_view_widgets/filter_token_widget.dart index f07fd9922..5a2a06e35 100644 --- a/lib/views/main_view/main_view_widgets/filter_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/filter_token_widget.dart @@ -2,7 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../model/states/token_filter.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/globals.dart'; +import '../../../utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart'; class SearchTokenWidget extends StatelessWidget { final bool searchActive; diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart b/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart index 48389c67f..c3cbd27fd 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart @@ -3,7 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../model/enums/introduction.dart'; -import '../../../../utils/riverpod_providers.dart'; +import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import '../../../../widgets/dialog_widgets/default_dialog.dart'; class AddTokenFolderDialog extends ConsumerWidget { 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 12ab76272..f39881f0f 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 @@ -6,7 +6,8 @@ import '../../../../../model/token_folder.dart'; import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; class DeleteTokenFolderAction extends StatelessWidget { 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 763d708df..7dd701f5b 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 @@ -4,8 +4,9 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/token_folder.dart'; import '../../../../../utils/customization/action_theme.dart'; +import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; class LockTokenFolderAction extends StatelessWidget { final TokenFolder folder; 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 1646c70d1..3267dfd80 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 @@ -7,7 +7,7 @@ import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; class RenameTokenFolderAction extends StatelessWidget { 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 4bfabfab5..c16fffaa6 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 @@ -13,8 +13,12 @@ import '../../../../model/token_folder.dart'; import '../../../../model/tokens/push_token.dart'; import '../../../../model/tokens/token.dart'; import '../../../../utils/customization/action_theme.dart'; +import '../../../../utils/globals.dart'; import '../../../../utils/lock_auth.dart'; -import '../../../../utils/riverpod_providers.dart'; +import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../../widgets/custom_trailing.dart'; import '../drag_target_divider.dart'; import '../token_widgets/token_widget_builder.dart'; diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart index 941a11359..32ba38425 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart @@ -2,10 +2,10 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; import '../../../../model/token_folder.dart'; -import '../../../../utils/riverpod_providers.dart'; +import '../../../../utils/logger.dart'; +import '../../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../../utils/utils.dart'; import 'token_folder_expandable.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart index 74bfdd2c3..977c0db33 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart @@ -3,7 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/introduction.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; import '../../../widgets/focused_item_as_overlay.dart'; import '../../add_token_manually_view/add_token_manually_view.dart'; import '../../settings_view/settings_view.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart index 4760ff6e9..0b2650c43 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart @@ -3,7 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../model/enums/introduction.dart'; -import '../../../../utils/riverpod_providers.dart'; +import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import '../../../../widgets/focused_item_as_overlay.dart'; import '../../../license_view/license_view.dart'; import '../../../push_token_view/push_tokens_view.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart index 43eae41e7..de5bd8108 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart @@ -4,7 +4,7 @@ import 'package:permission_handler/permission_handler.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../utils/globals.dart'; -import '../../../../utils/riverpod_providers.dart'; +import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../utils/view_utils.dart'; import '../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../qr_scanner_view/qr_scanner_view.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 9584aabcc..9c5b3053b 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 @@ -8,7 +8,9 @@ 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 '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../widgets/deactivateable_refresh_indicator.dart'; import '../../../widgets/drag_item_scroller.dart'; import '../../../widgets/introduction_widgets/token_introduction.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart index 49db79cdd..d1c0d4613 100644 --- a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart +++ b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart @@ -6,7 +6,9 @@ import '../../../model/states/token_filter.dart'; import '../../../model/token_folder.dart'; import '../../../model/tokens/token.dart'; import '../../../utils/logger.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart'; import 'folder_widgets/token_folder_expandable.dart'; import 'token_widgets/token_widget_builder.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 5dba75405..684f7a8e8 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 @@ -7,7 +7,7 @@ import '../../../../../../model/tokens/day_password_token.dart'; import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; -import '../../../../../../utils/riverpod_providers.dart'; +import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../default_token_actions/default_edit_action_dialog.dart'; import '../../token_action.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart index 1208517b9..bc320c78b 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart @@ -8,7 +8,8 @@ import 'package:intl/intl.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/enums/day_password_token_view_mode.dart'; import '../../../../../model/tokens/day_password_token.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../../utils/utils.dart'; import '../../../../../widgets/custom_texts.dart'; import '../../../../../widgets/custom_trailing.dart'; @@ -51,9 +52,9 @@ class _DayPasswordTokenWidgetTileState extends ConsumerState p0.copyWith(viewMode: DayPasswordTokenViewMode.VALIDUNTIL)); + ref.read(tokenProvider.notifier).updateToken(widget.token, (p0) => p0.copyWith(viewMode: DayPasswordTokenViewMode.VALIDUNTIL)); return; } if (widget.token.viewMode == DayPasswordTokenViewMode.VALIDUNTIL) { - globalRef?.read(tokenProvider.notifier).updateToken(widget.token, (p0) => p0.copyWith(viewMode: DayPasswordTokenViewMode.VALIDFOR)); + ref.read(tokenProvider.notifier).updateToken(widget.token, (p0) => p0.copyWith(viewMode: DayPasswordTokenViewMode.VALIDFOR)); return; } }, 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 730d20408..e515af2fa 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 @@ -6,7 +6,7 @@ import '../../../../../model/tokens/token.dart'; import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../loading_indicator.dart'; import '../token_action.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 206ab295a..90b327702 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 @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/enums/introduction.dart'; @@ -10,9 +9,11 @@ import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../../widgets/focused_item_as_overlay.dart'; import '../token_action.dart'; +import 'default_edit_action_dialog.dart'; class DefaultEditAction extends TokenAction { final Token token; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart index 088ce6843..52c3f4d58 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart @@ -2,11 +2,12 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; +import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; +import '../../../../../utils/globals.dart'; import '../../../../../utils/logger.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; class DefaultEditActionDialog extends ConsumerStatefulWidget { 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 3fcae56e9..631761f65 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 @@ -5,9 +5,11 @@ import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/enums/introduction.dart'; import '../../../../../model/tokens/token.dart'; import '../../../../../utils/customization/action_theme.dart'; +import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../../widgets/focused_item_as_overlay.dart'; import '../token_action.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 cc6280d89..6870a96d3 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 @@ -8,7 +8,7 @@ import '../../../../../../model/tokens/hotp_token.dart'; import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; -import '../../../../../../utils/riverpod_providers.dart'; +import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../default_token_actions/default_edit_action_dialog.dart'; import '../../token_action.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart index 4db3c83e4..e88dd4f8d 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart @@ -4,7 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/hotp_token.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/globals.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../../utils/utils.dart'; import '../../../../../widgets/custom_texts.dart'; import '../../../../../widgets/custom_trailing.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 b1692caa4..da806d7e7 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 @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart'; import '../../../../../../l10n/app_localizations.dart'; import '../../../../../../model/enums/introduction.dart'; @@ -9,9 +8,11 @@ import '../../../../../../model/tokens/push_token.dart'; import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; -import '../../../../../../utils/riverpod_providers.dart'; +import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../../../widgets/enable_text_form_field_after_many_taps.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; +import '../../default_token_actions/default_edit_action_dialog.dart'; import '../../token_action.dart'; class EditPushTokenAction extends TokenAction { diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart index a6d8c1fb4..a6a449802 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart @@ -4,7 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/enums/introduction.dart'; import '../../../../../model/tokens/push_token.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; import '../../../../../widgets/custom_trailing.dart'; import '../../../../../widgets/focused_item_as_overlay.dart'; import '../token_widget_tile.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 d1ac6ceae..f9c28c5e4 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 @@ -5,7 +5,8 @@ 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'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../../widgets/press_button.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart index e7d1b6688..d6a51cfb8 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart @@ -2,11 +2,12 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; import '../../../../model/mixins/sortable_mixin.dart'; import '../../../../model/tokens/token.dart'; -import '../../../../utils/riverpod_providers.dart'; +import '../../../../utils/globals.dart'; +import '../../../../utils/logger.dart'; +import '../../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../../utils/utils.dart'; import 'default_token_actions/default_delete_action.dart'; import 'default_token_actions/default_edit_action.dart'; 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 08275ed36..b023ff736 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 @@ -7,7 +7,7 @@ import '../../../../../../model/tokens/totp_token.dart'; import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; -import '../../../../../../utils/riverpod_providers.dart'; +import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../default_token_actions/default_edit_action_dialog.dart'; import '../../token_action.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart index ea0f23b4a..930630184 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart @@ -4,7 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/totp_token.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/globals.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../../utils/utils.dart'; import '../../../../../widgets/custom_texts.dart'; import '../../../../../widgets/custom_trailing.dart'; diff --git a/lib/views/push_token_view/widgets/push_tokens_view_list.dart b/lib/views/push_token_view/widgets/push_tokens_view_list.dart index 508192dd7..c9c236537 100644 --- a/lib/views/push_token_view/widgets/push_tokens_view_list.dart +++ b/lib/views/push_token_view/widgets/push_tokens_view_list.dart @@ -4,7 +4,8 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../model/mixins/sortable_mixin.dart'; import '../../../utils/push_provider.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../widgets/deactivateable_refresh_indicator.dart'; import '../../../widgets/drag_item_scroller.dart'; import '../../main_view/main_view_widgets/loading_indicator.dart'; diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/export_tokens_to_file_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/export_tokens_to_file_dialog.dart index f1ce1101e..e799d82e2 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/export_tokens_to_file_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/export_tokens_to_file_dialog.dart @@ -10,7 +10,7 @@ import '../../../../../mains/main_netknights.dart'; import '../../../../../model/tokens/token.dart'; import '../../../../../utils/encryption/token_encryption.dart'; import '../../../../../utils/lock_auth.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import '../../../../../utils/validators.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.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 7bc6656a0..2c2db61b1 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,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:privacyidea_authenticator/utils/lock_auth.dart'; import '../../../../../l10n/app_localizations.dart'; +import '../../../../../utils/lock_auth.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../settings_view_widgets/settings_list_tile_button.dart'; import 'export_tokens_to_file_dialog.dart'; diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart index 538318be0..1e1f73e78 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart @@ -3,7 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../main_view/main_view_widgets/token_widgets/token_widget_builder.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 a30666d5a..5433f6693 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 @@ -9,7 +9,7 @@ import 'package:zxing2/qrcode.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; import '../../../../../utils/encryption/token_encryption.dart'; -import '../../../../../utils/riverpod_providers.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; class ShowQrCodeDialog extends ConsumerWidget { diff --git a/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart b/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart index e745bb823..795b058b3 100644 --- a/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart +++ b/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart @@ -1,11 +1,11 @@ import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/model/enums/introduction.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; -import 'package:privacyidea_authenticator/widgets/countdown_button.dart'; import '../../../l10n/app_localizations.dart'; +import '../../../model/enums/introduction.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../widgets/countdown_button.dart'; import '../../../widgets/dialog_widgets/default_dialog.dart'; import '../../import_tokens_view/import_tokens_view.dart'; import '../settings_view_widgets/settings_groups.dart'; diff --git a/lib/views/settings_view/settings_groups/settings_group_language.dart b/lib/views/settings_view/settings_groups/settings_group_language.dart index 133b99554..c848b2500 100644 --- a/lib/views/settings_view/settings_groups/settings_group_language.dart +++ b/lib/views/settings_view/settings_groups/settings_group_language.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import '../settings_view_widgets/settings_groups.dart'; class SettingsGroupLanguage extends ConsumerWidget { 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 c3979df0f..5a1635526 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 @@ -3,7 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/tokens/push_token.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../settings_view_widgets/settings_groups.dart'; import '../settings_view_widgets/update_firebase_token_dialog.dart'; diff --git a/lib/views/settings_view/settings_view.dart b/lib/views/settings_view/settings_view.dart index b2e1fd3b0..274be7a51 100644 --- a/lib/views/settings_view/settings_view.dart +++ b/lib/views/settings_view/settings_view.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../l10n/app_localizations.dart'; import '../../model/tokens/push_token.dart'; -import '../../utils/riverpod_providers.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../widgets/push_request_listener.dart'; import '../view_interface.dart'; import 'settings_groups/settings_group_error_log.dart'; diff --git a/lib/views/settings_view/settings_view_widgets/logging_menu.dart b/lib/views/settings_view/settings_view_widgets/logging_menu.dart index 466f4f08a..6e396a656 100644 --- a/lib/views/settings_view/settings_view_widgets/logging_menu.dart +++ b/lib/views/settings_view/settings_view_widgets/logging_menu.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import '../../../widgets/dialog_widgets/default_dialog.dart'; import 'errorlog_buttons/delete_errorlog_button.dart'; import 'errorlog_buttons/send_errorlog_button.dart'; diff --git a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart index 20f2bb288..44792821e 100644 --- a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart +++ b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart @@ -25,7 +25,7 @@ import '../../../l10n/app_localizations.dart'; import '../../../model/tokens/push_token.dart'; import '../../../utils/globals.dart'; import '../../../utils/logger.dart'; -import '../../../utils/riverpod_providers.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../utils/view_utils.dart'; import '../../../widgets/dialog_widgets/default_dialog.dart'; diff --git a/lib/views/splash_screen/splash_screen.dart b/lib/views/splash_screen/splash_screen.dart index e934c0f19..a29211b30 100644 --- a/lib/views/splash_screen/splash_screen.dart +++ b/lib/views/splash_screen/splash_screen.dart @@ -6,7 +6,10 @@ import '../../utils/app_info_utils.dart'; import '../../utils/customization/application_customization.dart'; import '../../utils/home_widget_utils.dart'; import '../../utils/logger.dart'; -import '../../utils/riverpod_providers.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../main_view/main_view.dart'; import '../view_interface.dart'; diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index ff6e1d901..a3cb3a263 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -7,8 +7,14 @@ 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 '../utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../utils/riverpod/state_listeners/home_widget_deep_link_listener.dart'; +import '../utils/riverpod/state_listeners/home_widget_token_state_listener.dart'; +import '../utils/riverpod/state_listeners/navigation_deep_link_listener.dart'; +import '../utils/riverpod/state_listeners/token_container_token_state_listener.dart'; import 'app_wrappers/single_touch_recognizer.dart'; import 'app_wrappers/state_observer.dart'; @@ -82,7 +88,9 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { child: StateObserver( listeners: [ NavigationDeepLinkListener(deeplinkProvider: deeplinkProvider), + HomeWidgetDeepLinkListener(deeplinkProvider: deeplinkProvider), // TODO: Nochmal anschauen HomeWidgetTokenStateListener(tokenProvider: tokenProvider), + TokenContainerTokenStateListener(tokenProvider: tokenProvider, ref: ref), ], child: EasyDynamicThemeWidget( child: widget.child, diff --git a/lib/widgets/app_wrappers/state_observer.dart b/lib/widgets/app_wrappers/state_observer.dart index 582305636..38d27643a 100644 --- a/lib/widgets/app_wrappers/state_observer.dart +++ b/lib/widgets/app_wrappers/state_observer.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../utils/riverpod_state_listener.dart'; +import '../../interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart'; + class StateObserver extends ConsumerWidget { final List listeners; diff --git a/lib/widgets/countdown_button.dart b/lib/widgets/countdown_button.dart index ea55944cb..4b5ce5295 100644 --- a/lib/widgets/countdown_button.dart +++ b/lib/widgets/countdown_button.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:privacyidea_authenticator/model/extensions/color_extension.dart'; + +import '../model/extensions/color_extension.dart'; class CountdownButton extends StatefulWidget { final int countdownSeconds; diff --git a/lib/widgets/dialog_widgets/patch_notes_dialog.dart b/lib/widgets/dialog_widgets/patch_notes_dialog.dart index ffb810431..0f8c3d232 100644 --- a/lib/widgets/dialog_widgets/patch_notes_dialog.dart +++ b/lib/widgets/dialog_widgets/patch_notes_dialog.dart @@ -7,7 +7,8 @@ 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 '../../utils/globals.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import 'default_dialog.dart'; class PatchNotesDialog extends StatelessWidget { diff --git a/lib/widgets/dialog_widgets/push_request_dialog.dart b/lib/widgets/dialog_widgets/push_request_dialog.dart index e84e66ad2..2697d433d 100644 --- a/lib/widgets/dialog_widgets/push_request_dialog.dart +++ b/lib/widgets/dialog_widgets/push_request_dialog.dart @@ -10,7 +10,8 @@ import '../../model/tokens/push_token.dart'; import '../../utils/globals.dart'; import '../../utils/lock_auth.dart'; import '../../utils/logger.dart'; -import '../../utils/riverpod_providers.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../press_button.dart'; import 'default_dialog.dart'; diff --git a/lib/widgets/drag_item_scroller.dart b/lib/widgets/drag_item_scroller.dart index 25db850ec..7b6abdf32 100644 --- a/lib/widgets/drag_item_scroller.dart +++ b/lib/widgets/drag_item_scroller.dart @@ -4,8 +4,8 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../utils/globals.dart'; import '../utils/logger.dart'; -import '../utils/riverpod_providers.dart'; final dragItemScrollerStateProvider = StateProvider((ref) => false); diff --git a/lib/widgets/enable_text_form_field_after_many_taps.dart b/lib/widgets/enable_text_form_field_after_many_taps.dart index 32b803f1b..27e7fe943 100644 --- a/lib/widgets/enable_text_form_field_after_many_taps.dart +++ b/lib/widgets/enable_text_form_field_after_many_taps.dart @@ -1,4 +1,5 @@ import 'dart:async'; + import 'package:flutter/material.dart'; class EnableTextFormFieldAfterManyTaps extends StatefulWidget { diff --git a/lib/widgets/focused_item_as_overlay.dart b/lib/widgets/focused_item_as_overlay.dart index 0fc768d0a..d65184255 100644 --- a/lib/widgets/focused_item_as_overlay.dart +++ b/lib/widgets/focused_item_as_overlay.dart @@ -7,7 +7,7 @@ import 'package:flutter/material.dart'; import '../l10n/app_localizations.dart'; import '../utils/globals.dart'; import '../utils/logger.dart'; -import '../utils/riverpod_providers.dart'; +import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; import '../utils/utils.dart'; import 'pulse_icon.dart'; import 'tooltip_container.dart'; diff --git a/lib/widgets/hideable_widget_.dart b/lib/widgets/hideable_widget_.dart index 217dc9ba5..05966cf36 100644 --- a/lib/widgets/hideable_widget_.dart +++ b/lib/widgets/hideable_widget_.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../l10n/app_localizations.dart'; import '../model/tokens/otp_token.dart'; -import '../utils/riverpod_providers.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; class HideableWidget extends ConsumerWidget { final OTPToken token; diff --git a/lib/widgets/introduction_widgets/token_introduction.dart b/lib/widgets/introduction_widgets/token_introduction.dart index 4a001c2d4..f63385d30 100644 --- a/lib/widgets/introduction_widgets/token_introduction.dart +++ b/lib/widgets/introduction_widgets/token_introduction.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../l10n/app_localizations.dart'; import '../../model/enums/introduction.dart'; -import '../../utils/riverpod_providers.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; import '../focused_item_as_overlay.dart'; class TokenIntroduction extends ConsumerWidget { diff --git a/lib/widgets/push_request_listener.dart b/lib/widgets/push_request_listener.dart index 15aa0fda4..20a820811 100644 --- a/lib/widgets/push_request_listener.dart +++ b/lib/widgets/push_request_listener.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../utils/riverpod_providers.dart'; import '../utils/push_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart'; import 'dialog_widgets/push_request_dialog.dart'; class PushRequestListener extends ConsumerStatefulWidget { diff --git a/lib/widgets/status_bar.dart b/lib/widgets/status_bar.dart index 339f0c917..e87543ced 100644 --- a/lib/widgets/status_bar.dart +++ b/lib/widgets/status_bar.dart @@ -3,7 +3,7 @@ import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../utils/riverpod_providers.dart'; +import '../utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import '../utils/utils.dart'; class StatusBar extends ConsumerStatefulWidget { diff --git a/pubspec.yaml b/pubspec.yaml index 54a683f18..add1c4105 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ publish_to: none # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 4.4.0+404008 # TODO Set the right version number +version: 4.4.0+404008 # TODO Set the right version number # version: major.minor.build + 2x major|2x minor|3x build # version: version number + build number (optional) @@ -49,7 +49,7 @@ dependencies: hex: ^0.2.0 base32: ^2.1.1 pointycastle: ^3.7.3 - asn1lib: ^1.5.0 + asn1lib: ^1.5.0 encrypt: ^5.0.3 cryptography: ^2.7.0 otp: ^3.0.1 @@ -89,7 +89,7 @@ dependencies: path_provider: ^2.1.2 qr_flutter: ^4.1.0 image_cropper: ^7.1.0 - + dev_dependencies: flutter_driver: @@ -104,8 +104,8 @@ dev_dependencies: # dependencies to serialize objects to json - build_runner: ^2.4.6 - json_serializable: ^6.0.1 + build_runner: ^2.4.11 + json_serializable: ^6.8.0 # For information on the generic Dart part of this file, see the diff --git a/test/unit_test/state_notifiers/deeplink_notifier_test.dart b/test/unit_test/state_notifiers/deeplink_notifier_test.dart index 5de94c3b3..bc9634e7b 100644 --- a/test/unit_test/state_notifiers/deeplink_notifier_test.dart +++ b/test/unit_test/state_notifiers/deeplink_notifier_test.dart @@ -1,8 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:privacyidea_authenticator/model/deeplink.dart'; import 'package:privacyidea_authenticator/state_notifiers/deeplink_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_state_listener.dart'; - void main() { _testDeeplinkNotifier(); } diff --git a/test/unit_test/state_notifiers/sortable_notifier_test.dart b/test/unit_test/state_notifiers/sortable_notifier_test.dart index 2d411e08a..b7cb307e9 100644 --- a/test/unit_test/state_notifiers/sortable_notifier_test.dart +++ b/test/unit_test/state_notifiers/sortable_notifier_test.dart @@ -11,7 +11,10 @@ import 'package:privacyidea_authenticator/state_notifiers/settings_notifier.dart import 'package:privacyidea_authenticator/state_notifiers/sortable_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/riverpod_providers.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../tests_app_wrapper.mocks.dart'; diff --git a/test/unit_test/state_notifiers/token_notifier_test.dart b/test/unit_test/state_notifiers/token_notifier_test.dart index 6020583af..7c562f2c5 100644 --- a/test/unit_test/state_notifiers/token_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_notifier_test.dart @@ -21,7 +21,7 @@ import 'package:privacyidea_authenticator/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/firebase_utils.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; -import 'package:privacyidea_authenticator/utils/riverpod_providers.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; import 'token_notifier_test.mocks.dart'; From 404784fe3892890424e902d425bf7cbebf283ae0 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 16 Jul 2024 13:55:13 +0200 Subject: [PATCH 002/285] refactoring --- .../google_authenticator_qrfile_processor.dart | 1 - .../import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart | 2 +- lib/widgets/app_wrapper.dart | 2 +- lib/widgets/countdown_button.dart | 2 -- lib/widgets/tooltip_container.dart | 2 +- 5 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart index 543b0b332..2037637f5 100644 --- a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart +++ b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart @@ -114,7 +114,6 @@ Future _decodeQrImageIsolate(List args) async { if (rotation > 0) { image = img_lib.copyRotate(image, angle: rotation); } - print("Image: ${image.width}x${image.height} Rotation: $rotation Zoom: $zoomLevel"); LuminanceSource source = RGBLuminanceSource( image.width, diff --git a/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart b/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart index 2cf43c18a..3fb95eedd 100644 --- a/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart +++ b/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart @@ -54,7 +54,7 @@ class QrNotFoundDialog extends StatelessWidget { } catch (e) { if (!context.mounted) return; Navigator.of(context).pop(); - globalSnackbarKey.currentState?.showSnackBar(const SnackBar(content: const Text("File not currently available! Please try again."))); + globalSnackbarKey.currentState?.showSnackBar(const SnackBar(content: Text("File not currently available! Please try again."))); return; } if (!context.mounted) return; diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index a3cb3a263..a7a45a667 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -59,7 +59,7 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { if (await ref.read(tokenProvider.notifier).saveStateOnMinimizeApp() == false) { Logger.error('Failed to save tokens on Hide', name: 'tokenProvider#appStateProvider'); } - if (ref.read(tokenFolderProvider.notifier).collapseLockedFolders() == false) { + if (await ref.read(tokenFolderProvider.notifier).collapseLockedFolders() == false) { Logger.error('Failed to collapse locked folders on Hide', name: 'tokenFolderProvider#appStateProvider'); } await FlutterLocalNotificationsPlugin().cancelAll(); diff --git a/lib/widgets/countdown_button.dart b/lib/widgets/countdown_button.dart index 4b5ce5295..33d84cb65 100644 --- a/lib/widgets/countdown_button.dart +++ b/lib/widgets/countdown_button.dart @@ -58,8 +58,6 @@ class _CountdownButtonState extends State with SingleTickerProv color2 = color1; color1 = temp; }); - print(currentCount); - print(currentCount > 0); return currentCount > 0; } diff --git a/lib/widgets/tooltip_container.dart b/lib/widgets/tooltip_container.dart index 3bbe74dca..ba3afbbdb 100644 --- a/lib/widgets/tooltip_container.dart +++ b/lib/widgets/tooltip_container.dart @@ -25,7 +25,7 @@ class TooltipContainer extends StatelessWidget { border: Border.all(color: Theme.of(context).primaryColor, width: border), boxShadow: [ BoxShadow( - color: Theme.of(context).colorScheme.onBackground.withOpacity(0.3), + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.3), blurRadius: 8, spreadRadius: 1, ), From e7d0e80f9efa92a99e203e52a73bade99b0c2b12 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Wed, 17 Jul 2024 17:20:51 +0200 Subject: [PATCH 003/285] smartphone as a container --- lib/interfaces/repo/container_repository.dart | 15 + lib/l10n/app_en.arb | 10 +- lib/model/states/token_container_state.dart | 256 +++++++++++++++--- lib/model/states/token_container_state.g.dart | 62 +++-- lib/model/token_container.dart | 30 +- lib/model/token_container.g.dart | 20 +- lib/model/tokens/day_password_token.dart | 8 + lib/model/tokens/hotp_token.dart | 26 ++ lib/model/tokens/otp_token.dart | 27 ++ lib/model/tokens/push_token.dart | 12 + lib/model/tokens/steam_token.dart | 22 ++ lib/model/tokens/token.dart | 21 ++ lib/model/tokens/totp_token.dart | 28 +- ...brid_token_container_state_repository.dart | 95 ++++++- ...token_container_state_repository.dart.dart | 85 ++++-- .../token_container_notifier.dart | 60 +++- lib/utils/identifiers.dart | 3 + .../token_container_state_provider.dart | 5 +- .../home_widget_token_state_listener.dart | 4 +- .../token_container_token_state_listener.dart | 15 +- 20 files changed, 690 insertions(+), 114 deletions(-) diff --git a/lib/interfaces/repo/container_repository.dart b/lib/interfaces/repo/container_repository.dart index 978cbe013..d2839083f 100644 --- a/lib/interfaces/repo/container_repository.dart +++ b/lib/interfaces/repo/container_repository.dart @@ -1,6 +1,21 @@ +import 'package:privacyidea_authenticator/model/token_container.dart'; + import '../../model/states/token_container_state.dart'; abstract class TokenContainerStateRepository { + /// Save the container state to the repository + /// Returns the state that was actually written Future saveContainerState(TokenContainerState containerState); + + /// Load the container state from the repository + /// Returns the loaded state Future loadContainer(); + + /// Save a token template to the repository + /// Returns the template that was actually written + Future saveTokenTemplate(TokenTemplate tokenTemplate); + + /// Load a token template from the repository + /// Returns the loaded template + Future loadTokenTemplate(String tokenTemplateId); } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d79c2a9b8..dc6280247 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -696,5 +696,13 @@ "qrInFileNotFound2": "You can show me where the QR code is located.", "qrInFileNotFound3": "I expect i will find the code if it is in the middle of the marked area.", "markQrCode": "Mark QR Code", - "malformedData": "Malformed data" + "malformedData": "Malformed data", + "failedToLoad": "Failed to load: \"{name}\"", + "@failedToLoad": { + "placeholders": { + "name": { + "example": "token data" + } + } + } } \ No newline at end of file diff --git a/lib/model/states/token_container_state.dart b/lib/model/states/token_container_state.dart index 7c301545b..4fb37f74f 100644 --- a/lib/model/states/token_container_state.dart +++ b/lib/model/states/token_container_state.dart @@ -1,7 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; import '../token_container.dart'; -import '../tokens/token.dart'; part 'token_container_state.g.dart'; @@ -14,18 +13,14 @@ sealed class TokenContainerState extends TokenContainer { required super.containerId, required super.description, required super.type, - required super.tokens, + required super.tokenTemplates, }); - factory TokenContainerState.uninitialized() => const TokenContainerStateUninitialized( - containerId: '', - description: '', - type: '', - tokens: [], - ); + factory TokenContainerState.uninitialized() => const TokenContainerStateUninitialized(); factory TokenContainerState.fromJson(Map json) => switch (json['type']) { const ('TokenContainerStateUninitialized') => _$TokenContainerStateUninitializedFromJson(json), + const ('TokenContainerStateModified') => _$TokenContainerStateModifiedFromJson(json), const ('TokenContainerStateSynced') => _$TokenContainerStateSyncedFromJson(json), const ('TokenContainerStateSyncing') => _$TokenContainerStateSyncingFromJson(json), const ('TokenContainerStateUnsynced') => _$TokenContainerStateUnsyncedFromJson(json), @@ -39,6 +34,7 @@ sealed class TokenContainerState extends TokenContainer { Map toJson() { final json = switch (this) { TokenContainerStateUninitialized() => _$TokenContainerStateUninitializedToJson(this as TokenContainerStateUninitialized), + TokenContainerStateModified() => _$TokenContainerStateModifiedToJson(this as TokenContainerStateModified), TokenContainerStateSynced() => _$TokenContainerStateSyncedToJson(this as TokenContainerStateSynced), TokenContainerStateSyncing() => _$TokenContainerStateSyncingToJson(this as TokenContainerStateSyncing), TokenContainerStateUnsynced() => _$TokenContainerStateUnsyncedToJson(this as TokenContainerStateUnsynced), @@ -50,45 +46,49 @@ sealed class TokenContainerState extends TokenContainer { return json; } - T copyStateWith({ + @override + TokenContainerState copyWith({ + String? containerId, + String? description, + String? type, + List? tokenTemplates, + DateTime? lastSyncedAt, + }); + + T copyTransformInto({ dynamic data, DateTime? dateTime, DateTime? lastSyncedAt, String? containerId, String? description, String? type, - List? tokens, + List? tokenTemplates, }) { final copied = copyWith( containerId: containerId, description: description, type: type, - tokens: tokens, - ) as TokenContainerState; + tokenTemplates: tokenTemplates, + ); return copied.as(data: data, dateTime: dateTime); } T as({dynamic data, DateTime? dateTime}) => switch (T) { - const (TokenContainerStateUninitialized) => TokenContainerStateUninitialized( - containerId: containerId, - description: description, - type: type, - tokens: tokens, - ) as T, + const (TokenContainerStateUninitialized) => const TokenContainerStateUninitialized() as T, const (TokenContainerStateSynced) => TokenContainerStateSynced( - lastSyncedAt: lastSyncedAt ?? DateTime.now(), + lastSyncedAt: lastSyncedAt ?? lastSyncedAt ?? DateTime.now(), containerId: containerId, description: description, type: type, - tokens: tokens, + tokenTemplates: tokenTemplates, ) as T, const (TokenContainerStateSyncing) => TokenContainerStateSyncing( lastSyncedAt: lastSyncedAt, - syncStartedAt: dateTime ?? DateTime.now(), + syncStartedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), containerId: containerId, description: description, type: type, - tokens: tokens, + tokenTemplates: tokenTemplates, ) as T, const (TokenContainerStateUnsynced) => this is TokenContainerStateUnsynced ? (this as TokenContainerStateUnsynced).withIncrementedSyncAttempts() as T @@ -98,7 +98,7 @@ sealed class TokenContainerState extends TokenContainer { containerId: containerId, description: description, type: type, - tokens: tokens, + tokenTemplates: tokenTemplates, ) as T, const (TokenContainerStateError) => TokenContainerStateError( error: data, @@ -106,25 +106,25 @@ sealed class TokenContainerState extends TokenContainer { containerId: containerId, description: description, type: type, - tokens: tokens, + tokenTemplates: tokenTemplates, ) as T, const (TokenContainerStateDeactivated) => TokenContainerStateDeactivated( reason: data, - deactivatedAt: dateTime ?? DateTime.now(), + deactivatedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), lastSyncedAt: lastSyncedAt, containerId: containerId, description: description, type: type, - tokens: tokens, + tokenTemplates: tokenTemplates, ) as T, const (TokenContainerStateDeleted) => TokenContainerStateDeleted( reason: data, - deletedAt: dateTime ?? DateTime.now(), + deletedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), lastSyncedAt: lastSyncedAt, containerId: containerId, description: description, type: type, - tokens: tokens, + tokenTemplates: tokenTemplates, ) as T, _ => throw UnimplementedError(), }; @@ -133,14 +133,69 @@ sealed class TokenContainerState extends TokenContainer { /// ContainerState is not initialized @JsonSerializable() class TokenContainerStateUninitialized extends TokenContainerState { - const TokenContainerStateUninitialized({ + const TokenContainerStateUninitialized() + : super( + lastSyncedAt: null, + containerId: '', + description: '', + type: '', + tokenTemplates: const [], + ); + + @override + TokenContainerStateUnsynced copyWith({ + String? containerId, + String? description, + String? type, + List? tokenTemplates, + DateTime? lastSyncedAt, + }) { + return TokenContainerStateUnsynced( + lastSyncedAt: lastSyncedAt ?? lastSyncedAt, + containerId: containerId ?? this.containerId, + description: description ?? this.description, + type: type ?? this.type, + tokenTemplates: tokenTemplates ?? this.tokenTemplates, + ); + } +} + +/// ContainerState is modified +@JsonSerializable() +class TokenContainerStateModified extends TokenContainerState { + DateTime lastModifiedAt; + bool highPriority; + TokenContainerStateModified({ + required this.lastModifiedAt, + required this.highPriority, + required DateTime lastSyncedAt, required super.containerId, required super.description, required super.type, - required super.tokens, - }) : super(lastSyncedAt: null); -} + required super.tokenTemplates, + }) : super(lastSyncedAt: lastSyncedAt); + @override + TokenContainerStateModified copyWith({ + String? containerId, + String? description, + String? type, + List? tokenTemplates, + DateTime? lastSyncedAt, + DateTime? lastModifiedAt, + bool? highPriority, + }) { + return TokenContainerStateModified( + lastModifiedAt: lastModifiedAt ?? this.lastModifiedAt, + highPriority: highPriority ?? this.highPriority, + lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt ?? DateTime.now(), + containerId: containerId ?? this.containerId, + description: description ?? this.description, + type: type ?? this.type, + tokenTemplates: tokenTemplates ?? this.tokenTemplates, + ); + } +} /// ContainerState is successfully synced with repo @JsonSerializable() class TokenContainerStateSynced extends TokenContainerState { @@ -149,8 +204,25 @@ class TokenContainerStateSynced extends TokenContainerState { required super.containerId, required super.description, required super.type, - required super.tokens, + required super.tokenTemplates, }) : super(lastSyncedAt: lastSyncedAt); + + @override + TokenContainerStateSynced copyWith({ + String? containerId, + String? description, + String? type, + List? tokenTemplates, + DateTime? lastSyncedAt, + }) { + return TokenContainerStateSynced( + lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt ?? DateTime.now(), + containerId: containerId ?? this.containerId, + description: description ?? this.description, + type: type ?? this.type, + tokenTemplates: tokenTemplates ?? this.tokenTemplates, + ); + } } /// ContainerState is currently syncing @@ -167,8 +239,30 @@ class TokenContainerStateSyncing extends TokenContainerState { required super.containerId, required super.description, required super.type, - required super.tokens, + required super.tokenTemplates, }); + + @override + TokenContainerStateSyncing copyWith({ + String? containerId, + String? description, + String? type, + List? tokenTemplates, + DateTime? lastSyncedAt, + DateTime? syncStartedAt, + Duration? timeOut, + }) { + return TokenContainerStateSyncing( + syncStartedAt: syncStartedAt ?? this.syncStartedAt, + timeOut: timeOut ?? this.timeOut, + lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, + containerId: containerId ?? this.containerId, + description: description ?? this.description, + type: type ?? this.type, + tokenTemplates: tokenTemplates ?? this.tokenTemplates, + ); + } + } /// ContainerState is failed last sync attempt @@ -184,7 +278,7 @@ class TokenContainerStateUnsynced extends TokenContainerState { required super.containerId, required super.description, required super.type, - required super.tokens, + required super.tokenTemplates, }); TokenContainerStateUnsynced withIncrementedSyncAttempts() => withSyncAttempts(syncAttempts + 1); @@ -194,8 +288,28 @@ class TokenContainerStateUnsynced extends TokenContainerState { containerId: containerId, description: description, type: type, - tokens: tokens, + tokenTemplates: tokenTemplates, ); + + @override + TokenContainerStateUnsynced copyWith({ + String? containerId, + String? description, + String? type, + List? tokenTemplates, + DateTime? lastSyncedAt, + int? syncAttempts, + }) { + return TokenContainerStateUnsynced( + syncAttempts: syncAttempts ?? this.syncAttempts, + lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, + containerId: containerId ?? this.containerId, + description: description ?? this.description, + type: type ?? this.type, + tokenTemplates: tokenTemplates ?? this.tokenTemplates, + ); + } + } /// ContainerState is in error state @@ -208,8 +322,28 @@ class TokenContainerStateError extends TokenContainerState { required super.containerId, required super.description, required super.type, - required super.tokens, + required super.tokenTemplates, }); + + @override + TokenContainerStateError copyWith({ + String? containerId, + String? description, + String? type, + List? tokenTemplates, + DateTime? lastSyncedAt, + dynamic error, + }) { + return TokenContainerStateError( + error: error ?? this.error, + lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, + containerId: containerId ?? this.containerId, + description: description ?? this.description, + type: type ?? this.type, + tokenTemplates: tokenTemplates ?? this.tokenTemplates, + ); + } + } /// ContainerState is deactivated @@ -224,8 +358,30 @@ class TokenContainerStateDeactivated extends TokenContainerState { required super.containerId, required super.description, required super.type, - required super.tokens, + required super.tokenTemplates, }); + + @override + TokenContainerStateDeactivated copyWith({ + String? containerId, + String? description, + String? type, + List? tokenTemplates, + DateTime? lastSyncedAt, + DateTime? deactivatedAt, + dynamic reason, + }) { + return TokenContainerStateDeactivated( + reason: reason ?? this.reason, + deactivatedAt: deactivatedAt ?? deactivatedAt ?? DateTime.now(), + lastSyncedAt: lastSyncedAt ?? lastSyncedAt, + containerId: containerId ?? this.containerId, + description: description ?? this.description, + type: type ?? this.type, + tokenTemplates: tokenTemplates ?? this.tokenTemplates, + ); + } + } /// ContainerState is deleted repo-side @@ -240,6 +396,28 @@ class TokenContainerStateDeleted extends TokenContainerState { required super.containerId, required super.description, required super.type, - required super.tokens, + required super.tokenTemplates, }); + + @override + TokenContainerStateDeleted copyWith({ + String? containerId, + String? description, + String? type, + List? tokenTemplates, + DateTime? lastSyncedAt, + DateTime? deletedAt, + dynamic reason, + }) { + return TokenContainerStateDeleted( + reason: reason ?? this.reason, + deletedAt: deletedAt ?? this.deletedAt, + lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, + containerId: containerId ?? this.containerId, + description: description ?? this.description, + type: type ?? this.type, + tokenTemplates: tokenTemplates ?? this.tokenTemplates, + ); + } + } diff --git a/lib/model/states/token_container_state.g.dart b/lib/model/states/token_container_state.g.dart index a62228da9..b5d71be12 100644 --- a/lib/model/states/token_container_state.g.dart +++ b/lib/model/states/token_container_state.g.dart @@ -8,22 +8,36 @@ part of 'token_container_state.dart'; TokenContainerStateUninitialized _$TokenContainerStateUninitializedFromJson( Map json) => - TokenContainerStateUninitialized( + TokenContainerStateUninitialized(); + +Map _$TokenContainerStateUninitializedToJson( + TokenContainerStateUninitialized instance) => + {}; + +TokenContainerStateModified _$TokenContainerStateModifiedFromJson( + Map json) => + TokenContainerStateModified( + lastModifiedAt: DateTime.parse(json['lastModifiedAt'] as String), + highPriority: json['highPriority'] as bool, + lastSyncedAt: DateTime.parse(json['lastSyncedAt'] as String), containerId: json['containerId'] as String, description: json['description'] as String, type: json['type'] as String, - tokens: (json['tokens'] as List) - .map((e) => Token.fromJson(e as Map)) + tokenTemplates: (json['tokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), ); -Map _$TokenContainerStateUninitializedToJson( - TokenContainerStateUninitialized instance) => +Map _$TokenContainerStateModifiedToJson( + TokenContainerStateModified instance) => { 'containerId': instance.containerId, 'description': instance.description, 'type': instance.type, - 'tokens': instance.tokens, + 'tokenTemplates': instance.tokenTemplates, + 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), + 'lastModifiedAt': instance.lastModifiedAt.toIso8601String(), + 'highPriority': instance.highPriority, }; TokenContainerStateSynced _$TokenContainerStateSyncedFromJson( @@ -33,8 +47,8 @@ TokenContainerStateSynced _$TokenContainerStateSyncedFromJson( containerId: json['containerId'] as String, description: json['description'] as String, type: json['type'] as String, - tokens: (json['tokens'] as List) - .map((e) => Token.fromJson(e as Map)) + tokenTemplates: (json['tokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), ); @@ -44,7 +58,7 @@ Map _$TokenContainerStateSyncedToJson( 'containerId': instance.containerId, 'description': instance.description, 'type': instance.type, - 'tokens': instance.tokens, + 'tokenTemplates': instance.tokenTemplates, 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), }; @@ -61,8 +75,8 @@ TokenContainerStateSyncing _$TokenContainerStateSyncingFromJson( containerId: json['containerId'] as String, description: json['description'] as String, type: json['type'] as String, - tokens: (json['tokens'] as List) - .map((e) => Token.fromJson(e as Map)) + tokenTemplates: (json['tokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), ); @@ -72,7 +86,7 @@ Map _$TokenContainerStateSyncingToJson( 'containerId': instance.containerId, 'description': instance.description, 'type': instance.type, - 'tokens': instance.tokens, + 'tokenTemplates': instance.tokenTemplates, 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), 'syncStartedAt': instance.syncStartedAt.toIso8601String(), 'timeOut': instance.timeOut.inMicroseconds, @@ -89,8 +103,8 @@ TokenContainerStateUnsynced _$TokenContainerStateUnsyncedFromJson( containerId: json['containerId'] as String, description: json['description'] as String, type: json['type'] as String, - tokens: (json['tokens'] as List) - .map((e) => Token.fromJson(e as Map)) + tokenTemplates: (json['tokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), ); @@ -100,7 +114,7 @@ Map _$TokenContainerStateUnsyncedToJson( 'containerId': instance.containerId, 'description': instance.description, 'type': instance.type, - 'tokens': instance.tokens, + 'tokenTemplates': instance.tokenTemplates, 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), 'syncAttempts': instance.syncAttempts, 'lastError': instance.lastError, @@ -116,8 +130,8 @@ TokenContainerStateError _$TokenContainerStateErrorFromJson( containerId: json['containerId'] as String, description: json['description'] as String, type: json['type'] as String, - tokens: (json['tokens'] as List) - .map((e) => Token.fromJson(e as Map)) + tokenTemplates: (json['tokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), ); @@ -127,7 +141,7 @@ Map _$TokenContainerStateErrorToJson( 'containerId': instance.containerId, 'description': instance.description, 'type': instance.type, - 'tokens': instance.tokens, + 'tokenTemplates': instance.tokenTemplates, 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), 'error': instance.error, }; @@ -143,8 +157,8 @@ TokenContainerStateDeactivated _$TokenContainerStateDeactivatedFromJson( containerId: json['containerId'] as String, description: json['description'] as String, type: json['type'] as String, - tokens: (json['tokens'] as List) - .map((e) => Token.fromJson(e as Map)) + tokenTemplates: (json['tokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), ); @@ -154,7 +168,7 @@ Map _$TokenContainerStateDeactivatedToJson( 'containerId': instance.containerId, 'description': instance.description, 'type': instance.type, - 'tokens': instance.tokens, + 'tokenTemplates': instance.tokenTemplates, 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), 'deactivatedAt': instance.deactivatedAt.toIso8601String(), 'reason': instance.reason, @@ -171,8 +185,8 @@ TokenContainerStateDeleted _$TokenContainerStateDeletedFromJson( containerId: json['containerId'] as String, description: json['description'] as String, type: json['type'] as String, - tokens: (json['tokens'] as List) - .map((e) => Token.fromJson(e as Map)) + tokenTemplates: (json['tokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), ); @@ -182,7 +196,7 @@ Map _$TokenContainerStateDeletedToJson( 'containerId': instance.containerId, 'description': instance.description, 'type': instance.type, - 'tokens': instance.tokens, + 'tokenTemplates': instance.tokenTemplates, 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), 'deletedAt': instance.deletedAt.toIso8601String(), 'reason': instance.reason, diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index 1ec626e6d..5e777a097 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -1,6 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; - -import 'tokens/token.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; part 'token_container.g.dart'; @@ -9,13 +9,13 @@ class TokenContainer { final String containerId; final String description; final String type; - final List tokens; + final List tokenTemplates; const TokenContainer({ required this.containerId, required this.description, required this.type, - required this.tokens, + required this.tokenTemplates, }); factory TokenContainer.fromJson(Map json) => _$TokenContainerFromJson(json); @@ -25,13 +25,31 @@ class TokenContainer { String? containerId, String? description, String? type, - List? tokens, + List? tokenTemplates, }) { return TokenContainer( containerId: containerId ?? this.containerId, description: description ?? this.description, type: type ?? this.type, - tokens: tokens ?? this.tokens, + tokenTemplates: tokenTemplates ?? this.tokenTemplates, ); } } + +@JsonSerializable() +class TokenTemplate { + final Map data; + + String? get id { + final id = data[TOKEN_ID]; + if (id is! String?) { + Logger.error('TokenTemplate id is not a string'); + } + return id; + } + + TokenTemplate({required this.data}); + + factory TokenTemplate.fromJson(Map json) => _$TokenTemplateFromJson(json); + Map toJson() => _$TokenTemplateToJson(this); +} diff --git a/lib/model/token_container.g.dart b/lib/model/token_container.g.dart index 012ef32db..7deb29fd0 100644 --- a/lib/model/token_container.g.dart +++ b/lib/model/token_container.g.dart @@ -11,8 +11,8 @@ TokenContainer _$TokenContainerFromJson(Map json) => containerId: json['containerId'] as String, description: json['description'] as String, type: json['type'] as String, - tokens: (json['tokens'] as List) - .map((e) => Token.fromJson(e as Map)) + tokenTemplates: (json['tokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), ); @@ -21,5 +21,19 @@ Map _$TokenContainerToJson(TokenContainer instance) => 'containerId': instance.containerId, 'description': instance.description, 'type': instance.type, - 'tokens': instance.tokens, + 'tokenTemplates': instance.tokenTemplates, + }; + +TokenTemplate _$TokenTemplateFromJson(Map json) => + TokenTemplate( + data: json['data'] as Map, + modifiedAt: DateTime.parse(json['modifiedAt'] as String), + highPriority: json['highPriority'] as bool? ?? false, + ); + +Map _$TokenTemplateToJson(TokenTemplate instance) => + { + 'modifiedAt': instance.modifiedAt.toIso8601String(), + 'highPriority': instance.highPriority, + 'data': instance.data, }; diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index dc6076b5b..c979d43e4 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -138,6 +138,14 @@ class DayPasswordToken extends OTPToken { ); } + @override + Map toUriMap() { + return super.toUriMap() + ..addAll({ + URI_PERIOD: period.inSeconds, + }); + } + /// Validates the uriMap for the required fields throws [LocalizedArgumentError] if a field is missing or invalid. static void validateUriMap(Map uriMap) { if (uriMap[URI_SECRET] == null) { diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index c24f05a25..533e285c7 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -113,6 +113,32 @@ class HOTPToken extends OTPToken { ); } + /// ```dart + /// URI_TYPE: tokenType, + /// URI_COUNTER: counter, + /// ``` + /// ------ OTPTOKEN ------ + /// ```dart + /// URI_SECRET: Encodings.base32.decode(secret), + /// URI_ALGORITHM: algorithm.name, + /// URI_DIGITS: digits, + /// ``` + /// ------- TOKEN --------- + /// ```dart + /// URI_LABEL: label, + /// URI_ISSUER: issuer, + /// URI_PIN: pin, + /// URI_IMAGE: tokenImage, + /// URI_ORIGIN: jsonEncode(origin!.toJson()), + /// ``` + @override + Map toUriMap() { + return super.toUriMap() + ..addAll({ + URI_COUNTER: counter, + }); + } + /// Validates the uriMap for the required fields throws [LocalizedArgumentError] if a field is missing or invalid. static void validateUriMap(Map uriMap) { if (uriMap[URI_SECRET] == null) { diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index 36711210e..2b664081a 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -1,5 +1,9 @@ +import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; + import '../../utils/logger.dart'; import '../enums/algorithms.dart'; +import '../enums/encodings.dart'; import '../token_import/token_origin_data.dart'; import 'token.dart'; @@ -61,4 +65,27 @@ abstract class OTPToken extends Token { String toString() { return 'OTP${super.toString()}algorithm: $algorithm, digits: $digits, pin: $pin, '; } + + /// ```dart + /// URI_SECRET: Encodings.base32.decode(secret), + /// URI_ALGORITHM: algorithm.name, + /// URI_DIGITS: digits, + /// ``` + /// ------- TOKEN --------- + /// ```dart + /// URI_LABEL: label, + /// URI_ISSUER: issuer, + /// URI_PIN: pin, + /// URI_IMAGE: tokenImage, + /// URI_ORIGIN: jsonEncode(origin!.toJson()), + /// ``` + @override + Map toUriMap() { + return super.toUriMap() + ..addAll({ + URI_SECRET: Encodings.base32.decode(secret), + URI_ALGORITHM: algorithm.name, + URI_DIGITS: digits, + }); + } } diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index 3239f2191..c5b7aba96 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -178,6 +178,18 @@ class PushToken extends Token { origin: uriMap[URI_ORIGIN], ); + @override + Map toUriMap() { + return super.toUriMap() + ..addAll({ + URI_SERIAL: serial, + URI_SSL_VERIFY: sslVerify, + if (expirationDate != null) URI_TTL: expirationDate!.difference(DateTime.now()).inMinutes, + if (enrollmentCredentials != null) URI_ENROLLMENT_CREDENTIAL: enrollmentCredentials, + if (url != null) URI_ROLLOUT_URL: url.toString(), + }); + } + factory PushToken.fromJson(Map json) { final newToken = _$PushTokenFromJson(json); final currentRolloutState = switch (newToken.rolloutState) { diff --git a/lib/model/tokens/steam_token.dart b/lib/model/tokens/steam_token.dart index 4800e9b84..0eea73e64 100644 --- a/lib/model/tokens/steam_token.dart +++ b/lib/model/tokens/steam_token.dart @@ -123,6 +123,28 @@ class SteamToken extends TOTPToken { ); } + /// ----- TOTP TOKEN ----- + /// ```dart + /// URI_TYPE: tokenType, + /// URI_PERIOD: period, + /// ``` + /// ------ OTP TOKEN ------ + /// ```dart + /// URI_SECRET: Encodings.base32.decode(secret), + /// URI_ALGORITHM: algorithm.name, + /// URI_DIGITS: digits, + /// ``` + /// ------- TOKEN --------- + /// ```dart + /// URI_LABEL: label, + /// URI_ISSUER: issuer, + /// URI_PIN: pin, + /// URI_IMAGE: tokenImage, + /// URI_ORIGIN: jsonEncode(origin!.toJson()), + /// ``` + @override + Map toUriMap() => super.toUriMap(); + static SteamToken fromJson(Map json) => _$SteamTokenFromJson(json); @override Map toJson() => _$SteamTokenToJson(this); diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index 45357d90d..b3cba704f 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import '../../utils/identifiers.dart'; @@ -114,4 +116,23 @@ abstract class Token with SortableMixin { } Map toJson(); + + /// ```dart + /// URI_LABEL: label, + /// URI_ISSUER: issuer, + /// URI_PIN: pin, + /// URI_IMAGE: tokenImage, + /// URI_ORIGIN: jsonEncode(origin!.toJson()), + /// ``` + Map toUriMap() { + return { + TOKEN_ID: id, + URI_TYPE: type, + URI_LABEL: label, + URI_ISSUER: issuer, + URI_PIN: pin, + if (tokenImage != null) URI_IMAGE: tokenImage, + if (origin != null) URI_ORIGIN: jsonEncode(origin!.toJson()) + }; + } } diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index 868fbbcbd..b2f4609b7 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -21,6 +21,7 @@ class TOTPToken extends OTPToken { // this value is used to calculate the current 'counter' of this token // based on the UNIX systemtime), the counter is used to calculate the // current otp value + final int period; @override Duration get showDuration { @@ -29,7 +30,6 @@ class TOTPToken extends OTPToken { return duration; } - final int period; String otpFromTime(DateTime time) => algorithm.generateTOTPCodeString( secret: secret, time: time, @@ -123,6 +123,32 @@ class TOTPToken extends OTPToken { origin: uriMap[URI_ORIGIN], ); } + /// ----- TOTP TOKEN ----- + /// ```dart + /// URI_TYPE: tokenType, + /// URI_PERIOD: period, + /// ``` + /// ------ OTP TOKEN ------ + /// ```dart + /// URI_SECRET: Encodings.base32.decode(secret), + /// URI_ALGORITHM: algorithm.name, + /// URI_DIGITS: digits, + /// ``` + /// ------- TOKEN --------- + /// ```dart + /// URI_LABEL: label, + /// URI_ISSUER: issuer, + /// URI_PIN: pin, + /// URI_IMAGE: tokenImage, + /// URI_ORIGIN: jsonEncode(origin!.toJson()), + /// ``` + @override + Map toUriMap() { + return super.toUriMap() + ..addAll({ + URI_PERIOD: period, + }); + } /// Validates the uriMap for the required fields throws [LocalizedArgumentError] if a field is missing or invalid. static void validateUriMap(Map uriMap) { diff --git a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart index ff1cdd689..b25586721 100644 --- a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart @@ -1,46 +1,125 @@ +import 'package:privacyidea_authenticator/model/token_container.dart'; +import 'package:privacyidea_authenticator/utils/errors.dart'; +import 'package:collection/collection.dart'; + import '../../interfaces/repo/container_repository.dart'; import '../../model/states/token_container_state.dart'; +import '../../utils/logger.dart'; class HybridTokenContainerStateRepository implements TokenContainerStateRepository { final RemoteRepo _remoteRepository; final LocalRepo _localRepository; + final LocalRepo _syncedRepository; HybridTokenContainerStateRepository({ required RemoteRepo remoteRepository, required LocalRepo localRepository, + required LocalRepo syncedRepository, }) : _remoteRepository = remoteRepository, - _localRepository = localRepository; + _localRepository = localRepository, + _syncedRepository = syncedRepository; @override Future loadContainer({bool isInitial = false}) async { - final remoteState = await _remoteRepository.loadContainer(); - if (isInitial) return remoteState; - final localState = await _localRepository.loadContainer(); - return _merge(localState, remoteState); + TokenContainerState? remoteState; + TokenContainerState localState; + TokenContainerState newState; + try { + localState = await _localRepository.loadContainer(); + } catch (e) { + return TokenContainerStateError( + error: LocalizedException( + unlocalizedMessage: 'Failed to load local container state', + localizedMessage: (localization) => localization.failedToLoad('local container state'), + ), + lastSyncedAt: null, + containerId: '', + description: '', + type: '', + tokenTemplates: [], + ); + } + try { + remoteState = await _remoteRepository.loadContainer(); + } catch (e) { + newState = localState.copyTransformInto(); + return newState; + } + newState = _merge(localState, remoteState); + if (newState is TokenContainerStateSynced) { + try { + await _syncedRepository.saveContainerState(newState); + } catch (e) { + Logger.error('Failed to save synced state to local repository'); + } + } + return newState; } @override Future saveContainerState(TokenContainerState containerState) async { + if (containerState is TokenContainerStateError) { + Logger.warning('Cannot save error state to repository'); + return containerState; + } TokenContainerState remoteState; TokenContainerState newState; try { remoteState = await _remoteRepository.loadContainer(); } catch (e) { - newState = containerState.copyStateWith(); + newState = containerState.copyTransformInto(); return _localRepository.saveContainerState(newState); } newState = _merge(containerState, remoteState); try { await _remoteRepository.saveContainerState(newState); } catch (e) { - newState = newState.copyStateWith(); + newState = newState.copyTransformInto(); } await _localRepository.saveContainerState(newState); return newState; } TokenContainerState _merge(TokenContainerState localState, TokenContainerState remoteState) { - throw UnimplementedError(); + if (localState is TokenContainerStateUninitialized) { + // Uninitialized state is always overwritten by the other state + return remoteState; + } + if (remoteState is TokenContainerStateUninitialized) { + // Uninitialized state is always overwritten by the other state + return localState; + } + for (var localTemplate in localState.tokenTemplates) { + final remoteTemplate = remoteState.tokenTemplates.firstWhere((template) => template.id == localTemplate.id, orElse: () => localTemplate); + if (remoteTemplate != localTemplate) { + localTemplate.merge(remoteTemplate); + } + } + } + + /// Merges local and remote token templates with the last synced state + /// If both local and remote templates have changed, the remote changes are prioritized + Future _mergeTemplates(TokenTemplate local, TokenTemplate remote) async { + assert(local.id == remote.id); + final id = local.id; + final syncedTemplates = (await _syncedRepository.loadContainer()).tokenTemplates; + final synced = syncedTemplates.firstWhere((template) => template.id == id, orElse: () => TokenTemplate(data: {})); + final remoteDifferences = _getTemplateDifferences(synced.data, remote.data); + final localDifferences = _getTemplateDifferences(synced.data, local.data); + final mergedData = synced.data + ..addAll(localDifferences) + ..addAll(remoteDifferences); + return TokenTemplate(data: mergedData); + } + + Map _getTemplateDifferences(Map snyced, Map newState) { + final differences = {}; + for (var key in newState.keys) { + if (snyced[key] != newState[key]) { + differences[key] = newState[key]; + } + } + return differences; } } diff --git a/lib/repo/token_container_state_repositorys/preference_token_container_state_repository.dart.dart b/lib/repo/token_container_state_repositorys/preference_token_container_state_repository.dart.dart index 1dee7b247..5a8cbe707 100644 --- a/lib/repo/token_container_state_repositorys/preference_token_container_state_repository.dart.dart +++ b/lib/repo/token_container_state_repositorys/preference_token_container_state_repository.dart.dart @@ -1,23 +1,53 @@ import 'dart:convert'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:mutex/mutex.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; import '../../interfaces/repo/container_repository.dart'; import '../../model/states/token_container_state.dart'; -class PreferenceTokenContainerStateRepository implements TokenContainerStateRepository { +class SecureTokenContainerStateRepository implements TokenContainerStateRepository { static String prefix = 'token_container_state_'; - final String containerId; - final Future _prefs = SharedPreferences.getInstance(); + String get containerIdKey => _keyOf('containerId'); + String _keyOf(String id) => prefix + repoName + id; - PreferenceTokenContainerStateRepository(this.containerId); + final Mutex _m = Mutex(); + Future _protect(Future Function() f) => _m.protect(f); + + final String repoName; + final FlutterSecureStorage _storage = const FlutterSecureStorage(); + + SecureTokenContainerStateRepository({required this.repoName}); + + Future _write(String key, String value) => _protect(() => _storage.write(key: key, value: value)); + Future _read(String key) async { + String? value; + await _protect(() async => value = await _storage.read(key: key)); + return value; + } + + Future _delete(String key) => _protect(() => _storage.delete(key: key)); + Future> _readAll() async { + Map? keys; + await _protect(() async => keys = await _storage.readAll()); + keys!.removeWhere((key, value) => !key.startsWith(prefix + repoName)); + return keys!; + } @override - /// Save the container state to the shared preferences - /// Returns the state that was actually written to the shared preferences + Future saveContainerState(TokenContainerState containerState) async { - (await _prefs).setString(prefix + containerId, jsonEncode(containerState.toJson())); + for (var template in containerState.tokenTemplates) { + if (template.id == null) { + Logger.warning('Cannot save token template without id'); + continue; + } + _write(_keyOf(template.id!), jsonEncode(template.toJson())); + } + _write(containerIdKey, containerState.containerId); return containerState; } @@ -25,15 +55,36 @@ class PreferenceTokenContainerStateRepository implements TokenContainerStateRepo /// Load the container state from the shared preferences Future loadContainer() async { - final jsonString = (await _prefs).getString(prefix + containerId); - if (jsonString == null) { - return TokenContainerStateUninitialized( - containerId: containerId, - description: '', - type: '', - tokens: [], - ); + final keys = await _storage.readAll(); + final templates = []; + for (var key in keys.keys) { + if (key.startsWith(prefix + repoName)) { + final templateJson = await _storage.read(key: key); + if (templateJson == null) { + Logger.warning('Failed to read token template from shared preferences'); + continue; + } + final templateMap = jsonDecode(templateJson); + templates.add(TokenTemplate.fromJson(templateMap)); + } } - return TokenContainerState.fromJson(jsonDecode(jsonString)); + return TokenContainerState( + containerId: '', + description: '', + type: '', + tokenTemplates: templates, + ); + } + + @override + Future loadTokenTemplate(String tokenTemplateId) { + // TODO: implement loadTokenTemplate + throw UnimplementedError(); + } + + @override + Future saveTokenTemplate(TokenTemplate tokenTemplate) { + // TODO: implement saveTokenTemplate + throw UnimplementedError(); } } diff --git a/lib/state_notifiers/token_container_notifier.dart b/lib/state_notifiers/token_container_notifier.dart index 858aabef2..6848c9dd6 100644 --- a/lib/state_notifiers/token_container_notifier.dart +++ b/lib/state_notifiers/token_container_notifier.dart @@ -1,4 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mutex/mutex.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; import '../interfaces/repo/container_repository.dart'; import '../model/states/token_container_state.dart'; @@ -6,32 +8,70 @@ import '../model/tokens/token.dart'; class TokenContainerNotifier extends StateNotifier { final TokenContainerStateRepository _repository; - final StateNotifierProviderRef _ref; + late Future initState; + // final StateNotifierProviderRef _ref; + final Mutex _repoMutex = Mutex(); + final Mutex _updateMutex = Mutex(); TokenContainerNotifier({ - required StateNotifierProviderRef ref, + // required StateNotifierProviderRef ref, required TokenContainerStateRepository repository, TokenContainerState? initState, }) : _repository = repository, - _ref = ref, + // _ref = ref, super(initState ?? TokenContainerState.uninitialized()) { _init(); } Future _init() async { - throw UnimplementedError(); + initState = _loadFromRepo(); + state = await initState; } - Future loadContainerState() async { + + + + Future _loadFromRepo() async { + await _repoMutex.acquire(); final containerState = await _repository.loadContainer(); - state = containerState; + _repoMutex.release(); + return containerState; } - Future saveContainerState() async { - await _repository.saveContainerState(state); + Future _saveToRepo(TokenContainerState state) async { + await _repoMutex.acquire(); + final newState = await _repository.saveContainerState(state); + _repoMutex.release(); + return newState; } - Future updatedTokensIfContains(List lastlyUpdatedTokens) async { - throw UnimplementedError(); + Future addToken(Token token) async { + await _updateMutex.acquire(); + final newState = state.copyTransformInto( + tokenTemplates: [...state.tokenTemplates, TokenTemplate(data: token.toUriMap())], + ); + final savedState = await _saveToRepo(newState); + state = savedState; + _updateMutex.release(); + return savedState; + } + + Future updateTemplates(List updatedTemplates) async { + await _updateMutex.acquire(); + final newState = state.copyTransformInto( + tokenTemplates: state.tokenTemplates.map((oldToken) { + final updatedToken = updatedTemplates.firstWhere( + (newToken) => newToken.id == oldToken.id, + orElse: () => oldToken, + ); + return updatedToken; + }).toList(), + ); + final savedState = await _saveToRepo(newState); + state = savedState; + _updateMutex.release(); + return savedState; } } + + diff --git a/lib/utils/identifiers.dart b/lib/utils/identifiers.dart index 3b3811ddc..01396dc26 100644 --- a/lib/utils/identifiers.dart +++ b/lib/utils/identifiers.dart @@ -65,6 +65,9 @@ const String PUSH_REQUEST_SSL_VERIFY = 'sslverify'; // 6. const String PUSH_REQUEST_SIGNATURE = 'signature'; // 7. const String PUSH_REQUEST_ANSWERS = 'require_presence'; // 8. +// TokenContainer: +const String TOKEN_ID = 'id'; + const String GLOBAL_SECURE_REPO_PREFIX = 'app_v3_'; bool validateMap(Map map, List keys) { diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart index 4f048570b..c102e2f67 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart @@ -11,9 +11,10 @@ import '../../../logger.dart'; final tokenContainerStateProvider = StateNotifierProvider((ref) { Logger.info("New tokenContainerStateProvider created", name: 'tokenContainerStateProvider'); return TokenContainerNotifier( - ref: ref, + // ref: ref, repository: HybridTokenContainerStateRepository( - localRepository: PreferenceTokenContainerStateRepository('placeholder'), // TODO: Implement containerId + localRepository: SecureTokenContainerStateRepository('placeholder'), // TODO: Implement containerId + syncedRepository: SecureTokenContainerStateRepository('placeholder'), // TODO: Implement containerId remoteRepository: RemoteTokenContainerStateRepository( apiEndpoint: TokenContainerApiEndpoint(), // TODO: Nochmal anschauen containerId: 'placeholder', // TODO: Implement containerId diff --git a/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart index 7bafd1844..7f4edbca3 100644 --- a/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart @@ -5,5 +5,7 @@ import '../../home_widget_utils.dart'; class HomeWidgetTokenStateListener extends TokenStateListener { const HomeWidgetTokenStateListener({required super.tokenProvider}) : super(onNewState: _onNewState); - static void _onNewState(TokenState? previous, TokenState next) => HomeWidgetUtils().updateTokensIfLinked(next.lastlyUpdatedTokens); + static void _onNewState(TokenState? previous, TokenState next) { + HomeWidgetUtils().updateTokensIfLinked(next.lastlyUpdatedTokens); + } } diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index b5e3a9d47..872a120df 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -1,4 +1,6 @@ +import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; import '../../../model/states/token_state.dart'; @@ -8,9 +10,18 @@ class TokenContainerTokenStateListener extends TokenStateListener { TokenContainerTokenStateListener({ required super.tokenProvider, required WidgetRef ref, - }) : super(onNewState: (TokenState? previous, TokenState next) => _onNewState(previous, next, ref)); + }) : super(onNewState: (TokenState? previous, TokenState next) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _onNewState(previous, next, ref); + }); + }); static Future _onNewState(TokenState? previous, TokenState next, WidgetRef ref) async { - ref.read(tokenContainerStateProvider.notifier).updatedTokensIfContains(next.lastlyUpdatedTokens); + final containerNotifier = ref.read(tokenContainerStateProvider.notifier); + containerNotifier.updateTemplates( + next.lastlyUpdatedTokens.map((e) { + return TokenTemplate(data: e.toUriMap()); + }).toList(), + ); } } From 3a6d09aae1ea4b76abc603617b1260fdef006bf3 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:14:04 +0200 Subject: [PATCH 004/285] smartphone as a container --- lib/api/token_container_api_endpoint.dart | 11 +- lib/interfaces/api_endpoint.dart | 4 +- lib/interfaces/repo/container_repository.dart | 4 +- lib/model/credentials.dart | 0 lib/model/states/token_container_state.dart | 155 +++++++++++----- lib/model/token_container.g.dart | 4 - .../container_credentials_processor.dart | 19 ++ .../scheme_processor_interface.dart | 3 + ...brid_token_container_state_repository.dart | 175 +++++++++++++----- ...mote_token_container_state_repository.dart | 38 +++- ...oken_container_state_repository.dart.dart} | 59 +++--- .../token_container_notifier.dart | 2 +- .../token_container_state_provider.dart | 101 +++++++++- 13 files changed, 431 insertions(+), 144 deletions(-) create mode 100644 lib/model/credentials.dart create mode 100644 lib/processors/scheme_processors/container_credentials_processor.dart rename lib/repo/token_container_state_repositorys/{preference_token_container_state_repository.dart.dart => secure_token_container_state_repository.dart.dart} (55%) diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index bce3983f5..81066d02f 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -1,9 +1,16 @@ +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; + import '../interfaces/api_endpoint.dart'; import '../model/states/token_container_state.dart'; -class TokenContainerApiEndpoint implements ApiEndpioint { +class TokenContainerApiEndpoint implements ApiEndpioint { + @override + final Credentials credentials; + + const TokenContainerApiEndpoint({required this.credentials}); + @override - Future fetch(String id) { + Future fetch() { throw UnimplementedError(); } diff --git a/lib/interfaces/api_endpoint.dart b/lib/interfaces/api_endpoint.dart index ab4fa8f95..bcbb6d75b 100644 --- a/lib/interfaces/api_endpoint.dart +++ b/lib/interfaces/api_endpoint.dart @@ -1,4 +1,6 @@ abstract class ApiEndpioint { - Future fetch(Ref ref); + Ref get credentials; + + Future fetch(); Future save(Data data); } diff --git a/lib/interfaces/repo/container_repository.dart b/lib/interfaces/repo/container_repository.dart index d2839083f..10799375e 100644 --- a/lib/interfaces/repo/container_repository.dart +++ b/lib/interfaces/repo/container_repository.dart @@ -9,7 +9,7 @@ abstract class TokenContainerStateRepository { /// Load the container state from the repository /// Returns the loaded state - Future loadContainer(); + Future loadContainerState(); /// Save a token template to the repository /// Returns the template that was actually written @@ -17,5 +17,5 @@ abstract class TokenContainerStateRepository { /// Load a token template from the repository /// Returns the loaded template - Future loadTokenTemplate(String tokenTemplateId); + Future loadTokenTemplate(String tokenTemplateId); } diff --git a/lib/model/credentials.dart b/lib/model/credentials.dart new file mode 100644 index 000000000..e69de29bb diff --git a/lib/model/states/token_container_state.dart b/lib/model/states/token_container_state.dart index 4fb37f74f..230a12d67 100644 --- a/lib/model/states/token_container_state.dart +++ b/lib/model/states/token_container_state.dart @@ -30,48 +30,78 @@ sealed class TokenContainerState extends TokenContainer { _ => throw UnimplementedError(json['type']), }; - @override - Map toJson() { - final json = switch (this) { - TokenContainerStateUninitialized() => _$TokenContainerStateUninitializedToJson(this as TokenContainerStateUninitialized), - TokenContainerStateModified() => _$TokenContainerStateModifiedToJson(this as TokenContainerStateModified), - TokenContainerStateSynced() => _$TokenContainerStateSyncedToJson(this as TokenContainerStateSynced), - TokenContainerStateSyncing() => _$TokenContainerStateSyncingToJson(this as TokenContainerStateSyncing), - TokenContainerStateUnsynced() => _$TokenContainerStateUnsyncedToJson(this as TokenContainerStateUnsynced), - TokenContainerStateError() => _$TokenContainerStateErrorToJson(this as TokenContainerStateError), - TokenContainerStateDeactivated() => _$TokenContainerStateDeactivatedToJson(this as TokenContainerStateDeactivated), - TokenContainerStateDeleted() => _$TokenContainerStateDeletedToJson(this as TokenContainerStateDeleted), - }; - json['type'] = runtimeType.toString(); - return json; - } - - @override - TokenContainerState copyWith({ - String? containerId, - String? description, - String? type, - List? tokenTemplates, - DateTime? lastSyncedAt, - }); - - T copyTransformInto({ + factory TokenContainerState.fromTypeString({ + required String stateType, dynamic data, DateTime? dateTime, - DateTime? lastSyncedAt, String? containerId, String? description, String? type, - List? tokenTemplates, - }) { - final copied = copyWith( - containerId: containerId, - description: description, - type: type, - tokenTemplates: tokenTemplates, - ); - return copied.as(data: data, dateTime: dateTime); - } + List tokenTemplates = const [], + DateTime? lastSyncedAt, + }) => + switch (stateType) { + 'TokenContainerStateUninitialized' => const TokenContainerStateUninitialized(), + 'TokenContainerStateModified' => TokenContainerStateModified( + lastModifiedAt: dateTime ?? DateTime.now(), + highPriority: data ?? false, + lastSyncedAt: lastSyncedAt ?? DateTime.now(), + containerId: containerId ?? '', + description: description ?? '', + type: type ?? '', + tokenTemplates: tokenTemplates, + ), + 'TokenContainerStateSynced' => TokenContainerStateSynced( + lastSyncedAt: lastSyncedAt ?? DateTime.now(), + containerId: containerId ?? '', + description: description ?? '', + type: type ?? '', + tokenTemplates: tokenTemplates, + ), + 'TokenContainerStateSyncing' => TokenContainerStateSyncing( + syncStartedAt: dateTime ?? DateTime.now(), + lastSyncedAt: lastSyncedAt, + containerId: containerId ?? '', + description: description ?? '', + type: type ?? '', + tokenTemplates: tokenTemplates, + ), + 'TokenContainerStateUnsynced' => TokenContainerStateUnsynced( + syncAttempts: data is num ? data.floor() : 1, + lastSyncedAt: lastSyncedAt, + containerId: containerId ?? '', + description: description ?? '', + type: type ?? '', + tokenTemplates: tokenTemplates, + ), + 'TokenContainerStateError' => TokenContainerStateError( + error: data, + lastSyncedAt: lastSyncedAt, + containerId: containerId ?? '', + description: description ?? '', + type: type ?? '', + tokenTemplates: tokenTemplates, + ), + 'TokenContainerStateDeactivated' => TokenContainerStateDeactivated( + reason: data, + deactivatedAt: dateTime ?? DateTime.now(), + lastSyncedAt: lastSyncedAt, + containerId: containerId ?? '', + description: description ?? '', + type: type ?? '', + tokenTemplates: tokenTemplates, + ), + 'TokenContainerStateDeleted' => TokenContainerStateDeleted( + reason: data, + deletedAt: dateTime ?? DateTime.now(), + lastSyncedAt: lastSyncedAt, + containerId: containerId ?? '', + description: description ?? '', + type: type ?? '', + tokenTemplates: tokenTemplates, + ), + _ => throw UnimplementedError(stateType), + }; T as({dynamic data, DateTime? dateTime}) => switch (T) { const (TokenContainerStateUninitialized) => const TokenContainerStateUninitialized() as T, @@ -128,6 +158,51 @@ sealed class TokenContainerState extends TokenContainer { ) as T, _ => throw UnimplementedError(), }; + + @override + Map toJson() { + final json = switch (this) { + TokenContainerStateUninitialized() => _$TokenContainerStateUninitializedToJson(this as TokenContainerStateUninitialized), + TokenContainerStateModified() => _$TokenContainerStateModifiedToJson(this as TokenContainerStateModified), + TokenContainerStateSynced() => _$TokenContainerStateSyncedToJson(this as TokenContainerStateSynced), + TokenContainerStateSyncing() => _$TokenContainerStateSyncingToJson(this as TokenContainerStateSyncing), + TokenContainerStateUnsynced() => _$TokenContainerStateUnsyncedToJson(this as TokenContainerStateUnsynced), + TokenContainerStateError() => _$TokenContainerStateErrorToJson(this as TokenContainerStateError), + TokenContainerStateDeactivated() => _$TokenContainerStateDeactivatedToJson(this as TokenContainerStateDeactivated), + TokenContainerStateDeleted() => _$TokenContainerStateDeletedToJson(this as TokenContainerStateDeleted), + }; + json['type'] = runtimeType.toString(); + return json; + } + + @override + TokenContainerState copyWith({ + String? containerId, + String? description, + String? type, + List? tokenTemplates, + DateTime? lastSyncedAt, + }); + + T copyTransformInto({ + dynamic data, + DateTime? dateTime, + DateTime? lastSyncedAt, + String? containerId, + String? description, + String? type, + List? tokenTemplates, + }) { + final copied = copyWith( + containerId: containerId, + description: description, + type: type, + tokenTemplates: tokenTemplates, + ); + return copied.as(data: data, dateTime: dateTime); + } + + } /// ContainerState is not initialized @@ -196,6 +271,7 @@ class TokenContainerStateModified extends TokenContainerState { ); } } + /// ContainerState is successfully synced with repo @JsonSerializable() class TokenContainerStateSynced extends TokenContainerState { @@ -262,7 +338,6 @@ class TokenContainerStateSyncing extends TokenContainerState { tokenTemplates: tokenTemplates ?? this.tokenTemplates, ); } - } /// ContainerState is failed last sync attempt @@ -309,7 +384,6 @@ class TokenContainerStateUnsynced extends TokenContainerState { tokenTemplates: tokenTemplates ?? this.tokenTemplates, ); } - } /// ContainerState is in error state @@ -343,7 +417,6 @@ class TokenContainerStateError extends TokenContainerState { tokenTemplates: tokenTemplates ?? this.tokenTemplates, ); } - } /// ContainerState is deactivated @@ -381,7 +454,6 @@ class TokenContainerStateDeactivated extends TokenContainerState { tokenTemplates: tokenTemplates ?? this.tokenTemplates, ); } - } /// ContainerState is deleted repo-side @@ -419,5 +491,4 @@ class TokenContainerStateDeleted extends TokenContainerState { tokenTemplates: tokenTemplates ?? this.tokenTemplates, ); } - } diff --git a/lib/model/token_container.g.dart b/lib/model/token_container.g.dart index 7deb29fd0..591511658 100644 --- a/lib/model/token_container.g.dart +++ b/lib/model/token_container.g.dart @@ -27,13 +27,9 @@ Map _$TokenContainerToJson(TokenContainer instance) => TokenTemplate _$TokenTemplateFromJson(Map json) => TokenTemplate( data: json['data'] as Map, - modifiedAt: DateTime.parse(json['modifiedAt'] as String), - highPriority: json['highPriority'] as bool? ?? false, ); Map _$TokenTemplateToJson(TokenTemplate instance) => { - 'modifiedAt': instance.modifiedAt.toIso8601String(), - 'highPriority': instance.highPriority, 'data': instance.data, }; diff --git a/lib/processors/scheme_processors/container_credentials_processor.dart b/lib/processors/scheme_processors/container_credentials_processor.dart new file mode 100644 index 000000000..fa8b9223f --- /dev/null +++ b/lib/processors/scheme_processors/container_credentials_processor.dart @@ -0,0 +1,19 @@ +import 'package:privacyidea_authenticator/processors/scheme_processors/scheme_processor_interface.dart'; +import 'package:privacyidea_authenticator/utils/globals.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; + +class ContainerCredentialsProcessor extends SchemeProcessor { + @override + Set get supportedSchemes => {'container'}; // TODO: edit supportedSchemes to the real supported schemes + List get supportedHosts => ['credentials']; // TODO: edit supportedHosts to the real supported hosts + + const ContainerCredentialsProcessor(); + @override + Future processUri(Uri uri, {bool fromInit = false}) async { + if (!supportedHosts.contains(uri.host) || !supportedSchemes.contains(uri.scheme)) { + return null; + } + final credentials = Credentials.fromUriMap(uri.queryParameters); + globalRef?.read(credentialsProvider.notifier).setCredentials(credentials); + } +} diff --git a/lib/processors/scheme_processors/scheme_processor_interface.dart b/lib/processors/scheme_processors/scheme_processor_interface.dart index 235568510..20984e3ed 100644 --- a/lib/processors/scheme_processors/scheme_processor_interface.dart +++ b/lib/processors/scheme_processors/scheme_processor_interface.dart @@ -1,7 +1,9 @@ +import 'container_credentials_processor.dart'; import 'home_widget_processor.dart'; import 'navigation_scheme_processors/navigation_scheme_processor_interface.dart'; import 'token_import_scheme_processors/token_import_scheme_processor_interface.dart'; +/// On new impelementations, add them to the [SchemeProcessor.implementations] list abstract class SchemeProcessor { const SchemeProcessor(); Set get supportedSchemes; @@ -11,6 +13,7 @@ abstract class SchemeProcessor { const HomeWidgetProcessor(), ...NavigationSchemeProcessor.implementations, ...TokenImportSchemeProcessor.implementations, + const ContainerCredentialsProcessor(), ]; static Future processUriByAny(Uri uri, {bool fromInit = false}) async { for (SchemeProcessor processor in implementations) { diff --git a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart index b25586721..5d6aec9a8 100644 --- a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart @@ -8,25 +8,28 @@ import '../../utils/logger.dart'; class HybridTokenContainerStateRepository implements TokenContainerStateRepository { - final RemoteRepo _remoteRepository; final LocalRepo _localRepository; final LocalRepo _syncedRepository; + final RemoteRepo? _remoteRepository; HybridTokenContainerStateRepository({ - required RemoteRepo remoteRepository, required LocalRepo localRepository, required LocalRepo syncedRepository, - }) : _remoteRepository = remoteRepository, + required RemoteRepo? remoteRepository, + }) : _localRepository = localRepository, - _syncedRepository = syncedRepository; + _syncedRepository = syncedRepository, + _remoteRepository = remoteRepository; @override - Future loadContainer({bool isInitial = false}) async { + Future loadContainerState({bool isInitial = false}) async { TokenContainerState? remoteState; + TokenContainerState lastSyncedState; TokenContainerState localState; TokenContainerState newState; + try { - localState = await _localRepository.loadContainer(); + localState = await _localRepository.loadContainerState(); } catch (e) { return TokenContainerStateError( error: LocalizedException( @@ -41,12 +44,17 @@ class HybridTokenContainerStateRepository(); return newState; } - newState = _merge(localState, remoteState); + newState = await _merge( + localState: localState, + remoteState: remoteState, + lastSyncedState: lastSyncedState, + ); if (newState is TokenContainerStateSynced) { try { await _syncedRepository.saveContainerState(newState); @@ -58,68 +66,147 @@ class HybridTokenContainerStateRepository saveContainerState(TokenContainerState containerState) async { - if (containerState is TokenContainerStateError) { + Future saveContainerState(TokenContainerState newLocalState) async { + if (newLocalState is TokenContainerStateError) { Logger.warning('Cannot save error state to repository'); - return containerState; + return newLocalState; } - TokenContainerState remoteState; + TokenContainerState? remoteState; + TokenContainerState lastSyncedState; TokenContainerState newState; + try { - remoteState = await _remoteRepository.loadContainer(); + remoteState = await _remoteRepository?.loadContainerState(); + lastSyncedState = await _syncedRepository.loadContainerState(); } catch (e) { - newState = containerState.copyTransformInto(); + newState = newLocalState.copyTransformInto(); return _localRepository.saveContainerState(newState); } - newState = _merge(containerState, remoteState); + newState = await _merge( + localState: newLocalState, + remoteState: remoteState, + lastSyncedState: lastSyncedState, + ); try { - await _remoteRepository.saveContainerState(newState); + await _remoteRepository?.saveContainerState(newState); } catch (e) { newState = newState.copyTransformInto(); + await _localRepository.saveContainerState(newState); + return newState; } + await _syncedRepository.saveContainerState(newState); await _localRepository.saveContainerState(newState); return newState; } - TokenContainerState _merge(TokenContainerState localState, TokenContainerState remoteState) { + Future _merge({ + required TokenContainerState localState, + required TokenContainerState? remoteState, + required TokenContainerState lastSyncedState, + }) async { + List localTemplates; + List remoteTemplates; + List syncedTemplates; if (localState is TokenContainerStateUninitialized) { - // Uninitialized state is always overwritten by the other state - return remoteState; + // Uninitialized state is always overwritten by other states + localTemplates = []; + } else { + localTemplates = localState.tokenTemplates; } if (remoteState is TokenContainerStateUninitialized) { - // Uninitialized state is always overwritten by the other state - return localState; + // Uninitialized state is always overwritten by other states + remoteTemplates = []; + } else { + remoteTemplates = remoteState?.tokenTemplates ?? []; } - for (var localTemplate in localState.tokenTemplates) { - final remoteTemplate = remoteState.tokenTemplates.firstWhere((template) => template.id == localTemplate.id, orElse: () => localTemplate); - if (remoteTemplate != localTemplate) { - localTemplate.merge(remoteTemplate); - } + if (lastSyncedState is TokenContainerStateUninitialized) { + // Uninitialized state is always overwritten by other states + syncedTemplates = []; + } else { + syncedTemplates = lastSyncedState.tokenTemplates; } + + final mergedTemplates = await _mergeTemplateLists( + localTemplates: localTemplates, + remoteTemplates: remoteTemplates, + syncedTemplates: syncedTemplates, + ); + + final newSyncedState = TokenContainerStateSynced( + lastSyncedAt: DateTime.now(), + containerId: localState.containerId, + description: localState.description, + type: '', // TODO: Implement type + tokenTemplates: mergedTemplates, + ); + return newSyncedState; + } + + Future> _mergeTemplateLists({ + required List localTemplates, + required List remoteTemplates, + required List syncedTemplates, + }) async { + final mergedTemplates = []; + + // Add all templates that are in the synced state + for (var syncedTemplate in syncedTemplates) { + final localTemplateIndex = localTemplates.indexWhere((template) => template.id == syncedTemplate.id); + final localTemplate = localTemplateIndex != -1 ? localTemplates.removeAt(localTemplateIndex) : null; + final remoteTemplateIndex = remoteTemplates.indexWhere((template) => template.id == syncedTemplate.id); + final remoteTemplate = remoteTemplateIndex != -1 ? remoteTemplates.removeAt(remoteTemplateIndex) : null; + + mergedTemplates.add(await _mergeTemplates(localTemplate, remoteTemplate, syncedTemplate)); + } + + // Add all remaining local templates + for (var localTemplate in localTemplates) { + final remoteTemplateIndex = remoteTemplates.indexWhere((template) => template.id == localTemplate.id); + final remoteTemplate = remoteTemplateIndex != -1 ? remoteTemplates.removeAt(remoteTemplateIndex) : null; + + mergedTemplates.add(await _mergeTemplates(localTemplate, remoteTemplate, null)); + } + + // Add all remaining remote templates + mergedTemplates.addAll(remoteTemplates); + + return mergedTemplates; } /// Merges local and remote token templates with the last synced state /// If both local and remote templates have changed, the remote changes are prioritized - Future _mergeTemplates(TokenTemplate local, TokenTemplate remote) async { - assert(local.id == remote.id); - final id = local.id; - final syncedTemplates = (await _syncedRepository.loadContainer()).tokenTemplates; - final synced = syncedTemplates.firstWhere((template) => template.id == id, orElse: () => TokenTemplate(data: {})); - final remoteDifferences = _getTemplateDifferences(synced.data, remote.data); - final localDifferences = _getTemplateDifferences(synced.data, local.data); - final mergedData = synced.data - ..addAll(localDifferences) - ..addAll(remoteDifferences); + Future _mergeTemplates(TokenTemplate? local, TokenTemplate? remote, TokenTemplate? lastSynced) async { + final id = local?.id ?? remote?.id ?? lastSynced?.id; + assert(id != null, 'At least one template must be provided'); + assert((local == null || local.id == id) && (remote == null || remote.id == id) && (lastSynced == null || lastSynced.id == id), + 'All templates must have the same id'); + final mergedData = {}; + + final lastSyncedData = lastSynced?.data ?? {}; + mergedData.addAll(lastSyncedData); + final localData = local?.data ?? {}; + mergedData.addAll(localData); + final remoteData = remote?.data ?? {}; + mergedData.addAll(remoteData); + return TokenTemplate(data: mergedData); } - Map _getTemplateDifferences(Map snyced, Map newState) { - final differences = {}; - for (var key in newState.keys) { - if (snyced[key] != newState[key]) { - differences[key] = newState[key]; - } - } - return differences; + @override + Future loadTokenTemplate(String tokenTemplateId) async { + final state = await loadContainerState(); + final template = state.tokenTemplates.firstWhereOrNull((template) => template.id == tokenTemplateId); + return template; + } + + @override + Future saveTokenTemplate(TokenTemplate tokenTemplate) async { + final state = await loadContainerState(); + final templates = state.tokenTemplates; + templates.removeWhere((template) => template.id == tokenTemplate.id); + templates.add(tokenTemplate); + final newState = state.copyWith(tokenTemplates: templates); + final savedState = await saveContainerState(newState); + return savedState.tokenTemplates.firstWhere((template) => template.id == tokenTemplate.id); } } diff --git a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart index dbfea4cf4..f7806d21f 100644 --- a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart @@ -1,12 +1,18 @@ +import 'package:collection/collection.dart'; +import 'package:mutex/mutex.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; + import '../../api/token_container_api_endpoint.dart'; import '../../interfaces/repo/container_repository.dart'; import '../../model/states/token_container_state.dart'; class RemoteTokenContainerStateRepository implements TokenContainerStateRepository { final TokenContainerApiEndpoint apiEndpoint; - final String containerId; + final Mutex _m = Mutex(); + + Future _protect(Future Function() f) => _m.protect(f); - RemoteTokenContainerStateRepository({required this.apiEndpoint, required this.containerId}); + RemoteTokenContainerStateRepository({required this.apiEndpoint}); @override Future saveContainerState(TokenContainerState containerState) { @@ -14,7 +20,31 @@ class RemoteTokenContainerStateRepository implements TokenContainerStateReposito } @override - Future loadContainer() => _fetchContainerState(); + Future loadContainerState() => _fetchContainerState(); - Future _fetchContainerState() async => apiEndpoint.fetch(containerId); + Future _fetchContainerState() async { + TokenContainerState? state; + await _protect(() async => state = await apiEndpoint.fetch()); + return state!; + } + + @override + Future loadTokenTemplate(String tokenTemplateId) async { + final state = await loadContainerState(); + final template = state.tokenTemplates.firstWhereOrNull((element) => element.id == tokenTemplateId); + return template; + } + + @override + Future saveTokenTemplate(TokenTemplate tokenTemplate) async { + final state = await loadContainerState(); + final templateIndex = state.tokenTemplates.indexWhere((element) => element.id == tokenTemplate.id); + if (templateIndex == -1) { + state.tokenTemplates.add(tokenTemplate); + } else { + state.tokenTemplates[templateIndex] = tokenTemplate; + } + await saveContainerState(state); + return tokenTemplate; + } } diff --git a/lib/repo/token_container_state_repositorys/preference_token_container_state_repository.dart.dart b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart similarity index 55% rename from lib/repo/token_container_state_repositorys/preference_token_container_state_repository.dart.dart rename to lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart index 5a8cbe707..380280f65 100644 --- a/lib/repo/token_container_state_repositorys/preference_token_container_state_repository.dart.dart +++ b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:collection/collection.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:mutex/mutex.dart'; import 'package:privacyidea_authenticator/model/token_container.dart'; @@ -10,8 +11,7 @@ import '../../model/states/token_container_state.dart'; class SecureTokenContainerStateRepository implements TokenContainerStateRepository { static String prefix = 'token_container_state_'; - String get containerIdKey => _keyOf('containerId'); - String _keyOf(String id) => prefix + repoName + id; + String get _containerStateKey => '$prefix${repoName}_container_state'; final Mutex _m = Mutex(); Future _protect(Future Function() f) => _m.protect(f); @@ -37,54 +37,39 @@ class SecureTokenContainerStateRepository implements TokenContainerStateReposito } @override - - Future saveContainerState(TokenContainerState containerState) async { - for (var template in containerState.tokenTemplates) { - if (template.id == null) { - Logger.warning('Cannot save token template without id'); - continue; - } - _write(_keyOf(template.id!), jsonEncode(template.toJson())); - } - _write(containerIdKey, containerState.containerId); + await _write(_containerStateKey, jsonEncode(containerState)); return containerState; } @override /// Load the container state from the shared preferences - Future loadContainer() async { - final keys = await _storage.readAll(); - final templates = []; - for (var key in keys.keys) { - if (key.startsWith(prefix + repoName)) { - final templateJson = await _storage.read(key: key); - if (templateJson == null) { - Logger.warning('Failed to read token template from shared preferences'); - continue; - } - final templateMap = jsonDecode(templateJson); - templates.add(TokenTemplate.fromJson(templateMap)); - } + Future loadContainerState() async { + String? containerStateJsonString = await _read(_containerStateKey); + if (containerStateJsonString == null) { + Logger.info('No container state found in secure storage', name: 'SecureTokenContainerStateRepository'); + return TokenContainerState.uninitialized(); } - return TokenContainerState( - containerId: '', - description: '', - type: '', - tokenTemplates: templates, - ); + final json = jsonDecode(containerStateJsonString); + return TokenContainerState.fromJson(json); } @override - Future loadTokenTemplate(String tokenTemplateId) { - // TODO: implement loadTokenTemplate - throw UnimplementedError(); + Future loadTokenTemplate(String tokenTemplateId) async { + final state = await loadContainerState(); + final template = state.tokenTemplates.firstWhereOrNull((template) => template.id == tokenTemplateId); + return template; } @override - Future saveTokenTemplate(TokenTemplate tokenTemplate) { - // TODO: implement saveTokenTemplate - throw UnimplementedError(); + Future saveTokenTemplate(TokenTemplate tokenTemplate) async { + TokenContainerState state = await loadContainerState(); + final templates = state.tokenTemplates; + templates.removeWhere((template) => template.id == tokenTemplate.id); + templates.add(tokenTemplate); + state = state.copyWith(tokenTemplates: templates); + await saveContainerState(state); + return tokenTemplate; } } diff --git a/lib/state_notifiers/token_container_notifier.dart b/lib/state_notifiers/token_container_notifier.dart index 6848c9dd6..31d61bac7 100644 --- a/lib/state_notifiers/token_container_notifier.dart +++ b/lib/state_notifiers/token_container_notifier.dart @@ -33,7 +33,7 @@ class TokenContainerNotifier extends StateNotifier { Future _loadFromRepo() async { await _repoMutex.acquire(); - final containerState = await _repository.loadContainer(); + final containerState = await _repository.loadContainerState(); _repoMutex.release(); return containerState; } diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart index c102e2f67..de8850ab4 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart @@ -1,24 +1,111 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:mutex/mutex.dart'; import '../../../../api/token_container_api_endpoint.dart'; import '../../../../model/states/token_container_state.dart'; import '../../../../repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; -import '../../../../repo/token_container_state_repositorys/preference_token_container_state_repository.dart.dart'; +import '../../../../repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart'; import '../../../../repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; import '../../../../state_notifiers/token_container_notifier.dart'; import '../../../logger.dart'; final tokenContainerStateProvider = StateNotifierProvider((ref) { Logger.info("New tokenContainerStateProvider created", name: 'tokenContainerStateProvider'); + final c = ref.watch(credentialsProvider); return TokenContainerNotifier( // ref: ref, repository: HybridTokenContainerStateRepository( - localRepository: SecureTokenContainerStateRepository('placeholder'), // TODO: Implement containerId - syncedRepository: SecureTokenContainerStateRepository('placeholder'), // TODO: Implement containerId - remoteRepository: RemoteTokenContainerStateRepository( - apiEndpoint: TokenContainerApiEndpoint(), // TODO: Nochmal anschauen - containerId: 'placeholder', // TODO: Implement containerId - ), + localRepository: SecureTokenContainerStateRepository(repoName: 'local'), + syncedRepository: SecureTokenContainerStateRepository(repoName: 'synced'), + remoteRepository: c != null + // TODO: Nochmal anschauen + ? RemoteTokenContainerStateRepository(apiEndpoint: TokenContainerApiEndpoint(credentials: c)) + : null, ), ); }); + +final credentialsProvider = StateNotifierProvider((ref) { + Logger.info("New credentialsProvider created", name: 'credentialsProvider'); + return CredentialsNotifier(repo: SecureContainerCredentialsRepository()); +}); + +class CredentialsNotifier extends StateNotifier { + final Mutex _repoMutex = Mutex(); + final ContainerCredentialsRepository _repo; + late Future initState = _initState(); + + Future _initState() async { + final credentials = await _repo.loadCredentials(); + state = credentials; + return credentials; + } + + CredentialsNotifier({required ContainerCredentialsRepository repo}) + : _repo = repo, + super(null); + + Future setCredentials(Credentials credentials) async { + await _saveCredentialsToRepo(credentials); + state = credentials; + } + + Future deleteCredentials() async { + _deleteCredentialsFromRepo(); + state = null; + } + + Future _saveCredentialsToRepo(Credentials credentials) async { + await _repoMutex.protect(() async => await _repo.saveCredentials(credentials)); + } + + Future _deleteCredentialsFromRepo() async { + await _repoMutex.protect(() async => await _repo.deleteCredentials()); + } +} + +class SecureContainerCredentialsRepository extends ContainerCredentialsRepository { + static String containerCredentialsKey = 'containerCredentials'; + final Mutex _m = Mutex(); + Future _protect(Future Function() f) => _m.protect(f); + final FlutterSecureStorage _storage = const FlutterSecureStorage(); + + Future _write(String key, String value) => _protect(() => _storage.write(key: key, value: value)); + Future _read(String key) async => await _protect(() async => await _storage.read(key: key)); + Future _delete(String key) => _protect(() => _storage.delete(key: key)); + + @override + Future loadCredentials() async { + final credentials = await _read(containerCredentialsKey); + if (credentials == null) return null; + return Credentials(credentials: credentials); + } + + @override + Future saveCredentials(Credentials credentials) async { + await _write(containerCredentialsKey, credentials.credentials); + } + + @override + Future deleteCredentials() async { + await _delete(containerCredentialsKey); + } +} + +abstract class ContainerCredentialsRepository { + Future saveCredentials(Credentials credentials); + Future loadCredentials(); + Future deleteCredentials(); +} + +class Credentials { + final String credentials; // TODO: Change to real credentials (DUMMY) + Credentials({required this.credentials}); + + // TODO: DUMMY + factory Credentials.fromJson(Map json) => Credentials(credentials: json['credentials']); + Map toJson() => {'credentials': credentials}; + + static fromUriMap(Map queryParameters) {} // TODO: DUMMY +} From 1e20c3520e576002f3c37306d62fd13c5632dd40 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:52:11 +0200 Subject: [PATCH 005/285] smartphone as a container --- lib/model/enums/token_origin_source_type.dart | 3 +- lib/model/states/token_state.dart | 9 ++ lib/model/token_container.dart | 4 + lib/model/tokens/day_password_token.dart | 19 ++++ lib/model/tokens/hotp_token.dart | 19 ++++ lib/model/tokens/push_token.dart | 20 ++++ lib/model/tokens/token.dart | 28 ++++-- lib/model/tokens/totp_token.dart | 39 ++++---- ...ken_import_scheme_processor_interface.dart | 7 ++ lib/repo/secure_token_repository.dart | 1 + lib/state_notifiers/token_notifier.dart | 95 +++++++++++++++++++ lib/utils/home_widget_utils.dart | 26 +++-- .../token_container_state_provider.dart | 2 +- .../home_widget_token_state_listener.dart | 26 ++++- .../token_container_token_state_listener.dart | 10 +- .../add_token_manually_view.dart | 2 +- lib/widgets/app_wrapper.dart | 26 +++++ 17 files changed, 292 insertions(+), 44 deletions(-) diff --git a/lib/model/enums/token_origin_source_type.dart b/lib/model/enums/token_origin_source_type.dart index c24d1a34a..ebe415ee5 100644 --- a/lib/model/enums/token_origin_source_type.dart +++ b/lib/model/enums/token_origin_source_type.dart @@ -1,4 +1,4 @@ -// Do not rename or remove values, they are used for serialization. Only add new values. +// Do not rename or remove values, they are used for serialization. Only add new values at the end of the list. enum TokenOriginSourceType { backupFile, qrScan, @@ -8,4 +8,5 @@ enum TokenOriginSourceType { linkImport, manually, unknown, + container, } diff --git a/lib/model/states/token_state.dart b/lib/model/states/token_state.dart index 9f3d2199f..fcf45623b 100644 --- a/lib/model/states/token_state.dart +++ b/lib/model/states/token_state.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; import '../../utils/logger.dart'; import '../enums/push_token_rollout_state.dart'; +import '../enums/token_origin_source_type.dart'; +import '../token_container.dart'; import '../token_folder.dart'; import '../tokens/otp_token.dart'; import '../tokens/push_token.dart'; @@ -156,4 +158,11 @@ extension TokenListExtension on List { if (only.isNotEmpty && !only.contains(token.runtimeType)) return false; return true; }).toList(); + + List get fromContainer => where((token) => token.origin?.source == TokenOriginSourceType.container).toList(); + + List toTemplates() { + if (isEmpty) return []; + return map((token) => TokenTemplate(data: token.toUriMap())).toList(); + } } diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index 5e777a097..ef539cbe1 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -2,6 +2,8 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; +import 'tokens/token.dart'; + part 'token_container.g.dart'; @JsonSerializable() @@ -52,4 +54,6 @@ class TokenTemplate { factory TokenTemplate.fromJson(Map json) => _$TokenTemplateFromJson(json); Map toJson() => _$TokenTemplateToJson(this); + + Token toToken() => Token.fromUriMap(data); } diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index c979d43e4..a9d5d6202 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:uuid/uuid.dart'; import '../../utils/errors.dart'; @@ -121,6 +122,24 @@ class DayPasswordToken extends OTPToken { return DateTime.now().add(durationUntilNextOTP + const Duration(milliseconds: 1)); } + @override + DayPasswordToken copyWithFromTemplate(TokenTemplate template) { + final uriMap = template.data; + return copyWith( + label: uriMap[URI_LABEL], + issuer: uriMap[URI_ISSUER], + id: uriMap[TOKEN_ID], + algorithm: uriMap[URI_ALGORITHM] != null ? Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()) : null, + digits: uriMap[URI_DIGITS], + secret: uriMap[URI_SECRET] != null ? Encodings.base32.encode(uriMap[URI_SECRET]) : null, + period: uriMap[URI_PERIOD] != null ? Duration(seconds: uriMap[URI_PERIOD]) : null, + tokenImage: uriMap[URI_IMAGE], + pin: uriMap[URI_PIN], + isLocked: uriMap[URI_PIN], + origin: uriMap[URI_ORIGIN], + ); + } + factory DayPasswordToken.fromUriMap(Map uriMap) { validateUriMap(uriMap); return DayPasswordToken( diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index 533e285c7..826f7316e 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -1,4 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:uuid/uuid.dart'; import '../../utils/errors.dart'; @@ -96,6 +97,24 @@ class HOTPToken extends OTPToken { return 'H${super.toString()}counter: $counter}'; } + @override + HOTPToken copyWithFromTemplate(TokenTemplate template) { + final uriMap = template.data; + return copyWith( + label: uriMap[URI_LABEL], + issuer: uriMap[URI_ISSUER], + id: uriMap[TOKEN_ID], + algorithm: uriMap[URI_ALGORITHM] != null ? Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()) : null, + digits: uriMap[URI_DIGITS], + secret: uriMap[URI_SECRET] != null ? Encodings.base32.encode(uriMap[URI_SECRET]) : null, + counter: uriMap[URI_COUNTER], + tokenImage: uriMap[URI_IMAGE], + pin: uriMap[URI_PIN], + isLocked: uriMap[URI_PIN], + origin: uriMap[URI_ORIGIN], + ); + } + factory HOTPToken.fromUriMap(Map uriMap) { validateUriMap(uriMap); return HOTPToken( diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index c5b7aba96..1d60f5307 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -1,5 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:pointycastle/asymmetric/api.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:uuid/uuid.dart'; import '../../utils/custom_int_buffer.dart'; @@ -163,6 +164,25 @@ class PushToken extends Token { 'publicTokenKey: $publicTokenKey}'; } + @override + PushToken copyWithFromTemplate(TokenTemplate template) { + final uriMap = template.data; + return copyWith( + serial: uriMap[URI_SERIAL], + label: uriMap[URI_LABEL], + issuer: uriMap[URI_ISSUER], + id: uriMap[TOKEN_ID], + sslVerify: uriMap[URI_SSL_VERIFY], + enrollmentCredentials: uriMap[URI_ENROLLMENT_CREDENTIAL], + url: uriMap[URI_ROLLOUT_URL], + tokenImage: uriMap[URI_IMAGE], + pin: uriMap[URI_PIN], + isLocked: uriMap[URI_PIN], + origin: uriMap[URI_ORIGIN], + // do not update expirationDate + ); + } + factory PushToken.fromUriMap(Map uriMap) => PushToken( serial: uriMap[URI_SERIAL] ?? '', label: uriMap[URI_LABEL] ?? '', diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index b3cba704f..5ed7aac0f 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; import '../../utils/identifiers.dart'; import '../enums/token_types.dart'; @@ -17,6 +18,7 @@ import 'totp_token.dart'; abstract class Token with SortableMixin { bool? get isPrivacyIdeaToken => origin?.isPrivacyIdeaToken; final String tokenVersion = 'v1.0.0'; // The version of this token, this is used for serialization. + final String? containerId; // The id of the container this token belongs to. final String label; // the name of the token, it cannot be uses as an identifier final String issuer; // The issuer of this token, currently unused. final String id; // this is the identifier of the token @@ -29,9 +31,10 @@ abstract class Token with SortableMixin { final int? sortIndex; final TokenOriginData? origin; - // Must be string representation of TokenType enum. + /// Must be string representation of TokenType enum. final String type; // Used to identify the token when deserializing. + /// Creates a token from a json map. factory Token.fromJson(Map json) { String? type = json['type']; if (type == null) throw ArgumentError.value(json, 'Token#fromJson', 'Token type is not defined in the json'); @@ -42,6 +45,8 @@ abstract class Token with SortableMixin { if (TokenTypes.STEAM.isName(type, caseSensitive: false)) return SteamToken.fromJson(json); throw ArgumentError.value(json, 'Token#fromJson', 'Token type [$type] is not a supported'); } + + /// Creates a token from a uri map. factory Token.fromUriMap(Map uriMap) { String type = uriMap[URI_TYPE]; if (TokenTypes.HOTP.isName(type, caseSensitive: false)) return HOTPToken.fromUriMap(uriMap); @@ -55,6 +60,7 @@ abstract class Token with SortableMixin { const Token({ this.label = '', this.issuer = '', + this.containerId, required this.id, required this.type, this.tokenImage, @@ -115,15 +121,10 @@ abstract class Token with SortableMixin { 'origin: $origin, '; } + /// This is used to create a map that can be used to serialize the token. Map toJson(); - /// ```dart - /// URI_LABEL: label, - /// URI_ISSUER: issuer, - /// URI_PIN: pin, - /// URI_IMAGE: tokenImage, - /// URI_ORIGIN: jsonEncode(origin!.toJson()), - /// ``` + /// This is used to create a map that typically was created from a uri. Map toUriMap() { return { TOKEN_ID: id, @@ -135,4 +136,15 @@ abstract class Token with SortableMixin { if (origin != null) URI_ORIGIN: jsonEncode(origin!.toJson()) }; } + + bool doesMatchTemplate(TokenTemplate matchingTemplate) { + final uriMap = toUriMap(); + final templateData = matchingTemplate.data; + for (var key in templateData.keys) { + if (uriMap[key] != templateData[key]) return false; + } + return true; + } + + Token copyWithFromTemplate(TokenTemplate template); } diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index b2f4609b7..5822a2297 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -1,4 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:uuid/uuid.dart'; import '../../utils/errors.dart'; @@ -102,6 +103,24 @@ class TOTPToken extends OTPToken { ); } + @override + TOTPToken copyWithFromTemplate(TokenTemplate template) { + final uriMap = template.data; + return copyWith( + label: uriMap[URI_LABEL], + issuer: uriMap[URI_ISSUER], + id: uriMap[TOKEN_ID], + algorithm: uriMap[URI_ALGORITHM] != null ? Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()) : null, + digits: uriMap[URI_DIGITS], + tokenImage: uriMap[URI_IMAGE], + secret: uriMap[URI_SECRET] != null ? Encodings.base32.encode(uriMap[URI_SECRET]) : null, + period: uriMap[URI_PERIOD], + pin: uriMap[URI_PIN], + isLocked: uriMap[URI_PIN], + origin: uriMap[URI_ORIGIN], + ); + } + @override String toString() { return 'T${super.toString()}period: $period'; @@ -123,25 +142,7 @@ class TOTPToken extends OTPToken { origin: uriMap[URI_ORIGIN], ); } - /// ----- TOTP TOKEN ----- - /// ```dart - /// URI_TYPE: tokenType, - /// URI_PERIOD: period, - /// ``` - /// ------ OTP TOKEN ------ - /// ```dart - /// URI_SECRET: Encodings.base32.decode(secret), - /// URI_ALGORITHM: algorithm.name, - /// URI_DIGITS: digits, - /// ``` - /// ------- TOKEN --------- - /// ```dart - /// URI_LABEL: label, - /// URI_ISSUER: issuer, - /// URI_PIN: pin, - /// URI_IMAGE: tokenImage, - /// URI_ORIGIN: jsonEncode(origin!.toJson()), - /// ``` + @override Map toUriMap() { return super.toUriMap() diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart b/lib/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart index a67ff36c3..7302489b3 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart @@ -8,6 +8,13 @@ import 'privacyidea_authenticator_qr_processor.dart'; abstract class TokenImportSchemeProcessor with TokenImportProcessor implements SchemeProcessor { const TokenImportSchemeProcessor(); + + static Set get allSupportedSchemes => { + ...const OtpAuthProcessor().supportedSchemes, + ...const GoogleAuthenticatorQrProcessor().supportedSchemes, + ...const PrivacyIDEAAuthenticatorQrProcessor().supportedSchemes, + }; + static const Set implementations = { OtpAuthProcessor(), GoogleAuthenticatorQrProcessor(), diff --git a/lib/repo/secure_token_repository.dart b/lib/repo/secure_token_repository.dart index 35cd253b8..ebd0acb64 100644 --- a/lib/repo/secure_token_repository.dart +++ b/lib/repo/secure_token_repository.dart @@ -61,6 +61,7 @@ class SecureTokenRepository implements TokenRepository { Future loadToken(String id) => _protect(() => _loadToken(id)); Future _loadToken(String id) async { final token = await _storage.read(key: _TOKEN_PREFIX + id); + Logger.info('Loading token from secure storage: $id', name: 'secure_token_repository.dart#loadToken'); if (token == null) { Logger.warning('Token not found in secure storage', name: 'secure_token_repository.dart#loadToken'); return null; diff --git a/lib/state_notifiers/token_notifier.dart b/lib/state_notifiers/token_notifier.dart index f17c60521..e9bb867d6 100644 --- a/lib/state_notifiers/token_notifier.dart +++ b/lib/state_notifiers/token_notifier.dart @@ -11,6 +11,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; import 'package:pointycastle/asymmetric/api.dart'; +import 'package:privacyidea_authenticator/model/states/token_container_state.dart'; import '../interfaces/repo/token_repository.dart'; import '../l10n/app_localizations.dart'; @@ -21,6 +22,7 @@ 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/token_container.dart'; import '../model/tokens/hotp_token.dart'; import '../model/tokens/otp_token.dart'; import '../model/tokens/push_token.dart'; @@ -186,6 +188,28 @@ class TokenNotifier extends StateNotifier { return true; } + /// Removes a list of tokens and returns the tokens that could not be removed. + Future> _removeTokens(List tokens) async { + await _loadingRepoMutex.acquire(); + final oldState = state; + state = state.withoutTokens(tokens); + + final failedTokens = await _repo.deleteTokens(tokens); + if (failedTokens.isNotEmpty) { + Logger.warning( + 'Deleting tokens failed. Failed Tokens: ${failedTokens.length}', + name: 'token_notifier.dart#_deleteTokensRepo', + ); + final recoveredTokens = oldState.tokens.where((oldToken) => failedTokens.contains(oldToken)).toList(); + state = state.addOrReplaceTokens(recoveredTokens); + _loadingRepoMutex.release(); + return failedTokens; + } + _loadingRepoMutex.release(); + _handlePushTokensIfExist(); + return []; + } + /// Loads the tokens from the repository sets it as the new state and returns the new state. Future _loadFromRepo() async { await _loadingRepoMutex.acquire(); @@ -292,8 +316,65 @@ class TokenNotifier extends StateNotifier { /// Updates a token and returns the updated token if successful, the old token if not and null if the token does not exist. Future updateToken(T token, T Function(T) updater) async => _updateToken(token, updater); + /// Updates a list of tokens and returns the updated tokens if successful, the old tokens if not and an empty list if the tokens does not exist. Future> updateTokens(List tokens, T Function(T) updater) async => _updateTokens(tokens, updater); + /// Adds, Updates or Removes tokens based on the [TokenContainerState] and returns the updated tokens. + void updateContainerTokens(TokenContainer container) async { + final templatesToAdd = []; + final templatesToUpdate = []; + final templatesToRemove = []; + + final containerTokens = state.tokens.fromContainer; + final tokenTemplatesContainer = container.tokenTemplates; + final tokenTemplatesApp = containerTokens.toTemplates(); + for (var i = 0; i < tokenTemplatesContainer.length && tokenTemplatesApp.isNotEmpty; i++) { + // Searches for tokens that are in the container but not in the app to add them. + // If the token is already in the app, it will be updated. + // Reduces the [tokenTemplatesApp] list to only the tokens that are in the app but not in the container. These tokens will be removed. + final templateContainer = tokenTemplatesContainer[i]; + final tokenToUpdate = tokenTemplatesApp.firstWhereOrNull((templateApp) => templateApp.id == templateContainer.id); + if (tokenToUpdate == null) { + templatesToAdd.add(templateContainer); + } else { + templatesToUpdate.add(tokenToUpdate); + tokenTemplatesApp.remove(tokenToUpdate); + } + } + templatesToRemove.addAll(tokenTemplatesApp); + + final tokensToAdd = templatesToAdd.map((e) => e.toToken()).toList(); + final tokensToUpdate = []; + for (var template in templatesToUpdate) { + final token = containerTokens.firstWhereOrNull((token) => token.id == template.id); + if (token == null) continue; + final needsUpdate = !token.doesMatchTemplate(template); + if (needsUpdate) { + tokensToUpdate.add(token); + } + } + final tokensToRemove = []; + for (var template in templatesToRemove) { + final token = containerTokens.firstWhereOrNull((token) => token.id == template.id); + if (token == null) continue; + tokensToRemove.add(token); + } + + if (tokensToAdd.isNotEmpty) { + await addOrReplaceTokens(tokensToAdd); + } + if (tokensToUpdate.isNotEmpty) { + await updateTokens(tokensToUpdate, (p0) { + final template = templatesToUpdate.firstWhereOrNull((element) => element.id == p0.id); + if (template == null) return p0; + return p0.copyWithFromTemplate(template); + }); + } + if (tokensToRemove.isNotEmpty) { + await removeTokens(tokensToRemove); + } + } + /// Increments the counter of a HOTPToken and returns the updated token if successful, the old token if not and null if the token does not exist. Future incrementCounter(HOTPToken token) => _updateToken(token, (p0) => p0.copyWith(counter: token.counter + 1)); @@ -365,6 +446,7 @@ class TokenNotifier extends StateNotifier { return await updateTokens(hideLockedTokens, (p0) => p0.copyWith(isHidden: true)); } + /// Removes a token from the state and the repository. Future removeToken(Token token) async { if (token is PushToken) { await _removePushToken(token); @@ -373,6 +455,16 @@ class TokenNotifier extends StateNotifier { await _removeToken(token); } + /// Removes a list of tokens from the state and the repository. + Future removeTokens(List tokens) async { + final pushTokens = tokens.whereType().toList(); + final otherTokens = tokens.whereType().toList(); + await _removeTokens(otherTokens); + for (var token in pushTokens) { + await _removePushToken(token); + } + } + Future _removePushToken(PushToken token) async { try { await _firebaseUtils.deleteFirebaseToken(); @@ -689,6 +781,9 @@ class TokenNotifier extends StateNotifier { } Future> _tokensFromUri(Uri uri) async { + if (!TokenImportSchemeProcessor.allSupportedSchemes.contains(uri.scheme)) { + return Future.value([]); + } try { final results = await TokenImportSchemeProcessor.processUriByAny(uri); if (results == null || results.isEmpty) { diff --git a/lib/utils/home_widget_utils.dart b/lib/utils/home_widget_utils.dart index af5b51f40..93956aa74 100644 --- a/lib/utils/home_widget_utils.dart +++ b/lib/utils/home_widget_utils.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:home_widget/home_widget.dart'; import 'package:mutex/mutex.dart'; @@ -86,9 +87,9 @@ class HomeWidgetUtils { return tokens?.whereType().toList() ?? (await _loadTokensFromRepo()).whereType().toList(); } - static Future _getTokenOfTokenId(String? tokenId) async { + static Future _getTokenOfTokenId(String tokenId) async { await _repoMutex.acquire(); - final token = (await _loadTokensFromRepo()).firstWhereOrNull((token) => token.id == tokenId); + final token = (await _loadTokenFromRepo(tokenId)); _repoMutex.release(); return token; } @@ -159,6 +160,7 @@ class HomeWidgetUtils { // _packageId must be the exact id of the package variable in "AndroidManifest.xml" !! NOT the applicationId of the flavor !! static const String _packageId = "it.netknights.piauthenticator"; static Future> _loadTokensFromRepo() async => (await _tokenRepository?.loadTokens())?.whereType().toList() ?? []; + static Future _loadTokenFromRepo(String tokenId) async => (await _tokenRepository?.loadToken(tokenId)) as OTPToken; static Future? _saveTokensToRepo(List tokens) => _tokenRepository?.saveOrReplaceTokens(tokens); static Future> _loadFoldersFromRepo() async { @@ -169,8 +171,10 @@ class HomeWidgetUtils { return await HomeWidget.getWidgetData('$keyTokenId$widgetId'); } - Future getTokenOfWidgetId(String? widgetId) async { - return widgetId == null ? null : _getTokenOfTokenId(await getTokenIdOfWidgetId(widgetId)); + Future getTokenOfWidgetId(String widgetId) async { + final tokenId = await getTokenIdOfWidgetId(widgetId); + if (tokenId == null) return null; + return _getTokenOfTokenId(tokenId); } /// a token can be linked to multiple widgets but widgetIs can only be linked to one token @@ -246,6 +250,7 @@ class HomeWidgetUtils { // Call AFTER saving to the repository Future updateTokensIfLinked(List tokens) async { // Map + if (tokens.isEmpty) return; final hotpTokens = tokens.whereType().toList(); final hotpTokenIds = hotpTokens.map((e) => e.id).toList(); final linkedWidgetIds = await _getWidgetIdsOfTokens(hotpTokenIds); @@ -400,7 +405,16 @@ class HomeWidgetUtils { final Map _hideTimers = {}; void _hideOtpDelayed(String widgetId, int otpLength) { _hideTimers[widgetId]?.cancel(); - _hideTimers[widgetId] = Timer(_showDuration, () => _hideOtp(widgetId, otpLength)); + _hideTimers[widgetId] = Timer(_showDuration, () async { + await _hideOtp(widgetId, otpLength); + if (_hideTimers.length == 1 && _hideTimers.containsKey(widgetId)) { + _closeApp(); + } + }); + } + + static Future _closeApp() async { + SystemChannels.platform.invokeMethod('SystemNavigator.pop'); } Future _hideOtp(String widgetId, int otpLength) async { @@ -596,7 +610,7 @@ class HomeWidgetUtils { /// This method has to be called after change to the HomeWidget to notify the HomeWidget to update Future _notifyUpdate(Iterable updatedWidgetIds) async { if (updatedWidgetIds.isEmpty) return; - Logger.info('Update requested for: $updatedWidgetIds', name: 'home_widget_utils.dart#_notifyUpdate', stackTrace: StackTrace.current); + Logger.info('Update requested for: $updatedWidgetIds', name: 'home_widget_utils.dart#_notifyUpdate'); if (await _widgetIsRebuilding || _lastUpdate != null && DateTime.now().difference(_lastUpdate!) < _updateDelay) { Logger.info('Update delayed: $updatedWidgetIds', name: 'home_widget_utils.dart#_notifyUpdate'); _updatedWidgetIds.addAll(updatedWidgetIds); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart index de8850ab4..e06c5b387 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart @@ -10,7 +10,7 @@ import '../../../../repo/token_container_state_repositorys/remote_token_containe import '../../../../state_notifiers/token_container_notifier.dart'; import '../../../logger.dart'; -final tokenContainerStateProvider = StateNotifierProvider((ref) { +final tokenContainerProvider = StateNotifierProvider((ref) { Logger.info("New tokenContainerStateProvider created", name: 'tokenContainerStateProvider'); final c = ref.watch(credentialsProvider); return TokenContainerNotifier( diff --git a/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart index 7f4edbca3..0cbf7eb5e 100644 --- a/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart @@ -1,11 +1,35 @@ +import 'package:collection/collection.dart'; +import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; + import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; import '../../../model/states/token_state.dart'; +import '../../../model/tokens/token.dart'; import '../../home_widget_utils.dart'; class HomeWidgetTokenStateListener extends TokenStateListener { const HomeWidgetTokenStateListener({required super.tokenProvider}) : super(onNewState: _onNewState); static void _onNewState(TokenState? previous, TokenState next) { - HomeWidgetUtils().updateTokensIfLinked(next.lastlyUpdatedTokens); + final updateTokens = []; + if (previous == null) { + updateTokens.addAll(next.lastlyUpdatedTokens); + } else { + final previousTokens = previous.tokens; + final nextTokens = next.lastlyUpdatedTokens; + for (final nextToken in nextTokens) { + final previousToken = previousTokens.firstWhereOrNull((previousToken) => previousToken.id == nextToken.id); + if (previousToken == null) { + updateTokens.add(nextToken); + continue; + } + if (previousToken.issuer != nextToken.issuer || + previousToken.label != nextToken.label || + previousToken.isLocked != nextToken.isLocked || + (previousToken is HOTPToken && nextToken is HOTPToken && previousToken.counter != nextToken.counter)) { + updateTokens.add(nextToken); + } + } + } + HomeWidgetUtils().updateTokensIfLinked(updateTokens); } } diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index 872a120df..125129c8a 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -1,6 +1,5 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/model/token_container.dart'; import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; import '../../../model/states/token_state.dart'; @@ -17,11 +16,8 @@ class TokenContainerTokenStateListener extends TokenStateListener { }); static Future _onNewState(TokenState? previous, TokenState next, WidgetRef ref) async { - final containerNotifier = ref.read(tokenContainerStateProvider.notifier); - containerNotifier.updateTemplates( - next.lastlyUpdatedTokens.map((e) { - return TokenTemplate(data: e.toUriMap()); - }).toList(), - ); + final templates = next.lastlyUpdatedTokens.fromContainer.toTemplates(); + if (templates.isEmpty) return; + ref.read(tokenContainerProvider.notifier).updateTemplates(templates); } } 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 2d67575ea..e4ad8aeb9 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 @@ -21,7 +21,7 @@ class AddTokenManuallyView extends ConsumerStatefulWidget { static const routeName = '/add_token_manually'; static final List allowedDigits = [6, 8]; static final List allowedPeriodsTOTP = [30, 60]; - static final List allowedPeriodsDayPassword = [24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; + static final List allowedPeriodsDayPassword = List.generate(24, (i) => 24 - i, growable: false); const AddTokenManuallyView({super.key}); diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index a7a45a667..1e8726896 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -4,7 +4,11 @@ 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 'package:privacyidea_authenticator/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart'; +import 'package:privacyidea_authenticator/state_notifiers/token_container_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; +import '../model/states/token_container_state.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; import '../utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart'; @@ -91,6 +95,7 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { HomeWidgetDeepLinkListener(deeplinkProvider: deeplinkProvider), // TODO: Nochmal anschauen HomeWidgetTokenStateListener(tokenProvider: tokenProvider), TokenContainerTokenStateListener(tokenProvider: tokenProvider, ref: ref), + TokenStateTokenContainerListener(tokenContainerProvider: tokenContainerProvider, ref: ref), ], child: EasyDynamicThemeWidget( child: widget.child, @@ -99,3 +104,24 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { ); } } + +class TokenStateTokenContainerListener extends TokenContainerListener { + final WidgetRef ref; + TokenStateTokenContainerListener({ + required super.tokenContainerProvider, + required this.ref, + }) : super(onNewState: (previous, next) => _onNewState(previous, next, ref)); + + static Future _onNewState(TokenContainerState? previous, TokenContainerState? next, WidgetRef ref) async { + if (next == null) return; + final provider = ref.read(tokenProvider.notifier); + provider.updateContainerTokens(next); + } +} + +class TokenContainerListener extends StateNotifierProviderListener { + const TokenContainerListener({ + required StateNotifierProvider tokenContainerProvider, + required super.onNewState, + }) : super(provider: tokenContainerProvider); +} From d0e2f07eeccb5e2433705b1adef83a7bb8804ef4 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 22 Jul 2024 16:19:19 +0200 Subject: [PATCH 006/285] smartphone as a container --- lib/model/push_request.dart | 1 + lib/model/states/push_request_state.dart | 2 +- lib/model/states/token_container_state.dart | 13 +-- lib/model/states/token_container_state.g.dart | 4 +- .../token_import/token_origin_data.g.dart | 1 + lib/model/tokens/otp_token.dart | 24 ++-- lib/model/tokens/token.dart | 9 ++ lib/model/tokens/totp_token.dart | 16 +++ lib/repo/secure_push_request_repository.dart | 4 +- ...brid_token_container_state_repository.dart | 15 ++- ...mote_token_container_state_repository.dart | 21 ++-- .../push_request_notifier.dart | 2 +- lib/utils/custom_int_buffer.dart | 3 +- lib/utils/custom_int_buffer.g.dart | 5 + .../actions/edit_push_token_action.dart | 4 +- ... => enable_text_edit_after_many_taps.dart} | 10 +- .../hybrid_token_container_repo_test.dart | 106 ++++++++++++++++++ .../token_container_notifier_test.dart | 14 +++ .../utils/custom_int_buffer_test.dart | 22 +++- 19 files changed, 233 insertions(+), 43 deletions(-) rename lib/widgets/{enable_text_form_field_after_many_taps.dart => enable_text_edit_after_many_taps.dart} (77%) create mode 100644 test/unit_test/repo/hybrid_token_container_repo_test.dart create mode 100644 test/unit_test/state_notifiers/token_container_notifier_test.dart diff --git a/lib/model/push_request.dart b/lib/model/push_request.dart index 68e78b9b7..1d5949c4f 100644 --- a/lib/model/push_request.dart +++ b/lib/model/push_request.dart @@ -129,6 +129,7 @@ class PushRequest { if (data[PUSH_REQUEST_NONCE] is! String) { throw ArgumentError('Push request nonce is ${data[PUSH_REQUEST_NONCE].runtimeType}. Expected String.'); } + print('Nonce: ${data[PUSH_REQUEST_NONCE]}'); if (data[PUSH_REQUEST_SSL_VERIFY] is! String) { throw ArgumentError('Push request sslVerify is ${data[PUSH_REQUEST_SSL_VERIFY].runtimeType}. Expected String.'); } diff --git a/lib/model/states/push_request_state.dart b/lib/model/states/push_request_state.dart index 51fa8b8b4..910aa07c8 100644 --- a/lib/model/states/push_request_state.dart +++ b/lib/model/states/push_request_state.dart @@ -64,7 +64,7 @@ class PushRequestState { } @override - int get hashCode => pushRequests.hashCode * 31 + knownPushRequests.hashCode; + int get hashCode => Object.hashAll([pushRequests.hashCode, knownPushRequests.hashCode]); @override String toString() => 'PushRequestState(pushRequests: $pushRequests, knownPushRequests: $knownPushRequests)'; diff --git a/lib/model/states/token_container_state.dart b/lib/model/states/token_container_state.dart index 230a12d67..d0189796b 100644 --- a/lib/model/states/token_container_state.dart +++ b/lib/model/states/token_container_state.dart @@ -44,7 +44,6 @@ sealed class TokenContainerState extends TokenContainer { 'TokenContainerStateUninitialized' => const TokenContainerStateUninitialized(), 'TokenContainerStateModified' => TokenContainerStateModified( lastModifiedAt: dateTime ?? DateTime.now(), - highPriority: data ?? false, lastSyncedAt: lastSyncedAt ?? DateTime.now(), containerId: containerId ?? '', description: description ?? '', @@ -201,8 +200,6 @@ sealed class TokenContainerState extends TokenContainer { ); return copied.as(data: data, dateTime: dateTime); } - - } /// ContainerState is not initialized @@ -239,10 +236,8 @@ class TokenContainerStateUninitialized extends TokenContainerState { @JsonSerializable() class TokenContainerStateModified extends TokenContainerState { DateTime lastModifiedAt; - bool highPriority; TokenContainerStateModified({ required this.lastModifiedAt, - required this.highPriority, required DateTime lastSyncedAt, required super.containerId, required super.description, @@ -262,7 +257,6 @@ class TokenContainerStateModified extends TokenContainerState { }) { return TokenContainerStateModified( lastModifiedAt: lastModifiedAt ?? this.lastModifiedAt, - highPriority: highPriority ?? this.highPriority, lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt ?? DateTime.now(), containerId: containerId ?? this.containerId, description: description ?? this.description, @@ -275,6 +269,11 @@ class TokenContainerStateModified extends TokenContainerState { /// ContainerState is successfully synced with repo @JsonSerializable() class TokenContainerStateSynced extends TokenContainerState { + @override + DateTime get lastSyncedAt { + assert(super.lastSyncedAt != null, 'In a synced state, lastSyncedAt must not be null.'); + return super.lastSyncedAt!; + } TokenContainerStateSynced({ required DateTime lastSyncedAt, required super.containerId, @@ -292,7 +291,7 @@ class TokenContainerStateSynced extends TokenContainerState { DateTime? lastSyncedAt, }) { return TokenContainerStateSynced( - lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt ?? DateTime.now(), + lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, containerId: containerId ?? this.containerId, description: description ?? this.description, type: type ?? this.type, diff --git a/lib/model/states/token_container_state.g.dart b/lib/model/states/token_container_state.g.dart index b5d71be12..cf6047ac5 100644 --- a/lib/model/states/token_container_state.g.dart +++ b/lib/model/states/token_container_state.g.dart @@ -18,7 +18,6 @@ TokenContainerStateModified _$TokenContainerStateModifiedFromJson( Map json) => TokenContainerStateModified( lastModifiedAt: DateTime.parse(json['lastModifiedAt'] as String), - highPriority: json['highPriority'] as bool, lastSyncedAt: DateTime.parse(json['lastSyncedAt'] as String), containerId: json['containerId'] as String, description: json['description'] as String, @@ -37,7 +36,6 @@ Map _$TokenContainerStateModifiedToJson( 'tokenTemplates': instance.tokenTemplates, 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), 'lastModifiedAt': instance.lastModifiedAt.toIso8601String(), - 'highPriority': instance.highPriority, }; TokenContainerStateSynced _$TokenContainerStateSyncedFromJson( @@ -59,7 +57,7 @@ Map _$TokenContainerStateSyncedToJson( 'description': instance.description, 'type': instance.type, 'tokenTemplates': instance.tokenTemplates, - 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), + 'lastSyncedAt': instance.lastSyncedAt.toIso8601String(), }; TokenContainerStateSyncing _$TokenContainerStateSyncingFromJson( diff --git a/lib/model/token_import/token_origin_data.g.dart b/lib/model/token_import/token_origin_data.g.dart index 03b83af3f..48842db74 100644 --- a/lib/model/token_import/token_origin_data.g.dart +++ b/lib/model/token_import/token_origin_data.g.dart @@ -41,4 +41,5 @@ const _$TokenOriginSourceTypeEnumMap = { TokenOriginSourceType.linkImport: 'linkImport', TokenOriginSourceType.manually: 'manually', TokenOriginSourceType.unknown: 'unknown', + TokenOriginSourceType.container: 'container', }; diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index 2b664081a..a803c3e94 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -66,21 +66,23 @@ abstract class OTPToken extends Token { return 'OTP${super.toString()}algorithm: $algorithm, digits: $digits, pin: $pin, '; } + /// This is used to create a map that typically was created from a uri. /// ```dart - /// URI_SECRET: Encodings.base32.decode(secret), - /// URI_ALGORITHM: algorithm.name, - /// URI_DIGITS: digits, - /// ``` - /// ------- TOKEN --------- - /// ```dart - /// URI_LABEL: label, - /// URI_ISSUER: issuer, - /// URI_PIN: pin, - /// URI_IMAGE: tokenImage, - /// URI_ORIGIN: jsonEncode(origin!.toJson()), + /// ------------------------------ [OTPToken] ------------------------------ + /// URI_SECRET: base32 encoded string (String), + /// URI_ALGORITHM: algorithm name e.g. SHA1 (String), + /// URI_DIGITS: number of digits (int), + /// ------------------------------- [Token] --------------------------------- + /// URI_LABEL: name of the token (String), + /// URI_ISSUER: name of the issuer (String), + /// URI_PIN: is the user forced to have a pin (bool), + /// URI_IMAGE: url to an image e.g. "https://example.com/image.png" (String), + /// URI_ORIGIN: json string of the origin class (String), + /// ------------------------------------------------------------------------- /// ``` @override Map toUriMap() { + print(secret); return super.toUriMap() ..addAll({ URI_SECRET: Encodings.base32.decode(secret), diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index 5ed7aac0f..286cd1c87 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -125,6 +125,15 @@ abstract class Token with SortableMixin { Map toJson(); /// This is used to create a map that typically was created from a uri. + /// ```dart + /// ------------------------------- [Token] --------------------------------- + /// URI_LABEL: name of the token (String), + /// URI_ISSUER: name of the issuer (String), + /// URI_PIN: is the user forced to have a pin (bool), + /// URI_IMAGE: url to an image e.g. "https://example.com/image.png" (String), + /// URI_ORIGIN: json string of the origin class (String), + /// ------------------------------------------------------------------------- + /// ``` Map toUriMap() { return { TOKEN_ID: id, diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index 5822a2297..06194f452 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -143,6 +143,22 @@ class TOTPToken extends OTPToken { ); } + /// This is used to create a map that typically was created from a uri. + /// ```dart + /// ----------------------------- [TOTPToken] ------------------------------ + /// URI_PERIOD: period of otp generation in seconds (int), + /// ------------------------------ [OTPToken] ------------------------------ + /// URI_SECRET: base32 encoded string (String), + /// URI_ALGORITHM: algorithm name e.g. SHA1 (String), + /// URI_DIGITS: number of digits (int), + /// ------------------------------- [Token] --------------------------------- + /// URI_LABEL: name of the token (String), + /// URI_ISSUER: name of the issuer (String), + /// URI_PIN: is the user forced to have a pin (bool), + /// URI_IMAGE: url to an image e.g. "https://example.com/image.png" (String), + /// URI_ORIGIN: json string of the origin class (String), + /// ------------------------------------------------------------------------- + /// ``` @override Map toUriMap() { return super.toUriMap() diff --git a/lib/repo/secure_push_request_repository.dart b/lib/repo/secure_push_request_repository.dart index 8dacc8d17..3e848f750 100644 --- a/lib/repo/secure_push_request_repository.dart +++ b/lib/repo/secure_push_request_repository.dart @@ -30,6 +30,7 @@ class SecurePushRequestRepository implements PushRequestRepository { Future saveState(PushRequestState pushRequestState) => protect(() => _saveState(pushRequestState)); Future _saveState(PushRequestState pushRequestState) async { final stateJson = jsonEncode(pushRequestState.toJson()); + print('Saving state: $stateJson'); await _storage.write(key: _securePushRequestKey, value: stateJson); } @@ -41,8 +42,9 @@ class SecurePushRequestRepository implements PushRequestRepository { Future loadState() => protect(_loadState); Future _loadState() async { final String? stateJson = await _storage.read(key: _securePushRequestKey); + print('Loaded state: $stateJson'); if (stateJson == null) { - return const PushRequestState(pushRequests: [], knownPushRequests: CustomIntBuffer(list: [])); + return PushRequestState(pushRequests: [], knownPushRequests: CustomIntBuffer(list: [])); } return PushRequestState.fromJson(jsonDecode(stateJson)); } diff --git a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart index 5d6aec9a8..df95d41f7 100644 --- a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart @@ -12,12 +12,13 @@ class HybridTokenContainerStateRepository{}; + print('------------------------------------------------------------------'); + print('LastSynced: ${lastSynced?.data}'); final lastSyncedData = lastSynced?.data ?? {}; mergedData.addAll(lastSyncedData); + print('MergedData: $mergedData'); + print('------------------------------------------------------------------'); + print('Local: ${local?.data}'); final localData = local?.data ?? {}; mergedData.addAll(localData); + print('MergedData: $mergedData'); + print('------------------------------------------------------------------'); + print('Remote: ${remote?.data}'); final remoteData = remote?.data ?? {}; mergedData.addAll(remoteData); + print('MergedData: $mergedData'); + print('------------------------------------------------------------------'); return TokenTemplate(data: mergedData); } diff --git a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart index f7806d21f..c44dab8d1 100644 --- a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart @@ -10,23 +10,28 @@ class RemoteTokenContainerStateRepository implements TokenContainerStateReposito final TokenContainerApiEndpoint apiEndpoint; final Mutex _m = Mutex(); - Future _protect(Future Function() f) => _m.protect(f); + Future _protect(Future Function() f) async => _m.protect(f); RemoteTokenContainerStateRepository({required this.apiEndpoint}); @override - Future saveContainerState(TokenContainerState containerState) { - throw UnimplementedError(); + Future saveContainerState(TokenContainerState containerState) async => await _saveContainerState(containerState); + + Future _saveContainerState(TokenContainerState containerState) async { + try { + return await _protect(() async { + await apiEndpoint.save(containerState); + return containerState; + }); + } catch (e) { + rethrow; + } } @override Future loadContainerState() => _fetchContainerState(); - Future _fetchContainerState() async { - TokenContainerState? state; - await _protect(() async => state = await apiEndpoint.fetch()); - return state!; - } + Future _fetchContainerState() async => await _protect(() async => await apiEndpoint.fetch()); @override Future loadTokenTemplate(String tokenTemplateId) async { diff --git a/lib/state_notifiers/push_request_notifier.dart b/lib/state_notifiers/push_request_notifier.dart index a87c429be..cb9e94d11 100644 --- a/lib/state_notifiers/push_request_notifier.dart +++ b/lib/state_notifiers/push_request_notifier.dart @@ -65,7 +65,7 @@ class PushRequestNotifier extends StateNotifier { _rsaUtils = rsaUtils ?? const RsaUtils(), _pushRepo = pushRepo ?? const SecurePushRequestRepository(), super( - initState ?? const PushRequestState(pushRequests: [], knownPushRequests: CustomIntBuffer(list: [])), + initState ?? PushRequestState(pushRequests: [], knownPushRequests: CustomIntBuffer(list: [])), ) { _init(initState); } diff --git a/lib/utils/custom_int_buffer.dart b/lib/utils/custom_int_buffer.dart index 8e9290847..c92189a09 100644 --- a/lib/utils/custom_int_buffer.dart +++ b/lib/utils/custom_int_buffer.dart @@ -7,7 +7,8 @@ part 'custom_int_buffer.g.dart'; class CustomIntBuffer { final int maxSize; final List _list; - const CustomIntBuffer({this.maxSize = 100, List list = const []}) : _list = list; + List get list => _list; + CustomIntBuffer({this.maxSize = 100, List list = const []}) : _list = list.length > maxSize ? list.sublist(list.length - maxSize) : list; CustomIntBuffer copyWith({int? maxSize, List? list}) { return CustomIntBuffer( diff --git a/lib/utils/custom_int_buffer.g.dart b/lib/utils/custom_int_buffer.g.dart index ee8663450..b55869e7e 100644 --- a/lib/utils/custom_int_buffer.g.dart +++ b/lib/utils/custom_int_buffer.g.dart @@ -9,9 +9,14 @@ part of 'custom_int_buffer.dart'; CustomIntBuffer _$CustomIntBufferFromJson(Map json) => CustomIntBuffer( maxSize: (json['maxSize'] as num?)?.toInt() ?? 100, + list: (json['list'] as List?) + ?.map((e) => (e as num).toInt()) + .toList() ?? + const [], ); Map _$CustomIntBufferToJson(CustomIntBuffer instance) => { 'maxSize': instance.maxSize, + 'list': instance.list, }; 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 da806d7e7..4a5f89794 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 @@ -10,7 +10,7 @@ import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; -import '../../../../../../widgets/enable_text_form_field_after_many_taps.dart'; +import '../../../../../../widgets/enable_text_edit_after_many_taps.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../default_token_actions/default_edit_action_dialog.dart'; import '../../token_action.dart'; @@ -93,7 +93,7 @@ class EditPushTokenAction extends TokenAction { ), readOnly: true, ), - EnableTextFormFieldAfterManyTaps( + EnableTextEditAfterManyTaps( controller: pushUrl, decoration: InputDecoration(labelText: appLocalizations.pushEndpointUrl), autovalidateMode: AutovalidateMode.onUserInteraction, diff --git a/lib/widgets/enable_text_form_field_after_many_taps.dart b/lib/widgets/enable_text_edit_after_many_taps.dart similarity index 77% rename from lib/widgets/enable_text_form_field_after_many_taps.dart rename to lib/widgets/enable_text_edit_after_many_taps.dart index 27e7fe943..7be9a3113 100644 --- a/lib/widgets/enable_text_form_field_after_many_taps.dart +++ b/lib/widgets/enable_text_edit_after_many_taps.dart @@ -2,14 +2,14 @@ import 'dart:async'; import 'package:flutter/material.dart'; -class EnableTextFormFieldAfterManyTaps extends StatefulWidget { +class EnableTextEditAfterManyTaps extends StatefulWidget { final TextEditingController controller; final InputDecoration decoration; final AutovalidateMode? autovalidateMode; final String? Function(String?)? validator; final int maxTaps; - const EnableTextFormFieldAfterManyTaps({ + const EnableTextEditAfterManyTaps({ required this.controller, required this.decoration, this.maxTaps = 6, @@ -19,10 +19,10 @@ class EnableTextFormFieldAfterManyTaps extends StatefulWidget { }); @override - State createState() => _EnableTextFormFieldAfterManyTapsState(); + State createState() => _EnableTextEditAfterManyTapsState(); } -class _EnableTextFormFieldAfterManyTapsState extends State { +class _EnableTextEditAfterManyTapsState extends State { bool enabled = false; int counter = 0; Timer? timer; @@ -47,7 +47,7 @@ class _EnableTextFormFieldAfterManyTapsState extends State GestureDetector( onDoubleTap: !enabled ? () => tapped(2) : null, child: TextFormField( - key: Key('${widget.controller.hashCode}_enableTextFormFieldAfterManyTaps'), + key: Key('${widget.controller.hashCode}_enableTextEditAfterManyTaps'), readOnly: !enabled, onTap: !enabled ? () => tapped(1) : null, style: enabled ? null : Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).disabledColor), diff --git a/test/unit_test/repo/hybrid_token_container_repo_test.dart b/test/unit_test/repo/hybrid_token_container_repo_test.dart new file mode 100644 index 000000000..d1eb9606d --- /dev/null +++ b/test/unit_test/repo/hybrid_token_container_repo_test.dart @@ -0,0 +1,106 @@ +import 'package:collection/collection.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:privacyidea_authenticator/interfaces/repo/container_repository.dart'; +import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; +import 'package:privacyidea_authenticator/model/states/token_container_state.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; +import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; +import 'package:privacyidea_authenticator/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; + +class MockTokenContainerStateRepository implements TokenContainerStateRepository { + TokenContainerState savedState; + bool _doesThrow = false; + MockTokenContainerStateRepository({TokenContainerState? initialState}) : savedState = initialState ?? TokenContainerState.uninitialized(); + + void setThrow(bool value) { + _doesThrow = value; + } + + @override + Future loadContainerState() { + if (_doesThrow) throw Exception('Test exception'); + return Future.value(savedState); + } + + @override + Future loadTokenTemplate(String tokenTemplateId) { + if (_doesThrow) throw Exception('Test exception'); + final template = savedState.tokenTemplates.firstWhereOrNull((element) => element.id == tokenTemplateId); + return Future.value(template); + } + + @override + Future saveContainerState(TokenContainerState containerState) { + if (_doesThrow) throw Exception('Test exception'); + savedState = containerState; + return Future.value(savedState); + } + + @override + Future saveTokenTemplate(TokenTemplate tokenTemplate) { + if (_doesThrow) throw Exception('Test exception'); + final index = savedState.tokenTemplates.indexWhere((element) => element.id == tokenTemplate.id); + if (index == -1) { + savedState.tokenTemplates.add(tokenTemplate); + } else { + savedState.tokenTemplates[index] = tokenTemplate; + } + return Future.value(tokenTemplate); + } +} + +void main() { + _testHybridTokenContainerStateRepository(); +} + +void _testHybridTokenContainerStateRepository() { + group('HybridTokenContainerStateRepository test', () { + test('HybridTokenContainerStateRepository test', () async { + final token = TOTPToken( + period: 30, + id: 'TOTP_1', + algorithm: Algorithms.SHA1, + digits: 6, + secret: 'SECRET', + issuer: 'issuer', + ); + TokenContainerState? remoteState = TokenContainerStateModified( + lastModifiedAt: DateTime.now(), + lastSyncedAt: DateTime.now().subtract(const Duration(days: 1)), + containerId: 'containerId', + description: 'description', + type: 'type', + tokenTemplates: [TokenTemplate(data: token.toUriMap())], + ); + TokenContainerState? localState = TokenContainerStateModified( + lastModifiedAt: DateTime.now(), + lastSyncedAt: DateTime.now().subtract(const Duration(days: 1)), + containerId: 'containerId', + description: 'description', + type: 'type', + tokenTemplates: [TokenTemplate(data: token.copyWith(issuer: 'issuer2').toUriMap())], + ); + + final syncedRepo = MockTokenContainerStateRepository(); + final localRepo = MockTokenContainerStateRepository(initialState: localState); + final remoteRepo = MockTokenContainerStateRepository(initialState: remoteState); + + final hybridRepo = HybridTokenContainerStateRepository( + syncedRepository: syncedRepo, + localRepository: localRepo, + remoteRepository: remoteRepo, + ); + final dateTimeBefore = DateTime.now(); + final state = await hybridRepo.loadContainerState(); + await Future.delayed(const Duration(milliseconds: 1)); + final dateTimeAfter = DateTime.now(); + expect(state, isA()); + state as TokenContainerStateSynced; + expect(state.lastSyncedAt.isAfter(dateTimeBefore), isTrue); + expect(state.lastSyncedAt.isBefore(dateTimeAfter), isTrue); + expect(state.tokenTemplates.length, 1); + final template = state.tokenTemplates.first; + expect(template.data, token.toUriMap(), reason: 'Should be the remote state if both are changed since last sync'); + }); + }); +} diff --git a/test/unit_test/state_notifiers/token_container_notifier_test.dart b/test/unit_test/state_notifiers/token_container_notifier_test.dart new file mode 100644 index 000000000..149404753 --- /dev/null +++ b/test/unit_test/state_notifiers/token_container_notifier_test.dart @@ -0,0 +1,14 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:privacyidea_authenticator/state_notifiers/token_container_notifier.dart'; + +void main() { + _testTokenContainerNotifier(); +} + +void _testTokenContainerNotifier() { + group('Token Container Notifier Test', () { + test('creation', () { + // final tokenContainerNotifier = TokenContainerNotifier(); + }); + }); +} diff --git a/test/unit_test/utils/custom_int_buffer_test.dart b/test/unit_test/utils/custom_int_buffer_test.dart index 954b8921e..4ef2b70b8 100644 --- a/test/unit_test/utils/custom_int_buffer_test.dart +++ b/test/unit_test/utils/custom_int_buffer_test.dart @@ -28,7 +28,7 @@ void main() { void verifyCustomStringBufferWorks() { group('test custom string buffer', () { test('put elements in', () { - const buffer0_30 = CustomIntBuffer(maxSize: 30); + final buffer0_30 = CustomIntBuffer(maxSize: 30, list: []); expect(buffer0_30.maxSize, 30); expect(buffer0_30.length, 0); @@ -56,5 +56,25 @@ void verifyCustomStringBufferWorks() { expect(overflowBuffer.contains(3), true); expect(overflowBuffer.contains(4), true); }); + test('toJson', () { + final buffer = CustomIntBuffer(maxSize: 30, list: [1, 2, 3]); + final json = buffer.toJson(); + expect(json, { + 'maxSize': 30, + 'list': [1, 2, 3] + }); + }); + + test('fromJson', () { + final buffer = CustomIntBuffer.fromJson({ + 'maxSize': 30, + 'list': [1, 2, 3] + }); + expect(buffer.maxSize, 30); + expect(buffer.length, 3); + expect(buffer.contains(1), true); + expect(buffer.contains(2), true); + expect(buffer.contains(3), true); + }); }); } From f814958696ac5ac715a7d81f6282404a20afc392 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 23 Jul 2024 17:34:20 +0200 Subject: [PATCH 007/285] smartphone as a container --- lib/api/token_container_api_endpoint.dart | 34 +- .../default_firebase_options.dart | 4 +- lib/interfaces/api_endpoint.dart | 2 +- lib/interfaces/repo/container_repository.dart | 13 +- lib/model/credentials.dart | 0 lib/model/states/token_container_state.dart | 376 ++++++++++-------- lib/model/states/token_container_state.g.dart | 124 +++--- lib/model/states/token_state.dart | 14 +- lib/model/token_container.dart | 38 +- lib/model/token_container.g.dart | 12 +- lib/model/tokens/container_credentials.dart | 14 + lib/model/tokens/container_credentials.g.dart | 21 + lib/model/tokens/day_password_token.dart | 3 + lib/model/tokens/day_password_token.g.dart | 2 + lib/model/tokens/hotp_token.dart | 3 + lib/model/tokens/hotp_token.g.dart | 2 + lib/model/tokens/otp_token.dart | 2 + lib/model/tokens/steam_token.dart | 3 + lib/model/tokens/steam_token.g.dart | 2 + lib/model/tokens/token.dart | 7 +- lib/model/tokens/totp_token.dart | 3 + lib/model/tokens/totp_token.g.dart | 2 + .../container_credentials_processor.dart | 4 +- ...brid_token_container_state_repository.dart | 272 +++++-------- ...mote_token_container_state_repository.dart | 43 +- ...token_container_state_repository.dart.dart | 50 ++- .../token_container_notifier.dart | 70 ++-- lib/state_notifiers/token_notifier.dart | 28 +- .../token_container_state_provider.dart | 49 +-- .../token_container_token_state_listener.dart | 8 +- .../dialogs/select_tokens_dialog.dart | 3 +- .../hybrid_token_container_repo_test.dart | 45 +-- .../token_container_notifier_test.dart | 1 - 33 files changed, 663 insertions(+), 591 deletions(-) delete mode 100644 lib/model/credentials.dart create mode 100644 lib/model/tokens/container_credentials.dart create mode 100644 lib/model/tokens/container_credentials.g.dart diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index 81066d02f..b11145155 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -1,13 +1,17 @@ -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; import '../interfaces/api_endpoint.dart'; import '../model/states/token_container_state.dart'; +import '../model/tokens/container_credentials.dart'; + +class TokenContainerApiEndpoint implements ApiEndpioint { + final Map> _data = {}; -class TokenContainerApiEndpoint implements ApiEndpioint { @override - final Credentials credentials; + final ContainerCredentials credentials; - const TokenContainerApiEndpoint({required this.credentials}); + TokenContainerApiEndpoint({required this.credentials}); @override Future fetch() { @@ -15,7 +19,25 @@ class TokenContainerApiEndpoint implements ApiEndpioint save(TokenContainerState data) { - throw UnimplementedError(); + Future sync(TokenContainerState containerState) async { + if (_data.containsKey(containerState.serial) == false) { + return containerState.copyTransformInto(data: 'Container not found'); + } + final localTemplates = containerState.localTokenTemplates; + for (var localTemplate in localTemplates) { + if (localTemplate.id?.startsWith(containerState.serial) == true) { + final merged = localTemplate.copyWith({ + URI_LABEL: (localTemplate.data[URI_LABEL] as String).replaceFirst(r'.', '😀'), + }); + _data[containerState.serial]![localTemplate.id!] = merged; + } + } + final serverTemplatesMerged = _data[containerState.serial]!.values.toList(); + return TokenContainerStateSynced( + serial: containerState.serial, + description: 'Synced with server', + type: 'synced', + syncedTokenTemplates: serverTemplatesMerged, + ); } } diff --git a/lib/firebase_options/default_firebase_options.dart b/lib/firebase_options/default_firebase_options.dart index 850fb174b..e35a3a77d 100644 --- a/lib/firebase_options/default_firebase_options.dart +++ b/lib/firebase_options/default_firebase_options.dart @@ -3,7 +3,7 @@ import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; -// import 'netknights_firebase_options.dart'; +import 'netknights_firebase_options.dart'; /// Netknights [DefaultFirebaseOptions] for use with your Firebase apps. /// @@ -17,7 +17,7 @@ import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, Tar /// ``` class DefaultFirebaseOptions { static FirebaseOptions currentPlatformOf(String? app) => switch (app) { - // 'netknights' => NetknightsFirebaseOptions.currentPlatform, + 'netknights' => NetknightsFirebaseOptions.currentPlatform, _ => defaultCurrentPlatform, }; static FirebaseOptions get defaultCurrentPlatform { diff --git a/lib/interfaces/api_endpoint.dart b/lib/interfaces/api_endpoint.dart index bcbb6d75b..2b854d6f1 100644 --- a/lib/interfaces/api_endpoint.dart +++ b/lib/interfaces/api_endpoint.dart @@ -2,5 +2,5 @@ abstract class ApiEndpioint { Ref get credentials; Future fetch(); - Future save(Data data); + Future sync(Data data); } diff --git a/lib/interfaces/repo/container_repository.dart b/lib/interfaces/repo/container_repository.dart index 10799375e..57c378b85 100644 --- a/lib/interfaces/repo/container_repository.dart +++ b/lib/interfaces/repo/container_repository.dart @@ -1,4 +1,3 @@ -import 'package:privacyidea_authenticator/model/token_container.dart'; import '../../model/states/token_container_state.dart'; @@ -11,11 +10,11 @@ abstract class TokenContainerStateRepository { /// Returns the loaded state Future loadContainerState(); - /// Save a token template to the repository - /// Returns the template that was actually written - Future saveTokenTemplate(TokenTemplate tokenTemplate); + // /// Save a token template to the repository + // /// Returns the template that was actually written + // Future saveTokenTemplate(TokenTemplate tokenTemplate); - /// Load a token template from the repository - /// Returns the loaded template - Future loadTokenTemplate(String tokenTemplateId); + // /// Load a token template from the repository + // /// Returns the loaded template + // Future loadTokenTemplate(String tokenTemplateId); } diff --git a/lib/model/credentials.dart b/lib/model/credentials.dart deleted file mode 100644 index e69de29bb..000000000 diff --git a/lib/model/states/token_container_state.dart b/lib/model/states/token_container_state.dart index d0189796b..e9b5617f9 100644 --- a/lib/model/states/token_container_state.dart +++ b/lib/model/states/token_container_state.dart @@ -10,19 +10,21 @@ sealed class TokenContainerState extends TokenContainer { const TokenContainerState({ required this.lastSyncedAt, // TokenContainer - required super.containerId, + required super.serial, required super.description, required super.type, - required super.tokenTemplates, + required super.syncedTokenTemplates, + required super.localTokenTemplates, }); - factory TokenContainerState.uninitialized() => const TokenContainerStateUninitialized(); + factory TokenContainerState.uninitialized(List localTokenTemplates) => + TokenContainerStateUninitialized(localTokenTemplates: localTokenTemplates); factory TokenContainerState.fromJson(Map json) => switch (json['type']) { const ('TokenContainerStateUninitialized') => _$TokenContainerStateUninitializedFromJson(json), const ('TokenContainerStateModified') => _$TokenContainerStateModifiedFromJson(json), const ('TokenContainerStateSynced') => _$TokenContainerStateSyncedFromJson(json), - const ('TokenContainerStateSyncing') => _$TokenContainerStateSyncingFromJson(json), + // const ('TokenContainerStateSyncing') => _$TokenContainerStateSyncingFromJson(json), const ('TokenContainerStateUnsynced') => _$TokenContainerStateUnsyncedFromJson(json), const ('TokenContainerStateError') => _$TokenContainerStateErrorFromJson(json), const ('TokenContainerStateDeactivated') => _$TokenContainerStateDeactivatedFromJson(json), @@ -34,126 +36,136 @@ sealed class TokenContainerState extends TokenContainer { required String stateType, dynamic data, DateTime? dateTime, - String? containerId, + String? serial, String? description, String? type, - List tokenTemplates = const [], + List syncedTokenTemplates = const [], + List localTokenTemplates = const [], DateTime? lastSyncedAt, }) => switch (stateType) { - 'TokenContainerStateUninitialized' => const TokenContainerStateUninitialized(), + 'TokenContainerStateUninitialized' => TokenContainerStateUninitialized(localTokenTemplates: localTokenTemplates), 'TokenContainerStateModified' => TokenContainerStateModified( lastModifiedAt: dateTime ?? DateTime.now(), lastSyncedAt: lastSyncedAt ?? DateTime.now(), - containerId: containerId ?? '', + serial: serial ?? '', description: description ?? '', type: type ?? '', - tokenTemplates: tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates, + localTokenTemplates: localTokenTemplates, ), 'TokenContainerStateSynced' => TokenContainerStateSynced( lastSyncedAt: lastSyncedAt ?? DateTime.now(), - containerId: containerId ?? '', + serial: serial ?? '', description: description ?? '', type: type ?? '', - tokenTemplates: tokenTemplates, - ), - 'TokenContainerStateSyncing' => TokenContainerStateSyncing( - syncStartedAt: dateTime ?? DateTime.now(), - lastSyncedAt: lastSyncedAt, - containerId: containerId ?? '', - description: description ?? '', - type: type ?? '', - tokenTemplates: tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates, ), + // 'TokenContainerStateSyncing' => TokenContainerStateSyncing( + // syncStartedAt: dateTime ?? DateTime.now(), + // lastSyncedAt: lastSyncedAt, + // serial: serial ?? '', + // description: description ?? '', + // type: type ?? '', + // syncedTokenTemplates: tokenTemplates, + // ), 'TokenContainerStateUnsynced' => TokenContainerStateUnsynced( syncAttempts: data is num ? data.floor() : 1, lastSyncedAt: lastSyncedAt, - containerId: containerId ?? '', + serial: serial ?? '', description: description ?? '', type: type ?? '', - tokenTemplates: tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates, + localTokenTemplates: localTokenTemplates, ), 'TokenContainerStateError' => TokenContainerStateError( error: data, lastSyncedAt: lastSyncedAt, - containerId: containerId ?? '', + serial: serial ?? '', description: description ?? '', type: type ?? '', - tokenTemplates: tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates, + localTokenTemplates: localTokenTemplates, ), 'TokenContainerStateDeactivated' => TokenContainerStateDeactivated( reason: data, deactivatedAt: dateTime ?? DateTime.now(), lastSyncedAt: lastSyncedAt, - containerId: containerId ?? '', + serial: serial ?? '', description: description ?? '', type: type ?? '', - tokenTemplates: tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates, + localTokenTemplates: localTokenTemplates, ), 'TokenContainerStateDeleted' => TokenContainerStateDeleted( reason: data, deletedAt: dateTime ?? DateTime.now(), lastSyncedAt: lastSyncedAt, - containerId: containerId ?? '', + serial: serial ?? '', description: description ?? '', type: type ?? '', - tokenTemplates: tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates, + localTokenTemplates: localTokenTemplates, ), _ => throw UnimplementedError(stateType), }; T as({dynamic data, DateTime? dateTime}) => switch (T) { - const (TokenContainerStateUninitialized) => const TokenContainerStateUninitialized() as T, + const (TokenContainerStateUninitialized) => TokenContainerStateUninitialized(localTokenTemplates: localTokenTemplates) as T, const (TokenContainerStateSynced) => TokenContainerStateSynced( lastSyncedAt: lastSyncedAt ?? lastSyncedAt ?? DateTime.now(), - containerId: containerId, - description: description, - type: type, - tokenTemplates: tokenTemplates, - ) as T, - const (TokenContainerStateSyncing) => TokenContainerStateSyncing( - lastSyncedAt: lastSyncedAt, - syncStartedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), - containerId: containerId, + serial: serial, description: description, type: type, - tokenTemplates: tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates, ) as T, + // const (TokenContainerStateSyncing) => TokenContainerStateSyncing( + // lastSyncedAt: lastSyncedAt, + // syncStartedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), + // serial: serial, + // description: description, + // type: type, + // tokenTemplates: syncedTokenTemplates, + // ) as T, const (TokenContainerStateUnsynced) => this is TokenContainerStateUnsynced ? (this as TokenContainerStateUnsynced).withIncrementedSyncAttempts() as T : TokenContainerStateUnsynced( lastSyncedAt: lastSyncedAt, syncAttempts: data is num ? data.floor() : 1, - containerId: containerId, + serial: serial, description: description, type: type, - tokenTemplates: tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates, + localTokenTemplates: localTokenTemplates, ) as T, const (TokenContainerStateError) => TokenContainerStateError( error: data, lastSyncedAt: lastSyncedAt, - containerId: containerId, + serial: serial, description: description, type: type, - tokenTemplates: tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates, + localTokenTemplates: localTokenTemplates, ) as T, const (TokenContainerStateDeactivated) => TokenContainerStateDeactivated( reason: data, deactivatedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), lastSyncedAt: lastSyncedAt, - containerId: containerId, + serial: serial, description: description, type: type, - tokenTemplates: tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates, + localTokenTemplates: localTokenTemplates, ) as T, const (TokenContainerStateDeleted) => TokenContainerStateDeleted( reason: data, deletedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), lastSyncedAt: lastSyncedAt, - containerId: containerId, + serial: serial, description: description, type: type, - tokenTemplates: tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates, + localTokenTemplates: localTokenTemplates, ) as T, _ => throw UnimplementedError(), }; @@ -164,7 +176,7 @@ sealed class TokenContainerState extends TokenContainer { TokenContainerStateUninitialized() => _$TokenContainerStateUninitializedToJson(this as TokenContainerStateUninitialized), TokenContainerStateModified() => _$TokenContainerStateModifiedToJson(this as TokenContainerStateModified), TokenContainerStateSynced() => _$TokenContainerStateSyncedToJson(this as TokenContainerStateSynced), - TokenContainerStateSyncing() => _$TokenContainerStateSyncingToJson(this as TokenContainerStateSyncing), + // 'TokenContainerStateSyncing() => _$TokenContainerStateSyncingToJson(this as TokenContainerStateSyncing), TokenContainerStateUnsynced() => _$TokenContainerStateUnsyncedToJson(this as TokenContainerStateUnsynced), TokenContainerStateError() => _$TokenContainerStateErrorToJson(this as TokenContainerStateError), TokenContainerStateDeactivated() => _$TokenContainerStateDeactivatedToJson(this as TokenContainerStateDeactivated), @@ -175,11 +187,12 @@ sealed class TokenContainerState extends TokenContainer { } @override - TokenContainerState copyWith({ - String? containerId, + TokenContainerStateModified copyWith({ + String? serial, String? description, String? type, - List? tokenTemplates, + List? syncedTokenTemplates, + List? localTokenTemplates, DateTime? lastSyncedAt, }); @@ -187,16 +200,16 @@ sealed class TokenContainerState extends TokenContainer { dynamic data, DateTime? dateTime, DateTime? lastSyncedAt, - String? containerId, + String? serial, String? description, String? type, List? tokenTemplates, }) { final copied = copyWith( - containerId: containerId, + serial: serial, description: description, type: type, - tokenTemplates: tokenTemplates, + syncedTokenTemplates: tokenTemplates, ); return copied.as(data: data, dateTime: dateTime); } @@ -205,29 +218,32 @@ sealed class TokenContainerState extends TokenContainer { /// ContainerState is not initialized @JsonSerializable() class TokenContainerStateUninitialized extends TokenContainerState { - const TokenContainerStateUninitialized() + const TokenContainerStateUninitialized({required super.localTokenTemplates}) : super( lastSyncedAt: null, - containerId: '', + serial: '', description: '', type: '', - tokenTemplates: const [], + syncedTokenTemplates: const [], ); @override - TokenContainerStateUnsynced copyWith({ - String? containerId, + TokenContainerStateModified copyWith({ + String? serial, String? description, String? type, - List? tokenTemplates, + List? syncedTokenTemplates, + List? localTokenTemplates, DateTime? lastSyncedAt, }) { - return TokenContainerStateUnsynced( - lastSyncedAt: lastSyncedAt ?? lastSyncedAt, - containerId: containerId ?? this.containerId, + return TokenContainerStateModified( + lastModifiedAt: DateTime.now(), + lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, + serial: serial ?? this.serial, description: description ?? this.description, type: type ?? this.type, - tokenTemplates: tokenTemplates ?? this.tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, + localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, ); } } @@ -238,30 +254,32 @@ class TokenContainerStateModified extends TokenContainerState { DateTime lastModifiedAt; TokenContainerStateModified({ required this.lastModifiedAt, - required DateTime lastSyncedAt, - required super.containerId, + required super.lastSyncedAt, + required super.serial, required super.description, required super.type, - required super.tokenTemplates, - }) : super(lastSyncedAt: lastSyncedAt); + required super.syncedTokenTemplates, + required super.localTokenTemplates, + }); @override TokenContainerStateModified copyWith({ - String? containerId, + String? serial, String? description, String? type, - List? tokenTemplates, + List? syncedTokenTemplates, + List? localTokenTemplates, DateTime? lastSyncedAt, DateTime? lastModifiedAt, - bool? highPriority, }) { return TokenContainerStateModified( lastModifiedAt: lastModifiedAt ?? this.lastModifiedAt, lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt ?? DateTime.now(), - containerId: containerId ?? this.containerId, + serial: serial ?? this.serial, description: description ?? this.description, type: type ?? this.type, - tokenTemplates: tokenTemplates ?? this.tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, + localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, ); } } @@ -269,75 +287,74 @@ class TokenContainerStateModified extends TokenContainerState { /// ContainerState is successfully synced with repo @JsonSerializable() class TokenContainerStateSynced extends TokenContainerState { - @override - DateTime get lastSyncedAt { - assert(super.lastSyncedAt != null, 'In a synced state, lastSyncedAt must not be null.'); - return super.lastSyncedAt!; - } TokenContainerStateSynced({ - required DateTime lastSyncedAt, - required super.containerId, + required super.serial, required super.description, required super.type, - required super.tokenTemplates, - }) : super(lastSyncedAt: lastSyncedAt); + required super.syncedTokenTemplates, + DateTime? lastSyncedAt, + }) : super(lastSyncedAt: lastSyncedAt ?? DateTime.now(), localTokenTemplates: const []); @override - TokenContainerStateSynced copyWith({ - String? containerId, + TokenContainerStateModified copyWith({ + String? serial, String? description, String? type, - List? tokenTemplates, + List? syncedTokenTemplates, + List? localTokenTemplates, DateTime? lastSyncedAt, + DateTime? lastModifiedAt, }) { - return TokenContainerStateSynced( + return TokenContainerStateModified( lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, - containerId: containerId ?? this.containerId, + serial: serial ?? this.serial, description: description ?? this.description, type: type ?? this.type, - tokenTemplates: tokenTemplates ?? this.tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, + localTokenTemplates: const [], + lastModifiedAt: lastModifiedAt ?? DateTime.now(), ); } } -/// ContainerState is currently syncing -@JsonSerializable() -class TokenContainerStateSyncing extends TokenContainerState { - final DateTime syncStartedAt; - final Duration timeOut; - get isSyncing => DateTime.now().difference(syncStartedAt) < timeOut; - get isTimedOut => DateTime.now().difference(syncStartedAt) > timeOut; - const TokenContainerStateSyncing({ - required this.syncStartedAt, - this.timeOut = const Duration(seconds: 30), - required super.lastSyncedAt, - required super.containerId, - required super.description, - required super.type, - required super.tokenTemplates, - }); +// /// ContainerState is currently syncing +// @JsonSerializable() +// class TokenContainerStateSyncing extends TokenContainerState { +// final DateTime syncStartedAt; +// final Duration timeOut; +// get isSyncing => DateTime.now().difference(syncStartedAt) < timeOut; +// get isTimedOut => DateTime.now().difference(syncStartedAt) > timeOut; +// const TokenContainerStateSyncing({ +// required this.syncStartedAt, +// this.timeOut = const Duration(seconds: 30), +// required super.lastSyncedAt, +// required super.serial, +// required super.description, +// required super.type, +// required super.tokenTemplates, +// }); - @override - TokenContainerStateSyncing copyWith({ - String? containerId, - String? description, - String? type, - List? tokenTemplates, - DateTime? lastSyncedAt, - DateTime? syncStartedAt, - Duration? timeOut, - }) { - return TokenContainerStateSyncing( - syncStartedAt: syncStartedAt ?? this.syncStartedAt, - timeOut: timeOut ?? this.timeOut, - lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, - containerId: containerId ?? this.containerId, - description: description ?? this.description, - type: type ?? this.type, - tokenTemplates: tokenTemplates ?? this.tokenTemplates, - ); - } -} +// @override +// TokenContainerStateSyncing copyWith({ +// String? serial, +// String? description, +// String? type, +// List? syncedTokenTemplates, +// DateTime? lastSyncedAt, +// DateTime? syncStartedAt, +// Duration? timeOut, +// }) { +// return TokenContainerStateSyncing( +// syncStartedAt: syncStartedAt ?? this.syncStartedAt, +// timeOut: timeOut ?? this.timeOut, +// lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, +// serial: serial ?? this.serial, +// description: description ?? this.description, +// type: type ?? this.type, +// tokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, +// ); +// } +// } /// ContainerState is failed last sync attempt @JsonSerializable() @@ -349,38 +366,43 @@ class TokenContainerStateUnsynced extends TokenContainerState { this.syncAttempts = 1, this.lastError, required super.lastSyncedAt, - required super.containerId, + required super.serial, required super.description, required super.type, - required super.tokenTemplates, + required super.syncedTokenTemplates, + required super.localTokenTemplates, }); TokenContainerStateUnsynced withIncrementedSyncAttempts() => withSyncAttempts(syncAttempts + 1); TokenContainerStateUnsynced withSyncAttempts(int syncAttempts) => TokenContainerStateUnsynced( syncAttempts: syncAttempts, lastSyncedAt: lastSyncedAt, - containerId: containerId, + serial: serial, description: description, type: type, - tokenTemplates: tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates, + localTokenTemplates: localTokenTemplates, ); @override - TokenContainerStateUnsynced copyWith({ - String? containerId, + TokenContainerStateModified copyWith({ + String? serial, String? description, String? type, - List? tokenTemplates, + List? syncedTokenTemplates, + List? localTokenTemplates, DateTime? lastSyncedAt, + DateTime? lastModifiedAt, int? syncAttempts, }) { - return TokenContainerStateUnsynced( - syncAttempts: syncAttempts ?? this.syncAttempts, + return TokenContainerStateModified( + lastModifiedAt: lastModifiedAt ?? DateTime.now(), lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, - containerId: containerId ?? this.containerId, + serial: serial ?? this.serial, description: description ?? this.description, type: type ?? this.type, - tokenTemplates: tokenTemplates ?? this.tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, + localTokenTemplates: this.localTokenTemplates, ); } } @@ -391,29 +413,32 @@ class TokenContainerStateError extends TokenContainerState { final dynamic error; TokenContainerStateError({ required this.error, - required super.lastSyncedAt, - required super.containerId, - required super.description, - required super.type, - required super.tokenTemplates, - }); + super.lastSyncedAt, + super.serial = 'Error', + String? description, + super.type = 'Error', + super.syncedTokenTemplates = const [], + super.localTokenTemplates = const [], + }) : super(description: description ?? error.toString()); @override - TokenContainerStateError copyWith({ - String? containerId, + TokenContainerStateModified copyWith({ + String? serial, String? description, String? type, - List? tokenTemplates, + List? syncedTokenTemplates, + List? localTokenTemplates, DateTime? lastSyncedAt, - dynamic error, + DateTime? lastModifiedAt, }) { - return TokenContainerStateError( - error: error ?? this.error, + return TokenContainerStateModified( + lastModifiedAt: lastModifiedAt ?? DateTime.now(), lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, - containerId: containerId ?? this.containerId, + serial: serial ?? this.serial, description: description ?? this.description, type: type ?? this.type, - tokenTemplates: tokenTemplates ?? this.tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, + localTokenTemplates: this.localTokenTemplates, ); } } @@ -427,35 +452,36 @@ class TokenContainerStateDeactivated extends TokenContainerState { required this.reason, required this.deactivatedAt, required super.lastSyncedAt, - required super.containerId, + required super.serial, required super.description, required super.type, - required super.tokenTemplates, + required super.syncedTokenTemplates, + required super.localTokenTemplates, }); @override - TokenContainerStateDeactivated copyWith({ - String? containerId, + TokenContainerStateModified copyWith({ + String? serial, String? description, String? type, - List? tokenTemplates, + List? syncedTokenTemplates, + List? localTokenTemplates, DateTime? lastSyncedAt, - DateTime? deactivatedAt, - dynamic reason, + DateTime? lastModifiedAt, }) { - return TokenContainerStateDeactivated( - reason: reason ?? this.reason, - deactivatedAt: deactivatedAt ?? deactivatedAt ?? DateTime.now(), + return TokenContainerStateModified( + lastModifiedAt: lastModifiedAt ?? DateTime.now(), lastSyncedAt: lastSyncedAt ?? lastSyncedAt, - containerId: containerId ?? this.containerId, + serial: serial ?? this.serial, description: description ?? this.description, type: type ?? this.type, - tokenTemplates: tokenTemplates ?? this.tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, + localTokenTemplates: this.localTokenTemplates, ); } } -/// ContainerState is deleted repo-side +/// ContainerState is deleted repo-sseriale @JsonSerializable() class TokenContainerStateDeleted extends TokenContainerState { final DateTime deletedAt; @@ -464,30 +490,32 @@ class TokenContainerStateDeleted extends TokenContainerState { required this.reason, required this.deletedAt, required super.lastSyncedAt, - required super.containerId, + required super.serial, required super.description, required super.type, - required super.tokenTemplates, + required super.syncedTokenTemplates, + required super.localTokenTemplates, }); @override - TokenContainerStateDeleted copyWith({ - String? containerId, + TokenContainerStateModified copyWith({ + String? serial, String? description, String? type, - List? tokenTemplates, + List? syncedTokenTemplates, + List? localTokenTemplates, DateTime? lastSyncedAt, - DateTime? deletedAt, - dynamic reason, + DateTime? lastModifiedAt, + }) { - return TokenContainerStateDeleted( - reason: reason ?? this.reason, - deletedAt: deletedAt ?? this.deletedAt, + return TokenContainerStateModified( + lastModifiedAt: lastModifiedAt ?? DateTime.now(), lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, - containerId: containerId ?? this.containerId, + serial: serial ?? this.serial, description: description ?? this.description, type: type ?? this.type, - tokenTemplates: tokenTemplates ?? this.tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, + localTokenTemplates: this.localTokenTemplates, ); } } diff --git a/lib/model/states/token_container_state.g.dart b/lib/model/states/token_container_state.g.dart index cf6047ac5..e0a081455 100644 --- a/lib/model/states/token_container_state.g.dart +++ b/lib/model/states/token_container_state.g.dart @@ -8,21 +8,32 @@ part of 'token_container_state.dart'; TokenContainerStateUninitialized _$TokenContainerStateUninitializedFromJson( Map json) => - TokenContainerStateUninitialized(); + TokenContainerStateUninitialized( + localTokenTemplates: (json['localTokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) + .toList(), + ); Map _$TokenContainerStateUninitializedToJson( TokenContainerStateUninitialized instance) => - {}; + { + 'localTokenTemplates': instance.localTokenTemplates, + }; TokenContainerStateModified _$TokenContainerStateModifiedFromJson( Map json) => TokenContainerStateModified( lastModifiedAt: DateTime.parse(json['lastModifiedAt'] as String), - lastSyncedAt: DateTime.parse(json['lastSyncedAt'] as String), - containerId: json['containerId'] as String, + lastSyncedAt: json['lastSyncedAt'] == null + ? null + : DateTime.parse(json['lastSyncedAt'] as String), + serial: json['serial'] as String, description: json['description'] as String, type: json['type'] as String, - tokenTemplates: (json['tokenTemplates'] as List) + syncedTokenTemplates: (json['syncedTokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) + .toList(), + localTokenTemplates: (json['localTokenTemplates'] as List) .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), ); @@ -30,10 +41,11 @@ TokenContainerStateModified _$TokenContainerStateModifiedFromJson( Map _$TokenContainerStateModifiedToJson( TokenContainerStateModified instance) => { - 'containerId': instance.containerId, + 'serial': instance.serial, 'description': instance.description, 'type': instance.type, - 'tokenTemplates': instance.tokenTemplates, + 'syncedTokenTemplates': instance.syncedTokenTemplates, + 'localTokenTemplates': instance.localTokenTemplates, 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), 'lastModifiedAt': instance.lastModifiedAt.toIso8601String(), }; @@ -41,53 +53,25 @@ Map _$TokenContainerStateModifiedToJson( TokenContainerStateSynced _$TokenContainerStateSyncedFromJson( Map json) => TokenContainerStateSynced( - lastSyncedAt: DateTime.parse(json['lastSyncedAt'] as String), - containerId: json['containerId'] as String, + serial: json['serial'] as String, description: json['description'] as String, type: json['type'] as String, - tokenTemplates: (json['tokenTemplates'] as List) + syncedTokenTemplates: (json['syncedTokenTemplates'] as List) .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), - ); - -Map _$TokenContainerStateSyncedToJson( - TokenContainerStateSynced instance) => - { - 'containerId': instance.containerId, - 'description': instance.description, - 'type': instance.type, - 'tokenTemplates': instance.tokenTemplates, - 'lastSyncedAt': instance.lastSyncedAt.toIso8601String(), - }; - -TokenContainerStateSyncing _$TokenContainerStateSyncingFromJson( - Map json) => - TokenContainerStateSyncing( - syncStartedAt: DateTime.parse(json['syncStartedAt'] as String), - timeOut: json['timeOut'] == null - ? const Duration(seconds: 30) - : Duration(microseconds: (json['timeOut'] as num).toInt()), lastSyncedAt: json['lastSyncedAt'] == null ? null : DateTime.parse(json['lastSyncedAt'] as String), - containerId: json['containerId'] as String, - description: json['description'] as String, - type: json['type'] as String, - tokenTemplates: (json['tokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), ); -Map _$TokenContainerStateSyncingToJson( - TokenContainerStateSyncing instance) => +Map _$TokenContainerStateSyncedToJson( + TokenContainerStateSynced instance) => { - 'containerId': instance.containerId, + 'serial': instance.serial, 'description': instance.description, 'type': instance.type, - 'tokenTemplates': instance.tokenTemplates, + 'syncedTokenTemplates': instance.syncedTokenTemplates, 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), - 'syncStartedAt': instance.syncStartedAt.toIso8601String(), - 'timeOut': instance.timeOut.inMicroseconds, }; TokenContainerStateUnsynced _$TokenContainerStateUnsyncedFromJson( @@ -98,10 +82,13 @@ TokenContainerStateUnsynced _$TokenContainerStateUnsyncedFromJson( lastSyncedAt: json['lastSyncedAt'] == null ? null : DateTime.parse(json['lastSyncedAt'] as String), - containerId: json['containerId'] as String, + serial: json['serial'] as String, description: json['description'] as String, type: json['type'] as String, - tokenTemplates: (json['tokenTemplates'] as List) + syncedTokenTemplates: (json['syncedTokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) + .toList(), + localTokenTemplates: (json['localTokenTemplates'] as List) .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), ); @@ -109,10 +96,11 @@ TokenContainerStateUnsynced _$TokenContainerStateUnsyncedFromJson( Map _$TokenContainerStateUnsyncedToJson( TokenContainerStateUnsynced instance) => { - 'containerId': instance.containerId, + 'serial': instance.serial, 'description': instance.description, 'type': instance.type, - 'tokenTemplates': instance.tokenTemplates, + 'syncedTokenTemplates': instance.syncedTokenTemplates, + 'localTokenTemplates': instance.localTokenTemplates, 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), 'syncAttempts': instance.syncAttempts, 'lastError': instance.lastError, @@ -125,21 +113,27 @@ TokenContainerStateError _$TokenContainerStateErrorFromJson( lastSyncedAt: json['lastSyncedAt'] == null ? null : DateTime.parse(json['lastSyncedAt'] as String), - containerId: json['containerId'] as String, - description: json['description'] as String, - type: json['type'] as String, - tokenTemplates: (json['tokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), + serial: json['serial'] as String? ?? 'Error', + description: json['description'] as String?, + type: json['type'] as String? ?? 'Error', + syncedTokenTemplates: (json['syncedTokenTemplates'] as List?) + ?.map((e) => TokenTemplate.fromJson(e as Map)) + .toList() ?? + const [], + localTokenTemplates: (json['localTokenTemplates'] as List?) + ?.map((e) => TokenTemplate.fromJson(e as Map)) + .toList() ?? + const [], ); Map _$TokenContainerStateErrorToJson( TokenContainerStateError instance) => { - 'containerId': instance.containerId, + 'serial': instance.serial, 'description': instance.description, 'type': instance.type, - 'tokenTemplates': instance.tokenTemplates, + 'syncedTokenTemplates': instance.syncedTokenTemplates, + 'localTokenTemplates': instance.localTokenTemplates, 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), 'error': instance.error, }; @@ -152,10 +146,13 @@ TokenContainerStateDeactivated _$TokenContainerStateDeactivatedFromJson( lastSyncedAt: json['lastSyncedAt'] == null ? null : DateTime.parse(json['lastSyncedAt'] as String), - containerId: json['containerId'] as String, + serial: json['serial'] as String, description: json['description'] as String, type: json['type'] as String, - tokenTemplates: (json['tokenTemplates'] as List) + syncedTokenTemplates: (json['syncedTokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) + .toList(), + localTokenTemplates: (json['localTokenTemplates'] as List) .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), ); @@ -163,10 +160,11 @@ TokenContainerStateDeactivated _$TokenContainerStateDeactivatedFromJson( Map _$TokenContainerStateDeactivatedToJson( TokenContainerStateDeactivated instance) => { - 'containerId': instance.containerId, + 'serial': instance.serial, 'description': instance.description, 'type': instance.type, - 'tokenTemplates': instance.tokenTemplates, + 'syncedTokenTemplates': instance.syncedTokenTemplates, + 'localTokenTemplates': instance.localTokenTemplates, 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), 'deactivatedAt': instance.deactivatedAt.toIso8601String(), 'reason': instance.reason, @@ -180,10 +178,13 @@ TokenContainerStateDeleted _$TokenContainerStateDeletedFromJson( lastSyncedAt: json['lastSyncedAt'] == null ? null : DateTime.parse(json['lastSyncedAt'] as String), - containerId: json['containerId'] as String, + serial: json['serial'] as String, description: json['description'] as String, type: json['type'] as String, - tokenTemplates: (json['tokenTemplates'] as List) + syncedTokenTemplates: (json['syncedTokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) + .toList(), + localTokenTemplates: (json['localTokenTemplates'] as List) .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), ); @@ -191,10 +192,11 @@ TokenContainerStateDeleted _$TokenContainerStateDeletedFromJson( Map _$TokenContainerStateDeletedToJson( TokenContainerStateDeleted instance) => { - 'containerId': instance.containerId, + 'serial': instance.serial, 'description': instance.description, 'type': instance.type, - 'tokenTemplates': instance.tokenTemplates, + 'syncedTokenTemplates': instance.syncedTokenTemplates, + 'localTokenTemplates': instance.localTokenTemplates, 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), 'deletedAt': instance.deletedAt.toIso8601String(), 'reason': instance.reason, diff --git a/lib/model/states/token_state.dart b/lib/model/states/token_state.dart index fcf45623b..9d52ed3a2 100644 --- a/lib/model/states/token_state.dart +++ b/lib/model/states/token_state.dart @@ -29,7 +29,9 @@ class TokenState { : tokens = List.from(tokens), lastlyUpdatedTokens = List.from(lastlyUpdatedTokens ?? tokens); - List get nonPiTokens => tokens.where((token) => token.isPrivacyIdeaToken == false).toList(); + + + List get tokensNotInContainer => tokens.maybePiTokens.where((token) => token.containerId != null).toList(); PushToken? getTokenBySerial(String serial) => pushTokens.firstWhereOrNull((element) => element.serial == serial); @@ -142,9 +144,14 @@ class TokenState { tokens.inFolder(folder, only: only, exclude: exclude); List tokensWithoutFolder({List only = const [], List exclude = const []}) => tokens.withoutFolder(only: only, exclude: exclude); + + List containerTokens(String containerId) => tokens.piTokens.fromContainer(containerId); } extension TokenListExtension on List { + List get nonPiTokens => where((token) => token.isPrivacyIdeaToken == false).toList(); + List get maybePiTokens => where((token) => token.isPrivacyIdeaToken == null).toList(); + List get piTokens => where((token) => token.isPrivacyIdeaToken == true).toList(); List inFolder(TokenFolder folder, {List only = const [], List exclude = const []}) => where((token) { if (token.folderId != folder.folderId) return false; if (exclude.contains(token.runtimeType)) return false; @@ -159,10 +166,11 @@ extension TokenListExtension on List { return true; }).toList(); - List get fromContainer => where((token) => token.origin?.source == TokenOriginSourceType.container).toList(); + List fromContainer(String containerId) => + where((token) => token.origin?.source == TokenOriginSourceType.container && token.containerId == containerId).toList(); List toTemplates() { if (isEmpty) return []; - return map((token) => TokenTemplate(data: token.toUriMap())).toList(); + return map((token) => token.toTemplate()).toList(); } } diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index ef539cbe1..9b252ccb6 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -8,32 +8,36 @@ part 'token_container.g.dart'; @JsonSerializable() class TokenContainer { - final String containerId; + final String serial; final String description; final String type; - final List tokenTemplates; + final List syncedTokenTemplates; + final List localTokenTemplates; const TokenContainer({ - required this.containerId, + required this.serial, required this.description, required this.type, - required this.tokenTemplates, + required this.syncedTokenTemplates, + required this.localTokenTemplates, }); factory TokenContainer.fromJson(Map json) => _$TokenContainerFromJson(json); Map toJson() => _$TokenContainerToJson(this); TokenContainer copyWith({ - String? containerId, + String? serial, String? description, String? type, - List? tokenTemplates, + List? syncedTokenTemplates, + List? localTokenTemplates, }) { return TokenContainer( - containerId: containerId ?? this.containerId, + serial: serial ?? this.serial, description: description ?? this.description, type: type ?? this.type, - tokenTemplates: tokenTemplates ?? this.tokenTemplates, + syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, + localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, ); } } @@ -52,8 +56,26 @@ class TokenTemplate { TokenTemplate({required this.data}); + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is TokenTemplate && other.data == data; + } + + @override + int get hashCode => Object.hashAll([data, runtimeType]); + factory TokenTemplate.fromJson(Map json) => _$TokenTemplateFromJson(json); Map toJson() => _$TokenTemplateToJson(this); Token toToken() => Token.fromUriMap(data); + + TokenTemplate copyWith(Map replace) { + return TokenTemplate( + data: Map.from(data)..addAll(replace), + ); + } + + } diff --git a/lib/model/token_container.g.dart b/lib/model/token_container.g.dart index 591511658..0be44a752 100644 --- a/lib/model/token_container.g.dart +++ b/lib/model/token_container.g.dart @@ -8,20 +8,24 @@ part of 'token_container.dart'; TokenContainer _$TokenContainerFromJson(Map json) => TokenContainer( - containerId: json['containerId'] as String, + serial: json['serial'] as String, description: json['description'] as String, type: json['type'] as String, - tokenTemplates: (json['tokenTemplates'] as List) + syncedTokenTemplates: (json['syncedTokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) + .toList(), + localTokenTemplates: (json['localTokenTemplates'] as List) .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), ); Map _$TokenContainerToJson(TokenContainer instance) => { - 'containerId': instance.containerId, + 'serial': instance.serial, 'description': instance.description, 'type': instance.type, - 'tokenTemplates': instance.tokenTemplates, + 'syncedTokenTemplates': instance.syncedTokenTemplates, + 'localTokenTemplates': instance.localTokenTemplates, }; TokenTemplate _$TokenTemplateFromJson(Map json) => diff --git a/lib/model/tokens/container_credentials.dart b/lib/model/tokens/container_credentials.dart new file mode 100644 index 000000000..ec51e2421 --- /dev/null +++ b/lib/model/tokens/container_credentials.dart @@ -0,0 +1,14 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; + +part 'container_credentials.g.dart'; + +@JsonSerializable() +class ContainerCredentials extends PushToken { + ContainerCredentials({required super.serial, required super.id}); + + factory ContainerCredentials.fromUriMap(Map uriMap) => throw UnimplementedError(); // PushToken.fromUriMap(uriMap); + + factory ContainerCredentials.fromJson(Map json) => _$ContainerCredentialsFromJson(json); + Map toJson() => _$ContainerCredentialsToJson(this); +} diff --git a/lib/model/tokens/container_credentials.g.dart b/lib/model/tokens/container_credentials.g.dart new file mode 100644 index 000000000..40f015ec4 --- /dev/null +++ b/lib/model/tokens/container_credentials.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'container_credentials.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ContainerCredentials _$ContainerCredentialsFromJson( + Map json) => + ContainerCredentials( + serial: json['serial'] as String, + id: json['id'] as String, + ); + +Map _$ContainerCredentialsToJson( + ContainerCredentials instance) => + { + 'id': instance.id, + 'serial': instance.serial, + }; diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index a9d5d6202..4e1f5e3dd 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -30,6 +30,7 @@ class DayPasswordToken extends OTPToken { required super.algorithm, required super.digits, required super.secret, + super.serial, this.viewMode = DayPasswordTokenViewMode.VALIDFOR, String? type, // just for @JsonSerializable(): type of DayPasswordToken is always TokenTypes.DAYPASSWORD super.tokenImage, @@ -66,6 +67,7 @@ class DayPasswordToken extends OTPToken { @override DayPasswordToken copyWith({ + String? serial, Duration? period, DayPasswordTokenViewMode? viewMode, String? label, @@ -83,6 +85,7 @@ class DayPasswordToken extends OTPToken { TokenOriginData? origin, }) => DayPasswordToken( + serial: serial ?? this.serial, period: period ?? this.period, viewMode: viewMode ?? this.viewMode, label: label ?? this.label, diff --git a/lib/model/tokens/day_password_token.g.dart b/lib/model/tokens/day_password_token.g.dart index e85e5172e..5011056e3 100644 --- a/lib/model/tokens/day_password_token.g.dart +++ b/lib/model/tokens/day_password_token.g.dart @@ -13,6 +13,7 @@ DayPasswordToken _$DayPasswordTokenFromJson(Map json) => algorithm: $enumDecode(_$AlgorithmsEnumMap, json['algorithm']), digits: (json['digits'] as num).toInt(), secret: json['secret'] as String, + serial: json['serial'] as String?, viewMode: $enumDecodeNullable( _$DayPasswordTokenViewModeEnumMap, json['viewMode']) ?? DayPasswordTokenViewMode.VALIDFOR, @@ -35,6 +36,7 @@ Map _$DayPasswordTokenToJson(DayPasswordToken instance) => 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, + 'serial': instance.serial, 'pin': instance.pin, 'isLocked': instance.isLocked, 'isHidden': instance.isHidden, diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index 826f7316e..b437732cc 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -29,6 +29,7 @@ class HOTPToken extends OTPToken { required super.algorithm, required super.digits, required super.secret, + super.serial, String? type, // just for @JsonSerializable(): type of HOTPToken is always TokenTypes.HOTP super.tokenImage, super.pin, @@ -60,6 +61,7 @@ class HOTPToken extends OTPToken { @override HOTPToken copyWith({ + String? serial, int? counter, String? label, String? issuer, @@ -76,6 +78,7 @@ class HOTPToken extends OTPToken { TokenOriginData? origin, }) => HOTPToken( + serial: serial ?? this.serial, counter: counter ?? this.counter, label: label ?? this.label, issuer: issuer ?? this.issuer, diff --git a/lib/model/tokens/hotp_token.g.dart b/lib/model/tokens/hotp_token.g.dart index 0d2a99bd4..e6c23a3a6 100644 --- a/lib/model/tokens/hotp_token.g.dart +++ b/lib/model/tokens/hotp_token.g.dart @@ -12,6 +12,7 @@ HOTPToken _$HOTPTokenFromJson(Map json) => HOTPToken( algorithm: $enumDecode(_$AlgorithmsEnumMap, json['algorithm']), digits: (json['digits'] as num).toInt(), secret: json['secret'] as String, + serial: json['serial'] as String?, type: json['type'] as String?, tokenImage: json['tokenImage'] as String?, pin: json['pin'] as bool?, @@ -30,6 +31,7 @@ Map _$HOTPTokenToJson(HOTPToken instance) => { 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, + 'serial': instance.serial, 'pin': instance.pin, 'isLocked': instance.isLocked, 'isHidden': instance.isHidden, diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index a803c3e94..257ac44c2 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -24,6 +24,7 @@ abstract class OTPToken extends Token { required this.secret, required super.id, required super.type, + super.serial, super.pin, super.tokenImage, super.isLocked, @@ -46,6 +47,7 @@ abstract class OTPToken extends Token { @override OTPToken copyWith({ + String? serial, String? label, String? issuer, String? id, diff --git a/lib/model/tokens/steam_token.dart b/lib/model/tokens/steam_token.dart index 0eea73e64..5c8603566 100644 --- a/lib/model/tokens/steam_token.dart +++ b/lib/model/tokens/steam_token.dart @@ -26,6 +26,7 @@ class SteamToken extends TOTPToken { SteamToken({ required super.id, required super.secret, + super.serial, String? type, super.tokenImage, super.pin, @@ -45,6 +46,7 @@ class SteamToken extends TOTPToken { @override SteamToken copyWith({ + String? serial, String? label, String? issuer, String? id, @@ -61,6 +63,7 @@ class SteamToken extends TOTPToken { String? secret, }) { return SteamToken( + serial: serial ?? this.serial, label: label ?? this.label, issuer: issuer ?? this.issuer, id: id ?? this.id, diff --git a/lib/model/tokens/steam_token.g.dart b/lib/model/tokens/steam_token.g.dart index d104764d6..e5932abae 100644 --- a/lib/model/tokens/steam_token.g.dart +++ b/lib/model/tokens/steam_token.g.dart @@ -9,6 +9,7 @@ part of 'steam_token.dart'; SteamToken _$SteamTokenFromJson(Map json) => SteamToken( id: json['id'] as String, secret: json['secret'] as String, + serial: json['serial'] as String?, type: json['type'] as String?, tokenImage: json['tokenImage'] as String?, pin: json['pin'] as bool?, @@ -28,6 +29,7 @@ Map _$SteamTokenToJson(SteamToken instance) => 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, + 'serial': instance.serial, 'pin': instance.pin, 'isLocked': instance.isLocked, 'isHidden': instance.isHidden, diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index 286cd1c87..cfc9612c4 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -22,6 +22,7 @@ abstract class Token with SortableMixin { final String label; // the name of the token, it cannot be uses as an identifier final String issuer; // The issuer of this token, currently unused. final String id; // this is the identifier of the token + final String? serial; // The serial of the token, this is used to identify the token in the privacyIDEA server. final bool pin; final bool isLocked; final bool isHidden; @@ -58,6 +59,7 @@ abstract class Token with SortableMixin { } const Token({ + this.serial, this.label = '', this.issuer = '', this.containerId, @@ -86,6 +88,7 @@ abstract class Token with SortableMixin { @override Token copyWith({ + String? serial, String? label, String? issuer, String? id, @@ -136,7 +139,7 @@ abstract class Token with SortableMixin { /// ``` Map toUriMap() { return { - TOKEN_ID: id, + URI_SERIAL: id, URI_TYPE: type, URI_LABEL: label, URI_ISSUER: issuer, @@ -156,4 +159,6 @@ abstract class Token with SortableMixin { } Token copyWithFromTemplate(TokenTemplate template); + + TokenTemplate toTemplate() => TokenTemplate(data: toUriMap()); } diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index 06194f452..73dc6f893 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -48,6 +48,7 @@ class TOTPToken extends OTPToken { required super.algorithm, required super.digits, required super.secret, + super.serial, String? type, super.tokenImage, super.pin, @@ -70,6 +71,7 @@ class TOTPToken extends OTPToken { @override TOTPToken copyWith({ + String? serial, String? label, String? issuer, String? id, @@ -86,6 +88,7 @@ class TOTPToken extends OTPToken { TokenOriginData? origin, }) { return TOTPToken( + serial: serial ?? this.serial, label: label ?? this.label, issuer: issuer ?? this.issuer, id: id ?? this.id, diff --git a/lib/model/tokens/totp_token.g.dart b/lib/model/tokens/totp_token.g.dart index 86924f6c7..941c8ce74 100644 --- a/lib/model/tokens/totp_token.g.dart +++ b/lib/model/tokens/totp_token.g.dart @@ -12,6 +12,7 @@ TOTPToken _$TOTPTokenFromJson(Map json) => TOTPToken( algorithm: $enumDecode(_$AlgorithmsEnumMap, json['algorithm']), digits: (json['digits'] as num).toInt(), secret: json['secret'] as String, + serial: json['serial'] as String?, type: json['type'] as String?, tokenImage: json['tokenImage'] as String?, pin: json['pin'] as bool?, @@ -30,6 +31,7 @@ Map _$TOTPTokenToJson(TOTPToken instance) => { 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, + 'serial': instance.serial, 'pin': instance.pin, 'isLocked': instance.isLocked, 'isHidden': instance.isHidden, diff --git a/lib/processors/scheme_processors/container_credentials_processor.dart b/lib/processors/scheme_processors/container_credentials_processor.dart index fa8b9223f..6ec5db44e 100644 --- a/lib/processors/scheme_processors/container_credentials_processor.dart +++ b/lib/processors/scheme_processors/container_credentials_processor.dart @@ -2,6 +2,8 @@ import 'package:privacyidea_authenticator/processors/scheme_processors/scheme_pr import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; +import '../../model/tokens/container_credentials.dart'; + class ContainerCredentialsProcessor extends SchemeProcessor { @override Set get supportedSchemes => {'container'}; // TODO: edit supportedSchemes to the real supported schemes @@ -13,7 +15,7 @@ class ContainerCredentialsProcessor extends SchemeProcessor { if (!supportedHosts.contains(uri.host) || !supportedSchemes.contains(uri.scheme)) { return null; } - final credentials = Credentials.fromUriMap(uri.queryParameters); + final credentials = ContainerCredentials.fromUriMap(uri.queryParameters); globalRef?.read(credentialsProvider.notifier).setCredentials(credentials); } } diff --git a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart index df95d41f7..3c24338f9 100644 --- a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart @@ -1,6 +1,4 @@ -import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:privacyidea_authenticator/utils/errors.dart'; -import 'package:collection/collection.dart'; import '../../interfaces/repo/container_repository.dart'; import '../../model/states/token_container_state.dart'; @@ -9,23 +7,17 @@ import '../../utils/logger.dart'; class HybridTokenContainerStateRepository implements TokenContainerStateRepository { final LocalRepo _localRepository; - final LocalRepo _syncedRepository; - final RemoteRepo? _remoteRepository; - + final RemoteRepo _remoteRepository; /// HybridTokenContainerStateRepository({ required LocalRepo localRepository, - required LocalRepo syncedRepository, - required RemoteRepo? remoteRepository, + required RemoteRepo remoteRepository, }) : _localRepository = localRepository, - _syncedRepository = syncedRepository, _remoteRepository = remoteRepository; @override Future loadContainerState({bool isInitial = false}) async { - TokenContainerState? remoteState; - TokenContainerState lastSyncedState; TokenContainerState localState; TokenContainerState newState; @@ -37,187 +29,139 @@ class HybridTokenContainerStateRepository localization.failedToLoad('local container state'), ), - lastSyncedAt: null, - containerId: '', - description: '', - type: '', - tokenTemplates: [], ); } try { - remoteState = await _remoteRepository?.loadContainerState(); - lastSyncedState = await _syncedRepository.loadContainerState(); + newState = await _remoteRepository.saveContainerState(localState); } catch (e) { newState = localState.copyTransformInto(); - return newState; } - newState = await _merge( - localState: localState, - remoteState: remoteState, - lastSyncedState: lastSyncedState, - ); - if (newState is TokenContainerStateSynced) { - try { - await _syncedRepository.saveContainerState(newState); - } catch (e) { - Logger.error('Failed to save synced state to local repository'); - } + + + try { + await _localRepository.saveContainerState(newState); + } catch (e) { + Logger.error('Failed to save synced state to local repository'); } + return newState; } @override - Future saveContainerState(TokenContainerState newLocalState) async { - if (newLocalState is TokenContainerStateError) { + Future saveContainerState(TokenContainerState currentState) async { + if (currentState is TokenContainerStateError) { Logger.warning('Cannot save error state to repository'); - return newLocalState; + return currentState; } - TokenContainerState? remoteState; - TokenContainerState lastSyncedState; TokenContainerState newState; try { - remoteState = await _remoteRepository?.loadContainerState(); - lastSyncedState = await _syncedRepository.loadContainerState(); + newState = await _remoteRepository.saveContainerState(currentState); } catch (e) { - newState = newLocalState.copyTransformInto(); + Logger.warning('Failed to save state to remote repository: Changed to unsynced state'); + newState = currentState.copyTransformInto(); return _localRepository.saveContainerState(newState); } - newState = await _merge( - localState: newLocalState, - remoteState: remoteState, - lastSyncedState: lastSyncedState, - ); + try { - await _remoteRepository?.saveContainerState(newState); + newState = await _localRepository.saveContainerState(newState); } catch (e) { - newState = newState.copyTransformInto(); - await _localRepository.saveContainerState(newState); + Logger.error('Failed to save state to local repository'); return newState; } - await _syncedRepository.saveContainerState(newState); - await _localRepository.saveContainerState(newState); return newState; } - Future _merge({ - required TokenContainerState localState, - required TokenContainerState? remoteState, - required TokenContainerState lastSyncedState, - }) async { - List localTemplates; - List remoteTemplates; - List syncedTemplates; - if (localState is TokenContainerStateUninitialized) { - // Uninitialized state is always overwritten by other states - localTemplates = []; - } else { - localTemplates = localState.tokenTemplates; - } - if (remoteState is TokenContainerStateUninitialized) { - // Uninitialized state is always overwritten by other states - remoteTemplates = []; - } else { - remoteTemplates = remoteState?.tokenTemplates ?? []; - } - if (lastSyncedState is TokenContainerStateUninitialized) { - // Uninitialized state is always overwritten by other states - syncedTemplates = []; - } else { - syncedTemplates = lastSyncedState.tokenTemplates; - } - - final mergedTemplates = await _mergeTemplateLists( - localTemplates: localTemplates, - remoteTemplates: remoteTemplates, - syncedTemplates: syncedTemplates, - ); - - final newSyncedState = TokenContainerStateSynced( - lastSyncedAt: DateTime.now(), - containerId: localState.containerId, - description: localState.description, - type: '', // TODO: Implement type - tokenTemplates: mergedTemplates, - ); - return newSyncedState; - } - - Future> _mergeTemplateLists({ - required List localTemplates, - required List remoteTemplates, - required List syncedTemplates, - }) async { - final mergedTemplates = []; - - // Add all templates that are in the synced state - for (var syncedTemplate in syncedTemplates) { - final localTemplateIndex = localTemplates.indexWhere((template) => template.id == syncedTemplate.id); - final localTemplate = localTemplateIndex != -1 ? localTemplates.removeAt(localTemplateIndex) : null; - final remoteTemplateIndex = remoteTemplates.indexWhere((template) => template.id == syncedTemplate.id); - final remoteTemplate = remoteTemplateIndex != -1 ? remoteTemplates.removeAt(remoteTemplateIndex) : null; - - mergedTemplates.add(await _mergeTemplates(localTemplate, remoteTemplate, syncedTemplate)); - } - - // Add all remaining local templates - for (var localTemplate in localTemplates) { - final remoteTemplateIndex = remoteTemplates.indexWhere((template) => template.id == localTemplate.id); - final remoteTemplate = remoteTemplateIndex != -1 ? remoteTemplates.removeAt(remoteTemplateIndex) : null; - - mergedTemplates.add(await _mergeTemplates(localTemplate, remoteTemplate, null)); - } - - // Add all remaining remote templates - mergedTemplates.addAll(remoteTemplates); - - return mergedTemplates; - } + // Future _merge({ + // required TokenContainerState localState, + // required TokenContainerState remoteState, + // }) async { + // List localTemplates; + // List remoteTemplates; + // if (localState is TokenContainerStateUninitialized) { + // // Uninitialized state is always overwritten by other states + // localTemplates = []; + // } else { + // localTemplates = localState.tokenTemplates; + // } + // if (remoteState is TokenContainerStateUninitialized) { + // // Uninitialized state is always overwritten by other states + // remoteTemplates = []; + // } else { + // remoteTemplates = remoteState.tokenTemplates ?? []; + // } + + // final mergedTemplates = await _mergeTemplateLists( + // localTemplates: localTemplates, + // remoteTemplates: remoteTemplates, + // ); + + // final newSyncedState = TokenContainerStateSynced( + // lastSyncedAt: DateTime.now(), + // containerId: localState.containerId, + // description: localState.description, + // type: '', // TODO: Implement type + // tokenTemplates: mergedTemplates, + // ); + // return newSyncedState; + // } + + // Future> _mergeTemplateLists({ + // required List localTemplates, + // required List remoteTemplates, + // }) async { + // final mergedTemplates = []; + + // // Add all remaining local templates + // for (var localTemplate in localTemplates) { + // final remoteTemplate = remoteTemplates.firstWhereOrNull((template) => template.id == localTemplate.id); + // if (remoteTemplate == null) { + // mergedTemplates.add(localTemplate); + // continue; + // } + // mergedTemplates.add(await _mergeTemplates(localTemplate, remoteTemplate)); + // } + + // // Add all remaining remote templates + // mergedTemplates.addAll(remoteTemplates); + + // return mergedTemplates; + // } /// Merges local and remote token templates with the last synced state /// If both local and remote templates have changed, the remote changes are prioritized - Future _mergeTemplates(TokenTemplate? local, TokenTemplate? remote, TokenTemplate? lastSynced) async { - final id = local?.id ?? remote?.id ?? lastSynced?.id; - assert(id != null, 'At least one template must be provided'); - assert((local == null || local.id == id) && (remote == null || remote.id == id) && (lastSynced == null || lastSynced.id == id), - 'All templates must have the same id'); - final mergedData = {}; - - print('------------------------------------------------------------------'); - print('LastSynced: ${lastSynced?.data}'); - final lastSyncedData = lastSynced?.data ?? {}; - mergedData.addAll(lastSyncedData); - print('MergedData: $mergedData'); - print('------------------------------------------------------------------'); - print('Local: ${local?.data}'); - final localData = local?.data ?? {}; - mergedData.addAll(localData); - print('MergedData: $mergedData'); - print('------------------------------------------------------------------'); - print('Remote: ${remote?.data}'); - final remoteData = remote?.data ?? {}; - mergedData.addAll(remoteData); - print('MergedData: $mergedData'); - print('------------------------------------------------------------------'); - - return TokenTemplate(data: mergedData); - } - - @override - Future loadTokenTemplate(String tokenTemplateId) async { - final state = await loadContainerState(); - final template = state.tokenTemplates.firstWhereOrNull((template) => template.id == tokenTemplateId); - return template; - } - - @override - Future saveTokenTemplate(TokenTemplate tokenTemplate) async { - final state = await loadContainerState(); - final templates = state.tokenTemplates; - templates.removeWhere((template) => template.id == tokenTemplate.id); - templates.add(tokenTemplate); - final newState = state.copyWith(tokenTemplates: templates); - final savedState = await saveContainerState(newState); - return savedState.tokenTemplates.firstWhere((template) => template.id == tokenTemplate.id); - } + // Future _mergeTemplates(TokenTemplate local, TokenTemplate remote) async { + // assert(local.id == remote.id, 'Both templates must have the same id'); + // final mergedData = {}; + + // print('------------------------------------------------------------------'); + // print('Local: ${local.data}'); + // mergedData.addAll(local.data); + // print('MergedData: $mergedData'); + // print('------------------------------------------------------------------'); + // print('Remote: ${remote.data}'); + // mergedData.addAll(remote.data); + // print('MergedData: $mergedData'); + // print('------------------------------------------------------------------'); + + // return TokenTemplate(data: mergedData); + // } + + // @override + // Future loadTokenTemplate(String tokenTemplateId) async { + // final state = await loadContainerState(); + // final template = state.tokenTemplates.firstWhereOrNull((template) => template.id == tokenTemplateId); + // return template; + // } + + // @override + // Future saveTokenTemplate(TokenTemplate tokenTemplate) async { + // final state = await loadContainerState(); + // final templates = state.tokenTemplates; + // templates.removeWhere((template) => template.id == tokenTemplate.id); + // templates.add(tokenTemplate); + // final newState = state.copyWith(tokenTemplates: templates); + // final savedState = await saveContainerState(newState); + // return savedState.tokenTemplates.firstWhere((template) => template.id == tokenTemplate.id); + // } } diff --git a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart index c44dab8d1..4cc7d6be3 100644 --- a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart @@ -1,6 +1,4 @@ -import 'package:collection/collection.dart'; import 'package:mutex/mutex.dart'; -import 'package:privacyidea_authenticator/model/token_container.dart'; import '../../api/token_container_api_endpoint.dart'; import '../../interfaces/repo/container_repository.dart'; @@ -20,8 +18,8 @@ class RemoteTokenContainerStateRepository implements TokenContainerStateReposito Future _saveContainerState(TokenContainerState containerState) async { try { return await _protect(() async { - await apiEndpoint.save(containerState); - return containerState; + var synced = await apiEndpoint.sync(containerState); + return synced; }); } catch (e) { rethrow; @@ -33,23 +31,22 @@ class RemoteTokenContainerStateRepository implements TokenContainerStateReposito Future _fetchContainerState() async => await _protect(() async => await apiEndpoint.fetch()); - @override - Future loadTokenTemplate(String tokenTemplateId) async { - final state = await loadContainerState(); - final template = state.tokenTemplates.firstWhereOrNull((element) => element.id == tokenTemplateId); - return template; - } - - @override - Future saveTokenTemplate(TokenTemplate tokenTemplate) async { - final state = await loadContainerState(); - final templateIndex = state.tokenTemplates.indexWhere((element) => element.id == tokenTemplate.id); - if (templateIndex == -1) { - state.tokenTemplates.add(tokenTemplate); - } else { - state.tokenTemplates[templateIndex] = tokenTemplate; - } - await saveContainerState(state); - return tokenTemplate; - } + // @override + // Future loadTokenTemplate(String tokenTemplateId) async { + // final state = await loadContainerState(); + // final template = state.tokenTemplates.firstWhereOrNull((element) => element.id == tokenTemplateId); + // return template; + // } + + // @override + // Future saveTokenTemplate(TokenTemplate tokenTemplate) async { + // final state = await loadContainerState(); + // if (templateIndex == -1) { + // state.tokenTemplates.add(tokenTemplate); + // } else { + // state.tokenTemplates[templateIndex] = tokenTemplate; + // } + // await saveContainerState(state); + // return tokenTemplate; + // } } diff --git a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart index 380280f65..ffb5abc7a 100644 --- a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart +++ b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart @@ -1,25 +1,23 @@ import 'dart:convert'; -import 'package:collection/collection.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:mutex/mutex.dart'; -import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; - import '../../interfaces/repo/container_repository.dart'; import '../../model/states/token_container_state.dart'; class SecureTokenContainerStateRepository implements TokenContainerStateRepository { static String prefix = 'token_container_state_'; - String get _containerStateKey => '$prefix${repoName}_container_state'; - + String get _containerStateKey => '$prefix${containerId}_container_state'; final Mutex _m = Mutex(); Future _protect(Future Function() f) => _m.protect(f); - - final String repoName; final FlutterSecureStorage _storage = const FlutterSecureStorage(); - SecureTokenContainerStateRepository({required this.repoName}); + final String containerId; + + SecureTokenContainerStateRepository({ + required this.containerId, + }); Future _write(String key, String value) => _protect(() => _storage.write(key: key, value: value)); Future _read(String key) async { @@ -32,7 +30,7 @@ class SecureTokenContainerStateRepository implements TokenContainerStateReposito Future> _readAll() async { Map? keys; await _protect(() async => keys = await _storage.readAll()); - keys!.removeWhere((key, value) => !key.startsWith(prefix + repoName)); + keys!.removeWhere((key, value) => !key.startsWith(prefix + containerId)); return keys!; } @@ -49,27 +47,27 @@ class SecureTokenContainerStateRepository implements TokenContainerStateReposito String? containerStateJsonString = await _read(_containerStateKey); if (containerStateJsonString == null) { Logger.info('No container state found in secure storage', name: 'SecureTokenContainerStateRepository'); - return TokenContainerState.uninitialized(); + return TokenContainerState.uninitialized([]); } final json = jsonDecode(containerStateJsonString); return TokenContainerState.fromJson(json); } - @override - Future loadTokenTemplate(String tokenTemplateId) async { - final state = await loadContainerState(); - final template = state.tokenTemplates.firstWhereOrNull((template) => template.id == tokenTemplateId); - return template; - } + // @override + // Future loadTokenTemplate(String tokenTemplateId) async { + // final state = await loadContainerState(); + // final template = state.tokenTemplates.firstWhereOrNull((template) => template.id == tokenTemplateId); + // return template; + // } - @override - Future saveTokenTemplate(TokenTemplate tokenTemplate) async { - TokenContainerState state = await loadContainerState(); - final templates = state.tokenTemplates; - templates.removeWhere((template) => template.id == tokenTemplate.id); - templates.add(tokenTemplate); - state = state.copyWith(tokenTemplates: templates); - await saveContainerState(state); - return tokenTemplate; - } + // @override + // Future saveTokenTemplate(TokenTemplate tokenTemplate) async { + // TokenContainerState state = await loadContainerState(); + // final templates = state.tokenTemplates; + // templates.removeWhere((template) => template.id == tokenTemplate.id); + // templates.add(tokenTemplate); + // state = state.copyWith(tokenTemplates: templates); + // await saveContainerState(state); + // return tokenTemplate; + // } } diff --git a/lib/state_notifiers/token_container_notifier.dart b/lib/state_notifiers/token_container_notifier.dart index 31d61bac7..0b8e942ee 100644 --- a/lib/state_notifiers/token_container_notifier.dart +++ b/lib/state_notifiers/token_container_notifier.dart @@ -1,25 +1,26 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mutex/mutex.dart'; +import 'package:privacyidea_authenticator/model/states/token_state.dart'; import 'package:privacyidea_authenticator/model/token_container.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../interfaces/repo/container_repository.dart'; import '../model/states/token_container_state.dart'; -import '../model/tokens/token.dart'; class TokenContainerNotifier extends StateNotifier { final TokenContainerStateRepository _repository; late Future initState; - // final StateNotifierProviderRef _ref; + final StateNotifierProviderRef _ref; final Mutex _repoMutex = Mutex(); final Mutex _updateMutex = Mutex(); TokenContainerNotifier({ - // required StateNotifierProviderRef ref, + required StateNotifierProviderRef ref, required TokenContainerStateRepository repository, TokenContainerState? initState, }) : _repository = repository, - // _ref = ref, - super(initState ?? TokenContainerState.uninitialized()) { + _ref = ref, + super(initState ?? TokenContainerState.uninitialized([])) { _init(); } @@ -28,9 +29,6 @@ class TokenContainerNotifier extends StateNotifier { state = await initState; } - - - Future _loadFromRepo() async { await _repoMutex.acquire(); final containerState = await _repository.loadContainerState(); @@ -45,33 +43,47 @@ class TokenContainerNotifier extends StateNotifier { return newState; } - Future addToken(Token token) async { + Future handleTokenState(TokenState tokenState) async { await _updateMutex.acquire(); - final newState = state.copyTransformInto( - tokenTemplates: [...state.tokenTemplates, TokenTemplate(data: token.toUriMap())], - ); + final localTokens = tokenState.tokens.maybePiTokens; + final containerTokens = tokenState.containerTokens(state.serial); + final localTokenTemplates = localTokens.toTemplates(); + final containerTokenTemplates = containerTokens.toTemplates(); + final newState = state.copyWith(localTokenTemplates: localTokenTemplates, syncedTokenTemplates: containerTokenTemplates); final savedState = await _saveToRepo(newState); + _ref.read(tokenProvider.notifier).updateContainerTokens(savedState); state = savedState; _updateMutex.release(); return savedState; } - Future updateTemplates(List updatedTemplates) async { - await _updateMutex.acquire(); - final newState = state.copyTransformInto( - tokenTemplates: state.tokenTemplates.map((oldToken) { - final updatedToken = updatedTemplates.firstWhere( - (newToken) => newToken.id == oldToken.id, - orElse: () => oldToken, - ); - return updatedToken; - }).toList(), - ); - final savedState = await _saveToRepo(newState); - state = savedState; - _updateMutex.release(); - return savedState; - } -} + void addLocalTemplates(List maybePiTokenTemplates) {} + // Future addToken(Token token) async { + // await _updateMutex.acquire(); + // final newState = state.copyTransformInto( + // tokenTemplates: [...state.syncedTokenTemplates, TokenTemplate(data: token.toUriMap())], + // ); + // final savedState = await _saveToRepo(newState); + // state = savedState; + // _updateMutex.release(); + // return savedState; + // } + // Future updateTemplates(List updatedTemplates) async { + // await _updateMutex.acquire(); + // final newState = state.copyTransformInto( + // tokenTemplates: state.syncedTokenTemplates.map((oldToken) { + // final updatedToken = updatedTemplates.firstWhere( + // (newToken) => newToken.id == oldToken.id, + // orElse: () => oldToken, + // ); + // return updatedToken; + // }).toList(), + // ); + // final savedState = await _saveToRepo(newState); + // state = savedState; + // _updateMutex.release(); + // return savedState; + // } +} diff --git a/lib/state_notifiers/token_notifier.dart b/lib/state_notifiers/token_notifier.dart index e9bb867d6..b3e2b970c 100644 --- a/lib/state_notifiers/token_notifier.dart +++ b/lib/state_notifiers/token_notifier.dart @@ -325,28 +325,32 @@ class TokenNotifier extends StateNotifier { final templatesToUpdate = []; final templatesToRemove = []; - final containerTokens = state.tokens.fromContainer; - final tokenTemplatesContainer = container.tokenTemplates; - final tokenTemplatesApp = containerTokens.toTemplates(); - for (var i = 0; i < tokenTemplatesContainer.length && tokenTemplatesApp.isNotEmpty; i++) { + final knownContainerTokens = state.tokens.fromContainer(container.serial); + final syncedTokenTemplates = container.syncedTokenTemplates; + final knownContainerTemplates = knownContainerTokens.toTemplates(); + for (var i = 0; i < syncedTokenTemplates.length && knownContainerTemplates.isNotEmpty; i++) { // Searches for tokens that are in the container but not in the app to add them. // If the token is already in the app, it will be updated. // Reduces the [tokenTemplatesApp] list to only the tokens that are in the app but not in the container. These tokens will be removed. - final templateContainer = tokenTemplatesContainer[i]; - final tokenToUpdate = tokenTemplatesApp.firstWhereOrNull((templateApp) => templateApp.id == templateContainer.id); - if (tokenToUpdate == null) { + final templateContainer = syncedTokenTemplates[i]; + final tokenToKeep = knownContainerTemplates.firstWhereOrNull((templateApp) => templateApp.id == templateContainer.id); + if (tokenToKeep == null) { templatesToAdd.add(templateContainer); } else { - templatesToUpdate.add(tokenToUpdate); - tokenTemplatesApp.remove(tokenToUpdate); + if (tokenToKeep != templateContainer) { + // Only update the token if the template is different + templatesToUpdate.add(tokenToKeep); + } + knownContainerTemplates.remove(tokenToKeep); } } - templatesToRemove.addAll(tokenTemplatesApp); + // Removes all tokens that are in the app but not in the container. + templatesToRemove.addAll(knownContainerTemplates); final tokensToAdd = templatesToAdd.map((e) => e.toToken()).toList(); final tokensToUpdate = []; for (var template in templatesToUpdate) { - final token = containerTokens.firstWhereOrNull((token) => token.id == template.id); + final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id); if (token == null) continue; final needsUpdate = !token.doesMatchTemplate(template); if (needsUpdate) { @@ -355,7 +359,7 @@ class TokenNotifier extends StateNotifier { } final tokensToRemove = []; for (var template in templatesToRemove) { - final token = containerTokens.firstWhereOrNull((token) => token.id == template.id); + final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id); if (token == null) continue; tokensToRemove.add(token); } diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart index e06c5b387..3ccc1d1c2 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart @@ -1,9 +1,12 @@ +import 'dart:convert'; + import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:mutex/mutex.dart'; import '../../../../api/token_container_api_endpoint.dart'; import '../../../../model/states/token_container_state.dart'; +import '../../../../model/tokens/container_credentials.dart'; import '../../../../repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; import '../../../../repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart'; import '../../../../repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; @@ -14,29 +17,26 @@ final tokenContainerProvider = StateNotifierProvider((ref) { +final credentialsProvider = StateNotifierProvider((ref) { Logger.info("New credentialsProvider created", name: 'credentialsProvider'); return CredentialsNotifier(repo: SecureContainerCredentialsRepository()); }); -class CredentialsNotifier extends StateNotifier { +class CredentialsNotifier extends StateNotifier { final Mutex _repoMutex = Mutex(); final ContainerCredentialsRepository _repo; - late Future initState = _initState(); + late Future initState = _initState(); - Future _initState() async { + Future _initState() async { final credentials = await _repo.loadCredentials(); state = credentials; return credentials; @@ -46,7 +46,7 @@ class CredentialsNotifier extends StateNotifier { : _repo = repo, super(null); - Future setCredentials(Credentials credentials) async { + Future setCredentials(ContainerCredentials credentials) async { await _saveCredentialsToRepo(credentials); state = credentials; } @@ -56,7 +56,7 @@ class CredentialsNotifier extends StateNotifier { state = null; } - Future _saveCredentialsToRepo(Credentials credentials) async { + Future _saveCredentialsToRepo(ContainerCredentials credentials) async { await _repoMutex.protect(() async => await _repo.saveCredentials(credentials)); } @@ -76,15 +76,15 @@ class SecureContainerCredentialsRepository extends ContainerCredentialsRepositor Future _delete(String key) => _protect(() => _storage.delete(key: key)); @override - Future loadCredentials() async { + Future loadCredentials() async { final credentials = await _read(containerCredentialsKey); if (credentials == null) return null; - return Credentials(credentials: credentials); + return ContainerCredentials.fromJson(jsonDecode(credentials)); } @override - Future saveCredentials(Credentials credentials) async { - await _write(containerCredentialsKey, credentials.credentials); + Future saveCredentials(ContainerCredentials credentials) async { + await _write(containerCredentialsKey, jsonEncode(credentials.toJson())); } @override @@ -94,18 +94,7 @@ class SecureContainerCredentialsRepository extends ContainerCredentialsRepositor } abstract class ContainerCredentialsRepository { - Future saveCredentials(Credentials credentials); - Future loadCredentials(); + Future saveCredentials(ContainerCredentials credentials); + Future loadCredentials(); Future deleteCredentials(); } - -class Credentials { - final String credentials; // TODO: Change to real credentials (DUMMY) - Credentials({required this.credentials}); - - // TODO: DUMMY - factory Credentials.fromJson(Map json) => Credentials(credentials: json['credentials']); - Map toJson() => {'credentials': credentials}; - - static fromUriMap(Map queryParameters) {} // TODO: DUMMY -} diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index 125129c8a..037d20cf9 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -15,9 +15,9 @@ class TokenContainerTokenStateListener extends TokenStateListener { }); }); - static Future _onNewState(TokenState? previous, TokenState next, WidgetRef ref) async { - final templates = next.lastlyUpdatedTokens.fromContainer.toTemplates(); - if (templates.isEmpty) return; - ref.read(tokenContainerProvider.notifier).updateTemplates(templates); + static Future _onNewState(TokenState? previousState, TokenState nextState, WidgetRef ref) async { + final maybePiTokenTemplates = nextState.lastlyUpdatedTokens.maybePiTokens.toTemplates(); + if (maybePiTokenTemplates.isEmpty) return; + ref.read(tokenContainerProvider.notifier).addLocalTemplates(maybePiTokenTemplates); } } diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart index 1e1f73e78..e179e9578 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/model/states/token_state.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; @@ -21,7 +22,7 @@ class _SelectTokensDialogState extends ConsumerState { Set _selectedTokens = {}; @override Widget build(BuildContext context) { - final tokens = ref.read(tokenProvider).nonPiTokens; + final tokens = ref.read(tokenProvider).tokens.nonPiTokens; final exportEveryToken = tokens.length == _selectedTokens.length && _selectedTokens.containsAll(tokens); final theme = Theme.of(context); final appLocalizations = AppLocalizations.of(context)!; diff --git a/test/unit_test/repo/hybrid_token_container_repo_test.dart b/test/unit_test/repo/hybrid_token_container_repo_test.dart index d1eb9606d..4e480d58e 100644 --- a/test/unit_test/repo/hybrid_token_container_repo_test.dart +++ b/test/unit_test/repo/hybrid_token_container_repo_test.dart @@ -1,4 +1,4 @@ -import 'package:collection/collection.dart'; + import 'package:flutter_test/flutter_test.dart'; import 'package:privacyidea_authenticator/interfaces/repo/container_repository.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; @@ -10,7 +10,7 @@ import 'package:privacyidea_authenticator/repo/token_container_state_repositorys class MockTokenContainerStateRepository implements TokenContainerStateRepository { TokenContainerState savedState; bool _doesThrow = false; - MockTokenContainerStateRepository({TokenContainerState? initialState}) : savedState = initialState ?? TokenContainerState.uninitialized(); + MockTokenContainerStateRepository({TokenContainerState? initialState}) : savedState = initialState ?? TokenContainerState.uninitialized([]); void setThrow(bool value) { _doesThrow = value; @@ -22,13 +22,6 @@ class MockTokenContainerStateRepository implements TokenContainerStateRepository return Future.value(savedState); } - @override - Future loadTokenTemplate(String tokenTemplateId) { - if (_doesThrow) throw Exception('Test exception'); - final template = savedState.tokenTemplates.firstWhereOrNull((element) => element.id == tokenTemplateId); - return Future.value(template); - } - @override Future saveContainerState(TokenContainerState containerState) { if (_doesThrow) throw Exception('Test exception'); @@ -36,17 +29,6 @@ class MockTokenContainerStateRepository implements TokenContainerStateRepository return Future.value(savedState); } - @override - Future saveTokenTemplate(TokenTemplate tokenTemplate) { - if (_doesThrow) throw Exception('Test exception'); - final index = savedState.tokenTemplates.indexWhere((element) => element.id == tokenTemplate.id); - if (index == -1) { - savedState.tokenTemplates.add(tokenTemplate); - } else { - savedState.tokenTemplates[index] = tokenTemplate; - } - return Future.value(tokenTemplate); - } } void main() { @@ -64,29 +46,26 @@ void _testHybridTokenContainerStateRepository() { secret: 'SECRET', issuer: 'issuer', ); - TokenContainerState? remoteState = TokenContainerStateModified( - lastModifiedAt: DateTime.now(), - lastSyncedAt: DateTime.now().subtract(const Duration(days: 1)), - containerId: 'containerId', + TokenContainerState? remoteState = TokenContainerStateSynced( + serial: 'containerSerial', description: 'description', type: 'type', - tokenTemplates: [TokenTemplate(data: token.toUriMap())], + syncedTokenTemplates: [TokenTemplate(data: token.toUriMap())], ); TokenContainerState? localState = TokenContainerStateModified( lastModifiedAt: DateTime.now(), lastSyncedAt: DateTime.now().subtract(const Duration(days: 1)), - containerId: 'containerId', + serial: 'containerSerial', description: 'description', type: 'type', - tokenTemplates: [TokenTemplate(data: token.copyWith(issuer: 'issuer2').toUriMap())], + syncedTokenTemplates: [], + localTokenTemplates: [TokenTemplate(data: token.toUriMap())], ); - final syncedRepo = MockTokenContainerStateRepository(); final localRepo = MockTokenContainerStateRepository(initialState: localState); final remoteRepo = MockTokenContainerStateRepository(initialState: remoteState); final hybridRepo = HybridTokenContainerStateRepository( - syncedRepository: syncedRepo, localRepository: localRepo, remoteRepository: remoteRepo, ); @@ -96,10 +75,10 @@ void _testHybridTokenContainerStateRepository() { final dateTimeAfter = DateTime.now(); expect(state, isA()); state as TokenContainerStateSynced; - expect(state.lastSyncedAt.isAfter(dateTimeBefore), isTrue); - expect(state.lastSyncedAt.isBefore(dateTimeAfter), isTrue); - expect(state.tokenTemplates.length, 1); - final template = state.tokenTemplates.first; + expect(state.lastSyncedAt?.isAfter(dateTimeBefore), isTrue); + expect(state.lastSyncedAt?.isBefore(dateTimeAfter), isTrue); + expect(state.syncedTokenTemplates.length, 1); + final template = state.syncedTokenTemplates.first; expect(template.data, token.toUriMap(), reason: 'Should be the remote state if both are changed since last sync'); }); }); diff --git a/test/unit_test/state_notifiers/token_container_notifier_test.dart b/test/unit_test/state_notifiers/token_container_notifier_test.dart index 149404753..93965e1a8 100644 --- a/test/unit_test/state_notifiers/token_container_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_container_notifier_test.dart @@ -1,5 +1,4 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:privacyidea_authenticator/state_notifiers/token_container_notifier.dart'; void main() { _testTokenContainerNotifier(); From 9d095f2058c82aab366e1b2c86dce30ef96a3ffe Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:45:28 +0200 Subject: [PATCH 008/285] smartphone as a container --- build.yaml | 19 + integration_test/add_tokens_test.dart | 8 +- integration_test/copy_to_clipboard_test.dart | 8 +- integration_test/rename_and_delete_test.dart | 8 +- integration_test/two_step_rollout_test.dart | 8 +- integration_test/views_test.dart | 10 +- lib/api/token_container_api_endpoint.dart | 49 +- lib/interfaces/api_endpoint.dart | 5 +- lib/interfaces/repo/container_repository.dart | 8 +- .../notifier_provider_listener.dart | 14 + .../state_notifier_provider_listener.dart | 2 + .../deep_link_listener.dart | 2 +- .../token_state_listener.dart | 2 +- lib/model/states/token_container_state.dart | 950 ++--- lib/model/states/token_container_state.g.dart | 203 -- lib/model/states/token_state.dart | 8 +- lib/model/token_container.dart | 213 +- lib/model/token_container.freezed.dart | 3171 +++++++++++++++++ lib/model/token_container.g.dart | 184 +- lib/model/tokens/container_credentials.dart | 14 +- lib/model/tokens/container_credentials.g.dart | 9 +- lib/model/tokens/day_password_token.dart | 3 + lib/model/tokens/day_password_token.g.dart | 2 + lib/model/tokens/hotp_token.dart | 3 + lib/model/tokens/hotp_token.g.dart | 2 + lib/model/tokens/otp_token.dart | 2 + lib/model/tokens/push_token.dart | 10 +- lib/model/tokens/push_token.g.dart | 2 + lib/model/tokens/steam_token.dart | 3 + lib/model/tokens/steam_token.g.dart | 2 + lib/model/tokens/token.dart | 1 + lib/model/tokens/totp_token.dart | 3 + lib/model/tokens/totp_token.g.dart | 2 + .../container_credentials_processor.dart | 4 +- lib/repo/secure_push_request_repository.dart | 1 - ...brid_token_container_state_repository.dart | 45 +- ...mote_token_container_state_repository.dart | 21 +- ...token_container_state_repository.dart.dart | 65 +- .../token_container_notifier.dart | 89 - .../deeplink_provider.dart | 2 +- .../introduction_provider.dart | 2 +- .../progress_state_provider.dart | 2 +- .../push_request_provider.dart | 2 +- .../settings_provider.dart | 2 +- .../sortable_provider.dart | 2 +- .../token_container_state_provider.dart | 279 +- .../token_container_state_provider.g.dart | 213 ++ .../token_folder_provider.dart | 2 +- .../token_provider.dart | 2 +- .../token_container_token_state_listener.dart | 11 +- .../completed_introduction_notifier.dart | 8 +- .../state_notifiers/deeplink_notifier.dart | 4 +- .../progress_state_notifier.dart | 4 +- .../push_request_notifier.dart | 28 +- .../state_notifiers/settings_notifier.dart | 10 +- .../state_notifiers/sortable_notifier.dart | 19 +- .../token_container_notifier.dart | 93 + .../token_folder_notifier.dart | 8 +- .../state_notifiers/token_notifier.dart | 78 +- lib/widgets/app_wrapper.dart | 43 +- lib/widgets/app_wrappers/state_observer.dart | 8 +- pubspec.lock | 116 +- pubspec.yaml | 10 +- .../hybrid_token_container_repo_test.dart | 44 +- .../deeplink_notifier_test.dart | 2 +- .../push_request_notifier_test.dart | 2 +- .../settings_notifier_test.dart | 2 +- .../sortable_notifier_test.dart | 8 +- .../token_folder_notifier_test.dart | 2 +- .../state_notifiers/token_notifier_test.dart | 4 +- 70 files changed, 5022 insertions(+), 1145 deletions(-) create mode 100644 build.yaml create mode 100644 lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart delete mode 100644 lib/model/states/token_container_state.g.dart create mode 100644 lib/model/token_container.freezed.dart delete mode 100644 lib/state_notifiers/token_container_notifier.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart rename lib/{ => utils/riverpod}/state_notifiers/completed_introduction_notifier.dart (90%) rename lib/{ => utils/riverpod}/state_notifiers/deeplink_notifier.dart (96%) rename lib/{ => utils/riverpod}/state_notifiers/progress_state_notifier.dart (94%) rename lib/{ => utils/riverpod}/state_notifiers/push_request_notifier.dart (95%) rename lib/{ => utils/riverpod}/state_notifiers/settings_notifier.dart (95%) rename lib/{ => utils/riverpod}/state_notifiers/sortable_notifier.dart (71%) create mode 100644 lib/utils/riverpod/state_notifiers/token_container_notifier.dart rename lib/{ => utils/riverpod}/state_notifiers/token_folder_notifier.dart (96%) rename lib/{ => utils/riverpod}/state_notifiers/token_notifier.dart (92%) diff --git a/build.yaml b/build.yaml new file mode 100644 index 000000000..58baae405 --- /dev/null +++ b/build.yaml @@ -0,0 +1,19 @@ +targets: + $default: + builders: + riverpod_generator: + options: + # Could be changed to "my", such that riverpod_generator + # would generate "myCountProvider" instead of "countProvider" + provider_name_prefix: "" # (default) + # Similar to provider_name_prefix, this is an option for renaming + # providers with parameters ("families"). + # This takes precedence over provider_name_prefix. + provider_family_name_prefix: "" # (default) + # Could be changed to "Pod", such that riverpod_generator + # would generate "countPod" instead of "countProvider" + provider_name_suffix: "" # (default) + # Similar to provider_name_suffix, this is an option for renaming + # providers with parameters ("families"). + # This takes precedence over provider_name_suffix. + provider_family_name_suffix: "Of" # (default) \ No newline at end of file diff --git a/integration_test/add_tokens_test.dart b/integration_test/add_tokens_test.dart index 62ac65652..31fcbd7ee 100644 --- a/integration_test/add_tokens_test.dart +++ b/integration_test/add_tokens_test.dart @@ -12,10 +12,10 @@ import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/model/version.dart'; -import 'package:privacyidea_authenticator/state_notifiers/completed_introduction_notifier.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/riverpod/state_notifiers/completed_introduction_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; diff --git a/integration_test/copy_to_clipboard_test.dart b/integration_test/copy_to_clipboard_test.dart index 71a3c2054..b1e5213a8 100644 --- a/integration_test/copy_to_clipboard_test.dart +++ b/integration_test/copy_to_clipboard_test.dart @@ -9,10 +9,10 @@ import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; -import 'package:privacyidea_authenticator/state_notifiers/completed_introduction_notifier.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/riverpod/state_notifiers/completed_introduction_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; diff --git a/integration_test/rename_and_delete_test.dart b/integration_test/rename_and_delete_test.dart index bfdcdd9f9..150e00fe0 100644 --- a/integration_test/rename_and_delete_test.dart +++ b/integration_test/rename_and_delete_test.dart @@ -10,10 +10,10 @@ import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/state_notifiers/completed_introduction_notifier.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/riverpod/state_notifiers/completed_introduction_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; diff --git a/integration_test/two_step_rollout_test.dart b/integration_test/two_step_rollout_test.dart index 6b1f7b115..8a5e9c633 100644 --- a/integration_test/two_step_rollout_test.dart +++ b/integration_test/two_step_rollout_test.dart @@ -7,10 +7,10 @@ import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/states/settings_state.dart'; -import 'package:privacyidea_authenticator/state_notifiers/completed_introduction_notifier.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/riverpod/state_notifiers/completed_introduction_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index 5dcc8e288..d35b50ef1 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -10,11 +10,11 @@ import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/state_notifiers/completed_introduction_notifier.dart'; -import 'package:privacyidea_authenticator/state_notifiers/push_request_notifier.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/riverpod/state_notifiers/completed_introduction_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/push_request_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/push_provider.dart'; diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index b11145155..d16cc7f69 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -1,43 +1,62 @@ +import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import 'package:privacyidea_authenticator/model/token_container.dart'; +import 'package:privacyidea_authenticator/model/tokens/container_credentials.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; import '../interfaces/api_endpoint.dart'; -import '../model/states/token_container_state.dart'; -import '../model/tokens/container_credentials.dart'; +import '../model/enums/encodings.dart'; +import '../model/enums/token_types.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; -class TokenContainerApiEndpoint implements ApiEndpioint { - final Map> _data = {}; +final Map> _data = { + '123': { + 'tokenID65381723659': TokenTemplate( + data: { + URI_LABEL: '123 container token 1', + URI_SECRET: Encodings.base32.decode('SECRET'), + URI_TYPE: TokenTypes.TOTP.name, + }, + ) + } +}; +class TokenContainerApiEndpoint implements ApiEndpioint { @override - final ContainerCredentials credentials; - - TokenContainerApiEndpoint({required this.credentials}); + final ContainerCredential credential; + TokenContainerApiEndpoint({required this.credential}); @override - Future fetch() { + Future fetch() { throw UnimplementedError(); } @override - Future sync(TokenContainerState containerState) async { + Future sync(TokenContainer containerState) async { + Logger.warning('Syncing container with server', name: 'TokenContainerApiEndpoint'); if (_data.containsKey(containerState.serial) == false) { - return containerState.copyTransformInto(data: 'Container not found'); + return containerState.copyTransformInto(args: {'message': 'Container not found'}); } + Logger.warning('Container found', name: 'TokenContainerApiEndpoint'); final localTemplates = containerState.localTokenTemplates; for (var localTemplate in localTemplates) { - if (localTemplate.id?.startsWith(containerState.serial) == true) { - final merged = localTemplate.copyWith({ - URI_LABEL: (localTemplate.data[URI_LABEL] as String).replaceFirst(r'.', '😀'), + final oldLabel = localTemplate.data[URI_LABEL] as String; + if (oldLabel.startsWith(containerState.serial) == true) { + final merged = localTemplate.copyAddAll({ + URI_LABEL: oldLabel.replaceRange(oldLabel.length - 2, oldLabel.length - 1, '😀'), }); _data[containerState.serial]![localTemplate.id!] = merged; } } final serverTemplatesMerged = _data[containerState.serial]!.values.toList(); - return TokenContainerStateSynced( + return TokenContainerSynced( + lastSyncAt: DateTime.now(), serial: containerState.serial, description: 'Synced with server', - type: 'synced', syncedTokenTemplates: serverTemplatesMerged, + localTokenTemplates: [], ); } + + } diff --git a/lib/interfaces/api_endpoint.dart b/lib/interfaces/api_endpoint.dart index 2b854d6f1..a58edf317 100644 --- a/lib/interfaces/api_endpoint.dart +++ b/lib/interfaces/api_endpoint.dart @@ -1,6 +1,7 @@ -abstract class ApiEndpioint { - Ref get credentials; +import '../model/tokens/container_credentials.dart'; +abstract class ApiEndpioint { + ContainerCredential get credential; Future fetch(); Future sync(Data data); } diff --git a/lib/interfaces/repo/container_repository.dart b/lib/interfaces/repo/container_repository.dart index 57c378b85..f64bcba2d 100644 --- a/lib/interfaces/repo/container_repository.dart +++ b/lib/interfaces/repo/container_repository.dart @@ -1,14 +1,14 @@ -import '../../model/states/token_container_state.dart'; +import '../../model/token_container.dart'; -abstract class TokenContainerStateRepository { +abstract class TokenContainerRepository { /// Save the container state to the repository /// Returns the state that was actually written - Future saveContainerState(TokenContainerState containerState); + Future saveContainerState(TokenContainer containerState); /// Load the container state from the repository /// Returns the loaded state - Future loadContainerState(); + Future loadContainerState(); // /// Save a token template to the repository // /// Returns the template that was actually written diff --git a/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart b/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart new file mode 100644 index 000000000..d50431fdb --- /dev/null +++ b/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart @@ -0,0 +1,14 @@ +// ignore_for_file: invalid_use_of_internal_member + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +abstract class AsyncNotifierProviderListener, S> { + final AutoDisposeAsyncNotifierProviderImpl? provider; + final void Function(AsyncValue? previous, AsyncValue next)? onNewState; + const AsyncNotifierProviderListener({this.provider, this.onNewState}); + void buildListen(WidgetRef ref) { + if (provider == null || onNewState == null) return; + ref.listen(provider!, onNewState!); + } +} diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart index b8d46c45d..252a7a80f 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart @@ -9,3 +9,5 @@ abstract class StateNotifierProviderListener, S> { ref.listen(provider!, onNewState!); } } + + diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart index 107fcbe4e..1d3937fbd 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/deeplink.dart'; -import '../../../../state_notifiers/deeplink_notifier.dart'; +import '../../../../utils/riverpod/state_notifiers/deeplink_notifier.dart'; import '../state_notifier_provider_listener.dart'; abstract class DeepLinkListener extends StateNotifierProviderListener { diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart index a245ad1d1..28b596b20 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/states/token_state.dart'; -import '../../../../state_notifiers/token_notifier.dart'; +import '../../../../utils/riverpod/state_notifiers/token_notifier.dart'; import '../state_notifier_provider_listener.dart'; abstract class TokenStateListener extends StateNotifierProviderListener { diff --git a/lib/model/states/token_container_state.dart b/lib/model/states/token_container_state.dart index e9b5617f9..01bb96169 100644 --- a/lib/model/states/token_container_state.dart +++ b/lib/model/states/token_container_state.dart @@ -1,521 +1,521 @@ -import 'package:json_annotation/json_annotation.dart'; +// import 'package:json_annotation/json_annotation.dart'; -import '../token_container.dart'; +// import '../token_container.dart'; -part 'token_container_state.g.dart'; +// part 'token_container_state.g.dart'; -sealed class TokenContainerState extends TokenContainer { - final DateTime? lastSyncedAt; +// sealed class TokenContainer extends TokenContainer { +// final DateTime? lastSyncedAt; - const TokenContainerState({ - required this.lastSyncedAt, - // TokenContainer - required super.serial, - required super.description, - required super.type, - required super.syncedTokenTemplates, - required super.localTokenTemplates, - }); - - factory TokenContainerState.uninitialized(List localTokenTemplates) => - TokenContainerStateUninitialized(localTokenTemplates: localTokenTemplates); - - factory TokenContainerState.fromJson(Map json) => switch (json['type']) { - const ('TokenContainerStateUninitialized') => _$TokenContainerStateUninitializedFromJson(json), - const ('TokenContainerStateModified') => _$TokenContainerStateModifiedFromJson(json), - const ('TokenContainerStateSynced') => _$TokenContainerStateSyncedFromJson(json), - // const ('TokenContainerStateSyncing') => _$TokenContainerStateSyncingFromJson(json), - const ('TokenContainerStateUnsynced') => _$TokenContainerStateUnsyncedFromJson(json), - const ('TokenContainerStateError') => _$TokenContainerStateErrorFromJson(json), - const ('TokenContainerStateDeactivated') => _$TokenContainerStateDeactivatedFromJson(json), - const ('TokenContainerStateDeleted') => _$TokenContainerStateDeletedFromJson(json), - _ => throw UnimplementedError(json['type']), - }; - - factory TokenContainerState.fromTypeString({ - required String stateType, - dynamic data, - DateTime? dateTime, - String? serial, - String? description, - String? type, - List syncedTokenTemplates = const [], - List localTokenTemplates = const [], - DateTime? lastSyncedAt, - }) => - switch (stateType) { - 'TokenContainerStateUninitialized' => TokenContainerStateUninitialized(localTokenTemplates: localTokenTemplates), - 'TokenContainerStateModified' => TokenContainerStateModified( - lastModifiedAt: dateTime ?? DateTime.now(), - lastSyncedAt: lastSyncedAt ?? DateTime.now(), - serial: serial ?? '', - description: description ?? '', - type: type ?? '', - syncedTokenTemplates: syncedTokenTemplates, - localTokenTemplates: localTokenTemplates, - ), - 'TokenContainerStateSynced' => TokenContainerStateSynced( - lastSyncedAt: lastSyncedAt ?? DateTime.now(), - serial: serial ?? '', - description: description ?? '', - type: type ?? '', - syncedTokenTemplates: syncedTokenTemplates, - ), - // 'TokenContainerStateSyncing' => TokenContainerStateSyncing( - // syncStartedAt: dateTime ?? DateTime.now(), - // lastSyncedAt: lastSyncedAt, - // serial: serial ?? '', - // description: description ?? '', - // type: type ?? '', - // syncedTokenTemplates: tokenTemplates, - // ), - 'TokenContainerStateUnsynced' => TokenContainerStateUnsynced( - syncAttempts: data is num ? data.floor() : 1, - lastSyncedAt: lastSyncedAt, - serial: serial ?? '', - description: description ?? '', - type: type ?? '', - syncedTokenTemplates: syncedTokenTemplates, - localTokenTemplates: localTokenTemplates, - ), - 'TokenContainerStateError' => TokenContainerStateError( - error: data, - lastSyncedAt: lastSyncedAt, - serial: serial ?? '', - description: description ?? '', - type: type ?? '', - syncedTokenTemplates: syncedTokenTemplates, - localTokenTemplates: localTokenTemplates, - ), - 'TokenContainerStateDeactivated' => TokenContainerStateDeactivated( - reason: data, - deactivatedAt: dateTime ?? DateTime.now(), - lastSyncedAt: lastSyncedAt, - serial: serial ?? '', - description: description ?? '', - type: type ?? '', - syncedTokenTemplates: syncedTokenTemplates, - localTokenTemplates: localTokenTemplates, - ), - 'TokenContainerStateDeleted' => TokenContainerStateDeleted( - reason: data, - deletedAt: dateTime ?? DateTime.now(), - lastSyncedAt: lastSyncedAt, - serial: serial ?? '', - description: description ?? '', - type: type ?? '', - syncedTokenTemplates: syncedTokenTemplates, - localTokenTemplates: localTokenTemplates, - ), - _ => throw UnimplementedError(stateType), - }; - - T as({dynamic data, DateTime? dateTime}) => switch (T) { - const (TokenContainerStateUninitialized) => TokenContainerStateUninitialized(localTokenTemplates: localTokenTemplates) as T, - const (TokenContainerStateSynced) => TokenContainerStateSynced( - lastSyncedAt: lastSyncedAt ?? lastSyncedAt ?? DateTime.now(), - serial: serial, - description: description, - type: type, - syncedTokenTemplates: syncedTokenTemplates, - ) as T, - // const (TokenContainerStateSyncing) => TokenContainerStateSyncing( - // lastSyncedAt: lastSyncedAt, - // syncStartedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), - // serial: serial, - // description: description, - // type: type, - // tokenTemplates: syncedTokenTemplates, - // ) as T, - const (TokenContainerStateUnsynced) => this is TokenContainerStateUnsynced - ? (this as TokenContainerStateUnsynced).withIncrementedSyncAttempts() as T - : TokenContainerStateUnsynced( - lastSyncedAt: lastSyncedAt, - syncAttempts: data is num ? data.floor() : 1, - serial: serial, - description: description, - type: type, - syncedTokenTemplates: syncedTokenTemplates, - localTokenTemplates: localTokenTemplates, - ) as T, - const (TokenContainerStateError) => TokenContainerStateError( - error: data, - lastSyncedAt: lastSyncedAt, - serial: serial, - description: description, - type: type, - syncedTokenTemplates: syncedTokenTemplates, - localTokenTemplates: localTokenTemplates, - ) as T, - const (TokenContainerStateDeactivated) => TokenContainerStateDeactivated( - reason: data, - deactivatedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), - lastSyncedAt: lastSyncedAt, - serial: serial, - description: description, - type: type, - syncedTokenTemplates: syncedTokenTemplates, - localTokenTemplates: localTokenTemplates, - ) as T, - const (TokenContainerStateDeleted) => TokenContainerStateDeleted( - reason: data, - deletedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), - lastSyncedAt: lastSyncedAt, - serial: serial, - description: description, - type: type, - syncedTokenTemplates: syncedTokenTemplates, - localTokenTemplates: localTokenTemplates, - ) as T, - _ => throw UnimplementedError(), - }; +// const TokenContainer({ +// required this.lastSyncedAt, +// // TokenContainer +// required super.serial, +// required super.description, +// required super.type, +// required super.syncedTokenTemplates, +// required super.localTokenTemplates, +// }); - @override - Map toJson() { - final json = switch (this) { - TokenContainerStateUninitialized() => _$TokenContainerStateUninitializedToJson(this as TokenContainerStateUninitialized), - TokenContainerStateModified() => _$TokenContainerStateModifiedToJson(this as TokenContainerStateModified), - TokenContainerStateSynced() => _$TokenContainerStateSyncedToJson(this as TokenContainerStateSynced), - // 'TokenContainerStateSyncing() => _$TokenContainerStateSyncingToJson(this as TokenContainerStateSyncing), - TokenContainerStateUnsynced() => _$TokenContainerStateUnsyncedToJson(this as TokenContainerStateUnsynced), - TokenContainerStateError() => _$TokenContainerStateErrorToJson(this as TokenContainerStateError), - TokenContainerStateDeactivated() => _$TokenContainerStateDeactivatedToJson(this as TokenContainerStateDeactivated), - TokenContainerStateDeleted() => _$TokenContainerStateDeletedToJson(this as TokenContainerStateDeleted), - }; - json['type'] = runtimeType.toString(); - return json; - } +// factory TokenContainer.uninitialized(List localTokenTemplates) => +// TokenContainerUninitialized(localTokenTemplates: localTokenTemplates); - @override - TokenContainerStateModified copyWith({ - String? serial, - String? description, - String? type, - List? syncedTokenTemplates, - List? localTokenTemplates, - DateTime? lastSyncedAt, - }); +// factory TokenContainer.fromJson(Map json) => switch (json['type']) { +// const ('TokenContainerUninitialized') => _$TokenContainerUninitializedFromJson(json), +// const ('TokenContainerModified') => _$TokenContainerModifiedFromJson(json), +// const ('TokenContainerSynced') => _$TokenContainerSyncedFromJson(json), +// // const ('TokenContainerSyncing') => _$TokenContainerSyncingFromJson(json), +// const ('TokenContainerUnsynced') => _$TokenContainerUnsyncedFromJson(json), +// const ('TokenContainerError') => _$TokenContainerErrorFromJson(json), +// const ('TokenContainerDeactivated') => _$TokenContainerDeactivatedFromJson(json), +// const ('TokenContainerDeleted') => _$TokenContainerDeletedFromJson(json), +// _ => throw UnimplementedError(json['type']), +// }; - T copyTransformInto({ - dynamic data, - DateTime? dateTime, - DateTime? lastSyncedAt, - String? serial, - String? description, - String? type, - List? tokenTemplates, - }) { - final copied = copyWith( - serial: serial, - description: description, - type: type, - syncedTokenTemplates: tokenTemplates, - ); - return copied.as(data: data, dateTime: dateTime); - } -} +// factory TokenContainer.fromTypeString({ +// required String stateType, +// dynamic data, +// DateTime? dateTime, +// String? serial, +// String? description, +// String? type, +// List syncedTokenTemplates = const [], +// List localTokenTemplates = const [], +// DateTime? lastSyncedAt, +// }) => +// switch (stateType) { +// 'TokenContainerUninitialized' => TokenContainerUninitialized(localTokenTemplates: localTokenTemplates), +// 'TokenContainerModified' => TokenContainerModified( +// lastModifiedAt: dateTime ?? DateTime.now(), +// lastSyncedAt: lastSyncedAt ?? DateTime.now(), +// serial: serial ?? '', +// description: description ?? '', +// type: type ?? '', +// syncedTokenTemplates: syncedTokenTemplates, +// localTokenTemplates: localTokenTemplates, +// ), +// 'TokenContainerSynced' => TokenContainerSynced( +// lastSyncedAt: lastSyncedAt ?? DateTime.now(), +// serial: serial ?? '', +// description: description ?? '', +// type: type ?? '', +// syncedTokenTemplates: syncedTokenTemplates, +// ), +// // 'TokenContainerSyncing' => TokenContainerSyncing( +// // syncStartedAt: dateTime ?? DateTime.now(), +// // lastSyncedAt: lastSyncedAt, +// // serial: serial ?? '', +// // description: description ?? '', +// // type: type ?? '', +// // syncedTokenTemplates: tokenTemplates, +// // ), +// 'TokenContainerUnsynced' => TokenContainerUnsynced( +// syncAttempts: data is num ? data.floor() : 1, +// lastSyncedAt: lastSyncedAt, +// serial: serial ?? '', +// description: description ?? '', +// type: type ?? '', +// syncedTokenTemplates: syncedTokenTemplates, +// localTokenTemplates: localTokenTemplates, +// ), +// 'TokenContainerError' => TokenContainerError( +// error: data, +// lastSyncedAt: lastSyncedAt, +// serial: serial ?? '', +// description: description ?? '', +// type: type ?? '', +// syncedTokenTemplates: syncedTokenTemplates, +// localTokenTemplates: localTokenTemplates, +// ), +// 'TokenContainerDeactivated' => TokenContainerDeactivated( +// reason: data, +// deactivatedAt: dateTime ?? DateTime.now(), +// lastSyncedAt: lastSyncedAt, +// serial: serial ?? '', +// description: description ?? '', +// type: type ?? '', +// syncedTokenTemplates: syncedTokenTemplates, +// localTokenTemplates: localTokenTemplates, +// ), +// 'TokenContainerDeleted' => TokenContainerDeleted( +// reason: data, +// deletedAt: dateTime ?? DateTime.now(), +// lastSyncedAt: lastSyncedAt, +// serial: serial ?? '', +// description: description ?? '', +// type: type ?? '', +// syncedTokenTemplates: syncedTokenTemplates, +// localTokenTemplates: localTokenTemplates, +// ), +// _ => throw UnimplementedError(stateType), +// }; -/// ContainerState is not initialized -@JsonSerializable() -class TokenContainerStateUninitialized extends TokenContainerState { - const TokenContainerStateUninitialized({required super.localTokenTemplates}) - : super( - lastSyncedAt: null, - serial: '', - description: '', - type: '', - syncedTokenTemplates: const [], - ); +// T as({dynamic data, DateTime? dateTime}) => switch (T) { +// const (TokenContainerUninitialized) => TokenContainerUninitialized(localTokenTemplates: localTokenTemplates) as T, +// const (TokenContainerSynced) => TokenContainerSynced( +// lastSyncedAt: lastSyncedAt ?? lastSyncedAt ?? DateTime.now(), +// serial: serial, +// description: description, +// type: type, +// syncedTokenTemplates: syncedTokenTemplates, +// ) as T, +// // const (TokenContainerSyncing) => TokenContainerSyncing( +// // lastSyncedAt: lastSyncedAt, +// // syncStartedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), +// // serial: serial, +// // description: description, +// // type: type, +// // tokenTemplates: syncedTokenTemplates, +// // ) as T, +// const (TokenContainerUnsynced) => this is TokenContainerUnsynced +// ? (this as TokenContainerUnsynced).withIncrementedSyncAttempts() as T +// : TokenContainerUnsynced( +// lastSyncedAt: lastSyncedAt, +// syncAttempts: data is num ? data.floor() : 1, +// serial: serial, +// description: description, +// type: type, +// syncedTokenTemplates: syncedTokenTemplates, +// localTokenTemplates: localTokenTemplates, +// ) as T, +// const (TokenContainerError) => TokenContainerError( +// error: data, +// lastSyncedAt: lastSyncedAt, +// serial: serial, +// description: description, +// type: type, +// syncedTokenTemplates: syncedTokenTemplates, +// localTokenTemplates: localTokenTemplates, +// ) as T, +// const (TokenContainerDeactivated) => TokenContainerDeactivated( +// reason: data, +// deactivatedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), +// lastSyncedAt: lastSyncedAt, +// serial: serial, +// description: description, +// type: type, +// syncedTokenTemplates: syncedTokenTemplates, +// localTokenTemplates: localTokenTemplates, +// ) as T, +// const (TokenContainerDeleted) => TokenContainerDeleted( +// reason: data, +// deletedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), +// lastSyncedAt: lastSyncedAt, +// serial: serial, +// description: description, +// type: type, +// syncedTokenTemplates: syncedTokenTemplates, +// localTokenTemplates: localTokenTemplates, +// ) as T, +// _ => throw UnimplementedError(), +// }; - @override - TokenContainerStateModified copyWith({ - String? serial, - String? description, - String? type, - List? syncedTokenTemplates, - List? localTokenTemplates, - DateTime? lastSyncedAt, - }) { - return TokenContainerStateModified( - lastModifiedAt: DateTime.now(), - lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, - serial: serial ?? this.serial, - description: description ?? this.description, - type: type ?? this.type, - syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, - localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, - ); - } -} +// @override +// Map toJson() { +// final json = switch (this) { +// TokenContainerUninitialized() => _$TokenContainerUninitializedToJson(this as TokenContainerUninitialized), +// TokenContainerModified() => _$TokenContainerModifiedToJson(this as TokenContainerModified), +// TokenContainerSynced() => _$TokenContainerSyncedToJson(this as TokenContainerSynced), +// // 'TokenContainerSyncing() => _$TokenContainerSyncingToJson(this as TokenContainerSyncing), +// TokenContainerUnsynced() => _$TokenContainerUnsyncedToJson(this as TokenContainerUnsynced), +// TokenContainerError() => _$TokenContainerErrorToJson(this as TokenContainerError), +// TokenContainerDeactivated() => _$TokenContainerDeactivatedToJson(this as TokenContainerDeactivated), +// TokenContainerDeleted() => _$TokenContainerDeletedToJson(this as TokenContainerDeleted), +// }; +// json['type'] = runtimeType.toString(); +// return json; +// } -/// ContainerState is modified -@JsonSerializable() -class TokenContainerStateModified extends TokenContainerState { - DateTime lastModifiedAt; - TokenContainerStateModified({ - required this.lastModifiedAt, - required super.lastSyncedAt, - required super.serial, - required super.description, - required super.type, - required super.syncedTokenTemplates, - required super.localTokenTemplates, - }); +// @override +// TokenContainerModified copyWith({ +// String? serial, +// String? description, +// String? type, +// List? syncedTokenTemplates, +// List? localTokenTemplates, +// DateTime? lastSyncedAt, +// }); - @override - TokenContainerStateModified copyWith({ - String? serial, - String? description, - String? type, - List? syncedTokenTemplates, - List? localTokenTemplates, - DateTime? lastSyncedAt, - DateTime? lastModifiedAt, - }) { - return TokenContainerStateModified( - lastModifiedAt: lastModifiedAt ?? this.lastModifiedAt, - lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt ?? DateTime.now(), - serial: serial ?? this.serial, - description: description ?? this.description, - type: type ?? this.type, - syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, - localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, - ); - } -} +// T copyTransformInto({ +// dynamic data, +// DateTime? dateTime, +// DateTime? lastSyncedAt, +// String? serial, +// String? description, +// String? type, +// List? tokenTemplates, +// }) { +// final copied = copyWith( +// serial: serial, +// description: description, +// type: type, +// syncedTokenTemplates: tokenTemplates, +// ); +// return copied.as(data: data, dateTime: dateTime); +// } +// } -/// ContainerState is successfully synced with repo -@JsonSerializable() -class TokenContainerStateSynced extends TokenContainerState { - TokenContainerStateSynced({ - required super.serial, - required super.description, - required super.type, - required super.syncedTokenTemplates, - DateTime? lastSyncedAt, - }) : super(lastSyncedAt: lastSyncedAt ?? DateTime.now(), localTokenTemplates: const []); +// /// ContainerState is not initialized +// @JsonSerializable() +// class TokenContainerUninitialized extends TokenContainer { +// const TokenContainerUninitialized({required super.localTokenTemplates}) +// : super( +// lastSyncedAt: null, +// serial: '', +// description: '', +// type: '', +// syncedTokenTemplates: const [], +// ); - @override - TokenContainerStateModified copyWith({ - String? serial, - String? description, - String? type, - List? syncedTokenTemplates, - List? localTokenTemplates, - DateTime? lastSyncedAt, - DateTime? lastModifiedAt, - }) { - return TokenContainerStateModified( - lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, - serial: serial ?? this.serial, - description: description ?? this.description, - type: type ?? this.type, - syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, - localTokenTemplates: const [], - lastModifiedAt: lastModifiedAt ?? DateTime.now(), - ); - } -} +// @override +// TokenContainerModified copyWith({ +// String? serial, +// String? description, +// String? type, +// List? syncedTokenTemplates, +// List? localTokenTemplates, +// DateTime? lastSyncedAt, +// }) { +// return TokenContainerModified( +// lastModifiedAt: DateTime.now(), +// lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, +// serial: serial ?? this.serial, +// description: description ?? this.description, +// type: type ?? this.type, +// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, +// localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, +// ); +// } +// } -// /// ContainerState is currently syncing +// /// ContainerState is modified // @JsonSerializable() -// class TokenContainerStateSyncing extends TokenContainerState { -// final DateTime syncStartedAt; -// final Duration timeOut; -// get isSyncing => DateTime.now().difference(syncStartedAt) < timeOut; -// get isTimedOut => DateTime.now().difference(syncStartedAt) > timeOut; -// const TokenContainerStateSyncing({ -// required this.syncStartedAt, -// this.timeOut = const Duration(seconds: 30), +// class TokenContainerModified extends TokenContainer { +// DateTime lastModifiedAt; +// TokenContainerModified({ +// required this.lastModifiedAt, // required super.lastSyncedAt, // required super.serial, // required super.description, // required super.type, -// required super.tokenTemplates, +// required super.syncedTokenTemplates, +// required super.localTokenTemplates, // }); // @override -// TokenContainerStateSyncing copyWith({ +// TokenContainerModified copyWith({ // String? serial, // String? description, // String? type, // List? syncedTokenTemplates, +// List? localTokenTemplates, // DateTime? lastSyncedAt, -// DateTime? syncStartedAt, -// Duration? timeOut, +// DateTime? lastModifiedAt, // }) { -// return TokenContainerStateSyncing( -// syncStartedAt: syncStartedAt ?? this.syncStartedAt, -// timeOut: timeOut ?? this.timeOut, +// return TokenContainerModified( +// lastModifiedAt: lastModifiedAt ?? this.lastModifiedAt, +// lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt ?? DateTime.now(), +// serial: serial ?? this.serial, +// description: description ?? this.description, +// type: type ?? this.type, +// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, +// localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, +// ); +// } +// } + +// /// ContainerState is successfully synced with repo +// @JsonSerializable() +// class TokenContainerSynced extends TokenContainer { +// TokenContainerSynced({ +// required super.serial, +// required super.description, +// required super.type, +// required super.syncedTokenTemplates, +// DateTime? lastSyncedAt, +// }) : super(lastSyncedAt: lastSyncedAt ?? DateTime.now(), localTokenTemplates: const []); + +// @override +// TokenContainerModified copyWith({ +// String? serial, +// String? description, +// String? type, +// List? syncedTokenTemplates, +// List? localTokenTemplates, +// DateTime? lastSyncedAt, +// DateTime? lastModifiedAt, +// }) { +// return TokenContainerModified( // lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, // serial: serial ?? this.serial, // description: description ?? this.description, // type: type ?? this.type, -// tokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, +// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, +// localTokenTemplates: const [], +// lastModifiedAt: lastModifiedAt ?? DateTime.now(), // ); // } // } -/// ContainerState is failed last sync attempt -@JsonSerializable() -class TokenContainerStateUnsynced extends TokenContainerState { - final int syncAttempts; - final dynamic lastError; +// // /// ContainerState is currently syncing +// // @JsonSerializable() +// // class TokenContainerSyncing extends TokenContainer { +// // final DateTime syncStartedAt; +// // final Duration timeOut; +// // get isSyncing => DateTime.now().difference(syncStartedAt) < timeOut; +// // get isTimedOut => DateTime.now().difference(syncStartedAt) > timeOut; +// // const TokenContainerSyncing({ +// // required this.syncStartedAt, +// // this.timeOut = const Duration(seconds: 30), +// // required super.lastSyncedAt, +// // required super.serial, +// // required super.description, +// // required super.type, +// // required super.tokenTemplates, +// // }); - TokenContainerStateUnsynced({ - this.syncAttempts = 1, - this.lastError, - required super.lastSyncedAt, - required super.serial, - required super.description, - required super.type, - required super.syncedTokenTemplates, - required super.localTokenTemplates, - }); +// // @override +// // TokenContainerSyncing copyWith({ +// // String? serial, +// // String? description, +// // String? type, +// // List? syncedTokenTemplates, +// // DateTime? lastSyncedAt, +// // DateTime? syncStartedAt, +// // Duration? timeOut, +// // }) { +// // return TokenContainerSyncing( +// // syncStartedAt: syncStartedAt ?? this.syncStartedAt, +// // timeOut: timeOut ?? this.timeOut, +// // lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, +// // serial: serial ?? this.serial, +// // description: description ?? this.description, +// // type: type ?? this.type, +// // tokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, +// // ); +// // } +// // } - TokenContainerStateUnsynced withIncrementedSyncAttempts() => withSyncAttempts(syncAttempts + 1); - TokenContainerStateUnsynced withSyncAttempts(int syncAttempts) => TokenContainerStateUnsynced( - syncAttempts: syncAttempts, - lastSyncedAt: lastSyncedAt, - serial: serial, - description: description, - type: type, - syncedTokenTemplates: syncedTokenTemplates, - localTokenTemplates: localTokenTemplates, - ); +// /// ContainerState is failed last sync attempt +// @JsonSerializable() +// class TokenContainerUnsynced extends TokenContainer { +// final int syncAttempts; +// final dynamic lastError; - @override - TokenContainerStateModified copyWith({ - String? serial, - String? description, - String? type, - List? syncedTokenTemplates, - List? localTokenTemplates, - DateTime? lastSyncedAt, - DateTime? lastModifiedAt, - int? syncAttempts, - }) { - return TokenContainerStateModified( - lastModifiedAt: lastModifiedAt ?? DateTime.now(), - lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, - serial: serial ?? this.serial, - description: description ?? this.description, - type: type ?? this.type, - syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, - localTokenTemplates: this.localTokenTemplates, - ); - } -} +// TokenContainerUnsynced({ +// this.syncAttempts = 1, +// this.lastError, +// required super.lastSyncedAt, +// required super.serial, +// required super.description, +// required super.type, +// required super.syncedTokenTemplates, +// required super.localTokenTemplates, +// }); -/// ContainerState is in error state -@JsonSerializable() -class TokenContainerStateError extends TokenContainerState { - final dynamic error; - TokenContainerStateError({ - required this.error, - super.lastSyncedAt, - super.serial = 'Error', - String? description, - super.type = 'Error', - super.syncedTokenTemplates = const [], - super.localTokenTemplates = const [], - }) : super(description: description ?? error.toString()); +// TokenContainerUnsynced withIncrementedSyncAttempts() => withSyncAttempts(syncAttempts + 1); +// TokenContainerUnsynced withSyncAttempts(int syncAttempts) => TokenContainerUnsynced( +// syncAttempts: syncAttempts, +// lastSyncedAt: lastSyncedAt, +// serial: serial, +// description: description, +// type: type, +// syncedTokenTemplates: syncedTokenTemplates, +// localTokenTemplates: localTokenTemplates, +// ); - @override - TokenContainerStateModified copyWith({ - String? serial, - String? description, - String? type, - List? syncedTokenTemplates, - List? localTokenTemplates, - DateTime? lastSyncedAt, - DateTime? lastModifiedAt, - }) { - return TokenContainerStateModified( - lastModifiedAt: lastModifiedAt ?? DateTime.now(), - lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, - serial: serial ?? this.serial, - description: description ?? this.description, - type: type ?? this.type, - syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, - localTokenTemplates: this.localTokenTemplates, - ); - } -} +// @override +// TokenContainerModified copyWith({ +// String? serial, +// String? description, +// String? type, +// List? syncedTokenTemplates, +// List? localTokenTemplates, +// DateTime? lastSyncedAt, +// DateTime? lastModifiedAt, +// int? syncAttempts, +// }) { +// return TokenContainerModified( +// lastModifiedAt: lastModifiedAt ?? DateTime.now(), +// lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, +// serial: serial ?? this.serial, +// description: description ?? this.description, +// type: type ?? this.type, +// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, +// localTokenTemplates: this.localTokenTemplates, +// ); +// } +// } -/// ContainerState is deactivated -@JsonSerializable() -class TokenContainerStateDeactivated extends TokenContainerState { - final DateTime deactivatedAt; - final dynamic reason; - TokenContainerStateDeactivated({ - required this.reason, - required this.deactivatedAt, - required super.lastSyncedAt, - required super.serial, - required super.description, - required super.type, - required super.syncedTokenTemplates, - required super.localTokenTemplates, - }); +// /// ContainerState is in error state +// @JsonSerializable() +// class TokenContainerError extends TokenContainer { +// final dynamic error; +// TokenContainerError({ +// required this.error, +// super.lastSyncedAt, +// super.serial = 'Error', +// String? description, +// super.type = 'Error', +// super.syncedTokenTemplates = const [], +// super.localTokenTemplates = const [], +// }) : super(description: description ?? error.toString()); + +// @override +// TokenContainerModified copyWith({ +// String? serial, +// String? description, +// String? type, +// List? syncedTokenTemplates, +// List? localTokenTemplates, +// DateTime? lastSyncedAt, +// DateTime? lastModifiedAt, +// }) { +// return TokenContainerModified( +// lastModifiedAt: lastModifiedAt ?? DateTime.now(), +// lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, +// serial: serial ?? this.serial, +// description: description ?? this.description, +// type: type ?? this.type, +// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, +// localTokenTemplates: this.localTokenTemplates, +// ); +// } +// } + +// /// ContainerState is deactivated +// @JsonSerializable() +// class TokenContainerDeactivated extends TokenContainer { +// final DateTime deactivatedAt; +// final dynamic reason; +// TokenContainerDeactivated({ +// required this.reason, +// required this.deactivatedAt, +// required super.lastSyncedAt, +// required super.serial, +// required super.description, +// required super.type, +// required super.syncedTokenTemplates, +// required super.localTokenTemplates, +// }); - @override - TokenContainerStateModified copyWith({ - String? serial, - String? description, - String? type, - List? syncedTokenTemplates, - List? localTokenTemplates, - DateTime? lastSyncedAt, - DateTime? lastModifiedAt, - }) { - return TokenContainerStateModified( - lastModifiedAt: lastModifiedAt ?? DateTime.now(), - lastSyncedAt: lastSyncedAt ?? lastSyncedAt, - serial: serial ?? this.serial, - description: description ?? this.description, - type: type ?? this.type, - syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, - localTokenTemplates: this.localTokenTemplates, - ); - } -} +// @override +// TokenContainerModified copyWith({ +// String? serial, +// String? description, +// String? type, +// List? syncedTokenTemplates, +// List? localTokenTemplates, +// DateTime? lastSyncedAt, +// DateTime? lastModifiedAt, +// }) { +// return TokenContainerModified( +// lastModifiedAt: lastModifiedAt ?? DateTime.now(), +// lastSyncedAt: lastSyncedAt ?? lastSyncedAt, +// serial: serial ?? this.serial, +// description: description ?? this.description, +// type: type ?? this.type, +// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, +// localTokenTemplates: this.localTokenTemplates, +// ); +// } +// } -/// ContainerState is deleted repo-sseriale -@JsonSerializable() -class TokenContainerStateDeleted extends TokenContainerState { - final DateTime deletedAt; - final dynamic reason; - TokenContainerStateDeleted({ - required this.reason, - required this.deletedAt, - required super.lastSyncedAt, - required super.serial, - required super.description, - required super.type, - required super.syncedTokenTemplates, - required super.localTokenTemplates, - }); +// /// ContainerState is deleted repo-sseriale +// @JsonSerializable() +// class TokenContainerDeleted extends TokenContainer { +// final DateTime deletedAt; +// final dynamic reason; +// TokenContainerDeleted({ +// required this.reason, +// required this.deletedAt, +// required super.lastSyncedAt, +// required super.serial, +// required super.description, +// required super.type, +// required super.syncedTokenTemplates, +// required super.localTokenTemplates, +// }); - @override - TokenContainerStateModified copyWith({ - String? serial, - String? description, - String? type, - List? syncedTokenTemplates, - List? localTokenTemplates, - DateTime? lastSyncedAt, - DateTime? lastModifiedAt, +// @override +// TokenContainerModified copyWith({ +// String? serial, +// String? description, +// String? type, +// List? syncedTokenTemplates, +// List? localTokenTemplates, +// DateTime? lastSyncedAt, +// DateTime? lastModifiedAt, - }) { - return TokenContainerStateModified( - lastModifiedAt: lastModifiedAt ?? DateTime.now(), - lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, - serial: serial ?? this.serial, - description: description ?? this.description, - type: type ?? this.type, - syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, - localTokenTemplates: this.localTokenTemplates, - ); - } -} +// }) { +// return TokenContainerModified( +// lastModifiedAt: lastModifiedAt ?? DateTime.now(), +// lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, +// serial: serial ?? this.serial, +// description: description ?? this.description, +// type: type ?? this.type, +// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, +// localTokenTemplates: this.localTokenTemplates, +// ); +// } +// } diff --git a/lib/model/states/token_container_state.g.dart b/lib/model/states/token_container_state.g.dart deleted file mode 100644 index e0a081455..000000000 --- a/lib/model/states/token_container_state.g.dart +++ /dev/null @@ -1,203 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'token_container_state.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -TokenContainerStateUninitialized _$TokenContainerStateUninitializedFromJson( - Map json) => - TokenContainerStateUninitialized( - localTokenTemplates: (json['localTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - ); - -Map _$TokenContainerStateUninitializedToJson( - TokenContainerStateUninitialized instance) => - { - 'localTokenTemplates': instance.localTokenTemplates, - }; - -TokenContainerStateModified _$TokenContainerStateModifiedFromJson( - Map json) => - TokenContainerStateModified( - lastModifiedAt: DateTime.parse(json['lastModifiedAt'] as String), - lastSyncedAt: json['lastSyncedAt'] == null - ? null - : DateTime.parse(json['lastSyncedAt'] as String), - serial: json['serial'] as String, - description: json['description'] as String, - type: json['type'] as String, - syncedTokenTemplates: (json['syncedTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - localTokenTemplates: (json['localTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - ); - -Map _$TokenContainerStateModifiedToJson( - TokenContainerStateModified instance) => - { - 'serial': instance.serial, - 'description': instance.description, - 'type': instance.type, - 'syncedTokenTemplates': instance.syncedTokenTemplates, - 'localTokenTemplates': instance.localTokenTemplates, - 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), - 'lastModifiedAt': instance.lastModifiedAt.toIso8601String(), - }; - -TokenContainerStateSynced _$TokenContainerStateSyncedFromJson( - Map json) => - TokenContainerStateSynced( - serial: json['serial'] as String, - description: json['description'] as String, - type: json['type'] as String, - syncedTokenTemplates: (json['syncedTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - lastSyncedAt: json['lastSyncedAt'] == null - ? null - : DateTime.parse(json['lastSyncedAt'] as String), - ); - -Map _$TokenContainerStateSyncedToJson( - TokenContainerStateSynced instance) => - { - 'serial': instance.serial, - 'description': instance.description, - 'type': instance.type, - 'syncedTokenTemplates': instance.syncedTokenTemplates, - 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), - }; - -TokenContainerStateUnsynced _$TokenContainerStateUnsyncedFromJson( - Map json) => - TokenContainerStateUnsynced( - syncAttempts: (json['syncAttempts'] as num?)?.toInt() ?? 1, - lastError: json['lastError'], - lastSyncedAt: json['lastSyncedAt'] == null - ? null - : DateTime.parse(json['lastSyncedAt'] as String), - serial: json['serial'] as String, - description: json['description'] as String, - type: json['type'] as String, - syncedTokenTemplates: (json['syncedTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - localTokenTemplates: (json['localTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - ); - -Map _$TokenContainerStateUnsyncedToJson( - TokenContainerStateUnsynced instance) => - { - 'serial': instance.serial, - 'description': instance.description, - 'type': instance.type, - 'syncedTokenTemplates': instance.syncedTokenTemplates, - 'localTokenTemplates': instance.localTokenTemplates, - 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), - 'syncAttempts': instance.syncAttempts, - 'lastError': instance.lastError, - }; - -TokenContainerStateError _$TokenContainerStateErrorFromJson( - Map json) => - TokenContainerStateError( - error: json['error'], - lastSyncedAt: json['lastSyncedAt'] == null - ? null - : DateTime.parse(json['lastSyncedAt'] as String), - serial: json['serial'] as String? ?? 'Error', - description: json['description'] as String?, - type: json['type'] as String? ?? 'Error', - syncedTokenTemplates: (json['syncedTokenTemplates'] as List?) - ?.map((e) => TokenTemplate.fromJson(e as Map)) - .toList() ?? - const [], - localTokenTemplates: (json['localTokenTemplates'] as List?) - ?.map((e) => TokenTemplate.fromJson(e as Map)) - .toList() ?? - const [], - ); - -Map _$TokenContainerStateErrorToJson( - TokenContainerStateError instance) => - { - 'serial': instance.serial, - 'description': instance.description, - 'type': instance.type, - 'syncedTokenTemplates': instance.syncedTokenTemplates, - 'localTokenTemplates': instance.localTokenTemplates, - 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), - 'error': instance.error, - }; - -TokenContainerStateDeactivated _$TokenContainerStateDeactivatedFromJson( - Map json) => - TokenContainerStateDeactivated( - reason: json['reason'], - deactivatedAt: DateTime.parse(json['deactivatedAt'] as String), - lastSyncedAt: json['lastSyncedAt'] == null - ? null - : DateTime.parse(json['lastSyncedAt'] as String), - serial: json['serial'] as String, - description: json['description'] as String, - type: json['type'] as String, - syncedTokenTemplates: (json['syncedTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - localTokenTemplates: (json['localTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - ); - -Map _$TokenContainerStateDeactivatedToJson( - TokenContainerStateDeactivated instance) => - { - 'serial': instance.serial, - 'description': instance.description, - 'type': instance.type, - 'syncedTokenTemplates': instance.syncedTokenTemplates, - 'localTokenTemplates': instance.localTokenTemplates, - 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), - 'deactivatedAt': instance.deactivatedAt.toIso8601String(), - 'reason': instance.reason, - }; - -TokenContainerStateDeleted _$TokenContainerStateDeletedFromJson( - Map json) => - TokenContainerStateDeleted( - reason: json['reason'], - deletedAt: DateTime.parse(json['deletedAt'] as String), - lastSyncedAt: json['lastSyncedAt'] == null - ? null - : DateTime.parse(json['lastSyncedAt'] as String), - serial: json['serial'] as String, - description: json['description'] as String, - type: json['type'] as String, - syncedTokenTemplates: (json['syncedTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - localTokenTemplates: (json['localTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - ); - -Map _$TokenContainerStateDeletedToJson( - TokenContainerStateDeleted instance) => - { - 'serial': instance.serial, - 'description': instance.description, - 'type': instance.type, - 'syncedTokenTemplates': instance.syncedTokenTemplates, - 'localTokenTemplates': instance.localTokenTemplates, - 'lastSyncedAt': instance.lastSyncedAt?.toIso8601String(), - 'deletedAt': instance.deletedAt.toIso8601String(), - 'reason': instance.reason, - }; diff --git a/lib/model/states/token_state.dart b/lib/model/states/token_state.dart index 9d52ed3a2..d8ae9be4b 100644 --- a/lib/model/states/token_state.dart +++ b/lib/model/states/token_state.dart @@ -166,8 +166,12 @@ extension TokenListExtension on List { return true; }).toList(); - List fromContainer(String containerId) => - where((token) => token.origin?.source == TokenOriginSourceType.container && token.containerId == containerId).toList(); + List fromContainer(String containerId) { + return where((token) { + Logger.warning('token.origin?.source: ${token.origin?.source} && token.containerId: ${token.containerId}', name: 'token_state.dart#fromContainer'); + return token.origin?.source == TokenOriginSourceType.container && token.containerId == containerId; + }).toList(); + } List toTemplates() { if (isEmpty) return []; diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index 9b252ccb6..91e304a11 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -1,50 +1,162 @@ -import 'package:json_annotation/json_annotation.dart'; +// We need some unnecessary_overrides to force to add the fields in factory constructors +// ignore_for_file: unnecessary_overrides + +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; +import 'token_import/token_origin_data.dart'; import 'tokens/token.dart'; part 'token_container.g.dart'; - -@JsonSerializable() -class TokenContainer { - final String serial; - final String description; - final String type; - final List syncedTokenTemplates; - final List localTokenTemplates; - - const TokenContainer({ - required this.serial, - required this.description, - required this.type, - required this.syncedTokenTemplates, - required this.localTokenTemplates, - }); - - factory TokenContainer.fromJson(Map json) => _$TokenContainerFromJson(json); - Map toJson() => _$TokenContainerToJson(this); - - TokenContainer copyWith({ +part 'token_container.freezed.dart'; + +@freezed +sealed class TokenContainer with _$TokenContainer { + const TokenContainer._(); + // Overriding fields resutls in a error when someone + // forgot to add the field in a new factory constructor + // On error "The getter '...' isn't defined in a superclass of 'TokenContainer'." + // add the '...' field to the every factory constructor to get rid of this error + @override + String get serverName => super.serverName; + @override + DateTime? get lastSyncAt => super.lastSyncAt; + @override + String get serial => super.serial; + @override + String get description => super.description; + @override + List get syncedTokenTemplates => super.syncedTokenTemplates; + @override + List get localTokenTemplates => super.localTokenTemplates; + + const factory TokenContainer.uninitialized({ + // Base fields + @Default('PrivacyIDEA') String serverName, + @Default(null) DateTime? lastSyncAt, + @Default('none') String serial, + @Default('Uninitialized') String description, + @Default([]) List syncedTokenTemplates, + @Default([]) List localTokenTemplates, + // Base fields end + }) = TokenContainerUninitialized; + const factory TokenContainer.synced({ + // Base fields + @Default('PrivacyIDEA') String serverName, + required DateTime lastSyncAt, + required String serial, + required String description, + required List syncedTokenTemplates, + required List localTokenTemplates, + // Base fields end + }) = TokenContainerSynced; + const factory TokenContainer.modified({ + // Base fields + @Default('PrivacyIDEA') String serverName, + @Default(null) DateTime? lastSyncAt, + required String serial, + required String description, + required List syncedTokenTemplates, + required List localTokenTemplates, + // Base fields end + required DateTime lastModifiedAt, + }) = TokenContainerModified; + const factory TokenContainer.unsynced({ + // Base fields + @Default('PrivacyIDEA') String serverName, + @Default(null) DateTime? lastSyncAt, + required String serial, + required String description, + required List syncedTokenTemplates, + required List localTokenTemplates, + // Base fields end + String? message, + }) = TokenContainerUnsynced; + const factory TokenContainer.notFound({ + // Base fields + @Default('PrivacyIDEA') String serverName, + DateTime? lastSyncAt, + required String serial, + required String description, + required List syncedTokenTemplates, + required List localTokenTemplates, + // Base fields end + required String message, + }) = TokenContainerNotFound; + const factory TokenContainer.error({ + // Base fields + @Default('PrivacyIDEA') String serverName, + @Default(null) DateTime? lastSyncAt, + @Default('none') String serial, + @Default('Error') String description, + @Default([]) List syncedTokenTemplates, + @Default([]) List localTokenTemplates, + // Base fields end + required dynamic error, + }) = TokenContainerError; + + T copyTransformInto({ + // Base fields + String? serverName, + DateTime? lastSyncAt, String? serial, String? description, - String? type, List? syncedTokenTemplates, List? localTokenTemplates, - }) { - return TokenContainer( - serial: serial ?? this.serial, - description: description ?? this.description, - type: type ?? this.type, - syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, - localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, - ); - } + // Base fields end + Map? args, + }) => + switch (T) { + const (TokenContainerSynced) => TokenContainerSynced( + serverName: serverName ?? this.serverName, + lastSyncAt: lastSyncAt ?? this.lastSyncAt!, + serial: serial ?? this.serial, + description: description ?? this.description, + syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, + localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, + ) as T, + const (TokenContainerUnsynced) => TokenContainerUnsynced( + serverName: serverName ?? this.serverName, + lastSyncAt: lastSyncAt ?? this.lastSyncAt, + serial: serial ?? this.serial, + description: description ?? this.description, + syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, + localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, + ) as T, + const (TokenContainerNotFound) => TokenContainerNotFound( + serverName: serverName ?? this.serverName, + lastSyncAt: lastSyncAt ?? this.lastSyncAt, + serial: serial ?? this.serial, + description: description ?? this.description, + syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, + localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, + message: args != null && args['message'] != null ? args['message'] : 'Unknown message', + ) as T, + const (TokenContainerError) => TokenContainerError( + serverName: serverName ?? this.serverName, + lastSyncAt: lastSyncAt ?? this.lastSyncAt, + serial: serial ?? this.serial, + description: description ?? this.description, + syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, + localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, + error: args != null && args['error'] != null ? args['error'] : 'Unknown error', + ) as T, + _ => throw UnimplementedError('Unknown TokenContainer type: $T'), + }; + + factory TokenContainer.fromJson(Map json) => _$TokenContainerFromJson(json); } -@JsonSerializable() -class TokenTemplate { - final Map data; + +@freezed +class TokenTemplate with _$TokenTemplate { + TokenTemplate._(); + factory TokenTemplate({ + required Map data, + }) = _TokenTemplate; String? get id { final id = data[TOKEN_ID]; @@ -54,28 +166,15 @@ class TokenTemplate { return id; } - TokenTemplate({required this.data}); - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is TokenTemplate && other.data == data; - } - - @override - int get hashCode => Object.hashAll([data, runtimeType]); - factory TokenTemplate.fromJson(Map json) => _$TokenTemplateFromJson(json); - Map toJson() => _$TokenTemplateToJson(this); - - Token toToken() => Token.fromUriMap(data); - - TokenTemplate copyWith(Map replace) { - return TokenTemplate( - data: Map.from(data)..addAll(replace), - ); - } - + Token toToken(TokenContainer container) => Token.fromUriMap(data).copyWith( + containerId: () => container.serial, + origin: TokenOriginData( + appName: '${container.serverName} ${container.serial}', + data: data.toString(), + source: TokenOriginSourceType.container, + ), + ); + TokenTemplate copyAddAll(Map data) => TokenTemplate(data: this.data..addAll(data)); } diff --git a/lib/model/token_container.freezed.dart b/lib/model/token_container.freezed.dart new file mode 100644 index 000000000..b04d048ee --- /dev/null +++ b/lib/model/token_container.freezed.dart @@ -0,0 +1,3171 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'token_container.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +TokenContainer _$TokenContainerFromJson(Map json) { + switch (json['runtimeType']) { + case 'uninitialized': + return TokenContainerUninitialized.fromJson(json); + case 'synced': + return TokenContainerSynced.fromJson(json); + case 'modified': + return TokenContainerModified.fromJson(json); + case 'unsynced': + return TokenContainerUnsynced.fromJson(json); + case 'notFound': + return TokenContainerNotFound.fromJson(json); + case 'error': + return TokenContainerError.fromJson(json); + + default: + throw CheckedFromJsonException(json, 'runtimeType', 'TokenContainer', + 'Invalid union type "${json['runtimeType']}"!'); + } +} + +/// @nodoc +mixin _$TokenContainer { +// Base fields + String get serverName => throw _privateConstructorUsedError; + DateTime? get lastSyncAt => throw _privateConstructorUsedError; + String get serial => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + List get syncedTokenTemplates => + throw _privateConstructorUsedError; + List get localTokenTemplates => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + uninitialized, + required TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + synced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt) + modified, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message) + unsynced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message) + notFound, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error) + error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult? Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(TokenContainerUninitialized value) uninitialized, + required TResult Function(TokenContainerSynced value) synced, + required TResult Function(TokenContainerModified value) modified, + required TResult Function(TokenContainerUnsynced value) unsynced, + required TResult Function(TokenContainerNotFound value) notFound, + required TResult Function(TokenContainerError value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(TokenContainerUninitialized value)? uninitialized, + TResult? Function(TokenContainerSynced value)? synced, + TResult? Function(TokenContainerModified value)? modified, + TResult? Function(TokenContainerUnsynced value)? unsynced, + TResult? Function(TokenContainerNotFound value)? notFound, + TResult? Function(TokenContainerError value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(TokenContainerUninitialized value)? uninitialized, + TResult Function(TokenContainerSynced value)? synced, + TResult Function(TokenContainerModified value)? modified, + TResult Function(TokenContainerUnsynced value)? unsynced, + TResult Function(TokenContainerNotFound value)? notFound, + TResult Function(TokenContainerError value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $TokenContainerCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TokenContainerCopyWith<$Res> { + factory $TokenContainerCopyWith( + TokenContainer value, $Res Function(TokenContainer) then) = + _$TokenContainerCopyWithImpl<$Res, TokenContainer>; + @useResult + $Res call( + {String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates}); +} + +/// @nodoc +class _$TokenContainerCopyWithImpl<$Res, $Val extends TokenContainer> + implements $TokenContainerCopyWith<$Res> { + _$TokenContainerCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? serverName = null, + Object? lastSyncAt = null, + Object? serial = null, + Object? description = null, + Object? syncedTokenTemplates = null, + Object? localTokenTemplates = null, + }) { + return _then(_value.copyWith( + serverName: null == serverName + ? _value.serverName + : serverName // ignore: cast_nullable_to_non_nullable + as String, + lastSyncAt: null == lastSyncAt + ? _value.lastSyncAt! + : lastSyncAt // ignore: cast_nullable_to_non_nullable + as DateTime, + serial: null == serial + ? _value.serial + : serial // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + syncedTokenTemplates: null == syncedTokenTemplates + ? _value.syncedTokenTemplates + : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + localTokenTemplates: null == localTokenTemplates + ? _value.localTokenTemplates + : localTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TokenContainerUninitializedImplCopyWith<$Res> + implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerUninitializedImplCopyWith( + _$TokenContainerUninitializedImpl value, + $Res Function(_$TokenContainerUninitializedImpl) then) = + __$$TokenContainerUninitializedImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates}); +} + +/// @nodoc +class __$$TokenContainerUninitializedImplCopyWithImpl<$Res> + extends _$TokenContainerCopyWithImpl<$Res, + _$TokenContainerUninitializedImpl> + implements _$$TokenContainerUninitializedImplCopyWith<$Res> { + __$$TokenContainerUninitializedImplCopyWithImpl( + _$TokenContainerUninitializedImpl _value, + $Res Function(_$TokenContainerUninitializedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? serverName = null, + Object? lastSyncAt = freezed, + Object? serial = null, + Object? description = null, + Object? syncedTokenTemplates = null, + Object? localTokenTemplates = null, + }) { + return _then(_$TokenContainerUninitializedImpl( + serverName: null == serverName + ? _value.serverName + : serverName // ignore: cast_nullable_to_non_nullable + as String, + lastSyncAt: freezed == lastSyncAt + ? _value.lastSyncAt + : lastSyncAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + serial: null == serial + ? _value.serial + : serial // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + syncedTokenTemplates: null == syncedTokenTemplates + ? _value._syncedTokenTemplates + : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + localTokenTemplates: null == localTokenTemplates + ? _value._localTokenTemplates + : localTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized + with DiagnosticableTreeMixin { + const _$TokenContainerUninitializedImpl( + {this.serverName = 'PrivacyIDEA', + this.lastSyncAt = null, + this.serial = 'none', + this.description = 'Uninitialized', + final List syncedTokenTemplates = const [], + final List localTokenTemplates = const [], + final String? $type}) + : _syncedTokenTemplates = syncedTokenTemplates, + _localTokenTemplates = localTokenTemplates, + $type = $type ?? 'uninitialized', + super._(); + + factory _$TokenContainerUninitializedImpl.fromJson( + Map json) => + _$$TokenContainerUninitializedImplFromJson(json); + +// Base fields + @override + @JsonKey() + final String serverName; + @override + @JsonKey() + final DateTime? lastSyncAt; + @override + @JsonKey() + final String serial; + @override + @JsonKey() + final String description; + final List _syncedTokenTemplates; + @override + @JsonKey() + List get syncedTokenTemplates { + if (_syncedTokenTemplates is EqualUnmodifiableListView) + return _syncedTokenTemplates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_syncedTokenTemplates); + } + + final List _localTokenTemplates; + @override + @JsonKey() + List get localTokenTemplates { + if (_localTokenTemplates is EqualUnmodifiableListView) + return _localTokenTemplates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_localTokenTemplates); + } + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'TokenContainer.uninitialized(serverName: $serverName, lastSyncAt: $lastSyncAt, serial: $serial, description: $description, syncedTokenTemplates: $syncedTokenTemplates, localTokenTemplates: $localTokenTemplates)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'TokenContainer.uninitialized')) + ..add(DiagnosticsProperty('serverName', serverName)) + ..add(DiagnosticsProperty('lastSyncAt', lastSyncAt)) + ..add(DiagnosticsProperty('serial', serial)) + ..add(DiagnosticsProperty('description', description)) + ..add(DiagnosticsProperty('syncedTokenTemplates', syncedTokenTemplates)) + ..add(DiagnosticsProperty('localTokenTemplates', localTokenTemplates)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TokenContainerUninitializedImpl && + (identical(other.serverName, serverName) || + other.serverName == serverName) && + (identical(other.lastSyncAt, lastSyncAt) || + other.lastSyncAt == lastSyncAt) && + (identical(other.serial, serial) || other.serial == serial) && + (identical(other.description, description) || + other.description == description) && + const DeepCollectionEquality() + .equals(other._syncedTokenTemplates, _syncedTokenTemplates) && + const DeepCollectionEquality() + .equals(other._localTokenTemplates, _localTokenTemplates)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + serverName, + lastSyncAt, + serial, + description, + const DeepCollectionEquality().hash(_syncedTokenTemplates), + const DeepCollectionEquality().hash(_localTokenTemplates)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TokenContainerUninitializedImplCopyWith<_$TokenContainerUninitializedImpl> + get copyWith => __$$TokenContainerUninitializedImplCopyWithImpl< + _$TokenContainerUninitializedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + uninitialized, + required TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + synced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt) + modified, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message) + unsynced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message) + notFound, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error) + error, + }) { + return uninitialized(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult? Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + }) { + return uninitialized?.call(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + required TResult orElse(), + }) { + if (uninitialized != null) { + return uninitialized(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(TokenContainerUninitialized value) uninitialized, + required TResult Function(TokenContainerSynced value) synced, + required TResult Function(TokenContainerModified value) modified, + required TResult Function(TokenContainerUnsynced value) unsynced, + required TResult Function(TokenContainerNotFound value) notFound, + required TResult Function(TokenContainerError value) error, + }) { + return uninitialized(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(TokenContainerUninitialized value)? uninitialized, + TResult? Function(TokenContainerSynced value)? synced, + TResult? Function(TokenContainerModified value)? modified, + TResult? Function(TokenContainerUnsynced value)? unsynced, + TResult? Function(TokenContainerNotFound value)? notFound, + TResult? Function(TokenContainerError value)? error, + }) { + return uninitialized?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(TokenContainerUninitialized value)? uninitialized, + TResult Function(TokenContainerSynced value)? synced, + TResult Function(TokenContainerModified value)? modified, + TResult Function(TokenContainerUnsynced value)? unsynced, + TResult Function(TokenContainerNotFound value)? notFound, + TResult Function(TokenContainerError value)? error, + required TResult orElse(), + }) { + if (uninitialized != null) { + return uninitialized(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$TokenContainerUninitializedImplToJson( + this, + ); + } +} + +abstract class TokenContainerUninitialized extends TokenContainer { + const factory TokenContainerUninitialized( + {final String serverName, + final DateTime? lastSyncAt, + final String serial, + final String description, + final List syncedTokenTemplates, + final List localTokenTemplates}) = + _$TokenContainerUninitializedImpl; + const TokenContainerUninitialized._() : super._(); + + factory TokenContainerUninitialized.fromJson(Map json) = + _$TokenContainerUninitializedImpl.fromJson; + + @override // Base fields + String get serverName; + @override + DateTime? get lastSyncAt; + @override + String get serial; + @override + String get description; + @override + List get syncedTokenTemplates; + @override + List get localTokenTemplates; + @override + @JsonKey(ignore: true) + _$$TokenContainerUninitializedImplCopyWith<_$TokenContainerUninitializedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$TokenContainerSyncedImplCopyWith<$Res> + implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerSyncedImplCopyWith(_$TokenContainerSyncedImpl value, + $Res Function(_$TokenContainerSyncedImpl) then) = + __$$TokenContainerSyncedImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates}); +} + +/// @nodoc +class __$$TokenContainerSyncedImplCopyWithImpl<$Res> + extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerSyncedImpl> + implements _$$TokenContainerSyncedImplCopyWith<$Res> { + __$$TokenContainerSyncedImplCopyWithImpl(_$TokenContainerSyncedImpl _value, + $Res Function(_$TokenContainerSyncedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? serverName = null, + Object? lastSyncAt = null, + Object? serial = null, + Object? description = null, + Object? syncedTokenTemplates = null, + Object? localTokenTemplates = null, + }) { + return _then(_$TokenContainerSyncedImpl( + serverName: null == serverName + ? _value.serverName + : serverName // ignore: cast_nullable_to_non_nullable + as String, + lastSyncAt: null == lastSyncAt + ? _value.lastSyncAt + : lastSyncAt // ignore: cast_nullable_to_non_nullable + as DateTime, + serial: null == serial + ? _value.serial + : serial // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + syncedTokenTemplates: null == syncedTokenTemplates + ? _value._syncedTokenTemplates + : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + localTokenTemplates: null == localTokenTemplates + ? _value._localTokenTemplates + : localTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TokenContainerSyncedImpl extends TokenContainerSynced + with DiagnosticableTreeMixin { + const _$TokenContainerSyncedImpl( + {this.serverName = 'PrivacyIDEA', + required this.lastSyncAt, + required this.serial, + required this.description, + required final List syncedTokenTemplates, + required final List localTokenTemplates, + final String? $type}) + : _syncedTokenTemplates = syncedTokenTemplates, + _localTokenTemplates = localTokenTemplates, + $type = $type ?? 'synced', + super._(); + + factory _$TokenContainerSyncedImpl.fromJson(Map json) => + _$$TokenContainerSyncedImplFromJson(json); + +// Base fields + @override + @JsonKey() + final String serverName; + @override + final DateTime lastSyncAt; + @override + final String serial; + @override + final String description; + final List _syncedTokenTemplates; + @override + List get syncedTokenTemplates { + if (_syncedTokenTemplates is EqualUnmodifiableListView) + return _syncedTokenTemplates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_syncedTokenTemplates); + } + + final List _localTokenTemplates; + @override + List get localTokenTemplates { + if (_localTokenTemplates is EqualUnmodifiableListView) + return _localTokenTemplates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_localTokenTemplates); + } + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'TokenContainer.synced(serverName: $serverName, lastSyncAt: $lastSyncAt, serial: $serial, description: $description, syncedTokenTemplates: $syncedTokenTemplates, localTokenTemplates: $localTokenTemplates)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'TokenContainer.synced')) + ..add(DiagnosticsProperty('serverName', serverName)) + ..add(DiagnosticsProperty('lastSyncAt', lastSyncAt)) + ..add(DiagnosticsProperty('serial', serial)) + ..add(DiagnosticsProperty('description', description)) + ..add(DiagnosticsProperty('syncedTokenTemplates', syncedTokenTemplates)) + ..add(DiagnosticsProperty('localTokenTemplates', localTokenTemplates)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TokenContainerSyncedImpl && + (identical(other.serverName, serverName) || + other.serverName == serverName) && + (identical(other.lastSyncAt, lastSyncAt) || + other.lastSyncAt == lastSyncAt) && + (identical(other.serial, serial) || other.serial == serial) && + (identical(other.description, description) || + other.description == description) && + const DeepCollectionEquality() + .equals(other._syncedTokenTemplates, _syncedTokenTemplates) && + const DeepCollectionEquality() + .equals(other._localTokenTemplates, _localTokenTemplates)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + serverName, + lastSyncAt, + serial, + description, + const DeepCollectionEquality().hash(_syncedTokenTemplates), + const DeepCollectionEquality().hash(_localTokenTemplates)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TokenContainerSyncedImplCopyWith<_$TokenContainerSyncedImpl> + get copyWith => + __$$TokenContainerSyncedImplCopyWithImpl<_$TokenContainerSyncedImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + uninitialized, + required TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + synced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt) + modified, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message) + unsynced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message) + notFound, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error) + error, + }) { + return synced(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult? Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + }) { + return synced?.call(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + required TResult orElse(), + }) { + if (synced != null) { + return synced(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(TokenContainerUninitialized value) uninitialized, + required TResult Function(TokenContainerSynced value) synced, + required TResult Function(TokenContainerModified value) modified, + required TResult Function(TokenContainerUnsynced value) unsynced, + required TResult Function(TokenContainerNotFound value) notFound, + required TResult Function(TokenContainerError value) error, + }) { + return synced(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(TokenContainerUninitialized value)? uninitialized, + TResult? Function(TokenContainerSynced value)? synced, + TResult? Function(TokenContainerModified value)? modified, + TResult? Function(TokenContainerUnsynced value)? unsynced, + TResult? Function(TokenContainerNotFound value)? notFound, + TResult? Function(TokenContainerError value)? error, + }) { + return synced?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(TokenContainerUninitialized value)? uninitialized, + TResult Function(TokenContainerSynced value)? synced, + TResult Function(TokenContainerModified value)? modified, + TResult Function(TokenContainerUnsynced value)? unsynced, + TResult Function(TokenContainerNotFound value)? notFound, + TResult Function(TokenContainerError value)? error, + required TResult orElse(), + }) { + if (synced != null) { + return synced(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$TokenContainerSyncedImplToJson( + this, + ); + } +} + +abstract class TokenContainerSynced extends TokenContainer { + const factory TokenContainerSynced( + {final String serverName, + required final DateTime lastSyncAt, + required final String serial, + required final String description, + required final List syncedTokenTemplates, + required final List localTokenTemplates}) = + _$TokenContainerSyncedImpl; + const TokenContainerSynced._() : super._(); + + factory TokenContainerSynced.fromJson(Map json) = + _$TokenContainerSyncedImpl.fromJson; + + @override // Base fields + String get serverName; + @override + DateTime get lastSyncAt; + @override + String get serial; + @override + String get description; + @override + List get syncedTokenTemplates; + @override + List get localTokenTemplates; + @override + @JsonKey(ignore: true) + _$$TokenContainerSyncedImplCopyWith<_$TokenContainerSyncedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$TokenContainerModifiedImplCopyWith<$Res> + implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerModifiedImplCopyWith( + _$TokenContainerModifiedImpl value, + $Res Function(_$TokenContainerModifiedImpl) then) = + __$$TokenContainerModifiedImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt}); +} + +/// @nodoc +class __$$TokenContainerModifiedImplCopyWithImpl<$Res> + extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerModifiedImpl> + implements _$$TokenContainerModifiedImplCopyWith<$Res> { + __$$TokenContainerModifiedImplCopyWithImpl( + _$TokenContainerModifiedImpl _value, + $Res Function(_$TokenContainerModifiedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? serverName = null, + Object? lastSyncAt = freezed, + Object? serial = null, + Object? description = null, + Object? syncedTokenTemplates = null, + Object? localTokenTemplates = null, + Object? lastModifiedAt = null, + }) { + return _then(_$TokenContainerModifiedImpl( + serverName: null == serverName + ? _value.serverName + : serverName // ignore: cast_nullable_to_non_nullable + as String, + lastSyncAt: freezed == lastSyncAt + ? _value.lastSyncAt + : lastSyncAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + serial: null == serial + ? _value.serial + : serial // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + syncedTokenTemplates: null == syncedTokenTemplates + ? _value._syncedTokenTemplates + : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + localTokenTemplates: null == localTokenTemplates + ? _value._localTokenTemplates + : localTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + lastModifiedAt: null == lastModifiedAt + ? _value.lastModifiedAt + : lastModifiedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TokenContainerModifiedImpl extends TokenContainerModified + with DiagnosticableTreeMixin { + const _$TokenContainerModifiedImpl( + {this.serverName = 'PrivacyIDEA', + this.lastSyncAt = null, + required this.serial, + required this.description, + required final List syncedTokenTemplates, + required final List localTokenTemplates, + required this.lastModifiedAt, + final String? $type}) + : _syncedTokenTemplates = syncedTokenTemplates, + _localTokenTemplates = localTokenTemplates, + $type = $type ?? 'modified', + super._(); + + factory _$TokenContainerModifiedImpl.fromJson(Map json) => + _$$TokenContainerModifiedImplFromJson(json); + +// Base fields + @override + @JsonKey() + final String serverName; + @override + @JsonKey() + final DateTime? lastSyncAt; + @override + final String serial; + @override + final String description; + final List _syncedTokenTemplates; + @override + List get syncedTokenTemplates { + if (_syncedTokenTemplates is EqualUnmodifiableListView) + return _syncedTokenTemplates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_syncedTokenTemplates); + } + + final List _localTokenTemplates; + @override + List get localTokenTemplates { + if (_localTokenTemplates is EqualUnmodifiableListView) + return _localTokenTemplates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_localTokenTemplates); + } + +// Base fields end + @override + final DateTime lastModifiedAt; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'TokenContainer.modified(serverName: $serverName, lastSyncAt: $lastSyncAt, serial: $serial, description: $description, syncedTokenTemplates: $syncedTokenTemplates, localTokenTemplates: $localTokenTemplates, lastModifiedAt: $lastModifiedAt)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'TokenContainer.modified')) + ..add(DiagnosticsProperty('serverName', serverName)) + ..add(DiagnosticsProperty('lastSyncAt', lastSyncAt)) + ..add(DiagnosticsProperty('serial', serial)) + ..add(DiagnosticsProperty('description', description)) + ..add(DiagnosticsProperty('syncedTokenTemplates', syncedTokenTemplates)) + ..add(DiagnosticsProperty('localTokenTemplates', localTokenTemplates)) + ..add(DiagnosticsProperty('lastModifiedAt', lastModifiedAt)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TokenContainerModifiedImpl && + (identical(other.serverName, serverName) || + other.serverName == serverName) && + (identical(other.lastSyncAt, lastSyncAt) || + other.lastSyncAt == lastSyncAt) && + (identical(other.serial, serial) || other.serial == serial) && + (identical(other.description, description) || + other.description == description) && + const DeepCollectionEquality() + .equals(other._syncedTokenTemplates, _syncedTokenTemplates) && + const DeepCollectionEquality() + .equals(other._localTokenTemplates, _localTokenTemplates) && + (identical(other.lastModifiedAt, lastModifiedAt) || + other.lastModifiedAt == lastModifiedAt)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + serverName, + lastSyncAt, + serial, + description, + const DeepCollectionEquality().hash(_syncedTokenTemplates), + const DeepCollectionEquality().hash(_localTokenTemplates), + lastModifiedAt); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TokenContainerModifiedImplCopyWith<_$TokenContainerModifiedImpl> + get copyWith => __$$TokenContainerModifiedImplCopyWithImpl< + _$TokenContainerModifiedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + uninitialized, + required TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + synced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt) + modified, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message) + unsynced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message) + notFound, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error) + error, + }) { + return modified(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates, lastModifiedAt); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult? Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + }) { + return modified?.call(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates, lastModifiedAt); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + required TResult orElse(), + }) { + if (modified != null) { + return modified(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates, lastModifiedAt); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(TokenContainerUninitialized value) uninitialized, + required TResult Function(TokenContainerSynced value) synced, + required TResult Function(TokenContainerModified value) modified, + required TResult Function(TokenContainerUnsynced value) unsynced, + required TResult Function(TokenContainerNotFound value) notFound, + required TResult Function(TokenContainerError value) error, + }) { + return modified(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(TokenContainerUninitialized value)? uninitialized, + TResult? Function(TokenContainerSynced value)? synced, + TResult? Function(TokenContainerModified value)? modified, + TResult? Function(TokenContainerUnsynced value)? unsynced, + TResult? Function(TokenContainerNotFound value)? notFound, + TResult? Function(TokenContainerError value)? error, + }) { + return modified?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(TokenContainerUninitialized value)? uninitialized, + TResult Function(TokenContainerSynced value)? synced, + TResult Function(TokenContainerModified value)? modified, + TResult Function(TokenContainerUnsynced value)? unsynced, + TResult Function(TokenContainerNotFound value)? notFound, + TResult Function(TokenContainerError value)? error, + required TResult orElse(), + }) { + if (modified != null) { + return modified(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$TokenContainerModifiedImplToJson( + this, + ); + } +} + +abstract class TokenContainerModified extends TokenContainer { + const factory TokenContainerModified( + {final String serverName, + final DateTime? lastSyncAt, + required final String serial, + required final String description, + required final List syncedTokenTemplates, + required final List localTokenTemplates, + required final DateTime lastModifiedAt}) = _$TokenContainerModifiedImpl; + const TokenContainerModified._() : super._(); + + factory TokenContainerModified.fromJson(Map json) = + _$TokenContainerModifiedImpl.fromJson; + + @override // Base fields + String get serverName; + @override + DateTime? get lastSyncAt; + @override + String get serial; + @override + String get description; + @override + List get syncedTokenTemplates; + @override + List get localTokenTemplates; // Base fields end + DateTime get lastModifiedAt; + @override + @JsonKey(ignore: true) + _$$TokenContainerModifiedImplCopyWith<_$TokenContainerModifiedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$TokenContainerUnsyncedImplCopyWith<$Res> + implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerUnsyncedImplCopyWith( + _$TokenContainerUnsyncedImpl value, + $Res Function(_$TokenContainerUnsyncedImpl) then) = + __$$TokenContainerUnsyncedImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message}); +} + +/// @nodoc +class __$$TokenContainerUnsyncedImplCopyWithImpl<$Res> + extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerUnsyncedImpl> + implements _$$TokenContainerUnsyncedImplCopyWith<$Res> { + __$$TokenContainerUnsyncedImplCopyWithImpl( + _$TokenContainerUnsyncedImpl _value, + $Res Function(_$TokenContainerUnsyncedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? serverName = null, + Object? lastSyncAt = freezed, + Object? serial = null, + Object? description = null, + Object? syncedTokenTemplates = null, + Object? localTokenTemplates = null, + Object? message = freezed, + }) { + return _then(_$TokenContainerUnsyncedImpl( + serverName: null == serverName + ? _value.serverName + : serverName // ignore: cast_nullable_to_non_nullable + as String, + lastSyncAt: freezed == lastSyncAt + ? _value.lastSyncAt + : lastSyncAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + serial: null == serial + ? _value.serial + : serial // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + syncedTokenTemplates: null == syncedTokenTemplates + ? _value._syncedTokenTemplates + : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + localTokenTemplates: null == localTokenTemplates + ? _value._localTokenTemplates + : localTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + message: freezed == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TokenContainerUnsyncedImpl extends TokenContainerUnsynced + with DiagnosticableTreeMixin { + const _$TokenContainerUnsyncedImpl( + {this.serverName = 'PrivacyIDEA', + this.lastSyncAt = null, + required this.serial, + required this.description, + required final List syncedTokenTemplates, + required final List localTokenTemplates, + this.message, + final String? $type}) + : _syncedTokenTemplates = syncedTokenTemplates, + _localTokenTemplates = localTokenTemplates, + $type = $type ?? 'unsynced', + super._(); + + factory _$TokenContainerUnsyncedImpl.fromJson(Map json) => + _$$TokenContainerUnsyncedImplFromJson(json); + +// Base fields + @override + @JsonKey() + final String serverName; + @override + @JsonKey() + final DateTime? lastSyncAt; + @override + final String serial; + @override + final String description; + final List _syncedTokenTemplates; + @override + List get syncedTokenTemplates { + if (_syncedTokenTemplates is EqualUnmodifiableListView) + return _syncedTokenTemplates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_syncedTokenTemplates); + } + + final List _localTokenTemplates; + @override + List get localTokenTemplates { + if (_localTokenTemplates is EqualUnmodifiableListView) + return _localTokenTemplates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_localTokenTemplates); + } + +// Base fields end + @override + final String? message; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'TokenContainer.unsynced(serverName: $serverName, lastSyncAt: $lastSyncAt, serial: $serial, description: $description, syncedTokenTemplates: $syncedTokenTemplates, localTokenTemplates: $localTokenTemplates, message: $message)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'TokenContainer.unsynced')) + ..add(DiagnosticsProperty('serverName', serverName)) + ..add(DiagnosticsProperty('lastSyncAt', lastSyncAt)) + ..add(DiagnosticsProperty('serial', serial)) + ..add(DiagnosticsProperty('description', description)) + ..add(DiagnosticsProperty('syncedTokenTemplates', syncedTokenTemplates)) + ..add(DiagnosticsProperty('localTokenTemplates', localTokenTemplates)) + ..add(DiagnosticsProperty('message', message)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TokenContainerUnsyncedImpl && + (identical(other.serverName, serverName) || + other.serverName == serverName) && + (identical(other.lastSyncAt, lastSyncAt) || + other.lastSyncAt == lastSyncAt) && + (identical(other.serial, serial) || other.serial == serial) && + (identical(other.description, description) || + other.description == description) && + const DeepCollectionEquality() + .equals(other._syncedTokenTemplates, _syncedTokenTemplates) && + const DeepCollectionEquality() + .equals(other._localTokenTemplates, _localTokenTemplates) && + (identical(other.message, message) || other.message == message)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + serverName, + lastSyncAt, + serial, + description, + const DeepCollectionEquality().hash(_syncedTokenTemplates), + const DeepCollectionEquality().hash(_localTokenTemplates), + message); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TokenContainerUnsyncedImplCopyWith<_$TokenContainerUnsyncedImpl> + get copyWith => __$$TokenContainerUnsyncedImplCopyWithImpl< + _$TokenContainerUnsyncedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + uninitialized, + required TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + synced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt) + modified, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message) + unsynced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message) + notFound, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error) + error, + }) { + return unsynced(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates, message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult? Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + }) { + return unsynced?.call(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates, message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + required TResult orElse(), + }) { + if (unsynced != null) { + return unsynced(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates, message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(TokenContainerUninitialized value) uninitialized, + required TResult Function(TokenContainerSynced value) synced, + required TResult Function(TokenContainerModified value) modified, + required TResult Function(TokenContainerUnsynced value) unsynced, + required TResult Function(TokenContainerNotFound value) notFound, + required TResult Function(TokenContainerError value) error, + }) { + return unsynced(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(TokenContainerUninitialized value)? uninitialized, + TResult? Function(TokenContainerSynced value)? synced, + TResult? Function(TokenContainerModified value)? modified, + TResult? Function(TokenContainerUnsynced value)? unsynced, + TResult? Function(TokenContainerNotFound value)? notFound, + TResult? Function(TokenContainerError value)? error, + }) { + return unsynced?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(TokenContainerUninitialized value)? uninitialized, + TResult Function(TokenContainerSynced value)? synced, + TResult Function(TokenContainerModified value)? modified, + TResult Function(TokenContainerUnsynced value)? unsynced, + TResult Function(TokenContainerNotFound value)? notFound, + TResult Function(TokenContainerError value)? error, + required TResult orElse(), + }) { + if (unsynced != null) { + return unsynced(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$TokenContainerUnsyncedImplToJson( + this, + ); + } +} + +abstract class TokenContainerUnsynced extends TokenContainer { + const factory TokenContainerUnsynced( + {final String serverName, + final DateTime? lastSyncAt, + required final String serial, + required final String description, + required final List syncedTokenTemplates, + required final List localTokenTemplates, + final String? message}) = _$TokenContainerUnsyncedImpl; + const TokenContainerUnsynced._() : super._(); + + factory TokenContainerUnsynced.fromJson(Map json) = + _$TokenContainerUnsyncedImpl.fromJson; + + @override // Base fields + String get serverName; + @override + DateTime? get lastSyncAt; + @override + String get serial; + @override + String get description; + @override + List get syncedTokenTemplates; + @override + List get localTokenTemplates; // Base fields end + String? get message; + @override + @JsonKey(ignore: true) + _$$TokenContainerUnsyncedImplCopyWith<_$TokenContainerUnsyncedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$TokenContainerNotFoundImplCopyWith<$Res> + implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerNotFoundImplCopyWith( + _$TokenContainerNotFoundImpl value, + $Res Function(_$TokenContainerNotFoundImpl) then) = + __$$TokenContainerNotFoundImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message}); +} + +/// @nodoc +class __$$TokenContainerNotFoundImplCopyWithImpl<$Res> + extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerNotFoundImpl> + implements _$$TokenContainerNotFoundImplCopyWith<$Res> { + __$$TokenContainerNotFoundImplCopyWithImpl( + _$TokenContainerNotFoundImpl _value, + $Res Function(_$TokenContainerNotFoundImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? serverName = null, + Object? lastSyncAt = freezed, + Object? serial = null, + Object? description = null, + Object? syncedTokenTemplates = null, + Object? localTokenTemplates = null, + Object? message = null, + }) { + return _then(_$TokenContainerNotFoundImpl( + serverName: null == serverName + ? _value.serverName + : serverName // ignore: cast_nullable_to_non_nullable + as String, + lastSyncAt: freezed == lastSyncAt + ? _value.lastSyncAt + : lastSyncAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + serial: null == serial + ? _value.serial + : serial // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + syncedTokenTemplates: null == syncedTokenTemplates + ? _value._syncedTokenTemplates + : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + localTokenTemplates: null == localTokenTemplates + ? _value._localTokenTemplates + : localTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + message: null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TokenContainerNotFoundImpl extends TokenContainerNotFound + with DiagnosticableTreeMixin { + const _$TokenContainerNotFoundImpl( + {this.serverName = 'PrivacyIDEA', + this.lastSyncAt, + required this.serial, + required this.description, + required final List syncedTokenTemplates, + required final List localTokenTemplates, + required this.message, + final String? $type}) + : _syncedTokenTemplates = syncedTokenTemplates, + _localTokenTemplates = localTokenTemplates, + $type = $type ?? 'notFound', + super._(); + + factory _$TokenContainerNotFoundImpl.fromJson(Map json) => + _$$TokenContainerNotFoundImplFromJson(json); + +// Base fields + @override + @JsonKey() + final String serverName; + @override + final DateTime? lastSyncAt; + @override + final String serial; + @override + final String description; + final List _syncedTokenTemplates; + @override + List get syncedTokenTemplates { + if (_syncedTokenTemplates is EqualUnmodifiableListView) + return _syncedTokenTemplates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_syncedTokenTemplates); + } + + final List _localTokenTemplates; + @override + List get localTokenTemplates { + if (_localTokenTemplates is EqualUnmodifiableListView) + return _localTokenTemplates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_localTokenTemplates); + } + +// Base fields end + @override + final String message; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'TokenContainer.notFound(serverName: $serverName, lastSyncAt: $lastSyncAt, serial: $serial, description: $description, syncedTokenTemplates: $syncedTokenTemplates, localTokenTemplates: $localTokenTemplates, message: $message)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'TokenContainer.notFound')) + ..add(DiagnosticsProperty('serverName', serverName)) + ..add(DiagnosticsProperty('lastSyncAt', lastSyncAt)) + ..add(DiagnosticsProperty('serial', serial)) + ..add(DiagnosticsProperty('description', description)) + ..add(DiagnosticsProperty('syncedTokenTemplates', syncedTokenTemplates)) + ..add(DiagnosticsProperty('localTokenTemplates', localTokenTemplates)) + ..add(DiagnosticsProperty('message', message)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TokenContainerNotFoundImpl && + (identical(other.serverName, serverName) || + other.serverName == serverName) && + (identical(other.lastSyncAt, lastSyncAt) || + other.lastSyncAt == lastSyncAt) && + (identical(other.serial, serial) || other.serial == serial) && + (identical(other.description, description) || + other.description == description) && + const DeepCollectionEquality() + .equals(other._syncedTokenTemplates, _syncedTokenTemplates) && + const DeepCollectionEquality() + .equals(other._localTokenTemplates, _localTokenTemplates) && + (identical(other.message, message) || other.message == message)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + serverName, + lastSyncAt, + serial, + description, + const DeepCollectionEquality().hash(_syncedTokenTemplates), + const DeepCollectionEquality().hash(_localTokenTemplates), + message); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TokenContainerNotFoundImplCopyWith<_$TokenContainerNotFoundImpl> + get copyWith => __$$TokenContainerNotFoundImplCopyWithImpl< + _$TokenContainerNotFoundImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + uninitialized, + required TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + synced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt) + modified, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message) + unsynced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message) + notFound, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error) + error, + }) { + return notFound(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates, message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult? Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + }) { + return notFound?.call(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates, message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + required TResult orElse(), + }) { + if (notFound != null) { + return notFound(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates, message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(TokenContainerUninitialized value) uninitialized, + required TResult Function(TokenContainerSynced value) synced, + required TResult Function(TokenContainerModified value) modified, + required TResult Function(TokenContainerUnsynced value) unsynced, + required TResult Function(TokenContainerNotFound value) notFound, + required TResult Function(TokenContainerError value) error, + }) { + return notFound(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(TokenContainerUninitialized value)? uninitialized, + TResult? Function(TokenContainerSynced value)? synced, + TResult? Function(TokenContainerModified value)? modified, + TResult? Function(TokenContainerUnsynced value)? unsynced, + TResult? Function(TokenContainerNotFound value)? notFound, + TResult? Function(TokenContainerError value)? error, + }) { + return notFound?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(TokenContainerUninitialized value)? uninitialized, + TResult Function(TokenContainerSynced value)? synced, + TResult Function(TokenContainerModified value)? modified, + TResult Function(TokenContainerUnsynced value)? unsynced, + TResult Function(TokenContainerNotFound value)? notFound, + TResult Function(TokenContainerError value)? error, + required TResult orElse(), + }) { + if (notFound != null) { + return notFound(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$TokenContainerNotFoundImplToJson( + this, + ); + } +} + +abstract class TokenContainerNotFound extends TokenContainer { + const factory TokenContainerNotFound( + {final String serverName, + final DateTime? lastSyncAt, + required final String serial, + required final String description, + required final List syncedTokenTemplates, + required final List localTokenTemplates, + required final String message}) = _$TokenContainerNotFoundImpl; + const TokenContainerNotFound._() : super._(); + + factory TokenContainerNotFound.fromJson(Map json) = + _$TokenContainerNotFoundImpl.fromJson; + + @override // Base fields + String get serverName; + @override + DateTime? get lastSyncAt; + @override + String get serial; + @override + String get description; + @override + List get syncedTokenTemplates; + @override + List get localTokenTemplates; // Base fields end + String get message; + @override + @JsonKey(ignore: true) + _$$TokenContainerNotFoundImplCopyWith<_$TokenContainerNotFoundImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$TokenContainerErrorImplCopyWith<$Res> + implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerErrorImplCopyWith(_$TokenContainerErrorImpl value, + $Res Function(_$TokenContainerErrorImpl) then) = + __$$TokenContainerErrorImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error}); +} + +/// @nodoc +class __$$TokenContainerErrorImplCopyWithImpl<$Res> + extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerErrorImpl> + implements _$$TokenContainerErrorImplCopyWith<$Res> { + __$$TokenContainerErrorImplCopyWithImpl(_$TokenContainerErrorImpl _value, + $Res Function(_$TokenContainerErrorImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? serverName = null, + Object? lastSyncAt = freezed, + Object? serial = null, + Object? description = null, + Object? syncedTokenTemplates = null, + Object? localTokenTemplates = null, + Object? error = freezed, + }) { + return _then(_$TokenContainerErrorImpl( + serverName: null == serverName + ? _value.serverName + : serverName // ignore: cast_nullable_to_non_nullable + as String, + lastSyncAt: freezed == lastSyncAt + ? _value.lastSyncAt + : lastSyncAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + serial: null == serial + ? _value.serial + : serial // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + syncedTokenTemplates: null == syncedTokenTemplates + ? _value._syncedTokenTemplates + : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + localTokenTemplates: null == localTokenTemplates + ? _value._localTokenTemplates + : localTokenTemplates // ignore: cast_nullable_to_non_nullable + as List, + error: freezed == error + ? _value.error + : error // ignore: cast_nullable_to_non_nullable + as dynamic, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TokenContainerErrorImpl extends TokenContainerError + with DiagnosticableTreeMixin { + const _$TokenContainerErrorImpl( + {this.serverName = 'PrivacyIDEA', + this.lastSyncAt = null, + this.serial = 'none', + this.description = 'Error', + final List syncedTokenTemplates = const [], + final List localTokenTemplates = const [], + required this.error, + final String? $type}) + : _syncedTokenTemplates = syncedTokenTemplates, + _localTokenTemplates = localTokenTemplates, + $type = $type ?? 'error', + super._(); + + factory _$TokenContainerErrorImpl.fromJson(Map json) => + _$$TokenContainerErrorImplFromJson(json); + +// Base fields + @override + @JsonKey() + final String serverName; + @override + @JsonKey() + final DateTime? lastSyncAt; + @override + @JsonKey() + final String serial; + @override + @JsonKey() + final String description; + final List _syncedTokenTemplates; + @override + @JsonKey() + List get syncedTokenTemplates { + if (_syncedTokenTemplates is EqualUnmodifiableListView) + return _syncedTokenTemplates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_syncedTokenTemplates); + } + + final List _localTokenTemplates; + @override + @JsonKey() + List get localTokenTemplates { + if (_localTokenTemplates is EqualUnmodifiableListView) + return _localTokenTemplates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_localTokenTemplates); + } + +// Base fields end + @override + final dynamic error; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'TokenContainer.error(serverName: $serverName, lastSyncAt: $lastSyncAt, serial: $serial, description: $description, syncedTokenTemplates: $syncedTokenTemplates, localTokenTemplates: $localTokenTemplates, error: $error)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'TokenContainer.error')) + ..add(DiagnosticsProperty('serverName', serverName)) + ..add(DiagnosticsProperty('lastSyncAt', lastSyncAt)) + ..add(DiagnosticsProperty('serial', serial)) + ..add(DiagnosticsProperty('description', description)) + ..add(DiagnosticsProperty('syncedTokenTemplates', syncedTokenTemplates)) + ..add(DiagnosticsProperty('localTokenTemplates', localTokenTemplates)) + ..add(DiagnosticsProperty('error', error)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TokenContainerErrorImpl && + (identical(other.serverName, serverName) || + other.serverName == serverName) && + (identical(other.lastSyncAt, lastSyncAt) || + other.lastSyncAt == lastSyncAt) && + (identical(other.serial, serial) || other.serial == serial) && + (identical(other.description, description) || + other.description == description) && + const DeepCollectionEquality() + .equals(other._syncedTokenTemplates, _syncedTokenTemplates) && + const DeepCollectionEquality() + .equals(other._localTokenTemplates, _localTokenTemplates) && + const DeepCollectionEquality().equals(other.error, error)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + serverName, + lastSyncAt, + serial, + description, + const DeepCollectionEquality().hash(_syncedTokenTemplates), + const DeepCollectionEquality().hash(_localTokenTemplates), + const DeepCollectionEquality().hash(error)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TokenContainerErrorImplCopyWith<_$TokenContainerErrorImpl> get copyWith => + __$$TokenContainerErrorImplCopyWithImpl<_$TokenContainerErrorImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + uninitialized, + required TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates) + synced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt) + modified, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message) + unsynced, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message) + notFound, + required TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error) + error, + }) { + return error(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates, this.error); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult? Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult? Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + }) { + return error?.call(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates, this.error); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + uninitialized, + TResult Function( + String serverName, + DateTime lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates)? + synced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + DateTime lastModifiedAt)? + modified, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String? message)? + unsynced, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + String message)? + notFound, + TResult Function( + String serverName, + DateTime? lastSyncAt, + String serial, + String description, + List syncedTokenTemplates, + List localTokenTemplates, + dynamic error)? + error, + required TResult orElse(), + }) { + if (error != null) { + return error(serverName, lastSyncAt, serial, description, + syncedTokenTemplates, localTokenTemplates, this.error); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(TokenContainerUninitialized value) uninitialized, + required TResult Function(TokenContainerSynced value) synced, + required TResult Function(TokenContainerModified value) modified, + required TResult Function(TokenContainerUnsynced value) unsynced, + required TResult Function(TokenContainerNotFound value) notFound, + required TResult Function(TokenContainerError value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(TokenContainerUninitialized value)? uninitialized, + TResult? Function(TokenContainerSynced value)? synced, + TResult? Function(TokenContainerModified value)? modified, + TResult? Function(TokenContainerUnsynced value)? unsynced, + TResult? Function(TokenContainerNotFound value)? notFound, + TResult? Function(TokenContainerError value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(TokenContainerUninitialized value)? uninitialized, + TResult Function(TokenContainerSynced value)? synced, + TResult Function(TokenContainerModified value)? modified, + TResult Function(TokenContainerUnsynced value)? unsynced, + TResult Function(TokenContainerNotFound value)? notFound, + TResult Function(TokenContainerError value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$TokenContainerErrorImplToJson( + this, + ); + } +} + +abstract class TokenContainerError extends TokenContainer { + const factory TokenContainerError( + {final String serverName, + final DateTime? lastSyncAt, + final String serial, + final String description, + final List syncedTokenTemplates, + final List localTokenTemplates, + required final dynamic error}) = _$TokenContainerErrorImpl; + const TokenContainerError._() : super._(); + + factory TokenContainerError.fromJson(Map json) = + _$TokenContainerErrorImpl.fromJson; + + @override // Base fields + String get serverName; + @override + DateTime? get lastSyncAt; + @override + String get serial; + @override + String get description; + @override + List get syncedTokenTemplates; + @override + List get localTokenTemplates; // Base fields end + dynamic get error; + @override + @JsonKey(ignore: true) + _$$TokenContainerErrorImplCopyWith<_$TokenContainerErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +TokenTemplate _$TokenTemplateFromJson(Map json) { + return _TokenTemplate.fromJson(json); +} + +/// @nodoc +mixin _$TokenTemplate { + Map get data => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $TokenTemplateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TokenTemplateCopyWith<$Res> { + factory $TokenTemplateCopyWith( + TokenTemplate value, $Res Function(TokenTemplate) then) = + _$TokenTemplateCopyWithImpl<$Res, TokenTemplate>; + @useResult + $Res call({Map data}); +} + +/// @nodoc +class _$TokenTemplateCopyWithImpl<$Res, $Val extends TokenTemplate> + implements $TokenTemplateCopyWith<$Res> { + _$TokenTemplateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? data = null, + }) { + return _then(_value.copyWith( + data: null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TokenTemplateImplCopyWith<$Res> + implements $TokenTemplateCopyWith<$Res> { + factory _$$TokenTemplateImplCopyWith( + _$TokenTemplateImpl value, $Res Function(_$TokenTemplateImpl) then) = + __$$TokenTemplateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Map data}); +} + +/// @nodoc +class __$$TokenTemplateImplCopyWithImpl<$Res> + extends _$TokenTemplateCopyWithImpl<$Res, _$TokenTemplateImpl> + implements _$$TokenTemplateImplCopyWith<$Res> { + __$$TokenTemplateImplCopyWithImpl( + _$TokenTemplateImpl _value, $Res Function(_$TokenTemplateImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? data = null, + }) { + return _then(_$TokenTemplateImpl( + data: null == data + ? _value._data + : data // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TokenTemplateImpl extends _TokenTemplate with DiagnosticableTreeMixin { + _$TokenTemplateImpl({required final Map data}) + : _data = data, + super._(); + + factory _$TokenTemplateImpl.fromJson(Map json) => + _$$TokenTemplateImplFromJson(json); + + final Map _data; + @override + Map get data { + if (_data is EqualUnmodifiableMapView) return _data; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_data); + } + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'TokenTemplate(data: $data)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'TokenTemplate')) + ..add(DiagnosticsProperty('data', data)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TokenTemplateImpl && + const DeepCollectionEquality().equals(other._data, _data)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, const DeepCollectionEquality().hash(_data)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TokenTemplateImplCopyWith<_$TokenTemplateImpl> get copyWith => + __$$TokenTemplateImplCopyWithImpl<_$TokenTemplateImpl>(this, _$identity); + + @override + Map toJson() { + return _$$TokenTemplateImplToJson( + this, + ); + } +} + +abstract class _TokenTemplate extends TokenTemplate { + factory _TokenTemplate({required final Map data}) = + _$TokenTemplateImpl; + _TokenTemplate._() : super._(); + + factory _TokenTemplate.fromJson(Map json) = + _$TokenTemplateImpl.fromJson; + + @override + Map get data; + @override + @JsonKey(ignore: true) + _$$TokenTemplateImplCopyWith<_$TokenTemplateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/model/token_container.g.dart b/lib/model/token_container.g.dart index 0be44a752..659d300a1 100644 --- a/lib/model/token_container.g.dart +++ b/lib/model/token_container.g.dart @@ -6,34 +6,202 @@ part of 'token_container.dart'; // JsonSerializableGenerator // ************************************************************************** -TokenContainer _$TokenContainerFromJson(Map json) => - TokenContainer( +_$TokenContainerUninitializedImpl _$$TokenContainerUninitializedImplFromJson( + Map json) => + _$TokenContainerUninitializedImpl( + serverName: json['serverName'] as String? ?? 'PrivacyIDEA', + lastSyncAt: json['lastSyncAt'] == null + ? null + : DateTime.parse(json['lastSyncAt'] as String), + serial: json['serial'] as String? ?? 'none', + description: json['description'] as String? ?? 'Uninitialized', + syncedTokenTemplates: (json['syncedTokenTemplates'] as List?) + ?.map((e) => TokenTemplate.fromJson(e as Map)) + .toList() ?? + const [], + localTokenTemplates: (json['localTokenTemplates'] as List?) + ?.map((e) => TokenTemplate.fromJson(e as Map)) + .toList() ?? + const [], + $type: json['runtimeType'] as String?, + ); + +Map _$$TokenContainerUninitializedImplToJson( + _$TokenContainerUninitializedImpl instance) => + { + 'serverName': instance.serverName, + 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), + 'serial': instance.serial, + 'description': instance.description, + 'syncedTokenTemplates': instance.syncedTokenTemplates, + 'localTokenTemplates': instance.localTokenTemplates, + 'runtimeType': instance.$type, + }; + +_$TokenContainerSyncedImpl _$$TokenContainerSyncedImplFromJson( + Map json) => + _$TokenContainerSyncedImpl( + serverName: json['serverName'] as String? ?? 'PrivacyIDEA', + lastSyncAt: DateTime.parse(json['lastSyncAt'] as String), serial: json['serial'] as String, description: json['description'] as String, - type: json['type'] as String, syncedTokenTemplates: (json['syncedTokenTemplates'] as List) .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), localTokenTemplates: (json['localTokenTemplates'] as List) .map((e) => TokenTemplate.fromJson(e as Map)) .toList(), + $type: json['runtimeType'] as String?, + ); + +Map _$$TokenContainerSyncedImplToJson( + _$TokenContainerSyncedImpl instance) => + { + 'serverName': instance.serverName, + 'lastSyncAt': instance.lastSyncAt.toIso8601String(), + 'serial': instance.serial, + 'description': instance.description, + 'syncedTokenTemplates': instance.syncedTokenTemplates, + 'localTokenTemplates': instance.localTokenTemplates, + 'runtimeType': instance.$type, + }; + +_$TokenContainerModifiedImpl _$$TokenContainerModifiedImplFromJson( + Map json) => + _$TokenContainerModifiedImpl( + serverName: json['serverName'] as String? ?? 'PrivacyIDEA', + lastSyncAt: json['lastSyncAt'] == null + ? null + : DateTime.parse(json['lastSyncAt'] as String), + serial: json['serial'] as String, + description: json['description'] as String, + syncedTokenTemplates: (json['syncedTokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) + .toList(), + localTokenTemplates: (json['localTokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) + .toList(), + lastModifiedAt: DateTime.parse(json['lastModifiedAt'] as String), + $type: json['runtimeType'] as String?, + ); + +Map _$$TokenContainerModifiedImplToJson( + _$TokenContainerModifiedImpl instance) => + { + 'serverName': instance.serverName, + 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), + 'serial': instance.serial, + 'description': instance.description, + 'syncedTokenTemplates': instance.syncedTokenTemplates, + 'localTokenTemplates': instance.localTokenTemplates, + 'lastModifiedAt': instance.lastModifiedAt.toIso8601String(), + 'runtimeType': instance.$type, + }; + +_$TokenContainerUnsyncedImpl _$$TokenContainerUnsyncedImplFromJson( + Map json) => + _$TokenContainerUnsyncedImpl( + serverName: json['serverName'] as String? ?? 'PrivacyIDEA', + lastSyncAt: json['lastSyncAt'] == null + ? null + : DateTime.parse(json['lastSyncAt'] as String), + serial: json['serial'] as String, + description: json['description'] as String, + syncedTokenTemplates: (json['syncedTokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) + .toList(), + localTokenTemplates: (json['localTokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) + .toList(), + message: json['message'] as String?, + $type: json['runtimeType'] as String?, + ); + +Map _$$TokenContainerUnsyncedImplToJson( + _$TokenContainerUnsyncedImpl instance) => + { + 'serverName': instance.serverName, + 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), + 'serial': instance.serial, + 'description': instance.description, + 'syncedTokenTemplates': instance.syncedTokenTemplates, + 'localTokenTemplates': instance.localTokenTemplates, + 'message': instance.message, + 'runtimeType': instance.$type, + }; + +_$TokenContainerNotFoundImpl _$$TokenContainerNotFoundImplFromJson( + Map json) => + _$TokenContainerNotFoundImpl( + serverName: json['serverName'] as String? ?? 'PrivacyIDEA', + lastSyncAt: json['lastSyncAt'] == null + ? null + : DateTime.parse(json['lastSyncAt'] as String), + serial: json['serial'] as String, + description: json['description'] as String, + syncedTokenTemplates: (json['syncedTokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) + .toList(), + localTokenTemplates: (json['localTokenTemplates'] as List) + .map((e) => TokenTemplate.fromJson(e as Map)) + .toList(), + message: json['message'] as String, + $type: json['runtimeType'] as String?, + ); + +Map _$$TokenContainerNotFoundImplToJson( + _$TokenContainerNotFoundImpl instance) => + { + 'serverName': instance.serverName, + 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), + 'serial': instance.serial, + 'description': instance.description, + 'syncedTokenTemplates': instance.syncedTokenTemplates, + 'localTokenTemplates': instance.localTokenTemplates, + 'message': instance.message, + 'runtimeType': instance.$type, + }; + +_$TokenContainerErrorImpl _$$TokenContainerErrorImplFromJson( + Map json) => + _$TokenContainerErrorImpl( + serverName: json['serverName'] as String? ?? 'PrivacyIDEA', + lastSyncAt: json['lastSyncAt'] == null + ? null + : DateTime.parse(json['lastSyncAt'] as String), + serial: json['serial'] as String? ?? 'none', + description: json['description'] as String? ?? 'Error', + syncedTokenTemplates: (json['syncedTokenTemplates'] as List?) + ?.map((e) => TokenTemplate.fromJson(e as Map)) + .toList() ?? + const [], + localTokenTemplates: (json['localTokenTemplates'] as List?) + ?.map((e) => TokenTemplate.fromJson(e as Map)) + .toList() ?? + const [], + error: json['error'], + $type: json['runtimeType'] as String?, ); -Map _$TokenContainerToJson(TokenContainer instance) => +Map _$$TokenContainerErrorImplToJson( + _$TokenContainerErrorImpl instance) => { + 'serverName': instance.serverName, + 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), 'serial': instance.serial, 'description': instance.description, - 'type': instance.type, 'syncedTokenTemplates': instance.syncedTokenTemplates, 'localTokenTemplates': instance.localTokenTemplates, + 'error': instance.error, + 'runtimeType': instance.$type, }; -TokenTemplate _$TokenTemplateFromJson(Map json) => - TokenTemplate( +_$TokenTemplateImpl _$$TokenTemplateImplFromJson(Map json) => + _$TokenTemplateImpl( data: json['data'] as Map, ); -Map _$TokenTemplateToJson(TokenTemplate instance) => +Map _$$TokenTemplateImplToJson(_$TokenTemplateImpl instance) => { 'data': instance.data, }; diff --git a/lib/model/tokens/container_credentials.dart b/lib/model/tokens/container_credentials.dart index ec51e2421..cdc2809a7 100644 --- a/lib/model/tokens/container_credentials.dart +++ b/lib/model/tokens/container_credentials.dart @@ -4,11 +4,15 @@ import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; part 'container_credentials.g.dart'; @JsonSerializable() -class ContainerCredentials extends PushToken { - ContainerCredentials({required super.serial, required super.id}); +class ContainerCredential extends PushToken { + ContainerCredential({required super.serial, required super.id}); - factory ContainerCredentials.fromUriMap(Map uriMap) => throw UnimplementedError(); // PushToken.fromUriMap(uriMap); + factory ContainerCredential.fromUriMap(Map uriMap) => throw UnimplementedError(); // PushToken.fromUriMap(uriMap); - factory ContainerCredentials.fromJson(Map json) => _$ContainerCredentialsFromJson(json); - Map toJson() => _$ContainerCredentialsToJson(this); + factory ContainerCredential.fromJson(Map json) => _$ContainerCredentialFromJson(json); + @override + Map toJson() => _$ContainerCredentialToJson(this); + + @override + String toString() => 'ContainerCredential{serial: $serial, id: $id}'; } diff --git a/lib/model/tokens/container_credentials.g.dart b/lib/model/tokens/container_credentials.g.dart index 40f015ec4..25b4f76c1 100644 --- a/lib/model/tokens/container_credentials.g.dart +++ b/lib/model/tokens/container_credentials.g.dart @@ -6,15 +6,14 @@ part of 'container_credentials.dart'; // JsonSerializableGenerator // ************************************************************************** -ContainerCredentials _$ContainerCredentialsFromJson( - Map json) => - ContainerCredentials( +ContainerCredential _$ContainerCredentialFromJson(Map json) => + ContainerCredential( serial: json['serial'] as String, id: json['id'] as String, ); -Map _$ContainerCredentialsToJson( - ContainerCredentials instance) => +Map _$ContainerCredentialToJson( + ContainerCredential instance) => { 'id': instance.id, 'serial': instance.serial, diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index 4e1f5e3dd..a8e289a0e 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -30,6 +30,7 @@ class DayPasswordToken extends OTPToken { required super.algorithm, required super.digits, required super.secret, + super.containerId, super.serial, this.viewMode = DayPasswordTokenViewMode.VALIDFOR, String? type, // just for @JsonSerializable(): type of DayPasswordToken is always TokenTypes.DAYPASSWORD @@ -72,6 +73,7 @@ class DayPasswordToken extends OTPToken { DayPasswordTokenViewMode? viewMode, String? label, String? issuer, + String? Function()? containerId, String? id, Algorithms? algorithm, int? digits, @@ -90,6 +92,7 @@ class DayPasswordToken extends OTPToken { viewMode: viewMode ?? this.viewMode, label: label ?? this.label, issuer: issuer ?? this.issuer, + containerId: containerId != null ? containerId() : this.containerId, id: id ?? this.id, type: TokenTypes.DAYPASSWORD.name, algorithm: algorithm ?? this.algorithm, diff --git a/lib/model/tokens/day_password_token.g.dart b/lib/model/tokens/day_password_token.g.dart index 5011056e3..e2eb0838a 100644 --- a/lib/model/tokens/day_password_token.g.dart +++ b/lib/model/tokens/day_password_token.g.dart @@ -13,6 +13,7 @@ DayPasswordToken _$DayPasswordTokenFromJson(Map json) => algorithm: $enumDecode(_$AlgorithmsEnumMap, json['algorithm']), digits: (json['digits'] as num).toInt(), secret: json['secret'] as String, + containerId: json['containerId'] as String?, serial: json['serial'] as String?, viewMode: $enumDecodeNullable( _$DayPasswordTokenViewModeEnumMap, json['viewMode']) ?? @@ -33,6 +34,7 @@ DayPasswordToken _$DayPasswordTokenFromJson(Map json) => Map _$DayPasswordTokenToJson(DayPasswordToken instance) => { + 'containerId': instance.containerId, 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index b437732cc..af908f689 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -25,6 +25,7 @@ class HOTPToken extends OTPToken { HOTPToken({ this.counter = 0, + super.containerId, required super.id, required super.algorithm, required super.digits, @@ -65,6 +66,7 @@ class HOTPToken extends OTPToken { int? counter, String? label, String? issuer, + String? Function()? containerId, String? id, Algorithms? algorithm, int? digits, @@ -82,6 +84,7 @@ class HOTPToken extends OTPToken { counter: counter ?? this.counter, label: label ?? this.label, issuer: issuer ?? this.issuer, + containerId: containerId != null ? containerId() : this.containerId, id: id ?? this.id, algorithm: algorithm ?? this.algorithm, digits: digits ?? this.digits, diff --git a/lib/model/tokens/hotp_token.g.dart b/lib/model/tokens/hotp_token.g.dart index e6c23a3a6..1de25d45f 100644 --- a/lib/model/tokens/hotp_token.g.dart +++ b/lib/model/tokens/hotp_token.g.dart @@ -8,6 +8,7 @@ part of 'hotp_token.dart'; HOTPToken _$HOTPTokenFromJson(Map json) => HOTPToken( counter: (json['counter'] as num?)?.toInt() ?? 0, + containerId: json['containerId'] as String?, id: json['id'] as String, algorithm: $enumDecode(_$AlgorithmsEnumMap, json['algorithm']), digits: (json['digits'] as num).toInt(), @@ -28,6 +29,7 @@ HOTPToken _$HOTPTokenFromJson(Map json) => HOTPToken( ); Map _$HOTPTokenToJson(HOTPToken instance) => { + 'containerId': instance.containerId, 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index 257ac44c2..bcec6afcf 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -24,6 +24,7 @@ abstract class OTPToken extends Token { required this.secret, required super.id, required super.type, + super.containerId, super.serial, super.pin, super.tokenImage, @@ -50,6 +51,7 @@ abstract class OTPToken extends Token { String? serial, String? label, String? issuer, + String? Function()? containerId, String? id, Algorithms? algorithm, int? digits, diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index 1d60f5307..b5cf38363 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -17,7 +17,8 @@ part 'push_token.g.dart'; class PushToken extends Token { static RsaUtils rsaParser = const RsaUtils(); final DateTime? expirationDate; - final String serial; + @override + String get serial => super.serial!; final String? fbToken; @override @@ -44,9 +45,10 @@ class PushToken extends Token { PushToken withPrivateTokenKey(RSAPrivateKey key) => copyWith(privateTokenKey: rsaParser.serializeRSAPrivateKeyPKCS1(key)); PushToken({ - required this.serial, + required String serial, super.label, super.issuer, + super.containerId, required super.id, this.fbToken, this.url, @@ -69,7 +71,7 @@ class PushToken extends Token { }) : isRolledOut = isRolledOut ?? false, sslVerify = sslVerify ?? false, rolloutState = rolloutState ?? PushTokenRollOutState.rolloutNotStarted, - super(type: TokenTypes.PIPUSH.name); + super(type: TokenTypes.PIPUSH.name, serial: serial); @override bool sameValuesAs(Token other) { @@ -98,6 +100,7 @@ class PushToken extends Token { String? label, String? serial, String? issuer, + String? Function()? containerId, String? id, String? tokenImage, String? fbToken, @@ -124,6 +127,7 @@ class PushToken extends Token { issuer: issuer ?? this.issuer, tokenImage: tokenImage ?? this.tokenImage, fbToken: fbToken ?? this.fbToken, + containerId: containerId != null ? containerId() : this.containerId, id: id ?? this.id, pin: pin ?? this.pin, isLocked: isLocked ?? this.isLocked, diff --git a/lib/model/tokens/push_token.g.dart b/lib/model/tokens/push_token.g.dart index a651a9c71..fbd52d4a4 100644 --- a/lib/model/tokens/push_token.g.dart +++ b/lib/model/tokens/push_token.g.dart @@ -10,6 +10,7 @@ PushToken _$PushTokenFromJson(Map json) => PushToken( serial: json['serial'] as String, label: json['label'] as String? ?? '', issuer: json['issuer'] as String? ?? '', + containerId: json['containerId'] as String?, id: json['id'] as String, fbToken: json['fbToken'] as String?, url: json['url'] == null ? null : Uri.parse(json['url'] as String), @@ -37,6 +38,7 @@ PushToken _$PushTokenFromJson(Map json) => PushToken( ); Map _$PushTokenToJson(PushToken instance) => { + 'containerId': instance.containerId, 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, diff --git a/lib/model/tokens/steam_token.dart b/lib/model/tokens/steam_token.dart index 5c8603566..db1f5bafc 100644 --- a/lib/model/tokens/steam_token.dart +++ b/lib/model/tokens/steam_token.dart @@ -26,6 +26,7 @@ class SteamToken extends TOTPToken { SteamToken({ required super.id, required super.secret, + super.containerId, super.serial, String? type, super.tokenImage, @@ -49,6 +50,7 @@ class SteamToken extends TOTPToken { String? serial, String? label, String? issuer, + String? Function()? containerId, String? id, bool? isLocked, bool? isHidden, @@ -66,6 +68,7 @@ class SteamToken extends TOTPToken { serial: serial ?? this.serial, label: label ?? this.label, issuer: issuer ?? this.issuer, + containerId: containerId != null ? containerId() : this.containerId, id: id ?? this.id, secret: secret ?? this.secret, tokenImage: tokenImage ?? this.tokenImage, diff --git a/lib/model/tokens/steam_token.g.dart b/lib/model/tokens/steam_token.g.dart index e5932abae..6b539159f 100644 --- a/lib/model/tokens/steam_token.g.dart +++ b/lib/model/tokens/steam_token.g.dart @@ -9,6 +9,7 @@ part of 'steam_token.dart'; SteamToken _$SteamTokenFromJson(Map json) => SteamToken( id: json['id'] as String, secret: json['secret'] as String, + containerId: json['containerId'] as String?, serial: json['serial'] as String?, type: json['type'] as String?, tokenImage: json['tokenImage'] as String?, @@ -26,6 +27,7 @@ SteamToken _$SteamTokenFromJson(Map json) => SteamToken( Map _$SteamTokenToJson(SteamToken instance) => { + 'containerId': instance.containerId, 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index cfc9612c4..067c3e0c3 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -91,6 +91,7 @@ abstract class Token with SortableMixin { String? serial, String? label, String? issuer, + String? Function()? containerId, String? id, bool? isLocked, bool? isHidden, diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index 73dc6f893..5bc3e4a64 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -48,6 +48,7 @@ class TOTPToken extends OTPToken { required super.algorithm, required super.digits, required super.secret, + super.containerId, super.serial, String? type, super.tokenImage, @@ -74,6 +75,7 @@ class TOTPToken extends OTPToken { String? serial, String? label, String? issuer, + String? Function()? containerId, String? id, Algorithms? algorithm, int? digits, @@ -91,6 +93,7 @@ class TOTPToken extends OTPToken { serial: serial ?? this.serial, label: label ?? this.label, issuer: issuer ?? this.issuer, + containerId: containerId != null ? containerId() : this.containerId, id: id ?? this.id, algorithm: algorithm ?? this.algorithm, digits: digits ?? this.digits, diff --git a/lib/model/tokens/totp_token.g.dart b/lib/model/tokens/totp_token.g.dart index 941c8ce74..066f77b1d 100644 --- a/lib/model/tokens/totp_token.g.dart +++ b/lib/model/tokens/totp_token.g.dart @@ -12,6 +12,7 @@ TOTPToken _$TOTPTokenFromJson(Map json) => TOTPToken( algorithm: $enumDecode(_$AlgorithmsEnumMap, json['algorithm']), digits: (json['digits'] as num).toInt(), secret: json['secret'] as String, + containerId: json['containerId'] as String?, serial: json['serial'] as String?, type: json['type'] as String?, tokenImage: json['tokenImage'] as String?, @@ -28,6 +29,7 @@ TOTPToken _$TOTPTokenFromJson(Map json) => TOTPToken( ); Map _$TOTPTokenToJson(TOTPToken instance) => { + 'containerId': instance.containerId, 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, diff --git a/lib/processors/scheme_processors/container_credentials_processor.dart b/lib/processors/scheme_processors/container_credentials_processor.dart index 6ec5db44e..62dc5a81f 100644 --- a/lib/processors/scheme_processors/container_credentials_processor.dart +++ b/lib/processors/scheme_processors/container_credentials_processor.dart @@ -15,7 +15,7 @@ class ContainerCredentialsProcessor extends SchemeProcessor { if (!supportedHosts.contains(uri.host) || !supportedSchemes.contains(uri.scheme)) { return null; } - final credentials = ContainerCredentials.fromUriMap(uri.queryParameters); - globalRef?.read(credentialsProvider.notifier).setCredentials(credentials); + final credential = ContainerCredential.fromUriMap(uri.queryParameters); + globalRef?.read(credentialsProvider.notifier).addCredential(credential); } } diff --git a/lib/repo/secure_push_request_repository.dart b/lib/repo/secure_push_request_repository.dart index 3e848f750..ee3817550 100644 --- a/lib/repo/secure_push_request_repository.dart +++ b/lib/repo/secure_push_request_repository.dart @@ -42,7 +42,6 @@ class SecurePushRequestRepository implements PushRequestRepository { Future loadState() => protect(_loadState); Future _loadState() async { final String? stateJson = await _storage.read(key: _securePushRequestKey); - print('Loaded state: $stateJson'); if (stateJson == null) { return PushRequestState(pushRequests: [], knownPushRequests: CustomIntBuffer(list: [])); } diff --git a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart index 3c24338f9..3a18918a5 100644 --- a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart @@ -1,65 +1,68 @@ import 'package:privacyidea_authenticator/utils/errors.dart'; import '../../interfaces/repo/container_repository.dart'; -import '../../model/states/token_container_state.dart'; +import '../../model/token_container.dart'; import '../../utils/logger.dart'; -class HybridTokenContainerStateRepository - implements TokenContainerStateRepository { +class HybridTokenContainerRepository + implements TokenContainerRepository { final LocalRepo _localRepository; final RemoteRepo _remoteRepository; - /// - HybridTokenContainerStateRepository({ + + HybridTokenContainerRepository({ required LocalRepo localRepository, required RemoteRepo remoteRepository, }) : _localRepository = localRepository, _remoteRepository = remoteRepository; @override - Future loadContainerState({bool isInitial = false}) async { - TokenContainerState localState; - TokenContainerState newState; + Future loadContainerState({bool isInitial = false}) async { + Logger.warning('Loading container state', name: 'HybridTokenContainerRepository'); + TokenContainer localState; + TokenContainer newState; try { localState = await _localRepository.loadContainerState(); } catch (e) { - return TokenContainerStateError( + Logger.warning('Failed to load local container state'); + return TokenContainer.error( error: LocalizedException( unlocalizedMessage: 'Failed to load local container state', localizedMessage: (localization) => localization.failedToLoad('local container state'), ), + ); } try { newState = await _remoteRepository.saveContainerState(localState); } catch (e) { - newState = localState.copyTransformInto(); + newState = localState.copyTransformInto(); } try { await _localRepository.saveContainerState(newState); } catch (e) { - Logger.error('Failed to save synced state to local repository'); + Logger.error('Failed to save synced state to local repository', name: 'HybridTokenContainerRepository', error: e); } return newState; } @override - Future saveContainerState(TokenContainerState currentState) async { - if (currentState is TokenContainerStateError) { + Future saveContainerState(TokenContainer currentState) async { + if (currentState is TokenContainerError) { Logger.warning('Cannot save error state to repository'); return currentState; } - TokenContainerState newState; + TokenContainer newState; try { newState = await _remoteRepository.saveContainerState(currentState); } catch (e) { Logger.warning('Failed to save state to remote repository: Changed to unsynced state'); - newState = currentState.copyTransformInto(); + newState = currentState.copyTransformInto(); return _localRepository.saveContainerState(newState); } @@ -72,19 +75,19 @@ class HybridTokenContainerStateRepository _merge({ - // required TokenContainerState localState, - // required TokenContainerState remoteState, + // Future _merge({ + // required TokenContainer localState, + // required TokenContainer remoteState, // }) async { // List localTemplates; // List remoteTemplates; - // if (localState is TokenContainerStateUninitialized) { + // if (localState is TokenContainerUninitialized) { // // Uninitialized state is always overwritten by other states // localTemplates = []; // } else { // localTemplates = localState.tokenTemplates; // } - // if (remoteState is TokenContainerStateUninitialized) { + // if (remoteState is TokenContainerUninitialized) { // // Uninitialized state is always overwritten by other states // remoteTemplates = []; // } else { @@ -96,7 +99,7 @@ class HybridTokenContainerStateRepository _protect(Future Function() f) async => _m.protect(f); + Future _protect(Future Function() f) async => _m.protect(f); - RemoteTokenContainerStateRepository({required this.apiEndpoint}); + RemoteTokenContainerRepository({required this.apiEndpoint}); @override - Future saveContainerState(TokenContainerState containerState) async => await _saveContainerState(containerState); + Future saveContainerState(TokenContainer containerState) async => await _saveContainerState(containerState); - Future _saveContainerState(TokenContainerState containerState) async { + Future _saveContainerState(TokenContainer containerState) async { + Logger.warning('Saving container state', name: 'RemoteTokenContainerRepository'); try { return await _protect(() async { var synced = await apiEndpoint.sync(containerState); @@ -27,9 +29,12 @@ class RemoteTokenContainerStateRepository implements TokenContainerStateReposito } @override - Future loadContainerState() => _fetchContainerState(); + Future loadContainerState() { + Logger.warning('Loading container state', name: 'RemoteTokenContainerRepository'); + return _fetchContainerState(); + } - Future _fetchContainerState() async => await _protect(() async => await apiEndpoint.fetch()); + Future _fetchContainerState() async => await _protect(() async => await apiEndpoint.fetch()); // @override // Future loadTokenTemplate(String tokenTemplateId) async { diff --git a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart index ffb5abc7a..8188c9459 100644 --- a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart +++ b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart @@ -4,26 +4,27 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:mutex/mutex.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import '../../interfaces/repo/container_repository.dart'; -import '../../model/states/token_container_state.dart'; +import '../../model/token_container.dart'; -class SecureTokenContainerStateRepository implements TokenContainerStateRepository { +class SecureTokenContainerRepository implements TokenContainerRepository { static String prefix = 'token_container_state_'; String get _containerStateKey => '$prefix${containerId}_container_state'; final Mutex _m = Mutex(); - Future _protect(Future Function() f) => _m.protect(f); + Future _protect(Future Function() f) => _m.protect(f); final FlutterSecureStorage _storage = const FlutterSecureStorage(); final String containerId; - SecureTokenContainerStateRepository({ + SecureTokenContainerRepository({ required this.containerId, }); - Future _write(String key, String value) => _protect(() => _storage.write(key: key, value: value)); + Future _write(String key, String value) => _protect(() { + Logger.warning('Writing to secure storage: $key, $value', name: 'SecureTokenContainerRepository'); + return _storage.write(key: key, value: value); + }); Future _read(String key) async { - String? value; - await _protect(() async => value = await _storage.read(key: key)); - return value; + return await _protect(() async => await _storage.read(key: key)); } Future _delete(String key) => _protect(() => _storage.delete(key: key)); @@ -35,39 +36,43 @@ class SecureTokenContainerStateRepository implements TokenContainerStateReposito } @override - Future saveContainerState(TokenContainerState containerState) async { - await _write(_containerStateKey, jsonEncode(containerState)); + Future saveContainerState(TokenContainer containerState) async { + if (TokenContainer is TokenContainerError) { + Logger.error('Cannot save error state to repository', name: 'SecureTokenContainerRepository'); + return containerState; + } + Logger.warning('Saving container state', name: 'SecureTokenContainerRepository'); + final json = containerState.toJson(); + Logger.warning('Encoded container state: $json', name: 'SecureTokenContainerRepository'); + final jsonString = jsonEncode(json); + Logger.warning('Encoded container state string: $jsonString', name: 'SecureTokenContainerRepository'); + await _write(_containerStateKey, jsonString); + Logger.warning('Saved container state: $jsonString', name: 'SecureTokenContainerRepository'); return containerState; } @override /// Load the container state from the shared preferences - Future loadContainerState() async { + Future loadContainerState() async { + Logger.warning('Loading container state', name: 'SecureTokenContainerRepository'); String? containerStateJsonString = await _read(_containerStateKey); + Logger.warning('Loaded container state: $containerStateJsonString', name: 'SecureTokenContainerRepository'); if (containerStateJsonString == null) { - Logger.info('No container state found in secure storage', name: 'SecureTokenContainerStateRepository'); - return TokenContainerState.uninitialized([]); + Logger.info('No container state found in secure storage', name: 'SecureTokenContainerRepository'); + return const TokenContainer.uninitialized(serial: '123'); } final json = jsonDecode(containerStateJsonString); - return TokenContainerState.fromJson(json); + Logger.warning('Decoded container state: $json', name: 'SecureTokenContainerRepository'); + try { + final state = TokenContainer.fromJson(json); + return state; + } catch (e) { + Logger.error('Failed to decode container state', name: 'SecureTokenContainerRepository'); + await _delete(_containerStateKey); + return const TokenContainer.uninitialized(serial: '123'); + } } - // @override - // Future loadTokenTemplate(String tokenTemplateId) async { - // final state = await loadContainerState(); - // final template = state.tokenTemplates.firstWhereOrNull((template) => template.id == tokenTemplateId); - // return template; - // } - // @override - // Future saveTokenTemplate(TokenTemplate tokenTemplate) async { - // TokenContainerState state = await loadContainerState(); - // final templates = state.tokenTemplates; - // templates.removeWhere((template) => template.id == tokenTemplate.id); - // templates.add(tokenTemplate); - // state = state.copyWith(tokenTemplates: templates); - // await saveContainerState(state); - // return tokenTemplate; - // } } diff --git a/lib/state_notifiers/token_container_notifier.dart b/lib/state_notifiers/token_container_notifier.dart deleted file mode 100644 index 0b8e942ee..000000000 --- a/lib/state_notifiers/token_container_notifier.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mutex/mutex.dart'; -import 'package:privacyidea_authenticator/model/states/token_state.dart'; -import 'package:privacyidea_authenticator/model/token_container.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; - -import '../interfaces/repo/container_repository.dart'; -import '../model/states/token_container_state.dart'; - -class TokenContainerNotifier extends StateNotifier { - final TokenContainerStateRepository _repository; - late Future initState; - final StateNotifierProviderRef _ref; - final Mutex _repoMutex = Mutex(); - final Mutex _updateMutex = Mutex(); - - TokenContainerNotifier({ - required StateNotifierProviderRef ref, - required TokenContainerStateRepository repository, - TokenContainerState? initState, - }) : _repository = repository, - _ref = ref, - super(initState ?? TokenContainerState.uninitialized([])) { - _init(); - } - - Future _init() async { - initState = _loadFromRepo(); - state = await initState; - } - - Future _loadFromRepo() async { - await _repoMutex.acquire(); - final containerState = await _repository.loadContainerState(); - _repoMutex.release(); - return containerState; - } - - Future _saveToRepo(TokenContainerState state) async { - await _repoMutex.acquire(); - final newState = await _repository.saveContainerState(state); - _repoMutex.release(); - return newState; - } - - Future handleTokenState(TokenState tokenState) async { - await _updateMutex.acquire(); - final localTokens = tokenState.tokens.maybePiTokens; - final containerTokens = tokenState.containerTokens(state.serial); - final localTokenTemplates = localTokens.toTemplates(); - final containerTokenTemplates = containerTokens.toTemplates(); - final newState = state.copyWith(localTokenTemplates: localTokenTemplates, syncedTokenTemplates: containerTokenTemplates); - final savedState = await _saveToRepo(newState); - _ref.read(tokenProvider.notifier).updateContainerTokens(savedState); - state = savedState; - _updateMutex.release(); - return savedState; - } - - void addLocalTemplates(List maybePiTokenTemplates) {} - - // Future addToken(Token token) async { - // await _updateMutex.acquire(); - // final newState = state.copyTransformInto( - // tokenTemplates: [...state.syncedTokenTemplates, TokenTemplate(data: token.toUriMap())], - // ); - // final savedState = await _saveToRepo(newState); - // state = savedState; - // _updateMutex.release(); - // return savedState; - // } - - // Future updateTemplates(List updatedTemplates) async { - // await _updateMutex.acquire(); - // final newState = state.copyTransformInto( - // tokenTemplates: state.syncedTokenTemplates.map((oldToken) { - // final updatedToken = updatedTemplates.firstWhere( - // (newToken) => newToken.id == oldToken.id, - // orElse: () => oldToken, - // ); - // return updatedToken; - // }).toList(), - // ); - // final savedState = await _saveToRepo(newState); - // state = savedState; - // _updateMutex.release(); - // return savedState; - // } -} diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart index 4312c9e44..053f9cbbf 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart @@ -2,7 +2,7 @@ import 'package:app_links/app_links.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/deeplink.dart'; -import '../../../../state_notifiers/deeplink_notifier.dart'; +import '../../state_notifiers/deeplink_notifier.dart'; import '../../../home_widget_utils.dart'; import '../../../logger.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart index a7893b673..d5d9f6390 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/states/introduction_state.dart'; import '../../../../repo/preference_introduction_repository.dart'; -import '../../../../state_notifiers/completed_introduction_notifier.dart'; +import '../../state_notifiers/completed_introduction_notifier.dart'; import '../../../logger.dart'; final introductionProvider = StateNotifierProvider( diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart index ddec052d0..5764d103c 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/states/progress_state.dart'; -import '../../../../state_notifiers/progress_state_notifier.dart'; +import '../../state_notifiers/progress_state_notifier.dart'; import '../../../logger.dart'; final progressStateProvider = StateNotifierProvider((ref) { diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart index c7f8458ed..9ca13352f 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/states/push_request_state.dart'; -import '../../../../state_notifiers/push_request_notifier.dart'; +import '../../state_notifiers/push_request_notifier.dart'; import '../../../logger.dart'; import '../../../push_provider.dart'; import 'settings_provider.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart index e0b25b5a7..eeca52a0b 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/states/settings_state.dart'; import '../../../../repo/preference_settings_repository.dart'; -import '../../../../state_notifiers/settings_notifier.dart'; +import '../../state_notifiers/settings_notifier.dart'; final settingsProvider = StateNotifierProvider( (ref) { diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart index d5269dc93..69532ff04 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/mixins/sortable_mixin.dart'; import '../../../../model/states/token_folder_state.dart'; import '../../../../model/states/token_state.dart'; -import '../../../../state_notifiers/sortable_notifier.dart'; +import '../../state_notifiers/sortable_notifier.dart'; import '../../../logger.dart'; import 'token_folder_provider.dart'; import 'token_provider.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart index 3ccc1d1c2..452c9eac5 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart @@ -1,100 +1,273 @@ import 'dart:convert'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:collection/collection.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:json_annotation/json_annotation.dart'; import 'package:mutex/mutex.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/interfaces/repo/container_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../../api/token_container_api_endpoint.dart'; -import '../../../../model/states/token_container_state.dart'; -import '../../../../model/tokens/container_credentials.dart'; +import '../../../../model/states/token_state.dart'; +import '../../../../model/token_container.dart'; import '../../../../repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; -import '../../../../repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart'; import '../../../../repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; -import '../../../../state_notifiers/token_container_notifier.dart'; -import '../../../logger.dart'; - -final tokenContainerProvider = StateNotifierProvider((ref) { - Logger.info("New tokenContainerStateProvider created", name: 'tokenContainerStateProvider'); - final c = ref.watch(credentialsProvider); - return TokenContainerNotifier( - ref: ref, - repository: HybridTokenContainerStateRepository( - - localRepository: SecureTokenContainerStateRepository(containerId: 'local'), - remoteRepository: RemoteTokenContainerStateRepository(apiEndpoint: TokenContainerApiEndpoint(credentials: c!)) - ), - ); -}); - -final credentialsProvider = StateNotifierProvider((ref) { - Logger.info("New credentialsProvider created", name: 'credentialsProvider'); - return CredentialsNotifier(repo: SecureContainerCredentialsRepository()); -}); - -class CredentialsNotifier extends StateNotifier { +import '../../../../repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart'; +import '../../../../model/tokens/container_credentials.dart'; +import 'token_provider.dart'; + +part 'token_container_state_provider.g.dart'; + +@riverpod +class TokenContainerProvider extends _$TokenContainerProvider { + late final TokenContainerRepository _repository; + final Mutex _stateMutex = Mutex(); // Mutex to protect the state from being accessed while still waiting for the newest state to be delivered + final Mutex _repoMutex = Mutex(); // Mutex to protect the repository from being accessed while still waiting for the newest state to be delivered + + @override + Future build({ + required String containerSerial, + }) async { + await _stateMutex.acquire(); + final credential = ref.read(credentialsProvider).value?.credentialsOf(containerSerial); + if (credential == null) { + _stateMutex.release(); + return null; + } + _repository = HybridTokenContainerRepository( + localRepository: SecureTokenContainerRepository(containerId: containerSerial), + remoteRepository: RemoteTokenContainerRepository(apiEndpoint: TokenContainerApiEndpoint(credential: credential)), + ); + final initialState = _repository.loadContainerState(); + _stateMutex.release(); + return initialState; + } + + Future _saveToRepo(TokenContainer state) async { + await _repoMutex.acquire(); + final newState = await _repository.saveContainerState(state); + _repoMutex.release(); + return newState; + } + + Future handleTokenState(TokenState tokenState) async { + await _stateMutex.acquire(); + final localTokens = tokenState.tokens.maybePiTokens; + final oldState = state.value; + if (oldState == null) throw Exception('TokenContainer is null'); + final containerTokens = tokenState.containerTokens(oldState.serial); + final localTokenTemplates = localTokens.toTemplates(); + final containerTokenTemplates = containerTokens.toTemplates(); + final newState = oldState.copyWith(localTokenTemplates: localTokenTemplates, syncedTokenTemplates: containerTokenTemplates); + final savedState = await _saveToRepo(newState); + ref.read(tokenProvider.notifier).updateContainerTokens(savedState); + state = AsyncValue.data(savedState); + _stateMutex.release(); + return savedState; + } + + void addLocalTemplates(List maybePiTokenTemplates) { + throw UnimplementedError(); + } +} + +// final tokenContainerProvider = StateNotifierProvider.family((ref, containerId) { +// Logger.info("New tokenContainerStateProvider created", name: 'tokenContainerStateProvider'); +// final credentialsState = ref.watch(credentialsProvider); +// Logger.warning("credentialsState: $credentialsState", name: 'tokenContainerStateProvider'); +// return TokenContainerNotifier( +// ref: ref, +// repository: HybridTokenContainerRepository( +// localRepository: SecureTokenContainerRepository(containerId: 'local'), +// remoteRepository: RemoteTokenContainerRepository(apiEndpoint: TokenContainerApiEndpoint(credentialsState: credentialsState))), +// ); +// }); + +@riverpod +class CredentialsProvider extends _$CredentialsProvider { + final _stateMutex = Mutex(); + final _repoMutex = Mutex(); + late ContainerCredentialsRepository _repo; + + @override + Future build() async { + _repo = SecureContainerCredentialsRepository(); + return _repo.loadCredentialsState(); + } + + Future addCredential(ContainerCredential credential) async { + await _stateMutex.acquire(); + final newState = await _saveCredentialsToRepo(credential); + state = AsyncValue.data(newState); + _stateMutex.release(); + return newState; + } + + Future _saveCredentialsToRepo(ContainerCredential credential) async { + return await _repoMutex.protect(() async => await _repo.saveCredential(credential)); + } +} + +// final credentialsProvider = StateNotifierProvider((ref) { +// Logger.info("New credentialsProvider created", name: 'credentialsProvider'); +// return CredentialsNotifier(repo: MockContainerCredentialsRepository()); +// }); + +class MockContainerCredentialsRepository extends ContainerCredentialsRepository { + final state = CredentialsState(credentials: [ + ContainerCredential( + id: '123', + serial: '123', + ) + ]); + + @override + Future deleteAllCredentials() => Future.value(state); + + @override + Future deleteCredential(String id) => Future.value(state); + + @override + Future loadCredential(String id) => Future.value(state.credentials.firstOrNull); + + @override + Future loadCredentialsState() => Future.value(state); + + @override + Future saveCredential(ContainerCredential credential) => Future.value(state); + + @override + Future saveCredentialsState(CredentialsState credentialsState) => Future.value(state); +} + +@JsonSerializable() +class CredentialsState { + final List credentials; + + const CredentialsState({required this.credentials}); + + factory CredentialsState.fromJson(Map json) => _$CredentialsStateFromJson(json); + + Map toJson() => _$CredentialsStateToJson(this); + + factory CredentialsState.fromJsonStringList(List jsonStrings) { + final credentials = jsonStrings.map((jsonString) => ContainerCredential.fromJson(jsonDecode(jsonString))).toList(); + return CredentialsState(credentials: credentials); + } + + @override + String toString() { + return 'CredentialsState{credentials: $credentials}'; + } + + ContainerCredential? credentialsOf(String containerSerial) => credentials.firstWhereOrNull((credential) => credential.serial == containerSerial); +} + +class CredentialsNotifier extends StateNotifier { final Mutex _repoMutex = Mutex(); final ContainerCredentialsRepository _repo; - late Future initState = _initState(); + late Future initState = _initState(); - Future _initState() async { - final credentials = await _repo.loadCredentials(); + Future _initState() async { + final credentials = await _repo.loadCredentialsState(); state = credentials; return credentials; } - CredentialsNotifier({required ContainerCredentialsRepository repo}) + CredentialsNotifier({required ContainerCredentialsRepository repo, CredentialsState? initialState}) : _repo = repo, - super(null); + super(initialState ?? const CredentialsState(credentials: [])) { + _initState(); + } - Future setCredentials(ContainerCredentials credentials) async { - await _saveCredentialsToRepo(credentials); - state = credentials; + Future addCredentials(ContainerCredential credentials) async { + final newState = await _saveCredentialsToRepo(credentials); + state = newState; + return newState; } - Future deleteCredentials() async { - _deleteCredentialsFromRepo(); - state = null; + Future deleteCredentials(ContainerCredential credentials) async { + final newState = await _deleteCredentialsFromRepo(credentials); + state = newState; + return newState; } - Future _saveCredentialsToRepo(ContainerCredentials credentials) async { - await _repoMutex.protect(() async => await _repo.saveCredentials(credentials)); + Future _saveCredentialsToRepo(ContainerCredential credentials) async { + return await _repoMutex.protect(() async => await _repo.saveCredential(credentials)); } - Future _deleteCredentialsFromRepo() async { - await _repoMutex.protect(() async => await _repo.deleteCredentials()); + Future _deleteCredentialsFromRepo(ContainerCredential credentials) async { + return await _repoMutex.protect(() async => await _repo.deleteCredential(credentials.id)); } } class SecureContainerCredentialsRepository extends ContainerCredentialsRepository { - static String containerCredentialsKey = 'containerCredentials'; + String get containerCredentialsKey => 'containerCredentials'; + String _keyOfId(String id) => '$containerCredentialsKey.$id'; final Mutex _m = Mutex(); Future _protect(Future Function() f) => _m.protect(f); final FlutterSecureStorage _storage = const FlutterSecureStorage(); Future _write(String key, String value) => _protect(() => _storage.write(key: key, value: value)); Future _read(String key) async => await _protect(() async => await _storage.read(key: key)); + Future> _readAll() async => await _protect(() async => await _storage.readAll()); Future _delete(String key) => _protect(() => _storage.delete(key: key)); @override - Future loadCredentials() async { - final credentials = await _read(containerCredentialsKey); - if (credentials == null) return null; - return ContainerCredentials.fromJson(jsonDecode(credentials)); + Future loadCredentialsState() async { + final credentialsJsonString = await _readAll(); + if (credentialsJsonString.isEmpty) return const CredentialsState(credentials: []); + return CredentialsState.fromJsonStringList(credentialsJsonString.values.toList()); + } + + @override + Future saveCredentialsState(CredentialsState credentialsState) async { + final futures = []; + for (var credential in credentialsState.credentials) { + futures.add(saveCredential(credential)); + } + await Future.wait(futures); + return await loadCredentialsState(); + } + + @override + Future deleteCredential(String id) async { + await _delete(_keyOfId(id)); + return await loadCredentialsState(); + } + + @override + Future deleteAllCredentials() async { + final credentialKeys = (await _readAll()).keys.where((key) => key.startsWith(containerCredentialsKey)); + final futures = []; + for (var key in credentialKeys) { + futures.add(_delete(key)); + } + await Future.wait(futures); + return await loadCredentialsState(); } @override - Future saveCredentials(ContainerCredentials credentials) async { - await _write(containerCredentialsKey, jsonEncode(credentials.toJson())); + Future loadCredential(String id) async { + final credentialJsonString = await _read(_keyOfId(id)); + if (credentialJsonString == null) return null; + return ContainerCredential.fromJson(jsonDecode(credentialJsonString)); } @override - Future deleteCredentials() async { - await _delete(containerCredentialsKey); + Future saveCredential(ContainerCredential credential) async { + final credentialJsonString = jsonEncode(credential.toJson()); + await _write(_keyOfId(credential.id), credentialJsonString); + return await loadCredentialsState(); } } abstract class ContainerCredentialsRepository { - Future saveCredentials(ContainerCredentials credentials); - Future loadCredentials(); - Future deleteCredentials(); + Future saveCredential(ContainerCredential credential); + Future saveCredentialsState(CredentialsState credentialsState); + Future loadCredentialsState(); + Future loadCredential(String id); + Future deleteAllCredentials(); + Future deleteCredential(String id); } diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart new file mode 100644 index 000000000..6cd551db5 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart @@ -0,0 +1,213 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'token_container_state_provider.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CredentialsState _$CredentialsStateFromJson(Map json) => + CredentialsState( + credentials: (json['credentials'] as List) + .map((e) => ContainerCredential.fromJson(e as Map)) + .toList(), + ); + +Map _$CredentialsStateToJson(CredentialsState instance) => + { + 'credentials': instance.credentials, + }; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$tokenContainerProviderHash() => + r'2a5840df5d339ba273d534ea610bdeb00ce20f89'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$TokenContainerProvider + extends BuildlessAutoDisposeAsyncNotifier { + late final String containerSerial; + + FutureOr build({ + required String containerSerial, + }); +} + +/// See also [TokenContainerProvider]. +@ProviderFor(TokenContainerProvider) +const tokenContainerProviderOf = TokenContainerProviderFamily(); + +/// See also [TokenContainerProvider]. +class TokenContainerProviderFamily extends Family> { + /// See also [TokenContainerProvider]. + const TokenContainerProviderFamily(); + + /// See also [TokenContainerProvider]. + TokenContainerProviderProvider call({ + required String containerSerial, + }) { + return TokenContainerProviderProvider( + containerSerial: containerSerial, + ); + } + + @override + TokenContainerProviderProvider getProviderOverride( + covariant TokenContainerProviderProvider provider, + ) { + return call( + containerSerial: provider.containerSerial, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'tokenContainerProviderOf'; +} + +/// See also [TokenContainerProvider]. +class TokenContainerProviderProvider + extends AutoDisposeAsyncNotifierProviderImpl { + /// See also [TokenContainerProvider]. + TokenContainerProviderProvider({ + required String containerSerial, + }) : this._internal( + () => TokenContainerProvider()..containerSerial = containerSerial, + from: tokenContainerProviderOf, + name: r'tokenContainerProviderOf', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$tokenContainerProviderHash, + dependencies: TokenContainerProviderFamily._dependencies, + allTransitiveDependencies: + TokenContainerProviderFamily._allTransitiveDependencies, + containerSerial: containerSerial, + ); + + TokenContainerProviderProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.containerSerial, + }) : super.internal(); + + final String containerSerial; + + @override + FutureOr runNotifierBuild( + covariant TokenContainerProvider notifier, + ) { + return notifier.build( + containerSerial: containerSerial, + ); + } + + @override + Override overrideWith(TokenContainerProvider Function() create) { + return ProviderOverride( + origin: this, + override: TokenContainerProviderProvider._internal( + () => create()..containerSerial = containerSerial, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + containerSerial: containerSerial, + ), + ); + } + + @override + AutoDisposeAsyncNotifierProviderElement createElement() { + return _TokenContainerProviderProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is TokenContainerProviderProvider && + other.containerSerial == containerSerial; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, containerSerial.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin TokenContainerProviderRef + on AutoDisposeAsyncNotifierProviderRef { + /// The parameter `containerSerial` of this provider. + String get containerSerial; +} + +class _TokenContainerProviderProviderElement + extends AutoDisposeAsyncNotifierProviderElement with TokenContainerProviderRef { + _TokenContainerProviderProviderElement(super.provider); + + @override + String get containerSerial => + (origin as TokenContainerProviderProvider).containerSerial; +} + +String _$credentialsProviderHash() => + r'992a1e466ed24e4d0bc3eabbeaf33ff6ada90ee1'; + +/// See also [CredentialsProvider]. +@ProviderFor(CredentialsProvider) +final credentialsProvider = AutoDisposeAsyncNotifierProvider< + CredentialsProvider, CredentialsState>.internal( + CredentialsProvider.new, + name: r'credentialsProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$credentialsProviderHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$CredentialsProvider = AutoDisposeAsyncNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart index 3762b8692..395442949 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/states/token_folder_state.dart'; import '../../../../repo/preference_token_folder_repository.dart'; -import '../../../../state_notifiers/token_folder_notifier.dart'; +import '../../state_notifiers/token_folder_notifier.dart'; import '../../../logger.dart'; final tokenFolderProvider = StateNotifierProvider( diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart index e410c15ba..4fb84c5cd 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/states/token_state.dart'; -import '../../../../state_notifiers/token_notifier.dart'; +import '../../state_notifiers/token_notifier.dart'; import '../../../logger.dart'; import 'deeplink_provider.dart'; diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index 037d20cf9..25f6b5b38 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -1,12 +1,13 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; import '../../../model/states/token_state.dart'; import '../riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; -class TokenContainerTokenStateListener extends TokenStateListener { - TokenContainerTokenStateListener({ +class ContainerListensToTokenState extends TokenStateListener { + ContainerListensToTokenState({ required super.tokenProvider, required WidgetRef ref, }) : super(onNewState: (TokenState? previous, TokenState next) { @@ -16,8 +17,12 @@ class TokenContainerTokenStateListener extends TokenStateListener { }); static Future _onNewState(TokenState? previousState, TokenState nextState, WidgetRef ref) async { + Logger.warning('New token state', name: 'TokenContainerTokenStateListener'); final maybePiTokenTemplates = nextState.lastlyUpdatedTokens.maybePiTokens.toTemplates(); + final credentials = ref.read(credentialsProvider).value?.credentials ?? []; if (maybePiTokenTemplates.isEmpty) return; - ref.read(tokenContainerProvider.notifier).addLocalTemplates(maybePiTokenTemplates); + for (var credential in credentials) { + ref.read(tokenContainerProviderOf(containerSerial: credential.serial).notifier).addLocalTemplates(maybePiTokenTemplates); + } } } diff --git a/lib/state_notifiers/completed_introduction_notifier.dart b/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart similarity index 90% rename from lib/state_notifiers/completed_introduction_notifier.dart rename to lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart index 4025cfcb5..216a8152c 100644 --- a/lib/state_notifiers/completed_introduction_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart @@ -1,9 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../interfaces/repo/introduction_repository.dart'; -import '../model/enums/introduction.dart'; -import '../model/states/introduction_state.dart'; -import '../utils/logger.dart'; +import '../../../interfaces/repo/introduction_repository.dart'; +import '../../../model/enums/introduction.dart'; +import '../../../model/states/introduction_state.dart'; +import '../../logger.dart'; class IntroductionNotifier extends StateNotifier { late Future loadingRepo; diff --git a/lib/state_notifiers/deeplink_notifier.dart b/lib/utils/riverpod/state_notifiers/deeplink_notifier.dart similarity index 96% rename from lib/state_notifiers/deeplink_notifier.dart rename to lib/utils/riverpod/state_notifiers/deeplink_notifier.dart index bc8521d70..9187e0e53 100644 --- a/lib/state_notifiers/deeplink_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/deeplink_notifier.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../model/deeplink.dart'; -import '../utils/logger.dart'; +import '../../../model/deeplink.dart'; +import '../../logger.dart'; bool _initialUriIsHandled = false; diff --git a/lib/state_notifiers/progress_state_notifier.dart b/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart similarity index 94% rename from lib/state_notifiers/progress_state_notifier.dart rename to lib/utils/riverpod/state_notifiers/progress_state_notifier.dart index de4717a6f..6f85e69a0 100644 --- a/lib/state_notifiers/progress_state_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../model/states/progress_state.dart'; -import '../utils/logger.dart'; +import '../../../model/states/progress_state.dart'; +import '../../logger.dart'; class ProgressStateNotifier extends StateNotifier { ProgressStateNotifier() : super(null); diff --git a/lib/state_notifiers/push_request_notifier.dart b/lib/utils/riverpod/state_notifiers/push_request_notifier.dart similarity index 95% rename from lib/state_notifiers/push_request_notifier.dart rename to lib/utils/riverpod/state_notifiers/push_request_notifier.dart index cb9e94d11..4450168a3 100644 --- a/lib/state_notifiers/push_request_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/push_request_notifier.dart @@ -24,20 +24,20 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; -import '../interfaces/repo/push_request_repository.dart'; -import '../l10n/app_localizations.dart'; -import '../model/push_request.dart'; -import '../model/states/push_request_state.dart'; -import '../model/tokens/push_token.dart'; -import '../repo/secure_push_request_repository.dart'; -import '../utils/custom_int_buffer.dart'; -import '../utils/globals.dart'; -import '../utils/logger.dart'; -import '../utils/privacyidea_io_client.dart'; -import '../utils/push_provider.dart'; -import '../utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; -import '../utils/rsa_utils.dart'; -import '../utils/utils.dart'; +import '../../../interfaces/repo/push_request_repository.dart'; +import '../../../l10n/app_localizations.dart'; +import '../../../model/push_request.dart'; +import '../../../model/states/push_request_state.dart'; +import '../../../model/tokens/push_token.dart'; +import '../../../repo/secure_push_request_repository.dart'; +import '../../custom_int_buffer.dart'; +import '../../globals.dart'; +import '../../logger.dart'; +import '../../privacyidea_io_client.dart'; +import '../../push_provider.dart'; +import '../riverpod_providers/state_providers/status_message_provider.dart'; +import '../../rsa_utils.dart'; +import '../../utils.dart'; class PushRequestNotifier extends StateNotifier { late final Future initState; diff --git a/lib/state_notifiers/settings_notifier.dart b/lib/utils/riverpod/state_notifiers/settings_notifier.dart similarity index 95% rename from lib/state_notifiers/settings_notifier.dart rename to lib/utils/riverpod/state_notifiers/settings_notifier.dart index 49a363396..73277f120 100644 --- a/lib/state_notifiers/settings_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/settings_notifier.dart @@ -2,11 +2,11 @@ import 'dart:ui'; 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 '../../../interfaces/repo/settings_repository.dart'; +import '../../../model/states/settings_state.dart'; +import '../../../model/version.dart'; +import '../../logger.dart'; +import '../../push_provider.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/sortable_notifier.dart b/lib/utils/riverpod/state_notifiers/sortable_notifier.dart similarity index 71% rename from lib/state_notifiers/sortable_notifier.dart rename to lib/utils/riverpod/state_notifiers/sortable_notifier.dart index 87e3803ee..b38964723 100644 --- a/lib/state_notifiers/sortable_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/sortable_notifier.dart @@ -1,12 +1,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../model/extensions/sortable_list.dart'; -import '../model/mixins/sortable_mixin.dart'; -import '../model/token_folder.dart'; -import '../model/tokens/token.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; -import '../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; +import '../../../model/extensions/sortable_list.dart'; +import '../../../model/mixins/sortable_mixin.dart'; +import '../../../model/token_folder.dart'; +import '../../../model/tokens/token.dart'; +import '../riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import '../riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../riverpod_providers/state_providers/dragging_sortable_provider.dart'; class SortableNotifier extends StateNotifier> { final StateNotifierProviderRef _ref; @@ -38,13 +38,14 @@ class SortableNotifier extends StateNotifier> { var newState = List.from(state); newState.removeWhere((element) => element is T); newState.addAll(newList); + final listWithNulls = newState.toList(); newState = newState.sorted.fillNullIndices(); state = newState; await Future.wait([ Future.delayed(const Duration(milliseconds: 50)), - if (newList.any((e) => e is Token) && newList.any((element) => element.sortIndex == null)) + if (listWithNulls.any((e) => e is Token) && listWithNulls.any((element) => element.sortIndex == null)) _ref.read(tokenProvider.notifier).addOrReplaceTokens(state.whereType().toList()), - if (newList.any((e) => e is TokenFolder) && newList.any((element) => element.sortIndex == null)) + if (listWithNulls.any((e) => e is TokenFolder) && listWithNulls.any((element) => element.sortIndex == null)) _ref.read(tokenFolderProvider.notifier).addOrReplaceFolders(state.whereType().toList()), ]); diff --git a/lib/utils/riverpod/state_notifiers/token_container_notifier.dart b/lib/utils/riverpod/state_notifiers/token_container_notifier.dart new file mode 100644 index 000000000..dc7ce6f93 --- /dev/null +++ b/lib/utils/riverpod/state_notifiers/token_container_notifier.dart @@ -0,0 +1,93 @@ +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:mutex/mutex.dart'; +// import 'package:privacyidea_authenticator/model/states/token_state.dart'; +// import 'package:privacyidea_authenticator/model/token_container.dart'; +// import 'package:privacyidea_authenticator/utils/logger.dart'; +// import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; + +// import '../../../interfaces/repo/container_repository.dart'; +// import '../../../model/states/token_container_state.dart'; + +// class TokenContainerNotifier extends StateNotifier { +// final TokenContainerRepository _repository; +// late Future initState; +// final StateNotifierProviderRef _ref; +// final Mutex _repoMutex = Mutex(); +// final Mutex _updateMutex = Mutex(); + +// TokenContainerNotifier({ +// required StateNotifierProviderRef ref, +// required TokenContainerRepository repository, +// TokenContainer? initState, +// }) : _repository = repository, +// _ref = ref, +// super(initState ?? TokenContainer.uninitialized([])) { +// _init(); +// } + +// Future _init() async { +// initState = _loadFromRepo(); +// final newState = await initState; +// if (!mounted) return; +// state = newState; +// } + +// Future _loadFromRepo() async { +// Logger.warning('Loading container state from repo', name: 'TokenContainerNotifier'); +// await _repoMutex.acquire(); +// final containerState = await _repository.loadContainerState(); +// _repoMutex.release(); +// return containerState; +// } + +// Future _saveToRepo(TokenContainer state) async { +// await _repoMutex.acquire(); +// final newState = await _repository.saveContainerState(state); +// _repoMutex.release(); +// return newState; +// } + +// Future handleTokenState(TokenState tokenState) async { +// await _updateMutex.acquire(); +// final localTokens = tokenState.tokens.maybePiTokens; +// final containerTokens = tokenState.containerTokens(state.serial); +// final localTokenTemplates = localTokens.toTemplates(); +// final containerTokenTemplates = containerTokens.toTemplates(); +// final newState = state.copyWith(localTokenTemplates: localTokenTemplates, syncedTokenTemplates: containerTokenTemplates); +// final savedState = await _saveToRepo(newState); +// _ref.read(tokenProvider.notifier).updateContainerTokens(savedState); +// state = savedState; +// _updateMutex.release(); +// return savedState; +// } + +// void addLocalTemplates(List maybePiTokenTemplates) {} + +// // Future addToken(Token token) async { +// // await _updateMutex.acquire(); +// // final newState = state.copyTransformInto( +// // tokenTemplates: [...state.syncedTokenTemplates, TokenTemplate(data: token.toUriMap())], +// // ); +// // final savedState = await _saveToRepo(newState); +// // state = savedState; +// // _updateMutex.release(); +// // return savedState; +// // } + +// // Future updateTemplates(List updatedTemplates) async { +// // await _updateMutex.acquire(); +// // final newState = state.copyTransformInto( +// // tokenTemplates: state.syncedTokenTemplates.map((oldToken) { +// // final updatedToken = updatedTemplates.firstWhere( +// // (newToken) => newToken.id == oldToken.id, +// // orElse: () => oldToken, +// // ); +// // return updatedToken; +// // }).toList(), +// // ); +// // final savedState = await _saveToRepo(newState); +// // state = savedState; +// // _updateMutex.release(); +// // return savedState; +// // } +// } diff --git a/lib/state_notifiers/token_folder_notifier.dart b/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart similarity index 96% rename from lib/state_notifiers/token_folder_notifier.dart rename to lib/utils/riverpod/state_notifiers/token_folder_notifier.dart index 09d3f6a75..56c1cd6bc 100644 --- a/lib/state_notifiers/token_folder_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart @@ -1,10 +1,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mutex/mutex.dart'; -import '../interfaces/repo/token_folder_repository.dart'; -import '../model/states/token_folder_state.dart'; -import '../model/token_folder.dart'; -import '../utils/logger.dart'; +import '../../../interfaces/repo/token_folder_repository.dart'; +import '../../../model/states/token_folder_state.dart'; +import '../../../model/token_folder.dart'; +import '../../logger.dart'; class TokenFolderNotifier extends StateNotifier { late final Future initState; diff --git a/lib/state_notifiers/token_notifier.dart b/lib/utils/riverpod/state_notifiers/token_notifier.dart similarity index 92% rename from lib/state_notifiers/token_notifier.dart rename to lib/utils/riverpod/state_notifiers/token_notifier.dart index b3e2b970c..c1c08dcd1 100644 --- a/lib/state_notifiers/token_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_notifier.dart @@ -11,36 +11,35 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; import 'package:pointycastle/asymmetric/api.dart'; -import 'package:privacyidea_authenticator/model/states/token_container_state.dart'; - -import '../interfaces/repo/token_repository.dart'; -import '../l10n/app_localizations.dart'; -import '../model/enums/push_token_rollout_state.dart'; -import '../model/enums/token_import_type.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/token_container.dart'; -import '../model/tokens/hotp_token.dart'; -import '../model/tokens/otp_token.dart'; -import '../model/tokens/push_token.dart'; -import '../model/tokens/token.dart'; -import '../processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart'; -import '../repo/secure_token_repository.dart'; -import '../utils/firebase_utils.dart'; -import '../utils/globals.dart'; -import '../utils/identifiers.dart'; -import '../utils/lock_auth.dart'; -import '../utils/logger.dart'; -import '../utils/privacyidea_io_client.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; -import '../utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; -import '../utils/rsa_utils.dart'; -import '../utils/utils.dart'; -import '../utils/view_utils.dart'; -import '../views/import_tokens_view/pages/import_plain_tokens_page.dart'; + +import '../../../interfaces/repo/token_repository.dart'; +import '../../../l10n/app_localizations.dart'; +import '../../../model/enums/push_token_rollout_state.dart'; +import '../../../model/enums/token_import_type.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/token_container.dart'; +import '../../../model/tokens/hotp_token.dart'; +import '../../../model/tokens/otp_token.dart'; +import '../../../model/tokens/push_token.dart'; +import '../../../model/tokens/token.dart'; +import '../../../processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart'; +import '../../../repo/secure_token_repository.dart'; +import '../../firebase_utils.dart'; +import '../../globals.dart'; +import '../../identifiers.dart'; +import '../../lock_auth.dart'; +import '../../logger.dart'; +import '../../privacyidea_io_client.dart'; +import '../riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../riverpod_providers/state_providers/status_message_provider.dart'; +import '../../rsa_utils.dart'; +import '../../utils.dart'; +import '../../view_utils.dart'; +import '../../../views/import_tokens_view/pages/import_plain_tokens_page.dart'; class TokenNotifier extends StateNotifier { static final Map _hidingTimers = {}; @@ -116,6 +115,7 @@ class TokenNotifier extends StateNotifier { return failedTokens; } // [failedTokens] is empty, so every token was saved successfully and we dont need to filter the tokens + Logger.warning('State Updated', name: 'token_notifier.dart#_saveOrReplaceTokens', stackTrace: StackTrace.current); state = state.addOrReplaceTokens(tokens); _loadingRepoMutex.release(); return []; @@ -319,16 +319,20 @@ class TokenNotifier extends StateNotifier { /// Updates a list of tokens and returns the updated tokens if successful, the old tokens if not and an empty list if the tokens does not exist. Future> updateTokens(List tokens, T Function(T) updater) async => _updateTokens(tokens, updater); - /// Adds, Updates or Removes tokens based on the [TokenContainerState] and returns the updated tokens. + /// Adds, Updates or Removes tokens based on the [TokenContainer] and returns the updated tokens. void updateContainerTokens(TokenContainer container) async { + await initState; + Logger.warning('Updating tokens from container.', name: 'token_notifier.dart#updateContainerTokens'); final templatesToAdd = []; final templatesToUpdate = []; final templatesToRemove = []; final knownContainerTokens = state.tokens.fromContainer(container.serial); + Logger.warning('Known tokens: ${knownContainerTokens.length}', name: 'token_notifier.dart#updateContainerTokens'); final syncedTokenTemplates = container.syncedTokenTemplates; + Logger.warning('Synced tokens: ${syncedTokenTemplates.length}', name: 'token_notifier.dart#updateContainerTokens'); final knownContainerTemplates = knownContainerTokens.toTemplates(); - for (var i = 0; i < syncedTokenTemplates.length && knownContainerTemplates.isNotEmpty; i++) { + for (var i = 0; i < syncedTokenTemplates.length; i++) { // Searches for tokens that are in the container but not in the app to add them. // If the token is already in the app, it will be updated. // Reduces the [tokenTemplatesApp] list to only the tokens that are in the app but not in the container. These tokens will be removed. @@ -347,7 +351,12 @@ class TokenNotifier extends StateNotifier { // Removes all tokens that are in the app but not in the container. templatesToRemove.addAll(knownContainerTemplates); - final tokensToAdd = templatesToAdd.map((e) => e.toToken()).toList(); + Logger.warning('Templates to add: ${templatesToAdd.length}', name: 'token_notifier.dart#updateContainerTokens'); + Logger.warning('Templates to update: ${templatesToUpdate.length}', name: 'token_notifier.dart#updateContainerTokens'); + Logger.warning('Templates to remove: ${templatesToRemove.length}', name: 'token_notifier.dart#updateContainerTokens'); + + final tokensToAdd = templatesToAdd.map((e) => e.toToken(container)).toList(); + Logger.warning('Tokens to add: ${tokensToAdd.firstOrNull?.containerId}', name: 'token_notifier.dart#updateContainerTokens'); final tokensToUpdate = []; for (var template in templatesToUpdate) { final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id); @@ -364,9 +373,11 @@ class TokenNotifier extends StateNotifier { tokensToRemove.add(token); } + Logger.warning('Tokens to add: ${tokensToAdd.length}', name: 'token_notifier.dart#updateContainerTokens'); if (tokensToAdd.isNotEmpty) { await addOrReplaceTokens(tokensToAdd); } + Logger.warning('Tokens to update: ${tokensToUpdate.length}', name: 'token_notifier.dart#updateContainerTokens'); if (tokensToUpdate.isNotEmpty) { await updateTokens(tokensToUpdate, (p0) { final template = templatesToUpdate.firstWhereOrNull((element) => element.id == p0.id); @@ -374,6 +385,7 @@ class TokenNotifier extends StateNotifier { return p0.copyWithFromTemplate(template); }); } + Logger.warning('Tokens to remove: ${tokensToRemove.length}', name: 'token_notifier.dart#updateContainerTokens'); if (tokensToRemove.isNotEmpty) { await removeTokens(tokensToRemove); } diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index 1e8726896..c2abcc30b 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -4,11 +4,11 @@ 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 'package:privacyidea_authenticator/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart'; -import 'package:privacyidea_authenticator/state_notifiers/token_container_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; -import '../model/states/token_container_state.dart'; +import '../interfaces/riverpod/state_listeners/notifier_provider_listener.dart'; + +import '../model/token_container.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; import '../utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart'; @@ -88,14 +88,22 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { @override Widget build(BuildContext context) { + final credentials = ref.read(credentialsProvider).value?.credentials ?? []; return SingleTouchRecognizer( child: StateObserver( - listeners: [ + stateNotifierProviderListeners: [ NavigationDeepLinkListener(deeplinkProvider: deeplinkProvider), HomeWidgetDeepLinkListener(deeplinkProvider: deeplinkProvider), // TODO: Nochmal anschauen HomeWidgetTokenStateListener(tokenProvider: tokenProvider), - TokenContainerTokenStateListener(tokenProvider: tokenProvider, ref: ref), - TokenStateTokenContainerListener(tokenContainerProvider: tokenContainerProvider, ref: ref), + ContainerListensToTokenState(tokenProvider: tokenProvider, ref: ref), + ], + asyncNotifierProviderListeners: [ + ...credentials.map( + (credential) => TokenStateListensToContainer( + containerProvider: tokenContainerProviderOf(containerSerial: credential.serial), + ref: ref, + ), + ), ], child: EasyDynamicThemeWidget( child: widget.child, @@ -105,23 +113,26 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { } } -class TokenStateTokenContainerListener extends TokenContainerListener { +class TokenStateListensToContainer extends AsyncContainerListener { final WidgetRef ref; - TokenStateTokenContainerListener({ - required super.tokenContainerProvider, + TokenStateListensToContainer({ + required super.containerProvider, required this.ref, }) : super(onNewState: (previous, next) => _onNewState(previous, next, ref)); - static Future _onNewState(TokenContainerState? previous, TokenContainerState? next, WidgetRef ref) async { - if (next == null) return; + static Future _onNewState(AsyncValue? previous, AsyncValue next, WidgetRef ref) async { + Logger.warning('New TokenContainer', name: 'TokenStateTokenContainerListener'); + final value = next.value; + if (value == null) return; final provider = ref.read(tokenProvider.notifier); - provider.updateContainerTokens(next); + provider.updateContainerTokens(value); } } -class TokenContainerListener extends StateNotifierProviderListener { - const TokenContainerListener({ - required StateNotifierProvider tokenContainerProvider, + +abstract class AsyncContainerListener extends AsyncNotifierProviderListener { + const AsyncContainerListener({ + required TokenContainerProviderProvider containerProvider, required super.onNewState, - }) : super(provider: tokenContainerProvider); + }) : super(provider: containerProvider); } diff --git a/lib/widgets/app_wrappers/state_observer.dart b/lib/widgets/app_wrappers/state_observer.dart index 38d27643a..7dd07b247 100644 --- a/lib/widgets/app_wrappers/state_observer.dart +++ b/lib/widgets/app_wrappers/state_observer.dart @@ -1,18 +1,20 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../interfaces/riverpod/state_listeners/notifier_provider_listener.dart'; import '../../interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart'; class StateObserver extends ConsumerWidget { - final List listeners; + final List asyncNotifierProviderListeners; + final List stateNotifierProviderListeners; final Widget child; - const StateObserver({super.key, required this.listeners, required this.child}); + const StateObserver({super.key, this.asyncNotifierProviderListeners = const [], this.stateNotifierProviderListeners = const [], required this.child}); @override Widget build(BuildContext context, WidgetRef ref) { - for (final listener in listeners) { + for (final listener in stateNotifierProviderListeners) { listener.buildListen(ref); } return child; diff --git a/pubspec.lock b/pubspec.lock index bc26a6fe9..8f273ad57 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,6 +25,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.4.1" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" + url: "https://pub.dev" + source: hosted + version: "0.11.3" app_links: dependency: "direct main" description: @@ -241,6 +249,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + ci: + dependency: transitive + description: + name: ci + sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" clock: dependency: transitive description: @@ -329,6 +353,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + custom_lint: + dependency: "direct dev" + description: + name: custom_lint + sha256: "7c0aec12df22f9082146c354692056677f1e70bc43471644d1fdb36c6fdda799" + url: "https://pub.dev" + source: hosted + version: "0.6.4" + custom_lint_builder: + dependency: transitive + description: + name: custom_lint_builder + sha256: d7dc41e709dde223806660268678be7993559e523eb3164e2a1425fd6f7615a9 + url: "https://pub.dev" + source: hosted + version: "0.6.4" + custom_lint_core: + dependency: transitive + description: + name: custom_lint_core + sha256: a85e8f78f4c52f6c63cdaf8c872eb573db0231dcdf3c3a5906d493c1f8bc20e6 + url: "https://pub.dev" + source: hosted + version: "0.6.3" dart_style: dependency: transitive description: @@ -686,10 +734,26 @@ packages: dependency: "direct main" description: name: flutterlifecyclehooks - sha256: "25c16ff88513445c049735013ed66ddd364530c9467b5784a5667ee0e644dd67" + sha256: "6844e66c1a31ed6d310cb29d4a16bd54004acbfeedbfcb1dd4b1594d38e66beb" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 + url: "https://pub.dev" + source: hosted + version: "2.5.2" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" frontend_server_client: dependency: transitive description: @@ -743,6 +807,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e + url: "https://pub.dev" + source: hosted + version: "4.2.0" http: dependency: "direct main" description: @@ -1315,6 +1387,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.5.1" + riverpod_analyzer_utils: + dependency: transitive + description: + name: riverpod_analyzer_utils + sha256: "8b71f03fc47ae27d13769496a1746332df4cec43918aeba9aff1e232783a780f" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + riverpod_annotation: + dependency: "direct main" + description: + name: riverpod_annotation + sha256: e5e796c0eba4030c704e9dae1b834a6541814963292839dcf9638d53eba84f5c + url: "https://pub.dev" + source: hosted + version: "2.3.5" + riverpod_generator: + dependency: "direct main" + description: + name: riverpod_generator + sha256: d451608bf17a372025fc36058863737636625dfdb7e3cbf6142e0dfeb366ab22 + url: "https://pub.dev" + source: hosted + version: "2.4.0" + riverpod_lint: + dependency: "direct dev" + description: + name: riverpod_lint + sha256: "3c67c14ccd16f0c9d53e35ef70d06cd9d072e2fb14557326886bbde903b230a5" + url: "https://pub.dev" + source: hosted + version: "2.3.10" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index add1c4105..5415fb803 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -67,8 +67,8 @@ dependencies: connectivity_plus: ^6.0.1 flutter_mailer: ^2.1.1 # Riverpod - flutter_riverpod: ^2.3.6 - flutterlifecyclehooks: ^4.0.0 + flutter_riverpod: ^2.5.1 + flutterlifecyclehooks: ^5.0.0 # Icons lottie: ^3.0.0 cupertino_icons: ^1.0.4 @@ -89,6 +89,9 @@ dependencies: path_provider: ^2.1.2 qr_flutter: ^4.1.0 image_cropper: ^7.1.0 + riverpod_generator: ^2.4.0 + riverpod_annotation: ^2.3.5 + freezed_annotation: ^2.4.4 dev_dependencies: @@ -106,6 +109,9 @@ dev_dependencies: # dependencies to serialize objects to json build_runner: ^2.4.11 json_serializable: ^6.8.0 + custom_lint: ^0.6.4 + riverpod_lint: ^2.3.10 + freezed: ^2.5.2 # For information on the generic Dart part of this file, see the diff --git a/test/unit_test/repo/hybrid_token_container_repo_test.dart b/test/unit_test/repo/hybrid_token_container_repo_test.dart index 4e480d58e..3d1ee88db 100644 --- a/test/unit_test/repo/hybrid_token_container_repo_test.dart +++ b/test/unit_test/repo/hybrid_token_container_repo_test.dart @@ -2,28 +2,27 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:privacyidea_authenticator/interfaces/repo/container_repository.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; -import 'package:privacyidea_authenticator/model/states/token_container_state.dart'; import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; import 'package:privacyidea_authenticator/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; -class MockTokenContainerStateRepository implements TokenContainerStateRepository { - TokenContainerState savedState; +class MockTokenContainerRepository implements TokenContainerRepository { + TokenContainer savedState; bool _doesThrow = false; - MockTokenContainerStateRepository({TokenContainerState? initialState}) : savedState = initialState ?? TokenContainerState.uninitialized([]); + MockTokenContainerRepository({TokenContainer? initialState}) : savedState = initialState ?? const TokenContainer.uninitialized(); void setThrow(bool value) { _doesThrow = value; } @override - Future loadContainerState() { + Future loadContainerState() { if (_doesThrow) throw Exception('Test exception'); return Future.value(savedState); } @override - Future saveContainerState(TokenContainerState containerState) { + Future saveContainerState(TokenContainer containerState) { if (_doesThrow) throw Exception('Test exception'); savedState = containerState; return Future.value(savedState); @@ -32,12 +31,12 @@ class MockTokenContainerStateRepository implements TokenContainerStateRepository } void main() { - _testHybridTokenContainerStateRepository(); + _testHybridTokenContainerRepository(); } -void _testHybridTokenContainerStateRepository() { - group('HybridTokenContainerStateRepository test', () { - test('HybridTokenContainerStateRepository test', () async { +void _testHybridTokenContainerRepository() { + group('HybridTokenContainerRepository test', () { + test('HybridTokenContainerRepository test', () async { final token = TOTPToken( period: 30, id: 'TOTP_1', @@ -46,26 +45,27 @@ void _testHybridTokenContainerStateRepository() { secret: 'SECRET', issuer: 'issuer', ); - TokenContainerState? remoteState = TokenContainerStateSynced( + TokenContainer? remoteState = TokenContainer.synced( serial: 'containerSerial', description: 'description', - type: 'type', + syncedTokenTemplates: [TokenTemplate(data: token.toUriMap())], + lastSyncAt: DateTime.now(), + localTokenTemplates: [], ); - TokenContainerState? localState = TokenContainerStateModified( + TokenContainer? localState = TokenContainer.modified( lastModifiedAt: DateTime.now(), - lastSyncedAt: DateTime.now().subtract(const Duration(days: 1)), + lastSyncAt: DateTime.now().subtract(const Duration(days: 1)), serial: 'containerSerial', description: 'description', - type: 'type', syncedTokenTemplates: [], localTokenTemplates: [TokenTemplate(data: token.toUriMap())], ); - final localRepo = MockTokenContainerStateRepository(initialState: localState); - final remoteRepo = MockTokenContainerStateRepository(initialState: remoteState); + final localRepo = MockTokenContainerRepository(initialState: localState); + final remoteRepo = MockTokenContainerRepository(initialState: remoteState); - final hybridRepo = HybridTokenContainerStateRepository( + final hybridRepo = HybridTokenContainerRepository( localRepository: localRepo, remoteRepository: remoteRepo, ); @@ -73,10 +73,10 @@ void _testHybridTokenContainerStateRepository() { final state = await hybridRepo.loadContainerState(); await Future.delayed(const Duration(milliseconds: 1)); final dateTimeAfter = DateTime.now(); - expect(state, isA()); - state as TokenContainerStateSynced; - expect(state.lastSyncedAt?.isAfter(dateTimeBefore), isTrue); - expect(state.lastSyncedAt?.isBefore(dateTimeAfter), isTrue); + expect(state, isA()); + state as TokenContainerSynced; + expect(state.lastSyncAt.isAfter(dateTimeBefore), isTrue); + expect(state.lastSyncAt.isBefore(dateTimeAfter), isTrue); expect(state.syncedTokenTemplates.length, 1); final template = state.syncedTokenTemplates.first; expect(template.data, token.toUriMap(), reason: 'Should be the remote state if both are changed since last sync'); diff --git a/test/unit_test/state_notifiers/deeplink_notifier_test.dart b/test/unit_test/state_notifiers/deeplink_notifier_test.dart index bc9634e7b..72efc1f89 100644 --- a/test/unit_test/state_notifiers/deeplink_notifier_test.dart +++ b/test/unit_test/state_notifiers/deeplink_notifier_test.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:privacyidea_authenticator/model/deeplink.dart'; -import 'package:privacyidea_authenticator/state_notifiers/deeplink_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/deeplink_notifier.dart'; void main() { _testDeeplinkNotifier(); } diff --git a/test/unit_test/state_notifiers/push_request_notifier_test.dart b/test/unit_test/state_notifiers/push_request_notifier_test.dart index e4739fa7e..defa05324 100644 --- a/test/unit_test/state_notifiers/push_request_notifier_test.dart +++ b/test/unit_test/state_notifiers/push_request_notifier_test.dart @@ -6,7 +6,7 @@ import 'package:privacyidea_authenticator/interfaces/repo/push_request_repositor import 'package:privacyidea_authenticator/model/push_request.dart'; import 'package:privacyidea_authenticator/model/states/push_request_state.dart'; import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; -import 'package:privacyidea_authenticator/state_notifiers/push_request_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/push_request_notifier.dart'; import 'package:privacyidea_authenticator/utils/custom_int_buffer.dart'; import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; import 'package:privacyidea_authenticator/utils/push_provider.dart'; diff --git a/test/unit_test/state_notifiers/settings_notifier_test.dart b/test/unit_test/state_notifiers/settings_notifier_test.dart index f0eb7db10..0e5e80d71 100644 --- a/test/unit_test/state_notifiers/settings_notifier_test.dart +++ b/test/unit_test/state_notifiers/settings_notifier_test.dart @@ -5,7 +5,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:privacyidea_authenticator/interfaces/repo/settings_repository.dart'; import 'package:privacyidea_authenticator/model/states/settings_state.dart'; -import 'package:privacyidea_authenticator/state_notifiers/settings_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:mockito/annotations.dart'; import 'settings_notifier_test.mocks.dart'; diff --git a/test/unit_test/state_notifiers/sortable_notifier_test.dart b/test/unit_test/state_notifiers/sortable_notifier_test.dart index b7cb307e9..d77bb10bc 100644 --- a/test/unit_test/state_notifiers/sortable_notifier_test.dart +++ b/test/unit_test/state_notifiers/sortable_notifier_test.dart @@ -7,10 +7,10 @@ import 'package:privacyidea_authenticator/model/token_folder.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/state_notifiers/settings_notifier.dart'; -import 'package:privacyidea_authenticator/state_notifiers/sortable_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/riverpod/state_notifiers/settings_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/sortable_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; diff --git a/test/unit_test/state_notifiers/token_folder_notifier_test.dart b/test/unit_test/state_notifiers/token_folder_notifier_test.dart index b698ba594..d26b02c38 100644 --- a/test/unit_test/state_notifiers/token_folder_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_folder_notifier_test.dart @@ -5,7 +5,7 @@ import 'package:mockito/annotations.dart'; import 'package:privacyidea_authenticator/model/states/token_folder_state.dart'; import 'package:privacyidea_authenticator/model/token_folder.dart'; import 'package:privacyidea_authenticator/interfaces/repo/token_folder_repository.dart'; -import 'package:privacyidea_authenticator/state_notifiers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; import 'token_folder_notifier_test.mocks.dart'; diff --git a/test/unit_test/state_notifiers/token_notifier_test.dart b/test/unit_test/state_notifiers/token_notifier_test.dart index 7c562f2c5..e68a2f315 100644 --- a/test/unit_test/state_notifiers/token_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_notifier_test.dart @@ -16,8 +16,8 @@ import 'package:privacyidea_authenticator/model/states/token_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/state_notifiers/settings_notifier.dart'; -import 'package:privacyidea_authenticator/state_notifiers/token_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/firebase_utils.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; From 220d756418e3acfc480cd54aa1dee22c297bf24c Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:04:54 +0200 Subject: [PATCH 009/285] smartphone as a container --- lib/api/token_container_api_endpoint.dart | 35 +++-- .../notifier_provider_listener.dart | 3 + .../state_notifier_provider_listener.dart | 15 ++- .../deep_link_listener.dart | 1 + .../token_state_listener.dart | 1 + lib/model/push_request.dart | 2 +- lib/model/states/token_state.dart | 20 ++- lib/model/token_container.dart | 55 +++++++- lib/model/token_container.freezed.dart | 13 -- lib/model/tokens/day_password_token.dart | 2 +- lib/model/tokens/hotp_token.dart | 2 +- lib/model/tokens/otp_token.dart | 20 +-- lib/model/tokens/push_token.dart | 2 +- lib/model/tokens/token.dart | 6 +- lib/model/tokens/totp_token.dart | 11 +- .../container_credentials_processor.dart | 2 + lib/repo/secure_push_request_repository.dart | 3 +- ...brid_token_container_state_repository.dart | 9 +- ...mote_token_container_state_repository.dart | 32 +---- ...token_container_state_repository.dart.dart | 30 +++-- lib/utils/identifiers.dart | 2 +- lib/utils/logger.dart | 24 +++- lib/utils/privacyidea_io_client.dart | 16 +-- .../token_container_state_provider.dart | 122 ++++++++---------- .../token_container_state_provider.g.dart | 64 ++++----- .../home_widget_deep_link_listener.dart | 1 + .../home_widget_token_state_listener.dart | 6 +- .../navigation_deep_link_listener.dart | 1 + .../token_container_token_state_listener.dart | 18 ++- .../state_notifiers/sortable_notifier.dart | 2 + .../state_notifiers/token_notifier.dart | 43 +++--- lib/widgets/app_wrapper.dart | 15 ++- lib/widgets/app_wrappers/state_observer.dart | 3 + 33 files changed, 328 insertions(+), 253 deletions(-) diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index d16cc7f69..3f754b781 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -33,30 +33,47 @@ class TokenContainerApiEndpoint implements ApiEndpioint sync(TokenContainer containerState) async { - Logger.warning('Syncing container with server', name: 'TokenContainerApiEndpoint'); - if (_data.containsKey(containerState.serial) == false) { + Logger.info('Syncing container with server', name: 'TokenContainerApiEndpoint#sync'); + final serverTemplates = _data[containerState.serial]; + if (serverTemplates == null) { return containerState.copyTransformInto(args: {'message': 'Container not found'}); } - Logger.warning('Container found', name: 'TokenContainerApiEndpoint'); + for (var serial in serverTemplates.keys) { + final template = serverTemplates[serial]; + if (template?.serial == null) { + // Add serial(key of map) to template + serverTemplates[serial] = template!.copyAddAll({TOKEN_SERIAL: serial}); + } + } final localTemplates = containerState.localTokenTemplates; + Logger.debug('Local templates: ${localTemplates.length}', name: 'TokenContainerApiEndpoint#sync'); for (var localTemplate in localTemplates) { final oldLabel = localTemplate.data[URI_LABEL] as String; + Logger.debug('Old label: "$oldLabel" starts with "${containerState.serial}" ?', name: 'TokenContainerApiEndpoint#sync'); if (oldLabel.startsWith(containerState.serial) == true) { - final merged = localTemplate.copyAddAll({ - URI_LABEL: oldLabel.replaceRange(oldLabel.length - 2, oldLabel.length - 1, '😀'), + var merged = localTemplate.copyAddAll({ + URI_LABEL: oldLabel.replaceRange(oldLabel.length - 2, oldLabel.length, '😀'), }); - _data[containerState.serial]![localTemplate.id!] = merged; + Logger.debug('New label: "${merged.data[URI_LABEL]}"', name: 'TokenContainerApiEndpoint#sync'); + if (merged.serial == null) { + merged = merged.copyWith(data: merged.copyAddAll({TOKEN_SERIAL: 'tokenID${DateTime.now().millisecondsSinceEpoch}'}).data); + } + final localTemplateSerial = merged.serial!; + serverTemplates[localTemplateSerial] = merged; } } + Logger.debug('Server templates: ${serverTemplates}', name: 'TokenContainerApiEndpoint#sync'); + _data[containerState.serial] = serverTemplates; + Logger.debug('_data: $_data', name: 'TokenContainerApiEndpoint#sync'); final serverTemplatesMerged = _data[containerState.serial]!.values.toList(); - return TokenContainerSynced( + final newContainerState = TokenContainerSynced( lastSyncAt: DateTime.now(), serial: containerState.serial, description: 'Synced with server', syncedTokenTemplates: serverTemplatesMerged, localTokenTemplates: [], ); + Logger.debug('Synced container: $newContainerState', name: 'TokenContainerApiEndpoint#sync'); + return newContainerState; } - - } diff --git a/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart b/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart index d50431fdb..62c934d67 100644 --- a/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart @@ -3,11 +3,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../../../utils/logger.dart'; + abstract class AsyncNotifierProviderListener, S> { final AutoDisposeAsyncNotifierProviderImpl? provider; final void Function(AsyncValue? previous, AsyncValue next)? onNewState; const AsyncNotifierProviderListener({this.provider, this.onNewState}); void buildListen(WidgetRef ref) { + Logger.debug('Listening to $provider', name: 'AsyncNotifierProviderListener#buildListen'); if (provider == null || onNewState == null) return; ref.listen(provider!, onNewState!); } diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart index 252a7a80f..ba949d82f 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart @@ -1,12 +1,17 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; abstract class StateNotifierProviderListener, S> { - final StateNotifierProvider? provider; - final void Function(S? previous, S next)? onNewState; - const StateNotifierProviderListener({this.provider, this.onNewState}); + final String listenerName; + final StateNotifierProvider provider; + final void Function(S? previous, S next) onNewState; + const StateNotifierProviderListener({required this.provider, required this.onNewState, required this.listenerName}); void buildListen(WidgetRef ref) { - if (provider == null || onNewState == null) return; - ref.listen(provider!, onNewState!); + Logger.debug('("$listenerName") listening to provider ("$provider")', name: 'StateNotifierProviderListener#buildListen'); + ref.listen(provider, (previous, next) { + WidgetsBinding.instance.addPostFrameCallback((_) => onNewState(previous, next)); + }); } } diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart index 1d3937fbd..09748d441 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart @@ -8,5 +8,6 @@ abstract class DeepLinkListener extends StateNotifierProviderListener deeplinkProvider, required super.onNewState, + required super.listenerName, }) : super(provider: deeplinkProvider); } diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart index 28b596b20..aa65ae8bb 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart @@ -8,5 +8,6 @@ abstract class TokenStateListener extends StateNotifierProviderListener tokenProvider, required super.onNewState, + required super.listenerName, }) : super(provider: tokenProvider); } diff --git a/lib/model/push_request.dart b/lib/model/push_request.dart index 1d5949c4f..7b1983be4 100644 --- a/lib/model/push_request.dart +++ b/lib/model/push_request.dart @@ -129,7 +129,6 @@ class PushRequest { if (data[PUSH_REQUEST_NONCE] is! String) { throw ArgumentError('Push request nonce is ${data[PUSH_REQUEST_NONCE].runtimeType}. Expected String.'); } - print('Nonce: ${data[PUSH_REQUEST_NONCE]}'); if (data[PUSH_REQUEST_SSL_VERIFY] is! String) { throw ArgumentError('Push request sslVerify is ${data[PUSH_REQUEST_SSL_VERIFY].runtimeType}. Expected String.'); } @@ -142,6 +141,7 @@ class PushRequest { if (data[PUSH_REQUEST_ANSWERS] is! String?) { throw ArgumentError('Push request answers is ${data[PUSH_REQUEST_ANSWERS].runtimeType}. Expected List or null.'); } + Logger.debug('Push request data ($data) is valid.', name: 'push_request.dart#verifyData'); } Future verifySignature(PushToken token, {LegacyUtils legacyUtils = const LegacyUtils(), RsaUtils rsaUtils = const RsaUtils()}) async { diff --git a/lib/model/states/token_state.dart b/lib/model/states/token_state.dart index d8ae9be4b..07ccea17e 100644 --- a/lib/model/states/token_state.dart +++ b/lib/model/states/token_state.dart @@ -29,9 +29,11 @@ class TokenState { : tokens = List.from(tokens), lastlyUpdatedTokens = List.from(lastlyUpdatedTokens ?? tokens); - - - List get tokensNotInContainer => tokens.maybePiTokens.where((token) => token.containerId != null).toList(); + List get tokensNotInContainer { + final tokensNotInContainer = tokens.maybePiTokens.where((token) => token.containerId != null).toList(); + Logger.debug('${tokensNotInContainer.length}/${tokens.length} tokens not in container', name: 'token_state.dart#tokensNotInContainer'); + return tokensNotInContainer; + } PushToken? getTokenBySerial(String serial) => pushTokens.firstWhereOrNull((element) => element.serial == serial); @@ -150,7 +152,12 @@ class TokenState { extension TokenListExtension on List { List get nonPiTokens => where((token) => token.isPrivacyIdeaToken == false).toList(); - List get maybePiTokens => where((token) => token.isPrivacyIdeaToken == null).toList(); + List get maybePiTokens { + final maybePiTokens = where((token) => token.isPrivacyIdeaToken == null).toList(); + Logger.debug('${maybePiTokens.length}/$length tokens with "isPrivacyIdeaToken" == null', name: 'token_state.dart#maybePiTokens'); + return maybePiTokens; + } + List get piTokens => where((token) => token.isPrivacyIdeaToken == true).toList(); List inFolder(TokenFolder folder, {List only = const [], List exclude = const []}) => where((token) { if (token.folderId != folder.folderId) return false; @@ -167,10 +174,11 @@ extension TokenListExtension on List { }).toList(); List fromContainer(String containerId) { - return where((token) { - Logger.warning('token.origin?.source: ${token.origin?.source} && token.containerId: ${token.containerId}', name: 'token_state.dart#fromContainer'); + final filtered = where((token) { return token.origin?.source == TokenOriginSourceType.container && token.containerId == containerId; }).toList(); + Logger.debug('${filtered.length}/$length tokens with containerId: $containerId', name: 'token_state.dart#fromContainer'); + return filtered; } List toTemplates() { diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index 91e304a11..d10e6b358 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -158,23 +158,68 @@ class TokenTemplate with _$TokenTemplate { required Map data, }) = _TokenTemplate; - String? get id { - final id = data[TOKEN_ID]; - if (id is! String?) { + String? get serial { + final serial = data[URI_SERIAL]; + if (serial is! String?) { Logger.error('TokenTemplate id is not a string'); } - return id; + return serial; } + @override + operator ==(Object other) { + if (other is! TokenTemplate) { + print('other is not TokenTemplate'); + return false; + } + if (data.length != other.data.length) { + print('data length is not equal'); + return false; + } + for (var key in data.keys) { + if (data[key].toString() != other.data[key].toString()) { + print('data[$key] (${data[key]}) is not equal to other.data[$key] (${other.data[key]})'); + return false; + } + } + print('TokenTemplate is equal'); + return true; + } + + + factory TokenTemplate.fromJson(Map json) => _$TokenTemplateFromJson(json); + String get label => data[URI_LABEL] ?? 'No label'; + Token toToken(TokenContainer container) => Token.fromUriMap(data).copyWith( containerId: () => container.serial, origin: TokenOriginData( appName: '${container.serverName} ${container.serial}', data: data.toString(), source: TokenOriginSourceType.container, + isPrivacyIdeaToken: true, ), ); - TokenTemplate copyAddAll(Map data) => TokenTemplate(data: this.data..addAll(data)); + + /// Adds all key/value pairs of [other] to this map. + /// + /// If a key of [other] is already in this map, its value is overwritten. + /// + /// The operation is equivalent to doing `this[key] = value` for each key + /// and associated value in other. It iterates over [other], which must + /// therefore not change during the iteration. + /// ```dart + /// final planets = {1: 'Mercury', 2: 'Earth'}; + /// planets.addAll({5: 'Jupiter', 6: 'Saturn'}); + /// print(planets); // {1: Mercury, 2: Earth, 5: Jupiter, 6: Saturn} + /// ``` + TokenTemplate copyAddAll(Map addData) { + final newData = Map.from(data)..addAll(addData); + return TokenTemplate(data: newData); + } + + @override + int get hashCode => Object.hashAllUnordered(data.keys.map((key) => '$key:${data[key]}')); + } diff --git a/lib/model/token_container.freezed.dart b/lib/model/token_container.freezed.dart index b04d048ee..0df17a59d 100644 --- a/lib/model/token_container.freezed.dart +++ b/lib/model/token_container.freezed.dart @@ -3127,19 +3127,6 @@ class _$TokenTemplateImpl extends _TokenTemplate with DiagnosticableTreeMixin { ..add(DiagnosticsProperty('data', data)); } - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TokenTemplateImpl && - const DeepCollectionEquality().equals(other._data, _data)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => - Object.hash(runtimeType, const DeepCollectionEquality().hash(_data)); - @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index a8e289a0e..486e61ba3 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -134,7 +134,7 @@ class DayPasswordToken extends OTPToken { return copyWith( label: uriMap[URI_LABEL], issuer: uriMap[URI_ISSUER], - id: uriMap[TOKEN_ID], + id: uriMap[TOKEN_SERIAL], algorithm: uriMap[URI_ALGORITHM] != null ? Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()) : null, digits: uriMap[URI_DIGITS], secret: uriMap[URI_SECRET] != null ? Encodings.base32.encode(uriMap[URI_SECRET]) : null, diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index af908f689..5cf7e4935 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -109,7 +109,7 @@ class HOTPToken extends OTPToken { return copyWith( label: uriMap[URI_LABEL], issuer: uriMap[URI_ISSUER], - id: uriMap[TOKEN_ID], + id: uriMap[TOKEN_SERIAL], algorithm: uriMap[URI_ALGORITHM] != null ? Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()) : null, digits: uriMap[URI_DIGITS], secret: uriMap[URI_SECRET] != null ? Encodings.base32.encode(uriMap[URI_SECRET]) : null, diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index bcec6afcf..0a01af03c 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -1,9 +1,7 @@ -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; import '../../utils/logger.dart'; import '../enums/algorithms.dart'; -import '../enums/encodings.dart'; import '../token_import/token_origin_data.dart'; import 'token.dart'; @@ -70,26 +68,12 @@ abstract class OTPToken extends Token { return 'OTP${super.toString()}algorithm: $algorithm, digits: $digits, pin: $pin, '; } - /// This is used to create a map that typically was created from a uri. - /// ```dart - /// ------------------------------ [OTPToken] ------------------------------ - /// URI_SECRET: base32 encoded string (String), - /// URI_ALGORITHM: algorithm name e.g. SHA1 (String), - /// URI_DIGITS: number of digits (int), - /// ------------------------------- [Token] --------------------------------- - /// URI_LABEL: name of the token (String), - /// URI_ISSUER: name of the issuer (String), - /// URI_PIN: is the user forced to have a pin (bool), - /// URI_IMAGE: url to an image e.g. "https://example.com/image.png" (String), - /// URI_ORIGIN: json string of the origin class (String), - /// ------------------------------------------------------------------------- - /// ``` + @override Map toUriMap() { - print(secret); return super.toUriMap() ..addAll({ - URI_SECRET: Encodings.base32.decode(secret), + URI_SECRET: secret, URI_ALGORITHM: algorithm.name, URI_DIGITS: digits, }); diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index b5cf38363..6faf0e45a 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -175,7 +175,7 @@ class PushToken extends Token { serial: uriMap[URI_SERIAL], label: uriMap[URI_LABEL], issuer: uriMap[URI_ISSUER], - id: uriMap[TOKEN_ID], + id: uriMap[TOKEN_SERIAL], sslVerify: uriMap[URI_SSL_VERIFY], enrollmentCredentials: uriMap[URI_ENROLLMENT_CREDENTIAL], url: uriMap[URI_ROLLOUT_URL], diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index 067c3e0c3..11b22d276 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:privacyidea_authenticator/model/token_container.dart'; @@ -140,13 +138,13 @@ abstract class Token with SortableMixin { /// ``` Map toUriMap() { return { - URI_SERIAL: id, + URI_SERIAL: serial, URI_TYPE: type, URI_LABEL: label, URI_ISSUER: issuer, URI_PIN: pin, if (tokenImage != null) URI_IMAGE: tokenImage, - if (origin != null) URI_ORIGIN: jsonEncode(origin!.toJson()) + if (origin != null) URI_ORIGIN: origin!, }; } diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index 5bc3e4a64..15dc71b32 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -115,7 +115,7 @@ class TOTPToken extends OTPToken { return copyWith( label: uriMap[URI_LABEL], issuer: uriMap[URI_ISSUER], - id: uriMap[TOKEN_ID], + id: uriMap[TOKEN_SERIAL], algorithm: uriMap[URI_ALGORITHM] != null ? Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()) : null, digits: uriMap[URI_DIGITS], tokenImage: uriMap[URI_IMAGE], @@ -135,6 +135,7 @@ class TOTPToken extends OTPToken { factory TOTPToken.fromUriMap(Map uriMap) { validateUriMap(uriMap); return TOTPToken( + serial: uriMap[URI_SERIAL], label: uriMap[URI_LABEL] ?? '', issuer: uriMap[URI_ISSUER] ?? '', id: const Uuid().v4(), @@ -175,6 +176,14 @@ class TOTPToken extends OTPToken { /// Validates the uriMap for the required fields throws [LocalizedArgumentError] if a field is missing or invalid. static void validateUriMap(Map uriMap) { + if (uriMap[URI_SERIAL] is! String?) { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: 'Serial must be a string', + invalidValue: uriMap[URI_SERIAL], + name: URI_SERIAL, + ); + } if (uriMap[URI_SECRET] == null) { throw LocalizedArgumentError( localizedMessage: ((localizations, value, name) => localizations.secretIsRequired), diff --git a/lib/processors/scheme_processors/container_credentials_processor.dart b/lib/processors/scheme_processors/container_credentials_processor.dart index 62dc5a81f..fd92d9b34 100644 --- a/lib/processors/scheme_processors/container_credentials_processor.dart +++ b/lib/processors/scheme_processors/container_credentials_processor.dart @@ -1,5 +1,6 @@ import 'package:privacyidea_authenticator/processors/scheme_processors/scheme_processor_interface.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; import '../../model/tokens/container_credentials.dart'; @@ -16,6 +17,7 @@ class ContainerCredentialsProcessor extends SchemeProcessor { return null; } final credential = ContainerCredential.fromUriMap(uri.queryParameters); + Logger.warning('Adding credential to container', name: 'ContainerCredentialsProcessor'); globalRef?.read(credentialsProvider.notifier).addCredential(credential); } } diff --git a/lib/repo/secure_push_request_repository.dart b/lib/repo/secure_push_request_repository.dart index ee3817550..20143cac7 100644 --- a/lib/repo/secure_push_request_repository.dart +++ b/lib/repo/secure_push_request_repository.dart @@ -9,6 +9,7 @@ import '../interfaces/repo/push_request_repository.dart'; import '../model/push_request.dart'; import '../model/states/push_request_state.dart'; import '../utils/custom_int_buffer.dart'; +import '../utils/logger.dart'; class SecurePushRequestRepository implements PushRequestRepository { const SecurePushRequestRepository(); @@ -30,7 +31,7 @@ class SecurePushRequestRepository implements PushRequestRepository { Future saveState(PushRequestState pushRequestState) => protect(() => _saveState(pushRequestState)); Future _saveState(PushRequestState pushRequestState) async { final stateJson = jsonEncode(pushRequestState.toJson()); - print('Saving state: $stateJson'); + Logger.debug('Saving state: $stateJson', name: 'SecurePushRequestRepository'); await _storage.write(key: _securePushRequestKey, value: stateJson); } diff --git a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart index 3a18918a5..df79df563 100644 --- a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart @@ -60,8 +60,13 @@ class HybridTokenContainerRepository(); return _localRepository.saveContainerState(newState); } diff --git a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart index d42f6363d..b890cbeb0 100644 --- a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart @@ -17,41 +17,17 @@ class RemoteTokenContainerRepository implements TokenContainerRepository { Future saveContainerState(TokenContainer containerState) async => await _saveContainerState(containerState); Future _saveContainerState(TokenContainer containerState) async { - Logger.warning('Saving container state', name: 'RemoteTokenContainerRepository'); - try { - return await _protect(() async { - var synced = await apiEndpoint.sync(containerState); - return synced; - }); - } catch (e) { - rethrow; - } + Logger.info('Saving container state', name: 'RemoteTokenContainerRepository'); + return await _protect(() async => await apiEndpoint.sync(containerState)); } @override Future loadContainerState() { - Logger.warning('Loading container state', name: 'RemoteTokenContainerRepository'); + Logger.info('Loading container state', name: 'RemoteTokenContainerRepository'); return _fetchContainerState(); } Future _fetchContainerState() async => await _protect(() async => await apiEndpoint.fetch()); - // @override - // Future loadTokenTemplate(String tokenTemplateId) async { - // final state = await loadContainerState(); - // final template = state.tokenTemplates.firstWhereOrNull((element) => element.id == tokenTemplateId); - // return template; - // } - - // @override - // Future saveTokenTemplate(TokenTemplate tokenTemplate) async { - // final state = await loadContainerState(); - // if (templateIndex == -1) { - // state.tokenTemplates.add(tokenTemplate); - // } else { - // state.tokenTemplates[templateIndex] = tokenTemplate; - // } - // await saveContainerState(state); - // return tokenTemplate; - // } + } diff --git a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart index 8188c9459..ed5b4a1cd 100644 --- a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart +++ b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart @@ -20,11 +20,13 @@ class SecureTokenContainerRepository implements TokenContainerRepository { }); Future _write(String key, String value) => _protect(() { - Logger.warning('Writing to secure storage: $key, $value', name: 'SecureTokenContainerRepository'); + Logger.debug('Writing key: $key, value: $value', name: 'secure_token_container_state_repository.dart.dart#_write'); return _storage.write(key: key, value: value); }); Future _read(String key) async { - return await _protect(() async => await _storage.read(key: key)); + final value = await _protect(() async => await _storage.read(key: key)); + Logger.debug('Reading key: $key, value: $value', name: 'secure_token_container_state_repository.dart.dart#_read'); + return value; } Future _delete(String key) => _protect(() => _storage.delete(key: key)); @@ -37,17 +39,15 @@ class SecureTokenContainerRepository implements TokenContainerRepository { @override Future saveContainerState(TokenContainer containerState) async { + Logger.info('Saving container state', name: 'secure_token_container_state_repository.dart.dart#saveContainerState'); if (TokenContainer is TokenContainerError) { - Logger.error('Cannot save error state to repository', name: 'SecureTokenContainerRepository'); + Logger.error('Cannot save error state to repository', name: 'secure_token_container_state_repository.dart.dart#saveContainerState'); return containerState; } - Logger.warning('Saving container state', name: 'SecureTokenContainerRepository'); final json = containerState.toJson(); - Logger.warning('Encoded container state: $json', name: 'SecureTokenContainerRepository'); final jsonString = jsonEncode(json); - Logger.warning('Encoded container state string: $jsonString', name: 'SecureTokenContainerRepository'); await _write(_containerStateKey, jsonString); - Logger.warning('Saved container state: $jsonString', name: 'SecureTokenContainerRepository'); + Logger.debug('Saved container state: $jsonString', name: 'secure_token_container_state_repository.dart.dart#saveContainerState'); return containerState; } @@ -55,24 +55,26 @@ class SecureTokenContainerRepository implements TokenContainerRepository { /// Load the container state from the shared preferences Future loadContainerState() async { - Logger.warning('Loading container state', name: 'SecureTokenContainerRepository'); + Logger.info('Loading container state', name: 'secure_token_container_state_repository.dart.dart#loadContainerState'); String? containerStateJsonString = await _read(_containerStateKey); - Logger.warning('Loaded container state: $containerStateJsonString', name: 'SecureTokenContainerRepository'); + Logger.debug('Loaded jsonString: $containerStateJsonString', name: 'secure_token_container_state_repository.dart.dart#loadContainerState'); if (containerStateJsonString == null) { - Logger.info('No container state found in secure storage', name: 'SecureTokenContainerRepository'); return const TokenContainer.uninitialized(serial: '123'); } final json = jsonDecode(containerStateJsonString); - Logger.warning('Decoded container state: $json', name: 'SecureTokenContainerRepository'); try { final state = TokenContainer.fromJson(json); + Logger.debug('Loaded container state: $containerStateJsonString', name: 'secure_token_container_state_repository.dart.dart#loadContainerState'); return state; } catch (e) { - Logger.error('Failed to decode container state', name: 'SecureTokenContainerRepository'); + Logger.error( + 'Failed to decode container state', + name: 'secure_token_container_state_repository.dart.dart#loadContainerState', + error: e, + stackTrace: StackTrace.current, + ); await _delete(_containerStateKey); return const TokenContainer.uninitialized(serial: '123'); } } - - } diff --git a/lib/utils/identifiers.dart b/lib/utils/identifiers.dart index 01396dc26..14a0d0ae1 100644 --- a/lib/utils/identifiers.dart +++ b/lib/utils/identifiers.dart @@ -66,7 +66,7 @@ const String PUSH_REQUEST_SIGNATURE = 'signature'; // 7. const String PUSH_REQUEST_ANSWERS = 'require_presence'; // 8. // TokenContainer: -const String TOKEN_ID = 'id'; +const String TOKEN_SERIAL = 'serial'; const String GLOBAL_SECURE_REPO_PREFIX = 'app_v3_'; diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index 6da325588..46a897b99 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -21,6 +21,7 @@ import 'riverpod/riverpod_providers/state_notifier_providers/settings_provider.d final provider = Provider((ref) => 0); + class Logger { /*----------- STATIC FIELDS & GETTER -----------*/ static final Mutex _mutexWriteFile = Mutex(); @@ -30,6 +31,9 @@ class Logger { static printer.Logger print = printer.Logger( printer: printer.PrettyPrinter( methodCount: 0, + levelColors: { + printer.Level.debug: const printer.AnsiColor.fg(040), + }, colors: true, printEmojis: true, printTime: false, @@ -131,6 +135,18 @@ class Logger { _printWarning(warningString); } + /// Does nothing if in production/release mode + static void debug(String message, {dynamic error, dynamic stackTrace, String? name, bool verbose = false}) { + if (!kDebugMode) return; + String debugString = instance._convertLogToSingleString( + message, + stackTrace: stackTrace ?? (instance._verbose || verbose) ? StackTrace.current : null, + name: name, + logLevel: LogLevel.DEBUG, + ); + _printDebug(debugString); + } + static void warning(String message, {dynamic error, dynamic stackTrace, String? name, bool verbose = false}) => instance.logWarning(message, error: error, stackTrace: stackTrace, name: name, verbose: verbose); @@ -167,8 +183,6 @@ class Logger { await file.writeAsString('\n$fileMessage', mode: FileMode.append); } catch (e) { _printError(e.toString()); - } finally { - _print('Message logged into file'); } _mutexWriteFile.release(); } @@ -281,6 +295,11 @@ Device Parameters $deviceInfo"""; print.i(message); } + static void _printDebug(String message) { + if (!kDebugMode) return; + print.d(message); + } + static void _printWarning(String message) { if (!kDebugMode) return; print.w(message); @@ -361,6 +380,7 @@ final filterParameterKeys = ['fbtoken', 'new_fb_token', 'secret']; enum LogLevel { INFO, + DEBUG, WARNING, ERROR, } diff --git a/lib/utils/privacyidea_io_client.dart b/lib/utils/privacyidea_io_client.dart index 6ec0d8e4f..19b663137 100644 --- a/lib/utils/privacyidea_io_client.dart +++ b/lib/utils/privacyidea_io_client.dart @@ -114,17 +114,17 @@ class PrivacyideaIOClient { try { response = await ioClient.post(url, body: body).timeout(const Duration(seconds: 15)); } on HandshakeException catch (e, _) { - Logger.info('Handshake failed. sslVerify: $sslVerify', name: 'utils.dart#doPost'); + Logger.warning('Handshake failed. sslVerify: $sslVerify', name: 'utils.dart#doPost'); showMessage(message: 'Handshake failed, please check the server certificate and try again.'); response = Response('${e.runtimeType}', 525); } on TimeoutException catch (e, _) { - Logger.info('TimeoutException', name: 'utils.dart#doPost'); + Logger.info('Post request timed out', name: 'utils.dart#doPost'); response = Response('${e.runtimeType}', 408); } on SocketException catch (e, _) { - Logger.info('SocketException', name: 'utils.dart#doPost'); + Logger.info('Post request failed', name: 'utils.dart#doPost'); response = Response('${e.runtimeType}', 404); } catch (e, _) { - Logger.info('Unknown exception', name: 'utils.dart#doPost'); + Logger.warning('Something unexpected happened', name: 'utils.dart#doPost'); response = Response('${e.runtimeType}', 404); } @@ -174,17 +174,17 @@ class PrivacyideaIOClient { try { response = await ioClient.get(uri).timeout(const Duration(seconds: 15)); } on HandshakeException catch (e, _) { - Logger.info('Handshake failed. sslVerify: $sslVerify', name: 'utils.dart#doGet'); + Logger.warning('Handshake failed. sslVerify: $sslVerify', name: 'utils.dart#doGet'); showMessage(message: 'Handshake failed, please check the server certificate and try again.'); response = Response('${e.runtimeType}', 525); } on TimeoutException catch (e, _) { - Logger.info('TimeoutException', name: 'utils.dart#doGet'); + Logger.info('Get request timed out', name: 'utils.dart#doGet'); response = Response('${e.runtimeType}', 408); } on SocketException catch (e, _) { - Logger.info('SocketException', name: 'utils.dart#doGet'); + Logger.info('Get request failed', name: 'utils.dart#doGet'); response = Response('${e.runtimeType}', 404); } catch (e, _) { - Logger.info('Unknown exception', name: 'utils.dart#doGet'); + Logger.warning('Something unexpected happened', name: 'utils.dart#doGet'); response = Response('${e.runtimeType}', 404); } diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart index 452c9eac5..fd9286bd7 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart @@ -4,8 +4,8 @@ import 'package:collection/collection.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:mutex/mutex.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:privacyidea_authenticator/interfaces/repo/container_repository.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../../api/token_container_api_endpoint.dart'; @@ -26,20 +26,17 @@ class TokenContainerProvider extends _$TokenContainerProvider { final Mutex _repoMutex = Mutex(); // Mutex to protect the repository from being accessed while still waiting for the newest state to be delivered @override - Future build({ - required String containerSerial, + Future build({ + required ContainerCredential credential, }) async { + Logger.info('New tokenContainerStateProvider created', name: 'TokenContainerProvider#build'); await _stateMutex.acquire(); - final credential = ref.read(credentialsProvider).value?.credentialsOf(containerSerial); - if (credential == null) { - _stateMutex.release(); - return null; - } _repository = HybridTokenContainerRepository( - localRepository: SecureTokenContainerRepository(containerId: containerSerial), + localRepository: SecureTokenContainerRepository(containerId: credential.serial), remoteRepository: RemoteTokenContainerRepository(apiEndpoint: TokenContainerApiEndpoint(credential: credential)), ); final initialState = _repository.loadContainerState(); + Logger.debug('Initial state: $initialState', name: 'TokenContainerProvider#build'); _stateMutex.release(); return initialState; } @@ -67,24 +64,32 @@ class TokenContainerProvider extends _$TokenContainerProvider { return savedState; } - void addLocalTemplates(List maybePiTokenTemplates) { - throw UnimplementedError(); + /// Adds the given [maybePiTokenTemplates] to the localTokenTemplates of the container + /// and saves the new state to the repository. The rpository decides waht to do with the new state. + /// The saved state from the repo can contain the maybePiTokenTemplates or not. + Future tryAddLocalTemplates(List maybePiTokenTemplates) async { + Logger.info( + 'Trying to add (${maybePiTokenTemplates.length}) local templates to container (${credential.serial}).', + name: 'TokenContainerProvider#tryAddLocalTemplates', + ); + Logger.debug('Local templates (${maybePiTokenTemplates.length})', name: 'TokenContainerProvider#tryAddLocalTemplates'); + await _stateMutex.acquire(); + final oldState = (await future); + final newLocalTokenTemplates = [...maybePiTokenTemplates]; + final newState = oldState.copyWith(localTokenTemplates: newLocalTokenTemplates); + final savedState = await _saveToRepo(newState); + Logger.debug( + 'Saved TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', + name: 'TokenContainerProvider#tryAddLocalTemplates', + ); + ref.read(tokenProvider.notifier).updateContainerTokens(savedState); + state = AsyncValue.data(savedState); + _stateMutex.release(); + return savedState; } } -// final tokenContainerProvider = StateNotifierProvider.family((ref, containerId) { -// Logger.info("New tokenContainerStateProvider created", name: 'tokenContainerStateProvider'); -// final credentialsState = ref.watch(credentialsProvider); -// Logger.warning("credentialsState: $credentialsState", name: 'tokenContainerStateProvider'); -// return TokenContainerNotifier( -// ref: ref, -// repository: HybridTokenContainerRepository( -// localRepository: SecureTokenContainerRepository(containerId: 'local'), -// remoteRepository: RemoteTokenContainerRepository(apiEndpoint: TokenContainerApiEndpoint(credentialsState: credentialsState))), -// ); -// }); - -@riverpod +@Riverpod(keepAlive: true) class CredentialsProvider extends _$CredentialsProvider { final _stateMutex = Mutex(); final _repoMutex = Mutex(); @@ -93,9 +98,19 @@ class CredentialsProvider extends _$CredentialsProvider { @override Future build() async { _repo = SecureContainerCredentialsRepository(); + Logger.warning('Building credentialsProvider', name: 'CredentialsProvider'); return _repo.loadCredentialsState(); } + @override + Future update( + FutureOr Function(CredentialsState state) cb, { + FutureOr Function(Object, StackTrace)? onError, + }) async { + Logger.warning('Updating credentialsProvider', name: 'CredentialsProvider'); + return super.update(cb, onError: onError); + } + Future addCredential(ContainerCredential credential) async { await _stateMutex.acquire(); final newState = await _saveCredentialsToRepo(credential); @@ -109,11 +124,6 @@ class CredentialsProvider extends _$CredentialsProvider { } } -// final credentialsProvider = StateNotifierProvider((ref) { -// Logger.info("New credentialsProvider created", name: 'credentialsProvider'); -// return CredentialsNotifier(repo: MockContainerCredentialsRepository()); -// }); - class MockContainerCredentialsRepository extends ContainerCredentialsRepository { final state = CredentialsState(credentials: [ ContainerCredential( @@ -164,44 +174,6 @@ class CredentialsState { ContainerCredential? credentialsOf(String containerSerial) => credentials.firstWhereOrNull((credential) => credential.serial == containerSerial); } -class CredentialsNotifier extends StateNotifier { - final Mutex _repoMutex = Mutex(); - final ContainerCredentialsRepository _repo; - late Future initState = _initState(); - - Future _initState() async { - final credentials = await _repo.loadCredentialsState(); - state = credentials; - return credentials; - } - - CredentialsNotifier({required ContainerCredentialsRepository repo, CredentialsState? initialState}) - : _repo = repo, - super(initialState ?? const CredentialsState(credentials: [])) { - _initState(); - } - - Future addCredentials(ContainerCredential credentials) async { - final newState = await _saveCredentialsToRepo(credentials); - state = newState; - return newState; - } - - Future deleteCredentials(ContainerCredential credentials) async { - final newState = await _deleteCredentialsFromRepo(credentials); - state = newState; - return newState; - } - - Future _saveCredentialsToRepo(ContainerCredential credentials) async { - return await _repoMutex.protect(() async => await _repo.saveCredential(credentials)); - } - - Future _deleteCredentialsFromRepo(ContainerCredential credentials) async { - return await _repoMutex.protect(() async => await _repo.deleteCredential(credentials.id)); - } -} - class SecureContainerCredentialsRepository extends ContainerCredentialsRepository { String get containerCredentialsKey => 'containerCredentials'; String _keyOfId(String id) => '$containerCredentialsKey.$id'; @@ -211,18 +183,30 @@ class SecureContainerCredentialsRepository extends ContainerCredentialsRepositor Future _write(String key, String value) => _protect(() => _storage.write(key: key, value: value)); Future _read(String key) async => await _protect(() async => await _storage.read(key: key)); - Future> _readAll() async => await _protect(() async => await _storage.readAll()); + Future> _readAll() async => + await _protect(() async => (await _storage.readAll())..removeWhere((key, value) => !key.startsWith(containerCredentialsKey))); Future _delete(String key) => _protect(() => _storage.delete(key: key)); @override Future loadCredentialsState() async { final credentialsJsonString = await _readAll(); - if (credentialsJsonString.isEmpty) return const CredentialsState(credentials: []); + Logger.warning('Loaded credentials: $credentialsJsonString', name: 'SecureContainerCredentialsRepository'); + if (credentialsJsonString.isEmpty) { + final credentialState = CredentialsState(credentials: [ + ContainerCredential( + id: '123', + serial: '123', + ), + ]); + Logger.warning('Returning default credentials: $credentialState', name: 'SecureContainerCredentialsRepository'); + return credentialState; + } return CredentialsState.fromJsonStringList(credentialsJsonString.values.toList()); } @override Future saveCredentialsState(CredentialsState credentialsState) async { + Logger.warning('Saving credentials: $credentialsState', name: 'SecureContainerCredentialsRepository'); final futures = []; for (var credential in credentialsState.credentials) { futures.add(saveCredential(credential)); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart index 6cd551db5..603f3147b 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart @@ -23,7 +23,7 @@ Map _$CredentialsStateToJson(CredentialsState instance) => // ************************************************************************** String _$tokenContainerProviderHash() => - r'2a5840df5d339ba273d534ea610bdeb00ce20f89'; + r'c9d4677c538e4a3abe2574515fccbdb9b78bccee'; /// Copied from Dart SDK class _SystemHash { @@ -47,11 +47,11 @@ class _SystemHash { } abstract class _$TokenContainerProvider - extends BuildlessAutoDisposeAsyncNotifier { - late final String containerSerial; + extends BuildlessAutoDisposeAsyncNotifier { + late final ContainerCredential credential; - FutureOr build({ - required String containerSerial, + FutureOr build({ + required ContainerCredential credential, }); } @@ -60,16 +60,16 @@ abstract class _$TokenContainerProvider const tokenContainerProviderOf = TokenContainerProviderFamily(); /// See also [TokenContainerProvider]. -class TokenContainerProviderFamily extends Family> { +class TokenContainerProviderFamily extends Family> { /// See also [TokenContainerProvider]. const TokenContainerProviderFamily(); /// See also [TokenContainerProvider]. TokenContainerProviderProvider call({ - required String containerSerial, + required ContainerCredential credential, }) { return TokenContainerProviderProvider( - containerSerial: containerSerial, + credential: credential, ); } @@ -78,7 +78,7 @@ class TokenContainerProviderFamily extends Family> { covariant TokenContainerProviderProvider provider, ) { return call( - containerSerial: provider.containerSerial, + credential: provider.credential, ); } @@ -100,12 +100,12 @@ class TokenContainerProviderFamily extends Family> { /// See also [TokenContainerProvider]. class TokenContainerProviderProvider extends AutoDisposeAsyncNotifierProviderImpl { + TokenContainer> { /// See also [TokenContainerProvider]. TokenContainerProviderProvider({ - required String containerSerial, + required ContainerCredential credential, }) : this._internal( - () => TokenContainerProvider()..containerSerial = containerSerial, + () => TokenContainerProvider()..credential = credential, from: tokenContainerProviderOf, name: r'tokenContainerProviderOf', debugGetCreateSourceHash: @@ -115,7 +115,7 @@ class TokenContainerProviderProvider dependencies: TokenContainerProviderFamily._dependencies, allTransitiveDependencies: TokenContainerProviderFamily._allTransitiveDependencies, - containerSerial: containerSerial, + credential: credential, ); TokenContainerProviderProvider._internal( @@ -125,17 +125,17 @@ class TokenContainerProviderProvider required super.allTransitiveDependencies, required super.debugGetCreateSourceHash, required super.from, - required this.containerSerial, + required this.credential, }) : super.internal(); - final String containerSerial; + final ContainerCredential credential; @override - FutureOr runNotifierBuild( + FutureOr runNotifierBuild( covariant TokenContainerProvider notifier, ) { return notifier.build( - containerSerial: containerSerial, + credential: credential, ); } @@ -144,61 +144,61 @@ class TokenContainerProviderProvider return ProviderOverride( origin: this, override: TokenContainerProviderProvider._internal( - () => create()..containerSerial = containerSerial, + () => create()..credential = credential, from: from, name: null, dependencies: null, allTransitiveDependencies: null, debugGetCreateSourceHash: null, - containerSerial: containerSerial, + credential: credential, ), ); } @override AutoDisposeAsyncNotifierProviderElement createElement() { + TokenContainer> createElement() { return _TokenContainerProviderProviderElement(this); } @override bool operator ==(Object other) { return other is TokenContainerProviderProvider && - other.containerSerial == containerSerial; + other.credential == credential; } @override int get hashCode { var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, containerSerial.hashCode); + hash = _SystemHash.combine(hash, credential.hashCode); return _SystemHash.finish(hash); } } mixin TokenContainerProviderRef - on AutoDisposeAsyncNotifierProviderRef { - /// The parameter `containerSerial` of this provider. - String get containerSerial; + on AutoDisposeAsyncNotifierProviderRef { + /// The parameter `credential` of this provider. + ContainerCredential get credential; } class _TokenContainerProviderProviderElement extends AutoDisposeAsyncNotifierProviderElement with TokenContainerProviderRef { + TokenContainer> with TokenContainerProviderRef { _TokenContainerProviderProviderElement(super.provider); @override - String get containerSerial => - (origin as TokenContainerProviderProvider).containerSerial; + ContainerCredential get credential => + (origin as TokenContainerProviderProvider).credential; } String _$credentialsProviderHash() => - r'992a1e466ed24e4d0bc3eabbeaf33ff6ada90ee1'; + r'1cd835b458424a6ab8e1d60850ce455de9d0efa6'; /// See also [CredentialsProvider]. @ProviderFor(CredentialsProvider) -final credentialsProvider = AutoDisposeAsyncNotifierProvider< - CredentialsProvider, CredentialsState>.internal( +final credentialsProvider = + AsyncNotifierProvider.internal( CredentialsProvider.new, name: r'credentialsProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') @@ -208,6 +208,6 @@ final credentialsProvider = AutoDisposeAsyncNotifierProvider< allTransitiveDependencies: null, ); -typedef _$CredentialsProvider = AutoDisposeAsyncNotifier; +typedef _$CredentialsProvider = AsyncNotifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart b/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart index 60f9f8148..dcf47f411 100644 --- a/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart +++ b/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart @@ -7,6 +7,7 @@ class HomeWidgetDeepLinkListener extends DeepLinkListener { required super.deeplinkProvider, }) : super( onNewState: _onNewState, + listenerName: 'HomeWidgetProcessor().processUri', ); static void _onNewState(DeepLink? previous, DeepLink? next) { diff --git a/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart index 0cbf7eb5e..f434f2722 100644 --- a/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart @@ -7,7 +7,11 @@ import '../../../model/tokens/token.dart'; import '../../home_widget_utils.dart'; class HomeWidgetTokenStateListener extends TokenStateListener { - const HomeWidgetTokenStateListener({required super.tokenProvider}) : super(onNewState: _onNewState); + const HomeWidgetTokenStateListener({required super.tokenProvider}) + : super( + onNewState: _onNewState, + listenerName: 'HomeWidgetUtils().updateTokensIfLinked', + ); static void _onNewState(TokenState? previous, TokenState next) { final updateTokens = []; diff --git a/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart b/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart index 8abda01f1..8fee37380 100644 --- a/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart +++ b/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart @@ -11,6 +11,7 @@ class NavigationDeepLinkListener extends DeepLinkListener { onNewState: (DeepLink? previous, DeepLink? next) { _onNewState(previous, next); }, + listenerName: 'NavigationSchemeProcessor.processUriByAny', ) { _context = context; } diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index 25f6b5b38..f46874c0a 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -10,19 +10,25 @@ class ContainerListensToTokenState extends TokenStateListener { ContainerListensToTokenState({ required super.tokenProvider, required WidgetRef ref, - }) : super(onNewState: (TokenState? previous, TokenState next) { - WidgetsBinding.instance.addPostFrameCallback((_) { + }) : super( + onNewState: (TokenState? previous, TokenState next) => WidgetsBinding.instance.addPostFrameCallback((_) { _onNewState(previous, next, ref); - }); - }); + }), + listenerName: 'Container', + ); static Future _onNewState(TokenState? previousState, TokenState nextState, WidgetRef ref) async { Logger.warning('New token state', name: 'TokenContainerTokenStateListener'); final maybePiTokenTemplates = nextState.lastlyUpdatedTokens.maybePiTokens.toTemplates(); - final credentials = ref.read(credentialsProvider).value?.credentials ?? []; + Logger.warning(('Read credentials from credentialsProvider'), name: 'TokenContainerTokenStateListener'); + final credentials = (await ref.read(credentialsProvider.future)).credentials; + Logger.warning('Readed: $credentials', name: 'TokenContainerTokenStateListener'); + Logger.warning('maybePiTokenTemplates: $maybePiTokenTemplates', name: 'TokenContainerTokenStateListener'); if (maybePiTokenTemplates.isEmpty) return; for (var credential in credentials) { - ref.read(tokenContainerProviderOf(containerSerial: credential.serial).notifier).addLocalTemplates(maybePiTokenTemplates); + Logger.warning('Adding maybePiTokenTemplates (${maybePiTokenTemplates.length}) to container ${credential.serial}', + name: 'TokenContainerTokenStateListener'); + ref.read(tokenContainerProviderOf(credential: credential).notifier).tryAddLocalTemplates(maybePiTokenTemplates); } } } diff --git a/lib/utils/riverpod/state_notifiers/sortable_notifier.dart b/lib/utils/riverpod/state_notifiers/sortable_notifier.dart index b38964723..ee2d37893 100644 --- a/lib/utils/riverpod/state_notifiers/sortable_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/sortable_notifier.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; import '../../../model/extensions/sortable_list.dart'; import '../../../model/mixins/sortable_mixin.dart'; @@ -34,6 +35,7 @@ class SortableNotifier extends StateNotifier> { /// Handles a new list of [T]. /// First removes all elements of type [T] from the current state and then adds the new list. Future> handleNewStateList(List newList) async { + Logger.info('Handling new state list of type $T', name: 'SortableNotifier#handleNewStateList'); await _waitInit(); var newState = List.from(state); newState.removeWhere((element) => element is T); diff --git a/lib/utils/riverpod/state_notifiers/token_notifier.dart b/lib/utils/riverpod/state_notifiers/token_notifier.dart index c1c08dcd1..2fc962170 100644 --- a/lib/utils/riverpod/state_notifiers/token_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_notifier.dart @@ -115,8 +115,9 @@ class TokenNotifier extends StateNotifier { return failedTokens; } // [failedTokens] is empty, so every token was saved successfully and we dont need to filter the tokens - Logger.warning('State Updated', name: 'token_notifier.dart#_saveOrReplaceTokens', stackTrace: StackTrace.current); + Logger.info('Saved ${tokens.length} Tokens to storage.', name: 'token_notifier.dart#_saveOrReplaceTokens'); state = state.addOrReplaceTokens(tokens); + Logger.debug('New State: ${state.tokens.length} Tokens', name: 'token_notifier.dart#_saveOrReplaceTokens'); _loadingRepoMutex.release(); return []; } @@ -322,44 +323,45 @@ class TokenNotifier extends StateNotifier { /// Adds, Updates or Removes tokens based on the [TokenContainer] and returns the updated tokens. void updateContainerTokens(TokenContainer container) async { await initState; - Logger.warning('Updating tokens from container.', name: 'token_notifier.dart#updateContainerTokens'); + Logger.info('Updating tokens from container.', name: 'token_notifier.dart#updateContainerTokens'); final templatesToAdd = []; final templatesToUpdate = []; final templatesToRemove = []; final knownContainerTokens = state.tokens.fromContainer(container.serial); - Logger.warning('Known tokens: ${knownContainerTokens.length}', name: 'token_notifier.dart#updateContainerTokens'); final syncedTokenTemplates = container.syncedTokenTemplates; - Logger.warning('Synced tokens: ${syncedTokenTemplates.length}', name: 'token_notifier.dart#updateContainerTokens'); + Logger.debug('App knows server tokens: ${knownContainerTokens.length}/${syncedTokenTemplates.length}', name: 'token_notifier.dart#updateContainerTokens'); final knownContainerTemplates = knownContainerTokens.toTemplates(); for (var i = 0; i < syncedTokenTemplates.length; i++) { // Searches for tokens that are in the container but not in the app to add them. // If the token is already in the app, it will be updated. // Reduces the [tokenTemplatesApp] list to only the tokens that are in the app but not in the container. These tokens will be removed. final templateContainer = syncedTokenTemplates[i]; - final tokenToKeep = knownContainerTemplates.firstWhereOrNull((templateApp) => templateApp.id == templateContainer.id); - if (tokenToKeep == null) { + Logger.debug('Checking serial ${templateContainer.serial}', name: 'token_notifier.dart#updateContainerTokens'); + final templateToKeep = knownContainerTemplates.firstWhereOrNull((templateApp) => templateApp.serial == templateContainer.serial); + if (templateToKeep == null) { templatesToAdd.add(templateContainer); } else { - if (tokenToKeep != templateContainer) { + Logger.warning('templateToKeep != templateContainer => ${templateToKeep != templateContainer}', name: 'token_notifier.dart#updateContainerTokens'); + if (templateToKeep != templateContainer) { // Only update the token if the template is different - templatesToUpdate.add(tokenToKeep); + templatesToUpdate.add(templateToKeep); } - knownContainerTemplates.remove(tokenToKeep); + knownContainerTemplates.remove(templateToKeep); } } // Removes all tokens that are in the app but not in the container. templatesToRemove.addAll(knownContainerTemplates); - Logger.warning('Templates to add: ${templatesToAdd.length}', name: 'token_notifier.dart#updateContainerTokens'); - Logger.warning('Templates to update: ${templatesToUpdate.length}', name: 'token_notifier.dart#updateContainerTokens'); - Logger.warning('Templates to remove: ${templatesToRemove.length}', name: 'token_notifier.dart#updateContainerTokens'); + Logger.debug( + 'Add(${templatesToAdd.length}) | Check update(${templatesToUpdate.length}) | Remove(${templatesToRemove.length})', + name: 'token_notifier.dart#updateContainerTokens', + ); final tokensToAdd = templatesToAdd.map((e) => e.toToken(container)).toList(); - Logger.warning('Tokens to add: ${tokensToAdd.firstOrNull?.containerId}', name: 'token_notifier.dart#updateContainerTokens'); final tokensToUpdate = []; for (var template in templatesToUpdate) { - final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id); + final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.serial); if (token == null) continue; final needsUpdate = !token.doesMatchTemplate(template); if (needsUpdate) { @@ -368,24 +370,27 @@ class TokenNotifier extends StateNotifier { } final tokensToRemove = []; for (var template in templatesToRemove) { - final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id); + final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.serial); if (token == null) continue; tokensToRemove.add(token); } - Logger.warning('Tokens to add: ${tokensToAdd.length}', name: 'token_notifier.dart#updateContainerTokens'); + + Logger.debug( + 'Add(${tokensToAdd.length}) | Update(${tokensToUpdate.length}) | Remove(${tokensToRemove.length})', + name: 'token_notifier.dart#updateContainerTokens', + ); + if (tokensToAdd.isNotEmpty) { await addOrReplaceTokens(tokensToAdd); } - Logger.warning('Tokens to update: ${tokensToUpdate.length}', name: 'token_notifier.dart#updateContainerTokens'); if (tokensToUpdate.isNotEmpty) { await updateTokens(tokensToUpdate, (p0) { - final template = templatesToUpdate.firstWhereOrNull((element) => element.id == p0.id); + final template = templatesToUpdate.firstWhereOrNull((element) => element.serial == p0.id); if (template == null) return p0; return p0.copyWithFromTemplate(template); }); } - Logger.warning('Tokens to remove: ${tokensToRemove.length}', name: 'token_notifier.dart#updateContainerTokens'); if (tokensToRemove.isNotEmpty) { await removeTokens(tokensToRemove); } diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index c2abcc30b..535d9c6e4 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -88,7 +88,8 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { @override Widget build(BuildContext context) { - final credentials = ref.read(credentialsProvider).value?.credentials ?? []; + final credentials = ref.watch(credentialsProvider).value?.credentials ?? []; + Logger.debug('Credentials: $credentials', name: 'AppWrapper#build'); return SingleTouchRecognizer( child: StateObserver( stateNotifierProviderListeners: [ @@ -99,10 +100,12 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { ], asyncNotifierProviderListeners: [ ...credentials.map( - (credential) => TokenStateListensToContainer( - containerProvider: tokenContainerProviderOf(containerSerial: credential.serial), + (credential) { + return TokenStateListensToContainer( + containerProvider: tokenContainerProviderOf(credential: credential), ref: ref, - ), + ); + }, ), ], child: EasyDynamicThemeWidget( @@ -113,6 +116,8 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { } } + + class TokenStateListensToContainer extends AsyncContainerListener { final WidgetRef ref; TokenStateListensToContainer({ @@ -121,7 +126,7 @@ class TokenStateListensToContainer extends AsyncContainerListener { }) : super(onNewState: (previous, next) => _onNewState(previous, next, ref)); static Future _onNewState(AsyncValue? previous, AsyncValue next, WidgetRef ref) async { - Logger.warning('New TokenContainer', name: 'TokenStateTokenContainerListener'); + Logger.info('TokenState got new container state', name: 'TokenStateListensToContainer'); final value = next.value; if (value == null) return; final provider = ref.read(tokenProvider.notifier); diff --git a/lib/widgets/app_wrappers/state_observer.dart b/lib/widgets/app_wrappers/state_observer.dart index 7dd07b247..fc7da4586 100644 --- a/lib/widgets/app_wrappers/state_observer.dart +++ b/lib/widgets/app_wrappers/state_observer.dart @@ -17,6 +17,9 @@ class StateObserver extends ConsumerWidget { for (final listener in stateNotifierProviderListeners) { listener.buildListen(ref); } + for (final listener in asyncNotifierProviderListeners) { + listener.buildListen(ref); + } return child; } } From 6e4dc5c03b27597d93d8adbc2858efbb21d7c62b Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 29 Jul 2024 09:16:00 +0200 Subject: [PATCH 010/285] smartphone as a container --- lib/model/states/token_state.dart | 4 ++-- lib/model/token_container.dart | 2 +- lib/model/tokens/day_password_token.dart | 6 +++--- lib/model/tokens/day_password_token.g.dart | 4 ++-- lib/model/tokens/hotp_token.dart | 6 +++--- lib/model/tokens/hotp_token.g.dart | 4 ++-- lib/model/tokens/otp_token.dart | 4 ++-- lib/model/tokens/push_token.dart | 6 +++--- lib/model/tokens/push_token.g.dart | 4 ++-- lib/model/tokens/steam_token.dart | 6 +++--- lib/model/tokens/steam_token.g.dart | 4 ++-- lib/model/tokens/token.dart | 6 +++--- lib/model/tokens/totp_token.dart | 6 +++--- lib/model/tokens/totp_token.g.dart | 4 ++-- .../token_container_state_provider.g.dart | 2 +- 15 files changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/model/states/token_state.dart b/lib/model/states/token_state.dart index 07ccea17e..258392ed9 100644 --- a/lib/model/states/token_state.dart +++ b/lib/model/states/token_state.dart @@ -30,7 +30,7 @@ class TokenState { lastlyUpdatedTokens = List.from(lastlyUpdatedTokens ?? tokens); List get tokensNotInContainer { - final tokensNotInContainer = tokens.maybePiTokens.where((token) => token.containerId != null).toList(); + final tokensNotInContainer = tokens.maybePiTokens.where((token) => token.containerSerial != null).toList(); Logger.debug('${tokensNotInContainer.length}/${tokens.length} tokens not in container', name: 'token_state.dart#tokensNotInContainer'); return tokensNotInContainer; } @@ -175,7 +175,7 @@ extension TokenListExtension on List { List fromContainer(String containerId) { final filtered = where((token) { - return token.origin?.source == TokenOriginSourceType.container && token.containerId == containerId; + return token.origin?.source == TokenOriginSourceType.container && token.containerSerial == containerId; }).toList(); Logger.debug('${filtered.length}/$length tokens with containerId: $containerId', name: 'token_state.dart#fromContainer'); return filtered; diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index d10e6b358..967e25620 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -193,7 +193,7 @@ class TokenTemplate with _$TokenTemplate { String get label => data[URI_LABEL] ?? 'No label'; Token toToken(TokenContainer container) => Token.fromUriMap(data).copyWith( - containerId: () => container.serial, + containerSerial: () => container.serial, origin: TokenOriginData( appName: '${container.serverName} ${container.serial}', data: data.toString(), diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index 486e61ba3..d670743fa 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -30,7 +30,7 @@ class DayPasswordToken extends OTPToken { required super.algorithm, required super.digits, required super.secret, - super.containerId, + super.containerSerial, super.serial, this.viewMode = DayPasswordTokenViewMode.VALIDFOR, String? type, // just for @JsonSerializable(): type of DayPasswordToken is always TokenTypes.DAYPASSWORD @@ -73,7 +73,7 @@ class DayPasswordToken extends OTPToken { DayPasswordTokenViewMode? viewMode, String? label, String? issuer, - String? Function()? containerId, + String? Function()? containerSerial, String? id, Algorithms? algorithm, int? digits, @@ -92,7 +92,7 @@ class DayPasswordToken extends OTPToken { viewMode: viewMode ?? this.viewMode, label: label ?? this.label, issuer: issuer ?? this.issuer, - containerId: containerId != null ? containerId() : this.containerId, + containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, id: id ?? this.id, type: TokenTypes.DAYPASSWORD.name, algorithm: algorithm ?? this.algorithm, diff --git a/lib/model/tokens/day_password_token.g.dart b/lib/model/tokens/day_password_token.g.dart index e2eb0838a..072f69ff3 100644 --- a/lib/model/tokens/day_password_token.g.dart +++ b/lib/model/tokens/day_password_token.g.dart @@ -13,7 +13,7 @@ DayPasswordToken _$DayPasswordTokenFromJson(Map json) => algorithm: $enumDecode(_$AlgorithmsEnumMap, json['algorithm']), digits: (json['digits'] as num).toInt(), secret: json['secret'] as String, - containerId: json['containerId'] as String?, + containerSerial: json['containerSerial'] as String?, serial: json['serial'] as String?, viewMode: $enumDecodeNullable( _$DayPasswordTokenViewModeEnumMap, json['viewMode']) ?? @@ -34,7 +34,7 @@ DayPasswordToken _$DayPasswordTokenFromJson(Map json) => Map _$DayPasswordTokenToJson(DayPasswordToken instance) => { - 'containerId': instance.containerId, + 'containerSerial': instance.containerSerial, 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index 5cf7e4935..df1e7cbe7 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -25,7 +25,7 @@ class HOTPToken extends OTPToken { HOTPToken({ this.counter = 0, - super.containerId, + super.containerSerial, required super.id, required super.algorithm, required super.digits, @@ -66,7 +66,7 @@ class HOTPToken extends OTPToken { int? counter, String? label, String? issuer, - String? Function()? containerId, + String? Function()? containerSerial, String? id, Algorithms? algorithm, int? digits, @@ -84,7 +84,7 @@ class HOTPToken extends OTPToken { counter: counter ?? this.counter, label: label ?? this.label, issuer: issuer ?? this.issuer, - containerId: containerId != null ? containerId() : this.containerId, + containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, id: id ?? this.id, algorithm: algorithm ?? this.algorithm, digits: digits ?? this.digits, diff --git a/lib/model/tokens/hotp_token.g.dart b/lib/model/tokens/hotp_token.g.dart index 1de25d45f..bc83a0cda 100644 --- a/lib/model/tokens/hotp_token.g.dart +++ b/lib/model/tokens/hotp_token.g.dart @@ -8,7 +8,7 @@ part of 'hotp_token.dart'; HOTPToken _$HOTPTokenFromJson(Map json) => HOTPToken( counter: (json['counter'] as num?)?.toInt() ?? 0, - containerId: json['containerId'] as String?, + containerSerial: json['containerSerial'] as String?, id: json['id'] as String, algorithm: $enumDecode(_$AlgorithmsEnumMap, json['algorithm']), digits: (json['digits'] as num).toInt(), @@ -29,7 +29,7 @@ HOTPToken _$HOTPTokenFromJson(Map json) => HOTPToken( ); Map _$HOTPTokenToJson(HOTPToken instance) => { - 'containerId': instance.containerId, + 'containerSerial': instance.containerSerial, 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index 0a01af03c..7003bda0c 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -22,7 +22,7 @@ abstract class OTPToken extends Token { required this.secret, required super.id, required super.type, - super.containerId, + super.containerSerial, super.serial, super.pin, super.tokenImage, @@ -49,7 +49,7 @@ abstract class OTPToken extends Token { String? serial, String? label, String? issuer, - String? Function()? containerId, + String? Function()? containerSerial, String? id, Algorithms? algorithm, int? digits, diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index 6faf0e45a..ec7df008c 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -48,7 +48,7 @@ class PushToken extends Token { required String serial, super.label, super.issuer, - super.containerId, + super.containerSerial, required super.id, this.fbToken, this.url, @@ -100,7 +100,7 @@ class PushToken extends Token { String? label, String? serial, String? issuer, - String? Function()? containerId, + String? Function()? containerSerial, String? id, String? tokenImage, String? fbToken, @@ -127,7 +127,7 @@ class PushToken extends Token { issuer: issuer ?? this.issuer, tokenImage: tokenImage ?? this.tokenImage, fbToken: fbToken ?? this.fbToken, - containerId: containerId != null ? containerId() : this.containerId, + containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, id: id ?? this.id, pin: pin ?? this.pin, isLocked: isLocked ?? this.isLocked, diff --git a/lib/model/tokens/push_token.g.dart b/lib/model/tokens/push_token.g.dart index fbd52d4a4..922481066 100644 --- a/lib/model/tokens/push_token.g.dart +++ b/lib/model/tokens/push_token.g.dart @@ -10,7 +10,7 @@ PushToken _$PushTokenFromJson(Map json) => PushToken( serial: json['serial'] as String, label: json['label'] as String? ?? '', issuer: json['issuer'] as String? ?? '', - containerId: json['containerId'] as String?, + containerSerial: json['containerSerial'] as String?, id: json['id'] as String, fbToken: json['fbToken'] as String?, url: json['url'] == null ? null : Uri.parse(json['url'] as String), @@ -38,7 +38,7 @@ PushToken _$PushTokenFromJson(Map json) => PushToken( ); Map _$PushTokenToJson(PushToken instance) => { - 'containerId': instance.containerId, + 'containerSerial': instance.containerSerial, 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, diff --git a/lib/model/tokens/steam_token.dart b/lib/model/tokens/steam_token.dart index db1f5bafc..b7b7d2bfa 100644 --- a/lib/model/tokens/steam_token.dart +++ b/lib/model/tokens/steam_token.dart @@ -26,7 +26,7 @@ class SteamToken extends TOTPToken { SteamToken({ required super.id, required super.secret, - super.containerId, + super.containerSerial, super.serial, String? type, super.tokenImage, @@ -50,7 +50,7 @@ class SteamToken extends TOTPToken { String? serial, String? label, String? issuer, - String? Function()? containerId, + String? Function()? containerSerial, String? id, bool? isLocked, bool? isHidden, @@ -68,7 +68,7 @@ class SteamToken extends TOTPToken { serial: serial ?? this.serial, label: label ?? this.label, issuer: issuer ?? this.issuer, - containerId: containerId != null ? containerId() : this.containerId, + containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, id: id ?? this.id, secret: secret ?? this.secret, tokenImage: tokenImage ?? this.tokenImage, diff --git a/lib/model/tokens/steam_token.g.dart b/lib/model/tokens/steam_token.g.dart index 6b539159f..76487e26d 100644 --- a/lib/model/tokens/steam_token.g.dart +++ b/lib/model/tokens/steam_token.g.dart @@ -9,7 +9,7 @@ part of 'steam_token.dart'; SteamToken _$SteamTokenFromJson(Map json) => SteamToken( id: json['id'] as String, secret: json['secret'] as String, - containerId: json['containerId'] as String?, + containerSerial: json['containerSerial'] as String?, serial: json['serial'] as String?, type: json['type'] as String?, tokenImage: json['tokenImage'] as String?, @@ -27,7 +27,7 @@ SteamToken _$SteamTokenFromJson(Map json) => SteamToken( Map _$SteamTokenToJson(SteamToken instance) => { - 'containerId': instance.containerId, + 'containerSerial': instance.containerSerial, 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index 11b22d276..5e56dd749 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -16,7 +16,7 @@ import 'totp_token.dart'; abstract class Token with SortableMixin { bool? get isPrivacyIdeaToken => origin?.isPrivacyIdeaToken; final String tokenVersion = 'v1.0.0'; // The version of this token, this is used for serialization. - final String? containerId; // The id of the container this token belongs to. + final String? containerSerial; // The serial of the container this token belongs to. final String label; // the name of the token, it cannot be uses as an identifier final String issuer; // The issuer of this token, currently unused. final String id; // this is the identifier of the token @@ -60,7 +60,7 @@ abstract class Token with SortableMixin { this.serial, this.label = '', this.issuer = '', - this.containerId, + this.containerSerial, required this.id, required this.type, this.tokenImage, @@ -89,7 +89,7 @@ abstract class Token with SortableMixin { String? serial, String? label, String? issuer, - String? Function()? containerId, + String? Function()? containerSerial, String? id, bool? isLocked, bool? isHidden, diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index 15dc71b32..056fefc28 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -48,7 +48,7 @@ class TOTPToken extends OTPToken { required super.algorithm, required super.digits, required super.secret, - super.containerId, + super.containerSerial, super.serial, String? type, super.tokenImage, @@ -75,7 +75,7 @@ class TOTPToken extends OTPToken { String? serial, String? label, String? issuer, - String? Function()? containerId, + String? Function()? containerSerial, String? id, Algorithms? algorithm, int? digits, @@ -93,7 +93,7 @@ class TOTPToken extends OTPToken { serial: serial ?? this.serial, label: label ?? this.label, issuer: issuer ?? this.issuer, - containerId: containerId != null ? containerId() : this.containerId, + containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, id: id ?? this.id, algorithm: algorithm ?? this.algorithm, digits: digits ?? this.digits, diff --git a/lib/model/tokens/totp_token.g.dart b/lib/model/tokens/totp_token.g.dart index 066f77b1d..9af80575f 100644 --- a/lib/model/tokens/totp_token.g.dart +++ b/lib/model/tokens/totp_token.g.dart @@ -12,7 +12,7 @@ TOTPToken _$TOTPTokenFromJson(Map json) => TOTPToken( algorithm: $enumDecode(_$AlgorithmsEnumMap, json['algorithm']), digits: (json['digits'] as num).toInt(), secret: json['secret'] as String, - containerId: json['containerId'] as String?, + containerSerial: json['containerSerial'] as String?, serial: json['serial'] as String?, type: json['type'] as String?, tokenImage: json['tokenImage'] as String?, @@ -29,7 +29,7 @@ TOTPToken _$TOTPTokenFromJson(Map json) => TOTPToken( ); Map _$TOTPTokenToJson(TOTPToken instance) => { - 'containerId': instance.containerId, + 'containerSerial': instance.containerSerial, 'label': instance.label, 'issuer': instance.issuer, 'id': instance.id, diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart index 603f3147b..d04d716b6 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart @@ -23,7 +23,7 @@ Map _$CredentialsStateToJson(CredentialsState instance) => // ************************************************************************** String _$tokenContainerProviderHash() => - r'c9d4677c538e4a3abe2574515fccbdb9b78bccee'; + r'88f3953135b40aa4478987e3c41069ec94bc2a37'; /// Copied from Dart SDK class _SystemHash { From 5de32e6f3c331232b440170bbaa5970f71630838 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:48:57 +0200 Subject: [PATCH 011/285] token as a container --- lib/api/token_container_api_endpoint.dart | 13 +-- lib/model/states/token_state.dart | 29 ++++-- lib/model/token_container.dart | 81 +++++++++++++++- lib/model/tokens/day_password_token.dart | 9 +- lib/model/tokens/hotp_token.dart | 19 ++-- lib/model/tokens/otp_token.dart | 4 +- lib/model/tokens/push_token.dart | 8 +- lib/model/tokens/token.dart | 11 +-- lib/model/tokens/totp_token.dart | 33 ++++--- lib/utils/identifiers.dart | 9 +- lib/utils/logger.dart | 10 +- .../token_container_state_provider.dart | 29 +++++- .../token_container_token_state_listener.dart | 23 +++-- .../state_notifiers/token_notifier.dart | 96 ++++++++++++++----- 14 files changed, 281 insertions(+), 93 deletions(-) diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index 3f754b781..50fb27e4b 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -38,11 +38,11 @@ class TokenContainerApiEndpoint implements ApiEndpioint(args: {'message': 'Container not found'}); } - for (var serial in serverTemplates.keys) { - final template = serverTemplates[serial]; + for (var templateSerial in serverTemplates.keys) { + final template = serverTemplates[templateSerial]; if (template?.serial == null) { // Add serial(key of map) to template - serverTemplates[serial] = template!.copyAddAll({TOKEN_SERIAL: serial}); + serverTemplates[templateSerial] = template!.copyAddAll({URI_SERIAL: templateSerial}); } } final localTemplates = containerState.localTokenTemplates; @@ -52,17 +52,18 @@ class TokenContainerApiEndpoint implements ApiEndpioint tokens; final List lastlyUpdatedTokens; + final List lastlyDeletedTokens; List get otpTokens => tokens.whereType().toList(); bool get hasOTPTokens => otpTokens.isNotEmpty; @@ -25,9 +26,16 @@ class TokenState { List get pushTokensToRollOut => pushTokens.where((element) => !element.isRolledOut && element.rolloutState == PushTokenRollOutState.rolloutNotStarted).toList(); - TokenState({required List tokens, List? lastlyUpdatedTokens}) + List get maybePiTokens => tokens.maybePiTokens; + + TokenState({ + required List tokens, + List? lastlyUpdatedTokens, + List? lastlyDeletedTokens, + }) : tokens = List.from(tokens), - lastlyUpdatedTokens = List.from(lastlyUpdatedTokens ?? tokens); + lastlyUpdatedTokens = List.from(lastlyUpdatedTokens ?? tokens), + lastlyDeletedTokens = List.from(lastlyDeletedTokens ?? []); List get tokensNotInContainer { final tokensNotInContainer = tokens.maybePiTokens.where((token) => token.containerSerial != null).toList(); @@ -71,13 +79,16 @@ class TokenState { TokenState withoutToken(Token token) { final newTokens = List.from(tokens); newTokens.removeWhere((element) => element.id == token.id); - return TokenState(tokens: newTokens, lastlyUpdatedTokens: const []); + return TokenState(tokens: newTokens, lastlyUpdatedTokens: const [], lastlyDeletedTokens: [token]); } TokenState withoutTokens(List tokens) { final newTokens = List.from(this.tokens); - newTokens.removeWhere((element) => tokens.any((token) => token.id == element.id)); - return TokenState(tokens: newTokens, lastlyUpdatedTokens: const []); + newTokens.removeWhere((element) => tokens.any((token) { + Logger.debug('token.id ${token.id} == element.id ${element.id}', name: 'token_state.dart#withoutTokens'); + return token.id == element.id; + })); + return TokenState(tokens: newTokens, lastlyUpdatedTokens: const [], lastlyDeletedTokens: tokens); } // Add a token if it does not exist yet @@ -95,6 +106,8 @@ class TokenState { // Replace the token if it does exist // Do nothing if it does not exist + // Return the new state and a boolean = true if the token was replaced + // Return the old state and a boolean = false if the token was not replaced (TokenState, bool) replaceToken(Token token) { final newTokens = tokens.toList(); final index = newTokens.indexWhere((element) => element.id == token.id); @@ -173,11 +186,11 @@ extension TokenListExtension on List { return true; }).toList(); - List fromContainer(String containerId) { + List fromContainer(String containerSerial) { final filtered = where((token) { - return token.origin?.source == TokenOriginSourceType.container && token.containerSerial == containerId; + return token.origin?.source == TokenOriginSourceType.container && token.containerSerial == containerSerial; }).toList(); - Logger.debug('${filtered.length}/$length tokens with containerId: $containerId', name: 'token_state.dart#fromContainer'); + Logger.debug('${filtered.length}/$length tokens with containerSerial: $containerSerial', name: 'token_state.dart#fromContainer'); return filtered; } diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index 967e25620..30d643b16 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -1,6 +1,7 @@ // We need some unnecessary_overrides to force to add the fields in factory constructors // ignore_for_file: unnecessary_overrides +import 'package:collection/equality.dart'; import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; @@ -150,7 +151,6 @@ sealed class TokenContainer with _$TokenContainer { factory TokenContainer.fromJson(Map json) => _$TokenContainerFromJson(json); } - @freezed class TokenTemplate with _$TokenTemplate { TokenTemplate._(); @@ -158,6 +158,25 @@ class TokenTemplate with _$TokenTemplate { required Map data, }) = _TokenTemplate; + List get keys => data.keys.toList(); + List get values => data.values.toList(); + + String? get id { + final id = data[URI_ID]; + if (id is! String?) { + Logger.error('TokenTemplate id is not a string'); + } + return id; + } + + Uint8List? get secret { + final secret = data[URI_SECRET]; + if (secret is! Uint8List?) { + Logger.error('TokenTemplate secret is not a string'); + } + return secret; + } + String? get serial { final serial = data[URI_SERIAL]; if (serial is! String?) { @@ -166,6 +185,22 @@ class TokenTemplate with _$TokenTemplate { return serial; } + String? get type { + final type = data[URI_TYPE]; + if (type is! String?) { + Logger.error('TokenTemplate type is not a string'); + } + return type; + } + + String? get containerSerial { + final containerSerial = data[URI_CONTAINER_SERIAL]; + if (containerSerial is! String?) { + Logger.error('TokenTemplate containerSerial is not a string'); + } + return containerSerial; + } + @override operator ==(Object other) { if (other is! TokenTemplate) { @@ -186,8 +221,6 @@ class TokenTemplate with _$TokenTemplate { return true; } - - factory TokenTemplate.fromJson(Map json) => _$TokenTemplateFromJson(json); String get label => data[URI_LABEL] ?? 'No label'; @@ -222,4 +255,46 @@ class TokenTemplate with _$TokenTemplate { @override int get hashCode => Object.hashAllUnordered(data.keys.map((key) => '$key:${data[key]}')); + bool isSameTokenAs(TokenTemplate other) => id == other.id || serial == other.serial || const IterableEquality().equals(secret, other.secret); + + bool hasSameValuesAs(TokenTemplate serverTokenTemplate) { + Logger.debug('serverTokenTemplate.keys: ${serverTokenTemplate.keys}', name: 'TokenTemplate#hasSameValuesAs'); + for (var key in serverTokenTemplate.keys) { + if (data[key] is Iterable && serverTokenTemplate.data[key] is Iterable) { + if (!const IterableEquality().equals(data[key], serverTokenTemplate.data[key])) { + Logger.debug( + 'TokenTemplate has different values for key "$key": ${data[key]} != ${serverTokenTemplate.data[key]}', + name: 'TokenTemplate#hasSameValuesAs', + ); + return false; + } + Logger.debug( + '$key is iterable and has same values as serverTokenTemplate $key', + name: 'TokenTemplate#hasSameValuesAs', + ); + continue; + } + if (data[key] != serverTokenTemplate.data[key]) { + Logger.debug('TokenTemplate has different values for key "$key": ${data[key]} != ${serverTokenTemplate.data[key]}', + name: 'TokenTemplate#hasSameValuesAs'); + return false; + } + Logger.debug( + 'TokenTemplate has same values for key "$key": ${data[key]} == ${serverTokenTemplate.data[key]}', + name: 'TokenTemplate#hasSameValuesAs', + ); + } + Logger.debug( + 'AppTokenTemplate serial $serial/id $id has same values as serverTokenTemplate serial ${serverTokenTemplate.serial}/id ${serverTokenTemplate.id}', + name: 'TokenTemplate#hasSameValuesAs', + ); + return true; + } + + bool tokenWouldBeUpdated(Token token) { + Logger.debug('Checking if token would be updated', name: 'TokenTemplate#tokenWouldBeUpdated'); + final tokenTemplate = token.toTemplate(); + Logger.debug('TokenTemplate: \n$tokenTemplate\n has same values as \n$this\n ?', name: 'TokenTemplate#tokenWouldBeUpdated'); + return !tokenTemplate.hasSameValuesAs(this); + } } diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index d670743fa..fc7a6a35a 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:privacyidea_authenticator/model/token_container.dart'; @@ -134,7 +136,8 @@ class DayPasswordToken extends OTPToken { return copyWith( label: uriMap[URI_LABEL], issuer: uriMap[URI_ISSUER], - id: uriMap[TOKEN_SERIAL], + id: uriMap[URI_ID] == String ? uriMap[URI_ID] : const Uuid().v4(), + serial: uriMap[URI_SERIAL], algorithm: uriMap[URI_ALGORITHM] != null ? Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()) : null, digits: uriMap[URI_DIGITS], secret: uriMap[URI_SECRET] != null ? Encodings.base32.encode(uriMap[URI_SECRET]) : null, @@ -173,10 +176,10 @@ class DayPasswordToken extends OTPToken { /// Validates the uriMap for the required fields throws [LocalizedArgumentError] if a field is missing or invalid. static void validateUriMap(Map uriMap) { - if (uriMap[URI_SECRET] == null) { + if (uriMap[URI_SECRET] is! Uint8List) { throw LocalizedArgumentError( localizedMessage: ((localizations, value, name) => localizations.secretIsRequired), - unlocalizedMessage: 'Secret is required', + unlocalizedMessage: 'Secret is required and must be a Uint8List', invalidValue: uriMap[URI_SECRET], name: URI_SECRET, ); diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index df1e7cbe7..c5e42d8e0 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:json_annotation/json_annotation.dart'; import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:uuid/uuid.dart'; @@ -109,7 +111,8 @@ class HOTPToken extends OTPToken { return copyWith( label: uriMap[URI_LABEL], issuer: uriMap[URI_ISSUER], - id: uriMap[TOKEN_SERIAL], + id: uriMap[URI_ID], + serial: uriMap[URI_SERIAL], algorithm: uriMap[URI_ALGORITHM] != null ? Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()) : null, digits: uriMap[URI_DIGITS], secret: uriMap[URI_SECRET] != null ? Encodings.base32.encode(uriMap[URI_SECRET]) : null, @@ -126,7 +129,8 @@ class HOTPToken extends OTPToken { return HOTPToken( label: uriMap[URI_LABEL] ?? '', issuer: uriMap[URI_ISSUER] ?? '', - id: const Uuid().v4(), + id: uriMap[URI_ID] == String ? uriMap[URI_ID] : const Uuid().v4(), + serial: uriMap[URI_SERIAL], algorithm: Algorithms.values.byName((uriMap[URI_ALGORITHM] as String? ?? 'SHA1').toUpperCase()), digits: uriMap[URI_DIGITS] ?? 6, secret: Encodings.base32.encode(uriMap[URI_SECRET]), @@ -166,12 +170,13 @@ class HOTPToken extends OTPToken { /// Validates the uriMap for the required fields throws [LocalizedArgumentError] if a field is missing or invalid. static void validateUriMap(Map uriMap) { - if (uriMap[URI_SECRET] == null) { + if (uriMap[URI_SECRET] is! Uint8List) { throw LocalizedArgumentError( - invalidValue: uriMap[URI_SECRET], - name: URI_SECRET, - unlocalizedMessage: 'Secret is required', - localizedMessage: ((localizations, value, name) => localizations.secretIsRequired)); + localizedMessage: ((localizations, value, name) => localizations.secretIsRequired), + unlocalizedMessage: 'Secret is required and must be a Uint8List', + invalidValue: uriMap[URI_SECRET], + name: URI_SECRET, + ); } if (uriMap[URI_DIGITS] != null && uriMap[URI_DIGITS] < 1) { throw LocalizedArgumentError( diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index 7003bda0c..e1e0382fe 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -1,3 +1,5 @@ +import 'package:privacyidea_authenticator/model/enums/encodings.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; import '../../utils/logger.dart'; @@ -73,7 +75,7 @@ abstract class OTPToken extends Token { Map toUriMap() { return super.toUriMap() ..addAll({ - URI_SECRET: secret, + URI_SECRET: Encodings.base32.decode(secret), URI_ALGORITHM: algorithm.name, URI_DIGITS: digits, }); diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index ec7df008c..bfb3a131d 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -172,10 +172,10 @@ class PushToken extends Token { PushToken copyWithFromTemplate(TokenTemplate template) { final uriMap = template.data; return copyWith( - serial: uriMap[URI_SERIAL], label: uriMap[URI_LABEL], issuer: uriMap[URI_ISSUER], - id: uriMap[TOKEN_SERIAL], + id: uriMap[URI_ID], + serial: uriMap[URI_SERIAL], sslVerify: uriMap[URI_SSL_VERIFY], enrollmentCredentials: uriMap[URI_ENROLLMENT_CREDENTIAL], url: uriMap[URI_ROLLOUT_URL], @@ -188,10 +188,10 @@ class PushToken extends Token { } factory PushToken.fromUriMap(Map uriMap) => PushToken( - serial: uriMap[URI_SERIAL] ?? '', label: uriMap[URI_LABEL] ?? '', issuer: uriMap[URI_ISSUER] ?? '', - id: const Uuid().v4(), + id: uriMap[URI_ID] == String ? uriMap[URI_ID] : const Uuid().v4(), + serial: uriMap[URI_SERIAL], sslVerify: uriMap[URI_SSL_VERIFY], expirationDate: uriMap[URI_TTL] != null ? DateTime.now().add(Duration(minutes: uriMap[URI_TTL])) : null, enrollmentCredentials: uriMap[URI_ENROLLMENT_CREDENTIAL], diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index 5e56dd749..5ee0ff737 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -138,7 +138,9 @@ abstract class Token with SortableMixin { /// ``` Map toUriMap() { return { + URI_ID: id, URI_SERIAL: serial, + URI_CONTAINER_SERIAL: containerSerial, URI_TYPE: type, URI_LABEL: label, URI_ISSUER: issuer, @@ -148,15 +150,6 @@ abstract class Token with SortableMixin { }; } - bool doesMatchTemplate(TokenTemplate matchingTemplate) { - final uriMap = toUriMap(); - final templateData = matchingTemplate.data; - for (var key in templateData.keys) { - if (uriMap[key] != templateData[key]) return false; - } - return true; - } - Token copyWithFromTemplate(TokenTemplate template); TokenTemplate toTemplate() => TokenTemplate(data: toUriMap()); diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index 056fefc28..9b2f2c2cd 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:json_annotation/json_annotation.dart'; import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:uuid/uuid.dart'; @@ -112,10 +114,12 @@ class TOTPToken extends OTPToken { @override TOTPToken copyWithFromTemplate(TokenTemplate template) { final uriMap = template.data; - return copyWith( + + final newToken = copyWith( + id: uriMap[URI_ID], label: uriMap[URI_LABEL], issuer: uriMap[URI_ISSUER], - id: uriMap[TOKEN_SERIAL], + serial: uriMap[URI_SERIAL], algorithm: uriMap[URI_ALGORITHM] != null ? Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()) : null, digits: uriMap[URI_DIGITS], tokenImage: uriMap[URI_IMAGE], @@ -125,20 +129,23 @@ class TOTPToken extends OTPToken { isLocked: uriMap[URI_PIN], origin: uriMap[URI_ORIGIN], ); + Logger.debug('TOTPToken.copyWithFromTemplate old token: $this'); + Logger.debug('TOTPToken.copyWithFromTemplate new token: $newToken'); + return newToken; } @override String toString() { - return 'T${super.toString()}period: $period'; + return 'T${super.toString()}period: $period}'; } factory TOTPToken.fromUriMap(Map uriMap) { validateUriMap(uriMap); return TOTPToken( - serial: uriMap[URI_SERIAL], label: uriMap[URI_LABEL] ?? '', issuer: uriMap[URI_ISSUER] ?? '', - id: const Uuid().v4(), + id: uriMap[URI_ID] == String ? uriMap[URI_ID] : const Uuid().v4(), + serial: uriMap[URI_SERIAL], algorithm: Algorithms.values.byName((uriMap[URI_ALGORITHM] as String? ?? 'SHA1').toUpperCase()), digits: uriMap[URI_DIGITS] ?? 6, tokenImage: uriMap[URI_IMAGE], @@ -176,6 +183,14 @@ class TOTPToken extends OTPToken { /// Validates the uriMap for the required fields throws [LocalizedArgumentError] if a field is missing or invalid. static void validateUriMap(Map uriMap) { + if (uriMap[URI_SECRET] is! Uint8List) { + throw LocalizedArgumentError( + localizedMessage: ((localizations, value, name) => localizations.secretIsRequired), + unlocalizedMessage: 'Secret is required and must be a Uint8List', + invalidValue: uriMap[URI_SECRET], + name: URI_SECRET, + ); + } if (uriMap[URI_SERIAL] is! String?) { throw LocalizedArgumentError( localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), @@ -184,14 +199,6 @@ class TOTPToken extends OTPToken { name: URI_SERIAL, ); } - if (uriMap[URI_SECRET] == null) { - throw LocalizedArgumentError( - localizedMessage: ((localizations, value, name) => localizations.secretIsRequired), - unlocalizedMessage: 'Secret is required', - invalidValue: uriMap[URI_SECRET], - name: URI_SECRET, - ); - } if (uriMap[URI_DIGITS] != null && uriMap[URI_DIGITS] < 1) { throw LocalizedArgumentError( localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), diff --git a/lib/utils/identifiers.dart b/lib/utils/identifiers.dart index 14a0d0ae1..53537cd31 100644 --- a/lib/utils/identifiers.dart +++ b/lib/utils/identifiers.dart @@ -25,11 +25,14 @@ const defaultCrashReportRecipient = 'app-crash@netknights.it'; // qr codes: +const String URI_ID = 'URI_ID'; +const String URI_SERIAL = 'URI_SERIAL'; +const String URI_CONTAINER_SERIAL = 'URI_CONTAINER_SERIAL'; 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_SECRET = 'URI_SECRET'; // Should be Uint8List const String URI_COUNTER = 'URI_COUNTER'; const String URI_PERIOD = 'URI_PERIOD'; const String URI_ISSUER = 'URI_ISSUER'; @@ -43,7 +46,6 @@ 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'; @@ -65,9 +67,6 @@ const String PUSH_REQUEST_SSL_VERIFY = 'sslverify'; // 6. const String PUSH_REQUEST_SIGNATURE = 'signature'; // 7. const String PUSH_REQUEST_ANSWERS = 'require_presence'; // 8. -// TokenContainer: -const String TOKEN_SERIAL = 'serial'; - const String GLOBAL_SECURE_REPO_PREFIX = 'app_v3_'; bool validateMap(Map map, List keys) { diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index 46a897b99..650467194 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -291,17 +291,21 @@ Device Parameters $deviceInfo"""; /*----------- PRINTS -----------*/ static void _print(String message) { - if (!kDebugMode) return; + if (!kDebugMode) return; // add \n every 1000 characters only if the line is longer than 1000 characters + message = message.replaceAllMapped(RegExp(r'.{1000}'), (match) => '${match.group(0)}\n'); print.i(message); } static void _printDebug(String message) { if (!kDebugMode) return; + // add \n every 1000 characters only if the line is longer than 1000 characters + message = message.replaceAllMapped(RegExp(r'.{1000}'), (match) => '${match.group(0)}\n'); print.d(message); } static void _printWarning(String message) { - if (!kDebugMode) return; + if (!kDebugMode) return; // add \n every 1000 characters only if the line is longer than 1000 characters + message = message.replaceAllMapped(RegExp(r'.{1000}'), (match) => '${match.group(0)}\n'); print.w(message); } @@ -309,6 +313,8 @@ Device Parameters $deviceInfo"""; if (!kDebugMode) return; var message0 = DateTime.now().toString(); message0 += name != null ? ' [$name]\n' : '\n'; + message = message?.replaceAllMapped(RegExp(r'.{1000}'), (match) => '${match.group(0)}\n'); + // add \n every 1000 characters only if the line is longer than 1000 characters message0 += message ?? ''; print.e(message0, error: error, stackTrace: stackTrace); } diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart index fd9286bd7..928ba097d 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart @@ -58,7 +58,11 @@ class TokenContainerProvider extends _$TokenContainerProvider { final containerTokenTemplates = containerTokens.toTemplates(); final newState = oldState.copyWith(localTokenTemplates: localTokenTemplates, syncedTokenTemplates: containerTokenTemplates); final savedState = await _saveToRepo(newState); - ref.read(tokenProvider.notifier).updateContainerTokens(savedState); + if (savedState is! TokenContainerSynced) { + Logger.error('Failed to save state to repo', name: 'TokenContainerProvider#handleTokenState'); + return savedState; + } + await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); state = AsyncValue.data(savedState); _stateMutex.release(); return savedState; @@ -82,7 +86,28 @@ class TokenContainerProvider extends _$TokenContainerProvider { 'Saved TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', name: 'TokenContainerProvider#tryAddLocalTemplates', ); - ref.read(tokenProvider.notifier).updateContainerTokens(savedState); + await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); + state = AsyncValue.data(savedState); + _stateMutex.release(); + return savedState; + } + + Future handleDeletedTokenTemplates(List deletedPiTokenTemplates) async { + Logger.info( + 'Removing (${deletedPiTokenTemplates.length}) deleted token templates from container (${credential.serial}).', + name: 'TokenContainerProvider#handleDeletedTokenTemplates', + ); + Logger.debug('Deleted token templates (${deletedPiTokenTemplates.length})', name: 'TokenContainerProvider#handleDeletedTokenTemplates'); + await _stateMutex.acquire(); + final oldState = (await future); + final newLocalTokenTemplates = oldState.localTokenTemplates.where((template) => !deletedPiTokenTemplates.contains(template)).toList(); + final newState = oldState.copyWith(localTokenTemplates: newLocalTokenTemplates); + final savedState = await _saveToRepo(newState); + Logger.debug( + 'Saved TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', + name: 'TokenContainerProvider#handleDeletedTokenTemplates', + ); + await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); state = AsyncValue.data(savedState); _stateMutex.release(); return savedState; diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index f46874c0a..3cced0abe 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -20,15 +20,26 @@ class ContainerListensToTokenState extends TokenStateListener { static Future _onNewState(TokenState? previousState, TokenState nextState, WidgetRef ref) async { Logger.warning('New token state', name: 'TokenContainerTokenStateListener'); final maybePiTokenTemplates = nextState.lastlyUpdatedTokens.maybePiTokens.toTemplates(); - Logger.warning(('Read credentials from credentialsProvider'), name: 'TokenContainerTokenStateListener'); final credentials = (await ref.read(credentialsProvider.future)).credentials; Logger.warning('Readed: $credentials', name: 'TokenContainerTokenStateListener'); - Logger.warning('maybePiTokenTemplates: $maybePiTokenTemplates', name: 'TokenContainerTokenStateListener'); - if (maybePiTokenTemplates.isEmpty) return; + // if (maybePiTokenTemplates.isEmpty) return; for (var credential in credentials) { - Logger.warning('Adding maybePiTokenTemplates (${maybePiTokenTemplates.length}) to container ${credential.serial}', - name: 'TokenContainerTokenStateListener'); - ref.read(tokenContainerProviderOf(credential: credential).notifier).tryAddLocalTemplates(maybePiTokenTemplates); + + final deletedPiTokenTemplates = nextState.lastlyDeletedTokens.fromContainer(credential.serial).toTemplates(); + if (deletedPiTokenTemplates.isNotEmpty) { + Logger.warning( + 'Deleted (${deletedPiTokenTemplates.length}) tokens from container "${credential.serial}"', + name: 'TokenContainerTokenStateListener', + ); + await ref.read(tokenContainerProviderOf(credential: credential).notifier).handleDeletedTokenTemplates(deletedPiTokenTemplates); + } + if (maybePiTokenTemplates.isNotEmpty) { + Logger.warning( + 'Adding maybePiTokenTemplates (${maybePiTokenTemplates.length}) to container ${credential.serial}', + name: 'TokenContainerTokenStateListener', + ); + await ref.read(tokenContainerProviderOf(credential: credential).notifier).tryAddLocalTemplates(maybePiTokenTemplates); + } } } } diff --git a/lib/utils/riverpod/state_notifiers/token_notifier.dart b/lib/utils/riverpod/state_notifiers/token_notifier.dart index 01688ade2..2760f79a9 100644 --- a/lib/utils/riverpod/state_notifiers/token_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_notifier.dart @@ -191,6 +191,7 @@ class TokenNotifier extends StateNotifier { /// Removes a list of tokens and returns the tokens that could not be removed. Future> _removeTokens(List tokens) async { + Logger.info('Removing ${tokens.length} tokens.', name: 'token_notifier.dart#_removeTokens'); await _loadingRepoMutex.acquire(); final oldState = state; state = state.withoutTokens(tokens); @@ -335,74 +336,120 @@ class TokenNotifier extends StateNotifier { Future> updateTokens(List tokens, T Function(T) updater) async => _updateTokens(tokens, updater); /// Adds, Updates or Removes tokens based on the [TokenContainer] and returns the updated tokens. - void updateContainerTokens(TokenContainer container) async { + Future updateContainerTokens(TokenContainer container) async { await initState; Logger.info('Updating tokens from container.', name: 'token_notifier.dart#updateContainerTokens'); + await Future.delayed(const Duration(milliseconds: 2000)); final templatesToAdd = []; final templatesToUpdate = []; final templatesToRemove = []; - final knownContainerTokens = state.tokens.fromContainer(container.serial); - final syncedTokenTemplates = container.syncedTokenTemplates; - Logger.debug('App knows server tokens: ${knownContainerTokens.length}/${syncedTokenTemplates.length}', name: 'token_notifier.dart#updateContainerTokens'); - final knownContainerTemplates = knownContainerTokens.toTemplates(); - for (var i = 0; i < syncedTokenTemplates.length; i++) { + final knownContainerTokens = state.tokens.fromContainer(container.serial)..addAll(state.maybePiTokens); + final serverTokenTemplates = container.syncedTokenTemplates; + await Future.delayed(const Duration(milliseconds: 2000)); + Logger.debug( + 'App knows ${knownContainerTokens.length} of ${serverTokenTemplates.length} server tokens', + name: 'token_notifier.dart#updateContainerTokens', + ); + await Future.delayed(const Duration(milliseconds: 2000)); + final appTokenTemplates = knownContainerTokens.toTemplates(); + + Logger.debug('All server templates: ${serverTokenTemplates.join('\n')}', name: 'token_notifier.dart#updateContainerTokens'); + await Future.delayed(const Duration(milliseconds: 2000)); + for (var serverTokenTemplate in serverTokenTemplates) { + Logger.debug( + 'Checking server token template: $serverTokenTemplate', + name: 'token_notifier.dart#updateContainerTokens', + ); + await Future.delayed(const Duration(milliseconds: 2000)); // Searches for tokens that are in the container but not in the app to add them. // If the token is already in the app, it will be updated. // Reduces the [tokenTemplatesApp] list to only the tokens that are in the app but not in the container. These tokens will be removed. - final templateContainer = syncedTokenTemplates[i]; - Logger.debug('Checking serial ${templateContainer.serial}', name: 'token_notifier.dart#updateContainerTokens'); - final templateToKeep = knownContainerTemplates.firstWhereOrNull((templateApp) => templateApp.serial == templateContainer.serial); - if (templateToKeep == null) { - templatesToAdd.add(templateContainer); + final appTemplate = appTokenTemplates.firstWhereOrNull( + (templateApp) => templateApp.isSameTokenAs(serverTokenTemplate), + ); + if (appTemplate == null) { + Logger.debug( + 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} not found in app. Adding them.', + name: 'token_notifier.dart#updateContainerTokens', + ); + await Future.delayed(const Duration(milliseconds: 1000)); + templatesToAdd.add(serverTokenTemplate); } else { - Logger.warning('templateToKeep != templateContainer => ${templateToKeep != templateContainer}', name: 'token_notifier.dart#updateContainerTokens'); - if (templateToKeep != templateContainer) { + Logger.debug( + 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app. Checking for update.', + name: 'token_notifier.dart#updateContainerTokens', + ); + await Future.delayed(const Duration(milliseconds: 2000)); + appTokenTemplates.remove(appTemplate); + if (!appTemplate.hasSameValuesAs(serverTokenTemplate)) { + Logger.debug( + 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app the Template but is different. Check update.', + name: 'token_notifier.dart#updateContainerTokens', + ); + await Future.delayed(const Duration(milliseconds: 1000)); // Only update the token if the template is different - templatesToUpdate.add(templateToKeep); + templatesToUpdate.add(serverTokenTemplate); + } else { + Logger.debug( + 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app. And is the same. Skipping.', + name: 'token_notifier.dart#updateContainerTokens', + ); + await Future.delayed(const Duration(milliseconds: 1000)); } - knownContainerTemplates.remove(templateToKeep); } } // Removes all tokens that are in the app but not in the container. - templatesToRemove.addAll(knownContainerTemplates); + final remainingTokenTemplatesOfContainer = appTokenTemplates.where((template) => template.containerSerial == container.serial); + templatesToRemove.addAll(remainingTokenTemplatesOfContainer); Logger.debug( 'Add(${templatesToAdd.length}) | Check update(${templatesToUpdate.length}) | Remove(${templatesToRemove.length})', name: 'token_notifier.dart#updateContainerTokens', ); + await Future.delayed(const Duration(milliseconds: 2000)); final tokensToAdd = templatesToAdd.map((e) => e.toToken(container)).toList(); final tokensToUpdate = []; for (var template in templatesToUpdate) { - final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.serial); + final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id || token.serial == template.serial); if (token == null) continue; - final needsUpdate = !token.doesMatchTemplate(template); + final needsUpdate = template.tokenWouldBeUpdated(token); if (needsUpdate) { tokensToUpdate.add(token); } } final tokensToRemove = []; for (var template in templatesToRemove) { - final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.serial); + final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id || token.serial == template.serial); if (token == null) continue; tokensToRemove.add(token); } - Logger.debug( 'Add(${tokensToAdd.length}) | Update(${tokensToUpdate.length}) | Remove(${tokensToRemove.length})', name: 'token_notifier.dart#updateContainerTokens', ); + await Future.delayed(const Duration(milliseconds: 2000)); if (tokensToAdd.isNotEmpty) { await addOrReplaceTokens(tokensToAdd); } if (tokensToUpdate.isNotEmpty) { - await updateTokens(tokensToUpdate, (p0) { - final template = templatesToUpdate.firstWhereOrNull((element) => element.serial == p0.id); - if (template == null) return p0; - return p0.copyWithFromTemplate(template); + await updateTokens(tokensToUpdate, (token) { + final template = templatesToUpdate.firstWhereOrNull((template) => template.id == token.id || template.serial == token.serial); + if (template == null) { + Logger.debug( + 'Not able to update token with id ${token.id} or serial ${token.serial}. No token serial/id matches the templates serial/id.', + name: 'token_notifier.dart#updateContainerTokens', + ); + return token; + } + Logger.debug( + 'Updating token with id:"${token.id}"/serial:"${token.serial}".', + name: 'token_notifier.dart#updateContainerTokens', + ); + return token.copyWithFromTemplate(template); }); } if (tokensToRemove.isNotEmpty) { @@ -492,6 +539,7 @@ class TokenNotifier extends StateNotifier { /// Removes a list of tokens from the state and the repository. Future removeTokens(List tokens) async { + Logger.info('Removing ${tokens.length} tokens.', name: 'token_notifier.dart#removeTokens'); final pushTokens = tokens.whereType().toList(); final otherTokens = tokens.whereType().toList(); await _removeTokens(otherTokens); From cec050f6826ece33a9535ee819a84905764deb0b Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:59:26 +0200 Subject: [PATCH 012/285] token as a container --- lib/api/token_container_api_endpoint.dart | 2 +- .../riverpod/state_notifiers/token_notifier.dart | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index 50fb27e4b..4d2b8fc0f 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -52,7 +52,7 @@ class TokenContainerApiEndpoint implements ApiEndpioint { Future updateContainerTokens(TokenContainer container) async { await initState; Logger.info('Updating tokens from container.', name: 'token_notifier.dart#updateContainerTokens'); - await Future.delayed(const Duration(milliseconds: 2000)); final templatesToAdd = []; final templatesToUpdate = []; final templatesToRemove = []; final knownContainerTokens = state.tokens.fromContainer(container.serial)..addAll(state.maybePiTokens); final serverTokenTemplates = container.syncedTokenTemplates; - await Future.delayed(const Duration(milliseconds: 2000)); Logger.debug( 'App knows ${knownContainerTokens.length} of ${serverTokenTemplates.length} server tokens', name: 'token_notifier.dart#updateContainerTokens', ); - await Future.delayed(const Duration(milliseconds: 2000)); final appTokenTemplates = knownContainerTokens.toTemplates(); - Logger.debug('All server templates: ${serverTokenTemplates.join('\n')}', name: 'token_notifier.dart#updateContainerTokens'); - await Future.delayed(const Duration(milliseconds: 2000)); for (var serverTokenTemplate in serverTokenTemplates) { Logger.debug( 'Checking server token template: $serverTokenTemplate', name: 'token_notifier.dart#updateContainerTokens', ); - await Future.delayed(const Duration(milliseconds: 2000)); // Searches for tokens that are in the container but not in the app to add them. // If the token is already in the app, it will be updated. // Reduces the [tokenTemplatesApp] list to only the tokens that are in the app but not in the container. These tokens will be removed. @@ -373,21 +367,18 @@ class TokenNotifier extends StateNotifier { 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} not found in app. Adding them.', name: 'token_notifier.dart#updateContainerTokens', ); - await Future.delayed(const Duration(milliseconds: 1000)); templatesToAdd.add(serverTokenTemplate); } else { Logger.debug( 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app. Checking for update.', name: 'token_notifier.dart#updateContainerTokens', ); - await Future.delayed(const Duration(milliseconds: 2000)); appTokenTemplates.remove(appTemplate); if (!appTemplate.hasSameValuesAs(serverTokenTemplate)) { Logger.debug( 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app the Template but is different. Check update.', name: 'token_notifier.dart#updateContainerTokens', ); - await Future.delayed(const Duration(milliseconds: 1000)); // Only update the token if the template is different templatesToUpdate.add(serverTokenTemplate); } else { @@ -395,7 +386,6 @@ class TokenNotifier extends StateNotifier { 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app. And is the same. Skipping.', name: 'token_notifier.dart#updateContainerTokens', ); - await Future.delayed(const Duration(milliseconds: 1000)); } } } @@ -408,7 +398,6 @@ class TokenNotifier extends StateNotifier { name: 'token_notifier.dart#updateContainerTokens', ); - await Future.delayed(const Duration(milliseconds: 2000)); final tokensToAdd = templatesToAdd.map((e) => e.toToken(container)).toList(); final tokensToUpdate = []; for (var template in templatesToUpdate) { @@ -431,7 +420,6 @@ class TokenNotifier extends StateNotifier { name: 'token_notifier.dart#updateContainerTokens', ); - await Future.delayed(const Duration(milliseconds: 2000)); if (tokensToAdd.isNotEmpty) { await addOrReplaceTokens(tokensToAdd); } From 963dd8419b0e0a5c9ad9436697d9bfe1ddcfa02f Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:34:17 +0200 Subject: [PATCH 013/285] token as a container --- lib/model/token_container.dart | 18 ++------ .../token_container_state_provider.dart | 33 +++++++++++++ .../main_view_tokens_list.dart | 10 ++-- .../widgets/push_tokens_view_list.dart | 7 +-- lib/widgets/default_refresh_indicator.dart | 46 +++++++++++++++++++ 5 files changed, 89 insertions(+), 25 deletions(-) create mode 100644 lib/widgets/default_refresh_indicator.dart diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index 30d643b16..5eb4f977e 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -204,20 +204,20 @@ class TokenTemplate with _$TokenTemplate { @override operator ==(Object other) { if (other is! TokenTemplate) { - print('other is not TokenTemplate'); return false; } if (data.length != other.data.length) { - print('data length is not equal'); return false; } for (var key in data.keys) { - if (data[key].toString() != other.data[key].toString()) { - print('data[$key] (${data[key]}) is not equal to other.data[$key] (${other.data[key]})'); + if (data[key] is Iterable) { + if (!const IterableEquality().equals(data[key], other.data[key])) { + return false; + } + } else if (data[key].toString() != other.data[key].toString()) { return false; } } - print('TokenTemplate is equal'); return true; } @@ -268,10 +268,6 @@ class TokenTemplate with _$TokenTemplate { ); return false; } - Logger.debug( - '$key is iterable and has same values as serverTokenTemplate $key', - name: 'TokenTemplate#hasSameValuesAs', - ); continue; } if (data[key] != serverTokenTemplate.data[key]) { @@ -279,10 +275,6 @@ class TokenTemplate with _$TokenTemplate { name: 'TokenTemplate#hasSameValuesAs'); return false; } - Logger.debug( - 'TokenTemplate has same values for key "$key": ${data[key]} == ${serverTokenTemplate.data[key]}', - name: 'TokenTemplate#hasSameValuesAs', - ); } Logger.debug( 'AppTokenTemplate serial $serial/id $id has same values as serverTokenTemplate serial ${serverTokenTemplate.serial}/id ${serverTokenTemplate.id}', diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart index 928ba097d..ffb1c0c43 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart @@ -48,6 +48,13 @@ class TokenContainerProvider extends _$TokenContainerProvider { return newState; } + Future _fetchFromRepo() async { + await _repoMutex.acquire(); + final newState = await _repository.loadContainerState(); + _repoMutex.release(); + return newState; + } + Future handleTokenState(TokenState tokenState) async { await _stateMutex.acquire(); final localTokens = tokenState.tokens.maybePiTokens; @@ -112,6 +119,32 @@ class TokenContainerProvider extends _$TokenContainerProvider { _stateMutex.release(); return savedState; } + + Future fetchTokens() async { + await _stateMutex.acquire(); + final savedState = await _fetchFromRepo(); + Logger.debug( + 'Fetched TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', + name: 'TokenContainerProvider#tryAddLocalTemplates', + ); + await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); + state = AsyncValue.data(savedState); + _stateMutex.release(); + return savedState; + } + + Future sync() async { + await _stateMutex.acquire(); + final savedState = await _fetchFromRepo(); + Logger.debug( + 'Fetched TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', + name: 'TokenContainerProvider#tryAddLocalTemplates', + ); + await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); + state = AsyncValue.data(savedState); + _stateMutex.release(); + return savedState; + } } @Riverpod(keepAlive: true) 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 d960f51b0..35e1e54c2 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 @@ -7,15 +7,13 @@ 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/riverpod_providers/state_notifier_providers/settings_provider.dart'; import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart'; import '../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; -import '../../../widgets/deactivateable_refresh_indicator.dart'; +import '../../../widgets/default_refresh_indicator.dart'; import '../../../widgets/drag_item_scroller.dart'; import '../../../widgets/introduction_widgets/token_introduction.dart'; import 'drag_target_divider.dart'; -import 'loading_indicator.dart'; import 'no_token_screen.dart'; import 'sortable_widget_builder.dart'; @@ -86,9 +84,8 @@ class _MainViewTokensListState extends ConsumerState { Column( children: [ Flexible( - child: DeactivateableRefreshIndicator( + child: DefaultRefreshIndicator( allowToRefresh: allowToRefresh, - onRefresh: () async => LoadingIndicator.show(context, () async => PushProvider.instance?.pollForChallenges(isManually: true)), child: LayoutBuilder( builder: (context, constraints) => SingleChildScrollView( physics: _getScrollPhysics(allowToRefresh), @@ -111,9 +108,8 @@ class _MainViewTokensListState extends ConsumerState { ), ], ), - DeactivateableRefreshIndicator( + DefaultRefreshIndicator( allowToRefresh: allowToRefresh, - onRefresh: () async => LoadingIndicator.show(context, () async => PushProvider.instance?.pollForChallenges(isManually: true)), child: SlidableAutoCloseBehavior( child: DragItemScroller( listViewKey: listViewKey, diff --git a/lib/views/push_token_view/widgets/push_tokens_view_list.dart b/lib/views/push_token_view/widgets/push_tokens_view_list.dart index c9c236537..b68b4afaa 100644 --- a/lib/views/push_token_view/widgets/push_tokens_view_list.dart +++ b/lib/views/push_token_view/widgets/push_tokens_view_list.dart @@ -3,12 +3,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../model/mixins/sortable_mixin.dart'; -import '../../../utils/push_provider.dart'; import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; -import '../../../widgets/deactivateable_refresh_indicator.dart'; +import '../../../widgets/default_refresh_indicator.dart'; import '../../../widgets/drag_item_scroller.dart'; -import '../../main_view/main_view_widgets/loading_indicator.dart'; import '../../main_view/main_view_widgets/main_view_tokens_list.dart'; class PushTokensViwList extends ConsumerStatefulWidget { @@ -34,9 +32,8 @@ class _PushTokensViwListState extends ConsumerState { List sortables = [...pushTokens]; return Stack( children: [ - DeactivateableRefreshIndicator( + DefaultRefreshIndicator( allowToRefresh: allowToRefresh, - onRefresh: () async => LoadingIndicator.show(context, () async => PushProvider.instance?.pollForChallenges(isManually: true)), child: SlidableAutoCloseBehavior( child: DragItemScroller( listViewKey: listViewKey, diff --git a/lib/widgets/default_refresh_indicator.dart b/lib/widgets/default_refresh_indicator.dart new file mode 100644 index 000000000..2504653b5 --- /dev/null +++ b/lib/widgets/default_refresh_indicator.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; +import 'package:privacyidea_authenticator/widgets/deactivateable_refresh_indicator.dart'; + +import '../utils/push_provider.dart'; +import '../views/main_view/main_view_widgets/loading_indicator.dart'; + +class DefaultRefreshIndicator extends ConsumerStatefulWidget { + final Widget child; + final bool allowToRefresh; + + const DefaultRefreshIndicator({ + super.key, + required this.child, + required this.allowToRefresh, + }); + + @override + ConsumerState createState() => _DefaultRefreshIndicatorState(); +} + +class _DefaultRefreshIndicatorState extends ConsumerState { + bool isRefreshing = false; + @override + Widget build(BuildContext context) => DeactivateableRefreshIndicator( + onRefresh: () async { + setState(() { + isRefreshing = true; + }); + final future = LoadingIndicator.show(context, () async { + final pushProviderInstance = PushProvider.instance; + final credentials = (await ref.read(credentialsProvider.future)).credentials; + + await Future.wait([ + if (pushProviderInstance != null) pushProviderInstance.pollForChallenges(isManually: true), + for (var credential in credentials) (ref.read(tokenContainerProviderOf(credential: credential).notifier).sync()), + ]); + }); + await future; + if (mounted) setState(() => isRefreshing = false); + }, + allowToRefresh: widget.allowToRefresh && !isRefreshing, + child: widget.child, + ); +} From a6ff9e6c07d062e13ffc1d0f551c8ff166772666 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:38:37 +0200 Subject: [PATCH 014/285] fixed tests --- lib/model/token_container.dart | 7 +- lib/model/tokens/push_token.dart | 151 ++++++++++++++++-- ...mote_token_container_state_repository.dart | 2 +- .../model/token/push_token_test.dart | 3 +- .../hybrid_token_container_repo_test.dart | 29 +++- .../encryption/token_encryption_test.dart | 15 +- 6 files changed, 175 insertions(+), 32 deletions(-) diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index 5eb4f977e..175b15ae1 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -116,8 +116,11 @@ sealed class TokenContainer with _$TokenContainer { lastSyncAt: lastSyncAt ?? this.lastSyncAt!, serial: serial ?? this.serial, description: description ?? this.description, - syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, - localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, + syncedTokenTemplates: [ + ...(syncedTokenTemplates ?? this.syncedTokenTemplates), + ...(localTokenTemplates ?? this.localTokenTemplates), + ], + localTokenTemplates: [], ) as T, const (TokenContainerUnsynced) => TokenContainerUnsynced( serverName: serverName ?? this.serverName, diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index bfb3a131d..cac913ab3 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -1,10 +1,12 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:pointycastle/asymmetric/api.dart'; import 'package:privacyidea_authenticator/model/token_container.dart'; +import 'package:privacyidea_authenticator/utils/errors.dart'; import 'package:uuid/uuid.dart'; import '../../utils/custom_int_buffer.dart'; import '../../utils/identifiers.dart'; +import '../../utils/logger.dart'; import '../../utils/rsa_utils.dart'; import '../enums/push_token_rollout_state.dart'; import '../enums/token_types.dart'; @@ -187,20 +189,143 @@ class PushToken extends Token { ); } - factory PushToken.fromUriMap(Map uriMap) => PushToken( - label: uriMap[URI_LABEL] ?? '', - issuer: uriMap[URI_ISSUER] ?? '', - id: uriMap[URI_ID] == String ? uriMap[URI_ID] : const Uuid().v4(), - serial: uriMap[URI_SERIAL], - sslVerify: uriMap[URI_SSL_VERIFY], - expirationDate: uriMap[URI_TTL] != null ? DateTime.now().add(Duration(minutes: uriMap[URI_TTL])) : null, - enrollmentCredentials: uriMap[URI_ENROLLMENT_CREDENTIAL], - url: uriMap[URI_ROLLOUT_URL], - tokenImage: uriMap[URI_IMAGE], - pin: uriMap[URI_PIN], - isLocked: uriMap[URI_PIN], - origin: uriMap[URI_ORIGIN], + factory PushToken.fromUriMap(Map uriMap) { + validateUriMap(uriMap); + return PushToken( + label: uriMap[URI_LABEL] ?? '', + issuer: uriMap[URI_ISSUER] ?? '', + id: uriMap[URI_ID] == String ? uriMap[URI_ID] : const Uuid().v4(), + serial: uriMap[URI_SERIAL], + sslVerify: uriMap[URI_SSL_VERIFY], + expirationDate: uriMap[URI_TTL] != null ? DateTime.now().add(Duration(minutes: uriMap[URI_TTL])) : null, + enrollmentCredentials: uriMap[URI_ENROLLMENT_CREDENTIAL], + url: uriMap[URI_ROLLOUT_URL], + tokenImage: uriMap[URI_IMAGE], + pin: uriMap[URI_PIN], + isLocked: uriMap[URI_PIN], + origin: uriMap[URI_ORIGIN], + ); + } + + static void validateUriMap(Map uriMap) { + uriMap = Map.from(uriMap); + if (uriMap[URI_TYPE]?.toUpperCase() != TokenTypes.PIPUSH.name.toUpperCase()) { + throw ArgumentError('Invalid type: ${uriMap[URI_TYPE]}'); + } + uriMap.remove(URI_TYPE); + if (uriMap[URI_LABEL] is! String?) { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: 'Invalid value ${uriMap[URI_LABEL]} for parameter $URI_LABEL', + invalidValue: uriMap[URI_LABEL], + name: URI_LABEL, + ); + } + uriMap.remove(URI_LABEL); + if (uriMap[URI_ISSUER] is! String?) { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: 'Invalid value ${uriMap[URI_ISSUER]} for parameter $URI_ISSUER', + invalidValue: uriMap[URI_ISSUER], + name: URI_ISSUER, + ); + } + uriMap.remove(URI_ISSUER); + if (uriMap[URI_ID] is! String?) { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: 'Invalid value ${uriMap[URI_ID]} for parameter $URI_ID', + invalidValue: uriMap[URI_ID], + name: URI_ID, + ); + } + uriMap.remove(URI_ID); + if (uriMap[URI_SERIAL] is! String) { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: 'Invalid value ${uriMap[URI_SERIAL]} for parameter $URI_SERIAL', + invalidValue: uriMap[URI_SERIAL], + name: URI_SERIAL, ); + } + uriMap.remove(URI_SERIAL); + if (uriMap[URI_SSL_VERIFY] is! bool) { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: 'Invalid value ${uriMap[URI_SSL_VERIFY]} for parameter $URI_SSL_VERIFY', + invalidValue: uriMap[URI_SSL_VERIFY], + name: URI_SSL_VERIFY, + ); + } + uriMap.remove(URI_SSL_VERIFY); + /** + + expirationDate: uriMap[URI_TTL] != null ? DateTime.now().add(Duration(minutes: uriMap[URI_TTL])) : null, + enrollmentCredentials: uriMap[URI_ENROLLMENT_CREDENTIAL], + url: uriMap[URI_ROLLOUT_URL], + tokenImage: uriMap[URI_IMAGE], + pin: uriMap[URI_PIN], + isLocked: uriMap[URI_PIN], + origin: uriMap[URI_ORIGIN], + */ + if (uriMap[URI_TTL] is! int?) { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: 'Invalid value ${uriMap[URI_TTL]} for parameter $URI_TTL', + invalidValue: uriMap[URI_TTL], + name: URI_TTL, + ); + } + uriMap.remove(URI_TTL); + if (uriMap[URI_ENROLLMENT_CREDENTIAL] is! String?) { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: 'Invalid value ${uriMap[URI_ENROLLMENT_CREDENTIAL]} for parameter $URI_ENROLLMENT_CREDENTIAL', + invalidValue: uriMap[URI_ENROLLMENT_CREDENTIAL], + name: URI_ENROLLMENT_CREDENTIAL, + ); + } + uriMap.remove(URI_ENROLLMENT_CREDENTIAL); + if (uriMap[URI_ROLLOUT_URL] is! Uri?) { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: 'Invalid value ${uriMap[URI_ROLLOUT_URL]} for parameter $URI_ROLLOUT_URL', + invalidValue: uriMap[URI_ROLLOUT_URL], + name: URI_ROLLOUT_URL, + ); + } + uriMap.remove(URI_ROLLOUT_URL); + if (uriMap[URI_IMAGE] is! String?) { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: 'Invalid value ${uriMap[URI_IMAGE]} for parameter $URI_IMAGE', + invalidValue: uriMap[URI_IMAGE], + name: URI_IMAGE, + ); + } + uriMap.remove(URI_IMAGE); + if (uriMap[URI_PIN] is! bool?) { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: 'Invalid value ${uriMap[URI_PIN]} for parameter $URI_PIN', + invalidValue: uriMap[URI_PIN], + name: URI_PIN, + ); + } + uriMap.remove(URI_PIN); + if (uriMap[URI_ORIGIN] is! TokenOriginData?) { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), + unlocalizedMessage: 'Invalid value ${uriMap[URI_ORIGIN]} for parameter $URI_ORIGIN', + invalidValue: uriMap[URI_ORIGIN], + name: URI_ORIGIN, + ); + } + uriMap.remove(URI_ORIGIN); + if (uriMap.isNotEmpty) { + Logger.warning('Unknown parameters in uriMap: $uriMap'); + } + } @override Map toUriMap() { diff --git a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart index b890cbeb0..ec5eed465 100644 --- a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart @@ -18,7 +18,7 @@ class RemoteTokenContainerRepository implements TokenContainerRepository { Future _saveContainerState(TokenContainer containerState) async { Logger.info('Saving container state', name: 'RemoteTokenContainerRepository'); - return await _protect(() async => await apiEndpoint.sync(containerState)); + return await _protect(() async => (await apiEndpoint.sync(containerState)).copyTransformInto()); } @override diff --git a/test/unit_test/model/token/push_token_test.dart b/test/unit_test/model/token/push_token_test.dart index 31a935157..ca2b83321 100644 --- a/test/unit_test/model/token/push_token_test.dart +++ b/test/unit_test/model/token/push_token_test.dart @@ -139,6 +139,7 @@ void _testPushToken() { test('toJson', () { final tokenJson = pushToken.toJson(); final json = { + "containerSerial": null, "label": "label", "issuer": "issuer", "id": "id", @@ -187,7 +188,7 @@ void _testPushToken() { }); test('with empty map', () { final uriMap = {}; - expect(PushToken.fromUriMap(uriMap), isA()); + expect(() => PushToken.fromUriMap(uriMap), throwsA(isA())); }); }); }); diff --git a/test/unit_test/repo/hybrid_token_container_repo_test.dart b/test/unit_test/repo/hybrid_token_container_repo_test.dart index 3d1ee88db..cd9fd2179 100644 --- a/test/unit_test/repo/hybrid_token_container_repo_test.dart +++ b/test/unit_test/repo/hybrid_token_container_repo_test.dart @@ -1,10 +1,12 @@ - import 'package:flutter_test/flutter_test.dart'; +import 'package:privacyidea_authenticator/api/token_container_api_endpoint.dart'; import 'package:privacyidea_authenticator/interfaces/repo/container_repository.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; import 'package:privacyidea_authenticator/model/token_container.dart'; +import 'package:privacyidea_authenticator/model/tokens/container_credentials.dart'; import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; import 'package:privacyidea_authenticator/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; +import 'package:privacyidea_authenticator/repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; class MockTokenContainerRepository implements TokenContainerRepository { TokenContainer savedState; @@ -27,7 +29,25 @@ class MockTokenContainerRepository implements TokenContainerRepository { savedState = containerState; return Future.value(savedState); } +} + +class MockTokenContainerApiEndpoint implements TokenContainerApiEndpoint { + TokenContainer state; + MockTokenContainerApiEndpoint({required TokenContainer initialState}) : state = initialState; + + @override + Future fetch() { + return Future.value(state); + } + @override + Future sync(TokenContainer containerState) { + state = containerState.copyTransformInto(lastSyncAt: DateTime.now()); + return Future.value(state); + } + + @override + ContainerCredential get credential => throw UnimplementedError(); } void main() { @@ -48,8 +68,7 @@ void _testHybridTokenContainerRepository() { TokenContainer? remoteState = TokenContainer.synced( serial: 'containerSerial', description: 'description', - - syncedTokenTemplates: [TokenTemplate(data: token.toUriMap())], + syncedTokenTemplates: [token.toTemplate()], lastSyncAt: DateTime.now(), localTokenTemplates: [], ); @@ -59,11 +78,11 @@ void _testHybridTokenContainerRepository() { serial: 'containerSerial', description: 'description', syncedTokenTemplates: [], - localTokenTemplates: [TokenTemplate(data: token.toUriMap())], + localTokenTemplates: [token.toTemplate()], ); final localRepo = MockTokenContainerRepository(initialState: localState); - final remoteRepo = MockTokenContainerRepository(initialState: remoteState); + final remoteRepo = RemoteTokenContainerRepository(apiEndpoint: MockTokenContainerApiEndpoint(initialState: remoteState)); final hybridRepo = HybridTokenContainerRepository( localRepository: localRepo, diff --git a/test/unit_test/utils/encryption/token_encryption_test.dart b/test/unit_test/utils/encryption/token_encryption_test.dart index f7c1710ac..dd53a70b7 100644 --- a/test/unit_test/utils/encryption/token_encryption_test.dart +++ b/test/unit_test/utils/encryption/token_encryption_test.dart @@ -4,8 +4,8 @@ import 'package:privacyidea_authenticator/model/tokens/day_password_token.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/push_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/scheme_processors/token_import_scheme_processors/privacyidea_authenticator_qr_processor.dart'; import 'package:privacyidea_authenticator/utils/encryption/token_encryption.dart'; import 'package:zxing2/qrcode.dart'; @@ -48,20 +48,15 @@ void _testTokenEncryption() { DayPasswordToken(period: const Duration(hours: 24), id: 'id4', algorithm: Algorithms.SHA512, digits: 10, secret: 'secret4'), PushToken(serial: 'serial', id: 'id5'), ]; - const compressedTokensBase64 = [ - 'eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQxIiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IkhPVFAiLCJhbGdvcml0aG0iOiJTSEExIiwiZGlnaXRzIjo2LCJzZWNyZXQiOiJzZWNyZXQxIiwiY291bnRlciI6MH0=', - 'eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQyIiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IlRPVFAiLCJhbGdvcml0aG0iOiJTSEEyNTYiLCJkaWdpdHMiOjgsInNlY3JldCI6InNlY3JldDIiLCJwZXJpb2QiOjMwfQ==', - 'eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQzIiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IlNURUFNIiwic2VjcmV0Ijoic2VjcmV0MyJ9', - 'eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQ0IiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IkRBWVBBU1NXT1JEIiwiYWxnb3JpdGhtIjoiU0hBNTEyIiwiZGlnaXRzIjoxMCwic2VjcmV0Ijoic2VjcmV0NCIsInZpZXdNb2RlIjoiVkFMSURGT1IiLCJwZXJpb2QiOjg2NDAwMDAwMDAwfQ==', - 'eyJsYWJlbCI6IiIsImlzc3VlciI6IiIsImlkIjoiaWQ1IiwicGluIjpmYWxzZSwiaXNMb2NrZWQiOmZhbHNlLCJpc0hpZGRlbiI6ZmFsc2UsInRva2VuSW1hZ2UiOm51bGwsImZvbGRlcklkIjpudWxsLCJzb3J0SW5kZXgiOm51bGwsIm9yaWdpbiI6bnVsbCwidHlwZSI6IlBJUFVTSCIsImV4cGlyYXRpb25EYXRlIjpudWxsLCJzZXJpYWwiOiJzZXJpYWwiLCJmYlRva2VuIjpudWxsLCJzc2xWZXJpZnkiOmZhbHNlLCJlbnJvbGxtZW50Q3JlZGVudGlhbHMiOm51bGwsInVybCI6bnVsbCwiaXNSb2xsZWRPdXQiOmZhbHNlLCJyb2xsb3V0U3RhdGUiOiJyb2xsb3V0Tm90U3RhcnRlZCIsInB1YmxpY1NlcnZlcktleSI6bnVsbCwicHJpdmF0ZVRva2VuS2V5IjpudWxsLCJwdWJsaWNUb2tlbktleSI6bnVsbH0=', - ]; + for (var i = 0; tokensList.length > i; i++) { final token = tokensList[i]; - final compressed = compressedTokensBase64[i]; final qrCodeUri = TokenEncryption.generateExportUri(token: token); final uriString = qrCodeUri.toString(); + Token? decoded; expect(uriString.isNotEmpty, true); - expect(uriString, '${PrivacyIDEAAuthenticatorQrProcessor.scheme}://${PrivacyIDEAAuthenticatorQrProcessor.host}?data=$compressed'); + expect(() => decoded = TokenEncryption.fromExportUri(qrCodeUri), returnsNormally); + expect(decoded.runtimeType, tokensList[i].runtimeType); } }); From acb87707631b6d3aeb5456c2b45ce40b41303bc6 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:52:06 +0200 Subject: [PATCH 015/285] . --- lib/firebase_options/default_firebase_options.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/firebase_options/default_firebase_options.dart b/lib/firebase_options/default_firebase_options.dart index e35a3a77d..850fb174b 100644 --- a/lib/firebase_options/default_firebase_options.dart +++ b/lib/firebase_options/default_firebase_options.dart @@ -3,7 +3,7 @@ import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; -import 'netknights_firebase_options.dart'; +// import 'netknights_firebase_options.dart'; /// Netknights [DefaultFirebaseOptions] for use with your Firebase apps. /// @@ -17,7 +17,7 @@ import 'netknights_firebase_options.dart'; /// ``` class DefaultFirebaseOptions { static FirebaseOptions currentPlatformOf(String? app) => switch (app) { - 'netknights' => NetknightsFirebaseOptions.currentPlatform, + // 'netknights' => NetknightsFirebaseOptions.currentPlatform, _ => defaultCurrentPlatform, }; static FirebaseOptions get defaultCurrentPlatform { From 33af7acf982d02f648770d21626e9f564d340093 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 5 Aug 2024 10:48:03 +0200 Subject: [PATCH 016/285] refactoring --- lib/api/token_container_api_endpoint.dart | 2 +- .../deep_link_listener.dart | 24 +- .../container_credentials_processor.dart | 2 +- .../deeplink_provider.dart | 65 ++ .../deeplink_provider.g.dart | 23 + .../token_container_provider.dart} | 6 +- .../token_container_provider.g.dart} | 4 +- .../deeplink_provider.dart | 22 - .../token_provider.dart | 15 +- .../home_widget_deep_link_listener.dart | 9 +- .../navigation_deep_link_listener.dart | 8 +- .../token_container_token_state_listener.dart | 2 +- lib/widgets/app_wrapper.dart | 11 +- lib/widgets/app_wrappers/state_observer.dart | 15 +- lib/widgets/default_refresh_indicator.dart | 5 +- pubspec.lock | 2 +- pubspec.yaml | 1 + test/tests_app_wrapper.dart | 5 + test/tests_app_wrapper.mocks.dart | 532 +++++++++++---- .../push_request_notifier_test.dart | 9 +- .../push_request_notifier_test.mocks.dart | 590 ----------------- .../settings_notifier_test.dart | 6 +- .../settings_notifier_test.mocks.dart | 70 -- .../token_folder_notifier_test.dart | 5 +- .../token_folder_notifier_test.mocks.dart | 54 -- .../state_notifiers/token_notifier_test.dart | 17 +- .../token_notifier_test.mocks.dart | 618 ------------------ 27 files changed, 568 insertions(+), 1554 deletions(-) create mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.g.dart rename lib/utils/riverpod/riverpod_providers/{state_notifier_providers/token_container_state_provider.dart => generated_providers/token_container_provider.dart} (98%) rename lib/utils/riverpod/riverpod_providers/{state_notifier_providers/token_container_state_provider.g.dart => generated_providers/token_container_provider.g.dart} (98%) delete mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart delete mode 100644 test/unit_test/state_notifiers/push_request_notifier_test.mocks.dart delete mode 100644 test/unit_test/state_notifiers/settings_notifier_test.mocks.dart delete mode 100644 test/unit_test/state_notifiers/token_folder_notifier_test.mocks.dart delete mode 100644 test/unit_test/state_notifiers/token_notifier_test.mocks.dart diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index 4d2b8fc0f..3e0e4866d 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -7,7 +7,7 @@ import 'package:privacyidea_authenticator/utils/logger.dart'; import '../interfaces/api_endpoint.dart'; import '../model/enums/encodings.dart'; import '../model/enums/token_types.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart'; final Map> _data = { '123': { diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart index 09748d441..db4c40618 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart @@ -1,13 +1,29 @@ +import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/deeplink.dart'; -import '../../../../utils/riverpod/state_notifiers/deeplink_notifier.dart'; -import '../state_notifier_provider_listener.dart'; +import '../../../../utils/logger.dart'; +import '../../../../utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.dart'; -abstract class DeepLinkListener extends StateNotifierProviderListener { +abstract class DeepLinkListener extends StreamNotifierProviderListener { const DeepLinkListener({ - required StateNotifierProvider deeplinkProvider, + required StreamNotifierProvider deeplinkProvider, required super.onNewState, required super.listenerName, }) : super(provider: deeplinkProvider); } + + + +abstract class StreamNotifierProviderListener, S> { + final String listenerName; + final StreamNotifierProvider provider; + final void Function(AsyncValue? previous, AsyncValue next) onNewState; + const StreamNotifierProviderListener({required this.provider, required this.onNewState, required this.listenerName}); + void buildListen(WidgetRef ref) { + Logger.debug('("$listenerName") listening to provider ("$provider")', name: 'StateNotifierProviderListener#buildListen'); + ref.listen(provider, (previous, next) { + WidgetsBinding.instance.addPostFrameCallback((_) => onNewState(previous, next)); + }); + } +} diff --git a/lib/processors/scheme_processors/container_credentials_processor.dart b/lib/processors/scheme_processors/container_credentials_processor.dart index fd92d9b34..f0f8ea4e2 100644 --- a/lib/processors/scheme_processors/container_credentials_processor.dart +++ b/lib/processors/scheme_processors/container_credentials_processor.dart @@ -1,7 +1,7 @@ import 'package:privacyidea_authenticator/processors/scheme_processors/scheme_processor_interface.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart'; import '../../model/tokens/container_credentials.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.dart new file mode 100644 index 000000000..8a7978799 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.dart @@ -0,0 +1,65 @@ +import 'dart:async'; + +import 'package:app_links/app_links.dart'; +import 'package:async/async.dart'; +import 'package:flutter/foundation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../../model/deeplink.dart'; +import '../../state_notifiers/deeplink_notifier.dart'; +import '../../../home_widget_utils.dart'; +import '../../../logger.dart'; + +part 'deeplink_provider.g.dart'; + +final sources = [ + DeeplinkSource( + name: 'uni_links', + stream: AppLinks().uriLinkStream, + initialUri: AppLinks().getInitialLink(), + ), + DeeplinkSource( + name: 'home_widget', + stream: HomeWidgetUtils().widgetClicked, + initialUri: HomeWidgetUtils().initiallyLaunchedFromHomeWidget(), + ), +]; + +@Riverpod(keepAlive: true) +class DeeplinkProvider extends _$DeeplinkProvider { + @override + Stream build() async* { + Logger.info('New DeeplinkProvider created', name: 'DeeplinkProvider#build'); + final initial = await _handleInitialUri(sources); + if (initial != null) yield initial; + await for (var dl in _handleIncomingLinks(sources)) { + yield dl; + } + } + + /// Handle incoming links - the ones that the app will recieve from the OS + /// while already started. + Stream _handleIncomingLinks(List sources) async* { + if (kIsWeb) return; + final groupedStream = StreamGroup.merge(sources.map((source) => source.stream)); + await for (var uri in groupedStream) { + Logger.info('DeeplinkProvider got new uri'); + if (uri == null) return; + yield DeepLink(uri); + } + } + + Future _handleInitialUri(List sources) async { + Logger.info('_handleInitialUri called'); + + for (var source in sources) { + final initialUri = await source.initialUri; + if (initialUri != null) { + final initial = DeepLink(initialUri, fromInit: true); + Logger.info('Got initial uri from ${source.name}'); + return initial; // There should be only one initial uri + } + } + return null; + } +} diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.g.dart new file mode 100644 index 000000000..de8523302 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'deeplink_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$deeplinkProviderHash() => r'9f7e1aa1b9b5a7073f208d176d2c56395f1a987a'; + +/// See also [DeeplinkProvider]. +@ProviderFor(DeeplinkProvider) +final deeplinkProvider = StreamNotifierProvider.internal( + DeeplinkProvider.new, + name: r'deeplinkProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null : _$deeplinkProviderHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$DeeplinkProvider = StreamNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart similarity index 98% rename from lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart index ffb1c0c43..0aec4f227 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart @@ -15,9 +15,9 @@ import '../../../../repo/token_container_state_repositorys/hybrid_token_containe import '../../../../repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; import '../../../../repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart'; import '../../../../model/tokens/container_credentials.dart'; -import 'token_provider.dart'; +import '../state_notifier_providers/token_provider.dart'; -part 'token_container_state_provider.g.dart'; +part 'token_container_provider.g.dart'; @riverpod class TokenContainerProvider extends _$TokenContainerProvider { @@ -35,7 +35,7 @@ class TokenContainerProvider extends _$TokenContainerProvider { localRepository: SecureTokenContainerRepository(containerId: credential.serial), remoteRepository: RemoteTokenContainerRepository(apiEndpoint: TokenContainerApiEndpoint(credential: credential)), ); - final initialState = _repository.loadContainerState(); + final initialState = await _repository.loadContainerState(); Logger.debug('Initial state: $initialState', name: 'TokenContainerProvider#build'); _stateMutex.release(); return initialState; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.g.dart similarity index 98% rename from lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.g.dart index d04d716b6..55c34694d 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'token_container_state_provider.dart'; +part of 'token_container_provider.dart'; // ************************************************************************** // JsonSerializableGenerator @@ -23,7 +23,7 @@ Map _$CredentialsStateToJson(CredentialsState instance) => // ************************************************************************** String _$tokenContainerProviderHash() => - r'88f3953135b40aa4478987e3c41069ec94bc2a37'; + r'ec9832ed16a556f269ac27705af2a3cb27dd5fd1'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart deleted file mode 100644 index 053f9cbbf..000000000 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:app_links/app_links.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import '../../../../model/deeplink.dart'; -import '../../state_notifiers/deeplink_notifier.dart'; -import '../../../home_widget_utils.dart'; -import '../../../logger.dart'; - -final deeplinkProvider = StateNotifierProvider( - (ref) { - Logger.info("New DeeplinkNotifier created", name: 'deeplinkProvider'); - return DeeplinkNotifier(sources: [ - DeeplinkSource(name: 'uni_links', stream: AppLinks().uriLinkStream, initialUri: AppLinks().getInitialLink()), - DeeplinkSource( - name: 'home_widget', - stream: HomeWidgetUtils().widgetClicked, - initialUri: HomeWidgetUtils().initiallyLaunchedFromHomeWidget(), - ), - ]); - }, - name: 'deeplinkProvider', -); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart index 4fb84c5cd..a71f11a08 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/states/token_state.dart'; import '../../state_notifiers/token_notifier.dart'; import '../../../logger.dart'; -import 'deeplink_provider.dart'; +import '../generated_providers/deeplink_provider.dart'; final tokenProvider = StateNotifierProvider( (ref) { @@ -11,12 +11,13 @@ final tokenProvider = StateNotifierProvider( final newTokenNotifier = TokenNotifier(ref: ref); ref.listen(deeplinkProvider, (previous, newLink) { - if (newLink == null) { - Logger.info("Received null deeplink", name: 'tokenProvider#deeplinkProvider'); - return; - } - Logger.info("Received new deeplink", name: 'tokenProvider#deeplinkProvider'); - newTokenNotifier.handleLink(newLink.uri); + newLink.whenData( + (data) { + Logger.info("Received new deeplink with data: $data", name: 'tokenProvider#deeplinkProvider'); + newTokenNotifier.handleLink(data.uri); + }, + ); + }); return newTokenNotifier; diff --git a/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart b/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart index dcf47f411..8097aa024 100644 --- a/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart +++ b/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart @@ -1,3 +1,5 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart'; import '../../../model/deeplink.dart'; import '../../../processors/scheme_processors/home_widget_processor.dart'; @@ -10,8 +12,9 @@ class HomeWidgetDeepLinkListener extends DeepLinkListener { listenerName: 'HomeWidgetProcessor().processUri', ); - static void _onNewState(DeepLink? previous, DeepLink? next) { - if (next == null) return; - const HomeWidgetProcessor().processUri(next.uri, fromInit: next.fromInit); + static void _onNewState(AsyncValue? previous, AsyncValue next) { + next.whenData((next) { + const HomeWidgetProcessor().processUri(next.uri, fromInit: next.fromInit); + }); } } diff --git a/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart b/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart index 8fee37380..a2df68fa8 100644 --- a/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart +++ b/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart'; import '../../../model/deeplink.dart'; @@ -8,7 +9,7 @@ class NavigationDeepLinkListener extends DeepLinkListener { static BuildContext? _context; NavigationDeepLinkListener({required super.deeplinkProvider, BuildContext? context}) : super( - onNewState: (DeepLink? previous, DeepLink? next) { + onNewState: (AsyncValue? previous, AsyncValue next) { _onNewState(previous, next); }, listenerName: 'NavigationSchemeProcessor.processUriByAny', @@ -16,8 +17,9 @@ class NavigationDeepLinkListener extends DeepLinkListener { _context = context; } - static void _onNewState(DeepLink? previous, DeepLink? next) { - if (next == null) return; + static void _onNewState(AsyncValue? previous, AsyncValue next) { + next.whenData((next) { NavigationSchemeProcessor.processUriByAny(next.uri, context: _context, fromInit: next.fromInit); + }); } } diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index 3cced0abe..805c51107 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -4,7 +4,7 @@ import 'package:privacyidea_authenticator/utils/logger.dart'; import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; import '../../../model/states/token_state.dart'; -import '../riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; +import '../riverpod_providers/generated_providers/token_container_provider.dart'; class ContainerListensToTokenState extends TokenStateListener { ContainerListensToTokenState({ diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index 535d9c6e4..356a9cc6b 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -4,14 +4,14 @@ 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 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart'; import '../interfaces/riverpod/state_listeners/notifier_provider_listener.dart'; import '../model/token_container.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/deeplink_provider.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.dart'; import '../utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart'; import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; @@ -93,11 +93,14 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { return SingleTouchRecognizer( child: StateObserver( stateNotifierProviderListeners: [ - NavigationDeepLinkListener(deeplinkProvider: deeplinkProvider), - HomeWidgetDeepLinkListener(deeplinkProvider: deeplinkProvider), // TODO: Nochmal anschauen + HomeWidgetTokenStateListener(tokenProvider: tokenProvider), ContainerListensToTokenState(tokenProvider: tokenProvider, ref: ref), ], + streamNotifierProviderListeners: [ + NavigationDeepLinkListener(deeplinkProvider: deeplinkProvider), + HomeWidgetDeepLinkListener(deeplinkProvider: deeplinkProvider), // TODO: Nochmal anschauen + ], asyncNotifierProviderListeners: [ ...credentials.map( (credential) { diff --git a/lib/widgets/app_wrappers/state_observer.dart b/lib/widgets/app_wrappers/state_observer.dart index fc7da4586..ce65fce2e 100644 --- a/lib/widgets/app_wrappers/state_observer.dart +++ b/lib/widgets/app_wrappers/state_observer.dart @@ -1,16 +1,24 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart'; import '../../interfaces/riverpod/state_listeners/notifier_provider_listener.dart'; import '../../interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart'; class StateObserver extends ConsumerWidget { - final List asyncNotifierProviderListeners; final List stateNotifierProviderListeners; + final List asyncNotifierProviderListeners; + final List streamNotifierProviderListeners; final Widget child; - const StateObserver({super.key, this.asyncNotifierProviderListeners = const [], this.stateNotifierProviderListeners = const [], required this.child}); + const StateObserver({ + super.key, + this.asyncNotifierProviderListeners = const [], + this.stateNotifierProviderListeners = const [], + this.streamNotifierProviderListeners = const [], + required this.child, + }); @override Widget build(BuildContext context, WidgetRef ref) { @@ -20,6 +28,9 @@ class StateObserver extends ConsumerWidget { for (final listener in asyncNotifierProviderListeners) { listener.buildListen(ref); } + for (final listener in streamNotifierProviderListeners) { + listener.buildListen(ref); + } return child; } } diff --git a/lib/widgets/default_refresh_indicator.dart b/lib/widgets/default_refresh_indicator.dart index 2504653b5..2d6b9bdc8 100644 --- a/lib/widgets/default_refresh_indicator.dart +++ b/lib/widgets/default_refresh_indicator.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_container_state_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart'; import 'package:privacyidea_authenticator/widgets/deactivateable_refresh_indicator.dart'; +import '../utils/logger.dart'; import '../utils/push_provider.dart'; import '../views/main_view/main_view_widgets/loading_indicator.dart'; @@ -31,7 +32,7 @@ class _DefaultRefreshIndicatorState extends ConsumerState(), MockSpec(), MockSpec(), + MockSpec(), + MockSpec(), ]) +@GenerateMocks([]) class TestsAppWrapper extends StatelessWidget { final Widget child; final List overrides; diff --git a/test/tests_app_wrapper.mocks.dart b/test/tests_app_wrapper.mocks.dart index 040771d76..da42531ed 100644 --- a/test/tests_app_wrapper.mocks.dart +++ b/test/tests_app_wrapper.mocks.dart @@ -3,33 +3,39 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:typed_data' as _i15; +import 'dart:async' as _i11; +import 'dart:typed_data' as _i17; -import 'package:firebase_messaging/firebase_messaging.dart' as _i18; +import 'package:firebase_messaging/firebase_messaging.dart' as _i19; import 'package:http/http.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i14; +import 'package:mockito/src/dummies.dart' as _i16; import 'package:pointycastle/export.dart' as _i4; import 'package:privacyidea_authenticator/interfaces/repo/introduction_repository.dart' - as _i19; + as _i20; +import 'package:privacyidea_authenticator/interfaces/repo/push_request_repository.dart' + as _i23; import 'package:privacyidea_authenticator/interfaces/repo/settings_repository.dart' - as _i9; + as _i13; import 'package:privacyidea_authenticator/interfaces/repo/token_folder_repository.dart' - as _i10; + as _i14; import 'package:privacyidea_authenticator/interfaces/repo/token_repository.dart' - as _i6; + as _i10; +import 'package:privacyidea_authenticator/model/push_request.dart' as _i22; import 'package:privacyidea_authenticator/model/states/introduction_state.dart' as _i5; +import 'package:privacyidea_authenticator/model/states/push_request_state.dart' + as _i9; import 'package:privacyidea_authenticator/model/states/settings_state.dart' as _i2; -import 'package:privacyidea_authenticator/model/token_folder.dart' as _i11; -import 'package:privacyidea_authenticator/model/tokens/push_token.dart' as _i16; -import 'package:privacyidea_authenticator/model/tokens/token.dart' as _i8; -import 'package:privacyidea_authenticator/utils/firebase_utils.dart' as _i17; +import 'package:privacyidea_authenticator/model/token_folder.dart' as _i15; +import 'package:privacyidea_authenticator/model/tokens/push_token.dart' as _i18; +import 'package:privacyidea_authenticator/model/tokens/token.dart' as _i12; +import 'package:privacyidea_authenticator/utils/firebase_utils.dart' as _i6; import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart' - as _i12; -import 'package:privacyidea_authenticator/utils/rsa_utils.dart' as _i13; + as _i7; +import 'package:privacyidea_authenticator/utils/push_provider.dart' as _i21; +import 'package:privacyidea_authenticator/utils/rsa_utils.dart' as _i8; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -107,98 +113,141 @@ class _FakeIntroductionState_5 extends _i1.SmartFake ); } +class _FakeFirebaseUtils_6 extends _i1.SmartFake implements _i6.FirebaseUtils { + _FakeFirebaseUtils_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePrivacyideaIOClient_7 extends _i1.SmartFake + implements _i7.PrivacyideaIOClient { + _FakePrivacyideaIOClient_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeRsaUtils_8 extends _i1.SmartFake implements _i8.RsaUtils { + _FakeRsaUtils_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePushRequestState_9 extends _i1.SmartFake + implements _i9.PushRequestState { + _FakePushRequestState_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + /// A class which mocks [TokenRepository]. /// /// See the documentation for Mockito's code generation for more information. -class MockTokenRepository extends _i1.Mock implements _i6.TokenRepository { +class MockTokenRepository extends _i1.Mock implements _i10.TokenRepository { @override - _i7.Future<_i8.Token?> loadToken(String? id) => (super.noSuchMethod( + _i11.Future<_i12.Token?> loadToken(String? id) => (super.noSuchMethod( Invocation.method( #loadToken, [id], ), - returnValue: _i7.Future<_i8.Token?>.value(), - returnValueForMissingStub: _i7.Future<_i8.Token?>.value(), - ) as _i7.Future<_i8.Token?>); + returnValue: _i11.Future<_i12.Token?>.value(), + returnValueForMissingStub: _i11.Future<_i12.Token?>.value(), + ) as _i11.Future<_i12.Token?>); @override - _i7.Future> loadTokens() => (super.noSuchMethod( + _i11.Future> loadTokens() => (super.noSuchMethod( Invocation.method( #loadTokens, [], ), - returnValue: _i7.Future>.value(<_i8.Token>[]), + returnValue: _i11.Future>.value(<_i12.Token>[]), returnValueForMissingStub: - _i7.Future>.value(<_i8.Token>[]), - ) as _i7.Future>); + _i11.Future>.value(<_i12.Token>[]), + ) as _i11.Future>); @override - _i7.Future saveOrReplaceToken(_i8.Token? token) => (super.noSuchMethod( + _i11.Future saveOrReplaceToken(_i12.Token? token) => + (super.noSuchMethod( Invocation.method( #saveOrReplaceToken, [token], ), - returnValue: _i7.Future.value(false), - returnValueForMissingStub: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i11.Future.value(false), + returnValueForMissingStub: _i11.Future.value(false), + ) as _i11.Future); @override - _i7.Future> saveOrReplaceTokens( + _i11.Future> saveOrReplaceTokens( List? tokens) => (super.noSuchMethod( Invocation.method( #saveOrReplaceTokens, [tokens], ), - returnValue: _i7.Future>.value([]), - returnValueForMissingStub: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i11.Future>.value([]), + returnValueForMissingStub: _i11.Future>.value([]), + ) as _i11.Future>); @override - _i7.Future deleteToken(_i8.Token? token) => (super.noSuchMethod( + _i11.Future deleteToken(_i12.Token? token) => (super.noSuchMethod( Invocation.method( #deleteToken, [token], ), - returnValue: _i7.Future.value(false), - returnValueForMissingStub: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i11.Future.value(false), + returnValueForMissingStub: _i11.Future.value(false), + ) as _i11.Future); @override - _i7.Future> deleteTokens(List? tokens) => + _i11.Future> deleteTokens(List? tokens) => (super.noSuchMethod( Invocation.method( #deleteTokens, [tokens], ), - returnValue: _i7.Future>.value([]), - returnValueForMissingStub: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i11.Future>.value([]), + returnValueForMissingStub: _i11.Future>.value([]), + ) as _i11.Future>); } /// A class which mocks [SettingsRepository]. /// /// See the documentation for Mockito's code generation for more information. class MockSettingsRepository extends _i1.Mock - implements _i9.SettingsRepository { + implements _i13.SettingsRepository { @override - _i7.Future saveSettings(_i2.SettingsState? settings) => + _i11.Future saveSettings(_i2.SettingsState? settings) => (super.noSuchMethod( Invocation.method( #saveSettings, [settings], ), - returnValue: _i7.Future.value(false), - returnValueForMissingStub: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i11.Future.value(false), + returnValueForMissingStub: _i11.Future.value(false), + ) as _i11.Future); @override - _i7.Future<_i2.SettingsState> loadSettings() => (super.noSuchMethod( + _i11.Future<_i2.SettingsState> loadSettings() => (super.noSuchMethod( Invocation.method( #loadSettings, [], ), - returnValue: _i7.Future<_i2.SettingsState>.value(_FakeSettingsState_0( + returnValue: _i11.Future<_i2.SettingsState>.value(_FakeSettingsState_0( this, Invocation.method( #loadSettings, @@ -206,52 +255,52 @@ class MockSettingsRepository extends _i1.Mock ), )), returnValueForMissingStub: - _i7.Future<_i2.SettingsState>.value(_FakeSettingsState_0( + _i11.Future<_i2.SettingsState>.value(_FakeSettingsState_0( this, Invocation.method( #loadSettings, [], ), )), - ) as _i7.Future<_i2.SettingsState>); + ) as _i11.Future<_i2.SettingsState>); } /// A class which mocks [TokenFolderRepository]. /// /// See the documentation for Mockito's code generation for more information. class MockTokenFolderRepository extends _i1.Mock - implements _i10.TokenFolderRepository { + implements _i14.TokenFolderRepository { @override - _i7.Future saveReplaceList(List<_i11.TokenFolder>? folders) => + _i11.Future saveReplaceList(List<_i15.TokenFolder>? folders) => (super.noSuchMethod( Invocation.method( #saveReplaceList, [folders], ), - returnValue: _i7.Future.value(false), - returnValueForMissingStub: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i11.Future.value(false), + returnValueForMissingStub: _i11.Future.value(false), + ) as _i11.Future); @override - _i7.Future> loadFolders() => (super.noSuchMethod( + _i11.Future> loadFolders() => (super.noSuchMethod( Invocation.method( #loadFolders, [], ), returnValue: - _i7.Future>.value(<_i11.TokenFolder>[]), + _i11.Future>.value(<_i15.TokenFolder>[]), returnValueForMissingStub: - _i7.Future>.value(<_i11.TokenFolder>[]), - ) as _i7.Future>); + _i11.Future>.value(<_i15.TokenFolder>[]), + ) as _i11.Future>); } /// A class which mocks [PrivacyideaIOClient]. /// /// See the documentation for Mockito's code generation for more information. class MockPrivacyideaIOClient extends _i1.Mock - implements _i12.PrivacyideaIOClient { + implements _i7.PrivacyideaIOClient { @override - _i7.Future triggerNetworkAccessPermission({ + _i11.Future triggerNetworkAccessPermission({ required Uri? url, bool? sslVerify = true, bool? isRetry = false, @@ -266,12 +315,12 @@ class MockPrivacyideaIOClient extends _i1.Mock #isRetry: isRetry, }, ), - returnValue: _i7.Future.value(false), - returnValueForMissingStub: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i11.Future.value(false), + returnValueForMissingStub: _i11.Future.value(false), + ) as _i11.Future); @override - _i7.Future<_i3.Response> doPost({ + _i11.Future<_i3.Response> doPost({ required Uri? url, required Map? body, bool? sslVerify = true, @@ -286,7 +335,7 @@ class MockPrivacyideaIOClient extends _i1.Mock #sslVerify: sslVerify, }, ), - returnValue: _i7.Future<_i3.Response>.value(_FakeResponse_1( + returnValue: _i11.Future<_i3.Response>.value(_FakeResponse_1( this, Invocation.method( #doPost, @@ -299,7 +348,7 @@ class MockPrivacyideaIOClient extends _i1.Mock ), )), returnValueForMissingStub: - _i7.Future<_i3.Response>.value(_FakeResponse_1( + _i11.Future<_i3.Response>.value(_FakeResponse_1( this, Invocation.method( #doPost, @@ -311,10 +360,10 @@ class MockPrivacyideaIOClient extends _i1.Mock }, ), )), - ) as _i7.Future<_i3.Response>); + ) as _i11.Future<_i3.Response>); @override - _i7.Future<_i3.Response> doGet({ + _i11.Future<_i3.Response> doGet({ required Uri? url, required Map? parameters, bool? sslVerify = true, @@ -329,7 +378,7 @@ class MockPrivacyideaIOClient extends _i1.Mock #sslVerify: sslVerify, }, ), - returnValue: _i7.Future<_i3.Response>.value(_FakeResponse_1( + returnValue: _i11.Future<_i3.Response>.value(_FakeResponse_1( this, Invocation.method( #doGet, @@ -342,7 +391,7 @@ class MockPrivacyideaIOClient extends _i1.Mock ), )), returnValueForMissingStub: - _i7.Future<_i3.Response>.value(_FakeResponse_1( + _i11.Future<_i3.Response>.value(_FakeResponse_1( this, Invocation.method( #doGet, @@ -354,13 +403,13 @@ class MockPrivacyideaIOClient extends _i1.Mock }, ), )), - ) as _i7.Future<_i3.Response>); + ) as _i11.Future<_i3.Response>); } /// A class which mocks [RsaUtils]. /// /// See the documentation for Mockito's code generation for more information. -class MockRsaUtils extends _i1.Mock implements _i13.RsaUtils { +class MockRsaUtils extends _i1.Mock implements _i8.RsaUtils { @override _i4.RSAPublicKey deserializeRSAPublicKeyPKCS1(String? keyStr) => (super.noSuchMethod( @@ -391,14 +440,14 @@ class MockRsaUtils extends _i1.Mock implements _i13.RsaUtils { #serializeRSAPublicKeyPKCS1, [publicKey], ), - returnValue: _i14.dummyValue( + returnValue: _i16.dummyValue( this, Invocation.method( #serializeRSAPublicKeyPKCS1, [publicKey], ), ), - returnValueForMissingStub: _i14.dummyValue( + returnValueForMissingStub: _i16.dummyValue( this, Invocation.method( #serializeRSAPublicKeyPKCS1, @@ -437,14 +486,14 @@ class MockRsaUtils extends _i1.Mock implements _i13.RsaUtils { #serializeRSAPublicKeyPKCS8, [key], ), - returnValue: _i14.dummyValue( + returnValue: _i16.dummyValue( this, Invocation.method( #serializeRSAPublicKeyPKCS8, [key], ), ), - returnValueForMissingStub: _i14.dummyValue( + returnValueForMissingStub: _i16.dummyValue( this, Invocation.method( #serializeRSAPublicKeyPKCS8, @@ -460,14 +509,14 @@ class MockRsaUtils extends _i1.Mock implements _i13.RsaUtils { #serializeRSAPrivateKeyPKCS1, [key], ), - returnValue: _i14.dummyValue( + returnValue: _i16.dummyValue( this, Invocation.method( #serializeRSAPrivateKeyPKCS1, [key], ), ), - returnValueForMissingStub: _i14.dummyValue( + returnValueForMissingStub: _i16.dummyValue( this, Invocation.method( #serializeRSAPrivateKeyPKCS1, @@ -502,8 +551,8 @@ class MockRsaUtils extends _i1.Mock implements _i13.RsaUtils { @override bool verifyRSASignature( _i4.RSAPublicKey? publicKey, - _i15.Uint8List? signedMessage, - _i15.Uint8List? signature, + _i17.Uint8List? signedMessage, + _i17.Uint8List? signature, ) => (super.noSuchMethod( Invocation.method( @@ -519,8 +568,8 @@ class MockRsaUtils extends _i1.Mock implements _i13.RsaUtils { ) as bool); @override - _i7.Future trySignWithToken( - _i16.PushToken? token, + _i11.Future trySignWithToken( + _i18.PushToken? token, String? message, ) => (super.noSuchMethod( @@ -531,18 +580,18 @@ class MockRsaUtils extends _i1.Mock implements _i13.RsaUtils { message, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i7.Future<_i4.AsymmetricKeyPair<_i4.RSAPublicKey, _i4.RSAPrivateKey>> + _i11.Future<_i4.AsymmetricKeyPair<_i4.RSAPublicKey, _i4.RSAPrivateKey>> generateRSAKeyPair() => (super.noSuchMethod( Invocation.method( #generateRSAKeyPair, [], ), - returnValue: _i7.Future< + returnValue: _i11.Future< _i4.AsymmetricKeyPair<_i4.RSAPublicKey, _i4.RSAPrivateKey>>.value( _FakeAsymmetricKeyPair_4<_i4.RSAPublicKey, _i4.RSAPrivateKey>( @@ -552,7 +601,7 @@ class MockRsaUtils extends _i1.Mock implements _i13.RsaUtils { [], ), )), - returnValueForMissingStub: _i7.Future< + returnValueForMissingStub: _i11.Future< _i4.AsymmetricKeyPair<_i4.RSAPublicKey, _i4.RSAPrivateKey>>.value( _FakeAsymmetricKeyPair_4<_i4.RSAPublicKey, _i4.RSAPrivateKey>( @@ -562,13 +611,13 @@ class MockRsaUtils extends _i1.Mock implements _i13.RsaUtils { [], ), )), - ) as _i7.Future< + ) as _i11.Future< _i4.AsymmetricKeyPair<_i4.RSAPublicKey, _i4.RSAPrivateKey>>); @override String createBase32Signature( _i4.RSAPrivateKey? privateKey, - _i15.Uint8List? dataToSign, + _i17.Uint8List? dataToSign, ) => (super.noSuchMethod( Invocation.method( @@ -578,7 +627,7 @@ class MockRsaUtils extends _i1.Mock implements _i13.RsaUtils { dataToSign, ], ), - returnValue: _i14.dummyValue( + returnValue: _i16.dummyValue( this, Invocation.method( #createBase32Signature, @@ -588,7 +637,7 @@ class MockRsaUtils extends _i1.Mock implements _i13.RsaUtils { ], ), ), - returnValueForMissingStub: _i14.dummyValue( + returnValueForMissingStub: _i16.dummyValue( this, Invocation.method( #createBase32Signature, @@ -601,9 +650,9 @@ class MockRsaUtils extends _i1.Mock implements _i13.RsaUtils { ) as String); @override - _i15.Uint8List createRSASignature( + _i17.Uint8List createRSASignature( _i4.RSAPrivateKey? privateKey, - _i15.Uint8List? dataToSign, + _i17.Uint8List? dataToSign, ) => (super.noSuchMethod( Invocation.method( @@ -613,19 +662,19 @@ class MockRsaUtils extends _i1.Mock implements _i13.RsaUtils { dataToSign, ], ), - returnValue: _i15.Uint8List(0), - returnValueForMissingStub: _i15.Uint8List(0), - ) as _i15.Uint8List); + returnValue: _i17.Uint8List(0), + returnValueForMissingStub: _i17.Uint8List(0), + ) as _i17.Uint8List); } /// A class which mocks [FirebaseUtils]. /// /// See the documentation for Mockito's code generation for more information. -class MockFirebaseUtils extends _i1.Mock implements _i17.FirebaseUtils { +class MockFirebaseUtils extends _i1.Mock implements _i6.FirebaseUtils { @override - _i7.Future initFirebase({ - required _i7.Future Function(_i18.RemoteMessage)? foregroundHandler, - required _i7.Future Function(_i18.RemoteMessage)? backgroundHandler, + _i11.Future initFirebase({ + required _i11.Future Function(_i19.RemoteMessage)? foregroundHandler, + required _i11.Future Function(_i19.RemoteMessage)? backgroundHandler, required dynamic Function(String?)? updateFirebaseToken, }) => (super.noSuchMethod( @@ -638,97 +687,97 @@ class MockFirebaseUtils extends _i1.Mock implements _i17.FirebaseUtils { #updateFirebaseToken: updateFirebaseToken, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i7.Future getFBToken() => (super.noSuchMethod( + _i11.Future getFBToken() => (super.noSuchMethod( Invocation.method( #getFBToken, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i7.Future deleteFirebaseToken() => (super.noSuchMethod( + _i11.Future deleteFirebaseToken() => (super.noSuchMethod( Invocation.method( #deleteFirebaseToken, [], ), - returnValue: _i7.Future.value(false), - returnValueForMissingStub: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i11.Future.value(false), + returnValueForMissingStub: _i11.Future.value(false), + ) as _i11.Future); @override - _i7.Future setCurrentFirebaseToken(String? str) => (super.noSuchMethod( + _i11.Future setCurrentFirebaseToken(String? str) => (super.noSuchMethod( Invocation.method( #setCurrentFirebaseToken, [str], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i7.Future getCurrentFirebaseToken() => (super.noSuchMethod( + _i11.Future getCurrentFirebaseToken() => (super.noSuchMethod( Invocation.method( #getCurrentFirebaseToken, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i7.Future setNewFirebaseToken(String? str) => (super.noSuchMethod( + _i11.Future setNewFirebaseToken(String? str) => (super.noSuchMethod( Invocation.method( #setNewFirebaseToken, [str], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i7.Future getNewFirebaseToken() => (super.noSuchMethod( + _i11.Future getNewFirebaseToken() => (super.noSuchMethod( Invocation.method( #getNewFirebaseToken, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); } /// A class which mocks [IntroductionRepository]. /// /// See the documentation for Mockito's code generation for more information. class MockIntroductionRepository extends _i1.Mock - implements _i19.IntroductionRepository { + implements _i20.IntroductionRepository { @override - _i7.Future saveCompletedIntroductions( + _i11.Future saveCompletedIntroductions( _i5.IntroductionState? introductions) => (super.noSuchMethod( Invocation.method( #saveCompletedIntroductions, [introductions], ), - returnValue: _i7.Future.value(false), - returnValueForMissingStub: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i11.Future.value(false), + returnValueForMissingStub: _i11.Future.value(false), + ) as _i11.Future); @override - _i7.Future<_i5.IntroductionState> loadCompletedIntroductions() => + _i11.Future<_i5.IntroductionState> loadCompletedIntroductions() => (super.noSuchMethod( Invocation.method( #loadCompletedIntroductions, [], ), returnValue: - _i7.Future<_i5.IntroductionState>.value(_FakeIntroductionState_5( + _i11.Future<_i5.IntroductionState>.value(_FakeIntroductionState_5( this, Invocation.method( #loadCompletedIntroductions, @@ -736,12 +785,223 @@ class MockIntroductionRepository extends _i1.Mock ), )), returnValueForMissingStub: - _i7.Future<_i5.IntroductionState>.value(_FakeIntroductionState_5( + _i11.Future<_i5.IntroductionState>.value(_FakeIntroductionState_5( this, Invocation.method( #loadCompletedIntroductions, [], ), )), - ) as _i7.Future<_i5.IntroductionState>); + ) as _i11.Future<_i5.IntroductionState>); +} + +/// A class which mocks [PushProvider]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPushProvider extends _i1.Mock implements _i21.PushProvider { + MockPushProvider() { + _i1.throwOnMissingStub(this); + } + + @override + bool get pollingIsEnabled => (super.noSuchMethod( + Invocation.getter(#pollingIsEnabled), + returnValue: false, + ) as bool); + + @override + set pollingIsEnabled(bool? _pollingIsEnabled) => super.noSuchMethod( + Invocation.setter( + #pollingIsEnabled, + _pollingIsEnabled, + ), + returnValueForMissingStub: null, + ); + + @override + _i6.FirebaseUtils get firebaseUtils => (super.noSuchMethod( + Invocation.getter(#firebaseUtils), + returnValue: _FakeFirebaseUtils_6( + this, + Invocation.getter(#firebaseUtils), + ), + ) as _i6.FirebaseUtils); + + @override + _i7.PrivacyideaIOClient get ioClient => (super.noSuchMethod( + Invocation.getter(#ioClient), + returnValue: _FakePrivacyideaIOClient_7( + this, + Invocation.getter(#ioClient), + ), + ) as _i7.PrivacyideaIOClient); + + @override + _i8.RsaUtils get rsaUtils => (super.noSuchMethod( + Invocation.getter(#rsaUtils), + returnValue: _FakeRsaUtils_8( + this, + Invocation.getter(#rsaUtils), + ), + ) as _i8.RsaUtils); + + @override + void setPollingEnabled(bool? enablePolling) => super.noSuchMethod( + Invocation.method( + #setPollingEnabled, + [enablePolling], + ), + returnValueForMissingStub: null, + ); + + @override + _i11.Future pollForChallenges({required bool? isManually}) => + (super.noSuchMethod( + Invocation.method( + #pollForChallenges, + [], + {#isManually: isManually}, + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future pollForChallenge( + _i18.PushToken? token, { + bool? isManually = true, + }) => + (super.noSuchMethod( + Invocation.method( + #pollForChallenge, + [token], + {#isManually: isManually}, + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future< + (List<_i18.PushToken>, List<_i18.PushToken>)?> updateFirebaseToken( + [String? firebaseToken]) => + (super.noSuchMethod( + Invocation.method( + #updateFirebaseToken, + [firebaseToken], + ), + returnValue: + _i11.Future<(List<_i18.PushToken>, List<_i18.PushToken>)?>.value(), + ) as _i11.Future<(List<_i18.PushToken>, List<_i18.PushToken>)?>); + + @override + void unsubscribe(void Function(_i22.PushRequest)? newRequest) => + super.noSuchMethod( + Invocation.method( + #unsubscribe, + [newRequest], + ), + returnValueForMissingStub: null, + ); + + @override + void subscribe(void Function(_i22.PushRequest)? newRequest) => + super.noSuchMethod( + Invocation.method( + #subscribe, + [newRequest], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [PushRequestRepository]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPushRequestRepository extends _i1.Mock + implements _i23.PushRequestRepository { + MockPushRequestRepository() { + _i1.throwOnMissingStub(this); + } + + @override + _i11.Future<_i9.PushRequestState> loadState() => (super.noSuchMethod( + Invocation.method( + #loadState, + [], + ), + returnValue: + _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( + this, + Invocation.method( + #loadState, + [], + ), + )), + ) as _i11.Future<_i9.PushRequestState>); + + @override + _i11.Future saveState(_i9.PushRequestState? pushRequestState) => + (super.noSuchMethod( + Invocation.method( + #saveState, + [pushRequestState], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future clearState() => (super.noSuchMethod( + Invocation.method( + #clearState, + [], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future<_i9.PushRequestState> add( + _i22.PushRequest? pushRequest, { + _i9.PushRequestState? state, + }) => + (super.noSuchMethod( + Invocation.method( + #add, + [pushRequest], + {#state: state}, + ), + returnValue: + _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( + this, + Invocation.method( + #add, + [pushRequest], + {#state: state}, + ), + )), + ) as _i11.Future<_i9.PushRequestState>); + + @override + _i11.Future<_i9.PushRequestState> remove( + _i22.PushRequest? pushRequest, { + _i9.PushRequestState? state, + }) => + (super.noSuchMethod( + Invocation.method( + #remove, + [pushRequest], + {#state: state}, + ), + returnValue: + _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( + this, + Invocation.method( + #remove, + [pushRequest], + {#state: state}, + ), + )), + ) as _i11.Future<_i9.PushRequestState>); } diff --git a/test/unit_test/state_notifiers/push_request_notifier_test.dart b/test/unit_test/state_notifiers/push_request_notifier_test.dart index defa05324..d7c873772 100644 --- a/test/unit_test/state_notifiers/push_request_notifier_test.dart +++ b/test/unit_test/state_notifiers/push_request_notifier_test.dart @@ -2,20 +2,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart'; import 'package:mockito/mockito.dart'; -import 'package:privacyidea_authenticator/interfaces/repo/push_request_repository.dart'; import 'package:privacyidea_authenticator/model/push_request.dart'; import 'package:privacyidea_authenticator/model/states/push_request_state.dart'; import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/push_request_notifier.dart'; import 'package:privacyidea_authenticator/utils/custom_int_buffer.dart'; -import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; -import 'package:privacyidea_authenticator/utils/push_provider.dart'; -import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; -import 'package:mockito/annotations.dart'; -import 'push_request_notifier_test.mocks.dart'; +import '../../tests_app_wrapper.mocks.dart'; + -@GenerateMocks([RsaUtils, PrivacyideaIOClient, PushProvider, PushRequestRepository]) void main() { _testPushRequestNotifier(); } diff --git a/test/unit_test/state_notifiers/push_request_notifier_test.mocks.dart b/test/unit_test/state_notifiers/push_request_notifier_test.mocks.dart deleted file mode 100644 index 9e2bf0143..000000000 --- a/test/unit_test/state_notifiers/push_request_notifier_test.mocks.dart +++ /dev/null @@ -1,590 +0,0 @@ -// Mocks generated by Mockito 5.4.4 from annotations -// in privacyidea_authenticator/test/unit_test/state_notifiers/push_request_notifier_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i10; -import 'dart:typed_data' as _i9; - -import 'package:http/http.dart' as _i3; -import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i8; -import 'package:pointycastle/export.dart' as _i2; -import 'package:privacyidea_authenticator/interfaces/repo/push_request_repository.dart' as _i14; -import 'package:privacyidea_authenticator/model/push_request.dart' as _i13; -import 'package:privacyidea_authenticator/model/states/push_request_state.dart' as _i7; -import 'package:privacyidea_authenticator/model/tokens/push_token.dart' as _i11; -import 'package:privacyidea_authenticator/utils/firebase_utils.dart' as _i4; -import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart' as _i5; -import 'package:privacyidea_authenticator/utils/push_provider.dart' as _i12; -import 'package:privacyidea_authenticator/utils/rsa_utils.dart' as _i6; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: deprecated_member_use -// ignore_for_file: deprecated_member_use_from_same_package -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class - -class _FakeRSAPublicKey_0 extends _i1.SmartFake implements _i2.RSAPublicKey { - _FakeRSAPublicKey_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeRSAPrivateKey_1 extends _i1.SmartFake implements _i2.RSAPrivateKey { - _FakeRSAPrivateKey_1( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeAsymmetricKeyPair_2 extends _i1.SmartFake implements _i2.AsymmetricKeyPair { - _FakeAsymmetricKeyPair_2( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeResponse_3 extends _i1.SmartFake implements _i3.Response { - _FakeResponse_3( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeFirebaseUtils_4 extends _i1.SmartFake implements _i4.FirebaseUtils { - _FakeFirebaseUtils_4( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakePrivacyideaIOClient_5 extends _i1.SmartFake implements _i5.PrivacyideaIOClient { - _FakePrivacyideaIOClient_5( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeRsaUtils_6 extends _i1.SmartFake implements _i6.RsaUtils { - _FakeRsaUtils_6( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakePushRequestState_7 extends _i1.SmartFake implements _i7.PushRequestState { - _FakePushRequestState_7( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -/// A class which mocks [RsaUtils]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockRsaUtils extends _i1.Mock implements _i6.RsaUtils { - MockRsaUtils() { - _i1.throwOnMissingStub(this); - } - - @override - _i2.RSAPublicKey deserializeRSAPublicKeyPKCS1(String? keyStr) => (super.noSuchMethod( - Invocation.method( - #deserializeRSAPublicKeyPKCS1, - [keyStr], - ), - returnValue: _FakeRSAPublicKey_0( - this, - Invocation.method( - #deserializeRSAPublicKeyPKCS1, - [keyStr], - ), - ), - ) as _i2.RSAPublicKey); - - @override - String serializeRSAPublicKeyPKCS1(_i2.RSAPublicKey? publicKey) => (super.noSuchMethod( - Invocation.method( - #serializeRSAPublicKeyPKCS1, - [publicKey], - ), - returnValue: _i8.dummyValue( - this, - Invocation.method( - #serializeRSAPublicKeyPKCS1, - [publicKey], - ), - ), - ) as String); - - @override - _i2.RSAPublicKey deserializeRSAPublicKeyPKCS8(String? keyStr) => (super.noSuchMethod( - Invocation.method( - #deserializeRSAPublicKeyPKCS8, - [keyStr], - ), - returnValue: _FakeRSAPublicKey_0( - this, - Invocation.method( - #deserializeRSAPublicKeyPKCS8, - [keyStr], - ), - ), - ) as _i2.RSAPublicKey); - - @override - String serializeRSAPublicKeyPKCS8(_i2.RSAPublicKey? key) => (super.noSuchMethod( - Invocation.method( - #serializeRSAPublicKeyPKCS8, - [key], - ), - returnValue: _i8.dummyValue( - this, - Invocation.method( - #serializeRSAPublicKeyPKCS8, - [key], - ), - ), - ) as String); - - @override - String serializeRSAPrivateKeyPKCS1(_i2.RSAPrivateKey? key) => (super.noSuchMethod( - Invocation.method( - #serializeRSAPrivateKeyPKCS1, - [key], - ), - returnValue: _i8.dummyValue( - this, - Invocation.method( - #serializeRSAPrivateKeyPKCS1, - [key], - ), - ), - ) as String); - - @override - _i2.RSAPrivateKey deserializeRSAPrivateKeyPKCS1(String? keyStr) => (super.noSuchMethod( - Invocation.method( - #deserializeRSAPrivateKeyPKCS1, - [keyStr], - ), - returnValue: _FakeRSAPrivateKey_1( - this, - Invocation.method( - #deserializeRSAPrivateKeyPKCS1, - [keyStr], - ), - ), - ) as _i2.RSAPrivateKey); - - @override - bool verifyRSASignature( - _i2.RSAPublicKey? publicKey, - _i9.Uint8List? signedMessage, - _i9.Uint8List? signature, - ) => - (super.noSuchMethod( - Invocation.method( - #verifyRSASignature, - [ - publicKey, - signedMessage, - signature, - ], - ), - returnValue: false, - ) as bool); - - @override - _i10.Future trySignWithToken( - _i11.PushToken? token, - String? message, - ) => - (super.noSuchMethod( - Invocation.method( - #trySignWithToken, - [ - token, - message, - ], - ), - returnValue: _i10.Future.value(), - ) as _i10.Future); - - @override - _i10.Future<_i2.AsymmetricKeyPair<_i2.RSAPublicKey, _i2.RSAPrivateKey>> generateRSAKeyPair() => (super.noSuchMethod( - Invocation.method( - #generateRSAKeyPair, - [], - ), - returnValue: - _i10.Future<_i2.AsymmetricKeyPair<_i2.RSAPublicKey, _i2.RSAPrivateKey>>.value(_FakeAsymmetricKeyPair_2<_i2.RSAPublicKey, _i2.RSAPrivateKey>( - this, - Invocation.method( - #generateRSAKeyPair, - [], - ), - )), - ) as _i10.Future<_i2.AsymmetricKeyPair<_i2.RSAPublicKey, _i2.RSAPrivateKey>>); - - @override - String createBase32Signature( - _i2.RSAPrivateKey? privateKey, - _i9.Uint8List? dataToSign, - ) => - (super.noSuchMethod( - Invocation.method( - #createBase32Signature, - [ - privateKey, - dataToSign, - ], - ), - returnValue: _i8.dummyValue( - this, - Invocation.method( - #createBase32Signature, - [ - privateKey, - dataToSign, - ], - ), - ), - ) as String); - - @override - _i9.Uint8List createRSASignature( - _i2.RSAPrivateKey? privateKey, - _i9.Uint8List? dataToSign, - ) => - (super.noSuchMethod( - Invocation.method( - #createRSASignature, - [ - privateKey, - dataToSign, - ], - ), - returnValue: _i9.Uint8List(0), - ) as _i9.Uint8List); -} - -/// A class which mocks [PrivacyideaIOClient]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPrivacyideaIOClient extends _i1.Mock implements _i5.PrivacyideaIOClient { - MockPrivacyideaIOClient() { - _i1.throwOnMissingStub(this); - } - - @override - _i10.Future triggerNetworkAccessPermission({ - required Uri? url, - bool? sslVerify = true, - bool? isRetry = false, - }) => - (super.noSuchMethod( - Invocation.method( - #triggerNetworkAccessPermission, - [], - { - #url: url, - #sslVerify: sslVerify, - #isRetry: isRetry, - }, - ), - returnValue: _i10.Future.value(false), - ) as _i10.Future); - - @override - _i10.Future<_i3.Response> doPost({ - required Uri? url, - required Map? body, - bool? sslVerify = true, - }) => - (super.noSuchMethod( - Invocation.method( - #doPost, - [], - { - #url: url, - #body: body, - #sslVerify: sslVerify, - }, - ), - returnValue: _i10.Future<_i3.Response>.value(_FakeResponse_3( - this, - Invocation.method( - #doPost, - [], - { - #url: url, - #body: body, - #sslVerify: sslVerify, - }, - ), - )), - ) as _i10.Future<_i3.Response>); - - @override - _i10.Future<_i3.Response> doGet({ - required Uri? url, - required Map? parameters, - bool? sslVerify = true, - }) => - (super.noSuchMethod( - Invocation.method( - #doGet, - [], - { - #url: url, - #parameters: parameters, - #sslVerify: sslVerify, - }, - ), - returnValue: _i10.Future<_i3.Response>.value(_FakeResponse_3( - this, - Invocation.method( - #doGet, - [], - { - #url: url, - #parameters: parameters, - #sslVerify: sslVerify, - }, - ), - )), - ) as _i10.Future<_i3.Response>); -} - -/// A class which mocks [PushProvider]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPushProvider extends _i1.Mock implements _i12.PushProvider { - MockPushProvider() { - _i1.throwOnMissingStub(this); - } - - @override - bool get pollingIsEnabled => (super.noSuchMethod( - Invocation.getter(#pollingIsEnabled), - returnValue: false, - ) as bool); - - @override - set pollingIsEnabled(bool? _pollingIsEnabled) => super.noSuchMethod( - Invocation.setter( - #pollingIsEnabled, - _pollingIsEnabled, - ), - returnValueForMissingStub: null, - ); - - @override - _i4.FirebaseUtils get firebaseUtils => (super.noSuchMethod( - Invocation.getter(#firebaseUtils), - returnValue: _FakeFirebaseUtils_4( - this, - Invocation.getter(#firebaseUtils), - ), - ) as _i4.FirebaseUtils); - - @override - _i5.PrivacyideaIOClient get ioClient => (super.noSuchMethod( - Invocation.getter(#ioClient), - returnValue: _FakePrivacyideaIOClient_5( - this, - Invocation.getter(#ioClient), - ), - ) as _i5.PrivacyideaIOClient); - - @override - _i6.RsaUtils get rsaUtils => (super.noSuchMethod( - Invocation.getter(#rsaUtils), - returnValue: _FakeRsaUtils_6( - this, - Invocation.getter(#rsaUtils), - ), - ) as _i6.RsaUtils); - - @override - void setPollingEnabled(bool? enablePolling) => super.noSuchMethod( - Invocation.method( - #setPollingEnabled, - [enablePolling], - ), - returnValueForMissingStub: null, - ); - - @override - _i10.Future pollForChallenges({required bool? isManually}) => (super.noSuchMethod( - Invocation.method( - #pollForChallenges, - [], - {#isManually: isManually}, - ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); - - @override - _i10.Future pollForChallenge( - _i11.PushToken? token, { - bool? isManually = true, - }) => - (super.noSuchMethod( - Invocation.method( - #pollForChallenge, - [token], - {#isManually: isManually}, - ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); - - @override - _i10.Future<(List<_i11.PushToken>, List<_i11.PushToken>)?> updateFirebaseToken([String? firebaseToken]) => (super.noSuchMethod( - Invocation.method( - #updateFirebaseToken, - [firebaseToken], - ), - returnValue: _i10.Future<(List<_i11.PushToken>, List<_i11.PushToken>)?>.value(), - ) as _i10.Future<(List<_i11.PushToken>, List<_i11.PushToken>)?>); - - @override - void unsubscribe(void Function(_i13.PushRequest)? newRequest) => super.noSuchMethod( - Invocation.method( - #unsubscribe, - [newRequest], - ), - returnValueForMissingStub: null, - ); - - @override - void subscribe(void Function(_i13.PushRequest)? newRequest) => super.noSuchMethod( - Invocation.method( - #subscribe, - [newRequest], - ), - returnValueForMissingStub: null, - ); -} - -/// A class which mocks [PushRequestRepository]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPushRequestRepository extends _i1.Mock implements _i14.PushRequestRepository { - MockPushRequestRepository() { - _i1.throwOnMissingStub(this); - } - - @override - _i10.Future<_i7.PushRequestState> loadState() => (super.noSuchMethod( - Invocation.method( - #loadState, - [], - ), - returnValue: _i10.Future<_i7.PushRequestState>.value(_FakePushRequestState_7( - this, - Invocation.method( - #loadState, - [], - ), - )), - ) as _i10.Future<_i7.PushRequestState>); - - @override - _i10.Future saveState(_i7.PushRequestState? pushRequestState) => (super.noSuchMethod( - Invocation.method( - #saveState, - [pushRequestState], - ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); - - @override - _i10.Future clearState() => (super.noSuchMethod( - Invocation.method( - #clearState, - [], - ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); - - @override - _i10.Future<_i7.PushRequestState> add( - _i13.PushRequest? pushRequest, { - _i7.PushRequestState? state, - }) => - (super.noSuchMethod( - Invocation.method( - #add, - [pushRequest], - {#state: state}, - ), - returnValue: _i10.Future<_i7.PushRequestState>.value(_FakePushRequestState_7( - this, - Invocation.method( - #add, - [pushRequest], - {#state: state}, - ), - )), - ) as _i10.Future<_i7.PushRequestState>); - - @override - _i10.Future<_i7.PushRequestState> remove( - _i13.PushRequest? pushRequest, { - _i7.PushRequestState? state, - }) => - (super.noSuchMethod( - Invocation.method( - #remove, - [pushRequest], - {#state: state}, - ), - returnValue: _i10.Future<_i7.PushRequestState>.value(_FakePushRequestState_7( - this, - Invocation.method( - #remove, - [pushRequest], - {#state: state}, - ), - )), - ) as _i10.Future<_i7.PushRequestState>); -} diff --git a/test/unit_test/state_notifiers/settings_notifier_test.dart b/test/unit_test/state_notifiers/settings_notifier_test.dart index 0e5e80d71..a87ebc3bc 100644 --- a/test/unit_test/state_notifiers/settings_notifier_test.dart +++ b/test/unit_test/state_notifiers/settings_notifier_test.dart @@ -3,12 +3,11 @@ import 'dart:ui'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:privacyidea_authenticator/interfaces/repo/settings_repository.dart'; import 'package:privacyidea_authenticator/model/states/settings_state.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; -import 'package:mockito/annotations.dart'; -import 'settings_notifier_test.mocks.dart'; +import '../../tests_app_wrapper.mocks.dart'; + final _state = SettingsState( isFirstRun: false, @@ -21,7 +20,6 @@ final _state = SettingsState( crashReportRecipients: {'someone'}, ); -@GenerateMocks([SettingsRepository]) void main() { _testSettingsNotifier(); } diff --git a/test/unit_test/state_notifiers/settings_notifier_test.mocks.dart b/test/unit_test/state_notifiers/settings_notifier_test.mocks.dart deleted file mode 100644 index fc48da813..000000000 --- a/test/unit_test/state_notifiers/settings_notifier_test.mocks.dart +++ /dev/null @@ -1,70 +0,0 @@ -// Mocks generated by Mockito 5.4.4 from annotations -// in privacyidea_authenticator/test/unit_test/state_notifiers/settings_notifier_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i4; - -import 'package:mockito/mockito.dart' as _i1; -import 'package:privacyidea_authenticator/interfaces/repo/settings_repository.dart' - as _i3; -import 'package:privacyidea_authenticator/model/states/settings_state.dart' - as _i2; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: deprecated_member_use -// ignore_for_file: deprecated_member_use_from_same_package -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class - -class _FakeSettingsState_0 extends _i1.SmartFake implements _i2.SettingsState { - _FakeSettingsState_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -/// A class which mocks [SettingsRepository]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockSettingsRepository extends _i1.Mock - implements _i3.SettingsRepository { - MockSettingsRepository() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.Future saveSettings(_i2.SettingsState? settings) => - (super.noSuchMethod( - Invocation.method( - #saveSettings, - [settings], - ), - returnValue: _i4.Future.value(false), - ) as _i4.Future); - - @override - _i4.Future<_i2.SettingsState> loadSettings() => (super.noSuchMethod( - Invocation.method( - #loadSettings, - [], - ), - returnValue: _i4.Future<_i2.SettingsState>.value(_FakeSettingsState_0( - this, - Invocation.method( - #loadSettings, - [], - ), - )), - ) as _i4.Future<_i2.SettingsState>); -} diff --git a/test/unit_test/state_notifiers/token_folder_notifier_test.dart b/test/unit_test/state_notifiers/token_folder_notifier_test.dart index d26b02c38..770f34a37 100644 --- a/test/unit_test/state_notifiers/token_folder_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_folder_notifier_test.dart @@ -1,15 +1,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:mockito/annotations.dart'; import 'package:privacyidea_authenticator/model/states/token_folder_state.dart'; import 'package:privacyidea_authenticator/model/token_folder.dart'; -import 'package:privacyidea_authenticator/interfaces/repo/token_folder_repository.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; -import 'token_folder_notifier_test.mocks.dart'; +import '../../tests_app_wrapper.mocks.dart'; -@GenerateMocks([TokenFolderRepository]) void main() { _testTokenFolderNotifier(); } diff --git a/test/unit_test/state_notifiers/token_folder_notifier_test.mocks.dart b/test/unit_test/state_notifiers/token_folder_notifier_test.mocks.dart deleted file mode 100644 index aed7f00ea..000000000 --- a/test/unit_test/state_notifiers/token_folder_notifier_test.mocks.dart +++ /dev/null @@ -1,54 +0,0 @@ -// Mocks generated by Mockito 5.4.4 from annotations -// in privacyidea_authenticator/test/unit_test/state_notifiers/token_folder_notifier_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i3; - -import 'package:mockito/mockito.dart' as _i1; -import 'package:privacyidea_authenticator/interfaces/repo/token_folder_repository.dart' - as _i2; -import 'package:privacyidea_authenticator/model/token_folder.dart' as _i4; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: deprecated_member_use -// ignore_for_file: deprecated_member_use_from_same_package -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class - -/// A class which mocks [TokenFolderRepository]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockTokenFolderRepository extends _i1.Mock - implements _i2.TokenFolderRepository { - MockTokenFolderRepository() { - _i1.throwOnMissingStub(this); - } - - @override - _i3.Future saveReplaceList(List<_i4.TokenFolder>? folders) => - (super.noSuchMethod( - Invocation.method( - #saveReplaceList, - [folders], - ), - returnValue: _i3.Future.value(false), - ) as _i3.Future); - - @override - _i3.Future> loadFolders() => (super.noSuchMethod( - Invocation.method( - #loadFolders, - [], - ), - returnValue: - _i3.Future>.value(<_i4.TokenFolder>[]), - ) as _i3.Future>); -} diff --git a/test/unit_test/state_notifiers/token_notifier_test.dart b/test/unit_test/state_notifiers/token_notifier_test.dart index 4e129361e..e343e33f7 100644 --- a/test/unit_test/state_notifiers/token_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_notifier_test.dart @@ -2,10 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart'; import 'package:mockito/mockito.dart'; -import 'package:mockito/annotations.dart'; import 'package:pointycastle/export.dart'; -import 'package:privacyidea_authenticator/interfaces/repo/settings_repository.dart'; -import 'package:privacyidea_authenticator/interfaces/repo/token_repository.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; import 'package:privacyidea_authenticator/model/enums/push_token_rollout_state.dart'; import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; @@ -17,23 +14,13 @@ import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; -import 'package:privacyidea_authenticator/utils/firebase_utils.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; -import 'token_notifier_test.mocks.dart'; +import '../../tests_app_wrapper.mocks.dart'; + -@GenerateMocks( - [ - TokenRepository, - SettingsRepository, - RsaUtils, - PrivacyideaIOClient, - FirebaseUtils, - ], -) void main() { _testTokenNotifier(); } diff --git a/test/unit_test/state_notifiers/token_notifier_test.mocks.dart b/test/unit_test/state_notifiers/token_notifier_test.mocks.dart deleted file mode 100644 index a950d5238..000000000 --- a/test/unit_test/state_notifiers/token_notifier_test.mocks.dart +++ /dev/null @@ -1,618 +0,0 @@ -// Mocks generated by Mockito 5.4.4 from annotations -// in privacyidea_authenticator/test/unit_test/state_notifiers/token_notifier_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; -import 'dart:typed_data' as _i11; - -import 'package:firebase_messaging/firebase_messaging.dart' as _i15; -import 'package:http/http.dart' as _i4; -import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i10; -import 'package:pi_authenticator_legacy/pi_authenticator_legacy.dart' as _i16; -import 'package:pointycastle/export.dart' as _i3; -import 'package:privacyidea_authenticator/interfaces/repo/settings_repository.dart' - as _i8; -import 'package:privacyidea_authenticator/interfaces/repo/token_repository.dart' - as _i5; -import 'package:privacyidea_authenticator/model/states/settings_state.dart' - as _i2; -import 'package:privacyidea_authenticator/model/tokens/push_token.dart' as _i12; -import 'package:privacyidea_authenticator/model/tokens/token.dart' as _i7; -import 'package:privacyidea_authenticator/utils/firebase_utils.dart' as _i14; -import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart' - as _i13; -import 'package:privacyidea_authenticator/utils/rsa_utils.dart' as _i9; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: deprecated_member_use -// ignore_for_file: deprecated_member_use_from_same_package -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class - -class _FakeSettingsState_0 extends _i1.SmartFake implements _i2.SettingsState { - _FakeSettingsState_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeRSAPublicKey_1 extends _i1.SmartFake implements _i3.RSAPublicKey { - _FakeRSAPublicKey_1( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeRSAPrivateKey_2 extends _i1.SmartFake implements _i3.RSAPrivateKey { - _FakeRSAPrivateKey_2( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeAsymmetricKeyPair_3 extends _i1.SmartFake - implements _i3.AsymmetricKeyPair { - _FakeAsymmetricKeyPair_3( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeResponse_4 extends _i1.SmartFake implements _i4.Response { - _FakeResponse_4( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -/// A class which mocks [TokenRepository]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockTokenRepository extends _i1.Mock implements _i5.TokenRepository { - MockTokenRepository() { - _i1.throwOnMissingStub(this); - } - - @override - _i6.Future<_i7.Token?> loadToken(String? id) => (super.noSuchMethod( - Invocation.method( - #loadToken, - [id], - ), - returnValue: _i6.Future<_i7.Token?>.value(), - ) as _i6.Future<_i7.Token?>); - - @override - _i6.Future> loadTokens() => (super.noSuchMethod( - Invocation.method( - #loadTokens, - [], - ), - returnValue: _i6.Future>.value(<_i7.Token>[]), - ) as _i6.Future>); - - @override - _i6.Future saveOrReplaceToken(_i7.Token? token) => (super.noSuchMethod( - Invocation.method( - #saveOrReplaceToken, - [token], - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); - - @override - _i6.Future> saveOrReplaceTokens( - List? tokens) => - (super.noSuchMethod( - Invocation.method( - #saveOrReplaceTokens, - [tokens], - ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); - - @override - _i6.Future deleteToken(_i7.Token? token) => (super.noSuchMethod( - Invocation.method( - #deleteToken, - [token], - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); - - @override - _i6.Future> deleteTokens(List? tokens) => - (super.noSuchMethod( - Invocation.method( - #deleteTokens, - [tokens], - ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); -} - -/// A class which mocks [SettingsRepository]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockSettingsRepository extends _i1.Mock - implements _i8.SettingsRepository { - MockSettingsRepository() { - _i1.throwOnMissingStub(this); - } - - @override - _i6.Future saveSettings(_i2.SettingsState? settings) => - (super.noSuchMethod( - Invocation.method( - #saveSettings, - [settings], - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); - - @override - _i6.Future<_i2.SettingsState> loadSettings() => (super.noSuchMethod( - Invocation.method( - #loadSettings, - [], - ), - returnValue: _i6.Future<_i2.SettingsState>.value(_FakeSettingsState_0( - this, - Invocation.method( - #loadSettings, - [], - ), - )), - ) as _i6.Future<_i2.SettingsState>); -} - -/// A class which mocks [RsaUtils]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockRsaUtils extends _i1.Mock implements _i9.RsaUtils { - MockRsaUtils() { - _i1.throwOnMissingStub(this); - } - - @override - _i3.RSAPublicKey deserializeRSAPublicKeyPKCS1(String? keyStr) => - (super.noSuchMethod( - Invocation.method( - #deserializeRSAPublicKeyPKCS1, - [keyStr], - ), - returnValue: _FakeRSAPublicKey_1( - this, - Invocation.method( - #deserializeRSAPublicKeyPKCS1, - [keyStr], - ), - ), - ) as _i3.RSAPublicKey); - - @override - String serializeRSAPublicKeyPKCS1(_i3.RSAPublicKey? publicKey) => - (super.noSuchMethod( - Invocation.method( - #serializeRSAPublicKeyPKCS1, - [publicKey], - ), - returnValue: _i10.dummyValue( - this, - Invocation.method( - #serializeRSAPublicKeyPKCS1, - [publicKey], - ), - ), - ) as String); - - @override - _i3.RSAPublicKey deserializeRSAPublicKeyPKCS8(String? keyStr) => - (super.noSuchMethod( - Invocation.method( - #deserializeRSAPublicKeyPKCS8, - [keyStr], - ), - returnValue: _FakeRSAPublicKey_1( - this, - Invocation.method( - #deserializeRSAPublicKeyPKCS8, - [keyStr], - ), - ), - ) as _i3.RSAPublicKey); - - @override - String serializeRSAPublicKeyPKCS8(_i3.RSAPublicKey? key) => - (super.noSuchMethod( - Invocation.method( - #serializeRSAPublicKeyPKCS8, - [key], - ), - returnValue: _i10.dummyValue( - this, - Invocation.method( - #serializeRSAPublicKeyPKCS8, - [key], - ), - ), - ) as String); - - @override - String serializeRSAPrivateKeyPKCS1(_i3.RSAPrivateKey? key) => - (super.noSuchMethod( - Invocation.method( - #serializeRSAPrivateKeyPKCS1, - [key], - ), - returnValue: _i10.dummyValue( - this, - Invocation.method( - #serializeRSAPrivateKeyPKCS1, - [key], - ), - ), - ) as String); - - @override - _i3.RSAPrivateKey deserializeRSAPrivateKeyPKCS1(String? keyStr) => - (super.noSuchMethod( - Invocation.method( - #deserializeRSAPrivateKeyPKCS1, - [keyStr], - ), - returnValue: _FakeRSAPrivateKey_2( - this, - Invocation.method( - #deserializeRSAPrivateKeyPKCS1, - [keyStr], - ), - ), - ) as _i3.RSAPrivateKey); - - @override - bool verifyRSASignature( - _i3.RSAPublicKey? publicKey, - _i11.Uint8List? signedMessage, - _i11.Uint8List? signature, - ) => - (super.noSuchMethod( - Invocation.method( - #verifyRSASignature, - [ - publicKey, - signedMessage, - signature, - ], - ), - returnValue: false, - ) as bool); - - @override - _i6.Future trySignWithToken( - _i12.PushToken? token, - String? message, - ) => - (super.noSuchMethod( - Invocation.method( - #trySignWithToken, - [ - token, - message, - ], - ), - returnValue: _i6.Future.value(), - ) as _i6.Future); - - @override - _i6.Future<_i3.AsymmetricKeyPair<_i3.RSAPublicKey, _i3.RSAPrivateKey>> - generateRSAKeyPair() => (super.noSuchMethod( - Invocation.method( - #generateRSAKeyPair, - [], - ), - returnValue: _i6.Future< - _i3.AsymmetricKeyPair<_i3.RSAPublicKey, - _i3.RSAPrivateKey>>.value( - _FakeAsymmetricKeyPair_3<_i3.RSAPublicKey, _i3.RSAPrivateKey>( - this, - Invocation.method( - #generateRSAKeyPair, - [], - ), - )), - ) as _i6.Future< - _i3.AsymmetricKeyPair<_i3.RSAPublicKey, _i3.RSAPrivateKey>>); - - @override - String createBase32Signature( - _i3.RSAPrivateKey? privateKey, - _i11.Uint8List? dataToSign, - ) => - (super.noSuchMethod( - Invocation.method( - #createBase32Signature, - [ - privateKey, - dataToSign, - ], - ), - returnValue: _i10.dummyValue( - this, - Invocation.method( - #createBase32Signature, - [ - privateKey, - dataToSign, - ], - ), - ), - ) as String); - - @override - _i11.Uint8List createRSASignature( - _i3.RSAPrivateKey? privateKey, - _i11.Uint8List? dataToSign, - ) => - (super.noSuchMethod( - Invocation.method( - #createRSASignature, - [ - privateKey, - dataToSign, - ], - ), - returnValue: _i11.Uint8List(0), - ) as _i11.Uint8List); -} - -/// A class which mocks [PrivacyideaIOClient]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPrivacyideaIOClient extends _i1.Mock - implements _i13.PrivacyideaIOClient { - MockPrivacyideaIOClient() { - _i1.throwOnMissingStub(this); - } - - @override - _i6.Future triggerNetworkAccessPermission({ - required Uri? url, - bool? sslVerify = true, - bool? isRetry = false, - }) => - (super.noSuchMethod( - Invocation.method( - #triggerNetworkAccessPermission, - [], - { - #url: url, - #sslVerify: sslVerify, - #isRetry: isRetry, - }, - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); - - @override - _i6.Future<_i4.Response> doPost({ - required Uri? url, - required Map? body, - bool? sslVerify = true, - }) => - (super.noSuchMethod( - Invocation.method( - #doPost, - [], - { - #url: url, - #body: body, - #sslVerify: sslVerify, - }, - ), - returnValue: _i6.Future<_i4.Response>.value(_FakeResponse_4( - this, - Invocation.method( - #doPost, - [], - { - #url: url, - #body: body, - #sslVerify: sslVerify, - }, - ), - )), - ) as _i6.Future<_i4.Response>); - - @override - _i6.Future<_i4.Response> doGet({ - required Uri? url, - required Map? parameters, - bool? sslVerify = true, - }) => - (super.noSuchMethod( - Invocation.method( - #doGet, - [], - { - #url: url, - #parameters: parameters, - #sslVerify: sslVerify, - }, - ), - returnValue: _i6.Future<_i4.Response>.value(_FakeResponse_4( - this, - Invocation.method( - #doGet, - [], - { - #url: url, - #parameters: parameters, - #sslVerify: sslVerify, - }, - ), - )), - ) as _i6.Future<_i4.Response>); -} - -/// A class which mocks [FirebaseUtils]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockFirebaseUtils extends _i1.Mock implements _i14.FirebaseUtils { - MockFirebaseUtils() { - _i1.throwOnMissingStub(this); - } - - @override - _i6.Future initFirebase({ - required _i6.Future Function(_i15.RemoteMessage)? foregroundHandler, - required _i6.Future Function(_i15.RemoteMessage)? backgroundHandler, - required dynamic Function(String?)? updateFirebaseToken, - }) => - (super.noSuchMethod( - Invocation.method( - #initFirebase, - [], - { - #foregroundHandler: foregroundHandler, - #backgroundHandler: backgroundHandler, - #updateFirebaseToken: updateFirebaseToken, - }, - ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); - - @override - _i6.Future getFBToken() => (super.noSuchMethod( - Invocation.method( - #getFBToken, - [], - ), - returnValue: _i6.Future.value(), - ) as _i6.Future); - - @override - _i6.Future deleteFirebaseToken() => (super.noSuchMethod( - Invocation.method( - #deleteFirebaseToken, - [], - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); - - @override - _i6.Future setCurrentFirebaseToken(String? str) => (super.noSuchMethod( - Invocation.method( - #setCurrentFirebaseToken, - [str], - ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); - - @override - _i6.Future getCurrentFirebaseToken() => (super.noSuchMethod( - Invocation.method( - #getCurrentFirebaseToken, - [], - ), - returnValue: _i6.Future.value(), - ) as _i6.Future); - - @override - _i6.Future setNewFirebaseToken(String? str) => (super.noSuchMethod( - Invocation.method( - #setNewFirebaseToken, - [str], - ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); - - @override - _i6.Future getNewFirebaseToken() => (super.noSuchMethod( - Invocation.method( - #getNewFirebaseToken, - [], - ), - returnValue: _i6.Future.value(), - ) as _i6.Future); -} - -/// A class which mocks [LegacyUtils]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockLegacyUtils extends _i1.Mock implements _i16.LegacyUtils { - MockLegacyUtils() { - _i1.throwOnMissingStub(this); - } - - @override - _i6.Future sign( - String? serial, - String? message, - ) => - (super.noSuchMethod( - Invocation.method( - #sign, - [ - serial, - message, - ], - ), - returnValue: _i6.Future.value(_i10.dummyValue( - this, - Invocation.method( - #sign, - [ - serial, - message, - ], - ), - )), - ) as _i6.Future); - - @override - _i6.Future verify( - String? serial, - String? signedData, - String? signature, - ) => - (super.noSuchMethod( - Invocation.method( - #verify, - [ - serial, - signedData, - signature, - ], - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); -} From e15ad40f04b21cde9c7a2b5dc7fa8bebba22f53c Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 5 Aug 2024 12:19:55 +0200 Subject: [PATCH 017/285] refactoring --- build.yaml | 4 +- integration_test/add_tokens_test.dart | 4 +- integration_test/copy_to_clipboard_test.dart | 4 +- integration_test/rename_and_delete_test.dart | 4 +- integration_test/two_step_rollout_test.dart | 4 +- integration_test/views_test.dart | 4 +- lib/api/token_container_api_endpoint.dart | 2 +- .../container_credentials_repository.dart | 11 + .../repo/introduction_repository.dart | 2 +- .../repo/push_request_repository.dart | 2 +- lib/interfaces/repo/settings_repository.dart | 2 +- .../deep_link_listener.dart | 6 +- .../token_state_listener.dart | 2 +- .../enums/introduction_extension.dart | 2 +- .../riverpod_states/credentials_state.dart | 29 ++ .../credentials_state.freezed.dart | 142 ++++++++ .../riverpod_states/credentials_state.g.dart | 19 ++ .../introduction_state.dart | 0 .../introduction_state.g.dart | 0 .../progress_state.dart | 0 .../push_request_state.dart | 0 .../push_request_state.g.dart | 0 .../settings_state.dart | 0 .../token_container_state.dart | 0 .../token_filter.dart | 0 .../token_folder_state.dart | 0 .../token_state.dart | 0 .../container_credentials_processor.dart | 4 +- .../preference_introduction_repository.dart | 2 +- lib/repo/preference_settings_repository.dart | 2 +- ...cure_container_credentials_repository.dart | 82 +++++ lib/repo/secure_push_request_repository.dart | 2 +- .../credential_nofitier.dart | 72 ++++ .../credential_nofitier.g.dart | 27 ++ ...k_provider.dart => deeplink_notifier.dart} | 8 +- ...ovider.g.dart => deeplink_notifier.g.dart} | 21 +- .../token_container_notifier.dart | 143 ++++++++ ...g.dart => token_container_notifier.g.dart} | 116 +++---- .../token_container_provider.dart | 315 ------------------ .../introduction_provider.dart | 2 +- .../progress_state_provider.dart | 2 +- .../push_request_provider.dart | 2 +- .../settings_provider.dart | 2 +- .../sortable_provider.dart | 4 +- .../token_folder_provider.dart | 2 +- .../token_provider.dart | 6 +- .../token_filter_provider.dart | 2 +- .../home_widget_token_state_listener.dart | 2 +- .../token_container_token_state_listener.dart | 11 +- .../completed_introduction_notifier.dart | 2 +- .../progress_state_notifier.dart | 2 +- .../push_request_notifier.dart | 2 +- .../state_notifiers/settings_notifier.dart | 2 +- .../token_folder_notifier.dart | 2 +- .../state_notifiers/token_notifier.dart | 2 +- lib/views/main_view/main_view.dart | 2 +- .../filter_token_widget.dart | 2 +- .../token_folder_expandable.dart | 2 +- .../main_view_tokens_list_filtered.dart | 2 +- .../dialogs/select_tokens_dialog.dart | 2 +- lib/widgets/app_wrapper.dart | 17 +- lib/widgets/default_refresh_indicator.dart | 7 +- test/tests_app_wrapper.mocks.dart | 55 ++- .../model/states/introduction_state_test.dart | 2 +- .../model/states/settings_state_test.dart | 2 +- .../model/states/token_folder_state_test.dart | 2 +- .../model/states/token_state_test.dart | 2 +- .../push_request_notifier_test.dart | 2 +- .../settings_notifier_test.dart | 2 +- .../sortable_notifier_test.dart | 2 +- .../token_folder_notifier_test.dart | 2 +- .../state_notifiers/token_notifier_test.dart | 4 +- 72 files changed, 704 insertions(+), 489 deletions(-) create mode 100644 lib/interfaces/repo/container_credentials_repository.dart create mode 100644 lib/model/riverpod_states/credentials_state.dart create mode 100644 lib/model/riverpod_states/credentials_state.freezed.dart create mode 100644 lib/model/riverpod_states/credentials_state.g.dart rename lib/model/{states => riverpod_states}/introduction_state.dart (100%) rename lib/model/{states => riverpod_states}/introduction_state.g.dart (100%) rename lib/model/{states => riverpod_states}/progress_state.dart (100%) rename lib/model/{states => riverpod_states}/push_request_state.dart (100%) rename lib/model/{states => riverpod_states}/push_request_state.g.dart (100%) rename lib/model/{states => riverpod_states}/settings_state.dart (100%) rename lib/model/{states => riverpod_states}/token_container_state.dart (100%) rename lib/model/{states => riverpod_states}/token_filter.dart (100%) rename lib/model/{states => riverpod_states}/token_folder_state.dart (100%) rename lib/model/{states => riverpod_states}/token_state.dart (100%) create mode 100644 lib/repo/secure_container_credentials_repository.dart create mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart create mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.g.dart rename lib/utils/riverpod/riverpod_providers/generated_providers/{deeplink_provider.dart => deeplink_notifier.dart} (89%) rename lib/utils/riverpod/riverpod_providers/generated_providers/{deeplink_provider.g.dart => deeplink_notifier.g.dart} (52%) create mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart rename lib/utils/riverpod/riverpod_providers/generated_providers/{token_container_provider.g.dart => token_container_notifier.g.dart} (51%) delete mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart diff --git a/build.yaml b/build.yaml index 58baae405..4d5a60720 100644 --- a/build.yaml +++ b/build.yaml @@ -12,8 +12,8 @@ targets: provider_family_name_prefix: "" # (default) # Could be changed to "Pod", such that riverpod_generator # would generate "countPod" instead of "countProvider" - provider_name_suffix: "" # (default) + provider_name_suffix: "Provider" # (default) # Similar to provider_name_suffix, this is an option for renaming # providers with parameters ("families"). # This takes precedence over provider_name_suffix. - provider_family_name_suffix: "Of" # (default) \ No newline at end of file + provider_family_name_suffix: "ProviderOf" # (default) \ No newline at end of file diff --git a/integration_test/add_tokens_test.dart b/integration_test/add_tokens_test.dart index 31fcbd7ee..30511a9e1 100644 --- a/integration_test/add_tokens_test.dart +++ b/integration_test/add_tokens_test.dart @@ -8,8 +8,8 @@ import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; import 'package:privacyidea_authenticator/model/enums/encodings.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/enums/token_types.dart'; -import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; -import 'package:privacyidea_authenticator/model/states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/completed_introduction_notifier.dart'; diff --git a/integration_test/copy_to_clipboard_test.dart b/integration_test/copy_to_clipboard_test.dart index b1e5213a8..ca824d085 100644 --- a/integration_test/copy_to_clipboard_test.dart +++ b/integration_test/copy_to_clipboard_test.dart @@ -6,8 +6,8 @@ import 'package:mockito/mockito.dart'; import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; -import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; -import 'package:privacyidea_authenticator/model/states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/completed_introduction_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; diff --git a/integration_test/rename_and_delete_test.dart b/integration_test/rename_and_delete_test.dart index 150e00fe0..c3b73aabb 100644 --- a/integration_test/rename_and_delete_test.dart +++ b/integration_test/rename_and_delete_test.dart @@ -6,8 +6,8 @@ import 'package:privacyidea_authenticator/l10n/app_localizations_en.dart'; import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; -import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; -import 'package:privacyidea_authenticator/model/states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/completed_introduction_notifier.dart'; diff --git a/integration_test/two_step_rollout_test.dart b/integration_test/two_step_rollout_test.dart index 8a5e9c633..e1762ec98 100644 --- a/integration_test/two_step_rollout_test.dart +++ b/integration_test/two_step_rollout_test.dart @@ -5,8 +5,8 @@ import 'package:mockito/mockito.dart'; import 'package:privacyidea_authenticator/l10n/app_localizations_en.dart'; import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; -import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; -import 'package:privacyidea_authenticator/model/states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/completed_introduction_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index d35b50ef1..a101a3849 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -7,8 +7,8 @@ import 'package:pointycastle/asymmetric/api.dart'; import 'package:privacyidea_authenticator/l10n/app_localizations_en.dart'; import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; -import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; -import 'package:privacyidea_authenticator/model/states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/completed_introduction_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/push_request_notifier.dart'; diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index 3e0e4866d..3bab2c89f 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -7,7 +7,7 @@ import 'package:privacyidea_authenticator/utils/logger.dart'; import '../interfaces/api_endpoint.dart'; import '../model/enums/encodings.dart'; import '../model/enums/token_types.dart'; -import '../utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart'; +import '../model/riverpod_states/credentials_state.dart'; final Map> _data = { '123': { diff --git a/lib/interfaces/repo/container_credentials_repository.dart b/lib/interfaces/repo/container_credentials_repository.dart new file mode 100644 index 000000000..1dfff679d --- /dev/null +++ b/lib/interfaces/repo/container_credentials_repository.dart @@ -0,0 +1,11 @@ +import '../../model/riverpod_states/credentials_state.dart'; +import '../../model/tokens/container_credentials.dart'; + +abstract class ContainerCredentialsRepository { + Future saveCredential(ContainerCredential credential); + Future saveCredentialsState(CredentialsState credentialsState); + Future loadCredentialsState(); + Future loadCredential(String id); + Future deleteAllCredentials(); + Future deleteCredential(String id); +} diff --git a/lib/interfaces/repo/introduction_repository.dart b/lib/interfaces/repo/introduction_repository.dart index 175c29192..03e1c3f0e 100644 --- a/lib/interfaces/repo/introduction_repository.dart +++ b/lib/interfaces/repo/introduction_repository.dart @@ -1,4 +1,4 @@ -import '../../model/states/introduction_state.dart'; +import '../../model/riverpod_states/introduction_state.dart'; abstract class IntroductionRepository { Future saveCompletedIntroductions(IntroductionState introductions); diff --git a/lib/interfaces/repo/push_request_repository.dart b/lib/interfaces/repo/push_request_repository.dart index 2778e5ef0..88bcc3e07 100644 --- a/lib/interfaces/repo/push_request_repository.dart +++ b/lib/interfaces/repo/push_request_repository.dart @@ -1,5 +1,5 @@ import '../../model/push_request.dart' show PushRequest; -import '../../model/states/push_request_state.dart' show PushRequestState; +import '../../model/riverpod_states/push_request_state.dart' show PushRequestState; abstract class PushRequestRepository { /// Load the [PushRequestState] from the repository diff --git a/lib/interfaces/repo/settings_repository.dart b/lib/interfaces/repo/settings_repository.dart index 0538e84fc..7509c43dd 100644 --- a/lib/interfaces/repo/settings_repository.dart +++ b/lib/interfaces/repo/settings_repository.dart @@ -1,4 +1,4 @@ -import '../../model/states/settings_state.dart'; +import '../../model/riverpod_states/settings_state.dart'; abstract class SettingsRepository { Future saveSettings(SettingsState settings); diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart index db4c40618..7be00dae2 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart @@ -3,11 +3,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/deeplink.dart'; import '../../../../utils/logger.dart'; -import '../../../../utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.dart'; +import '../../../../utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart'; -abstract class DeepLinkListener extends StreamNotifierProviderListener { +abstract class DeepLinkListener extends StreamNotifierProviderListener { const DeepLinkListener({ - required StreamNotifierProvider deeplinkProvider, + required StreamNotifierProvider deeplinkProvider, required super.onNewState, required super.listenerName, }) : super(provider: deeplinkProvider); diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart index aa65ae8bb..a512996b0 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../model/states/token_state.dart'; +import '../../../../model/riverpod_states/token_state.dart'; import '../../../../utils/riverpod/state_notifiers/token_notifier.dart'; import '../state_notifier_provider_listener.dart'; diff --git a/lib/model/extensions/enums/introduction_extension.dart b/lib/model/extensions/enums/introduction_extension.dart index 35690875d..5aa1c038e 100644 --- a/lib/model/extensions/enums/introduction_extension.dart +++ b/lib/model/extensions/enums/introduction_extension.dart @@ -4,7 +4,7 @@ import '../../../l10n/app_localizations.dart'; import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../enums/introduction.dart'; -import '../../states/introduction_state.dart'; +import '../../riverpod_states/introduction_state.dart'; extension IntroductionX on Introduction { /// Checks if the condition for the given state is fulfilled. diff --git a/lib/model/riverpod_states/credentials_state.dart b/lib/model/riverpod_states/credentials_state.dart new file mode 100644 index 000000000..d38b2cc48 --- /dev/null +++ b/lib/model/riverpod_states/credentials_state.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; + +import 'package:collection/collection.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../tokens/container_credentials.dart'; + +part 'credentials_state.freezed.dart'; +part 'credentials_state.g.dart'; + +@Freezed() +@JsonSerializable(explicitToJson: true) +class CredentialsState with _$CredentialsState { + const CredentialsState._(); + const factory CredentialsState({ + required List credentials, + }) = _CredentialsState; + + @override + String toString() { + return 'CredentialsState{credentials: $credentials}'; + } + + ContainerCredential? credentialsOf(String containerSerial) => credentials.firstWhereOrNull((credential) => credential.serial == containerSerial); + static CredentialsState fromJsonStringList(List jsonStrings) { + final credentials = jsonStrings.map((jsonString) => ContainerCredential.fromJson(jsonDecode(jsonString))).toList(); + return CredentialsState(credentials: credentials); + } +} diff --git a/lib/model/riverpod_states/credentials_state.freezed.dart b/lib/model/riverpod_states/credentials_state.freezed.dart new file mode 100644 index 000000000..4b7988cd2 --- /dev/null +++ b/lib/model/riverpod_states/credentials_state.freezed.dart @@ -0,0 +1,142 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'credentials_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$CredentialsState { + List get credentials => + throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $CredentialsStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CredentialsStateCopyWith<$Res> { + factory $CredentialsStateCopyWith( + CredentialsState value, $Res Function(CredentialsState) then) = + _$CredentialsStateCopyWithImpl<$Res, CredentialsState>; + @useResult + $Res call({List credentials}); +} + +/// @nodoc +class _$CredentialsStateCopyWithImpl<$Res, $Val extends CredentialsState> + implements $CredentialsStateCopyWith<$Res> { + _$CredentialsStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? credentials = null, + }) { + return _then(_value.copyWith( + credentials: null == credentials + ? _value.credentials + : credentials // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CredentialsStateImplCopyWith<$Res> + implements $CredentialsStateCopyWith<$Res> { + factory _$$CredentialsStateImplCopyWith(_$CredentialsStateImpl value, + $Res Function(_$CredentialsStateImpl) then) = + __$$CredentialsStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({List credentials}); +} + +/// @nodoc +class __$$CredentialsStateImplCopyWithImpl<$Res> + extends _$CredentialsStateCopyWithImpl<$Res, _$CredentialsStateImpl> + implements _$$CredentialsStateImplCopyWith<$Res> { + __$$CredentialsStateImplCopyWithImpl(_$CredentialsStateImpl _value, + $Res Function(_$CredentialsStateImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? credentials = null, + }) { + return _then(_$CredentialsStateImpl( + credentials: null == credentials + ? _value._credentials + : credentials // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$CredentialsStateImpl extends _CredentialsState { + const _$CredentialsStateImpl( + {required final List credentials}) + : _credentials = credentials, + super._(); + + final List _credentials; + @override + List get credentials { + if (_credentials is EqualUnmodifiableListView) return _credentials; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_credentials); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CredentialsStateImpl && + const DeepCollectionEquality() + .equals(other._credentials, _credentials)); + } + + @override + int get hashCode => Object.hash( + runtimeType, const DeepCollectionEquality().hash(_credentials)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CredentialsStateImplCopyWith<_$CredentialsStateImpl> get copyWith => + __$$CredentialsStateImplCopyWithImpl<_$CredentialsStateImpl>( + this, _$identity); +} + +abstract class _CredentialsState extends CredentialsState { + const factory _CredentialsState( + {required final List credentials}) = + _$CredentialsStateImpl; + const _CredentialsState._() : super._(); + + @override + List get credentials; + @override + @JsonKey(ignore: true) + _$$CredentialsStateImplCopyWith<_$CredentialsStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/model/riverpod_states/credentials_state.g.dart b/lib/model/riverpod_states/credentials_state.g.dart new file mode 100644 index 000000000..361f8addb --- /dev/null +++ b/lib/model/riverpod_states/credentials_state.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'credentials_state.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CredentialsState _$CredentialsStateFromJson(Map json) => + CredentialsState( + credentials: (json['credentials'] as List) + .map((e) => ContainerCredential.fromJson(e as Map)) + .toList(), + ); + +Map _$CredentialsStateToJson(CredentialsState instance) => + { + 'credentials': instance.credentials.map((e) => e.toJson()).toList(), + }; diff --git a/lib/model/states/introduction_state.dart b/lib/model/riverpod_states/introduction_state.dart similarity index 100% rename from lib/model/states/introduction_state.dart rename to lib/model/riverpod_states/introduction_state.dart diff --git a/lib/model/states/introduction_state.g.dart b/lib/model/riverpod_states/introduction_state.g.dart similarity index 100% rename from lib/model/states/introduction_state.g.dart rename to lib/model/riverpod_states/introduction_state.g.dart diff --git a/lib/model/states/progress_state.dart b/lib/model/riverpod_states/progress_state.dart similarity index 100% rename from lib/model/states/progress_state.dart rename to lib/model/riverpod_states/progress_state.dart diff --git a/lib/model/states/push_request_state.dart b/lib/model/riverpod_states/push_request_state.dart similarity index 100% rename from lib/model/states/push_request_state.dart rename to lib/model/riverpod_states/push_request_state.dart diff --git a/lib/model/states/push_request_state.g.dart b/lib/model/riverpod_states/push_request_state.g.dart similarity index 100% rename from lib/model/states/push_request_state.g.dart rename to lib/model/riverpod_states/push_request_state.g.dart diff --git a/lib/model/states/settings_state.dart b/lib/model/riverpod_states/settings_state.dart similarity index 100% rename from lib/model/states/settings_state.dart rename to lib/model/riverpod_states/settings_state.dart diff --git a/lib/model/states/token_container_state.dart b/lib/model/riverpod_states/token_container_state.dart similarity index 100% rename from lib/model/states/token_container_state.dart rename to lib/model/riverpod_states/token_container_state.dart diff --git a/lib/model/states/token_filter.dart b/lib/model/riverpod_states/token_filter.dart similarity index 100% rename from lib/model/states/token_filter.dart rename to lib/model/riverpod_states/token_filter.dart diff --git a/lib/model/states/token_folder_state.dart b/lib/model/riverpod_states/token_folder_state.dart similarity index 100% rename from lib/model/states/token_folder_state.dart rename to lib/model/riverpod_states/token_folder_state.dart diff --git a/lib/model/states/token_state.dart b/lib/model/riverpod_states/token_state.dart similarity index 100% rename from lib/model/states/token_state.dart rename to lib/model/riverpod_states/token_state.dart diff --git a/lib/processors/scheme_processors/container_credentials_processor.dart b/lib/processors/scheme_processors/container_credentials_processor.dart index f0f8ea4e2..fdd7d2d8b 100644 --- a/lib/processors/scheme_processors/container_credentials_processor.dart +++ b/lib/processors/scheme_processors/container_credentials_processor.dart @@ -1,9 +1,9 @@ import 'package:privacyidea_authenticator/processors/scheme_processors/scheme_processor_interface.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart'; import '../../model/tokens/container_credentials.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart'; class ContainerCredentialsProcessor extends SchemeProcessor { @override @@ -18,6 +18,6 @@ class ContainerCredentialsProcessor extends SchemeProcessor { } final credential = ContainerCredential.fromUriMap(uri.queryParameters); Logger.warning('Adding credential to container', name: 'ContainerCredentialsProcessor'); - globalRef?.read(credentialsProvider.notifier).addCredential(credential); + globalRef?.read(credentialsNotifierProvider.notifier).addCredential(credential); } } diff --git a/lib/repo/preference_introduction_repository.dart b/lib/repo/preference_introduction_repository.dart index 5b8b28f44..96691e125 100644 --- a/lib/repo/preference_introduction_repository.dart +++ b/lib/repo/preference_introduction_repository.dart @@ -4,7 +4,7 @@ import 'package:mutex/mutex.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../interfaces/repo/introduction_repository.dart'; -import '../model/states/introduction_state.dart'; +import '../model/riverpod_states/introduction_state.dart'; import '../utils/logger.dart'; class PreferenceIntroductionRepository implements IntroductionRepository { diff --git a/lib/repo/preference_settings_repository.dart b/lib/repo/preference_settings_repository.dart index a4a1d6171..245dec83e 100644 --- a/lib/repo/preference_settings_repository.dart +++ b/lib/repo/preference_settings_repository.dart @@ -2,7 +2,7 @@ import 'package:mutex/mutex.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../interfaces/repo/settings_repository.dart'; -import '../model/states/settings_state.dart'; +import '../model/riverpod_states/settings_state.dart'; import '../model/version.dart'; class PreferenceSettingsRepository extends SettingsRepository { diff --git a/lib/repo/secure_container_credentials_repository.dart b/lib/repo/secure_container_credentials_repository.dart new file mode 100644 index 000000000..6b6bdcdbe --- /dev/null +++ b/lib/repo/secure_container_credentials_repository.dart @@ -0,0 +1,82 @@ +import 'dart:convert'; + +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:mutex/mutex.dart'; +import 'package:privacyidea_authenticator/interfaces/repo/container_credentials_repository.dart'; + +import '../model/riverpod_states/credentials_state.dart'; +import '../model/tokens/container_credentials.dart'; +import '../utils/logger.dart'; + +class SecureContainerCredentialsRepository extends ContainerCredentialsRepository { + String get containerCredentialsKey => 'containerCredentials'; + String _keyOfId(String id) => '$containerCredentialsKey.$id'; + final Mutex _m = Mutex(); + Future _protect(Future Function() f) => _m.protect(f); + final FlutterSecureStorage _storage = const FlutterSecureStorage(); + + Future _write(String key, String value) => _protect(() => _storage.write(key: key, value: value)); + Future _read(String key) async => await _protect(() async => await _storage.read(key: key)); + Future> _readAll() async => + await _protect(() async => (await _storage.readAll())..removeWhere((key, value) => !key.startsWith(containerCredentialsKey))); + Future _delete(String key) => _protect(() => _storage.delete(key: key)); + + @override + Future loadCredentialsState() async { + final credentialsJsonString = await _readAll(); + Logger.warning('Loaded credentials: $credentialsJsonString', name: 'SecureContainerCredentialsRepository'); + if (credentialsJsonString.isEmpty) { + final credentialState = CredentialsState(credentials: [ + ContainerCredential( + id: '123', + serial: '123', + ), + ]); + Logger.warning('Returning default credentials: $credentialState', name: 'SecureContainerCredentialsRepository'); + return credentialState; + } + return CredentialsState.fromJsonStringList(credentialsJsonString.values.toList()); + } + + @override + Future saveCredentialsState(CredentialsState credentialsState) async { + Logger.warning('Saving credentials: $credentialsState', name: 'SecureContainerCredentialsRepository'); + final futures = []; + for (var credential in credentialsState.credentials) { + futures.add(saveCredential(credential)); + } + await Future.wait(futures); + return await loadCredentialsState(); + } + + @override + Future deleteCredential(String id) async { + await _delete(_keyOfId(id)); + return await loadCredentialsState(); + } + + @override + Future deleteAllCredentials() async { + final credentialKeys = (await _readAll()).keys.where((key) => key.startsWith(containerCredentialsKey)); + final futures = []; + for (var key in credentialKeys) { + futures.add(_delete(key)); + } + await Future.wait(futures); + return await loadCredentialsState(); + } + + @override + Future loadCredential(String id) async { + final credentialJsonString = await _read(_keyOfId(id)); + if (credentialJsonString == null) return null; + return ContainerCredential.fromJson(jsonDecode(credentialJsonString)); + } + + @override + Future saveCredential(ContainerCredential credential) async { + final credentialJsonString = jsonEncode(credential.toJson()); + await _write(_keyOfId(credential.id), credentialJsonString); + return await loadCredentialsState(); + } +} diff --git a/lib/repo/secure_push_request_repository.dart b/lib/repo/secure_push_request_repository.dart index 20143cac7..a598f7cdb 100644 --- a/lib/repo/secure_push_request_repository.dart +++ b/lib/repo/secure_push_request_repository.dart @@ -7,7 +7,7 @@ import 'package:mutex/mutex.dart'; import '../interfaces/repo/push_request_repository.dart'; import '../model/push_request.dart'; -import '../model/states/push_request_state.dart'; +import '../model/riverpod_states/push_request_state.dart'; import '../utils/custom_int_buffer.dart'; import '../utils/logger.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart new file mode 100644 index 000000000..e0f75eab1 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart @@ -0,0 +1,72 @@ +import 'package:mutex/mutex.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../../interfaces/repo/container_credentials_repository.dart'; +import '../../../../model/riverpod_states/credentials_state.dart'; +import '../../../../model/tokens/container_credentials.dart'; +import '../../../../repo/secure_container_credentials_repository.dart'; +import '../../../logger.dart'; + +part 'credential_nofitier.g.dart'; + +@Riverpod(keepAlive: true) +class CredentialsNotifier extends _$CredentialsNotifier { + final _stateMutex = Mutex(); + final _repoMutex = Mutex(); + late ContainerCredentialsRepository _repo; + + @override + Future build() async { + _repo = SecureContainerCredentialsRepository(); + Logger.warning('Building credentialsProvider', name: 'CredentialsNotifier'); + return _repo.loadCredentialsState(); + } + + @override + Future update( + FutureOr Function(CredentialsState state) cb, { + FutureOr Function(Object, StackTrace)? onError, + }) async { + Logger.warning('Updating credentialsProvider', name: 'CredentialsNotifier'); + return super.update(cb, onError: onError); + } + + Future addCredential(ContainerCredential credential) async { + await _stateMutex.acquire(); + final newState = await _saveCredentialsToRepo(credential); + state = AsyncValue.data(newState); + _stateMutex.release(); + return newState; + } + + Future _saveCredentialsToRepo(ContainerCredential credential) async { + return await _repoMutex.protect(() async => await _repo.saveCredential(credential)); + } +} + +class MockContainerCredentialsRepository extends ContainerCredentialsRepository { + final state = CredentialsState(credentials: [ + ContainerCredential( + id: '123', + serial: '123', + ) + ]); + + @override + Future deleteAllCredentials() => Future.value(state); + + @override + Future deleteCredential(String id) => Future.value(state); + + @override + Future loadCredential(String id) => Future.value(state.credentials.firstOrNull); + + @override + Future loadCredentialsState() => Future.value(state); + + @override + Future saveCredential(ContainerCredential credential) => Future.value(state); + + @override + Future saveCredentialsState(CredentialsState credentialsState) => Future.value(state); +} diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.g.dart new file mode 100644 index 000000000..18f5e8ef7 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'credential_nofitier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$credentialsNotifierHash() => + r'459a5bf82645911570b3c7947743ff2f959fa751'; + +/// See also [CredentialsNotifier]. +@ProviderFor(CredentialsNotifier) +final credentialsNotifierProvider = + AsyncNotifierProvider.internal( + CredentialsNotifier.new, + name: r'credentialsNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$credentialsNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$CredentialsNotifier = AsyncNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart similarity index 89% rename from lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart index 8a7978799..0a12bde99 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart @@ -10,7 +10,7 @@ import '../../state_notifiers/deeplink_notifier.dart'; import '../../../home_widget_utils.dart'; import '../../../logger.dart'; -part 'deeplink_provider.g.dart'; +part 'deeplink_notifier.g.dart'; final sources = [ DeeplinkSource( @@ -26,10 +26,10 @@ final sources = [ ]; @Riverpod(keepAlive: true) -class DeeplinkProvider extends _$DeeplinkProvider { +class DeeplinkNotifier extends _$DeeplinkNotifier { @override Stream build() async* { - Logger.info('New DeeplinkProvider created', name: 'DeeplinkProvider#build'); + Logger.info('New DeeplinkNotifier created', name: 'DeeplinkNotifier#build'); final initial = await _handleInitialUri(sources); if (initial != null) yield initial; await for (var dl in _handleIncomingLinks(sources)) { @@ -43,7 +43,7 @@ class DeeplinkProvider extends _$DeeplinkProvider { if (kIsWeb) return; final groupedStream = StreamGroup.merge(sources.map((source) => source.stream)); await for (var uri in groupedStream) { - Logger.info('DeeplinkProvider got new uri'); + Logger.info('DeeplinkNotifier got new uri'); if (uri == null) return; yield DeepLink(uri); } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.g.dart similarity index 52% rename from lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.g.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.g.dart index de8523302..0d611c110 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.g.dart @@ -1,23 +1,26 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'deeplink_provider.dart'; +part of 'deeplink_notifier.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$deeplinkProviderHash() => r'9f7e1aa1b9b5a7073f208d176d2c56395f1a987a'; +String _$deeplinkNotifierHash() => r'be8127ccfb6d48a9cd5c00776c306db860fa49a1'; -/// See also [DeeplinkProvider]. -@ProviderFor(DeeplinkProvider) -final deeplinkProvider = StreamNotifierProvider.internal( - DeeplinkProvider.new, - name: r'deeplinkProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null : _$deeplinkProviderHash, +/// See also [DeeplinkNotifier]. +@ProviderFor(DeeplinkNotifier) +final deeplinkNotifierProvider = + StreamNotifierProvider.internal( + DeeplinkNotifier.new, + name: r'deeplinkNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$deeplinkNotifierHash, dependencies: null, allTransitiveDependencies: null, ); -typedef _$DeeplinkProvider = StreamNotifier; +typedef _$DeeplinkNotifier = StreamNotifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart new file mode 100644 index 000000000..c00ad0896 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -0,0 +1,143 @@ +import 'package:mutex/mutex.dart'; +import 'package:privacyidea_authenticator/interfaces/repo/container_repository.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../../api/token_container_api_endpoint.dart'; +import '../../../../model/riverpod_states/token_state.dart'; +import '../../../../model/token_container.dart'; +import '../../../../repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; +import '../../../../repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; +import '../../../../repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart'; +import '../../../../model/tokens/container_credentials.dart'; +import '../state_notifier_providers/token_provider.dart'; + +part 'token_container_notifier.g.dart'; + +@riverpod +class TokenContainerNotifier extends _$TokenContainerNotifier { + late final TokenContainerRepository _repository; + final Mutex _stateMutex = Mutex(); // Mutex to protect the state from being accessed while still waiting for the newest state to be delivered + final Mutex _repoMutex = Mutex(); // Mutex to protect the repository from being accessed while still waiting for the newest state to be delivered + + @override + Future build({ + required ContainerCredential credential, + }) async { + Logger.info('New tokenContainerStateProvider created', name: 'TokenContainerNotifier#build'); + await _stateMutex.acquire(); + _repository = HybridTokenContainerRepository( + localRepository: SecureTokenContainerRepository(containerId: credential.serial), + remoteRepository: RemoteTokenContainerRepository(apiEndpoint: TokenContainerApiEndpoint(credential: credential)), + ); + final initialState = await _repository.loadContainerState(); + Logger.debug('Initial state: $initialState', name: 'TokenContainerNotifier#build'); + _stateMutex.release(); + return initialState; + } + + Future _saveToRepo(TokenContainer state) async { + await _repoMutex.acquire(); + final newState = await _repository.saveContainerState(state); + _repoMutex.release(); + return newState; + } + + Future _fetchFromRepo() async { + await _repoMutex.acquire(); + final newState = await _repository.loadContainerState(); + _repoMutex.release(); + return newState; + } + + Future handleTokenState(TokenState tokenState) async { + await _stateMutex.acquire(); + final localTokens = tokenState.tokens.maybePiTokens; + final oldState = state.value; + if (oldState == null) throw Exception('TokenContainer is null'); + final containerTokens = tokenState.containerTokens(oldState.serial); + final localTokenTemplates = localTokens.toTemplates(); + final containerTokenTemplates = containerTokens.toTemplates(); + final newState = oldState.copyWith(localTokenTemplates: localTokenTemplates, syncedTokenTemplates: containerTokenTemplates); + final savedState = await _saveToRepo(newState); + if (savedState is! TokenContainerSynced) { + Logger.error('Failed to save state to repo', name: 'TokenContainerNotifier#handleTokenState'); + return savedState; + } + await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); + state = AsyncValue.data(savedState); + _stateMutex.release(); + return savedState; + } + + /// Adds the given [maybePiTokenTemplates] to the localTokenTemplates of the container + /// and saves the new state to the repository. The rpository decides waht to do with the new state. + /// The saved state from the repo can contain the maybePiTokenTemplates or not. + Future tryAddLocalTemplates(List maybePiTokenTemplates) async { + Logger.info( + 'Trying to add (${maybePiTokenTemplates.length}) local templates to container (${credential.serial}).', + name: 'TokenContainerNotifier#tryAddLocalTemplates', + ); + Logger.debug('Local templates (${maybePiTokenTemplates.length})', name: 'TokenContainerNotifier#tryAddLocalTemplates'); + await _stateMutex.acquire(); + final oldState = (await future); + final newLocalTokenTemplates = [...maybePiTokenTemplates]; + final newState = oldState.copyWith(localTokenTemplates: newLocalTokenTemplates); + final savedState = await _saveToRepo(newState); + Logger.debug( + 'Saved TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', + name: 'TokenContainerNotifier#tryAddLocalTemplates', + ); + await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); + state = AsyncValue.data(savedState); + _stateMutex.release(); + return savedState; + } + + Future handleDeletedTokenTemplates(List deletedPiTokenTemplates) async { + Logger.info( + 'Removing (${deletedPiTokenTemplates.length}) deleted token templates from container (${credential.serial}).', + name: 'TokenContainerNotifier#handleDeletedTokenTemplates', + ); + Logger.debug('Deleted token templates (${deletedPiTokenTemplates.length})', name: 'TokenContainerNotifier#handleDeletedTokenTemplates'); + await _stateMutex.acquire(); + final oldState = (await future); + final newLocalTokenTemplates = oldState.localTokenTemplates.where((template) => !deletedPiTokenTemplates.contains(template)).toList(); + final newState = oldState.copyWith(localTokenTemplates: newLocalTokenTemplates); + final savedState = await _saveToRepo(newState); + Logger.debug( + 'Saved TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', + name: 'TokenContainerNotifier#handleDeletedTokenTemplates', + ); + await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); + state = AsyncValue.data(savedState); + _stateMutex.release(); + return savedState; + } + + Future fetchTokens() async { + await _stateMutex.acquire(); + final savedState = await _fetchFromRepo(); + Logger.debug( + 'Fetched TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', + name: 'TokenContainerNotifier#tryAddLocalTemplates', + ); + await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); + state = AsyncValue.data(savedState); + _stateMutex.release(); + return savedState; + } + + Future sync() async { + await _stateMutex.acquire(); + final savedState = await _fetchFromRepo(); + Logger.debug( + 'Fetched TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', + name: 'TokenContainerNotifier#tryAddLocalTemplates', + ); + await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); + state = AsyncValue.data(savedState); + _stateMutex.release(); + return savedState; + } +} diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart similarity index 51% rename from lib/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.g.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart index 55c34694d..33d145432 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart @@ -1,29 +1,13 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'token_container_provider.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -CredentialsState _$CredentialsStateFromJson(Map json) => - CredentialsState( - credentials: (json['credentials'] as List) - .map((e) => ContainerCredential.fromJson(e as Map)) - .toList(), - ); - -Map _$CredentialsStateToJson(CredentialsState instance) => - { - 'credentials': instance.credentials, - }; +part of 'token_container_notifier.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$tokenContainerProviderHash() => - r'ec9832ed16a556f269ac27705af2a3cb27dd5fd1'; +String _$tokenContainerNotifierHash() => + r'fa7e50e1970516f60df6ba4b2ccf19dad3ab5736'; /// Copied from Dart SDK class _SystemHash { @@ -46,7 +30,7 @@ class _SystemHash { } } -abstract class _$TokenContainerProvider +abstract class _$TokenContainerNotifier extends BuildlessAutoDisposeAsyncNotifier { late final ContainerCredential credential; @@ -55,27 +39,27 @@ abstract class _$TokenContainerProvider }); } -/// See also [TokenContainerProvider]. -@ProviderFor(TokenContainerProvider) -const tokenContainerProviderOf = TokenContainerProviderFamily(); +/// See also [TokenContainerNotifier]. +@ProviderFor(TokenContainerNotifier) +const tokenContainerNotifierProviderOf = TokenContainerNotifierFamily(); -/// See also [TokenContainerProvider]. -class TokenContainerProviderFamily extends Family> { - /// See also [TokenContainerProvider]. - const TokenContainerProviderFamily(); +/// See also [TokenContainerNotifier]. +class TokenContainerNotifierFamily extends Family> { + /// See also [TokenContainerNotifier]. + const TokenContainerNotifierFamily(); - /// See also [TokenContainerProvider]. - TokenContainerProviderProvider call({ + /// See also [TokenContainerNotifier]. + TokenContainerNotifierProvider call({ required ContainerCredential credential, }) { - return TokenContainerProviderProvider( + return TokenContainerNotifierProvider( credential: credential, ); } @override - TokenContainerProviderProvider getProviderOverride( - covariant TokenContainerProviderProvider provider, + TokenContainerNotifierProvider getProviderOverride( + covariant TokenContainerNotifierProvider provider, ) { return call( credential: provider.credential, @@ -94,31 +78,31 @@ class TokenContainerProviderFamily extends Family> { _allTransitiveDependencies; @override - String? get name => r'tokenContainerProviderOf'; + String? get name => r'tokenContainerNotifierProviderOf'; } -/// See also [TokenContainerProvider]. -class TokenContainerProviderProvider - extends AutoDisposeAsyncNotifierProviderImpl { - /// See also [TokenContainerProvider]. - TokenContainerProviderProvider({ + /// See also [TokenContainerNotifier]. + TokenContainerNotifierProvider({ required ContainerCredential credential, }) : this._internal( - () => TokenContainerProvider()..credential = credential, - from: tokenContainerProviderOf, - name: r'tokenContainerProviderOf', + () => TokenContainerNotifier()..credential = credential, + from: tokenContainerNotifierProviderOf, + name: r'tokenContainerNotifierProviderOf', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$tokenContainerProviderHash, - dependencies: TokenContainerProviderFamily._dependencies, + : _$tokenContainerNotifierHash, + dependencies: TokenContainerNotifierFamily._dependencies, allTransitiveDependencies: - TokenContainerProviderFamily._allTransitiveDependencies, + TokenContainerNotifierFamily._allTransitiveDependencies, credential: credential, ); - TokenContainerProviderProvider._internal( + TokenContainerNotifierProvider._internal( super._createNotifier, { required super.name, required super.dependencies, @@ -132,7 +116,7 @@ class TokenContainerProviderProvider @override FutureOr runNotifierBuild( - covariant TokenContainerProvider notifier, + covariant TokenContainerNotifier notifier, ) { return notifier.build( credential: credential, @@ -140,10 +124,10 @@ class TokenContainerProviderProvider } @override - Override overrideWith(TokenContainerProvider Function() create) { + Override overrideWith(TokenContainerNotifier Function() create) { return ProviderOverride( origin: this, - override: TokenContainerProviderProvider._internal( + override: TokenContainerNotifierProvider._internal( () => create()..credential = credential, from: from, name: null, @@ -156,14 +140,14 @@ class TokenContainerProviderProvider } @override - AutoDisposeAsyncNotifierProviderElement createElement() { - return _TokenContainerProviderProviderElement(this); + return _TokenContainerNotifierProviderElement(this); } @override bool operator ==(Object other) { - return other is TokenContainerProviderProvider && + return other is TokenContainerNotifierProvider && other.credential == credential; } @@ -176,38 +160,20 @@ class TokenContainerProviderProvider } } -mixin TokenContainerProviderRef +mixin TokenContainerNotifierRef on AutoDisposeAsyncNotifierProviderRef { /// The parameter `credential` of this provider. ContainerCredential get credential; } -class _TokenContainerProviderProviderElement - extends AutoDisposeAsyncNotifierProviderElement with TokenContainerProviderRef { - _TokenContainerProviderProviderElement(super.provider); +class _TokenContainerNotifierProviderElement + extends AutoDisposeAsyncNotifierProviderElement with TokenContainerNotifierRef { + _TokenContainerNotifierProviderElement(super.provider); @override ContainerCredential get credential => - (origin as TokenContainerProviderProvider).credential; + (origin as TokenContainerNotifierProvider).credential; } - -String _$credentialsProviderHash() => - r'1cd835b458424a6ab8e1d60850ce455de9d0efa6'; - -/// See also [CredentialsProvider]. -@ProviderFor(CredentialsProvider) -final credentialsProvider = - AsyncNotifierProvider.internal( - CredentialsProvider.new, - name: r'credentialsProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$credentialsProviderHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$CredentialsProvider = AsyncNotifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart deleted file mode 100644 index 0aec4f227..000000000 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart +++ /dev/null @@ -1,315 +0,0 @@ -import 'dart:convert'; - -import 'package:collection/collection.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:mutex/mutex.dart'; -import 'package:privacyidea_authenticator/interfaces/repo/container_repository.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -import '../../../../api/token_container_api_endpoint.dart'; -import '../../../../model/states/token_state.dart'; -import '../../../../model/token_container.dart'; -import '../../../../repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; -import '../../../../repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; -import '../../../../repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart'; -import '../../../../model/tokens/container_credentials.dart'; -import '../state_notifier_providers/token_provider.dart'; - -part 'token_container_provider.g.dart'; - -@riverpod -class TokenContainerProvider extends _$TokenContainerProvider { - late final TokenContainerRepository _repository; - final Mutex _stateMutex = Mutex(); // Mutex to protect the state from being accessed while still waiting for the newest state to be delivered - final Mutex _repoMutex = Mutex(); // Mutex to protect the repository from being accessed while still waiting for the newest state to be delivered - - @override - Future build({ - required ContainerCredential credential, - }) async { - Logger.info('New tokenContainerStateProvider created', name: 'TokenContainerProvider#build'); - await _stateMutex.acquire(); - _repository = HybridTokenContainerRepository( - localRepository: SecureTokenContainerRepository(containerId: credential.serial), - remoteRepository: RemoteTokenContainerRepository(apiEndpoint: TokenContainerApiEndpoint(credential: credential)), - ); - final initialState = await _repository.loadContainerState(); - Logger.debug('Initial state: $initialState', name: 'TokenContainerProvider#build'); - _stateMutex.release(); - return initialState; - } - - Future _saveToRepo(TokenContainer state) async { - await _repoMutex.acquire(); - final newState = await _repository.saveContainerState(state); - _repoMutex.release(); - return newState; - } - - Future _fetchFromRepo() async { - await _repoMutex.acquire(); - final newState = await _repository.loadContainerState(); - _repoMutex.release(); - return newState; - } - - Future handleTokenState(TokenState tokenState) async { - await _stateMutex.acquire(); - final localTokens = tokenState.tokens.maybePiTokens; - final oldState = state.value; - if (oldState == null) throw Exception('TokenContainer is null'); - final containerTokens = tokenState.containerTokens(oldState.serial); - final localTokenTemplates = localTokens.toTemplates(); - final containerTokenTemplates = containerTokens.toTemplates(); - final newState = oldState.copyWith(localTokenTemplates: localTokenTemplates, syncedTokenTemplates: containerTokenTemplates); - final savedState = await _saveToRepo(newState); - if (savedState is! TokenContainerSynced) { - Logger.error('Failed to save state to repo', name: 'TokenContainerProvider#handleTokenState'); - return savedState; - } - await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); - state = AsyncValue.data(savedState); - _stateMutex.release(); - return savedState; - } - - /// Adds the given [maybePiTokenTemplates] to the localTokenTemplates of the container - /// and saves the new state to the repository. The rpository decides waht to do with the new state. - /// The saved state from the repo can contain the maybePiTokenTemplates or not. - Future tryAddLocalTemplates(List maybePiTokenTemplates) async { - Logger.info( - 'Trying to add (${maybePiTokenTemplates.length}) local templates to container (${credential.serial}).', - name: 'TokenContainerProvider#tryAddLocalTemplates', - ); - Logger.debug('Local templates (${maybePiTokenTemplates.length})', name: 'TokenContainerProvider#tryAddLocalTemplates'); - await _stateMutex.acquire(); - final oldState = (await future); - final newLocalTokenTemplates = [...maybePiTokenTemplates]; - final newState = oldState.copyWith(localTokenTemplates: newLocalTokenTemplates); - final savedState = await _saveToRepo(newState); - Logger.debug( - 'Saved TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', - name: 'TokenContainerProvider#tryAddLocalTemplates', - ); - await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); - state = AsyncValue.data(savedState); - _stateMutex.release(); - return savedState; - } - - Future handleDeletedTokenTemplates(List deletedPiTokenTemplates) async { - Logger.info( - 'Removing (${deletedPiTokenTemplates.length}) deleted token templates from container (${credential.serial}).', - name: 'TokenContainerProvider#handleDeletedTokenTemplates', - ); - Logger.debug('Deleted token templates (${deletedPiTokenTemplates.length})', name: 'TokenContainerProvider#handleDeletedTokenTemplates'); - await _stateMutex.acquire(); - final oldState = (await future); - final newLocalTokenTemplates = oldState.localTokenTemplates.where((template) => !deletedPiTokenTemplates.contains(template)).toList(); - final newState = oldState.copyWith(localTokenTemplates: newLocalTokenTemplates); - final savedState = await _saveToRepo(newState); - Logger.debug( - 'Saved TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', - name: 'TokenContainerProvider#handleDeletedTokenTemplates', - ); - await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); - state = AsyncValue.data(savedState); - _stateMutex.release(); - return savedState; - } - - Future fetchTokens() async { - await _stateMutex.acquire(); - final savedState = await _fetchFromRepo(); - Logger.debug( - 'Fetched TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', - name: 'TokenContainerProvider#tryAddLocalTemplates', - ); - await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); - state = AsyncValue.data(savedState); - _stateMutex.release(); - return savedState; - } - - Future sync() async { - await _stateMutex.acquire(); - final savedState = await _fetchFromRepo(); - Logger.debug( - 'Fetched TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', - name: 'TokenContainerProvider#tryAddLocalTemplates', - ); - await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); - state = AsyncValue.data(savedState); - _stateMutex.release(); - return savedState; - } -} - -@Riverpod(keepAlive: true) -class CredentialsProvider extends _$CredentialsProvider { - final _stateMutex = Mutex(); - final _repoMutex = Mutex(); - late ContainerCredentialsRepository _repo; - - @override - Future build() async { - _repo = SecureContainerCredentialsRepository(); - Logger.warning('Building credentialsProvider', name: 'CredentialsProvider'); - return _repo.loadCredentialsState(); - } - - @override - Future update( - FutureOr Function(CredentialsState state) cb, { - FutureOr Function(Object, StackTrace)? onError, - }) async { - Logger.warning('Updating credentialsProvider', name: 'CredentialsProvider'); - return super.update(cb, onError: onError); - } - - Future addCredential(ContainerCredential credential) async { - await _stateMutex.acquire(); - final newState = await _saveCredentialsToRepo(credential); - state = AsyncValue.data(newState); - _stateMutex.release(); - return newState; - } - - Future _saveCredentialsToRepo(ContainerCredential credential) async { - return await _repoMutex.protect(() async => await _repo.saveCredential(credential)); - } -} - -class MockContainerCredentialsRepository extends ContainerCredentialsRepository { - final state = CredentialsState(credentials: [ - ContainerCredential( - id: '123', - serial: '123', - ) - ]); - - @override - Future deleteAllCredentials() => Future.value(state); - - @override - Future deleteCredential(String id) => Future.value(state); - - @override - Future loadCredential(String id) => Future.value(state.credentials.firstOrNull); - - @override - Future loadCredentialsState() => Future.value(state); - - @override - Future saveCredential(ContainerCredential credential) => Future.value(state); - - @override - Future saveCredentialsState(CredentialsState credentialsState) => Future.value(state); -} - -@JsonSerializable() -class CredentialsState { - final List credentials; - - const CredentialsState({required this.credentials}); - - factory CredentialsState.fromJson(Map json) => _$CredentialsStateFromJson(json); - - Map toJson() => _$CredentialsStateToJson(this); - - factory CredentialsState.fromJsonStringList(List jsonStrings) { - final credentials = jsonStrings.map((jsonString) => ContainerCredential.fromJson(jsonDecode(jsonString))).toList(); - return CredentialsState(credentials: credentials); - } - - @override - String toString() { - return 'CredentialsState{credentials: $credentials}'; - } - - ContainerCredential? credentialsOf(String containerSerial) => credentials.firstWhereOrNull((credential) => credential.serial == containerSerial); -} - -class SecureContainerCredentialsRepository extends ContainerCredentialsRepository { - String get containerCredentialsKey => 'containerCredentials'; - String _keyOfId(String id) => '$containerCredentialsKey.$id'; - final Mutex _m = Mutex(); - Future _protect(Future Function() f) => _m.protect(f); - final FlutterSecureStorage _storage = const FlutterSecureStorage(); - - Future _write(String key, String value) => _protect(() => _storage.write(key: key, value: value)); - Future _read(String key) async => await _protect(() async => await _storage.read(key: key)); - Future> _readAll() async => - await _protect(() async => (await _storage.readAll())..removeWhere((key, value) => !key.startsWith(containerCredentialsKey))); - Future _delete(String key) => _protect(() => _storage.delete(key: key)); - - @override - Future loadCredentialsState() async { - final credentialsJsonString = await _readAll(); - Logger.warning('Loaded credentials: $credentialsJsonString', name: 'SecureContainerCredentialsRepository'); - if (credentialsJsonString.isEmpty) { - final credentialState = CredentialsState(credentials: [ - ContainerCredential( - id: '123', - serial: '123', - ), - ]); - Logger.warning('Returning default credentials: $credentialState', name: 'SecureContainerCredentialsRepository'); - return credentialState; - } - return CredentialsState.fromJsonStringList(credentialsJsonString.values.toList()); - } - - @override - Future saveCredentialsState(CredentialsState credentialsState) async { - Logger.warning('Saving credentials: $credentialsState', name: 'SecureContainerCredentialsRepository'); - final futures = []; - for (var credential in credentialsState.credentials) { - futures.add(saveCredential(credential)); - } - await Future.wait(futures); - return await loadCredentialsState(); - } - - @override - Future deleteCredential(String id) async { - await _delete(_keyOfId(id)); - return await loadCredentialsState(); - } - - @override - Future deleteAllCredentials() async { - final credentialKeys = (await _readAll()).keys.where((key) => key.startsWith(containerCredentialsKey)); - final futures = []; - for (var key in credentialKeys) { - futures.add(_delete(key)); - } - await Future.wait(futures); - return await loadCredentialsState(); - } - - @override - Future loadCredential(String id) async { - final credentialJsonString = await _read(_keyOfId(id)); - if (credentialJsonString == null) return null; - return ContainerCredential.fromJson(jsonDecode(credentialJsonString)); - } - - @override - Future saveCredential(ContainerCredential credential) async { - final credentialJsonString = jsonEncode(credential.toJson()); - await _write(_keyOfId(credential.id), credentialJsonString); - return await loadCredentialsState(); - } -} - -abstract class ContainerCredentialsRepository { - Future saveCredential(ContainerCredential credential); - Future saveCredentialsState(CredentialsState credentialsState); - Future loadCredentialsState(); - Future loadCredential(String id); - Future deleteAllCredentials(); - Future deleteCredential(String id); -} diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart index d5d9f6390..4af86b460 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../model/states/introduction_state.dart'; +import '../../../../model/riverpod_states/introduction_state.dart'; import '../../../../repo/preference_introduction_repository.dart'; import '../../state_notifiers/completed_introduction_notifier.dart'; import '../../../logger.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart index 5764d103c..ae4f9c521 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../model/states/progress_state.dart'; +import '../../../../model/riverpod_states/progress_state.dart'; import '../../state_notifiers/progress_state_notifier.dart'; import '../../../logger.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart index 9ca13352f..d3b91a15b 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../model/states/push_request_state.dart'; +import '../../../../model/riverpod_states/push_request_state.dart'; import '../../state_notifiers/push_request_notifier.dart'; import '../../../logger.dart'; import '../../../push_provider.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart index eeca52a0b..fc6180583 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../model/states/settings_state.dart'; +import '../../../../model/riverpod_states/settings_state.dart'; import '../../../../repo/preference_settings_repository.dart'; import '../../state_notifiers/settings_notifier.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart index 69532ff04..68779b5bd 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart @@ -1,8 +1,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/mixins/sortable_mixin.dart'; -import '../../../../model/states/token_folder_state.dart'; -import '../../../../model/states/token_state.dart'; +import '../../../../model/riverpod_states/token_folder_state.dart'; +import '../../../../model/riverpod_states/token_state.dart'; import '../../state_notifiers/sortable_notifier.dart'; import '../../../logger.dart'; import 'token_folder_provider.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart index 395442949..c694071a2 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../model/states/token_folder_state.dart'; +import '../../../../model/riverpod_states/token_folder_state.dart'; import '../../../../repo/preference_token_folder_repository.dart'; import '../../state_notifiers/token_folder_notifier.dart'; import '../../../logger.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart index a71f11a08..4e81d0f37 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart @@ -1,16 +1,16 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../model/states/token_state.dart'; +import '../../../../model/riverpod_states/token_state.dart'; import '../../state_notifiers/token_notifier.dart'; import '../../../logger.dart'; -import '../generated_providers/deeplink_provider.dart'; +import '../generated_providers/deeplink_notifier.dart'; final tokenProvider = StateNotifierProvider( (ref) { Logger.info("New TokenNotifier created"); final newTokenNotifier = TokenNotifier(ref: ref); - ref.listen(deeplinkProvider, (previous, newLink) { + ref.listen(deeplinkNotifierProvider, (previous, newLink) { newLink.whenData( (data) { Logger.info("Received new deeplink with data: $data", name: 'tokenProvider#deeplinkProvider'); diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart index 4cee3e0a7..a250e43c3 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../model/states/token_filter.dart'; +import '../../../../model/riverpod_states/token_filter.dart'; final tokenFilterProvider = StateProvider((ref) => null); diff --git a/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart index f434f2722..7060d7f74 100644 --- a/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart @@ -2,7 +2,7 @@ import 'package:collection/collection.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; -import '../../../model/states/token_state.dart'; +import '../../../model/riverpod_states/token_state.dart'; import '../../../model/tokens/token.dart'; import '../../home_widget_utils.dart'; diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index 805c51107..5f916e535 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -3,8 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; -import '../../../model/states/token_state.dart'; -import '../riverpod_providers/generated_providers/token_container_provider.dart'; +import '../../../model/riverpod_states/token_state.dart'; +import '../riverpod_providers/generated_providers/credential_nofitier.dart'; +import '../riverpod_providers/generated_providers/token_container_notifier.dart'; class ContainerListensToTokenState extends TokenStateListener { ContainerListensToTokenState({ @@ -20,7 +21,7 @@ class ContainerListensToTokenState extends TokenStateListener { static Future _onNewState(TokenState? previousState, TokenState nextState, WidgetRef ref) async { Logger.warning('New token state', name: 'TokenContainerTokenStateListener'); final maybePiTokenTemplates = nextState.lastlyUpdatedTokens.maybePiTokens.toTemplates(); - final credentials = (await ref.read(credentialsProvider.future)).credentials; + final credentials = (await ref.read(credentialsNotifierProvider.future)).credentials; Logger.warning('Readed: $credentials', name: 'TokenContainerTokenStateListener'); // if (maybePiTokenTemplates.isEmpty) return; for (var credential in credentials) { @@ -31,14 +32,14 @@ class ContainerListensToTokenState extends TokenStateListener { 'Deleted (${deletedPiTokenTemplates.length}) tokens from container "${credential.serial}"', name: 'TokenContainerTokenStateListener', ); - await ref.read(tokenContainerProviderOf(credential: credential).notifier).handleDeletedTokenTemplates(deletedPiTokenTemplates); + await ref.read(tokenContainerNotifierProviderOf(credential: credential).notifier).handleDeletedTokenTemplates(deletedPiTokenTemplates); } if (maybePiTokenTemplates.isNotEmpty) { Logger.warning( 'Adding maybePiTokenTemplates (${maybePiTokenTemplates.length}) to container ${credential.serial}', name: 'TokenContainerTokenStateListener', ); - await ref.read(tokenContainerProviderOf(credential: credential).notifier).tryAddLocalTemplates(maybePiTokenTemplates); + await ref.read(tokenContainerNotifierProviderOf(credential: credential).notifier).tryAddLocalTemplates(maybePiTokenTemplates); } } } diff --git a/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart b/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart index 216a8152c..832cce0a2 100644 --- a/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../interfaces/repo/introduction_repository.dart'; import '../../../model/enums/introduction.dart'; -import '../../../model/states/introduction_state.dart'; +import '../../../model/riverpod_states/introduction_state.dart'; import '../../logger.dart'; class IntroductionNotifier extends StateNotifier { diff --git a/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart b/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart index 6f85e69a0..ae10dd510 100644 --- a/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../model/states/progress_state.dart'; +import '../../../model/riverpod_states/progress_state.dart'; import '../../logger.dart'; class ProgressStateNotifier extends StateNotifier { diff --git a/lib/utils/riverpod/state_notifiers/push_request_notifier.dart b/lib/utils/riverpod/state_notifiers/push_request_notifier.dart index 4450168a3..95a692dd8 100644 --- a/lib/utils/riverpod/state_notifiers/push_request_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/push_request_notifier.dart @@ -27,7 +27,7 @@ import 'package:mutex/mutex.dart'; import '../../../interfaces/repo/push_request_repository.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/push_request.dart'; -import '../../../model/states/push_request_state.dart'; +import '../../../model/riverpod_states/push_request_state.dart'; import '../../../model/tokens/push_token.dart'; import '../../../repo/secure_push_request_repository.dart'; import '../../custom_int_buffer.dart'; diff --git a/lib/utils/riverpod/state_notifiers/settings_notifier.dart b/lib/utils/riverpod/state_notifiers/settings_notifier.dart index 73277f120..98cc6f5f5 100644 --- a/lib/utils/riverpod/state_notifiers/settings_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/settings_notifier.dart @@ -3,7 +3,7 @@ import 'dart:ui'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../interfaces/repo/settings_repository.dart'; -import '../../../model/states/settings_state.dart'; +import '../../../model/riverpod_states/settings_state.dart'; import '../../../model/version.dart'; import '../../logger.dart'; import '../../push_provider.dart'; diff --git a/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart b/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart index 56c1cd6bc..b77ee20e2 100644 --- a/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mutex/mutex.dart'; import '../../../interfaces/repo/token_folder_repository.dart'; -import '../../../model/states/token_folder_state.dart'; +import '../../../model/riverpod_states/token_folder_state.dart'; import '../../../model/token_folder.dart'; import '../../logger.dart'; diff --git a/lib/utils/riverpod/state_notifiers/token_notifier.dart b/lib/utils/riverpod/state_notifiers/token_notifier.dart index 4ca435772..9ee86f936 100644 --- a/lib/utils/riverpod/state_notifiers/token_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_notifier.dart @@ -20,7 +20,7 @@ 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/riverpod_states/token_state.dart'; import '../../../model/token_container.dart'; import '../../../model/tokens/hotp_token.dart'; import '../../../model/tokens/otp_token.dart'; diff --git a/lib/views/main_view/main_view.dart b/lib/views/main_view/main_view.dart index 4e156104c..d1b28d64e 100644 --- a/lib/views/main_view/main_view.dart +++ b/lib/views/main_view/main_view.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../l10n/app_localizations.dart'; -import '../../model/states/token_filter.dart'; +import '../../model/riverpod_states/token_filter.dart'; import '../../utils/globals.dart'; import '../../utils/logger.dart'; import '../../utils/patch_notes_utils.dart'; diff --git a/lib/views/main_view/main_view_widgets/filter_token_widget.dart b/lib/views/main_view/main_view_widgets/filter_token_widget.dart index 5a2a06e35..ae4087ea6 100644 --- a/lib/views/main_view/main_view_widgets/filter_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/filter_token_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../model/states/token_filter.dart'; +import '../../../model/riverpod_states/token_filter.dart'; import '../../../utils/globals.dart'; import '../../../utils/riverpod/riverpod_providers/state_providers/token_filter_provider.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 c16fffaa6..0f04692cf 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 @@ -8,7 +8,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import '../../../../l10n/app_localizations.dart'; -import '../../../../model/states/token_filter.dart'; +import '../../../../model/riverpod_states/token_filter.dart'; import '../../../../model/token_folder.dart'; import '../../../../model/tokens/push_token.dart'; import '../../../../model/tokens/token.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart index d1c0d4613..71c12a434 100644 --- a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart +++ b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../model/mixins/sortable_mixin.dart'; -import '../../../model/states/token_filter.dart'; +import '../../../model/riverpod_states/token_filter.dart'; import '../../../model/token_folder.dart'; import '../../../model/tokens/token.dart'; import '../../../utils/logger.dart'; diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart index e179e9578..eab2692c8 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/model/states/token_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_state.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index 356a9cc6b..81970aaef 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -4,14 +4,15 @@ 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 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart'; import '../interfaces/riverpod/state_listeners/notifier_provider_listener.dart'; import '../model/token_container.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; -import '../utils/riverpod/riverpod_providers/generated_providers/deeplink_provider.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import '../utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart'; import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; @@ -88,7 +89,7 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { @override Widget build(BuildContext context) { - final credentials = ref.watch(credentialsProvider).value?.credentials ?? []; + final credentials = ref.watch(credentialsNotifierProvider).value?.credentials ?? []; Logger.debug('Credentials: $credentials', name: 'AppWrapper#build'); return SingleTouchRecognizer( child: StateObserver( @@ -98,14 +99,14 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { ContainerListensToTokenState(tokenProvider: tokenProvider, ref: ref), ], streamNotifierProviderListeners: [ - NavigationDeepLinkListener(deeplinkProvider: deeplinkProvider), - HomeWidgetDeepLinkListener(deeplinkProvider: deeplinkProvider), // TODO: Nochmal anschauen + NavigationDeepLinkListener(deeplinkProvider: deeplinkNotifierProvider), + HomeWidgetDeepLinkListener(deeplinkProvider: deeplinkNotifierProvider), // TODO: Nochmal anschauen ], asyncNotifierProviderListeners: [ ...credentials.map( (credential) { return TokenStateListensToContainer( - containerProvider: tokenContainerProviderOf(credential: credential), + containerProvider: tokenContainerNotifierProviderOf(credential: credential), ref: ref, ); }, @@ -138,9 +139,9 @@ class TokenStateListensToContainer extends AsyncContainerListener { } -abstract class AsyncContainerListener extends AsyncNotifierProviderListener { +abstract class AsyncContainerListener extends AsyncNotifierProviderListener { const AsyncContainerListener({ - required TokenContainerProviderProvider containerProvider, + required TokenContainerNotifierProvider containerProvider, required super.onNewState, }) : super(provider: containerProvider); } diff --git a/lib/widgets/default_refresh_indicator.dart b/lib/widgets/default_refresh_indicator.dart index 2d6b9bdc8..931bcbbe4 100644 --- a/lib/widgets/default_refresh_indicator.dart +++ b/lib/widgets/default_refresh_indicator.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_container_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import 'package:privacyidea_authenticator/widgets/deactivateable_refresh_indicator.dart'; import '../utils/logger.dart'; import '../utils/push_provider.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart'; import '../views/main_view/main_view_widgets/loading_indicator.dart'; class DefaultRefreshIndicator extends ConsumerStatefulWidget { @@ -31,11 +32,11 @@ class _DefaultRefreshIndicatorState extends ConsumerState (super.noSuchMethod( Invocation.getter(#pollingIsEnabled), returnValue: false, + returnValueForMissingStub: false, ) as bool); @override @@ -825,6 +822,10 @@ class MockPushProvider extends _i1.Mock implements _i21.PushProvider { this, Invocation.getter(#firebaseUtils), ), + returnValueForMissingStub: _FakeFirebaseUtils_6( + this, + Invocation.getter(#firebaseUtils), + ), ) as _i6.FirebaseUtils); @override @@ -834,6 +835,10 @@ class MockPushProvider extends _i1.Mock implements _i21.PushProvider { this, Invocation.getter(#ioClient), ), + returnValueForMissingStub: _FakePrivacyideaIOClient_7( + this, + Invocation.getter(#ioClient), + ), ) as _i7.PrivacyideaIOClient); @override @@ -843,6 +848,10 @@ class MockPushProvider extends _i1.Mock implements _i21.PushProvider { this, Invocation.getter(#rsaUtils), ), + returnValueForMissingStub: _FakeRsaUtils_8( + this, + Invocation.getter(#rsaUtils), + ), ) as _i8.RsaUtils); @override @@ -892,6 +901,8 @@ class MockPushProvider extends _i1.Mock implements _i21.PushProvider { ), returnValue: _i11.Future<(List<_i18.PushToken>, List<_i18.PushToken>)?>.value(), + returnValueForMissingStub: + _i11.Future<(List<_i18.PushToken>, List<_i18.PushToken>)?>.value(), ) as _i11.Future<(List<_i18.PushToken>, List<_i18.PushToken>)?>); @override @@ -920,10 +931,6 @@ class MockPushProvider extends _i1.Mock implements _i21.PushProvider { /// See the documentation for Mockito's code generation for more information. class MockPushRequestRepository extends _i1.Mock implements _i23.PushRequestRepository { - MockPushRequestRepository() { - _i1.throwOnMissingStub(this); - } - @override _i11.Future<_i9.PushRequestState> loadState() => (super.noSuchMethod( Invocation.method( @@ -938,6 +945,14 @@ class MockPushRequestRepository extends _i1.Mock [], ), )), + returnValueForMissingStub: + _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( + this, + Invocation.method( + #loadState, + [], + ), + )), ) as _i11.Future<_i9.PushRequestState>); @override @@ -981,6 +996,15 @@ class MockPushRequestRepository extends _i1.Mock {#state: state}, ), )), + returnValueForMissingStub: + _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( + this, + Invocation.method( + #add, + [pushRequest], + {#state: state}, + ), + )), ) as _i11.Future<_i9.PushRequestState>); @override @@ -1003,5 +1027,14 @@ class MockPushRequestRepository extends _i1.Mock {#state: state}, ), )), + returnValueForMissingStub: + _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( + this, + Invocation.method( + #remove, + [pushRequest], + {#state: state}, + ), + )), ) as _i11.Future<_i9.PushRequestState>); } diff --git a/test/unit_test/model/states/introduction_state_test.dart b/test/unit_test/model/states/introduction_state_test.dart index d18bcbf60..83a99eb91 100644 --- a/test/unit_test/model/states/introduction_state_test.dart +++ b/test/unit_test/model/states/introduction_state_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; -import 'package:privacyidea_authenticator/model/states/introduction_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; void main() { group('IntroductionState', () { diff --git a/test/unit_test/model/states/settings_state_test.dart b/test/unit_test/model/states/settings_state_test.dart index 377690f78..fb07c1059 100644 --- a/test/unit_test/model/states/settings_state_test.dart +++ b/test/unit_test/model/states/settings_state_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:privacyidea_authenticator/model/states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; void main() { _testSettingsState(); diff --git a/test/unit_test/model/states/token_folder_state_test.dart b/test/unit_test/model/states/token_folder_state_test.dart index c137bb027..eaad7f452 100644 --- a/test/unit_test/model/states/token_folder_state_test.dart +++ b/test/unit_test/model/states/token_folder_state_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:privacyidea_authenticator/model/states/token_folder_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart'; import 'package:privacyidea_authenticator/model/token_folder.dart'; void main() { diff --git a/test/unit_test/model/states/token_state_test.dart b/test/unit_test/model/states/token_state_test.dart index 53b121b4b..4ae58d10c 100644 --- a/test/unit_test/model/states/token_state_test.dart +++ b/test/unit_test/model/states/token_state_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:privacyidea_authenticator/model/states/token_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_state.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:mockito/mockito.dart'; diff --git a/test/unit_test/state_notifiers/push_request_notifier_test.dart b/test/unit_test/state_notifiers/push_request_notifier_test.dart index d7c873772..9f35a6a2c 100644 --- a/test/unit_test/state_notifiers/push_request_notifier_test.dart +++ b/test/unit_test/state_notifiers/push_request_notifier_test.dart @@ -3,7 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart'; import 'package:mockito/mockito.dart'; import 'package:privacyidea_authenticator/model/push_request.dart'; -import 'package:privacyidea_authenticator/model/states/push_request_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/push_request_state.dart'; import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/push_request_notifier.dart'; import 'package:privacyidea_authenticator/utils/custom_int_buffer.dart'; diff --git a/test/unit_test/state_notifiers/settings_notifier_test.dart b/test/unit_test/state_notifiers/settings_notifier_test.dart index a87ebc3bc..e60f87055 100644 --- a/test/unit_test/state_notifiers/settings_notifier_test.dart +++ b/test/unit_test/state_notifiers/settings_notifier_test.dart @@ -3,7 +3,7 @@ import 'dart:ui'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:privacyidea_authenticator/model/states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import '../../tests_app_wrapper.mocks.dart'; diff --git a/test/unit_test/state_notifiers/sortable_notifier_test.dart b/test/unit_test/state_notifiers/sortable_notifier_test.dart index d77bb10bc..75e29ad3f 100644 --- a/test/unit_test/state_notifiers/sortable_notifier_test.dart +++ b/test/unit_test/state_notifiers/sortable_notifier_test.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; -import 'package:privacyidea_authenticator/model/states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/model/token_folder.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; diff --git a/test/unit_test/state_notifiers/token_folder_notifier_test.dart b/test/unit_test/state_notifiers/token_folder_notifier_test.dart index 770f34a37..df732e3d3 100644 --- a/test/unit_test/state_notifiers/token_folder_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_folder_notifier_test.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:privacyidea_authenticator/model/states/token_folder_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart'; import 'package:privacyidea_authenticator/model/token_folder.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; diff --git a/test/unit_test/state_notifiers/token_notifier_test.dart b/test/unit_test/state_notifiers/token_notifier_test.dart index e343e33f7..3621aad4b 100644 --- a/test/unit_test/state_notifiers/token_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_notifier_test.dart @@ -7,8 +7,8 @@ import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; import 'package:privacyidea_authenticator/model/enums/push_token_rollout_state.dart'; import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; -import 'package:privacyidea_authenticator/model/states/settings_state.dart'; -import 'package:privacyidea_authenticator/model/states/token_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; From de9753a8ab7a04676f68c0be0a6b567c36b716f9 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:18:12 +0200 Subject: [PATCH 018/285] added licenses --- lib/api/token_container_api_endpoint.dart | 29 +- .../default_firebase_options.dart | 19 + lib/interfaces/api_endpoint.dart | 19 + .../container_credentials_repository.dart | 19 + lib/interfaces/repo/container_repository.dart | 19 + .../repo/introduction_repository.dart | 19 + .../repo/push_request_repository.dart | 19 + lib/interfaces/repo/settings_repository.dart | 19 + .../repo/token_folder_repository.dart | 19 + lib/interfaces/repo/token_repository.dart | 19 + .../notifier_provider_listener.dart | 19 + .../state_notifier_provider_listener.dart | 23 +- .../deep_link_listener.dart | 21 +- .../token_state_listener.dart | 19 + lib/mains/main_customizer.dart | 37 +- lib/mains/main_netknights.dart | 2 +- lib/model/deeplink.dart | 20 + lib/model/encryption/uint_8_buffer.dart | 19 + lib/model/enums/algorithms.dart | 19 + lib/model/enums/app_feature.dart | 19 + .../enums/day_password_token_view_mode.dart | 19 + lib/model/enums/encodings.dart | 19 + lib/model/enums/introduction.dart | 19 + lib/model/enums/patch_note_type.dart | 19 + lib/model/enums/push_token_rollout_state.dart | 19 + lib/model/enums/token_import_type.dart | 19 + lib/model/enums/token_origin_source_type.dart | 19 + lib/model/enums/token_types.dart | 19 + lib/model/extensions/color_extension.dart | 19 + lib/model/extensions/enum_extension.dart | 19 + .../enums/algorithms_extension.dart | 19 + .../enums/app_feature_extension.dart | 19 + .../extensions/enums/encodings_extension.dart | 19 + .../enums/introduction_extension.dart | 19 + .../enums/patch_note_type_extension.dart | 19 + .../push_token_rollout_state_extension.dart | 19 + .../enums/token_import_type_extension.dart | 19 + .../enums/token_origin_source_type.dart | 19 + lib/model/extensions/int_extension.dart | 19 + lib/model/extensions/sortable_list.dart | 19 + lib/model/mixins/sortable_mixin.dart | 19 + lib/model/processor_result.dart | 19 + lib/model/push_request.dart | 19 + .../riverpod_states/credentials_state.dart | 19 + .../riverpod_states/introduction_state.dart | 19 + lib/model/riverpod_states/progress_state.dart | 19 + .../riverpod_states/push_request_state.dart | 19 + lib/model/riverpod_states/settings_state.dart | 19 + .../token_container_state.dart | 19 + lib/model/riverpod_states/token_filter.dart | 19 + .../riverpod_states/token_folder_state.dart | 19 + lib/model/riverpod_states/token_state.dart | 22 +- lib/model/token_container.dart | 22 +- lib/model/token_folder.dart | 19 + .../token_import/token_import_entry.dart | 19 + .../token_import/token_import_origin.dart | 19 + .../token_import/token_import_source.dart | 19 + lib/model/token_import/token_origin_data.dart | 19 + lib/model/tokens/container_credentials.dart | 21 +- lib/model/tokens/day_password_token.dart | 21 +- lib/model/tokens/hotp_token.dart | 21 +- lib/model/tokens/otp_token.dart | 26 +- lib/model/tokens/push_token.dart | 23 +- lib/model/tokens/steam_token.dart | 19 + lib/model/tokens/token.dart | 21 +- lib/model/tokens/totp_token.dart | 21 +- lib/model/version.dart | 19 + .../mixins/token_import_processor.dart | 19 + .../container_credentials_processor.dart | 25 +- .../home_widget_processor.dart | 19 + .../home_widget_navigate_processor.dart | 19 + ...navigation_scheme_processor_interface.dart | 19 + .../scheme_processor_interface.dart | 19 + .../free_otp_plus_qr_processor.dart | 19 + .../google_authenticator_qr_processor.dart | 19 + .../otp_auth_processor.dart | 19 + ...rivacyidea_authenticator_qr_processor.dart | 19 + ...ken_import_scheme_processor_interface.dart | 19 + .../aegis_import_file_processor.dart | 19 + ...thenticator_pro_import_file_processor.dart | 19 + .../free_otp_plus_import_file_processor.dart | 19 + ...google_authenticator_qrfile_processor.dart | 19 + ...a_authenticator_import_file_processor.dart | 19 + ...token_import_file_processor_interface.dart | 19 + .../two_fas_import_file_processor.dart | 19 + .../preference_introduction_repository.dart | 19 + lib/repo/preference_settings_repository.dart | 19 + .../preference_token_folder_repository.dart | 19 + ...cure_container_credentials_repository.dart | 2 +- lib/repo/secure_push_request_repository.dart | 19 + lib/repo/secure_token_repository.dart | 2 +- ...brid_token_container_state_repository.dart | 24 +- ...mote_token_container_state_repository.dart | 23 +- ...token_container_state_repository.dart.dart | 21 +- lib/utils/app_info_utils.dart | 19 + lib/utils/crypto_utils.dart | 2 +- lib/utils/custom_int_buffer.dart | 19 + lib/utils/customization/action_theme.dart | 19 + .../application_customization.dart | 19 + .../customization/extended_text_theme.dart | 19 + .../customization/theme_customization.dart | 19 + lib/utils/encryption/aes_encrypted.dart | 19 + lib/utils/encryption/token_encryption.dart | 19 + lib/utils/errors.dart | 19 + lib/utils/firebase_utils.dart | 19 + lib/utils/globals.dart | 2 +- lib/utils/home_widget_utils.dart | 19 + lib/utils/identifiers.dart | 2 +- lib/utils/image_converter.dart | 776 +++++++++--------- lib/utils/lock_auth.dart | 19 + lib/utils/logger.dart | 20 +- lib/utils/patch_notes_utils.dart | 19 + lib/utils/pi_mailer.dart | 19 + lib/utils/pi_notifications.dart | 19 + lib/utils/privacyidea_io_client.dart | 2 +- lib/utils/push_provider.dart | 3 +- .../credential_nofitier.dart | 19 + .../deeplink_notifier.dart | 19 + .../token_container_notifier.dart | 23 +- .../introduction_provider.dart | 19 + .../progress_state_provider.dart | 19 + .../push_request_provider.dart | 19 + .../settings_provider.dart | 19 + .../sortable_provider.dart | 19 + .../token_folder_provider.dart | 19 + .../token_provider.dart | 20 +- .../app_constraints_provider.dart | 19 + .../application_customizer_provider.dart | 19 + .../dragging_sortable_provider.dart | 19 + .../state_providers/home_widget_provider.dart | 19 + .../status_message_provider.dart | 19 + .../token_filter_provider.dart | 19 + .../connectivity_provider.dart | 19 + .../home_widget_deep_link_listener.dart | 19 + .../home_widget_token_state_listener.dart | 21 +- .../navigation_deep_link_listener.dart | 21 +- .../token_container_token_state_listener.dart | 22 +- .../completed_introduction_notifier.dart | 19 + .../state_notifiers/deeplink_notifier.dart | 19 + .../progress_state_notifier.dart | 19 + .../push_request_notifier.dart | 2 +- .../state_notifiers/settings_notifier.dart | 19 + .../state_notifiers/sortable_notifier.dart | 21 +- .../token_container_notifier.dart | 19 + .../token_folder_notifier.dart | 19 + .../state_notifiers/token_notifier.dart | 19 + lib/utils/rsa_utils.dart | 2 +- lib/utils/token_import_origins.dart | 19 + lib/utils/utils.dart | 2 +- lib/utils/validators.dart | 19 + lib/utils/view_utils.dart | 19 + .../add_token_manually_view.dart | 19 + .../labeled_dropdown_button.dart | 19 + lib/views/feedback_view/feedback_view.dart | 19 + .../import_tokens_view.dart | 19 + .../pages/import_encrypted_data_page.dart | 19 + .../pages/import_plain_tokens_page.dart | 19 + .../pages/import_start_page.dart | 19 + .../pages/select_import_type_page.dart | 19 + .../conflicted_import_tokens_list.dart | 19 + .../conflicted_import_tokens_tile.dart | 19 + .../widgets/dialogs/qr_not_found_dialog.dart | 19 + .../widgets/failed_imports_list.dart | 19 + .../no_conflict_import_tokens_list.dart | 19 + .../no_conflict_import_tokens_tile.dart | 19 + lib/views/license_view/license_view.dart | 19 + .../link_home_widget_view.dart | 19 + lib/views/main_view/main_view.dart | 19 + .../main_view_widgets/app_bar_item.dart | 19 + .../connectivity_listener.dart | 19 + .../custom_paint_navigation_bar.dart | 19 + .../drag_target_divider.dart | 19 + .../main_view_widgets/expandable_appbar.dart | 19 + .../filter_token_widget.dart | 19 + .../add_token_folder_dialog.dart | 19 + .../delete_token_folder_action.dart | 19 + .../lock_token_folder_action.dart | 19 + .../rename_token_folder_action.dart | 19 + .../token_folder_expandable.dart | 19 + .../folder_widgets/token_folder_widget.dart | 19 + .../main_view_widgets/loading_indicator.dart | 19 + .../main_view_navigation_bar.dart | 19 + .../license_push_view_button.dart | 19 + .../qr_scanner_button.dart | 19 + .../main_view_tokens_list.dart | 25 +- .../main_view_tokens_list_filtered.dart | 19 + .../main_view_widgets/no_token_screen.dart | 19 + .../sortable_widget_builder.dart | 19 + .../edit_day_password_token_action.dart | 19 + .../day_password_token_widget.dart | 19 + .../day_password_token_widget_tile.dart | 19 + .../default_delete_action.dart | 19 + .../default_edit_action.dart | 19 + .../default_edit_action_dialog.dart | 19 + .../default_lock_action.dart | 19 + .../actions/edit_hotp_token_action.dart | 19 + .../hotp_token_widgets/hotp_token_widget.dart | 19 + .../hotp_token_widget_tile.dart | 19 + .../actions/edit_push_token_action.dart | 19 + .../push_token_widgets/push_token_widget.dart | 19 + .../push_token_widget_tile.dart | 19 + .../rollout_failed_widget.dart | 19 + .../push_token_widgets/rollout_widget.dart | 19 + .../token_widgets/token_action.dart | 19 + .../token_widgets/token_widget.dart | 19 + .../token_widgets/token_widget_base.dart | 19 + .../token_widgets/token_widget_builder.dart | 19 + .../token_widgets/token_widget_slideable.dart | 19 + .../token_widgets/token_widget_tile.dart | 19 + .../actions/edit_totp_token_action.dart | 19 + .../totp_token_widgets/totp_token_widget.dart | 19 + .../totp_token_widget_tile.dart | 19 + .../totp_token_widget_tile_countdown.dart | 19 + .../push_token_view/push_tokens_view.dart | 19 + .../widgets/push_tokens_view_list.dart | 19 + .../qr_scanner_view/qr_scanner_view.dart | 2 +- .../qr_scanner_widget.dart | 19 + .../dialogs/export_tokens_to_file_dialog.dart | 19 + .../dialogs/select_export_type_dialog.dart | 19 + .../dialogs/select_tokens_dialog.dart | 21 +- .../dialogs/show_qr_code_dialog.dart | 19 + .../settings_group_error_log.dart | 19 + .../settings_group_general.dart | 19 + .../settings_group_import_export_tokens.dart | 19 + .../settings_group_language.dart | 19 + .../settings_group_push_token.dart | 19 + .../settings_groups/settings_group_theme.dart | 19 + lib/views/settings_view/settings_view.dart | 19 + .../dialogs/ask_log_sended_dialog.dart | 19 + .../delete_errorlog_button.dart | 19 + .../errorlog_buttons/errorlog_button.dart | 19 + .../send_errorlog_button.dart | 19 + .../show_errorlog_button.dart | 19 + .../settings_view_widgets/logging_menu.dart | 19 + .../send_error_dialog.dart | 19 + .../settings_groups.dart | 2 +- .../settings_list_tile_button.dart | 19 + .../update_firebase_token_dialog.dart | 2 +- lib/views/splash_screen/splash_screen.dart | 19 + lib/views/view_interface.dart | 19 + lib/widgets/app_wrappers/state_observer.dart | 22 +- lib/widgets/default_refresh_indicator.dart | 4 +- 242 files changed, 4733 insertions(+), 475 deletions(-) diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index 3bab2c89f..9b66d516b 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -1,8 +1,27 @@ -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; -import 'package:privacyidea_authenticator/model/token_container.dart'; -import 'package:privacyidea_authenticator/model/tokens/container_credentials.dart'; -import 'package:privacyidea_authenticator/utils/identifiers.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../model/extensions/enums/encodings_extension.dart'; +import '../model/token_container.dart'; +import '../model/tokens/container_credentials.dart'; +import '../utils/identifiers.dart'; +import '../utils/logger.dart'; import '../interfaces/api_endpoint.dart'; import '../model/enums/encodings.dart'; diff --git a/lib/firebase_options/default_firebase_options.dart b/lib/firebase_options/default_firebase_options.dart index 850fb174b..1db3e3096 100644 --- a/lib/firebase_options/default_firebase_options.dart +++ b/lib/firebase_options/default_firebase_options.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // File generated by FlutterFire CLI. // ignore_for_file: type=lint import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; diff --git a/lib/interfaces/api_endpoint.dart b/lib/interfaces/api_endpoint.dart index a58edf317..c7f7231f6 100644 --- a/lib/interfaces/api_endpoint.dart +++ b/lib/interfaces/api_endpoint.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../model/tokens/container_credentials.dart'; abstract class ApiEndpioint { diff --git a/lib/interfaces/repo/container_credentials_repository.dart b/lib/interfaces/repo/container_credentials_repository.dart index 1dfff679d..33d833a82 100644 --- a/lib/interfaces/repo/container_credentials_repository.dart +++ b/lib/interfaces/repo/container_credentials_repository.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../model/riverpod_states/credentials_state.dart'; import '../../model/tokens/container_credentials.dart'; diff --git a/lib/interfaces/repo/container_repository.dart b/lib/interfaces/repo/container_repository.dart index f64bcba2d..c6f484cb0 100644 --- a/lib/interfaces/repo/container_repository.dart +++ b/lib/interfaces/repo/container_repository.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../model/token_container.dart'; diff --git a/lib/interfaces/repo/introduction_repository.dart b/lib/interfaces/repo/introduction_repository.dart index 03e1c3f0e..ae27bb4c2 100644 --- a/lib/interfaces/repo/introduction_repository.dart +++ b/lib/interfaces/repo/introduction_repository.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../model/riverpod_states/introduction_state.dart'; abstract class IntroductionRepository { diff --git a/lib/interfaces/repo/push_request_repository.dart b/lib/interfaces/repo/push_request_repository.dart index 88bcc3e07..bd0274f35 100644 --- a/lib/interfaces/repo/push_request_repository.dart +++ b/lib/interfaces/repo/push_request_repository.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../model/push_request.dart' show PushRequest; import '../../model/riverpod_states/push_request_state.dart' show PushRequestState; diff --git a/lib/interfaces/repo/settings_repository.dart b/lib/interfaces/repo/settings_repository.dart index 7509c43dd..87df825a8 100644 --- a/lib/interfaces/repo/settings_repository.dart +++ b/lib/interfaces/repo/settings_repository.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../model/riverpod_states/settings_state.dart'; abstract class SettingsRepository { diff --git a/lib/interfaces/repo/token_folder_repository.dart b/lib/interfaces/repo/token_folder_repository.dart index 053acc9f6..7e6eaf62d 100644 --- a/lib/interfaces/repo/token_folder_repository.dart +++ b/lib/interfaces/repo/token_folder_repository.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../model/token_folder.dart'; abstract class TokenFolderRepository { diff --git a/lib/interfaces/repo/token_repository.dart b/lib/interfaces/repo/token_repository.dart index cda5e9752..b0876afea 100644 --- a/lib/interfaces/repo/token_repository.dart +++ b/lib/interfaces/repo/token_repository.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../model/tokens/token.dart'; abstract class TokenRepository { diff --git a/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart b/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart index 62c934d67..17d83eef5 100644 --- a/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // ignore_for_file: invalid_use_of_internal_member import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart index ba949d82f..b81bbedba 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart @@ -1,6 +1,25 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; +import '../../../utils/logger.dart'; abstract class StateNotifierProviderListener, S> { final String listenerName; @@ -14,5 +33,3 @@ abstract class StateNotifierProviderListener, S> { }); } } - - diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart index 7be00dae2..e8b4ce4e8 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -13,8 +32,6 @@ abstract class DeepLinkListener extends StreamNotifierProviderListener, S> { final String listenerName; final StreamNotifierProvider provider; diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart index a512996b0..90d09040b 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../../model/riverpod_states/token_state.dart'; diff --git a/lib/mains/main_customizer.dart b/lib/mains/main_customizer.dart index 0e409afc8..2b197576b 100644 --- a/lib/mains/main_customizer.dart +++ b/lib/mains/main_customizer.dart @@ -1,23 +1,22 @@ /* - 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. -*/ + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:easy_dynamic_theme/easy_dynamic_theme.dart'; import 'package:flutter/gestures.dart'; diff --git a/lib/mains/main_netknights.dart b/lib/mains/main_netknights.dart index 3ef10c8d7..fe3677197 100644 --- a/lib/mains/main_netknights.dart +++ b/lib/mains/main_netknights.dart @@ -4,7 +4,7 @@ Authors: Timo Sturm Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH + Copyright (c) 2017-2024 NetKnights GmbH Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. diff --git a/lib/model/deeplink.dart b/lib/model/deeplink.dart index c28203370..c47cbb7bf 100644 --- a/lib/model/deeplink.dart +++ b/lib/model/deeplink.dart @@ -1,3 +1,23 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ + class DeepLink { final Uri uri; final bool fromInit; diff --git a/lib/model/encryption/uint_8_buffer.dart b/lib/model/encryption/uint_8_buffer.dart index 804717f31..c033d01e4 100644 --- a/lib/model/encryption/uint_8_buffer.dart +++ b/lib/model/encryption/uint_8_buffer.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:typed_data'; class Uint8Buffer { diff --git a/lib/model/enums/algorithms.dart b/lib/model/enums/algorithms.dart index 1ea218a7b..75600d217 100644 --- a/lib/model/enums/algorithms.dart +++ b/lib/model/enums/algorithms.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // ignore_for_file: constant_identifier_names // Do not rename, remove or reorder values, they are used for serialization. Only add new values at the end. enum Algorithms { diff --git a/lib/model/enums/app_feature.dart b/lib/model/enums/app_feature.dart index bbce5f1ef..a773fe6f6 100644 --- a/lib/model/enums/app_feature.dart +++ b/lib/model/enums/app_feature.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // Do not rename or remove values, they are used for serialization. Only add new values. enum AppFeature { patchNotes, diff --git a/lib/model/enums/day_password_token_view_mode.dart b/lib/model/enums/day_password_token_view_mode.dart index e06569c57..0ea76ff3d 100644 --- a/lib/model/enums/day_password_token_view_mode.dart +++ b/lib/model/enums/day_password_token_view_mode.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // ignore_for_file: constant_identifier_names // Do not rename or remove values, they are used for serialization. Only add new values. enum DayPasswordTokenViewMode { diff --git a/lib/model/enums/encodings.dart b/lib/model/enums/encodings.dart index 7b8333cd8..1c6465203 100644 --- a/lib/model/enums/encodings.dart +++ b/lib/model/enums/encodings.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ enum Encodings { none, base32, diff --git a/lib/model/enums/introduction.dart b/lib/model/enums/introduction.dart index b1ff1af1a..8c2cb1023 100644 --- a/lib/model/enums/introduction.dart +++ b/lib/model/enums/introduction.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // Do not rename or remove values, they are used for serialization. Only add new values. enum Introduction { introductionScreen, // 1st start diff --git a/lib/model/enums/patch_note_type.dart b/lib/model/enums/patch_note_type.dart index 7e917bb19..e615e3dba 100644 --- a/lib/model/enums/patch_note_type.dart +++ b/lib/model/enums/patch_note_type.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ enum PatchNoteType { newFeature, improvement, diff --git a/lib/model/enums/push_token_rollout_state.dart b/lib/model/enums/push_token_rollout_state.dart index e4e34399a..d38440ee1 100644 --- a/lib/model/enums/push_token_rollout_state.dart +++ b/lib/model/enums/push_token_rollout_state.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // Do not rename or remove values, they are used for serialization. Only add new values. enum PushTokenRollOutState { rolloutNotStarted, diff --git a/lib/model/enums/token_import_type.dart b/lib/model/enums/token_import_type.dart index 0f92f2e59..c030b7b4b 100644 --- a/lib/model/enums/token_import_type.dart +++ b/lib/model/enums/token_import_type.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // Do not rename or remove values, they are used for serialization. Only add new values. enum TokenImportType { backupFile, diff --git a/lib/model/enums/token_origin_source_type.dart b/lib/model/enums/token_origin_source_type.dart index ebe415ee5..1b8e7223a 100644 --- a/lib/model/enums/token_origin_source_type.dart +++ b/lib/model/enums/token_origin_source_type.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // Do not rename or remove values, they are used for serialization. Only add new values at the end of the list. enum TokenOriginSourceType { backupFile, diff --git a/lib/model/enums/token_types.dart b/lib/model/enums/token_types.dart index a4756ce86..fffd02762 100644 --- a/lib/model/enums/token_types.dart +++ b/lib/model/enums/token_types.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // ignore_for_file: constant_identifier_names // Do not rename or remove values, they are used for serialization. Only add new values. enum TokenTypes { diff --git a/lib/model/extensions/color_extension.dart b/lib/model/extensions/color_extension.dart index 0c3d53011..8c4e0afd4 100644 --- a/lib/model/extensions/color_extension.dart +++ b/lib/model/extensions/color_extension.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:ui'; extension ColorExtension on Color { diff --git a/lib/model/extensions/enum_extension.dart b/lib/model/extensions/enum_extension.dart index 301f32513..76d11b37b 100644 --- a/lib/model/extensions/enum_extension.dart +++ b/lib/model/extensions/enum_extension.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ extension EnumExtension on Enum { bool isName(String enumName, {bool caseSensitive = true}) => caseSensitive ? name == enumName : name.toLowerCase() == enumName.toLowerCase(); } diff --git a/lib/model/extensions/enums/algorithms_extension.dart b/lib/model/extensions/enums/algorithms_extension.dart index 5402adc3e..103ee778a 100644 --- a/lib/model/extensions/enums/algorithms_extension.dart +++ b/lib/model/extensions/enums/algorithms_extension.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:otp/otp.dart'; import '../../enums/algorithms.dart'; diff --git a/lib/model/extensions/enums/app_feature_extension.dart b/lib/model/extensions/enums/app_feature_extension.dart index 7d3fee097..c3c411a8e 100644 --- a/lib/model/extensions/enums/app_feature_extension.dart +++ b/lib/model/extensions/enums/app_feature_extension.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../enums/app_feature.dart'; extension AppFeatureX on AppFeature { diff --git a/lib/model/extensions/enums/encodings_extension.dart b/lib/model/extensions/enums/encodings_extension.dart index e176c9ccf..eeaa2fb10 100644 --- a/lib/model/extensions/enums/encodings_extension.dart +++ b/lib/model/extensions/enums/encodings_extension.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; import 'package:base32/base32.dart'; diff --git a/lib/model/extensions/enums/introduction_extension.dart b/lib/model/extensions/enums/introduction_extension.dart index 5aa1c038e..714b21f82 100644 --- a/lib/model/extensions/enums/introduction_extension.dart +++ b/lib/model/extensions/enums/introduction_extension.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; diff --git a/lib/model/extensions/enums/patch_note_type_extension.dart b/lib/model/extensions/enums/patch_note_type_extension.dart index adad05302..2c1341e42 100644 --- a/lib/model/extensions/enums/patch_note_type_extension.dart +++ b/lib/model/extensions/enums/patch_note_type_extension.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../../l10n/app_localizations.dart'; import '../../enums/patch_note_type.dart'; diff --git a/lib/model/extensions/enums/push_token_rollout_state_extension.dart b/lib/model/extensions/enums/push_token_rollout_state_extension.dart index 925ecbd64..7e8d57351 100644 --- a/lib/model/extensions/enums/push_token_rollout_state_extension.dart +++ b/lib/model/extensions/enums/push_token_rollout_state_extension.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../../l10n/app_localizations.dart'; import '../../enums/push_token_rollout_state.dart'; diff --git a/lib/model/extensions/enums/token_import_type_extension.dart b/lib/model/extensions/enums/token_import_type_extension.dart index b1dd8ce19..a07e59e79 100644 --- a/lib/model/extensions/enums/token_import_type_extension.dart +++ b/lib/model/extensions/enums/token_import_type_extension.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../l10n/app_localizations.dart'; diff --git a/lib/model/extensions/enums/token_origin_source_type.dart b/lib/model/extensions/enums/token_origin_source_type.dart index bd4521d5e..fc8117d98 100644 --- a/lib/model/extensions/enums/token_origin_source_type.dart +++ b/lib/model/extensions/enums/token_origin_source_type.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../../utils/utils.dart'; import '../../enums/token_origin_source_type.dart'; import '../../token_import/token_origin_data.dart'; diff --git a/lib/model/extensions/int_extension.dart b/lib/model/extensions/int_extension.dart index 334753970..e30e4b239 100644 --- a/lib/model/extensions/int_extension.dart +++ b/lib/model/extensions/int_extension.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:math' as math; import 'dart:typed_data'; diff --git a/lib/model/extensions/sortable_list.dart b/lib/model/extensions/sortable_list.dart index eee5374b5..3a0c4a73b 100644 --- a/lib/model/extensions/sortable_list.dart +++ b/lib/model/extensions/sortable_list.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../mixins/sortable_mixin.dart'; extension SortableList on List { diff --git a/lib/model/mixins/sortable_mixin.dart b/lib/model/mixins/sortable_mixin.dart index 5274c67f0..a9d0b6e35 100644 --- a/lib/model/mixins/sortable_mixin.dart +++ b/lib/model/mixins/sortable_mixin.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ mixin SortableMixin { int? get sortIndex; SortableMixin copyWith({int? sortIndex}); diff --git a/lib/model/processor_result.dart b/lib/model/processor_result.dart index b2387c3ab..6f78b073a 100644 --- a/lib/model/processor_result.dart +++ b/lib/model/processor_result.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ abstract class ProcessorResult { const ProcessorResult(); factory ProcessorResult.success(T data) => ProcessorResultSuccess(data); diff --git a/lib/model/push_request.dart b/lib/model/push_request.dart index f6c1f0bf7..89ccccc38 100644 --- a/lib/model/push_request.dart +++ b/lib/model/push_request.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 NetKnights GmbH + * + * Licensed under the Apache License, emailemailemailVersion 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:convert'; import 'package:base32/base32.dart'; diff --git a/lib/model/riverpod_states/credentials_state.dart b/lib/model/riverpod_states/credentials_state.dart index d38b2cc48..eda0784f7 100644 --- a/lib/model/riverpod_states/credentials_state.dart +++ b/lib/model/riverpod_states/credentials_state.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; import 'package:collection/collection.dart'; diff --git a/lib/model/riverpod_states/introduction_state.dart b/lib/model/riverpod_states/introduction_state.dart index 2f3ae9d2c..8e13a872c 100644 --- a/lib/model/riverpod_states/introduction_state.dart +++ b/lib/model/riverpod_states/introduction_state.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import 'package:json_annotation/json_annotation.dart'; diff --git a/lib/model/riverpod_states/progress_state.dart b/lib/model/riverpod_states/progress_state.dart index 48a6c03c4..ede019be8 100644 --- a/lib/model/riverpod_states/progress_state.dart +++ b/lib/model/riverpod_states/progress_state.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ class ProgressState { final int max; final int value; diff --git a/lib/model/riverpod_states/push_request_state.dart b/lib/model/riverpod_states/push_request_state.dart index 910aa07c8..1d05ad51e 100644 --- a/lib/model/riverpod_states/push_request_state.dart +++ b/lib/model/riverpod_states/push_request_state.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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'; import 'package:json_annotation/json_annotation.dart'; diff --git a/lib/model/riverpod_states/settings_state.dart b/lib/model/riverpod_states/settings_state.dart index c954029bb..0a4022ff3 100644 --- a/lib/model/riverpod_states/settings_state.dart +++ b/lib/model/riverpod_states/settings_state.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:io'; import 'dart:ui'; diff --git a/lib/model/riverpod_states/token_container_state.dart b/lib/model/riverpod_states/token_container_state.dart index 01bb96169..399b790c4 100644 --- a/lib/model/riverpod_states/token_container_state.dart +++ b/lib/model/riverpod_states/token_container_state.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:json_annotation/json_annotation.dart'; // import '../token_container.dart'; diff --git a/lib/model/riverpod_states/token_filter.dart b/lib/model/riverpod_states/token_filter.dart index 80d7c2a06..8d2b923cd 100644 --- a/lib/model/riverpod_states/token_filter.dart +++ b/lib/model/riverpod_states/token_filter.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../tokens/push_token.dart'; import '../tokens/token.dart'; diff --git a/lib/model/riverpod_states/token_folder_state.dart b/lib/model/riverpod_states/token_folder_state.dart index f87626322..bcf3cd492 100644 --- a/lib/model/riverpod_states/token_folder_state.dart +++ b/lib/model/riverpod_states/token_folder_state.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:math'; import 'package:collection/collection.dart'; diff --git a/lib/model/riverpod_states/token_state.dart b/lib/model/riverpod_states/token_state.dart index 04197d272..c5f656cfc 100644 --- a/lib/model/riverpod_states/token_state.dart +++ b/lib/model/riverpod_states/token_state.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -32,8 +51,7 @@ class TokenState { required List tokens, List? lastlyUpdatedTokens, List? lastlyDeletedTokens, - }) - : tokens = List.from(tokens), + }) : tokens = List.from(tokens), lastlyUpdatedTokens = List.from(lastlyUpdatedTokens ?? tokens), lastlyDeletedTokens = List.from(lastlyDeletedTokens ?? []); diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index 175b15ae1..c264ba4fd 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -1,7 +1,27 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ + // We need some unnecessary_overrides to force to add the fields in factory constructors // ignore_for_file: unnecessary_overrides -import 'package:collection/equality.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; diff --git a/lib/model/token_folder.dart b/lib/model/token_folder.dart index 12c48674f..fe7840d7d 100644 --- a/lib/model/token_folder.dart +++ b/lib/model/token_folder.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:json_annotation/json_annotation.dart'; diff --git a/lib/model/token_import/token_import_entry.dart b/lib/model/token_import/token_import_entry.dart index 9863db91a..686e0b874 100644 --- a/lib/model/token_import/token_import_entry.dart +++ b/lib/model/token_import/token_import_entry.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../tokens/token.dart'; class TokenImportEntry { diff --git a/lib/model/token_import/token_import_origin.dart b/lib/model/token_import/token_import_origin.dart index 78f931819..191dda601 100644 --- a/lib/model/token_import/token_import_origin.dart +++ b/lib/model/token_import/token_import_origin.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 'token_import_source.dart'; class TokenImportOrigin { diff --git a/lib/model/token_import/token_import_source.dart b/lib/model/token_import/token_import_source.dart index 9e15f1b71..1f7eb5afb 100644 --- a/lib/model/token_import/token_import_source.dart +++ b/lib/model/token_import/token_import_source.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../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 37c8ca72e..e279b541e 100644 --- a/lib/model/token_import/token_origin_data.dart +++ b/lib/model/token_import/token_origin_data.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:json_annotation/json_annotation.dart'; import '../enums/token_origin_source_type.dart'; diff --git a/lib/model/tokens/container_credentials.dart b/lib/model/tokens/container_credentials.dart index cdc2809a7..3ff6b575d 100644 --- a/lib/model/tokens/container_credentials.dart +++ b/lib/model/tokens/container_credentials.dart @@ -1,5 +1,24 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:json_annotation/json_annotation.dart'; -import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; +import 'push_token.dart'; part 'container_credentials.g.dart'; diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index fc7a6a35a..c8de998cf 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -1,8 +1,27 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:typed_data'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:privacyidea_authenticator/model/token_container.dart'; +import '../token_container.dart'; import 'package:uuid/uuid.dart'; import '../../utils/errors.dart'; diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index c5e42d8e0..657839e2b 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -1,7 +1,26 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:typed_data'; import 'package:json_annotation/json_annotation.dart'; -import 'package:privacyidea_authenticator/model/token_container.dart'; +import '../token_container.dart'; import 'package:uuid/uuid.dart'; import '../../utils/errors.dart'; diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index e1e0382fe..6d2879ebf 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -1,6 +1,25 @@ -import 'package:privacyidea_authenticator/model/enums/encodings.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; -import 'package:privacyidea_authenticator/utils/identifiers.dart'; +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../enums/encodings.dart'; +import '../extensions/enums/encodings_extension.dart'; +import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; import '../enums/algorithms.dart'; @@ -70,7 +89,6 @@ abstract class OTPToken extends Token { return 'OTP${super.toString()}algorithm: $algorithm, digits: $digits, pin: $pin, '; } - @override Map toUriMap() { return super.toUriMap() diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index cac913ab3..e52befc65 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -1,7 +1,26 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:json_annotation/json_annotation.dart'; import 'package:pointycastle/asymmetric/api.dart'; -import 'package:privacyidea_authenticator/model/token_container.dart'; -import 'package:privacyidea_authenticator/utils/errors.dart'; +import '../token_container.dart'; +import '../../utils/errors.dart'; import 'package:uuid/uuid.dart'; import '../../utils/custom_int_buffer.dart'; diff --git a/lib/model/tokens/steam_token.dart b/lib/model/tokens/steam_token.dart index b7b7d2bfa..0d1ac4c0b 100644 --- a/lib/model/tokens/steam_token.dart +++ b/lib/model/tokens/steam_token.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:base32/base32.dart'; import 'package:crypto/crypto.dart'; import 'package:json_annotation/json_annotation.dart'; diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index 5ee0ff737..d8f1c9e3c 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -1,5 +1,24 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; -import 'package:privacyidea_authenticator/model/token_container.dart'; +import '../token_container.dart'; import '../../utils/identifiers.dart'; import '../enums/token_types.dart'; diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index 9b2f2c2cd..1e345bc22 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -1,7 +1,26 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:typed_data'; import 'package:json_annotation/json_annotation.dart'; -import 'package:privacyidea_authenticator/model/token_container.dart'; +import '../token_container.dart'; import 'package:uuid/uuid.dart'; import '../../utils/errors.dart'; diff --git a/lib/model/version.dart b/lib/model/version.dart index b5084027d..3fdb47104 100644 --- a/lib/model/version.dart +++ b/lib/model/version.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:json_annotation/json_annotation.dart'; part 'version.g.dart'; diff --git a/lib/processors/mixins/token_import_processor.dart b/lib/processors/mixins/token_import_processor.dart index d2c22255d..ed816a78a 100644 --- a/lib/processors/mixins/token_import_processor.dart +++ b/lib/processors/mixins/token_import_processor.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../model/processor_result.dart'; import '../../model/tokens/token.dart'; import '../scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart'; diff --git a/lib/processors/scheme_processors/container_credentials_processor.dart b/lib/processors/scheme_processors/container_credentials_processor.dart index fdd7d2d8b..509ae657e 100644 --- a/lib/processors/scheme_processors/container_credentials_processor.dart +++ b/lib/processors/scheme_processors/container_credentials_processor.dart @@ -1,6 +1,25 @@ -import 'package:privacyidea_authenticator/processors/scheme_processors/scheme_processor_interface.dart'; -import 'package:privacyidea_authenticator/utils/globals.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 'scheme_processor_interface.dart'; +import '../../utils/globals.dart'; +import '../../utils/logger.dart'; import '../../model/tokens/container_credentials.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart'; diff --git a/lib/processors/scheme_processors/home_widget_processor.dart b/lib/processors/scheme_processors/home_widget_processor.dart index c86e43ad2..54a05f8b8 100644 --- a/lib/processors/scheme_processors/home_widget_processor.dart +++ b/lib/processors/scheme_processors/home_widget_processor.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../utils/home_widget_utils.dart'; import '../../utils/logger.dart'; import 'scheme_processor_interface.dart'; diff --git a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart index 69f862f85..b872e6ff0 100644 --- a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart +++ b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../utils/globals.dart'; diff --git a/lib/processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart b/lib/processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart index c258a24c2..e780e7db1 100644 --- a/lib/processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart +++ b/lib/processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../utils/globals.dart'; diff --git a/lib/processors/scheme_processors/scheme_processor_interface.dart b/lib/processors/scheme_processors/scheme_processor_interface.dart index 20984e3ed..2fe4be6d0 100644 --- a/lib/processors/scheme_processors/scheme_processor_interface.dart +++ b/lib/processors/scheme_processors/scheme_processor_interface.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 'container_credentials_processor.dart'; import 'home_widget_processor.dart'; import 'navigation_scheme_processors/navigation_scheme_processor_interface.dart'; 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 089239a31..8f1127437 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,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../../model/enums/token_origin_source_type.dart'; import '../../../model/extensions/enums/token_origin_source_type.dart'; import '../../../model/processor_result.dart'; diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart b/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart index 0c1d2e876..7803d7224 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // Original Code: https://github.com/johncallahan/otpauth_migration/blob/main/lib/otpauth_migration.dart Copyright © 2022 John R. Callahan // Modified by Frank Merkel 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 d0780e178..66136558c 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,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:typed_data'; import 'package:collection/collection.dart'; 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 cf4f8958a..c49b97edb 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,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../../model/processor_result.dart'; import '../../../model/tokens/token.dart'; import '../../../utils/encryption/token_encryption.dart'; diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart b/lib/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart index 7302489b3..615b38e81 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../../model/processor_result.dart'; import '../../../model/tokens/token.dart'; import '../../mixins/token_import_processor.dart'; 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 eb428c2ce..5939e0e10 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // ignore_for_file: constant_identifier_names import 'dart:convert'; 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 2388dc38d..a7d7559b1 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // ignore_for_file: constant_identifier_names, empty_catches import 'dart:convert'; diff --git a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart index 922262da5..48b6d6b14 100644 --- a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // ignore_for_file: constant_identifier_names import 'dart:convert'; diff --git a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart index 2037637f5..941a2cc99 100644 --- a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart +++ b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 'dart:math'; 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 d9bcb4245..46c64a6e7 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; import 'package:file_selector/file_selector.dart'; 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 75a0bdc85..5be6c4810 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:file_selector/file_selector.dart'; import '../../model/processor_result.dart'; 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 083dbc654..4a5be5458 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // ignore_for_file: constant_identifier_names import 'dart:convert'; diff --git a/lib/repo/preference_introduction_repository.dart b/lib/repo/preference_introduction_repository.dart index 96691e125..73aecdbd8 100644 --- a/lib/repo/preference_introduction_repository.dart +++ b/lib/repo/preference_introduction_repository.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; import 'package:mutex/mutex.dart'; diff --git a/lib/repo/preference_settings_repository.dart b/lib/repo/preference_settings_repository.dart index 245dec83e..ac52079fe 100644 --- a/lib/repo/preference_settings_repository.dart +++ b/lib/repo/preference_settings_repository.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:mutex/mutex.dart'; import 'package:shared_preferences/shared_preferences.dart'; diff --git a/lib/repo/preference_token_folder_repository.dart b/lib/repo/preference_token_folder_repository.dart index 2084b54ab..3422bc905 100644 --- a/lib/repo/preference_token_folder_repository.dart +++ b/lib/repo/preference_token_folder_repository.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; import 'package:mutex/mutex.dart'; diff --git a/lib/repo/secure_container_credentials_repository.dart b/lib/repo/secure_container_credentials_repository.dart index 6b6bdcdbe..9b74b5422 100644 --- a/lib/repo/secure_container_credentials_repository.dart +++ b/lib/repo/secure_container_credentials_repository.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:mutex/mutex.dart'; -import 'package:privacyidea_authenticator/interfaces/repo/container_credentials_repository.dart'; +import '../interfaces/repo/container_credentials_repository.dart'; import '../model/riverpod_states/credentials_state.dart'; import '../model/tokens/container_credentials.dart'; diff --git a/lib/repo/secure_push_request_repository.dart b/lib/repo/secure_push_request_repository.dart index a598f7cdb..a5fb206af 100644 --- a/lib/repo/secure_push_request_repository.dart +++ b/lib/repo/secure_push_request_repository.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // ignore_for_file: constant_identifier_names import 'dart:convert'; diff --git a/lib/repo/secure_token_repository.dart b/lib/repo/secure_token_repository.dart index ebd0acb64..4bd660cdb 100644 --- a/lib/repo/secure_token_repository.dart +++ b/lib/repo/secure_token_repository.dart @@ -5,7 +5,7 @@ Authors: Timo Sturm Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH + Copyright (c) 2017-2024 NetKnights GmbH Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. diff --git a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart index df79df563..1bd00c938 100644 --- a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart @@ -1,4 +1,23 @@ -import 'package:privacyidea_authenticator/utils/errors.dart'; +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../utils/errors.dart'; import '../../interfaces/repo/container_repository.dart'; import '../../model/token_container.dart'; @@ -9,7 +28,6 @@ class HybridTokenContainerRepository localization.failedToLoad('local container state'), ), - ); } try { @@ -40,7 +57,6 @@ class HybridTokenContainerRepository(); } - try { await _localRepository.saveContainerState(newState); } catch (e) { diff --git a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart index ec5eed465..72c91bac0 100644 --- a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart @@ -1,5 +1,24 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:mutex/mutex.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; +import '../../utils/logger.dart'; import '../../api/token_container_api_endpoint.dart'; import '../../interfaces/repo/container_repository.dart'; @@ -28,6 +47,4 @@ class RemoteTokenContainerRepository implements TokenContainerRepository { } Future _fetchContainerState() async => await _protect(() async => await apiEndpoint.fetch()); - - } diff --git a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart index ed5b4a1cd..752900ee8 100644 --- a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart +++ b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart @@ -1,8 +1,27 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:mutex/mutex.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; +import '../../utils/logger.dart'; import '../../interfaces/repo/container_repository.dart'; import '../../model/token_container.dart'; diff --git a/lib/utils/app_info_utils.dart b/lib/utils/app_info_utils.dart index 015e1d123..eade7e225 100644 --- a/lib/utils/app_info_utils.dart +++ b/lib/utils/app_info_utils.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:io'; import 'package:device_info_plus/device_info_plus.dart'; diff --git a/lib/utils/crypto_utils.dart b/lib/utils/crypto_utils.dart index 0d708768b..9cfc04dde 100644 --- a/lib/utils/crypto_utils.dart +++ b/lib/utils/crypto_utils.dart @@ -3,7 +3,7 @@ Authors: Timo Sturm Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH + Copyright (c) 2017-2024 NetKnights GmbH Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. diff --git a/lib/utils/custom_int_buffer.dart b/lib/utils/custom_int_buffer.dart index eb6b498d5..7298bbbc6 100644 --- a/lib/utils/custom_int_buffer.dart +++ b/lib/utils/custom_int_buffer.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:math'; import 'package:flutter/foundation.dart'; diff --git a/lib/utils/customization/action_theme.dart b/lib/utils/customization/action_theme.dart index 589b64856..a7a49ba5e 100644 --- a/lib/utils/customization/action_theme.dart +++ b/lib/utils/customization/action_theme.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; class ActionTheme extends ThemeExtension { diff --git a/lib/utils/customization/application_customization.dart b/lib/utils/customization/application_customization.dart index 21df5bcea..8b1a14b16 100644 --- a/lib/utils/customization/application_customization.dart +++ b/lib/utils/customization/application_customization.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; import 'dart:typed_data'; diff --git a/lib/utils/customization/extended_text_theme.dart b/lib/utils/customization/extended_text_theme.dart index 289e38395..a946eb31a 100644 --- a/lib/utils/customization/extended_text_theme.dart +++ b/lib/utils/customization/extended_text_theme.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; class ExtendedTextTheme extends ThemeExtension { diff --git a/lib/utils/customization/theme_customization.dart b/lib/utils/customization/theme_customization.dart index 49b4f806b..37a3b51b6 100644 --- a/lib/utils/customization/theme_customization.dart +++ b/lib/utils/customization/theme_customization.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:math'; import 'package:flutter/material.dart'; diff --git a/lib/utils/encryption/aes_encrypted.dart b/lib/utils/encryption/aes_encrypted.dart index bcebdc7b6..ecf347336 100644 --- a/lib/utils/encryption/aes_encrypted.dart +++ b/lib/utils/encryption/aes_encrypted.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; import 'dart:math'; import 'dart:typed_data'; diff --git a/lib/utils/encryption/token_encryption.dart b/lib/utils/encryption/token_encryption.dart index 0925aab44..440f19f74 100644 --- a/lib/utils/encryption/token_encryption.dart +++ b/lib/utils/encryption/token_encryption.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; import 'package:zxing2/qrcode.dart'; diff --git a/lib/utils/errors.dart b/lib/utils/errors.dart index 5bc7e3de6..bdd55fc36 100644 --- a/lib/utils/errors.dart +++ b/lib/utils/errors.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../l10n/app_localizations.dart'; class LocalizedArgumentError extends LocalizedException implements ArgumentError { diff --git a/lib/utils/firebase_utils.dart b/lib/utils/firebase_utils.dart index 6c1c7d01f..4fca041b8 100644 --- a/lib/utils/firebase_utils.dart +++ b/lib/utils/firebase_utils.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // ignore_for_file: constant_identifier_names import 'dart:io'; diff --git a/lib/utils/globals.dart b/lib/utils/globals.dart index 86ae3bca7..4502c4d54 100644 --- a/lib/utils/globals.dart +++ b/lib/utils/globals.dart @@ -5,7 +5,7 @@ Authors: Timo Sturm Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH + Copyright (c) 2017-2024 NetKnights GmbH Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. diff --git a/lib/utils/home_widget_utils.dart b/lib/utils/home_widget_utils.dart index 93956aa74..f09fef02a 100644 --- a/lib/utils/home_widget_utils.dart +++ b/lib/utils/home_widget_utils.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; import 'dart:io'; diff --git a/lib/utils/identifiers.dart b/lib/utils/identifiers.dart index 53537cd31..2925890b1 100644 --- a/lib/utils/identifiers.dart +++ b/lib/utils/identifiers.dart @@ -5,7 +5,7 @@ Authors: Timo Sturm Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH + Copyright (c) 2017-2024 NetKnights GmbH Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. diff --git a/lib/utils/image_converter.dart b/lib/utils/image_converter.dart index 50ac981a9..8716af93d 100644 --- a/lib/utils/image_converter.dart +++ b/lib/utils/image_converter.dart @@ -1,388 +1,388 @@ -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; - -import '../utils/logger.dart'; - -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? cropLeft, int? cropRight, int? cropTop, int? cropBottom}) { - return switch (image.format.group) { - ImageFormatGroup.yuv420 => ImageConverter._fromYUV420(image, rotation, isFrontCamera, cropLeft ?? 0, cropRight ?? 0, cropTop ?? 0, cropBottom ?? 0), - ImageFormatGroup.bgra8888 => ImageConverter._fromBGRA8888(image, rotation, isFrontCamera, cropLeft ?? 0, cropRight ?? 0, cropTop ?? 0, cropBottom ?? 0), - ImageFormatGroup.jpeg => ImageConverter._fromJPEG(image), - ImageFormatGroup.nv21 => ImageConverter._fromNV21(image, - rotation: rotation, mirror: isFrontCamera, cropLeft: cropLeft ?? 0, cropRight: cropRight ?? 0, cropTop: cropTop ?? 0, cropBottom: cropBottom ?? 0), - ImageFormatGroup.unknown => throw ArgumentError('Unknown image format', 'image.format.group'), - }; - } - - factory ImageConverter._fromNV21(CameraImage image, - {int rotation = 0, bool mirror = false, int cropLeft = 0, int cropRight = 0, int cropTop = 0, int cropBottom = 0}) { - Uint8List yuv420sp = image.planes[0].bytes; - rotation = 360 - (rotation % 360); // if the rotation is 90, we need to rotate by 270 to get the correct rotation - Logger.info( - 'Converting NV21 image with rotation: $rotation, mirror: $mirror, cropLeft: $cropLeft, cropRight: $cropRight, cropTop: $cropTop, cropBottom: $cropBottom, width: ${image.width}, height: ${image.height}'); - final height = image.height; - final width = image.width; - final int frameSize = width * height; - - final int outputWidth; - final int outputHeight; - final int rotatedCropLeft; - final int rotatedCropRight; - final int rotatedCropTop; - final int rotatedCropBottom; - - 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 degrees and flip horizontally - getNewX = (x, y) => height - y - 1; - getNewY = (x, y) => width - x - 1; - rotatedCropRight = cropBottom; - rotatedCropBottom = cropRight; - rotatedCropLeft = cropTop; - rotatedCropTop = cropLeft; - } else { - // rotate by 90 degrees - getNewX = (x, y) => y; - getNewY = (x, y) => width - x - 1; - rotatedCropRight = cropTop; - rotatedCropBottom = cropRight; - rotatedCropLeft = cropBottom; - rotatedCropTop = cropLeft; - } - break; - case 180: - outputWidth = width; - outputHeight = height; - if (mirror) { - // rotate by 180 degrees and flip horizontally - getNewX = (x, y) => x; - getNewY = (x, y) => height - y - 1; - - rotatedCropBottom = cropTop; - rotatedCropLeft = cropLeft; - rotatedCropTop = cropBottom; - rotatedCropRight = cropRight; - } else { - // rotate by 180 degrees - getNewX = (x, y) => width - x - 1; - getNewY = (x, y) => height - y - 1; - rotatedCropBottom = cropTop; - rotatedCropLeft = cropRight; - rotatedCropTop = cropBottom; - rotatedCropRight = cropLeft; - } - break; - case 270: - outputWidth = height; - outputHeight = width; - if (mirror) { - // rotate by 270 degrees and flip horizontally - getNewX = (x, y) => y; - getNewY = (x, y) => height - x; - - rotatedCropLeft = cropBottom; - rotatedCropTop = cropRight; - rotatedCropRight = cropTop; - rotatedCropBottom = cropLeft; - } else { - // rotate by 270 degrees - getNewX = (x, y) => height - y - 1; - getNewY = (x, y) => x; - rotatedCropLeft = cropTop; - rotatedCropTop = cropRight; - rotatedCropRight = cropBottom; - rotatedCropBottom = cropLeft; - } - break; - - default: - outputWidth = width; - outputHeight = height; - if (mirror) { - // do not rotate, flip horizontally - getNewX = (x, y) => x; - getNewY = (x, y) => height - y - 1; - rotatedCropTop = cropTop; - rotatedCropRight = cropLeft; - rotatedCropBottom = cropBottom; - rotatedCropLeft = cropRight; - } else { - // do not rotate - getNewX = (x, y) => x; - getNewY = (x, y) => y; - rotatedCropTop = cropTop; - rotatedCropRight = cropRight; - rotatedCropBottom = cropBottom; - rotatedCropLeft = cropLeft; - } - break; - } - - // imgLib -> Image package from https://pub.dartlang.org/packages/image - var convertedImage = imglib.Image(width: outputWidth, height: outputHeight); // Create Image buffer - - for (int yAxisPixel = cropTop, yp = cropLeft + cropTop * width; yAxisPixel < height - cropBottom; yAxisPixel++, yp += cropLeft + cropRight) { - int uvp = frameSize + (yAxisPixel >> 1) * width + cropLeft, u = 0, v = 0; - for (int xAxisPixel = cropLeft; xAxisPixel < width - cropRight; xAxisPixel++, yp++) { - int y = (0xff & yuv420sp[yp]) - 16; - if (y < 0) y = 0; - if ((xAxisPixel & 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( - getNewX(xAxisPixel, yAxisPixel), - getNewY(xAxisPixel, yAxisPixel), - ((r << 6) & 0xff0000) >> 16, - ((g >> 2) & 0xff00) >> 8, - (b >> 10) & 0xff, - 0xff, - ); - } - } - - return ImageConverter( - image: imglib.copyCrop( - convertedImage, - x: rotatedCropLeft, - y: rotatedCropTop, - width: convertedImage.width - rotatedCropLeft - rotatedCropRight, - height: convertedImage.height - rotatedCropTop - rotatedCropBottom, - ), - ); - } - - factory ImageConverter._fromJPEG(CameraImage image) { - Logger.info('Converting JPEG image to 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) { - Logger.info('Converting BGRA8888 image to Image'); - 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, - order: imglib.ChannelOrder.bgra, - ); - if (rotation != 0) { - 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 cropLeft = 0, - int cropRight = 0, - int cropTop = 0, - int cropBottom = 0, - ]) { - Logger.info('Converting YUV420 image to Image'); - 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 rotatedCropLeft; - final int rotatedCropRight; - final int rotatedCropTop; - final int rotatedCropBottom; - - 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; - rotatedCropRight = cropBottom; - rotatedCropBottom = cropRight; - rotatedCropLeft = cropTop; - rotatedCropTop = cropLeft; - } else { - getNewX = (x, y) => y; - getNewY = (x, y) => width - x - 1; - rotatedCropRight = cropTop; - rotatedCropBottom = cropRight; - rotatedCropLeft = cropBottom; - rotatedCropTop = cropLeft; - } - 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; - - rotatedCropBottom = cropTop; - rotatedCropLeft = cropLeft; - rotatedCropTop = cropBottom; - rotatedCropRight = cropRight; - } else { - getNewX = (x, y) => width - x - 1; - getNewY = (x, y) => height - y - 1; - rotatedCropBottom = cropTop; - rotatedCropLeft = cropRight; - rotatedCropTop = cropBottom; - rotatedCropRight = cropLeft; - } - break; - case 270: - outputWidth = height; - outputHeight = width; - if (mirror) { - // rotate by 270 and flip horizontally - getNewX = (x, y) => y; - getNewY = (x, y) => height - x; - - rotatedCropLeft = cropBottom; - rotatedCropTop = cropRight; - rotatedCropRight = cropTop; - rotatedCropBottom = cropLeft; - } else { - getNewX = (x, y) => height - y - 1; - getNewY = (x, y) => x; - rotatedCropLeft = cropTop; - rotatedCropTop = cropRight; - rotatedCropRight = cropBottom; - rotatedCropBottom = cropLeft; - } - break; - - default: - outputWidth = width; - outputHeight = height; - if (mirror) { - // flip horizontally - getNewX = (x, y) => x; - getNewY = (x, y) => height - y - 1; - rotatedCropTop = cropTop; - rotatedCropRight = cropLeft; - rotatedCropBottom = cropBottom; - rotatedCropLeft = cropRight; - } else { - getNewX = (x, y) => x; - getNewY = (x, y) => y; - rotatedCropTop = cropTop; - rotatedCropRight = cropRight; - rotatedCropBottom = cropBottom; - rotatedCropLeft = cropLeft; - } - 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 = cropTop; y < height - cropBottom; y++) { - for (int x = cropLeft; x < width - cropRight; 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 cropedImg = imglib.copyCrop( - img, - x: rotatedCropLeft, - y: rotatedCropTop, - width: img.width - rotatedCropLeft - rotatedCropRight, - height: img.height - rotatedCropTop - rotatedCropBottom, - ); - return ImageConverter(image: cropedImg); - } - - 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; - } -} +// 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; + +// import '../utils/logger.dart'; + +// 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? cropLeft, int? cropRight, int? cropTop, int? cropBottom}) { +// return switch (image.format.group) { +// ImageFormatGroup.yuv420 => ImageConverter._fromYUV420(image, rotation, isFrontCamera, cropLeft ?? 0, cropRight ?? 0, cropTop ?? 0, cropBottom ?? 0), +// ImageFormatGroup.bgra8888 => ImageConverter._fromBGRA8888(image, rotation, isFrontCamera, cropLeft ?? 0, cropRight ?? 0, cropTop ?? 0, cropBottom ?? 0), +// ImageFormatGroup.jpeg => ImageConverter._fromJPEG(image), +// ImageFormatGroup.nv21 => ImageConverter._fromNV21(image, +// rotation: rotation, mirror: isFrontCamera, cropLeft: cropLeft ?? 0, cropRight: cropRight ?? 0, cropTop: cropTop ?? 0, cropBottom: cropBottom ?? 0), +// ImageFormatGroup.unknown => throw ArgumentError('Unknown image format', 'image.format.group'), +// }; +// } + +// factory ImageConverter._fromNV21(CameraImage image, +// {int rotation = 0, bool mirror = false, int cropLeft = 0, int cropRight = 0, int cropTop = 0, int cropBottom = 0}) { +// Uint8List yuv420sp = image.planes[0].bytes; +// rotation = 360 - (rotation % 360); // if the rotation is 90, we need to rotate by 270 to get the correct rotation +// Logger.info( +// 'Converting NV21 image with rotation: $rotation, mirror: $mirror, cropLeft: $cropLeft, cropRight: $cropRight, cropTop: $cropTop, cropBottom: $cropBottom, width: ${image.width}, height: ${image.height}'); +// final height = image.height; +// final width = image.width; +// final int frameSize = width * height; + +// final int outputWidth; +// final int outputHeight; +// final int rotatedCropLeft; +// final int rotatedCropRight; +// final int rotatedCropTop; +// final int rotatedCropBottom; + +// 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 degrees and flip horizontally +// getNewX = (x, y) => height - y - 1; +// getNewY = (x, y) => width - x - 1; +// rotatedCropRight = cropBottom; +// rotatedCropBottom = cropRight; +// rotatedCropLeft = cropTop; +// rotatedCropTop = cropLeft; +// } else { +// // rotate by 90 degrees +// getNewX = (x, y) => y; +// getNewY = (x, y) => width - x - 1; +// rotatedCropRight = cropTop; +// rotatedCropBottom = cropRight; +// rotatedCropLeft = cropBottom; +// rotatedCropTop = cropLeft; +// } +// break; +// case 180: +// outputWidth = width; +// outputHeight = height; +// if (mirror) { +// // rotate by 180 degrees and flip horizontally +// getNewX = (x, y) => x; +// getNewY = (x, y) => height - y - 1; + +// rotatedCropBottom = cropTop; +// rotatedCropLeft = cropLeft; +// rotatedCropTop = cropBottom; +// rotatedCropRight = cropRight; +// } else { +// // rotate by 180 degrees +// getNewX = (x, y) => width - x - 1; +// getNewY = (x, y) => height - y - 1; +// rotatedCropBottom = cropTop; +// rotatedCropLeft = cropRight; +// rotatedCropTop = cropBottom; +// rotatedCropRight = cropLeft; +// } +// break; +// case 270: +// outputWidth = height; +// outputHeight = width; +// if (mirror) { +// // rotate by 270 degrees and flip horizontally +// getNewX = (x, y) => y; +// getNewY = (x, y) => height - x; + +// rotatedCropLeft = cropBottom; +// rotatedCropTop = cropRight; +// rotatedCropRight = cropTop; +// rotatedCropBottom = cropLeft; +// } else { +// // rotate by 270 degrees +// getNewX = (x, y) => height - y - 1; +// getNewY = (x, y) => x; +// rotatedCropLeft = cropTop; +// rotatedCropTop = cropRight; +// rotatedCropRight = cropBottom; +// rotatedCropBottom = cropLeft; +// } +// break; + +// default: +// outputWidth = width; +// outputHeight = height; +// if (mirror) { +// // do not rotate, flip horizontally +// getNewX = (x, y) => x; +// getNewY = (x, y) => height - y - 1; +// rotatedCropTop = cropTop; +// rotatedCropRight = cropLeft; +// rotatedCropBottom = cropBottom; +// rotatedCropLeft = cropRight; +// } else { +// // do not rotate +// getNewX = (x, y) => x; +// getNewY = (x, y) => y; +// rotatedCropTop = cropTop; +// rotatedCropRight = cropRight; +// rotatedCropBottom = cropBottom; +// rotatedCropLeft = cropLeft; +// } +// break; +// } + +// // imgLib -> Image package from https://pub.dartlang.org/packages/image +// var convertedImage = imglib.Image(width: outputWidth, height: outputHeight); // Create Image buffer + +// for (int yAxisPixel = cropTop, yp = cropLeft + cropTop * width; yAxisPixel < height - cropBottom; yAxisPixel++, yp += cropLeft + cropRight) { +// int uvp = frameSize + (yAxisPixel >> 1) * width + cropLeft, u = 0, v = 0; +// for (int xAxisPixel = cropLeft; xAxisPixel < width - cropRight; xAxisPixel++, yp++) { +// int y = (0xff & yuv420sp[yp]) - 16; +// if (y < 0) y = 0; +// if ((xAxisPixel & 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( +// getNewX(xAxisPixel, yAxisPixel), +// getNewY(xAxisPixel, yAxisPixel), +// ((r << 6) & 0xff0000) >> 16, +// ((g >> 2) & 0xff00) >> 8, +// (b >> 10) & 0xff, +// 0xff, +// ); +// } +// } + +// return ImageConverter( +// image: imglib.copyCrop( +// convertedImage, +// x: rotatedCropLeft, +// y: rotatedCropTop, +// width: convertedImage.width - rotatedCropLeft - rotatedCropRight, +// height: convertedImage.height - rotatedCropTop - rotatedCropBottom, +// ), +// ); +// } + +// factory ImageConverter._fromJPEG(CameraImage image) { +// Logger.info('Converting JPEG image to 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) { +// Logger.info('Converting BGRA8888 image to Image'); +// 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, +// order: imglib.ChannelOrder.bgra, +// ); +// if (rotation != 0) { +// 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 cropLeft = 0, +// int cropRight = 0, +// int cropTop = 0, +// int cropBottom = 0, +// ]) { +// Logger.info('Converting YUV420 image to Image'); +// 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 rotatedCropLeft; +// final int rotatedCropRight; +// final int rotatedCropTop; +// final int rotatedCropBottom; + +// 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; +// rotatedCropRight = cropBottom; +// rotatedCropBottom = cropRight; +// rotatedCropLeft = cropTop; +// rotatedCropTop = cropLeft; +// } else { +// getNewX = (x, y) => y; +// getNewY = (x, y) => width - x - 1; +// rotatedCropRight = cropTop; +// rotatedCropBottom = cropRight; +// rotatedCropLeft = cropBottom; +// rotatedCropTop = cropLeft; +// } +// 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; + +// rotatedCropBottom = cropTop; +// rotatedCropLeft = cropLeft; +// rotatedCropTop = cropBottom; +// rotatedCropRight = cropRight; +// } else { +// getNewX = (x, y) => width - x - 1; +// getNewY = (x, y) => height - y - 1; +// rotatedCropBottom = cropTop; +// rotatedCropLeft = cropRight; +// rotatedCropTop = cropBottom; +// rotatedCropRight = cropLeft; +// } +// break; +// case 270: +// outputWidth = height; +// outputHeight = width; +// if (mirror) { +// // rotate by 270 and flip horizontally +// getNewX = (x, y) => y; +// getNewY = (x, y) => height - x; + +// rotatedCropLeft = cropBottom; +// rotatedCropTop = cropRight; +// rotatedCropRight = cropTop; +// rotatedCropBottom = cropLeft; +// } else { +// getNewX = (x, y) => height - y - 1; +// getNewY = (x, y) => x; +// rotatedCropLeft = cropTop; +// rotatedCropTop = cropRight; +// rotatedCropRight = cropBottom; +// rotatedCropBottom = cropLeft; +// } +// break; + +// default: +// outputWidth = width; +// outputHeight = height; +// if (mirror) { +// // flip horizontally +// getNewX = (x, y) => x; +// getNewY = (x, y) => height - y - 1; +// rotatedCropTop = cropTop; +// rotatedCropRight = cropLeft; +// rotatedCropBottom = cropBottom; +// rotatedCropLeft = cropRight; +// } else { +// getNewX = (x, y) => x; +// getNewY = (x, y) => y; +// rotatedCropTop = cropTop; +// rotatedCropRight = cropRight; +// rotatedCropBottom = cropBottom; +// rotatedCropLeft = cropLeft; +// } +// 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 = cropTop; y < height - cropBottom; y++) { +// for (int x = cropLeft; x < width - cropRight; 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 cropedImg = imglib.copyCrop( +// img, +// x: rotatedCropLeft, +// y: rotatedCropTop, +// width: img.width - rotatedCropLeft - rotatedCropRight, +// height: img.height - rotatedCropTop - rotatedCropBottom, +// ); +// return ImageConverter(image: cropedImg); +// } + +// 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/lib/utils/lock_auth.dart b/lib/utils/lock_auth.dart index 6dd2cfa6f..0a4df35ce 100644 --- a/lib/utils/lock_auth.dart +++ b/lib/utils/lock_auth.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 'package:flutter/foundation.dart'; diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index 650467194..a956d6d0e 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ // ignore_for_file: constant_identifier_names import 'dart:async'; @@ -21,7 +40,6 @@ import 'riverpod/riverpod_providers/state_notifier_providers/settings_provider.d final provider = Provider((ref) => 0); - class Logger { /*----------- STATIC FIELDS & GETTER -----------*/ static final Mutex _mutexWriteFile = Mutex(); diff --git a/lib/utils/patch_notes_utils.dart b/lib/utils/patch_notes_utils.dart index 3be77640e..225112837 100644 --- a/lib/utils/patch_notes_utils.dart +++ b/lib/utils/patch_notes_utils.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../l10n/app_localizations.dart'; diff --git a/lib/utils/pi_mailer.dart b/lib/utils/pi_mailer.dart index 8de0a3311..00bf6336a 100644 --- a/lib/utils/pi_mailer.dart +++ b/lib/utils/pi_mailer.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mailer/flutter_mailer.dart'; diff --git a/lib/utils/pi_notifications.dart b/lib/utils/pi_notifications.dart index 8a98113a7..7e00ca54a 100644 --- a/lib/utils/pi_notifications.dart +++ b/lib/utils/pi_notifications.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_local_notifications/flutter_local_notifications.dart'; class PiNotifications { diff --git a/lib/utils/privacyidea_io_client.dart b/lib/utils/privacyidea_io_client.dart index 19b663137..6a64699b2 100644 --- a/lib/utils/privacyidea_io_client.dart +++ b/lib/utils/privacyidea_io_client.dart @@ -3,7 +3,7 @@ Authors: Timo Sturm Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH + Copyright (c) 2017-2024 NetKnights GmbH Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. diff --git a/lib/utils/push_provider.dart b/lib/utils/push_provider.dart index 63ae55ab8..0c4fbc11a 100644 --- a/lib/utils/push_provider.dart +++ b/lib/utils/push_provider.dart @@ -3,7 +3,7 @@ Authors: Timo Sturm Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH + Copyright (c) 2017-2024 NetKnights GmbH Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. @@ -64,7 +64,6 @@ class PushProvider { RsaUtils _rsaUtils; RsaUtils get rsaUtils => _rsaUtils; - PushProvider._({ FirebaseUtils? firebaseUtils, PrivacyideaIOClient? ioClient, diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart index e0f75eab1..189a7906f 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:mutex/mutex.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart index 0a12bde99..d7eea653b 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 'package:app_links/app_links.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart index c00ad0896..c44ad7b11 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -1,6 +1,25 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:mutex/mutex.dart'; -import 'package:privacyidea_authenticator/interfaces/repo/container_repository.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; +import '../../../../interfaces/repo/container_repository.dart'; +import '../../../logger.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../../api/token_container_api_endpoint.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart index 4af86b460..5733d0f0b 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../../model/riverpod_states/introduction_state.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart index ae4f9c521..2a3f2f6d3 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../../model/riverpod_states/progress_state.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart index d3b91a15b..f9c3a916e 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../../model/riverpod_states/push_request_state.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart index fc6180583..2be88d69f 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../../model/riverpod_states/settings_state.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart index 68779b5bd..f40fec012 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../../model/mixins/sortable_mixin.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart index c694071a2..78a112ec0 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../../model/riverpod_states/token_folder_state.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart index 4e81d0f37..e70ed9b0b 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../../model/riverpod_states/token_state.dart'; @@ -17,7 +36,6 @@ final tokenProvider = StateNotifierProvider( newTokenNotifier.handleLink(data.uri); }, ); - }); return newTokenNotifier; diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart index fee317907..d3e8d634c 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/rendering.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart index 85cdd6d53..abf197511 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../../model/enums/app_feature.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart index 527529afd..c0f7604a2 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../../model/mixins/sortable_mixin.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart index 033b3297f..6eb9b40fe 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../../model/tokens/otp_token.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart index 9a93308d4..610d31e94 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../logger.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart index a250e43c3..e72df1d41 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../../model/riverpod_states/token_filter.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart b/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart index 5625d1b84..caea99c07 100644 --- a/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:connectivity_plus/connectivity_plus.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart b/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart index 8097aa024..822f28d00 100644 --- a/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart +++ b/lib/utils/riverpod/state_listeners/home_widget_deep_link_listener.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart'; diff --git a/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart index 7060d7f74..b1c91f65e 100644 --- a/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart @@ -1,5 +1,24 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:collection/collection.dart'; -import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; +import '../../../model/tokens/hotp_token.dart'; import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; import '../../../model/riverpod_states/token_state.dart'; diff --git a/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart b/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart index a2df68fa8..392c46681 100644 --- a/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart +++ b/lib/utils/riverpod/state_listeners/navigation_deep_link_listener.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -19,7 +38,7 @@ class NavigationDeepLinkListener extends DeepLinkListener { static void _onNewState(AsyncValue? previous, AsyncValue next) { next.whenData((next) { - NavigationSchemeProcessor.processUriByAny(next.uri, context: _context, fromInit: next.fromInit); + NavigationSchemeProcessor.processUriByAny(next.uri, context: _context, fromInit: next.fromInit); }); } } diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index 5f916e535..6f826e642 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -1,6 +1,25 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; +import '../../logger.dart'; import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; import '../../../model/riverpod_states/token_state.dart'; @@ -25,7 +44,6 @@ class ContainerListensToTokenState extends TokenStateListener { Logger.warning('Readed: $credentials', name: 'TokenContainerTokenStateListener'); // if (maybePiTokenTemplates.isEmpty) return; for (var credential in credentials) { - final deletedPiTokenTemplates = nextState.lastlyDeletedTokens.fromContainer(credential.serial).toTemplates(); if (deletedPiTokenTemplates.isNotEmpty) { Logger.warning( diff --git a/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart b/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart index 832cce0a2..cfd559f0c 100644 --- a/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../interfaces/repo/introduction_repository.dart'; diff --git a/lib/utils/riverpod/state_notifiers/deeplink_notifier.dart b/lib/utils/riverpod/state_notifiers/deeplink_notifier.dart index 9187e0e53..3bd1af66f 100644 --- a/lib/utils/riverpod/state_notifiers/deeplink_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/deeplink_notifier.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 'package:flutter/foundation.dart'; diff --git a/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart b/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart index ae10dd510..ac4a491ec 100644 --- a/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../model/riverpod_states/progress_state.dart'; diff --git a/lib/utils/riverpod/state_notifiers/push_request_notifier.dart b/lib/utils/riverpod/state_notifiers/push_request_notifier.dart index 95a692dd8..8af7fead8 100644 --- a/lib/utils/riverpod/state_notifiers/push_request_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/push_request_notifier.dart @@ -3,7 +3,7 @@ Authors: Timo Sturm Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH + Copyright (c) 2017-2024 NetKnights GmbH Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. diff --git a/lib/utils/riverpod/state_notifiers/settings_notifier.dart b/lib/utils/riverpod/state_notifiers/settings_notifier.dart index 98cc6f5f5..e12e319d4 100644 --- a/lib/utils/riverpod/state_notifiers/settings_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/settings_notifier.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:ui'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/utils/riverpod/state_notifiers/sortable_notifier.dart b/lib/utils/riverpod/state_notifiers/sortable_notifier.dart index ee2d37893..463d31936 100644 --- a/lib/utils/riverpod/state_notifiers/sortable_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/sortable_notifier.dart @@ -1,5 +1,24 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; +import '../../logger.dart'; import '../../../model/extensions/sortable_list.dart'; import '../../../model/mixins/sortable_mixin.dart'; diff --git a/lib/utils/riverpod/state_notifiers/token_container_notifier.dart b/lib/utils/riverpod/state_notifiers/token_container_notifier.dart index dc7ce6f93..6d8ccf144 100644 --- a/lib/utils/riverpod/state_notifiers/token_container_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_container_notifier.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; // import 'package:mutex/mutex.dart'; // import 'package:privacyidea_authenticator/model/states/token_state.dart'; diff --git a/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart b/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart index b77ee20e2..7eeb9d3c1 100644 --- a/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import 'package:mutex/mutex.dart'; diff --git a/lib/utils/riverpod/state_notifiers/token_notifier.dart b/lib/utils/riverpod/state_notifiers/token_notifier.dart index 9ee86f936..68b1eb9e5 100644 --- a/lib/utils/riverpod/state_notifiers/token_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_notifier.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; import 'dart:io'; diff --git a/lib/utils/rsa_utils.dart b/lib/utils/rsa_utils.dart index 4c82faf29..56a5b8e98 100644 --- a/lib/utils/rsa_utils.dart +++ b/lib/utils/rsa_utils.dart @@ -3,7 +3,7 @@ Authors: Timo Sturm Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH + Copyright (c) 2017-2024 NetKnights GmbH Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. diff --git a/lib/utils/token_import_origins.dart b/lib/utils/token_import_origins.dart index eae2dfcde..d258112a2 100644 --- a/lib/utils/token_import_origins.dart +++ b/lib/utils/token_import_origins.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../mains/main_netknights.dart'; import '../model/enums/token_import_type.dart'; import '../model/token_import/token_import_origin.dart'; diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 7d0a08387..2759747ff 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -3,7 +3,7 @@ Authors: Timo Sturm Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH + Copyright (c) 2017-2024 NetKnights GmbH Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. diff --git a/lib/utils/validators.dart b/lib/utils/validators.dart index 794e81819..31b0a834d 100644 --- a/lib/utils/validators.dart +++ b/lib/utils/validators.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../l10n/app_localizations.dart'; class Validators { diff --git a/lib/utils/view_utils.dart b/lib/utils/view_utils.dart index e80b08110..2f0274f9c 100644 --- a/lib/utils/view_utils.dart +++ b/lib/utils/view_utils.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'globals.dart'; 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 e4ad8aeb9..866220213 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; import 'package:flutter/material.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 822d58eb7..9cac8fa0d 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,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../utils/logger.dart'; diff --git a/lib/views/feedback_view/feedback_view.dart b/lib/views/feedback_view/feedback_view.dart index 03f57451e..4c34ec6c7 100644 --- a/lib/views/feedback_view/feedback_view.dart +++ b/lib/views/feedback_view/feedback_view.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/gestures.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/lib/views/import_tokens_view/import_tokens_view.dart b/lib/views/import_tokens_view/import_tokens_view.dart index f6d48f154..f92639144 100644 --- a/lib/views/import_tokens_view/import_tokens_view.dart +++ b/lib/views/import_tokens_view/import_tokens_view.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/import_tokens_view/pages/import_encrypted_data_page.dart b/lib/views/import_tokens_view/pages/import_encrypted_data_page.dart index d876b6a3d..ee73e5502 100644 --- a/lib/views/import_tokens_view/pages/import_encrypted_data_page.dart +++ b/lib/views/import_tokens_view/pages/import_encrypted_data_page.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../l10n/app_localizations.dart'; 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 7ad7cffd6..e5c6f99ca 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,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.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 6c5d4235c..df3fce2cd 100644 --- a/lib/views/import_tokens_view/pages/import_start_page.dart +++ b/lib/views/import_tokens_view/pages/import_start_page.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:io'; import 'package:file_selector/file_selector.dart'; 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 849b17187..4b7ce87a9 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,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; diff --git a/lib/views/import_tokens_view/widgets/conflicted_import_tokens_list.dart b/lib/views/import_tokens_view/widgets/conflicted_import_tokens_list.dart index eb5b2459f..7f5a0b6e8 100644 --- a/lib/views/import_tokens_view/widgets/conflicted_import_tokens_list.dart +++ b/lib/views/import_tokens_view/widgets/conflicted_import_tokens_list.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../model/token_import/token_import_entry.dart'; diff --git a/lib/views/import_tokens_view/widgets/conflicted_import_tokens_tile.dart b/lib/views/import_tokens_view/widgets/conflicted_import_tokens_tile.dart index 87b6d181d..5eae3ce2a 100644 --- a/lib/views/import_tokens_view/widgets/conflicted_import_tokens_tile.dart +++ b/lib/views/import_tokens_view/widgets/conflicted_import_tokens_tile.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../model/token_import/token_import_entry.dart'; diff --git a/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart b/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart index 3fb95eedd..b3890ccac 100644 --- a/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart +++ b/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:image_cropper/image_cropper.dart'; diff --git a/lib/views/import_tokens_view/widgets/failed_imports_list.dart b/lib/views/import_tokens_view/widgets/failed_imports_list.dart index 738d7d225..d02ebd77d 100644 --- a/lib/views/import_tokens_view/widgets/failed_imports_list.dart +++ b/lib/views/import_tokens_view/widgets/failed_imports_list.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../l10n/app_localizations.dart'; diff --git a/lib/views/import_tokens_view/widgets/no_conflict_import_tokens_list.dart b/lib/views/import_tokens_view/widgets/no_conflict_import_tokens_list.dart index 86628549b..d52067f53 100644 --- a/lib/views/import_tokens_view/widgets/no_conflict_import_tokens_list.dart +++ b/lib/views/import_tokens_view/widgets/no_conflict_import_tokens_list.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../model/token_import/token_import_entry.dart'; diff --git a/lib/views/import_tokens_view/widgets/no_conflict_import_tokens_tile.dart b/lib/views/import_tokens_view/widgets/no_conflict_import_tokens_tile.dart index 15d77685c..d37688bcc 100644 --- a/lib/views/import_tokens_view/widgets/no_conflict_import_tokens_tile.dart +++ b/lib/views/import_tokens_view/widgets/no_conflict_import_tokens_tile.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../model/tokens/token.dart'; diff --git a/lib/views/license_view/license_view.dart b/lib/views/license_view/license_view.dart index 9f8ff2a00..d26bf2f55 100644 --- a/lib/views/license_view/license_view.dart +++ b/lib/views/license_view/license_view.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:package_info_plus/package_info_plus.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 00b2d0860..d9fd57936 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:app_minimizer/app_minimizer.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/main_view/main_view.dart b/lib/views/main_view/main_view.dart index d1b28d64e..1bb33dd5c 100644 --- a/lib/views/main_view/main_view.dart +++ b/lib/views/main_view/main_view.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/main_view/main_view_widgets/app_bar_item.dart b/lib/views/main_view/main_view_widgets/app_bar_item.dart index ba285459b..83e8c82ae 100644 --- a/lib/views/main_view/main_view_widgets/app_bar_item.dart +++ b/lib/views/main_view/main_view_widgets/app_bar_item.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; class AppBarItem extends StatelessWidget { 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 b078f832b..7b5dacf25 100644 --- a/lib/views/main_view/main_view_widgets/connectivity_listener.dart +++ b/lib/views/main_view/main_view_widgets/connectivity_listener.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/main_view/main_view_widgets/custom_paint_navigation_bar.dart b/lib/views/main_view/main_view_widgets/custom_paint_navigation_bar.dart index 80f8f696f..570fce195 100644 --- a/lib/views/main_view/main_view_widgets/custom_paint_navigation_bar.dart +++ b/lib/views/main_view/main_view_widgets/custom_paint_navigation_bar.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:math'; import 'package:flutter/material.dart'; 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 a6926d381..0d9e7b89f 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:math'; import 'package:flutter/material.dart'; diff --git a/lib/views/main_view/main_view_widgets/expandable_appbar.dart b/lib/views/main_view/main_view_widgets/expandable_appbar.dart index 4c3605910..c2379d53e 100644 --- a/lib/views/main_view/main_view_widgets/expandable_appbar.dart +++ b/lib/views/main_view/main_view_widgets/expandable_appbar.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'filter_token_widget.dart'; diff --git a/lib/views/main_view/main_view_widgets/filter_token_widget.dart b/lib/views/main_view/main_view_widgets/filter_token_widget.dart index ae4087ea6..68351832b 100644 --- a/lib/views/main_view/main_view_widgets/filter_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/filter_token_widget.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart b/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart index c3cbd27fd..7f9832490 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.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 f39881f0f..1b5f79974 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_slidable/flutter_slidable.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 7dd701f5b..80e6325f9 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_slidable/flutter_slidable.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 3267dfd80..5a51a8abc 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_slidable/flutter_slidable.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 0f04692cf..73aa6815e 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:developer'; diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart index 32ba38425..37facab71 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:math'; import 'package:flutter/material.dart'; diff --git a/lib/views/main_view/main_view_widgets/loading_indicator.dart b/lib/views/main_view/main_view_widgets/loading_indicator.dart index e2b4652c1..c0b7dce9b 100644 --- a/lib/views/main_view/main_view_widgets/loading_indicator.dart +++ b/lib/views/main_view/main_view_widgets/loading_indicator.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../utils/logger.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart index 977c0db33..dd5bac668 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart index 0b2650c43..924329a30 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart index de5bd8108..3d963f546 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:permission_handler/permission_handler.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 35e1e54c2..13fe8d787 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,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -142,12 +161,10 @@ class _MainViewTokensListState extends ConsumerState { ), ), ), - ], - ); - + ], + ); } - ScrollPhysics _getScrollPhysics(bool allowToRefresh) => allowToRefresh ? const AlwaysScrollableScrollPhysics(parent: ClampingScrollPhysics()) : const BouncingScrollPhysics(); } diff --git a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart index 71c12a434..d89818a53 100644 --- a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart +++ b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/main_view/main_view_widgets/no_token_screen.dart b/lib/views/main_view/main_view_widgets/no_token_screen.dart index 021ef9d5a..aeb097a92 100644 --- a/lib/views/main_view/main_view_widgets/no_token_screen.dart +++ b/lib/views/main_view/main_view_widgets/no_token_screen.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../l10n/app_localizations.dart'; diff --git a/lib/views/main_view/main_view_widgets/sortable_widget_builder.dart b/lib/views/main_view/main_view_widgets/sortable_widget_builder.dart index 4737ef6ca..0dfaec32d 100644 --- a/lib/views/main_view/main_view_widgets/sortable_widget_builder.dart +++ b/lib/views/main_view/main_view_widgets/sortable_widget_builder.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../model/mixins/sortable_mixin.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 684f7a8e8..ba1fe4edf 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget.dart index efd1524aa..4803df97e 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../../../model/tokens/day_password_token.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart index bc320c78b..aca4baa46 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 'package:flutter/material.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 e515af2fa..31d690e03 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_slidable/flutter_slidable.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 90b327702..600a50433 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart index 52c3f4d58..c1e1a4532 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 'package:flutter/material.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 631761f65..4c83fc512 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_slidable/flutter_slidable.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 6870a96d3..e8765a15e 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart index fa143587d..acc629314 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../../../model/tokens/hotp_token.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart index e88dd4f8d..163ec6435 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.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 4a5f89794..ff8a4173e 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart index 5ab8bbb48..889233aa5 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:ui'; import 'package:flutter/material.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart index a6a449802..7ca7dd8d4 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.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 f9c28c5e4..e413eeffe 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,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.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 bcc76fa5d..64dde0d11 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,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../../../l10n/app_localizations.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_action.dart index 54272f575..b6c43b71a 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_action.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget.dart index 4a81e4871..12120eb37 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'token_widget_base.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart index d6a51cfb8..0c102968c 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:math'; import 'package:flutter/material.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_builder.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_builder.dart index 2a401d859..6824c2c3c 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_builder.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_builder.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../../model/tokens/day_password_token.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_slideable.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_slideable.dart index a5a548838..fd9e5afba 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_slideable.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_slideable.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_tile.dart index 748ab7c88..06fe9efae 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_tile.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; 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 b023ff736..9f800b669 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget.dart index 20e062e1a..683fd0727 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../../../model/mixins/sortable_mixin.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart index 930630184..027fe28e4 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile_countdown.dart b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile_countdown.dart index 9ee9c47b2..ef1c28775 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile_countdown.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile_countdown.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; class TotpTokenWidgetTileCountdown extends StatefulWidget { diff --git a/lib/views/push_token_view/push_tokens_view.dart b/lib/views/push_token_view/push_tokens_view.dart index 7e07c1c51..fb6e4da89 100644 --- a/lib/views/push_token_view/push_tokens_view.dart +++ b/lib/views/push_token_view/push_tokens_view.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../widgets/push_request_listener.dart'; diff --git a/lib/views/push_token_view/widgets/push_tokens_view_list.dart b/lib/views/push_token_view/widgets/push_tokens_view_list.dart index b68b4afaa..e4de742d4 100644 --- a/lib/views/push_token_view/widgets/push_tokens_view_list.dart +++ b/lib/views/push_token_view/widgets/push_tokens_view_list.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; diff --git a/lib/views/qr_scanner_view/qr_scanner_view.dart b/lib/views/qr_scanner_view/qr_scanner_view.dart index 3f63c4261..087f9e2f4 100644 --- a/lib/views/qr_scanner_view/qr_scanner_view.dart +++ b/lib/views/qr_scanner_view/qr_scanner_view.dart @@ -3,7 +3,7 @@ Authors: Timo Sturm Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH + Copyright (c) 2017-2024 NetKnights GmbH Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. 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 470df7558..1832a781c 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_zxing/flutter_zxing.dart'; diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/export_tokens_to_file_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/export_tokens_to_file_dialog.dart index e799d82e2..2616d7f78 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/export_tokens_to_file_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/export_tokens_to_file_dialog.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:io'; import 'package:flutter/foundation.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 2c2db61b1..44f3ed6a4 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,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../../../l10n/app_localizations.dart'; diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart index eab2692c8..0386ebba1 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart @@ -1,6 +1,25 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/model/riverpod_states/token_state.dart'; +import '../../../../../model/riverpod_states/token_state.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.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 5433f6693..935d21637 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:math'; import 'dart:typed_data'; diff --git a/lib/views/settings_view/settings_groups/settings_group_error_log.dart b/lib/views/settings_view/settings_groups/settings_group_error_log.dart index 9536bb269..8e5e2dba2 100644 --- a/lib/views/settings_view/settings_groups/settings_group_error_log.dart +++ b/lib/views/settings_view/settings_groups/settings_group_error_log.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../l10n/app_localizations.dart'; diff --git a/lib/views/settings_view/settings_groups/settings_group_general.dart b/lib/views/settings_view/settings_groups/settings_group_general.dart index c879ec942..c520fa3e5 100644 --- a/lib/views/settings_view/settings_groups/settings_group_general.dart +++ b/lib/views/settings_view/settings_groups/settings_group_general.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:simple_icons/simple_icons.dart'; diff --git a/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart b/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart index 795b058b3..5f5ecde51 100644 --- a/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart +++ b/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/settings_view/settings_groups/settings_group_language.dart b/lib/views/settings_view/settings_groups/settings_group_language.dart index c848b2500..0c803b56c 100644 --- a/lib/views/settings_view/settings_groups/settings_group_language.dart +++ b/lib/views/settings_view/settings_groups/settings_group_language.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; 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 5a1635526..d86e894c0 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/settings_view/settings_groups/settings_group_theme.dart b/lib/views/settings_view/settings_groups/settings_group_theme.dart index 77d32c44d..38c93f8e7 100644 --- a/lib/views/settings_view/settings_groups/settings_group_theme.dart +++ b/lib/views/settings_view/settings_groups/settings_group_theme.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:easy_dynamic_theme/easy_dynamic_theme.dart'; import 'package:flutter/material.dart'; diff --git a/lib/views/settings_view/settings_view.dart b/lib/views/settings_view/settings_view.dart index 274be7a51..49e87d623 100644 --- a/lib/views/settings_view/settings_view.dart +++ b/lib/views/settings_view/settings_view.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/settings_view/settings_view_widgets/dialogs/ask_log_sended_dialog.dart b/lib/views/settings_view/settings_view_widgets/dialogs/ask_log_sended_dialog.dart index 7dafce8b7..23f8434de 100644 --- a/lib/views/settings_view/settings_view_widgets/dialogs/ask_log_sended_dialog.dart +++ b/lib/views/settings_view/settings_view_widgets/dialogs/ask_log_sended_dialog.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../../l10n/app_localizations.dart'; diff --git a/lib/views/settings_view/settings_view_widgets/errorlog_buttons/delete_errorlog_button.dart b/lib/views/settings_view/settings_view_widgets/errorlog_buttons/delete_errorlog_button.dart index 6ed2e37c2..0ec39bdd3 100644 --- a/lib/views/settings_view/settings_view_widgets/errorlog_buttons/delete_errorlog_button.dart +++ b/lib/views/settings_view/settings_view_widgets/errorlog_buttons/delete_errorlog_button.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../../l10n/app_localizations.dart'; diff --git a/lib/views/settings_view/settings_view_widgets/errorlog_buttons/errorlog_button.dart b/lib/views/settings_view/settings_view_widgets/errorlog_buttons/errorlog_button.dart index 2e42dea67..b107a4425 100644 --- a/lib/views/settings_view/settings_view_widgets/errorlog_buttons/errorlog_button.dart +++ b/lib/views/settings_view/settings_view_widgets/errorlog_buttons/errorlog_button.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; class ErrorlogButton extends StatelessWidget { 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 cea48a1a8..8acfa4c76 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 @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../../l10n/app_localizations.dart'; diff --git a/lib/views/settings_view/settings_view_widgets/errorlog_buttons/show_errorlog_button.dart b/lib/views/settings_view/settings_view_widgets/errorlog_buttons/show_errorlog_button.dart index ffda2881b..8e685a74c 100644 --- a/lib/views/settings_view/settings_view_widgets/errorlog_buttons/show_errorlog_button.dart +++ b/lib/views/settings_view/settings_view_widgets/errorlog_buttons/show_errorlog_button.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import '../../../../l10n/app_localizations.dart'; diff --git a/lib/views/settings_view/settings_view_widgets/logging_menu.dart b/lib/views/settings_view/settings_view_widgets/logging_menu.dart index 6e396a656..e016a3551 100644 --- a/lib/views/settings_view/settings_view_widgets/logging_menu.dart +++ b/lib/views/settings_view/settings_view_widgets/logging_menu.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/settings_view/settings_view_widgets/send_error_dialog.dart b/lib/views/settings_view/settings_view_widgets/send_error_dialog.dart index 4ab545d6a..3f7e510bb 100644 --- a/lib/views/settings_view/settings_view_widgets/send_error_dialog.dart +++ b/lib/views/settings_view/settings_view_widgets/send_error_dialog.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/lib/views/settings_view/settings_view_widgets/settings_groups.dart b/lib/views/settings_view/settings_view_widgets/settings_groups.dart index a44ea81dd..097547f4a 100644 --- a/lib/views/settings_view/settings_view_widgets/settings_groups.dart +++ b/lib/views/settings_view/settings_view_widgets/settings_groups.dart @@ -3,7 +3,7 @@ Authors: Timo Sturm Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH + Copyright (c) 2017-2024 NetKnights GmbH Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. diff --git a/lib/views/settings_view/settings_view_widgets/settings_list_tile_button.dart b/lib/views/settings_view/settings_view_widgets/settings_list_tile_button.dart index 1b46bd830..02a57113f 100644 --- a/lib/views/settings_view/settings_view_widgets/settings_list_tile_button.dart +++ b/lib/views/settings_view/settings_view_widgets/settings_list_tile_button.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; class SettingsListTileButton extends StatelessWidget { diff --git a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart index 44792821e..cb0f38774 100644 --- a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart +++ b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart @@ -3,7 +3,7 @@ Authors: Timo Sturm Frank Merkel - Copyright (c) 2017-2023 NetKnights GmbH + Copyright (c) 2017-2024 NetKnights GmbH Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. diff --git a/lib/views/splash_screen/splash_screen.dart b/lib/views/splash_screen/splash_screen.dart index a29211b30..7093d4420 100644 --- a/lib/views/splash_screen/splash_screen.dart +++ b/lib/views/splash_screen/splash_screen.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/views/view_interface.dart b/lib/views/view_interface.dart index 338b0c476..d0295832f 100644 --- a/lib/views/view_interface.dart +++ b/lib/views/view_interface.dart @@ -1,3 +1,22 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/widgets/app_wrappers/state_observer.dart b/lib/widgets/app_wrappers/state_observer.dart index ce65fce2e..57a5e5365 100644 --- a/lib/widgets/app_wrappers/state_observer.dart +++ b/lib/widgets/app_wrappers/state_observer.dart @@ -1,11 +1,29 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart'; +import '../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart'; import '../../interfaces/riverpod/state_listeners/notifier_provider_listener.dart'; import '../../interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart'; - class StateObserver extends ConsumerWidget { final List stateNotifierProviderListeners; final List asyncNotifierProviderListeners; diff --git a/lib/widgets/default_refresh_indicator.dart b/lib/widgets/default_refresh_indicator.dart index 931bcbbe4..be63b0a70 100644 --- a/lib/widgets/default_refresh_indicator.dart +++ b/lib/widgets/default_refresh_indicator.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; -import 'package:privacyidea_authenticator/widgets/deactivateable_refresh_indicator.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; +import 'deactivateable_refresh_indicator.dart'; import '../utils/logger.dart'; import '../utils/push_provider.dart'; From bafce4ec4be6a4b6076d47f4aa537538860ea66a Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:08:04 +0200 Subject: [PATCH 019/285] refactoring --- integration_test/add_tokens_test.dart | 5 +- integration_test/copy_to_clipboard_test.dart | 5 +- integration_test/rename_and_delete_test.dart | 5 +- integration_test/two_step_rollout_test.dart | 5 +- integration_test/views_test.dart | 5 +- .../enums/introduction_extension.dart | 19 -- .../container_credentials_processor.dart | 2 +- ...nofitier.dart => credential_notifier.dart} | 2 +- ...tier.g.dart => credential_notifier.g.dart} | 2 +- .../introduction_provider.dart | 80 ++++++++ .../introduction_provider.g.dart | 175 ++++++++++++++++++ .../introduction_provider.dart | 32 ---- .../token_container_token_state_listener.dart | 2 +- .../completed_introduction_notifier.dart | 150 +++++++-------- .../add_token_folder_dialog.dart | 9 +- .../main_view_navigation_bar.dart | 32 +++- .../license_push_view_button.dart | 10 +- .../edit_day_password_token_action.dart | 11 +- .../default_edit_action.dart | 10 +- .../default_lock_action.dart | 11 +- .../actions/edit_hotp_token_action.dart | 10 +- .../actions/edit_push_token_action.dart | 10 +- .../push_token_widget_tile.dart | 10 +- .../actions/edit_totp_token_action.dart | 11 +- .../settings_group_import_export_tokens.dart | 7 +- lib/views/splash_screen/splash_screen.dart | 5 +- lib/widgets/app_wrapper.dart | 8 +- lib/widgets/default_refresh_indicator.dart | 2 +- .../token_introduction.dart | 51 ++--- 29 files changed, 468 insertions(+), 218 deletions(-) rename lib/utils/riverpod/riverpod_providers/generated_providers/{credential_nofitier.dart => credential_notifier.dart} (98%) rename lib/utils/riverpod/riverpod_providers/generated_providers/{credential_nofitier.g.dart => credential_notifier.g.dart} (96%) create mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart delete mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart diff --git a/integration_test/add_tokens_test.dart b/integration_test/add_tokens_test.dart index 30511a9e1..c4a4a62d3 100644 --- a/integration_test/add_tokens_test.dart +++ b/integration_test/add_tokens_test.dart @@ -12,12 +12,11 @@ import 'package:privacyidea_authenticator/model/riverpod_states/introduction_sta import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/model/version.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/completed_introduction_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; @@ -79,7 +78,7 @@ void main() { settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepository)), tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), - introductionProvider.overrideWith((ref) => IntroductionNotifier(repository: mockIntroductionRepository)), + introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), )); diff --git a/integration_test/copy_to_clipboard_test.dart b/integration_test/copy_to_clipboard_test.dart index ca824d085..5068dbe67 100644 --- a/integration_test/copy_to_clipboard_test.dart +++ b/integration_test/copy_to_clipboard_test.dart @@ -9,13 +9,12 @@ import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/completed_introduction_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/model/version.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; @@ -53,7 +52,7 @@ void main() { settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepository)), tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), - introductionProvider.overrideWith((ref) => IntroductionNotifier(repository: mockIntroductionRepository)), + introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), )); diff --git a/integration_test/rename_and_delete_test.dart b/integration_test/rename_and_delete_test.dart index c3b73aabb..7e20f603e 100644 --- a/integration_test/rename_and_delete_test.dart +++ b/integration_test/rename_and_delete_test.dart @@ -10,13 +10,12 @@ import 'package:privacyidea_authenticator/model/riverpod_states/introduction_sta import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/completed_introduction_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/model/version.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; @@ -68,7 +67,7 @@ void main() { settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepository)), tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), - introductionProvider.overrideWith((ref) => IntroductionNotifier(repository: mockIntroductionRepository)), + introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), )); diff --git a/integration_test/two_step_rollout_test.dart b/integration_test/two_step_rollout_test.dart index e1762ec98..c6a6e2aef 100644 --- a/integration_test/two_step_rollout_test.dart +++ b/integration_test/two_step_rollout_test.dart @@ -7,7 +7,6 @@ import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/completed_introduction_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; @@ -15,7 +14,7 @@ import 'package:privacyidea_authenticator/utils/customization/application_custom import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/model/version.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; @@ -57,7 +56,7 @@ void main() { settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepository)), tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), - introductionProvider.overrideWith((ref) => IntroductionNotifier(repository: mockIntroductionRepository)), + introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), )); diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index a101a3849..3b7ec61aa 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -10,7 +10,6 @@ import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/completed_introduction_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/push_request_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; @@ -18,7 +17,7 @@ import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_n import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/push_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; @@ -103,7 +102,7 @@ void main() { ), ), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), - introductionProvider.overrideWith((ref) => IntroductionNotifier(repository: mockIntroductionRepository)), + introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), )); diff --git a/lib/model/extensions/enums/introduction_extension.dart b/lib/model/extensions/enums/introduction_extension.dart index 714b21f82..5aa1c038e 100644 --- a/lib/model/extensions/enums/introduction_extension.dart +++ b/lib/model/extensions/enums/introduction_extension.dart @@ -1,22 +1,3 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; diff --git a/lib/processors/scheme_processors/container_credentials_processor.dart b/lib/processors/scheme_processors/container_credentials_processor.dart index 509ae657e..166449b82 100644 --- a/lib/processors/scheme_processors/container_credentials_processor.dart +++ b/lib/processors/scheme_processors/container_credentials_processor.dart @@ -22,7 +22,7 @@ import '../../utils/globals.dart'; import '../../utils/logger.dart'; import '../../model/tokens/container_credentials.dart'; -import '../../utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; class ContainerCredentialsProcessor extends SchemeProcessor { @override diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart similarity index 98% rename from lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart index 189a7906f..4b60bf66e 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart @@ -26,7 +26,7 @@ import '../../../../model/tokens/container_credentials.dart'; import '../../../../repo/secure_container_credentials_repository.dart'; import '../../../logger.dart'; -part 'credential_nofitier.g.dart'; +part 'credential_notifier.g.dart'; @Riverpod(keepAlive: true) class CredentialsNotifier extends _$CredentialsNotifier { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart similarity index 96% rename from lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.g.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart index 18f5e8ef7..423e3cd42 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'credential_nofitier.dart'; +part of 'credential_notifier.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart new file mode 100644 index 000000000..d9c33b1ec --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart @@ -0,0 +1,80 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../../interfaces/repo/introduction_repository.dart'; +import '../../../../model/enums/introduction.dart'; +import '../../../../model/riverpod_states/introduction_state.dart'; +import '../../../../repo/preference_introduction_repository.dart'; +import '../../../logger.dart'; + +part 'introduction_provider.g.dart'; + +final introductionNotifierProvider = introductionNotifierProviderOf(repo: PreferenceIntroductionRepository()); + +@Riverpod(keepAlive: true) +class IntroductionNotifier extends _$IntroductionNotifier { + final IntroductionRepository? _repositoryOverride; + late final IntroductionRepository _repo; + IntroductionNotifier({IntroductionRepository? repoOverride}) + : _repositoryOverride = repoOverride, + super(); + + @override + Future build({required IntroductionRepository repo}) async { + Logger.info('New IntroductionNotifier created', name: 'introduction_provider.dart#build'); + _repo = _repositoryOverride ?? repo; + return await _loadFromRepo(); + } + + Future _loadFromRepo() async { + final newState = await _repo.loadCompletedIntroductions(); + + Logger.info('Loading completed introductions from repo: $newState', name: 'settings_notifier.dart#_loadFromRepo'); + return newState; + } + + Future _saveToRepo(IntroductionState state) async { + final success = await _repo.saveCompletedIntroductions(state); + if (success) { + Logger.info('Saving completed introductions to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); + } else { + Logger.warning('Failed to save completed introductions to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); + } + } + + Future complete(Introduction introduction) async { + final newState = (await future).withCompletedIntroduction(introduction); + await _saveToRepo(newState); + state = AsyncValue.data(newState); + } + + Future uncomplete(Introduction introduction) async { + final newState = (await future).withoutCompletedIntroduction(introduction); + await _saveToRepo(newState); + state = AsyncValue.data(newState); + } + + Future completeAll() async { + final newState = (await future).withAllCompleted(); + await _saveToRepo(newState); + state = AsyncValue.data(newState); + } +} diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart new file mode 100644 index 000000000..4bceb9511 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart @@ -0,0 +1,175 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'introduction_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$introductionNotifierHash() => + r'6e8e9a8c6335ca86e2afe6d34ca159c6717388e5'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$IntroductionNotifier + extends BuildlessAsyncNotifier { + late final IntroductionRepository repo; + + FutureOr build({ + required IntroductionRepository repo, + }); +} + +/// See also [IntroductionNotifier]. +@ProviderFor(IntroductionNotifier) +const introductionNotifierProviderOf = IntroductionNotifierFamily(); + +/// See also [IntroductionNotifier]. +class IntroductionNotifierFamily extends Family> { + /// See also [IntroductionNotifier]. + const IntroductionNotifierFamily(); + + /// See also [IntroductionNotifier]. + IntroductionNotifierProvider call({ + required IntroductionRepository repo, + }) { + return IntroductionNotifierProvider( + repo: repo, + ); + } + + @override + IntroductionNotifierProvider getProviderOverride( + covariant IntroductionNotifierProvider provider, + ) { + return call( + repo: provider.repo, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'introductionNotifierProviderOf'; +} + +/// See also [IntroductionNotifier]. +class IntroductionNotifierProvider + extends AsyncNotifierProviderImpl { + /// See also [IntroductionNotifier]. + IntroductionNotifierProvider({ + required IntroductionRepository repo, + }) : this._internal( + () => IntroductionNotifier()..repo = repo, + from: introductionNotifierProviderOf, + name: r'introductionNotifierProviderOf', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$introductionNotifierHash, + dependencies: IntroductionNotifierFamily._dependencies, + allTransitiveDependencies: + IntroductionNotifierFamily._allTransitiveDependencies, + repo: repo, + ); + + IntroductionNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.repo, + }) : super.internal(); + + final IntroductionRepository repo; + + @override + FutureOr runNotifierBuild( + covariant IntroductionNotifier notifier, + ) { + return notifier.build( + repo: repo, + ); + } + + @override + Override overrideWith(IntroductionNotifier Function() create) { + return ProviderOverride( + origin: this, + override: IntroductionNotifierProvider._internal( + () => create()..repo = repo, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + repo: repo, + ), + ); + } + + @override + AsyncNotifierProviderElement + createElement() { + return _IntroductionNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is IntroductionNotifierProvider && other.repo == repo; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, repo.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin IntroductionNotifierRef on AsyncNotifierProviderRef { + /// The parameter `repo` of this provider. + IntroductionRepository get repo; +} + +class _IntroductionNotifierProviderElement extends AsyncNotifierProviderElement< + IntroductionNotifier, IntroductionState> with IntroductionNotifierRef { + _IntroductionNotifierProviderElement(super.provider); + + @override + IntroductionRepository get repo => + (origin as IntroductionNotifierProvider).repo; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart deleted file mode 100644 index 5733d0f0b..000000000 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart +++ /dev/null @@ -1,32 +0,0 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; - -import '../../../../model/riverpod_states/introduction_state.dart'; -import '../../../../repo/preference_introduction_repository.dart'; -import '../../state_notifiers/completed_introduction_notifier.dart'; -import '../../../logger.dart'; - -final introductionProvider = StateNotifierProvider( - (ref) { - Logger.info("New introductionProvider created", name: 'introductionProvider'); - return IntroductionNotifier(repository: PreferenceIntroductionRepository()); - }, -); diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index 6f826e642..f95abf188 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -23,7 +23,7 @@ import '../../logger.dart'; import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; import '../../../model/riverpod_states/token_state.dart'; -import '../riverpod_providers/generated_providers/credential_nofitier.dart'; +import '../riverpod_providers/generated_providers/credential_notifier.dart'; import '../riverpod_providers/generated_providers/token_container_notifier.dart'; class ContainerListensToTokenState extends TokenStateListener { diff --git a/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart b/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart index cfd559f0c..98f93af38 100644 --- a/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart @@ -1,85 +1,85 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; -import '../../../interfaces/repo/introduction_repository.dart'; -import '../../../model/enums/introduction.dart'; -import '../../../model/riverpod_states/introduction_state.dart'; -import '../../logger.dart'; +// import '../../../interfaces/repo/introduction_repository.dart'; +// import '../../../model/enums/introduction.dart'; +// import '../../../model/riverpod_states/introduction_state.dart'; +// import '../../logger.dart'; -class IntroductionNotifier extends StateNotifier { - late Future loadingRepo; +// class IntroductionNotifier extends StateNotifier { +// late Future loadingRepo; - final IntroductionRepository _repo; - IntroductionNotifier({required IntroductionRepository repository}) - : _repo = repository, - super(const IntroductionState()) { - _init(); - } +// final IntroductionRepository _repo; +// IntroductionNotifier({required IntroductionRepository repository}) +// : _repo = repository, +// super(const IntroductionState()) { +// _init(); +// } - Future _init() async { - loadingRepo = Future(() async { - await loadFromRepo(); - return state; - }); - await loadingRepo; - } +// Future _init() async { +// loadingRepo = Future(() async { +// await loadFromRepo(); +// return state; +// }); +// await loadingRepo; +// } - Future loadFromRepo() async { - loadingRepo = Future(() async { - final newState = await _repo.loadCompletedIntroductions(); - state = newState; - Logger.info('Loading completed introductions from repo: $state', name: 'settings_notifier.dart#_loadFromRepo'); - return newState; - }); - await loadingRepo; - } +// Future loadFromRepo() async { +// loadingRepo = Future(() async { +// final newState = await _repo.loadCompletedIntroductions(); +// state = newState; +// Logger.info('Loading completed introductions from repo: $state', name: 'settings_notifier.dart#_loadFromRepo'); +// return newState; +// }); +// await loadingRepo; +// } - Future _saveToRepo() async { - loadingRepo = Future(() async { - final success = await _repo.saveCompletedIntroductions(state); - if (success) { - Logger.info('Saving completed introductions to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); - } else { - Logger.warning('Failed to save completed introductions to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); - } - return state; - }); - await loadingRepo; - } +// Future _saveToRepo() async { +// loadingRepo = Future(() async { +// final success = await _repo.saveCompletedIntroductions(state); +// if (success) { +// Logger.info('Saving completed introductions to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); +// } else { +// Logger.warning('Failed to save completed introductions to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); +// } +// return state; +// }); +// await loadingRepo; +// } - Future complete(Introduction introduction) async { - state = state.withCompletedIntroduction(introduction); - await _saveToRepo(); - } +// Future complete(Introduction introduction) async { +// state = state.withCompletedIntroduction(introduction); +// await _saveToRepo(); +// } - Future uncomplete(Introduction introduction) async { - state = state.withoutCompletedIntroduction(introduction); - await _saveToRepo(); - } +// Future uncomplete(Introduction introduction) async { +// state = state.withoutCompletedIntroduction(introduction); +// await _saveToRepo(); +// } - bool isCompleted(Introduction introduction) => state.isCompleted(introduction); - bool isUncompleted(Introduction introduction) => state.isUncompleted(introduction); +// bool isCompleted(Introduction introduction) => state.isCompleted(introduction); +// bool isUncompleted(Introduction introduction) => state.isUncompleted(introduction); - Future completeAll() async { - state = state.withAllCompleted(); - await _saveToRepo(); - } -} +// Future completeAll() async { +// state = state.withAllCompleted(); +// await _saveToRepo(); +// } +// } diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart b/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart index 7f9832490..900fd7a05 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart @@ -22,7 +22,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../model/enums/introduction.dart'; -import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import '../../../../widgets/dialog_widgets/default_dialog.dart'; @@ -63,11 +63,12 @@ class AddTokenFolderDialog extends ConsumerWidget { overflow: TextOverflow.fade, softWrap: false, ), - onPressed: () { - if (ref.read(introductionProvider).isCompleted(Introduction.addFolder) == false) { - ref.read(introductionProvider.notifier).complete(Introduction.addFolder); + onPressed: () async { + if ((await ref.read(introductionNotifierProvider.future)).isCompleted(Introduction.addFolder) == false) { + ref.read(introductionNotifierProvider.notifier).complete(Introduction.addFolder); } ref.read(tokenFolderProvider.notifier).addNewFolder(textController.text); + if (!context.mounted) return; Navigator.pop(context); }), ], diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart index dd5bac668..ec296a3e7 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart @@ -19,10 +19,11 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/introduction.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; import '../../../widgets/focused_item_as_overlay.dart'; import '../../add_token_manually_view/add_token_manually_view.dart'; @@ -64,9 +65,13 @@ class MainViewNavigationBar extends ConsumerWidget { heightFactor: 0.6, child: FocusedItemAsOverlay( onComplete: () { - ref.read(introductionProvider.notifier).complete(Introduction.scanQrCode); + ref.read(introductionNotifierProvider.notifier).complete(Introduction.scanQrCode); }, - isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.scanQrCode), + isFocused: ref.watch(introductionNotifierProvider).when( + data: (data) => data.isConditionFulfilled(ref, Introduction.scanQrCode), + error: (_, __) => false, + loading: () => false, + ), tooltipWhenFocused: AppLocalizations.of(context)!.introScanQrCode, child: const QrScannerButton()), ), @@ -93,10 +98,12 @@ class MainViewNavigationBar extends ConsumerWidget { Navigator.pushNamed(context, AddTokenManuallyView.routeName); }, icon: FocusedItemAsOverlay( - onComplete: () { - ref.read(introductionProvider.notifier).complete(Introduction.addManually); - }, - isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.addManually), + onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.addManually), + isFocused: ref.watch(introductionNotifierProvider).when( + data: (data) => data.isConditionFulfilled(ref, Introduction.addManually), + error: (_, __) => false, + loading: () => false, + ), tooltipWhenFocused: AppLocalizations.of(context)!.introAddTokenManually, child: FittedBox( child: Icon( @@ -115,9 +122,16 @@ class MainViewNavigationBar extends ConsumerWidget { child: Padding( padding: EdgeInsets.only(top: navHeight * 0.1, bottom: navHeight * 0.2), child: FocusedItemAsOverlay( - isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.addFolder), + isFocused: ref.watch(introductionNotifierProvider).when( + data: (data) { + Logger.info('IntroductionNotifier isConditionFulfilled: ${data.isConditionFulfilled(ref, Introduction.addFolder)}'); + return data.isConditionFulfilled(ref, Introduction.addFolder); + }, + error: (_, __) => false, + loading: () => false, + ), tooltipWhenFocused: AppLocalizations.of(context)!.introAddFolder, - onComplete: () => ref.read(introductionProvider.notifier).complete(Introduction.addFolder), + onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.addFolder), child: AppBarItem( tooltip: AppLocalizations.of(context)!.addFolder, onPressed: () { diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart index 924329a30..5de50dbd4 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart @@ -22,7 +22,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../model/enums/introduction.dart'; -import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import '../../../../widgets/focused_item_as_overlay.dart'; import '../../../license_view/license_view.dart'; @@ -37,9 +37,13 @@ class LicensePushViewButton extends ConsumerWidget { final hidePushTokens = ref.watch(settingsProvider).hidePushTokens; return hidePushTokens ? FocusedItemAsOverlay( - isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.hidePushTokens), + isFocused: ref.watch(introductionNotifierProvider).when( + data: (value) => value.isConditionFulfilled(ref, Introduction.hidePushTokens), + error: (Object error, StackTrace stackTrace) => false, + loading: () => false, + ), tooltipWhenFocused: AppLocalizations.of(context)!.introHidePushTokens, - onComplete: () => ref.read(introductionProvider.notifier).complete(Introduction.hidePushTokens), + onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.hidePushTokens), child: AppBarItem( tooltip: AppLocalizations.of(context)!.pushTokens, onPressed: () => Navigator.pushNamed(context, PushTokensView.routeName), 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 ba1fe4edf..7f46a530b 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 @@ -19,6 +19,7 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../../../../l10n/app_localizations.dart'; import '../../../../../../model/enums/introduction.dart'; @@ -26,7 +27,7 @@ import '../../../../../../model/tokens/day_password_token.dart'; import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; -import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../default_token_actions/default_edit_action_dialog.dart'; import '../../token_action.dart'; @@ -53,8 +54,12 @@ class EditDayPassowrdTokenAction extends TokenAction { tooltipWhenFocused: AppLocalizations.of(context)!.introEditToken, childIsMoving: true, alignment: Alignment.bottomCenter, - isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.editToken), - onComplete: () => ref.read(introductionProvider.notifier).complete(Introduction.editToken), + isFocused: ref.watch(introductionNotifierProvider).when( + data: (value) => value.isConditionFulfilled(ref, Introduction.editToken), + error: (Object error, StackTrace stackTrace) => false, + loading: () => false, + ), + onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.editToken), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, 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 600a50433..69c3b2c9d 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 @@ -28,7 +28,7 @@ import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../../widgets/focused_item_as_overlay.dart'; import '../token_action.dart'; @@ -52,8 +52,12 @@ class DefaultEditAction extends TokenAction { child: FocusedItemAsOverlay( tooltipWhenFocused: AppLocalizations.of(context)!.editToken, childIsMoving: true, - isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.editToken), - onComplete: () => ref.read(introductionProvider.notifier).complete(Introduction.editToken), + isFocused: ref.watch(introductionNotifierProvider).when( + data: (value) => value.isConditionFulfilled(ref, Introduction.editToken), + error: (Object error, StackTrace stackTrace) => false, + loading: () => false, + ), + onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.editToken), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, 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 4c83fc512..fc3726335 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 @@ -18,6 +18,7 @@ * limitations under the License. */ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../l10n/app_localizations.dart'; @@ -27,7 +28,7 @@ import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../../widgets/focused_item_as_overlay.dart'; import '../token_action.dart'; @@ -52,8 +53,12 @@ class DefaultLockAction extends TokenAction { tooltipWhenFocused: AppLocalizations.of(context)!.introLockToken, childIsMoving: true, alignment: Alignment.bottomCenter, - isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.lockToken), - onComplete: () => ref.read(introductionProvider.notifier).complete(Introduction.lockToken), + isFocused: ref.watch(introductionNotifierProvider).when( + data: (value) => value.isConditionFulfilled(ref, Introduction.lockToken), + error: (Object error, StackTrace stackTrace) => false, + loading: () => false, + ), + onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.lockToken), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, 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 e8765a15e..b7cd38a1b 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 @@ -27,7 +27,7 @@ import '../../../../../../model/tokens/hotp_token.dart'; import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; -import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../default_token_actions/default_edit_action_dialog.dart'; import '../../token_action.dart'; @@ -54,8 +54,12 @@ class EditHOTPTokenAction extends TokenAction { tooltipWhenFocused: AppLocalizations.of(context)!.introEditToken, childIsMoving: true, alignment: Alignment.bottomCenter, - isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.editToken), - onComplete: () => ref.read(introductionProvider.notifier).complete(Introduction.editToken), + isFocused: ref.watch(introductionNotifierProvider).when( + data: (value) => value.isConditionFulfilled(ref, Introduction.editToken), + error: (Object error, StackTrace stackTrace) => false, + loading: () => false, + ), + onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.editToken), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, 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 ff8a4173e..597af44bb 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 @@ -27,7 +27,7 @@ import '../../../../../../model/tokens/push_token.dart'; import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; -import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../../../widgets/enable_text_edit_after_many_taps.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; @@ -58,8 +58,12 @@ class EditPushTokenAction extends TokenAction { tooltipWhenFocused: appLocalizations.introEditToken, childIsMoving: true, alignment: Alignment.bottomCenter, - isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.editToken), - onComplete: () => ref.read(introductionProvider.notifier).complete(Introduction.editToken), + isFocused: ref.watch(introductionNotifierProvider).when( + data: (value) => value.isConditionFulfilled(ref, Introduction.editToken), + error: (Object error, StackTrace stackTrace) => false, + loading: () => false, + ), + onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.editToken), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart index 7ca7dd8d4..23f5aaff6 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart @@ -23,7 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/enums/introduction.dart'; import '../../../../../model/tokens/push_token.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../../widgets/custom_trailing.dart'; import '../../../../../widgets/focused_item_as_overlay.dart'; import '../token_widget_tile.dart'; @@ -52,9 +52,13 @@ class PushTokenWidgetTile extends ConsumerWidget { trailing: FocusedItemAsOverlay( tooltipWhenFocused: AppLocalizations.of(context)!.introPollForChallenges, alignment: Alignment.centerLeft, - isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.pollForChallenges), + isFocused: ref.watch(introductionNotifierProvider).when( + data: (value) => value.isConditionFulfilled(ref, Introduction.pollForChallenges), + error: (Object error, StackTrace stackTrace) => false, + loading: () => false, + ), onComplete: () { - ref.read(introductionProvider.notifier).complete(Introduction.pollForChallenges); + ref.read(introductionNotifierProvider.notifier).complete(Introduction.pollForChallenges); }, child: const CustomTrailing( child: FittedBox( 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 9f800b669..580661263 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 @@ -19,6 +19,7 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../../../../l10n/app_localizations.dart'; import '../../../../../../model/enums/introduction.dart'; @@ -26,7 +27,7 @@ import '../../../../../../model/tokens/totp_token.dart'; import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; -import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../default_token_actions/default_edit_action_dialog.dart'; import '../../token_action.dart'; @@ -53,8 +54,12 @@ class EditTOTPTokenAction extends TokenAction { tooltipWhenFocused: AppLocalizations.of(context)!.introEditToken, childIsMoving: true, alignment: Alignment.bottomCenter, - isFocused: ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.editToken), - onComplete: () => ref.read(introductionProvider.notifier).complete(Introduction.editToken), + isFocused: ref.watch(introductionNotifierProvider).when( + data: (value) => value.isConditionFulfilled(ref, Introduction.editToken), + error: (Object error, StackTrace stackTrace) => false, + loading: () => false, + ), + onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.editToken), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart b/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart index 5f5ecde51..a2b322a97 100644 --- a/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart +++ b/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart @@ -23,7 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/introduction.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../widgets/countdown_button.dart'; import '../../../widgets/dialog_widgets/default_dialog.dart'; import '../../import_tokens_view/import_tokens_view.dart'; @@ -76,7 +76,8 @@ class _SettingsGroupImportExportTokensState extends ConsumerState( useRootNavigator: false, context: context, @@ -96,7 +97,7 @@ class _SettingsGroupImportExportTokensState extends ConsumerState( useRootNavigator: false, diff --git a/lib/views/splash_screen/splash_screen.dart b/lib/views/splash_screen/splash_screen.dart index 7093d4420..412c20e71 100644 --- a/lib/views/splash_screen/splash_screen.dart +++ b/lib/views/splash_screen/splash_screen.dart @@ -25,7 +25,7 @@ import '../../utils/app_info_utils.dart'; import '../../utils/customization/application_customization.dart'; import '../../utils/home_widget_utils.dart'; import '../../utils/logger.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; @@ -52,7 +52,7 @@ class _SplashScreenState extends ConsumerState { _customization = widget.customization; WidgetsBinding.instance.addPostFrameCallback((_) { if (_customization.disabledFeatures.contains(AppFeature.introductions)) { - ref.read(introductionProvider.notifier).completeAll(); + ref.read(introductionNotifierProvider.notifier).completeAll(); } }); @@ -65,7 +65,6 @@ class _SplashScreenState extends ConsumerState { Future.delayed(_splashScreenDuration), ref.read(settingsProvider.notifier).loadingRepo, ref.read(tokenProvider.notifier).initState, - ref.read(introductionProvider.notifier).loadingRepo, ref.read(tokenFolderProvider.notifier).initState, AppInfoUtils.init(), HomeWidgetUtils().homeWidgetInit(), diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index 81970aaef..a4c0ce9a5 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -10,7 +10,7 @@ import '../interfaces/riverpod/state_listeners/notifier_provider_listener.dart'; import '../model/token_container.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; -import '../utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import '../utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart'; @@ -94,7 +94,6 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { return SingleTouchRecognizer( child: StateObserver( stateNotifierProviderListeners: [ - HomeWidgetTokenStateListener(tokenProvider: tokenProvider), ContainerListensToTokenState(tokenProvider: tokenProvider, ref: ref), ], @@ -107,7 +106,7 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { (credential) { return TokenStateListensToContainer( containerProvider: tokenContainerNotifierProviderOf(credential: credential), - ref: ref, + ref: ref, ); }, ), @@ -120,8 +119,6 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { } } - - class TokenStateListensToContainer extends AsyncContainerListener { final WidgetRef ref; TokenStateListensToContainer({ @@ -138,7 +135,6 @@ class TokenStateListensToContainer extends AsyncContainerListener { } } - abstract class AsyncContainerListener extends AsyncNotifierProviderListener { const AsyncContainerListener({ required TokenContainerNotifierProvider containerProvider, diff --git a/lib/widgets/default_refresh_indicator.dart b/lib/widgets/default_refresh_indicator.dart index be63b0a70..b2c59d09d 100644 --- a/lib/widgets/default_refresh_indicator.dart +++ b/lib/widgets/default_refresh_indicator.dart @@ -5,7 +5,7 @@ import 'deactivateable_refresh_indicator.dart'; import '../utils/logger.dart'; import '../utils/push_provider.dart'; -import '../utils/riverpod/riverpod_providers/generated_providers/credential_nofitier.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; import '../views/main_view/main_view_widgets/loading_indicator.dart'; class DefaultRefreshIndicator extends ConsumerStatefulWidget { diff --git a/lib/widgets/introduction_widgets/token_introduction.dart b/lib/widgets/introduction_widgets/token_introduction.dart index f63385d30..e805ba30a 100644 --- a/lib/widgets/introduction_widgets/token_introduction.dart +++ b/lib/widgets/introduction_widgets/token_introduction.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../l10n/app_localizations.dart'; import '../../model/enums/introduction.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/introduction_provider.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../focused_item_as_overlay.dart'; class TokenIntroduction extends ConsumerWidget { @@ -11,27 +11,32 @@ class TokenIntroduction extends ConsumerWidget { const TokenIntroduction({required this.child, super.key}); @override - Widget build(context, ref) { - if (ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.tokenSwipe)) { - return FocusedItemAsOverlay( - isFocused: true, - tooltipWhenFocused: AppLocalizations.of(context)!.introTokenSwipe, - alignment: Alignment.bottomCenter, - onComplete: () => ref.read(introductionProvider.notifier).complete(Introduction.tokenSwipe), - overlayChild: Column(children: [child]), - child: child, - ); - } - if (ref.watch(introductionProvider).isConditionFulfilled(ref, Introduction.dragToken)) { - return FocusedItemAsOverlay( - isFocused: true, - tooltipWhenFocused: AppLocalizations.of(context)!.introDragToken, - alignment: Alignment.bottomCenter, - onComplete: () => ref.read(introductionProvider.notifier).complete(Introduction.dragToken), - overlayChild: Column(children: [child]), - child: child, + Widget build(context, ref) => ref.watch(introductionNotifierProvider).when( + data: (value) { + if (value.isConditionFulfilled(ref, Introduction.tokenSwipe)) { + return FocusedItemAsOverlay( + isFocused: true, + tooltipWhenFocused: AppLocalizations.of(context)!.introTokenSwipe, + alignment: Alignment.bottomCenter, + onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.tokenSwipe), + overlayChild: Column(children: [child]), + child: child, + ); + } + + if (value.isConditionFulfilled(ref, Introduction.dragToken)) { + return FocusedItemAsOverlay( + isFocused: true, + tooltipWhenFocused: AppLocalizations.of(context)!.introDragToken, + alignment: Alignment.bottomCenter, + onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.dragToken), + overlayChild: Column(children: [child]), + child: child, + ); + } + return child; + }, + error: (error, stackTrace) => child, + loading: () => child, ); - } - return child; - } } From dbc6ae18f1d3b6f5a555b00811ac9c7c62a30e50 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:25:27 +0200 Subject: [PATCH 020/285] refactoring --- integration_test/add_tokens_test.dart | 3 +- integration_test/copy_to_clipboard_test.dart | 3 +- integration_test/rename_and_delete_test.dart | 3 +- integration_test/two_step_rollout_test.dart | 3 +- integration_test/views_test.dart | 44 +- .../repo/push_request_repository.dart | 4 +- lib/mains/main_customizer.dart | 4 +- lib/mains/main_netknights.dart | 4 +- .../enums/introduction_extension.dart | 5 +- lib/model/riverpod_states/settings_state.dart | 40 +- lib/repo/secure_push_request_repository.dart | 4 +- lib/utils/logger.dart | 6 +- lib/utils/push_provider.dart | 5 +- .../introduction_provider.dart | 6 +- .../introduction_provider.g.dart | 2 +- .../push_request_provider.dart | 556 ++++++++++++ .../push_request_provider.g.dart | 241 ++++++ .../push_request_provider.dart | 63 -- .../settings_provider.dart | 164 +++- .../settings_provider.g.dart | 174 ++++ .../push_request_notifier.dart | 798 +++++++++--------- .../state_notifiers/settings_notifier.dart | 310 +++---- .../state_notifiers/token_notifier.dart | 2 +- lib/views/main_view/main_view.dart | 2 +- .../token_folder_expandable.dart | 4 +- .../main_view_navigation_bar.dart | 23 +- .../license_push_view_button.dart | 45 +- .../main_view_tokens_list.dart | 4 +- .../day_password_token_widget_tile.dart | 3 +- .../settings_group_language.dart | 14 +- .../settings_group_push_token.dart | 170 ++-- .../settings_view_widgets/logging_menu.dart | 82 +- lib/views/splash_screen/splash_screen.dart | 12 +- lib/widgets/app_wrapper.dart | 2 +- .../dialog_widgets/push_request_dialog.dart | 2 +- lib/widgets/push_request_listener.dart | 14 +- test/tests_app_wrapper.mocks.dart | 16 +- .../push_request_notifier_test.dart | 86 +- .../settings_notifier_test.dart | 70 +- .../sortable_notifier_test.dart | 3 +- .../state_notifiers/token_notifier_test.dart | 42 +- 41 files changed, 2032 insertions(+), 1006 deletions(-) create mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart delete mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.g.dart diff --git a/integration_test/add_tokens_test.dart b/integration_test/add_tokens_test.dart index c4a4a62d3..c09fdefa4 100644 --- a/integration_test/add_tokens_test.dart +++ b/integration_test/add_tokens_test.dart @@ -12,7 +12,6 @@ import 'package:privacyidea_authenticator/model/riverpod_states/introduction_sta import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/model/version.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; @@ -75,7 +74,7 @@ void main() { (tester) async { await tester.pumpWidget(TestsAppWrapper( overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepository)), + settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepository)), tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), diff --git a/integration_test/copy_to_clipboard_test.dart b/integration_test/copy_to_clipboard_test.dart index 5068dbe67..9ec907a1d 100644 --- a/integration_test/copy_to_clipboard_test.dart +++ b/integration_test/copy_to_clipboard_test.dart @@ -9,7 +9,6 @@ import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; @@ -49,7 +48,7 @@ void main() { testWidgets('Copy to Clipboard Test', (tester) async { await tester.pumpWidget(TestsAppWrapper( overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepository)), + settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepository)), tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), diff --git a/integration_test/rename_and_delete_test.dart b/integration_test/rename_and_delete_test.dart index 7e20f603e..5b11f9f59 100644 --- a/integration_test/rename_and_delete_test.dart +++ b/integration_test/rename_and_delete_test.dart @@ -10,7 +10,6 @@ import 'package:privacyidea_authenticator/model/riverpod_states/introduction_sta import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; @@ -64,7 +63,7 @@ void main() { testWidgets('Rename and Delete Token', (tester) async { await tester.pumpWidget(TestsAppWrapper( overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepository)), + settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepository)), tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), diff --git a/integration_test/two_step_rollout_test.dart b/integration_test/two_step_rollout_test.dart index c6a6e2aef..f2bb801fc 100644 --- a/integration_test/two_step_rollout_test.dart +++ b/integration_test/two_step_rollout_test.dart @@ -7,7 +7,6 @@ import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; @@ -53,7 +52,7 @@ void main() { (tester) async { await tester.pumpWidget(TestsAppWrapper( overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepository)), + settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepository)), tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index 3b7ec61aa..5366ba1a4 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -7,18 +7,19 @@ import 'package:pointycastle/asymmetric/api.dart'; import 'package:privacyidea_authenticator/l10n/app_localizations_en.dart'; import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; +import 'package:privacyidea_authenticator/model/push_request.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/push_request_state.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/push_request_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; +import 'package:privacyidea_authenticator/utils/custom_int_buffer.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/push_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; @@ -38,11 +39,13 @@ void main() { late final MockFirebaseUtils mockFirebaseUtils; late final MockPrivacyideaIOClient mockIOClient; late final MockIntroductionRepository mockIntroductionRepository; + late final MockPushRequestRepository mockPushRequestRepository; setUp(() { mockSettingsRepository = MockSettingsRepository(); when(mockSettingsRepository.loadSettings()).thenAnswer((_) async => SettingsState(isFirstRun: false, useSystemLocale: false, localePreference: const Locale('en'), latestStartedVersion: Version.parse('999.999.999'))); when(mockSettingsRepository.saveSettings(any)).thenAnswer((_) async => true); + mockTokenRepository = MockTokenRepository(); var tokens = []; when(mockTokenRepository.loadTokens()).thenAnswer((_) async => tokens); @@ -57,6 +60,7 @@ void main() { tokens.removeWhere((element) => element.id == (arguments[0] as Token).id); return true; }); + mockTokenFolderRepository = MockTokenFolderRepository(); when(mockTokenFolderRepository.loadFolders()).thenAnswer((_) async => []); when(mockTokenFolderRepository.saveReplaceList(any)).thenAnswer((_) async => true); @@ -67,6 +71,7 @@ void main() { mockFirebaseUtils = MockFirebaseUtils(); when(mockFirebaseUtils.getFBToken()).thenAnswer((_) => Future.value('fbToken')); when(mockRsaUtils.deserializeRSAPublicKeyPKCS1('publicKey')).thenAnswer((_) => RSAPublicKey(BigInt.one, BigInt.one)); + mockIOClient = MockPrivacyideaIOClient(); when(mockIOClient.doPost( url: anyNamed('url'), @@ -76,12 +81,33 @@ void main() { mockIntroductionRepository = MockIntroductionRepository(); when(mockIntroductionRepository.loadCompletedIntroductions()) .thenAnswer((_) async => const IntroductionState(completedIntroductions: {...Introduction.values})); + + mockPushRequestRepository = MockPushRequestRepository(); + var state = PushRequestState(pushRequests: [], knownPushRequests: CustomIntBuffer(list: [])); + when(mockPushRequestRepository.saveState(any)).thenAnswer((invocation) async { + final arguments = invocation.positionalArguments; + state = arguments[0] as PushRequestState; + }); + when(mockPushRequestRepository.loadState()).thenAnswer((_) async => state); + when(mockPushRequestRepository.addRequest(any)).thenAnswer((invocation) async { + final arguments = invocation.positionalArguments; + state = state.withRequest(arguments[0] as PushRequest); + return state; + }); + when(mockPushRequestRepository.removeRequest(any)).thenAnswer((invocation) async { + final arguments = invocation.positionalArguments; + state = state.withoutRequest(arguments[0] as PushRequest); + return state; + }); + when(mockPushRequestRepository.clearState()).thenAnswer((_) async { + state = PushRequestState(pushRequests: [], knownPushRequests: CustomIntBuffer(list: [])); + }); }); testWidgets('Views Test', (tester) async { await tester.pumpWidget(TestsAppWrapper( overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepository)), + settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepository)), tokenProvider.overrideWith((ref) => TokenNotifier( repository: mockTokenRepository, rsaUtils: mockRsaUtils, @@ -90,15 +116,15 @@ void main() { ref: ref, )), pushRequestProvider.overrideWith( - (ref) => PushRequestNotifier( - rsaUtils: mockRsaUtils, - ref: ref, - pushProvider: PushProvider( + () => PushRequestNotifier( + rsaUtilsOverride: mockRsaUtils, + ioClientOverride: mockIOClient, + pushRepoOverride: mockPushRequestRepository, + pushProviderOverride: PushProvider( rsaUtils: mockRsaUtils, ioClient: mockIOClient, firebaseUtils: mockFirebaseUtils, ), - ioClient: mockIOClient, ), ), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), diff --git a/lib/interfaces/repo/push_request_repository.dart b/lib/interfaces/repo/push_request_repository.dart index bd0274f35..aba6bcd20 100644 --- a/lib/interfaces/repo/push_request_repository.dart +++ b/lib/interfaces/repo/push_request_repository.dart @@ -31,8 +31,8 @@ abstract class PushRequestRepository { Future clearState(); /// Add a [PushRequest] to the [PushRequestState] - Future add(PushRequest pushRequest, {PushRequestState? state}); + Future addRequest(PushRequest pushRequest, {PushRequestState? state}); /// Remove a [PushRequest] from the [PushRequestState] - Future remove(PushRequest pushRequest, {PushRequestState? state}); + Future removeRequest(PushRequest pushRequest, {PushRequestState? state}); } diff --git a/lib/mains/main_customizer.dart b/lib/mains/main_customizer.dart index 2b197576b..cce029bac 100644 --- a/lib/mains/main_customizer.dart +++ b/lib/mains/main_customizer.dart @@ -25,6 +25,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../l10n/app_localizations.dart'; import '../model/enums/app_feature.dart'; +import '../model/riverpod_states/settings_state.dart'; import '../utils/globals.dart'; import '../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; @@ -50,7 +51,6 @@ class CustomizationAuthenticator extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { WidgetsFlutterBinding.ensureInitialized(); - final locale = ref.watch(settingsProvider).currentLocale; final applicationCustomizer = ref.watch(applicationCustomizerProvider); return LayoutBuilder( builder: (context, constraints) { @@ -67,7 +67,7 @@ class CustomizationAuthenticator extends ConsumerWidget { navigatorKey: globalNavigatorKey, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, - locale: locale, + locale: ref.watch(settingsProvider).whenOrNull(data: (data) => data.currentLocale) ?? SettingsState.localeDefault, title: applicationCustomizer.appName, theme: applicationCustomizer.generateLightTheme(), darkTheme: applicationCustomizer.generateDarkTheme(), diff --git a/lib/mains/main_netknights.dart b/lib/mains/main_netknights.dart index fe3677197..153ed2bef 100644 --- a/lib/mains/main_netknights.dart +++ b/lib/mains/main_netknights.dart @@ -23,6 +23,7 @@ import 'package:easy_dynamic_theme/easy_dynamic_theme.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import '../firebase_options/default_firebase_options.dart'; import '../l10n/app_localizations.dart'; @@ -77,7 +78,6 @@ class PrivacyIDEAAuthenticator extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { globalRef = ref; - final locale = ref.watch(settingsProvider).currentLocale; return LayoutBuilder(builder: (context, constraints) { WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(appConstraintsProvider.notifier).state = constraints; @@ -91,7 +91,7 @@ class PrivacyIDEAAuthenticator extends ConsumerWidget { navigatorKey: globalNavigatorKey, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, - locale: locale, + locale: ref.watch(settingsProvider).whenOrNull(data: (data) => data.currentLocale) ?? SettingsState.localeDefault, title: _customization.appName, theme: _customization.generateLightTheme(), darkTheme: _customization.generateDarkTheme(), diff --git a/lib/model/extensions/enums/introduction_extension.dart b/lib/model/extensions/enums/introduction_extension.dart index 5aa1c038e..494f7b23c 100644 --- a/lib/model/extensions/enums/introduction_extension.dart +++ b/lib/model/extensions/enums/introduction_extension.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import '../../../l10n/app_localizations.dart'; import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; @@ -28,8 +29,8 @@ extension IntroductionX on Introduction { state.isUncompleted(Introduction.pollForChallenges) && Introduction.dragToken.isConditionFulfilled(ref, state) == false && Introduction.addFolder.isConditionFulfilled(ref, state) == false, - Introduction.hidePushTokens => - ref.watch(settingsProvider).hidePushTokens && state.isCompleted(Introduction.pollForChallenges) && state.isUncompleted(Introduction.hidePushTokens), + Introduction.hidePushTokens => ref.watch(settingsProvider).whenOrNull(data: (data) => data.hidePushTokens) ?? + SettingsState.hidePushTokensDefault && state.isCompleted(Introduction.pollForChallenges) && state.isUncompleted(Introduction.hidePushTokens), Introduction.exportTokens => state.isUncompleted(Introduction.exportTokens), }; diff --git a/lib/model/riverpod_states/settings_state.dart b/lib/model/riverpod_states/settings_state.dart index 0a4022ff3..9910c17f5 100644 --- a/lib/model/riverpod_states/settings_state.dart +++ b/lib/model/riverpod_states/settings_state.dart @@ -28,18 +28,18 @@ import '../version.dart'; /// This class contains all device specific settings. E.g., the language used, whether to show the guide on start, etc. class SettingsState { - static bool get _isFirstRunDefault => true; - static bool get _showGuideOnStartDefault => true; - static bool get _hideOtpsDefault => false; - static bool get _enablePollDefault => false; - static Set get _crashReportRecipientsDefault => {defaultCrashReportRecipient}; - static Locale get _localePreferenceDefault => AppLocalizations.supportedLocales + static bool get isFirstRunDefault => true; + static bool get showGuideOnStartDefault => true; + static bool get hideOtpsDefault => false; + static bool get enablePollingDefault => false; + static Set get crashReportRecipientsDefault => {defaultCrashReportRecipient}; + static Locale get localeDefault => AppLocalizations.supportedLocales .firstWhere((locale) => locale.languageCode == (!kIsWeb ? Platform.localeName.substring(0, 2) : 'en'), orElse: () => const Locale('en')); - static bool get _useSystemLocaleDefault => true; - static bool get _enableLoggingDefault => false; - static bool get _hidePushTokensDefault => false; - static Version get _latestStartedVersionDefault => Version.parse('0.0.0'); + static bool get useSystemLocaleDefault => true; + static bool get verboseLoggingDefault => false; + static bool get hidePushTokensDefault => false; + static Version get latestStartedVersionDefault => Version.parse('0.0.0'); final bool isFirstRun; final bool showGuideOnStart; @@ -67,16 +67,16 @@ class SettingsState { bool? verboseLogging, bool? hidePushTokens, Version? latestStartedVersion, - }) : isFirstRun = isFirstRun ?? _isFirstRunDefault, - showGuideOnStart = showGuideOnStart ?? _showGuideOnStartDefault, - hideOpts = hideOpts ?? _hideOtpsDefault, - enablePolling = enablePolling ?? _enablePollDefault, - crashReportRecipients = crashReportRecipients ?? _crashReportRecipientsDefault, - localePreference = localePreference ?? _localePreferenceDefault, - useSystemLocale = useSystemLocale ?? _useSystemLocaleDefault, - verboseLogging = verboseLogging ?? _enableLoggingDefault, - hidePushTokens = hidePushTokens ?? _hidePushTokensDefault, - latestStartedVersion = latestStartedVersion ?? _latestStartedVersionDefault; + }) : isFirstRun = isFirstRun ?? isFirstRunDefault, + showGuideOnStart = showGuideOnStart ?? showGuideOnStartDefault, + hideOpts = hideOpts ?? hideOtpsDefault, + enablePolling = enablePolling ?? enablePollingDefault, + crashReportRecipients = crashReportRecipients ?? crashReportRecipientsDefault, + localePreference = localePreference ?? localeDefault, + useSystemLocale = useSystemLocale ?? useSystemLocaleDefault, + verboseLogging = verboseLogging ?? verboseLoggingDefault, + hidePushTokens = hidePushTokens ?? hidePushTokensDefault, + latestStartedVersion = latestStartedVersion ?? latestStartedVersionDefault; SettingsState copyWith({ bool? isFirstRun, diff --git a/lib/repo/secure_push_request_repository.dart b/lib/repo/secure_push_request_repository.dart index a5fb206af..31e17b7a2 100644 --- a/lib/repo/secure_push_request_repository.dart +++ b/lib/repo/secure_push_request_repository.dart @@ -73,7 +73,7 @@ class SecurePushRequestRepository implements PushRequestRepository { /// Adds a push request in the given state if it is not already known. /// If no state is given, the current state is loaded from the secure storage. /// This is a critical section, so it is protected by Mutex. - Future add(PushRequest pushRequest, {PushRequestState? state}) => protect(() async { + Future addRequest(PushRequest pushRequest, {PushRequestState? state}) => protect(() async { state ??= await _loadState(); if (state!.knowsRequest(pushRequest)) { return state!; @@ -88,7 +88,7 @@ class SecurePushRequestRepository implements PushRequestRepository { /// Remove a push request from the state. /// If no state is given, the current state is loaded from the secure storage. /// This is a critical section, so it is protected by Mutex. - Future remove(PushRequest pushRequest, {PushRequestState? state}) => protect(() async { + Future removeRequest(PushRequest pushRequest, {PushRequestState? state}) => protect(() async { state ??= await _loadState(); final newState = state!.withoutRequest(pushRequest); await _saveState(newState); diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index a956d6d0e..45081bddc 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -36,7 +36,6 @@ import '../utils/app_info_utils.dart'; import '../utils/pi_mailer.dart'; import '../views/settings_view/settings_view_widgets/send_error_dialog.dart'; import 'globals.dart'; -import 'riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; final provider = Provider((ref) => 0); @@ -94,8 +93,9 @@ class Logger { String get _filename => 'logfile.txt'; String? get _fullPath => _logPath != null ? '$_logPath/$_filename' : null; bool get _verbose { - if (globalRef == null) return false; - return globalRef!.read(settingsProvider).verboseLogging; + // if (globalRef == null) return false; + return false; + // return globalRef!.read(settingsProvider).whenOrNull(data: (data) => data.verboseLogging) ?? SettingsState.verboseLoggingDefault; //TODO: fix it } bool get logfileHasContent { diff --git a/lib/utils/push_provider.dart b/lib/utils/push_provider.dart index 0c4fbc11a..88891535d 100644 --- a/lib/utils/push_provider.dart +++ b/lib/utils/push_provider.dart @@ -259,7 +259,7 @@ class PushProvider { } try { - await _defaultPushRequestRepo.add(pushRequest); + await _defaultPushRequestRepo.addRequest(pushRequest); } catch (e) { Logger.error('Could not save push request state.', name: 'push_provider.dart#_handleIncomingRequestBackground', error: e); return false; @@ -292,8 +292,7 @@ class PushProvider { List pushTokens = globalRef?.read(tokenProvider).tokens.whereType().where((t) => t.isRolledOut && t.url != null).toList() ?? []; // Disable polling if no push tokens exist if (pushTokens.isEmpty) { - await globalRef?.read(settingsProvider.notifier).loadingRepo; - if (globalRef?.read(settingsProvider).enablePolling == true) { + if ((await globalRef?.read(settingsProvider.future))?.enablePolling == true) { Logger.info('No push token is available for polling, polling is disabled.', name: 'push_provider.dart#pollForChallenges'); globalRef?.read(settingsProvider.notifier).setPolling(false); } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart index d9c33b1ec..77b06e287 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart @@ -47,16 +47,16 @@ class IntroductionNotifier extends _$IntroductionNotifier { Future _loadFromRepo() async { final newState = await _repo.loadCompletedIntroductions(); - Logger.info('Loading completed introductions from repo: $newState', name: 'settings_notifier.dart#_loadFromRepo'); + Logger.info('Loading completed introductions from repo: $newState', name: 'introduction_provider.dart#_loadFromRepo'); return newState; } Future _saveToRepo(IntroductionState state) async { final success = await _repo.saveCompletedIntroductions(state); if (success) { - Logger.info('Saving completed introductions to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); + Logger.info('Saving completed introductions to repo: $state', name: 'introduction_provider.dart#_saveToRepo'); } else { - Logger.warning('Failed to save completed introductions to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); + Logger.warning('Failed to save completed introductions to repo: $state', name: 'introduction_provider.dart#_saveToRepo'); } } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart index 4bceb9511..bd89c2af3 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart @@ -7,7 +7,7 @@ part of 'introduction_provider.dart'; // ************************************************************************** String _$introductionNotifierHash() => - r'6e8e9a8c6335ca86e2afe6d34ca159c6717388e5'; + r'2c84c088bf01feb395c187f1671a4c822edf1b42'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart new file mode 100644 index 000000000..1089e068e --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart @@ -0,0 +1,556 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 'package:http/http.dart'; +import 'package:mutex/mutex.dart'; +import 'package:privacyidea_authenticator/interfaces/repo/push_request_repository.dart'; +import 'package:privacyidea_authenticator/utils/custom_int_buffer.dart'; +import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../../l10n/app_localizations.dart'; +import '../../../../model/push_request.dart'; +import '../../../../model/riverpod_states/push_request_state.dart'; +import '../../../../model/tokens/push_token.dart'; +import '../../../../repo/secure_push_request_repository.dart'; +import '../../../globals.dart'; +import '../../../privacyidea_io_client.dart'; +import '../../../logger.dart'; +import '../../../push_provider.dart'; +import '../../../utils.dart'; +import '../state_providers/status_message_provider.dart'; + +part 'push_request_provider.g.dart'; + +final pushRequestProvider = pushRequestNotifierProviderOf( + rsaUtils: const RsaUtils(), + ioClient: const PrivacyideaIOClient(), + pushProvider: PushProvider(), + pushRepo: const SecurePushRequestRepository(), +); + +@Riverpod(keepAlive: true) +class PushRequestNotifier extends _$PushRequestNotifier { + final loadingRepoMutex = Mutex(); + final updatingRequestMutex = Mutex(); + + final Map _expirationTimers = {}; + + @override + RsaUtils get rsaUtils => _rsaUtils; + final RsaUtils? _rsaUtilsOverride; + late final RsaUtils _rsaUtils; + + @override + PrivacyideaIOClient get ioClient => _ioClient; + final PrivacyideaIOClient? _ioClientOverride; + late final PrivacyideaIOClient _ioClient; + + @override + PushProvider get pushProvider => _pushProvider; + final PushProvider? _pushProviderOverride; + late final PushProvider _pushProvider; + + @override + PushRequestRepository get pushRepo => _pushRepo; + final PushRequestRepository? _pushRepoOverride; + late final PushRequestRepository _pushRepo; + + PushRequestNotifier({ + RsaUtils? rsaUtilsOverride, + PrivacyideaIOClient? ioClientOverride, + PushProvider? pushProviderOverride, + PushRequestRepository? pushRepoOverride, + }) : _pushProviderOverride = pushProviderOverride, + _rsaUtilsOverride = rsaUtilsOverride, + _ioClientOverride = ioClientOverride, + _pushRepoOverride = pushRepoOverride; + + @override + Future build({ + required RsaUtils rsaUtils, + required PrivacyideaIOClient ioClient, + required PushProvider pushProvider, + required PushRequestRepository pushRepo, + }) async { + _rsaUtils = _rsaUtilsOverride ?? rsaUtils; + _ioClient = _ioClientOverride ?? ioClient; + _pushProvider = _pushProviderOverride ?? pushProvider; + _pushRepo = _pushRepoOverride ?? pushRepo; + Logger.info('New PushRequestNotifier created', name: 'pushRequestProvider#build'); + _pushProvider.subscribe(add); + return PushRequestState(pushRequests: [], knownPushRequests: CustomIntBuffer(list: [])); + } + + void swapPushProvider(PushProvider newProvider) { + _pushProvider.unsubscribe(add); + _pushProvider = newProvider; + _pushProvider.subscribe(add); + } + + /* + ///////////////////////////////////////////////////////////////////////////// + //////////////////// Repository and PushRequest Handling //////////////////// + ///////////////////////////////////////////////////////////////////////////// + /// Repository layer is always use loadingRepoMutex for the latest state + */ + + Future _loadFromRepo() async { + await loadingRepoMutex.acquire(); + final PushRequestState loadedState; + try { + loadedState = await _pushRepo.loadState(); + } catch (e) { + Logger.error('Failed to load push request state from repo.', name: 'push_request_notifier.dart#_loadFromRepo', error: e); + loadingRepoMutex.release(); + return (await future); + } + _renewTimers(loadedState.pushRequests); + state = AsyncValue.data(loadedState); + loadingRepoMutex.release(); + return loadedState; + } + + /// Adds a PushRequest to repo and state. Returns true if successful, false if not. + /// If the request already exists, it will be replaced. + Future _addOrReplacePushRequest(PushRequest pushRequest) async { + await loadingRepoMutex.acquire(); + final oldState = (await future); + final newState = oldState.addOrReplace(pushRequest); + try { + await _pushRepo.saveState(newState); + } catch (e) { + Logger.warning( + 'Failed to save push request: $pushRequest', + name: 'push_request_notifier.dart#_addOrReplacePushRequest', + error: e, + ); + loadingRepoMutex.release(); + return false; + } + state = AsyncValue.data(newState); + loadingRepoMutex.release(); + return true; + } + + /// Replaces a PushRequest in repo and state. Returns true if successful, false if not. + Future _replacePushRequest(PushRequest pushRequest) async { + await loadingRepoMutex.acquire(); + final oldState = (await future); + final (newState, replaced) = oldState.replaceRequest(pushRequest); + if (!replaced) { + Logger.warning( + 'Tried to replace a push request that does not exist.', + name: 'push_request_notifier.dart#_replacePushRequest', + ); + loadingRepoMutex.release(); + return false; + } + try { + await _pushRepo.saveState(newState); + } catch (e) { + Logger.warning( + 'Failed to save push request: $pushRequest', + name: 'push_request_notifier.dart#_replacePushRequest', + error: e, + ); + loadingRepoMutex.release(); + return false; + } + state = AsyncValue.data(newState); + loadingRepoMutex.release(); + return true; + } + + /// Removes a PushRequest from repo and state. Returns true if successful, false if not. + Future _remove(PushRequest pushRequest) async { + _cancelTimer(pushRequest); + await loadingRepoMutex.acquire(); + final newState = (await future).withoutRequest(pushRequest); + try { + await _pushRepo.saveState(newState); + } catch (e) { + Logger.error( + 'Failed to save push request state after removing push request.', + name: 'push_request_notifier.dart#_remove', + error: e, + ); + loadingRepoMutex.release(); + _setupTimer(pushRequest); + return false; + } + state = AsyncValue.data(newState); + loadingRepoMutex.release(); + return true; + } + /* + ////////////////////////////////////////////////////////////////////////////// + ////////////////////// Update PushRequest Methods //////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + /// Updating layer is always use updatingRequestMutex for the latest state + */ + + /// Updates a PushRequest of the current state. The updated PushRequest is saved to the repo and the state. Returns the updated PushRequest if successful, null if not. + Future _updatePushRequest(PushRequest pushRequest, Future Function(PushRequest) updater) async { + await updatingRequestMutex.acquire(); + final current = (await future).currentOf(pushRequest); + if (current == null) { + Logger.warning('Tried to update a push request that does not exist.', name: 'push_request_notifier.dart#updatePushRequest'); + updatingRequestMutex.release(); + return null; + } + final updated = await updater(current); + final replaced = await _replacePushRequest(updated); + updatingRequestMutex.release(); + return replaced ? updated : current; + } + + /* + ////////////////////////////////////////////////////////////////////////////// + //////////////////////////// Public Methods ////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + /// There is no need to use mutexes because the updating functions are always using the latest version of the updating tokens. + */ + + Future pollForChallenges({required bool isManually}) => pushProvider.pollForChallenges(isManually: isManually); + + Future loadStateFromRepo() => _loadFromRepo(); + + /// Accepts a push request and returns true if successful, false if not. + /// An accepted push request is removed from the state. + /// It should be still in the CustomIntBuffer of the state. + Future accept(PushToken pushToken, PushRequest pushRequest, {String? selectedAnswer}) async { + if (pushRequest.accepted != null) { + Logger.warning('The push request is already accepted or declined.', name: 'push_request_notifier.dart#accept'); + + return false; + } + Logger.info('Accept push request.', name: 'push_request_notifier.dart#accept'); + final updated = await _updatePushRequest(pushRequest, (p0) async { + final updated = p0.copyWith(accepted: true, selectedAnswer: () => selectedAnswer); + final success = await _handleReaction(pushRequest: updated, token: pushToken); + if (!success) { + Logger.warning('Failed to handle push request reaction.', name: 'push_request_notifier.dart#accept'); + return p0; + } + return updated; + }); + if (updated == null || updated.accepted != true) { + Logger.warning('Failed to accept push request.', name: 'push_request_notifier.dart#accept'); + return false; + } + await _remove(updated); + return true; + } + + Future decline(PushToken pushToken, PushRequest pushRequest) async { + if (pushRequest.accepted != null) { + Logger.warning('The push request is already accepted or declined.', name: 'push_request_notifier.dart#decline'); + return false; + } + Logger.info('Decline push request.', name: 'push_request_notifier.dart#decline'); + final updated = await _updatePushRequest(pushRequest, (p0) async { + final updated = p0.copyWith(accepted: false, selectedAnswer: () => null); + final success = await _handleReaction(pushRequest: updated, token: pushToken); + if (!success) { + Logger.warning('Failed to handle push request reaction.', name: 'push_request_notifier.dart#accept'); + return p0; + } + return updated; + }); + if (updated == null || updated.accepted != false) { + Logger.warning('Failed to decline push request.', name: 'push_request_notifier.dart#decline'); + return false; + } + await _remove(updated); + return true; + } + + Future add(PushRequest pr) async { + if ((await future).knowsRequestId(pr.id)) { + Logger.info( + 'The push request already exists.', + name: 'token_notifier.dart#addPushRequestToToken', + ); + return false; + } + // Save the pending request. + final success = await _addOrReplacePushRequest(pr); + + // Remove the request after it expires. + if (success) _setupTimer(pr); + Logger.info('Added push request ${pr.id} to state', name: 'token_notifier.dart#addPushRequestToToken'); + return true; + } + + Future remove(PushRequest pushRequest) => _remove(pushRequest); + + ////////////////////////////////////////////////////////////////////////////// + ///////////////////////// Helper Methods ///////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + + void _renewTimers(List pushRequests) { + _cancalAllTimers(); + _setupAllTimers(pushRequests); + } + + void _cancelTimer(PushRequest pr) { + Logger.info('Canceling timer for push request ${pr.id}', name: 'push_request_notifier.dart#_cancelTimer'); + final timer = _expirationTimers.remove(pr.id.toString())?..cancel(); + if (timer == null) { + Logger.warning('Timer for push request ${pr.id} not found.', name: 'push_request_notifier.dart#_cancelTimer'); + } + } + + void _cancalAllTimers() { + if (_expirationTimers.keys.isNotEmpty) { + Logger.info('Canceling all timers: [${_expirationTimers.keys}]', name: 'push_request_notifier.dart#_cancelAllTimers'); + } + final ids = _expirationTimers.keys.toList(); + for (var id in ids) { + _expirationTimers.remove(id.toString())?.cancel(); + } + } + + /// Sets up a timer to remove the push request after it expires. + /// If the request is already expired, it will be removed immediately. + /// When the timer is set up, the old timer is canceled. + void _setupTimer(PushRequest pr) { + _expirationTimers[pr.id.toString()]?.cancel(); + int time = pr.expirationDate.difference(DateTime.now()).inMilliseconds; + if (time < 1) { + _remove(pr); + return; + } + _expirationTimers[pr.id.toString()] = Timer(Duration(milliseconds: time), () async => _remove(pr)); + } + + void _setupAllTimers(List pushRequests) { + _cancalAllTimers(); + for (var pr in pushRequests) { + int time = pr.expirationDate.difference(DateTime.now()).inMilliseconds; + if (time < 1) { + _remove(pr); + } + _expirationTimers[pr.id.toString()] = Timer(Duration(milliseconds: time), () async => _remove(pr)); + } + } + + Future _handleReaction({required PushRequest pushRequest, required PushToken token}) async { + if (pushRequest.accepted == null) return false; + Logger.info('Push auth request accepted=${pushRequest.accepted}, sending response to privacyidea', name: 'token_widgets.dart#handleReaction'); + // POST https://privacyideaserver/validate/check + // nonce= + // serial= + // signature= + // decline=1 (optional) + // presence_answer= (optional) + final Map body = { + 'nonce': pushRequest.nonce, + 'serial': token.serial, + }; + // signature ::= {nonce}|{serial}[|decline] + String msg = '${pushRequest.nonce}|${token.serial}'; + if (pushRequest.accepted! == false) { + body['decline'] = '1'; + msg += '|decline'; + } + if (pushRequest.possibleAnswers != null && pushRequest.selectedAnswer != null) { + body['presence_answer'] = pushRequest.selectedAnswer!; + msg += '|${pushRequest.selectedAnswer!}'; + } + Logger.warning('Signature message: $msg', name: 'token_widgets.dart#handleReaction'); + String? signature = await _rsaUtils.trySignWithToken(token, msg); + if (signature == null) { + Logger.warning('Failed to sign push request response.', name: 'token_widgets.dart#handleReaction'); + return false; + } + + body['signature'] = signature; + + Response response; + try { + Logger.info('Sending push request response.', name: 'token_widgets.dart#_handleReaction'); + response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body); + } catch (e) { + Logger.warning('Sending push request response failed. Retrying.', name: 'token_widgets.dart#handleReaction'); + try { + response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body); + } catch (e) { + Logger.warning('Sending push request response failed consistently.', name: 'token_widgets.dart#handleReaction', error: e); + ref.read(statusMessageProvider.notifier).state = (AppLocalizations.of(await globalContext)!.connectionFailed, null); + return false; + } + } + if (response.statusCode != 200) { + final appLocalizations = AppLocalizations.of(await globalContext)!; + ref.read(statusMessageProvider.notifier).state = ( + '${appLocalizations.sendPushRequestResponseFailed}\n${appLocalizations.statusCode(response.statusCode)}', + tryJsonDecode(response.body)?["result"]?["error"]?["message"], + ); + Logger.warning('Sending push request response failed.', name: 'token_widgets.dart#handleReaction'); + return false; + } + return true; + } +} + + + + + + +// StateNotifierProvider( +// (ref) { +// Logger.info("New PushRequestNotifier created", name: 'pushRequestProvider'); +// final tokenState = ref.read(tokenProvider); +// PushProvider pushProvider = tokenState.hasPushTokens ? PushProvider() : PlaceholderPushProvider(); // Until the state is loaded from the repo +// final pushRequestNotifier = PushRequestNotifier( +// ref: ref, +// pushProvider: pushProvider, +// ); + +// ref.listen(tokenProvider, (previous, next) { +// if (previous?.hasPushTokens == true && next.hasPushTokens == false) { +// /// Last push token was deleted +// Logger.info('Last push token was deleted. Deactivating push provider and deleting firebase token.', name: 'pushRequestProvider#tokenProvider'); +// pushRequestNotifier.swapPushProvider(PlaceholderPushProvider()); +// pushProvider.firebaseUtils.deleteFirebaseToken(); +// } +// if (previous?.hasPushTokens != true && next.hasPushTokens == true) { +// /// First push token was added +// Logger.info('First push token was added. Activating push provider.', name: 'pushRequestProvider#tokenProvider'); +// pushRequestNotifier.swapPushProvider(PushProvider()); +// } +// }); + +// ref.listen(settingsProvider, (previous, next) { +// if (previous?.enablePolling != next.enablePolling) { +// Logger.info("Polling enabled changed from ${previous?.enablePolling} to ${next.enablePolling}", name: 'pushRequestProvider#settingsProvider'); +// pushRequestNotifier.pushProvider.setPollingEnabled(next.enablePolling); +// } +// }); + +// return pushRequestNotifier; +// }, +// name: 'pushRequestProvider', +// ); + + +/** + * + * /* + privacyIDEA Authenticator + + Authors: Timo Sturm + Frank Merkel + Copyright (c) 2017-2024 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 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart'; +import 'package:mutex/mutex.dart'; + +import '../../../interfaces/repo/push_request_repository.dart'; +import '../../../l10n/app_localizations.dart'; +import '../../../model/push_request.dart'; +import '../../../model/riverpod_states/push_request_state.dart'; +import '../../../model/tokens/push_token.dart'; +import '../../../repo/secure_push_request_repository.dart'; +import '../../custom_int_buffer.dart'; +import '../../globals.dart'; +import '../../logger.dart'; +import '../../privacyidea_io_client.dart'; +import '../../push_provider.dart'; +import '../riverpod_providers/state_providers/status_message_provider.dart'; +import '../../rsa_utils.dart'; +import '../../utils.dart'; + +class PushRequestNotifier extends StateNotifier { + late final Future initState; + final StateNotifierProviderRef ref; + final loadingRepoMutex = Mutex(); + final updatingRequestMutex = Mutex(); + final PushRequestRepository _pushRepo; + + PushProvider _pushProvider; + PushProvider get pushProvider => _pushProvider; + final PrivacyideaIOClient _ioClient; + final RsaUtils _rsaUtils; + + + PushRequestNotifier({ + PushRequestState? initState, + PrivacyideaIOClient? ioClient, + required PushProvider pushProvider, + required this.ref, + RsaUtils? rsaUtils, + PushRequestRepository? pushRepo, + }) : _ioClient = ioClient ?? const PrivacyideaIOClient(), + _pushProvider = pushProvider, + _rsaUtils = rsaUtils ?? const RsaUtils(), + _pushRepo = pushRepo ?? const SecurePushRequestRepository(), + super( + initState ?? PushRequestState(pushRequests: [], knownPushRequests: CustomIntBuffer(list: [])), + ) { + _init(initState); + } + + void swapPushProvider(PushProvider newProvider) { + _pushProvider.unsubscribe(add); + _pushProvider = newProvider; + _pushProvider.subscribe(add); + } + + Future _init(PushRequestState? initialState) async { + initState = initialState != null ? Future.value(initialState) : _loadFromRepo(); + _pushProvider.subscribe(add); + await initState; + Logger.info('PushRequestNotifier initialized', name: 'push_request_notifier.dart#_init'); + } + + @override + void dispose() { + _pushProvider.unsubscribe(add); + _cancalAllTimers(); + super.dispose(); + } + + + + +} + + */ \ No newline at end of file diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart new file mode 100644 index 000000000..5473a3a39 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart @@ -0,0 +1,241 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'push_request_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$pushRequestNotifierHash() => + r'34e1b940ac7ae3abb4d5887bae44d76f147b6c38'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$PushRequestNotifier + extends BuildlessAsyncNotifier { + late final RsaUtils rsaUtils; + late final PrivacyideaIOClient ioClient; + late final PushProvider pushProvider; + late final PushRequestRepository pushRepo; + + FutureOr build({ + required RsaUtils rsaUtils, + required PrivacyideaIOClient ioClient, + required PushProvider pushProvider, + required PushRequestRepository pushRepo, + }); +} + +/// See also [PushRequestNotifier]. +@ProviderFor(PushRequestNotifier) +const pushRequestNotifierProviderOf = PushRequestNotifierFamily(); + +/// See also [PushRequestNotifier]. +class PushRequestNotifierFamily extends Family> { + /// See also [PushRequestNotifier]. + const PushRequestNotifierFamily(); + + /// See also [PushRequestNotifier]. + PushRequestNotifierProvider call({ + required RsaUtils rsaUtils, + required PrivacyideaIOClient ioClient, + required PushProvider pushProvider, + required PushRequestRepository pushRepo, + }) { + return PushRequestNotifierProvider( + rsaUtils: rsaUtils, + ioClient: ioClient, + pushProvider: pushProvider, + pushRepo: pushRepo, + ); + } + + @override + PushRequestNotifierProvider getProviderOverride( + covariant PushRequestNotifierProvider provider, + ) { + return call( + rsaUtils: provider.rsaUtils, + ioClient: provider.ioClient, + pushProvider: provider.pushProvider, + pushRepo: provider.pushRepo, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'pushRequestNotifierProviderOf'; +} + +/// See also [PushRequestNotifier]. +class PushRequestNotifierProvider + extends AsyncNotifierProviderImpl { + /// See also [PushRequestNotifier]. + PushRequestNotifierProvider({ + required RsaUtils rsaUtils, + required PrivacyideaIOClient ioClient, + required PushProvider pushProvider, + required PushRequestRepository pushRepo, + }) : this._internal( + () => PushRequestNotifier() + ..rsaUtils = rsaUtils + ..ioClient = ioClient + ..pushProvider = pushProvider + ..pushRepo = pushRepo, + from: pushRequestNotifierProviderOf, + name: r'pushRequestNotifierProviderOf', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$pushRequestNotifierHash, + dependencies: PushRequestNotifierFamily._dependencies, + allTransitiveDependencies: + PushRequestNotifierFamily._allTransitiveDependencies, + rsaUtils: rsaUtils, + ioClient: ioClient, + pushProvider: pushProvider, + pushRepo: pushRepo, + ); + + PushRequestNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.rsaUtils, + required this.ioClient, + required this.pushProvider, + required this.pushRepo, + }) : super.internal(); + + final RsaUtils rsaUtils; + final PrivacyideaIOClient ioClient; + final PushProvider pushProvider; + final PushRequestRepository pushRepo; + + @override + FutureOr runNotifierBuild( + covariant PushRequestNotifier notifier, + ) { + return notifier.build( + rsaUtils: rsaUtils, + ioClient: ioClient, + pushProvider: pushProvider, + pushRepo: pushRepo, + ); + } + + @override + Override overrideWith(PushRequestNotifier Function() create) { + return ProviderOverride( + origin: this, + override: PushRequestNotifierProvider._internal( + () => create() + ..rsaUtils = rsaUtils + ..ioClient = ioClient + ..pushProvider = pushProvider + ..pushRepo = pushRepo, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + rsaUtils: rsaUtils, + ioClient: ioClient, + pushProvider: pushProvider, + pushRepo: pushRepo, + ), + ); + } + + @override + AsyncNotifierProviderElement + createElement() { + return _PushRequestNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is PushRequestNotifierProvider && + other.rsaUtils == rsaUtils && + other.ioClient == ioClient && + other.pushProvider == pushProvider && + other.pushRepo == pushRepo; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, rsaUtils.hashCode); + hash = _SystemHash.combine(hash, ioClient.hashCode); + hash = _SystemHash.combine(hash, pushProvider.hashCode); + hash = _SystemHash.combine(hash, pushRepo.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin PushRequestNotifierRef on AsyncNotifierProviderRef { + /// The parameter `rsaUtils` of this provider. + RsaUtils get rsaUtils; + + /// The parameter `ioClient` of this provider. + PrivacyideaIOClient get ioClient; + + /// The parameter `pushProvider` of this provider. + PushProvider get pushProvider; + + /// The parameter `pushRepo` of this provider. + PushRequestRepository get pushRepo; +} + +class _PushRequestNotifierProviderElement + extends AsyncNotifierProviderElement + with PushRequestNotifierRef { + _PushRequestNotifierProviderElement(super.provider); + + @override + RsaUtils get rsaUtils => (origin as PushRequestNotifierProvider).rsaUtils; + @override + PrivacyideaIOClient get ioClient => + (origin as PushRequestNotifierProvider).ioClient; + @override + PushProvider get pushProvider => + (origin as PushRequestNotifierProvider).pushProvider; + @override + PushRequestRepository get pushRepo => + (origin as PushRequestNotifierProvider).pushRepo; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart deleted file mode 100644 index f9c3a916e..000000000 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart +++ /dev/null @@ -1,63 +0,0 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; - -import '../../../../model/riverpod_states/push_request_state.dart'; -import '../../state_notifiers/push_request_notifier.dart'; -import '../../../logger.dart'; -import '../../../push_provider.dart'; -import 'settings_provider.dart'; -import 'token_provider.dart'; - -final pushRequestProvider = StateNotifierProvider( - (ref) { - Logger.info("New PushRequestNotifier created", name: 'pushRequestProvider'); - final tokenState = ref.read(tokenProvider); - PushProvider pushProvider = tokenState.hasPushTokens ? PushProvider() : PlaceholderPushProvider(); // Until the state is loaded from the repo - final pushRequestNotifier = PushRequestNotifier( - ref: ref, - pushProvider: pushProvider, - ); - - ref.listen(tokenProvider, (previous, next) { - if (previous?.hasPushTokens == true && next.hasPushTokens == false) { - /// Last push token was deleted - Logger.info('Last push token was deleted. Deactivating push provider and deleting firebase token.', name: 'pushRequestProvider#tokenProvider'); - pushRequestNotifier.swapPushProvider(PlaceholderPushProvider()); - pushProvider.firebaseUtils.deleteFirebaseToken(); - } - if (previous?.hasPushTokens != true && next.hasPushTokens == true) { - /// First push token was added - Logger.info('First push token was added. Activating push provider.', name: 'pushRequestProvider#tokenProvider'); - pushRequestNotifier.swapPushProvider(PushProvider()); - } - }); - - ref.listen(settingsProvider, (previous, next) { - if (previous?.enablePolling != next.enablePolling) { - Logger.info("Polling enabled changed from ${previous?.enablePolling} to ${next.enablePolling}", name: 'pushRequestProvider#settingsProvider'); - pushRequestNotifier.pushProvider.setPollingEnabled(next.enablePolling); - } - }); - - return pushRequestNotifier; - }, - name: 'pushRequestProvider', -); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart index 2be88d69f..2c2f82986 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart @@ -17,16 +17,158 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'dart:ui'; + +import 'package:mutex/mutex.dart'; +import 'package:privacyidea_authenticator/repo/preference_settings_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../../interfaces/repo/settings_repository.dart'; import '../../../../model/riverpod_states/settings_state.dart'; -import '../../../../repo/preference_settings_repository.dart'; -import '../../state_notifiers/settings_notifier.dart'; - -final settingsProvider = StateNotifierProvider( - (ref) { - // Using Logger here will cause a circular dependency because Logger uses settingsProvider (logging verbosity) - return SettingsNotifier(repository: PreferenceSettingsRepository()); - }, - name: 'settingsProvider', -); +import '../../../../model/version.dart'; +import '../../../logger.dart'; + +part 'settings_provider.g.dart'; + +final settingsProvider = settingsNotifierProviderOf(repo: PreferenceSettingsRepository()); + +@Riverpod(keepAlive: true) +class SettingsNotifier extends _$SettingsNotifier { + final Mutex _repoMutex = Mutex(); + final Mutex _stateMutex = Mutex(); + + SettingsNotifier({SettingsRepository? repoOverride}) : _repoOverride = repoOverride; + + @override + SettingsRepository get repo => _repo; + final SettingsRepository? _repoOverride; + late final SettingsRepository _repo; + + @override + Future build({ + required SettingsRepository repo, + }) async { + Logger.info('New settings notifier created', name: 'settings_notifier.dart#build'); + _repo = _repoOverride ?? repo; + final newState = await _loadFromRepo(); + return newState; + } + + Future _loadFromRepo() async { + await _repoMutex.acquire(); + final newState = await _repo.loadSettings(); + Logger.info('Loading settings from repo: $newState', name: 'settings_notifier.dart#_loadFromRepo'); + _repoMutex.release(); + return newState; + } + + Future _saveToRepo(SettingsState state) async { + await _repoMutex.acquire(); + Logger.info('Saving settings to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); + final success = await _repo.saveSettings(state); + _repoMutex.release(); + return success; + } + + Future updateState(SettingsState Function(SettingsState oldState) updater) async { + await _stateMutex.acquire(); + final oldState = await future; + final newState = updater(oldState); + final success = await _saveToRepo(newState); + if (success) { + state = AsyncValue.data(newState); + _stateMutex.release(); + return newState; + } else { + _stateMutex.release(); + return oldState; + } + } + + Future addCrashReportRecipient(String email) async { + Logger.info('Crash report recipient added: $email', name: 'settings_notifier.dart#addCrashReportRecipient'); + return updateState((oldState) { + final updatedSet = oldState.crashReportRecipients..add(email); + return oldState.copyWith(crashReportRecipients: updatedSet); + }); + } + + Future removeCrashReportRecipient(String email) async { + Logger.info('Crash report recipient removed: $email', name: 'settings_notifier.dart#removeCrashReportRecipient'); + return updateState((oldState) { + final updatedSet = oldState.crashReportRecipients..remove(email); + return oldState.copyWith(crashReportRecipients: updatedSet); + }); + } + + Future setisFirstRun(bool value) { + Logger.info('First run set to $value', name: 'settings_notifier.dart#setFirstRun'); + return updateState((oldState) => oldState.copyWith(isFirstRun: value)); + } + + Future sethideOTPs(bool value) { + Logger.info('Hide OTPs set to $value', name: 'settings_notifier.dart#setHideOTPs'); + return updateState((oldState) => oldState.copyWith(hideOpts: value)); + } + + Future setshowGuideOnStart(bool value) { + Logger.info('Show guide on start set to $value', name: 'settings_notifier.dart#setShowGuideOnStart'); + return updateState((oldState) => oldState.copyWith(showGuideOnStart: value)); + } + + Future setLocalePreference(Locale locale) { + Logger.info('Locale set to $locale', name: 'settings_notifier.dart#setLocalePreference'); + return updateState((oldState) => oldState.copyWith(localePreference: locale)); + } + + Future setUseSystemLocale(bool value) { + Logger.info('Use system locale set to $value', name: 'settings_notifier.dart#setUseSystemLocale'); + return updateState((oldState) => oldState.copyWith(useSystemLocale: value)); + } + + Future enablePolling() { + Logger.info('Polling set to true', name: 'settings_notifier.dart#enablePolling'); + return updateState((oldState) => oldState.copyWith(enablePolling: true)); + } + + Future disablePolling() { + Logger.info('Polling set to false', name: 'settings_notifier.dart#disablePolling'); + return updateState((oldState) => oldState.copyWith(enablePolling: false)); + } + + Future setPolling(bool value) { + Logger.info('Polling set to $value', name: 'settings_notifier.dart#setPolling'); + return updateState((oldState) => oldState.copyWith(enablePolling: value)); + } + + Future setLocale(Locale locale) { + Logger.info('Locale set to $locale', name: 'settings_notifier.dart#setLocale'); + return updateState((oldState) => oldState.copyWith(localePreference: locale)); + } + + Future setVerboseLogging(bool value) { + Logger.info('Verbose logging set to $value', name: 'settings_notifier.dart#setVerboseLogging'); + return updateState((oldState) => oldState.copyWith(verboseLogging: value)); + } + + Future toggleVerboseLogging() { + Logger.info('Toggling verbose logging', name: 'settings_notifier.dart#toggleVerboseLogging'); + return updateState((oldState) => oldState.copyWith(verboseLogging: !oldState.verboseLogging)); + } + + Future setFirstRun(bool value) { + Logger.info('First run set to $value', name: 'settings_notifier.dart#setFirstRun'); + return updateState((oldState) => oldState.copyWith(isFirstRun: value)); + } + + Future setHidePushTokens(bool value) { + Logger.info('Hide push tokens set to $value', name: 'settings_notifier.dart#setHidePushTokens'); + return updateState((oldState) => oldState.copyWith(hidePushTokens: value)); + } + + Future setLatestStartedVersion(Version version) { + Logger.info('Latest started version set to $version', name: 'settings_notifier.dart#setLatestStartedVersion'); + return updateState((oldState) => oldState.copyWith(latestStartedVersion: version)); + } +} diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.g.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.g.dart new file mode 100644 index 000000000..baa9f9a01 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.g.dart @@ -0,0 +1,174 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'settings_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$settingsNotifierHash() => r'83f54c5fafceae7ace984ef06ac564bee445771f'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$SettingsNotifier + extends BuildlessAsyncNotifier { + late final SettingsRepository repo; + + FutureOr build({ + required SettingsRepository repo, + }); +} + +/// See also [SettingsNotifier]. +@ProviderFor(SettingsNotifier) +const settingsNotifierProviderOf = SettingsNotifierFamily(); + +/// See also [SettingsNotifier]. +class SettingsNotifierFamily extends Family> { + /// See also [SettingsNotifier]. + const SettingsNotifierFamily(); + + /// See also [SettingsNotifier]. + SettingsNotifierProvider call({ + required SettingsRepository repo, + }) { + return SettingsNotifierProvider( + repo: repo, + ); + } + + @override + SettingsNotifierProvider getProviderOverride( + covariant SettingsNotifierProvider provider, + ) { + return call( + repo: provider.repo, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'settingsNotifierProviderOf'; +} + +/// See also [SettingsNotifier]. +class SettingsNotifierProvider + extends AsyncNotifierProviderImpl { + /// See also [SettingsNotifier]. + SettingsNotifierProvider({ + required SettingsRepository repo, + }) : this._internal( + () => SettingsNotifier()..repo = repo, + from: settingsNotifierProviderOf, + name: r'settingsNotifierProviderOf', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$settingsNotifierHash, + dependencies: SettingsNotifierFamily._dependencies, + allTransitiveDependencies: + SettingsNotifierFamily._allTransitiveDependencies, + repo: repo, + ); + + SettingsNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.repo, + }) : super.internal(); + + final SettingsRepository repo; + + @override + FutureOr runNotifierBuild( + covariant SettingsNotifier notifier, + ) { + return notifier.build( + repo: repo, + ); + } + + @override + Override overrideWith(SettingsNotifier Function() create) { + return ProviderOverride( + origin: this, + override: SettingsNotifierProvider._internal( + () => create()..repo = repo, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + repo: repo, + ), + ); + } + + @override + AsyncNotifierProviderElement + createElement() { + return _SettingsNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is SettingsNotifierProvider && other.repo == repo; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, repo.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin SettingsNotifierRef on AsyncNotifierProviderRef { + /// The parameter `repo` of this provider. + SettingsRepository get repo; +} + +class _SettingsNotifierProviderElement + extends AsyncNotifierProviderElement + with SettingsNotifierRef { + _SettingsNotifierProviderElement(super.provider); + + @override + SettingsRepository get repo => (origin as SettingsNotifierProvider).repo; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/state_notifiers/push_request_notifier.dart b/lib/utils/riverpod/state_notifiers/push_request_notifier.dart index 8af7fead8..5fe573b1f 100644 --- a/lib/utils/riverpod/state_notifiers/push_request_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/push_request_notifier.dart @@ -1,399 +1,399 @@ -/* - privacyIDEA Authenticator - - Authors: Timo Sturm - Frank Merkel - Copyright (c) 2017-2024 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 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:http/http.dart'; -import 'package:mutex/mutex.dart'; - -import '../../../interfaces/repo/push_request_repository.dart'; -import '../../../l10n/app_localizations.dart'; -import '../../../model/push_request.dart'; -import '../../../model/riverpod_states/push_request_state.dart'; -import '../../../model/tokens/push_token.dart'; -import '../../../repo/secure_push_request_repository.dart'; -import '../../custom_int_buffer.dart'; -import '../../globals.dart'; -import '../../logger.dart'; -import '../../privacyidea_io_client.dart'; -import '../../push_provider.dart'; -import '../riverpod_providers/state_providers/status_message_provider.dart'; -import '../../rsa_utils.dart'; -import '../../utils.dart'; - -class PushRequestNotifier extends StateNotifier { - late final Future initState; - final StateNotifierProviderRef ref; - final loadingRepoMutex = Mutex(); - final updatingRequestMutex = Mutex(); - final PushRequestRepository _pushRepo; - - PushProvider _pushProvider; - PushProvider get pushProvider => _pushProvider; - final PrivacyideaIOClient _ioClient; - final RsaUtils _rsaUtils; - - final Map _expirationTimers = {}; - - PushRequestNotifier({ - PushRequestState? initState, - PrivacyideaIOClient? ioClient, - required PushProvider pushProvider, - required this.ref, - RsaUtils? rsaUtils, - PushRequestRepository? pushRepo, - }) : _ioClient = ioClient ?? const PrivacyideaIOClient(), - _pushProvider = pushProvider, - _rsaUtils = rsaUtils ?? const RsaUtils(), - _pushRepo = pushRepo ?? const SecurePushRequestRepository(), - super( - initState ?? PushRequestState(pushRequests: [], knownPushRequests: CustomIntBuffer(list: [])), - ) { - _init(initState); - } - - void swapPushProvider(PushProvider newProvider) { - _pushProvider.unsubscribe(add); - _pushProvider = newProvider; - _pushProvider.subscribe(add); - } - - Future _init(PushRequestState? initialState) async { - initState = initialState != null ? Future.value(initialState) : _loadFromRepo(); - _pushProvider.subscribe(add); - await initState; - Logger.info('PushRequestNotifier initialized', name: 'push_request_notifier.dart#_init'); - } - - @override - void dispose() { - _pushProvider.unsubscribe(add); - _cancalAllTimers(); - super.dispose(); - } - - /* - ///////////////////////////////////////////////////////////////////////////// - //////////////////// Repository and PushRequest Handling //////////////////// - ///////////////////////////////////////////////////////////////////////////// - /// Repository layer is always use loadingRepoMutex for the latest state - */ - - Future _loadFromRepo() async { - await loadingRepoMutex.acquire(); - final PushRequestState loadedState; - try { - loadedState = await _pushRepo.loadState(); - } catch (e) { - Logger.error('Failed to load push request state from repo.', name: 'push_request_notifier.dart#_loadFromRepo', error: e); - loadingRepoMutex.release(); - return state; - } - _renewTimers(loadedState.pushRequests); - state = loadedState; - loadingRepoMutex.release(); - return loadedState; - } - - /// Adds a PushRequest to repo and state. Returns true if successful, false if not. - /// If the request already exists, it will be replaced. - Future _addOrReplacePushRequest(PushRequest pushRequest) async { - await loadingRepoMutex.acquire(); - final oldState = state; - final newState = oldState.addOrReplace(pushRequest); - try { - await _pushRepo.saveState(newState); - } catch (e) { - Logger.warning( - 'Failed to save push request: $pushRequest', - name: 'push_request_notifier.dart#_addOrReplacePushRequest', - error: e, - ); - loadingRepoMutex.release(); - return false; - } - state = newState; - loadingRepoMutex.release(); - return true; - } - - /// Replaces a PushRequest in repo and state. Returns true if successful, false if not. - Future _replacePushRequest(PushRequest pushRequest) async { - await loadingRepoMutex.acquire(); - final oldState = state; - final (newState, replaced) = oldState.replaceRequest(pushRequest); - if (!replaced) { - Logger.warning( - 'Tried to replace a push request that does not exist.', - name: 'push_request_notifier.dart#_replacePushRequest', - ); - loadingRepoMutex.release(); - return false; - } - try { - await _pushRepo.saveState(newState); - } catch (e) { - Logger.warning( - 'Failed to save push request: $pushRequest', - name: 'push_request_notifier.dart#_replacePushRequest', - error: e, - ); - loadingRepoMutex.release(); - return false; - } - state = newState; - loadingRepoMutex.release(); - return true; - } - - /// Removes a PushRequest from repo and state. Returns true if successful, false if not. - Future _remove(PushRequest pushRequest) async { - _cancelTimer(pushRequest); - await loadingRepoMutex.acquire(); - final newState = state.withoutRequest(pushRequest); - try { - await _pushRepo.saveState(newState); - } catch (e) { - Logger.error( - 'Failed to save push request state after removing push request.', - name: 'push_request_notifier.dart#_remove', - error: e, - ); - loadingRepoMutex.release(); - _setupTimer(pushRequest); - return false; - } - state = newState; - loadingRepoMutex.release(); - return true; - } - /* - ////////////////////////////////////////////////////////////////////////////// - ////////////////////// Update PushRequest Methods //////////////////////////// - ////////////////////////////////////////////////////////////////////////////// - /// Updating layer is always use updatingRequestMutex for the latest state - */ - - /// Updates a PushRequest of the current state. The updated PushRequest is saved to the repo and the state. Returns the updated PushRequest if successful, null if not. - Future _updatePushRequest(PushRequest pushRequest, Future Function(PushRequest) updater) async { - await updatingRequestMutex.acquire(); - final current = state.currentOf(pushRequest); - if (current == null) { - Logger.warning('Tried to update a push request that does not exist.', name: 'push_request_notifier.dart#updatePushRequest'); - updatingRequestMutex.release(); - return null; - } - final updated = await updater(current); - final replaced = await _replacePushRequest(updated); - updatingRequestMutex.release(); - return replaced ? updated : current; - } - - /* - ////////////////////////////////////////////////////////////////////////////// - //////////////////////////// Public Methods ////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////// - /// There is no need to use mutexes because the updating functions are always using the latest version of the updating tokens. - */ - - Future pollForChallenges({required bool isManually}) => pushProvider.pollForChallenges(isManually: isManually); - - Future loadStateFromRepo() => _loadFromRepo(); - - /// Accepts a push request and returns true if successful, false if not. - /// An accepted push request is removed from the state. - /// It should be still in the CustomIntBuffer of the state. - Future accept(PushToken pushToken, PushRequest pushRequest, {String? selectedAnswer}) async { - if (pushRequest.accepted != null) { - Logger.warning('The push request is already accepted or declined.', name: 'push_request_notifier.dart#accept'); - - return false; - } - Logger.info('Accept push request.', name: 'push_request_notifier.dart#accept'); - final updated = await _updatePushRequest(pushRequest, (p0) async { - final updated = p0.copyWith(accepted: true, selectedAnswer: () => selectedAnswer); - final success = await _handleReaction(pushRequest: updated, token: pushToken); - if (!success) { - Logger.warning('Failed to handle push request reaction.', name: 'push_request_notifier.dart#accept'); - return p0; - } - return updated; - }); - if (updated == null || updated.accepted != true) { - Logger.warning('Failed to accept push request.', name: 'push_request_notifier.dart#accept'); - return false; - } - await _remove(updated); - return true; - } - - Future decline(PushToken pushToken, PushRequest pushRequest) async { - if (pushRequest.accepted != null) { - Logger.warning('The push request is already accepted or declined.', name: 'push_request_notifier.dart#decline'); - return false; - } - Logger.info('Decline push request.', name: 'push_request_notifier.dart#decline'); - final updated = await _updatePushRequest(pushRequest, (p0) async { - final updated = p0.copyWith(accepted: false, selectedAnswer: () => null); - final success = await _handleReaction(pushRequest: updated, token: pushToken); - if (!success) { - Logger.warning('Failed to handle push request reaction.', name: 'push_request_notifier.dart#accept'); - return p0; - } - return updated; - }); - if (updated == null || updated.accepted != false) { - Logger.warning('Failed to decline push request.', name: 'push_request_notifier.dart#decline'); - return false; - } - await _remove(updated); - return true; - } - - Future add(PushRequest pr) async { - if (state.knowsRequestId(pr.id)) { - Logger.info( - 'The push request already exists.', - name: 'token_notifier.dart#addPushRequestToToken', - ); - return false; - } - // Save the pending request. - final success = await _addOrReplacePushRequest(pr); - - // Remove the request after it expires. - if (success) _setupTimer(pr); - Logger.info('Added push request ${pr.id} to state', name: 'token_notifier.dart#addPushRequestToToken'); - return true; - } - - Future remove(PushRequest pushRequest) => _remove(pushRequest); - - ////////////////////////////////////////////////////////////////////////////// - ///////////////////////// Helper Methods ///////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////// - - void _renewTimers(List pushRequests) { - _cancalAllTimers(); - _setupAllTimers(pushRequests); - } - - void _cancelTimer(PushRequest pr) { - Logger.info('Canceling timer for push request ${pr.id}', name: 'push_request_notifier.dart#_cancelTimer'); - final timer = _expirationTimers.remove(pr.id.toString())?..cancel(); - if (timer == null) { - Logger.warning('Timer for push request ${pr.id} not found.', name: 'push_request_notifier.dart#_cancelTimer'); - } - } - - void _cancalAllTimers() { - if (_expirationTimers.keys.isNotEmpty) { - Logger.info('Canceling all timers: [${_expirationTimers.keys}]', name: 'push_request_notifier.dart#_cancelAllTimers'); - } - final ids = _expirationTimers.keys.toList(); - for (var id in ids) { - _expirationTimers.remove(id.toString())?.cancel(); - } - } - - /// Sets up a timer to remove the push request after it expires. - /// If the request is already expired, it will be removed immediately. - /// When the timer is set up, the old timer is canceled. - void _setupTimer(PushRequest pr) { - _expirationTimers[pr.id.toString()]?.cancel(); - int time = pr.expirationDate.difference(DateTime.now()).inMilliseconds; - if (time < 1) { - if (!mounted) return; - _remove(pr); - return; - } - _expirationTimers[pr.id.toString()] = Timer(Duration(milliseconds: time), () async => _remove(pr)); - } - - void _setupAllTimers(List pushRequests) { - _cancalAllTimers(); - for (var pr in pushRequests) { - int time = pr.expirationDate.difference(DateTime.now()).inMilliseconds; - if (time < 1) { - _remove(pr); - } - _expirationTimers[pr.id.toString()] = Timer(Duration(milliseconds: time), () async => _remove(pr)); - } - } - - Future _handleReaction({required PushRequest pushRequest, required PushToken token}) async { - if (pushRequest.accepted == null) return false; - Logger.info('Push auth request accepted=${pushRequest.accepted}, sending response to privacyidea', name: 'token_widgets.dart#handleReaction'); - // POST https://privacyideaserver/validate/check - // nonce= - // serial= - // signature= - // decline=1 (optional) - // presence_answer= (optional) - final Map body = { - 'nonce': pushRequest.nonce, - 'serial': token.serial, - }; - // signature ::= {nonce}|{serial}[|decline] - String msg = '${pushRequest.nonce}|${token.serial}'; - if (pushRequest.accepted! == false) { - body['decline'] = '1'; - msg += '|decline'; - } - if (pushRequest.possibleAnswers != null && pushRequest.selectedAnswer != null) { - body['presence_answer'] = pushRequest.selectedAnswer!; - msg += '|${pushRequest.selectedAnswer!}'; - } - Logger.warning('Signature message: $msg', name: 'token_widgets.dart#handleReaction'); - String? signature = await _rsaUtils.trySignWithToken(token, msg); - if (signature == null) { - Logger.warning('Failed to sign push request response.', name: 'token_widgets.dart#handleReaction'); - return false; - } - - body['signature'] = signature; - - Response response; - try { - Logger.info('Sending push request response.', name: 'token_widgets.dart#_handleReaction'); - response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body); - } catch (e) { - Logger.warning('Sending push request response failed. Retrying.', name: 'token_widgets.dart#handleReaction'); - try { - response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body); - } catch (e) { - Logger.warning('Sending push request response failed consistently.', name: 'token_widgets.dart#handleReaction', error: e); - ref.read(statusMessageProvider.notifier).state = (AppLocalizations.of(await globalContext)!.connectionFailed, null); - return false; - } - } - if (response.statusCode != 200) { - final appLocalizations = AppLocalizations.of(await globalContext)!; - ref.read(statusMessageProvider.notifier).state = ( - '${appLocalizations.sendPushRequestResponseFailed}\n${appLocalizations.statusCode(response.statusCode)}', - tryJsonDecode(response.body)?["result"]?["error"]?["message"], - ); - Logger.warning('Sending push request response failed.', name: 'token_widgets.dart#handleReaction'); - return false; - } - return true; - } -} +// /* +// privacyIDEA Authenticator + +// Authors: Timo Sturm +// Frank Merkel +// Copyright (c) 2017-2024 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 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:http/http.dart'; +// import 'package:mutex/mutex.dart'; + +// import '../../../interfaces/repo/push_request_repository.dart'; +// import '../../../l10n/app_localizations.dart'; +// import '../../../model/push_request.dart'; +// import '../../../model/riverpod_states/push_request_state.dart'; +// import '../../../model/tokens/push_token.dart'; +// import '../../../repo/secure_push_request_repository.dart'; +// import '../../custom_int_buffer.dart'; +// import '../../globals.dart'; +// import '../../logger.dart'; +// import '../../privacyidea_io_client.dart'; +// import '../../push_provider.dart'; +// import '../riverpod_providers/state_providers/status_message_provider.dart'; +// import '../../rsa_utils.dart'; +// import '../../utils.dart'; + +// class PushRequestNotifier extends StateNotifier { +// late final Future initState; +// final StateNotifierProviderRef ref; +// final loadingRepoMutex = Mutex(); +// final updatingRequestMutex = Mutex(); +// final PushRequestRepository _pushRepo; + +// PushProvider _pushProvider; +// PushProvider get pushProvider => _pushProvider; +// final PrivacyideaIOClient _ioClient; +// final RsaUtils _rsaUtils; + +// final Map _expirationTimers = {}; + +// PushRequestNotifier({ +// PushRequestState? initState, +// PrivacyideaIOClient? ioClient, +// required PushProvider pushProvider, +// required this.ref, +// RsaUtils? rsaUtils, +// PushRequestRepository? pushRepo, +// }) : _ioClient = ioClient ?? const PrivacyideaIOClient(), +// _pushProvider = pushProvider, +// _rsaUtils = rsaUtils ?? const RsaUtils(), +// _pushRepo = pushRepo ?? const SecurePushRequestRepository(), +// super( +// initState ?? PushRequestState(pushRequests: [], knownPushRequests: CustomIntBuffer(list: [])), +// ) { +// _init(initState); +// } + +// void swapPushProvider(PushProvider newProvider) { +// _pushProvider.unsubscribe(add); +// _pushProvider = newProvider; +// _pushProvider.subscribe(add); +// } + +// Future _init(PushRequestState? initialState) async { +// initState = initialState != null ? Future.value(initialState) : _loadFromRepo(); +// _pushProvider.subscribe(add); +// await initState; +// Logger.info('PushRequestNotifier initialized', name: 'push_request_notifier.dart#_init'); +// } + +// @override +// void dispose() { +// _pushProvider.unsubscribe(add); +// _cancalAllTimers(); +// super.dispose(); +// } + +// /* +// ///////////////////////////////////////////////////////////////////////////// +// //////////////////// Repository and PushRequest Handling //////////////////// +// ///////////////////////////////////////////////////////////////////////////// +// /// Repository layer is always use loadingRepoMutex for the latest state +// */ + +// Future _loadFromRepo() async { +// await loadingRepoMutex.acquire(); +// final PushRequestState loadedState; +// try { +// loadedState = await _pushRepo.loadState(); +// } catch (e) { +// Logger.error('Failed to load push request state from repo.', name: 'push_request_notifier.dart#_loadFromRepo', error: e); +// loadingRepoMutex.release(); +// return state; +// } +// _renewTimers(loadedState.pushRequests); +// state = loadedState; +// loadingRepoMutex.release(); +// return loadedState; +// } + +// /// Adds a PushRequest to repo and state. Returns true if successful, false if not. +// /// If the request already exists, it will be replaced. +// Future _addOrReplacePushRequest(PushRequest pushRequest) async { +// await loadingRepoMutex.acquire(); +// final oldState = state; +// final newState = oldState.addOrReplace(pushRequest); +// try { +// await _pushRepo.saveState(newState); +// } catch (e) { +// Logger.warning( +// 'Failed to save push request: $pushRequest', +// name: 'push_request_notifier.dart#_addOrReplacePushRequest', +// error: e, +// ); +// loadingRepoMutex.release(); +// return false; +// } +// state = newState; +// loadingRepoMutex.release(); +// return true; +// } + +// /// Replaces a PushRequest in repo and state. Returns true if successful, false if not. +// Future _replacePushRequest(PushRequest pushRequest) async { +// await loadingRepoMutex.acquire(); +// final oldState = state; +// final (newState, replaced) = oldState.replaceRequest(pushRequest); +// if (!replaced) { +// Logger.warning( +// 'Tried to replace a push request that does not exist.', +// name: 'push_request_notifier.dart#_replacePushRequest', +// ); +// loadingRepoMutex.release(); +// return false; +// } +// try { +// await _pushRepo.saveState(newState); +// } catch (e) { +// Logger.warning( +// 'Failed to save push request: $pushRequest', +// name: 'push_request_notifier.dart#_replacePushRequest', +// error: e, +// ); +// loadingRepoMutex.release(); +// return false; +// } +// state = newState; +// loadingRepoMutex.release(); +// return true; +// } + +// /// Removes a PushRequest from repo and state. Returns true if successful, false if not. +// Future _remove(PushRequest pushRequest) async { +// _cancelTimer(pushRequest); +// await loadingRepoMutex.acquire(); +// final newState = state.withoutRequest(pushRequest); +// try { +// await _pushRepo.saveState(newState); +// } catch (e) { +// Logger.error( +// 'Failed to save push request state after removing push request.', +// name: 'push_request_notifier.dart#_remove', +// error: e, +// ); +// loadingRepoMutex.release(); +// _setupTimer(pushRequest); +// return false; +// } +// state = newState; +// loadingRepoMutex.release(); +// return true; +// } +// /* +// ////////////////////////////////////////////////////////////////////////////// +// ////////////////////// Update PushRequest Methods //////////////////////////// +// ////////////////////////////////////////////////////////////////////////////// +// /// Updating layer is always use updatingRequestMutex for the latest state +// */ + +// /// Updates a PushRequest of the current state. The updated PushRequest is saved to the repo and the state. Returns the updated PushRequest if successful, null if not. +// Future _updatePushRequest(PushRequest pushRequest, Future Function(PushRequest) updater) async { +// await updatingRequestMutex.acquire(); +// final current = state.currentOf(pushRequest); +// if (current == null) { +// Logger.warning('Tried to update a push request that does not exist.', name: 'push_request_notifier.dart#updatePushRequest'); +// updatingRequestMutex.release(); +// return null; +// } +// final updated = await updater(current); +// final replaced = await _replacePushRequest(updated); +// updatingRequestMutex.release(); +// return replaced ? updated : current; +// } + +// /* +// ////////////////////////////////////////////////////////////////////////////// +// //////////////////////////// Public Methods ////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////// +// /// There is no need to use mutexes because the updating functions are always using the latest version of the updating tokens. +// */ + +// Future pollForChallenges({required bool isManually}) => pushProvider.pollForChallenges(isManually: isManually); + +// Future loadStateFromRepo() => _loadFromRepo(); + +// /// Accepts a push request and returns true if successful, false if not. +// /// An accepted push request is removed from the state. +// /// It should be still in the CustomIntBuffer of the state. +// Future accept(PushToken pushToken, PushRequest pushRequest, {String? selectedAnswer}) async { +// if (pushRequest.accepted != null) { +// Logger.warning('The push request is already accepted or declined.', name: 'push_request_notifier.dart#accept'); + +// return false; +// } +// Logger.info('Accept push request.', name: 'push_request_notifier.dart#accept'); +// final updated = await _updatePushRequest(pushRequest, (p0) async { +// final updated = p0.copyWith(accepted: true, selectedAnswer: () => selectedAnswer); +// final success = await _handleReaction(pushRequest: updated, token: pushToken); +// if (!success) { +// Logger.warning('Failed to handle push request reaction.', name: 'push_request_notifier.dart#accept'); +// return p0; +// } +// return updated; +// }); +// if (updated == null || updated.accepted != true) { +// Logger.warning('Failed to accept push request.', name: 'push_request_notifier.dart#accept'); +// return false; +// } +// await _remove(updated); +// return true; +// } + +// Future decline(PushToken pushToken, PushRequest pushRequest) async { +// if (pushRequest.accepted != null) { +// Logger.warning('The push request is already accepted or declined.', name: 'push_request_notifier.dart#decline'); +// return false; +// } +// Logger.info('Decline push request.', name: 'push_request_notifier.dart#decline'); +// final updated = await _updatePushRequest(pushRequest, (p0) async { +// final updated = p0.copyWith(accepted: false, selectedAnswer: () => null); +// final success = await _handleReaction(pushRequest: updated, token: pushToken); +// if (!success) { +// Logger.warning('Failed to handle push request reaction.', name: 'push_request_notifier.dart#accept'); +// return p0; +// } +// return updated; +// }); +// if (updated == null || updated.accepted != false) { +// Logger.warning('Failed to decline push request.', name: 'push_request_notifier.dart#decline'); +// return false; +// } +// await _remove(updated); +// return true; +// } + +// Future add(PushRequest pr) async { +// if (state.knowsRequestId(pr.id)) { +// Logger.info( +// 'The push request already exists.', +// name: 'token_notifier.dart#addPushRequestToToken', +// ); +// return false; +// } +// // Save the pending request. +// final success = await _addOrReplacePushRequest(pr); + +// // Remove the request after it expires. +// if (success) _setupTimer(pr); +// Logger.info('Added push request ${pr.id} to state', name: 'token_notifier.dart#addPushRequestToToken'); +// return true; +// } + +// Future remove(PushRequest pushRequest) => _remove(pushRequest); + +// ////////////////////////////////////////////////////////////////////////////// +// ///////////////////////// Helper Methods ///////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////// + +// void _renewTimers(List pushRequests) { +// _cancalAllTimers(); +// _setupAllTimers(pushRequests); +// } + +// void _cancelTimer(PushRequest pr) { +// Logger.info('Canceling timer for push request ${pr.id}', name: 'push_request_notifier.dart#_cancelTimer'); +// final timer = _expirationTimers.remove(pr.id.toString())?..cancel(); +// if (timer == null) { +// Logger.warning('Timer for push request ${pr.id} not found.', name: 'push_request_notifier.dart#_cancelTimer'); +// } +// } + +// void _cancalAllTimers() { +// if (_expirationTimers.keys.isNotEmpty) { +// Logger.info('Canceling all timers: [${_expirationTimers.keys}]', name: 'push_request_notifier.dart#_cancelAllTimers'); +// } +// final ids = _expirationTimers.keys.toList(); +// for (var id in ids) { +// _expirationTimers.remove(id.toString())?.cancel(); +// } +// } + +// /// Sets up a timer to remove the push request after it expires. +// /// If the request is already expired, it will be removed immediately. +// /// When the timer is set up, the old timer is canceled. +// void _setupTimer(PushRequest pr) { +// _expirationTimers[pr.id.toString()]?.cancel(); +// int time = pr.expirationDate.difference(DateTime.now()).inMilliseconds; +// if (time < 1) { +// if (!mounted) return; +// _remove(pr); +// return; +// } +// _expirationTimers[pr.id.toString()] = Timer(Duration(milliseconds: time), () async => _remove(pr)); +// } + +// void _setupAllTimers(List pushRequests) { +// _cancalAllTimers(); +// for (var pr in pushRequests) { +// int time = pr.expirationDate.difference(DateTime.now()).inMilliseconds; +// if (time < 1) { +// _remove(pr); +// } +// _expirationTimers[pr.id.toString()] = Timer(Duration(milliseconds: time), () async => _remove(pr)); +// } +// } + +// Future _handleReaction({required PushRequest pushRequest, required PushToken token}) async { +// if (pushRequest.accepted == null) return false; +// Logger.info('Push auth request accepted=${pushRequest.accepted}, sending response to privacyidea', name: 'token_widgets.dart#handleReaction'); +// // POST https://privacyideaserver/validate/check +// // nonce= +// // serial= +// // signature= +// // decline=1 (optional) +// // presence_answer= (optional) +// final Map body = { +// 'nonce': pushRequest.nonce, +// 'serial': token.serial, +// }; +// // signature ::= {nonce}|{serial}[|decline] +// String msg = '${pushRequest.nonce}|${token.serial}'; +// if (pushRequest.accepted! == false) { +// body['decline'] = '1'; +// msg += '|decline'; +// } +// if (pushRequest.possibleAnswers != null && pushRequest.selectedAnswer != null) { +// body['presence_answer'] = pushRequest.selectedAnswer!; +// msg += '|${pushRequest.selectedAnswer!}'; +// } +// Logger.warning('Signature message: $msg', name: 'token_widgets.dart#handleReaction'); +// String? signature = await _rsaUtils.trySignWithToken(token, msg); +// if (signature == null) { +// Logger.warning('Failed to sign push request response.', name: 'token_widgets.dart#handleReaction'); +// return false; +// } + +// body['signature'] = signature; + +// Response response; +// try { +// Logger.info('Sending push request response.', name: 'token_widgets.dart#_handleReaction'); +// response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body); +// } catch (e) { +// Logger.warning('Sending push request response failed. Retrying.', name: 'token_widgets.dart#handleReaction'); +// try { +// response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body); +// } catch (e) { +// Logger.warning('Sending push request response failed consistently.', name: 'token_widgets.dart#handleReaction', error: e); +// ref.read(statusMessageProvider.notifier).state = (AppLocalizations.of(await globalContext)!.connectionFailed, null); +// return false; +// } +// } +// if (response.statusCode != 200) { +// final appLocalizations = AppLocalizations.of(await globalContext)!; +// ref.read(statusMessageProvider.notifier).state = ( +// '${appLocalizations.sendPushRequestResponseFailed}\n${appLocalizations.statusCode(response.statusCode)}', +// tryJsonDecode(response.body)?["result"]?["error"]?["message"], +// ); +// Logger.warning('Sending push request response failed.', name: 'token_widgets.dart#handleReaction'); +// return false; +// } +// return true; +// } +// } diff --git a/lib/utils/riverpod/state_notifiers/settings_notifier.dart b/lib/utils/riverpod/state_notifiers/settings_notifier.dart index e12e319d4..5a45b5292 100644 --- a/lib/utils/riverpod/state_notifiers/settings_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/settings_notifier.dart @@ -1,155 +1,155 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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:ui'; - -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import '../../../interfaces/repo/settings_repository.dart'; -import '../../../model/riverpod_states/settings_state.dart'; -import '../../../model/version.dart'; -import '../../logger.dart'; -import '../../push_provider.dart'; - -/// This class provies access to the device specific settings. -/// It also ensures that the settings are saved to the device. -/// To Update a state use: ref.read(settingsProvider.notifier).anyMethod(value) -class SettingsNotifier extends StateNotifier { - late Future loadingRepo; - final SettingsRepository _repo; - - SettingsNotifier({ - required SettingsRepository repository, - SettingsState? initialState, - }) : _repo = repository, - super(initialState ?? SettingsState()) { - loadFromRepo(); - } - Future loadFromRepo() async { - loadingRepo = Future(() async { - final newState = await _repo.loadSettings(); - PushProvider.instance?.setPollingEnabled(state.enablePolling); - state = newState; - Logger.info('Loading settings from repo: $newState', name: 'settings_notifier.dart#_loadFromRepo'); - return newState; - }); - await loadingRepo; - } - - void _saveToRepo() async { - loadingRepo = Future(() async { - await _repo.saveSettings(state); - Logger.info('Saving settings to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); - return state; - }); - } - - void addCrashReportRecipient(String email) { - Logger.info('Crash report recipient added: $email', name: 'settings_notifier.dart#addCrashReportRecipient'); - var updatedSet = state.crashReportRecipients..add(email); - state = state.copyWith(crashReportRecipients: updatedSet); - _saveToRepo(); - } - - set isFirstRun(bool value) { - Logger.info('First run set to $value', name: 'settings_notifier.dart#setFirstRun'); - state = state.copyWith(isFirstRun: value); - _saveToRepo(); - } - - set hideOTPs(bool value) { - Logger.info('Hide OTPs set to $value', name: 'settings_notifier.dart#setHideOTPs'); - state = state.copyWith(hideOpts: value); - _saveToRepo(); - } - - set showGuideOnStart(bool value) { - Logger.info('Show guide on start set to $value', name: 'settings_notifier.dart#setShowGuideOnStart'); - state = state.copyWith(showGuideOnStart: value); - _saveToRepo(); - } - - void setLocalePreference(Locale locale) { - Logger.info('Locale set to $locale', name: 'settings_notifier.dart#setLocalePreference'); - state = state.copyWith(localePreference: locale); - _saveToRepo(); - } - - void setUseSystemLocale(bool value) { - Logger.info('Use system locale set to $value', name: 'settings_notifier.dart#setUseSystemLocale'); - state = state.copyWith(useSystemLocale: value); - _saveToRepo(); - } - - void enablePolling() { - Logger.info('Polling set to true', name: 'settings_notifier.dart#enablePolling'); - state = state.copyWith(enablePolling: true); - _saveToRepo(); - } - - void disablePolling() { - Logger.info('Polling set to false', name: 'settings_notifier.dart#disablePolling'); - state = state.copyWith(enablePolling: false); - _saveToRepo(); - } - - void setPolling(bool value) { - Logger.info('Polling set to $value', name: 'settings_notifier.dart#setPolling'); - state = state.copyWith(enablePolling: value); - _saveToRepo(); - } - - void setLocale(Locale locale) { - Logger.info('Locale set to $locale', name: 'settings_notifier.dart#setLocale'); - state = state.copyWith(localePreference: locale); - _saveToRepo(); - } - - void setVerboseLogging(bool value) { - Logger.info('Verbose logging set to $value', name: 'settings_notifier.dart#setVerboseLogging'); - state = state.copyWith(verboseLogging: value); - _saveToRepo(); - } - - void toggleVerboseLogging() { - final value = !state.verboseLogging; - Logger.info('Verbose logging set to $value', name: 'settings_notifier.dart#setVerboseLogging'); - state = state.copyWith(verboseLogging: value); - _saveToRepo(); - } - - void setFirstRun(bool value) { - Logger.info('First run set to $value', name: 'settings_notifier.dart#setFirstRun'); - state = state.copyWith(isFirstRun: value); - _saveToRepo(); - } - - void setHidePushTokens(bool value) { - Logger.info('Hide push tokens set to $value', name: 'settings_notifier.dart#setHidePushTokens'); - state = state.copyWith(hidePushTokens: value); - _saveToRepo(); - } - - void setLatestStartedVersion(Version version) { - if (state.latestStartedVersion >= version) return; - Logger.info('Latest started version set to $version', name: 'settings_notifier.dart#setLatestStartedVersion'); - state = state.copyWith(latestStartedVersion: version); - _saveToRepo(); - } -} +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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:ui'; + +// import 'package:flutter_riverpod/flutter_riverpod.dart'; + +// import '../../../interfaces/repo/settings_repository.dart'; +// import '../../../model/riverpod_states/settings_state.dart'; +// import '../../../model/version.dart'; +// import '../../logger.dart'; +// import '../../push_provider.dart'; + +// /// This class provies access to the device specific settings. +// /// It also ensures that the settings are saved to the device. +// /// To Update a state use: ref.read(settingsProvider.notifier).anyMethod(value) +// class SettingsNotifier extends StateNotifier { +// late Future loadingRepo; +// final SettingsRepository _repo; + +// SettingsNotifier({ +// required SettingsRepository repository, +// SettingsState? initialState, +// }) : _repo = repository, +// super(initialState ?? SettingsState()) { +// loadFromRepo(); +// } +// Future loadFromRepo() async { +// loadingRepo = Future(() async { +// final newState = await _repo.loadSettings(); +// PushProvider.instance?.setPollingEnabled(state.enablePolling); +// state = newState; +// Logger.info('Loading settings from repo: $newState', name: 'settings_notifier.dart#_loadFromRepo'); +// return newState; +// }); +// await loadingRepo; +// } + +// void _saveToRepo() async { +// loadingRepo = Future(() async { +// await _repo.saveSettings(state); +// Logger.info('Saving settings to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); +// return state; +// }); +// } + +// void addCrashReportRecipient(String email) { +// Logger.info('Crash report recipient added: $email', name: 'settings_notifier.dart#addCrashReportRecipient'); +// var updatedSet = state.crashReportRecipients..add(email); +// state = state.copyWith(crashReportRecipients: updatedSet); +// _saveToRepo(); +// } + +// set isFirstRun(bool value) { +// Logger.info('First run set to $value', name: 'settings_notifier.dart#setFirstRun'); +// state = state.copyWith(isFirstRun: value); +// _saveToRepo(); +// } + +// set hideOTPs(bool value) { +// Logger.info('Hide OTPs set to $value', name: 'settings_notifier.dart#setHideOTPs'); +// state = state.copyWith(hideOpts: value); +// _saveToRepo(); +// } + +// set showGuideOnStart(bool value) { +// Logger.info('Show guide on start set to $value', name: 'settings_notifier.dart#setShowGuideOnStart'); +// state = state.copyWith(showGuideOnStart: value); +// _saveToRepo(); +// } + +// void setLocalePreference(Locale locale) { +// Logger.info('Locale set to $locale', name: 'settings_notifier.dart#setLocalePreference'); +// state = state.copyWith(localePreference: locale); +// _saveToRepo(); +// } + +// void setUseSystemLocale(bool value) { +// Logger.info('Use system locale set to $value', name: 'settings_notifier.dart#setUseSystemLocale'); +// state = state.copyWith(useSystemLocale: value); +// _saveToRepo(); +// } + +// void enablePolling() { +// Logger.info('Polling set to true', name: 'settings_notifier.dart#enablePolling'); +// state = state.copyWith(enablePolling: true); +// _saveToRepo(); +// } + +// void disablePolling() { +// Logger.info('Polling set to false', name: 'settings_notifier.dart#disablePolling'); +// state = state.copyWith(enablePolling: false); +// _saveToRepo(); +// } + +// void setPolling(bool value) { +// Logger.info('Polling set to $value', name: 'settings_notifier.dart#setPolling'); +// state = state.copyWith(enablePolling: value); +// _saveToRepo(); +// } + +// void setLocale(Locale locale) { +// Logger.info('Locale set to $locale', name: 'settings_notifier.dart#setLocale'); +// state = state.copyWith(localePreference: locale); +// _saveToRepo(); +// } + +// void setVerboseLogging(bool value) { +// Logger.info('Verbose logging set to $value', name: 'settings_notifier.dart#setVerboseLogging'); +// state = state.copyWith(verboseLogging: value); +// _saveToRepo(); +// } + +// void toggleVerboseLogging() { +// final value = !state.verboseLogging; +// Logger.info('Verbose logging set to $value', name: 'settings_notifier.dart#setVerboseLogging'); +// state = state.copyWith(verboseLogging: value); +// _saveToRepo(); +// } + +// void setFirstRun(bool value) { +// Logger.info('First run set to $value', name: 'settings_notifier.dart#setFirstRun'); +// state = state.copyWith(isFirstRun: value); +// _saveToRepo(); +// } + +// void setHidePushTokens(bool value) { +// Logger.info('Hide push tokens set to $value', name: 'settings_notifier.dart#setHidePushTokens'); +// state = state.copyWith(hidePushTokens: value); +// _saveToRepo(); +// } + +// void setLatestStartedVersion(Version version) { +// if (state.latestStartedVersion >= version) return; +// Logger.info('Latest started version set to $version', name: 'settings_notifier.dart#setLatestStartedVersion'); +// state = state.copyWith(latestStartedVersion: version); +// _saveToRepo(); +// } +// } diff --git a/lib/utils/riverpod/state_notifiers/token_notifier.dart b/lib/utils/riverpod/state_notifiers/token_notifier.dart index 68b1eb9e5..a6c156ef1 100644 --- a/lib/utils/riverpod/state_notifiers/token_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_notifier.dart @@ -931,7 +931,7 @@ class TokenNotifier extends StateNotifier { Logger.info('Handling push tokens if they exist.', name: 'token_notifier.dart#_handlePushTokensIfExist'); final pushTokens = state.pushTokens; if (pushTokens.isEmpty || state.pushTokens.isEmpty) { - if (ref.read(settingsProvider).hidePushTokens == true) { + if ((await ref.read(settingsProvider.future)).hidePushTokens == true) { ref.read(settingsProvider.notifier).setHidePushTokens(false); } } diff --git a/lib/views/main_view/main_view.dart b/lib/views/main_view/main_view.dart index 1bb33dd5c..3f0abc27b 100644 --- a/lib/views/main_view/main_view.dart +++ b/lib/views/main_view/main_view.dart @@ -61,7 +61,7 @@ class _MainViewState extends ConsumerState { @override void initState() { super.initState(); - final latestStartedVersion = globalRef?.read(settingsProvider).latestStartedVersion; + final latestStartedVersion = globalRef?.read(settingsProvider).whenOrNull(data: (data) => data)?.latestStartedVersion; Logger.info('Latest started version: $latestStartedVersion', name: 'main_view.dart#initState'); if (latestStartedVersion == null || widget.disablePatchNotes) return; WidgetsBinding.instance.addPostFrameCallback((_) { 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 73aa6815e..236c655bb 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 @@ -27,6 +27,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import '../../../../l10n/app_localizations.dart'; +import '../../../../model/riverpod_states/settings_state.dart'; import '../../../../model/riverpod_states/token_filter.dart'; import '../../../../model/token_folder.dart'; import '../../../../model/tokens/push_token.dart'; @@ -95,7 +96,8 @@ class _TokenFolderExpandableState extends ConsumerState w @override ExpandablePanel build(BuildContext context) { - final tokens = ref.watch(tokenProvider).tokensInFolder(widget.folder, exclude: ref.watch(settingsProvider).hidePushTokens ? [PushToken] : []); + final hitePushTokens = ref.watch(settingsProvider).whenOrNull(data: (data) => data.hidePushTokens) ?? SettingsState.hidePushTokensDefault; + final tokens = ref.watch(tokenProvider).tokensInFolder(widget.folder, exclude: hitePushTokens ? [PushToken] : []); tokens.sort((a, b) => a.compareTo(b)); final draggingSortable = ref.watch(draggingSortableProvider); if (widget.expandOverride == null) { diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart index ec296a3e7..fa48c5f81 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart @@ -19,7 +19,6 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/introduction.dart'; @@ -40,6 +39,7 @@ class MainViewNavigationBar extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { BoxConstraints? constraints = ref.watch(appConstraintsProvider); + final introProv = ref.watch(introductionNotifierProvider); constraints ??= const BoxConstraints(); final navWidth = constraints.maxWidth; final navHeight = constraints.maxHeight * 0.10; @@ -67,11 +67,7 @@ class MainViewNavigationBar extends ConsumerWidget { onComplete: () { ref.read(introductionNotifierProvider.notifier).complete(Introduction.scanQrCode); }, - isFocused: ref.watch(introductionNotifierProvider).when( - data: (data) => data.isConditionFulfilled(ref, Introduction.scanQrCode), - error: (_, __) => false, - loading: () => false, - ), + isFocused: introProv.whenOrNull(data: (data) => data.isConditionFulfilled(ref, Introduction.scanQrCode)) ?? false, tooltipWhenFocused: AppLocalizations.of(context)!.introScanQrCode, child: const QrScannerButton()), ), @@ -99,11 +95,7 @@ class MainViewNavigationBar extends ConsumerWidget { }, icon: FocusedItemAsOverlay( onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.addManually), - isFocused: ref.watch(introductionNotifierProvider).when( - data: (data) => data.isConditionFulfilled(ref, Introduction.addManually), - error: (_, __) => false, - loading: () => false, - ), + isFocused: introProv.whenOrNull(data: (data) => data.isConditionFulfilled(ref, Introduction.addManually)) ?? false, tooltipWhenFocused: AppLocalizations.of(context)!.introAddTokenManually, child: FittedBox( child: Icon( @@ -122,14 +114,7 @@ class MainViewNavigationBar extends ConsumerWidget { child: Padding( padding: EdgeInsets.only(top: navHeight * 0.1, bottom: navHeight * 0.2), child: FocusedItemAsOverlay( - isFocused: ref.watch(introductionNotifierProvider).when( - data: (data) { - Logger.info('IntroductionNotifier isConditionFulfilled: ${data.isConditionFulfilled(ref, Introduction.addFolder)}'); - return data.isConditionFulfilled(ref, Introduction.addFolder); - }, - error: (_, __) => false, - loading: () => false, - ), + isFocused: introProv.whenOrNull(data: (data) => data.isConditionFulfilled(ref, Introduction.addFolder)) ?? false, tooltipWhenFocused: AppLocalizations.of(context)!.introAddFolder, onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.addFolder), child: AppBarItem( diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart index 5de50dbd4..50301156b 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart @@ -19,6 +19,7 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../model/enums/introduction.dart'; @@ -33,27 +34,25 @@ class LicensePushViewButton extends ConsumerWidget { const LicensePushViewButton({super.key}); @override - Widget build(BuildContext context, WidgetRef ref) { - final hidePushTokens = ref.watch(settingsProvider).hidePushTokens; - return hidePushTokens - ? FocusedItemAsOverlay( - isFocused: ref.watch(introductionNotifierProvider).when( - data: (value) => value.isConditionFulfilled(ref, Introduction.hidePushTokens), - error: (Object error, StackTrace stackTrace) => false, - loading: () => false, - ), - tooltipWhenFocused: AppLocalizations.of(context)!.introHidePushTokens, - onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.hidePushTokens), - child: AppBarItem( - tooltip: AppLocalizations.of(context)!.pushTokens, - onPressed: () => Navigator.pushNamed(context, PushTokensView.routeName), - icon: const Icon(Icons.notifications), - ), - ) - : AppBarItem( - tooltip: AppLocalizations.of(context)!.licenses, - onPressed: () => Navigator.of(context).pushNamed(LicenseView.routeName), - icon: const Icon(Icons.info_outline), - ); - } + Widget build(BuildContext context, WidgetRef ref) => + (ref.watch(settingsProvider).whenOrNull(data: (data) => data.hidePushTokens) ?? SettingsState.hidePushTokensDefault) + ? FocusedItemAsOverlay( + isFocused: ref.watch(introductionNotifierProvider).when( + data: (value) => value.isConditionFulfilled(ref, Introduction.hidePushTokens), + error: (Object error, StackTrace stackTrace) => false, + loading: () => false, + ), + tooltipWhenFocused: AppLocalizations.of(context)!.introHidePushTokens, + onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.hidePushTokens), + child: AppBarItem( + tooltip: AppLocalizations.of(context)!.pushTokens, + onPressed: () => Navigator.pushNamed(context, PushTokensView.routeName), + icon: const Icon(Icons.notifications), + ), + ) + : AppBarItem( + tooltip: AppLocalizations.of(context)!.licenses, + onPressed: () => Navigator.of(context).pushNamed(LicenseView.routeName), + icon: const Icon(Icons.info_outline), + ); } 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 13fe8d787..a52021f50 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 @@ -23,6 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../model/mixins/sortable_mixin.dart'; +import '../../../model/riverpod_states/settings_state.dart'; import '../../../model/token_folder.dart'; import '../../../model/tokens/push_token.dart'; import '../../../model/tokens/token.dart'; @@ -84,7 +85,8 @@ class _MainViewTokensListState extends ConsumerState { final draggingSortable = ref.watch(draggingSortableProvider); final allSortables = ref.watch(sortableProvider); final allowToRefresh = allSortables.any((element) => element is PushToken); - bool filterPushTokens = ref.watch(settingsProvider).hidePushTokens && allowToRefresh; + final hidePushTokens = ref.watch(settingsProvider).whenOrNull(data: (data) => data.hidePushTokens) ?? SettingsState.hidePushTokensDefault; + final filterPushTokens = hidePushTokens && allowToRefresh; final showSortables = []; // List of sortables that should be shown in the list for (var element in allSortables) { diff --git a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart index aca4baa46..e865a3e93 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart @@ -26,6 +26,7 @@ import 'package:intl/intl.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/enums/day_password_token_view_mode.dart'; +import '../../../../../model/riverpod_states/settings_state.dart'; import '../../../../../model/tokens/day_password_token.dart'; import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; @@ -87,7 +88,7 @@ class _DayPasswordTokenWidgetTileState extends ConsumerState data.currentLocale) ?? SettingsState.localeDefault; final dateTimeTokenEnd = widget.token.nextOTPTimeStart; final yMdFormat = DateFormat.yMMMd(currentLocale.languageCode); final yMdString = yMdFormat.format(dateTimeTokenEnd); diff --git a/lib/views/settings_view/settings_groups/settings_group_language.dart b/lib/views/settings_view/settings_groups/settings_group_language.dart index 0c803b56c..fbac7c690 100644 --- a/lib/views/settings_view/settings_groups/settings_group_language.dart +++ b/lib/views/settings_view/settings_groups/settings_group_language.dart @@ -19,6 +19,7 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import '../../../l10n/app_localizations.dart'; import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; @@ -30,6 +31,9 @@ class SettingsGroupLanguage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final localizations = AppLocalizations.of(context)!; + final settings = ref.watch(settingsProvider).whenOrNull(data: (data) => data); + final useSystemLocale = settings?.useSystemLocale ?? SettingsState.useSystemLocaleDefault; + final currentLocale = settings?.currentLocale ?? SettingsState.localeDefault; return SettingsGroup( title: localizations.language, children: [ @@ -42,31 +46,31 @@ class SettingsGroupLanguage extends ConsumerWidget { localizations.useDeviceLocaleDescription, overflow: TextOverflow.fade, ), - value: ref.watch(settingsProvider).useSystemLocale, + value: useSystemLocale, onChanged: (value) => ref.read(settingsProvider.notifier).setUseSystemLocale(value)), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: DropdownButton( disabledHint: Text( - '${ref.watch(settingsProvider).currentLocale}', + '$currentLocale', style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.grey), overflow: TextOverflow.fade, softWrap: false, ), isExpanded: true, - value: ref.watch(settingsProvider).currentLocale, + value: currentLocale, items: AppLocalizations.supportedLocales.map>((Locale itemLocale) { return DropdownMenuItem( value: itemLocale, child: Text( '$itemLocale', overflow: TextOverflow.fade, - style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: ref.watch(settingsProvider).useSystemLocale ? Colors.grey : null), + style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: useSystemLocale ? Colors.grey : null), softWrap: false, ), ); }).toList(), - onChanged: ref.watch(settingsProvider).useSystemLocale ? null : (value) => ref.read(settingsProvider.notifier).setLocalePreference(value!), + onChanged: useSystemLocale ? null : (value) => ref.read(settingsProvider.notifier).setLocalePreference(value!), ), ), ], 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 d86e894c0..416323bb9 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 @@ -21,6 +21,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; +import '../../../model/riverpod_states/settings_state.dart'; import '../../../model/tokens/push_token.dart'; import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; @@ -37,96 +38,99 @@ class SettingsGroupPushToken extends ConsumerWidget { }); @override - Widget build(BuildContext context, WidgetRef ref) => SettingsGroup( - isActive: enablePushSettingsGroup, - title: AppLocalizations.of(context)!.pushToken, - children: [ - ListTile( - title: Text( - AppLocalizations.of(context)!.synchronizePushTokens, - style: Theme.of(context).textTheme.bodyMedium, - ), - subtitle: Text( - AppLocalizations.of(context)!.synchronizesTokensWithServer, - overflow: TextOverflow.fade, - ), - trailing: ElevatedButton( - onPressed: enablePushSettingsGroup - ? () { - showDialog( - useRootNavigator: false, - context: context, - barrierDismissible: false, - builder: (context) => const UpdateFirebaseTokenDialog(), - ); - } - : null, - child: Text( - AppLocalizations.of(context)!.sync, - overflow: TextOverflow.fade, - softWrap: false, - ), - ), + Widget build(BuildContext context, WidgetRef ref) { + final settingsState = ref.watch(settingsProvider).whenOrNull(data: (data) => data); + return SettingsGroup( + isActive: enablePushSettingsGroup, + title: AppLocalizations.of(context)!.pushToken, + children: [ + ListTile( + title: Text( + AppLocalizations.of(context)!.synchronizePushTokens, + style: Theme.of(context).textTheme.bodyMedium, ), - ListTile( - title: RichText( - text: TextSpan( - children: [ - TextSpan( - text: AppLocalizations.of(context)!.enablePolling, - style: Theme.of(context).textTheme.bodyMedium, - ), - // Add clickable icon to inform user of unsupported push tokens (for polling) - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(left: 10), - child: unsupportedPushTokens.isNotEmpty && enablePushSettingsGroup - ? GestureDetector( - onTap: () => _showPollingInfo(context, unsupportedPushTokens), - child: const Icon( - Icons.info_outline, - color: Colors.red, - ), - ) - : null, - ), - ), - ], - ), - ), - subtitle: Text( - AppLocalizations.of(context)!.requestPushChallengesPeriodically, + subtitle: Text( + AppLocalizations.of(context)!.synchronizesTokensWithServer, + overflow: TextOverflow.fade, + ), + trailing: ElevatedButton( + onPressed: enablePushSettingsGroup + ? () { + showDialog( + useRootNavigator: false, + context: context, + barrierDismissible: false, + builder: (context) => const UpdateFirebaseTokenDialog(), + ); + } + : null, + child: Text( + AppLocalizations.of(context)!.sync, overflow: TextOverflow.fade, - ), - trailing: Switch( - value: ref.watch(settingsProvider).enablePolling, - onChanged: enablePushSettingsGroup ? (value) => ref.read(settingsProvider.notifier).setPolling(value) : null, + softWrap: false, ), ), - ListTile( - title: RichText( - text: TextSpan( - children: [ - TextSpan( - text: AppLocalizations.of(context)!.hidePushTokens, - style: Theme.of(context).textTheme.bodyMedium, + ), + ListTile( + title: RichText( + text: TextSpan( + children: [ + TextSpan( + text: AppLocalizations.of(context)!.enablePolling, + style: Theme.of(context).textTheme.bodyMedium, + ), + // Add clickable icon to inform user of unsupported push tokens (for polling) + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(left: 10), + child: unsupportedPushTokens.isNotEmpty && enablePushSettingsGroup + ? GestureDetector( + onTap: () => _showPollingInfo(context, unsupportedPushTokens), + child: const Icon( + Icons.info_outline, + color: Colors.red, + ), + ) + : null, ), - ], - ), - ), - subtitle: Text( - AppLocalizations.of(context)!.hidePushTokensDescription, - overflow: TextOverflow.fade, + ), + ], ), - trailing: Switch( - value: ref.watch(settingsProvider).hidePushTokens, - onChanged: enablePushSettingsGroup && ref.watch(tokenProvider).hasOTPTokens - ? (value) => ref.read(settingsProvider.notifier).setHidePushTokens(value) - : null, + ), + subtitle: Text( + AppLocalizations.of(context)!.requestPushChallengesPeriodically, + overflow: TextOverflow.fade, + ), + trailing: Switch( + value: settingsState?.enablePolling ?? SettingsState.enablePollingDefault, + onChanged: enablePushSettingsGroup ? (value) => ref.read(settingsProvider.notifier).setPolling(value) : null, + ), + ), + ListTile( + title: RichText( + text: TextSpan( + children: [ + TextSpan( + text: AppLocalizations.of(context)!.hidePushTokens, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], ), - ) - ], - ); + ), + subtitle: Text( + AppLocalizations.of(context)!.hidePushTokensDescription, + overflow: TextOverflow.fade, + ), + trailing: Switch( + value: settingsState?.hidePushTokens ?? SettingsState.hidePushTokensDefault, + onChanged: enablePushSettingsGroup && ref.watch(tokenProvider).hasOTPTokens + ? (value) => ref.read(settingsProvider.notifier).setHidePushTokens(value) + : null, + ), + ) + ], + ); + } /// Shows a dialog to the user that displays all push tokens that do not /// support polling. diff --git a/lib/views/settings_view/settings_view_widgets/logging_menu.dart b/lib/views/settings_view/settings_view_widgets/logging_menu.dart index e016a3551..f0f5392bf 100644 --- a/lib/views/settings_view/settings_view_widgets/logging_menu.dart +++ b/lib/views/settings_view/settings_view_widgets/logging_menu.dart @@ -19,6 +19,7 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import '../../../l10n/app_localizations.dart'; import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; @@ -31,49 +32,48 @@ class LoggingMenu extends ConsumerWidget { const LoggingMenu({super.key}); @override - Widget build(BuildContext context, WidgetRef ref) { - final settings = ref.watch(settingsProvider); - final verboseLogging = settings.verboseLogging; - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: DefaultDialog( - scrollable: true, - title: Text( - AppLocalizations.of(context)!.logMenu, - style: Theme.of(context).listTileTheme.titleTextStyle, - ), - content: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - title: Text( - AppLocalizations.of(context)!.verboseLogging, - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.center, + Widget build(BuildContext context, WidgetRef ref) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: DefaultDialog( + scrollable: true, + title: Text( + AppLocalizations.of(context)!.logMenu, + style: Theme.of(context).listTileTheme.titleTextStyle, + ), + content: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: Text( + AppLocalizations.of(context)!.verboseLogging, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + contentPadding: const EdgeInsets.all(0), + trailing: Switch( + value: ref.watch(settingsProvider).whenOrNull(data: (data) => data.verboseLogging) ?? SettingsState.verboseLoggingDefault, + onChanged: (value) => ref.read(settingsProvider.notifier).setVerboseLogging(value), + ), + style: ListTileStyle.list, + onTap: () => ref.read(settingsProvider.notifier).toggleVerboseLogging(), + ), + const Divider(), + const ShowErrorLogButton(), + const DeleteErrorlogButton(), + const SendErrorLogButton(), + ], + ), + actions: [ + TextButton( + child: Text( + AppLocalizations.of(context)!.dismiss, + overflow: TextOverflow.fade, + softWrap: false, ), - contentPadding: const EdgeInsets.all(0), - trailing: Switch(value: verboseLogging, onChanged: (value) => ref.read(settingsProvider.notifier).setVerboseLogging(value)), - style: ListTileStyle.list, - onTap: () => ref.read(settingsProvider.notifier).toggleVerboseLogging(), + onPressed: () => Navigator.pop(context), ), - const Divider(), - const ShowErrorLogButton(), - const DeleteErrorlogButton(), - const SendErrorLogButton(), ], ), - actions: [ - TextButton( - child: Text( - AppLocalizations.of(context)!.dismiss, - overflow: TextOverflow.fade, - softWrap: false, - ), - onPressed: () => Navigator.pop(context), - ), - ], - ), - ); - } + ); } diff --git a/lib/views/splash_screen/splash_screen.dart b/lib/views/splash_screen/splash_screen.dart index 412c20e71..1d4ef44c0 100644 --- a/lib/views/splash_screen/splash_screen.dart +++ b/lib/views/splash_screen/splash_screen.dart @@ -26,7 +26,6 @@ import '../../utils/customization/application_customization.dart'; import '../../utils/home_widget_utils.dart'; import '../../utils/logger.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../main_view/main_view.dart'; @@ -63,7 +62,7 @@ class _SplashScreenState extends ConsumerState { Future.wait( [ Future.delayed(_splashScreenDuration), - ref.read(settingsProvider.notifier).loadingRepo, + // ref.read(settingsProvider.future), ref.read(tokenProvider.notifier).initState, ref.read(tokenFolderProvider.notifier).initState, AppInfoUtils.init(), @@ -74,7 +73,14 @@ class _SplashScreenState extends ConsumerState { _navigate(); }, ).catchError((error) async { - Logger.error('Error while loading the app.', error: error, stackTrace: StackTrace.current, name: 'main.dart#initState'); + if (error is Error) { + Logger.error('Error while loading the app.', error: error, stackTrace: error.stackTrace, name: 'main.dart#initState'); + return []; + } + if (error is Exception) { + Logger.error('Error while loading the app.', error: error, stackTrace: StackTrace.current, name: 'main.dart#initState'); + return []; + } return []; }).then((values) async { if (!mounted) return; diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index a4c0ce9a5..6a27b5c54 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -13,7 +13,7 @@ import '../utils/logger.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart'; import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../utils/riverpod/state_listeners/home_widget_deep_link_listener.dart'; diff --git a/lib/widgets/dialog_widgets/push_request_dialog.dart b/lib/widgets/dialog_widgets/push_request_dialog.dart index 2697d433d..83993b7b8 100644 --- a/lib/widgets/dialog_widgets/push_request_dialog.dart +++ b/lib/widgets/dialog_widgets/push_request_dialog.dart @@ -10,7 +10,7 @@ import '../../model/tokens/push_token.dart'; import '../../utils/globals.dart'; import '../../utils/lock_auth.dart'; import '../../utils/logger.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart'; import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../press_button.dart'; import 'default_dialog.dart'; diff --git a/lib/widgets/push_request_listener.dart b/lib/widgets/push_request_listener.dart index 20a820811..c013369d2 100644 --- a/lib/widgets/push_request_listener.dart +++ b/lib/widgets/push_request_listener.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../utils/push_provider.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/push_request_provider.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart'; import 'dialog_widgets/push_request_dialog.dart'; class PushRequestListener extends ConsumerStatefulWidget { @@ -24,15 +24,15 @@ class _PushRequestListenerState extends ConsumerState { @override Widget build(BuildContext context) { - final pushRequest = ref.watch(pushRequestProvider).pushRequests.firstOrNull; + final pushRequest = ref.watch(pushRequestProvider).whenOrNull(data: (data) => data.pushRequests.firstOrNull); + if (pushRequest == null) return widget.child; return Stack( children: [ widget.child, - if (pushRequest != null) - PushRequestDialog( - pushRequest, - key: Key('${pushRequest.hashCode.toString()}#PushRequestDialog'), - ), + PushRequestDialog( + pushRequest, + key: Key('${pushRequest.hashCode.toString()}#PushRequestDialog'), + ), ], ); } diff --git a/test/tests_app_wrapper.mocks.dart b/test/tests_app_wrapper.mocks.dart index c517cae8e..19882c12c 100644 --- a/test/tests_app_wrapper.mocks.dart +++ b/test/tests_app_wrapper.mocks.dart @@ -977,13 +977,13 @@ class MockPushRequestRepository extends _i1.Mock ) as _i11.Future); @override - _i11.Future<_i9.PushRequestState> add( + _i11.Future<_i9.PushRequestState> addRequest( _i22.PushRequest? pushRequest, { _i9.PushRequestState? state, }) => (super.noSuchMethod( Invocation.method( - #add, + #addRequest, [pushRequest], {#state: state}, ), @@ -991,7 +991,7 @@ class MockPushRequestRepository extends _i1.Mock _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( this, Invocation.method( - #add, + #addRequest, [pushRequest], {#state: state}, ), @@ -1000,7 +1000,7 @@ class MockPushRequestRepository extends _i1.Mock _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( this, Invocation.method( - #add, + #addRequest, [pushRequest], {#state: state}, ), @@ -1008,13 +1008,13 @@ class MockPushRequestRepository extends _i1.Mock ) as _i11.Future<_i9.PushRequestState>); @override - _i11.Future<_i9.PushRequestState> remove( + _i11.Future<_i9.PushRequestState> removeRequest( _i22.PushRequest? pushRequest, { _i9.PushRequestState? state, }) => (super.noSuchMethod( Invocation.method( - #remove, + #removeRequest, [pushRequest], {#state: state}, ), @@ -1022,7 +1022,7 @@ class MockPushRequestRepository extends _i1.Mock _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( this, Invocation.method( - #remove, + #removeRequest, [pushRequest], {#state: state}, ), @@ -1031,7 +1031,7 @@ class MockPushRequestRepository extends _i1.Mock _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( this, Invocation.method( - #remove, + #removeRequest, [pushRequest], {#state: state}, ), diff --git a/test/unit_test/state_notifiers/push_request_notifier_test.dart b/test/unit_test/state_notifiers/push_request_notifier_test.dart index 9f35a6a2c..772dcbd8a 100644 --- a/test/unit_test/state_notifiers/push_request_notifier_test.dart +++ b/test/unit_test/state_notifiers/push_request_notifier_test.dart @@ -5,12 +5,11 @@ import 'package:mockito/mockito.dart'; import 'package:privacyidea_authenticator/model/push_request.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/push_request_state.dart'; import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/push_request_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart'; import 'package:privacyidea_authenticator/utils/custom_int_buffer.dart'; import '../../tests_app_wrapper.mocks.dart'; - void main() { _testPushRequestNotifier(); } @@ -23,13 +22,19 @@ void _testPushRequestNotifier() { final mockPushProvider = MockPushProvider(); final mockRsaUtils = MockRsaUtils(); final mockPushRepo = MockPushRequestRepository(); - final provider = StateNotifierProvider((ref) => PushRequestNotifier( - ref: ref, - ioClient: mockIoClient, - pushProvider: mockPushProvider, - rsaUtils: mockRsaUtils, - pushRepo: mockPushRepo, - )); + final pushProvider = pushRequestNotifierProviderOf( + ioClient: mockIoClient, + rsaUtils: mockRsaUtils, + pushProvider: mockPushProvider, + pushRepo: mockPushRepo, + ); + // + //StateNotifierProvider((ref) => PushRequestNotifier( + // ioClient: mockIoClient, + // rsaUtils: mockRsaUtils, + // pushProviderOverride: mockPushProvider, + // pushRepoOverride: mockPushRepo, + // )); final pr = PushRequest( title: 'title', question: 'question', @@ -53,7 +58,7 @@ void _testPushRequestNotifier() { )).thenAnswer((_) async => Response('', 200)); when(mockPushRepo.saveState(any)).thenAnswer((_) async {}); when(mockPushRepo.loadState()).thenAnswer((_) async => before); - final initState = await container.read(provider.notifier).initState; + final initState = await container.read(pushProvider.future); expect(initState, before); when(mockRsaUtils.trySignWithToken(any, any)).thenAnswer((_) async => 'signature'); when(mockIoClient.doPost( @@ -63,9 +68,9 @@ void _testPushRequestNotifier() { )).thenAnswer((_) async => Response('', 200)); when(mockPushRepo.saveState(any)).thenAnswer((_) async {}); - await container.read(provider.notifier).accept(PushToken(serial: 'serial', id: 'id'), pr); + await container.read(pushProvider.notifier).accept(PushToken(serial: 'serial', id: 'id'), pr); - expect(container.read(provider), after); + expect(container.read(pushProvider), after); verify(mockPushRepo.loadState()).called(1); verify(mockRsaUtils.trySignWithToken(any, any)).called(1); verify(mockIoClient.doPost( @@ -81,13 +86,12 @@ void _testPushRequestNotifier() { final mockPushProvider = MockPushProvider(); final mockRsaUtils = MockRsaUtils(); final mockPushRepo = MockPushRequestRepository(); - final provider = StateNotifierProvider((ref) => PushRequestNotifier( - ref: ref, - ioClient: mockIoClient, - pushProvider: mockPushProvider, - rsaUtils: mockRsaUtils, - pushRepo: mockPushRepo, - )); + final pushProvider = pushRequestNotifierProviderOf( + ioClient: mockIoClient, + rsaUtils: mockRsaUtils, + pushProvider: mockPushProvider, + pushRepo: mockPushRepo, + ); final pr = PushRequest( title: 'title', question: 'question', @@ -111,7 +115,7 @@ void _testPushRequestNotifier() { )).thenAnswer((_) async => Response('', 200)); when(mockPushRepo.saveState(any)).thenAnswer((_) async {}); when(mockPushRepo.loadState()).thenAnswer((_) async => before); - final initState = await container.read(provider.notifier).initState; + final initState = await container.read(pushProvider.future); expect(initState, before); when(mockRsaUtils.trySignWithToken(any, any)).thenAnswer((_) async => 'signature'); when(mockIoClient.doPost( @@ -120,8 +124,8 @@ void _testPushRequestNotifier() { sslVerify: anyNamed('sslVerify'), )).thenAnswer((_) async => Response('', 200)); when(mockPushRepo.saveState(any)).thenAnswer((_) async {}); - await container.read(provider.notifier).decline(PushToken(serial: 'serial', id: 'id'), pr); - expect(container.read(provider), after); + await container.read(pushProvider.notifier).decline(PushToken(serial: 'serial', id: 'id'), pr); + expect((await container.read(pushProvider.future)), after); verify(mockPushRepo.loadState()).called(1); verify(mockRsaUtils.trySignWithToken(any, any)).called(1); verify(mockIoClient.doPost( @@ -138,13 +142,12 @@ void _testPushRequestNotifier() { final mockPushProvider = MockPushProvider(); final mockRsaUtils = MockRsaUtils(); final mockPushRepo = MockPushRequestRepository(); - final provider = StateNotifierProvider((ref) => PushRequestNotifier( - ref: ref, - ioClient: mockIoClient, - pushProvider: mockPushProvider, - rsaUtils: mockRsaUtils, - pushRepo: mockPushRepo, - )); + final pushProvider = pushRequestNotifierProviderOf( + ioClient: mockIoClient, + rsaUtils: mockRsaUtils, + pushProvider: mockPushProvider, + pushRepo: mockPushRepo, + ); final pr = PushRequest( title: 'title', question: 'question', @@ -163,10 +166,10 @@ void _testPushRequestNotifier() { when(mockPushRepo.loadState()).thenAnswer((_) async => before); when(mockPushRepo.saveState(any)).thenAnswer((_) async {}); - final initState = await container.read(provider.notifier).initState; + final initState = await container.read(pushProvider.future); expect(initState, before); - await container.read(provider.notifier).add(pr2); - expect(container.read(provider), after); + await container.read(pushProvider.notifier).add(pr2); + expect((await container.read(pushProvider.future)), after); }); test('remove', () async { final container = ProviderContainer(); @@ -174,13 +177,12 @@ void _testPushRequestNotifier() { final mockPushProvider = MockPushProvider(); final mockRsaUtils = MockRsaUtils(); final mockPushRepo = MockPushRequestRepository(); - final provider = StateNotifierProvider((ref) => PushRequestNotifier( - ref: ref, - ioClient: mockIoClient, - pushProvider: mockPushProvider, - rsaUtils: mockRsaUtils, - pushRepo: mockPushRepo, - )); + final pushProvider = pushRequestNotifierProviderOf( + ioClient: mockIoClient, + rsaUtils: mockRsaUtils, + pushProvider: mockPushProvider, + pushRepo: mockPushRepo, + ); final pr = PushRequest( title: 'title', question: 'question', @@ -199,11 +201,11 @@ void _testPushRequestNotifier() { when(mockPushRepo.loadState()).thenAnswer((_) async => before); when(mockPushRepo.saveState(any)).thenAnswer((_) async {}); - final initState = await container.read(provider.notifier).initState; + final initState = await container.read(pushProvider.future); expect(initState, before); - final success = await container.read(provider.notifier).remove(pr2); + final success = await container.read(pushProvider.notifier).remove(pr2); expect(success, true); - expect(container.read(provider), after); + expect(container.read(pushProvider), after); }); }); } diff --git a/test/unit_test/state_notifiers/settings_notifier_test.dart b/test/unit_test/state_notifiers/settings_notifier_test.dart index e60f87055..c88daa3fa 100644 --- a/test/unit_test/state_notifiers/settings_notifier_test.dart +++ b/test/unit_test/state_notifiers/settings_notifier_test.dart @@ -4,11 +4,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; import '../../tests_app_wrapper.mocks.dart'; - final _state = SettingsState( isFirstRun: false, hideOpts: false, @@ -30,25 +29,12 @@ void _testSettingsNotifier() { test('load state from repo on creation', () async { final container = ProviderContainer(); when(mockRepo.loadSettings()).thenAnswer((_) async => _state); - final testProvider = StateNotifierProvider((ref) => SettingsNotifier( - initialState: SettingsState( - isFirstRun: true, - hideOpts: true, - showGuideOnStart: false, - localePreference: const Locale('de'), - useSystemLocale: false, - enablePolling: false, - verboseLogging: true, - crashReportRecipients: {'someone'}, - ), - repository: MockSettingsRepository(), - )); - Future.delayed(const Duration(milliseconds: 1000)).then((value) { - final state = container.read(testProvider); - expect(state, isNotNull); - expect(state, _state); - verify(mockRepo.loadSettings()).called(1); - }); + final testProvider = settingsNotifierProviderOf(repo: mockRepo); + + final state = await container.read(testProvider.future); + expect(state, isNotNull); + expect(state, _state); + verify(mockRepo.loadSettings()).called(1); }); test('addCrashReportRecipient', () { @@ -58,10 +44,8 @@ void _testSettingsNotifier() { ); when(mockRepo.loadSettings()).thenAnswer((_) async => _state); when(mockRepo.saveSettings(copyWithSettings)).thenAnswer((_) async => true); - final testProvider = StateNotifierProvider((ref) => SettingsNotifier( - initialState: _state, - repository: mockRepo, - )); + + final testProvider = settingsNotifierProviderOf(repo: mockRepo); final notifier = container.read(testProvider.notifier); notifier.addCrashReportRecipient('anotherOne'); Future.delayed(const Duration(milliseconds: 1000)).then((value) { @@ -78,10 +62,8 @@ void _testSettingsNotifier() { ); when(mockRepo.loadSettings()).thenAnswer((_) async => _state); when(mockRepo.saveSettings(copyWithSettings)).thenAnswer((_) async => true); - final testProvider = StateNotifierProvider((ref) => SettingsNotifier( - initialState: _state, - repository: mockRepo, - )); + + final testProvider = settingsNotifierProviderOf(repo: mockRepo); final notifier = container.read(testProvider.notifier); notifier.setLocalePreference(const Locale('en')); Future.delayed(const Duration(milliseconds: 1000)).then((value) { @@ -98,10 +80,8 @@ void _testSettingsNotifier() { ); when(mockRepo.loadSettings()).thenAnswer((_) async => _state); when(mockRepo.saveSettings(copyWithSettings)).thenAnswer((_) async => true); - final testProvider = StateNotifierProvider((ref) => SettingsNotifier( - initialState: _state, - repository: mockRepo, - )); + + final testProvider = settingsNotifierProviderOf(repo: mockRepo); final notifier = container.read(testProvider.notifier); notifier.setUseSystemLocale(!_state.useSystemLocale); Future.delayed(const Duration(milliseconds: 1000)).then((value) { @@ -118,10 +98,8 @@ void _testSettingsNotifier() { ); when(mockRepo.loadSettings()).thenAnswer((_) async => _state); when(mockRepo.saveSettings(copyWithSettings)).thenAnswer((_) async => true); - final testProvider = StateNotifierProvider((ref) => SettingsNotifier( - initialState: _state, - repository: mockRepo, - )); + + final testProvider = settingsNotifierProviderOf(repo: mockRepo); final notifier = container.read(testProvider.notifier); notifier.setPolling(!_state.enablePolling); Future.delayed(const Duration(milliseconds: 1000)).then((value) { @@ -138,10 +116,8 @@ void _testSettingsNotifier() { ); when(mockRepo.loadSettings()).thenAnswer((_) async => _state); when(mockRepo.saveSettings(copyWithSettings)).thenAnswer((_) async => true); - final testProvider = StateNotifierProvider((ref) => SettingsNotifier( - initialState: _state, - repository: mockRepo, - )); + + final testProvider = settingsNotifierProviderOf(repo: mockRepo); final notifier = container.read(testProvider.notifier); notifier.setVerboseLogging(!_state.verboseLogging); Future.delayed(const Duration(milliseconds: 1000)).then((value) { @@ -158,10 +134,8 @@ void _testSettingsNotifier() { ); when(mockRepo.loadSettings()).thenAnswer((_) async => _state); when(mockRepo.saveSettings(copyWithSettings)).thenAnswer((_) async => true); - final testProvider = StateNotifierProvider((ref) => SettingsNotifier( - initialState: _state, - repository: mockRepo, - )); + + final testProvider = settingsNotifierProviderOf(repo: mockRepo); final notifier = container.read(testProvider.notifier); notifier.toggleVerboseLogging(); Future.delayed(const Duration(milliseconds: 1000)).then((value) { @@ -178,10 +152,8 @@ void _testSettingsNotifier() { ); when(mockRepo.loadSettings()).thenAnswer((_) async => _state); when(mockRepo.saveSettings(copyWithSettings)).thenAnswer((_) async => true); - final testProvider = StateNotifierProvider((ref) => SettingsNotifier( - initialState: _state, - repository: mockRepo, - )); + + final testProvider = settingsNotifierProviderOf(repo: mockRepo); final notifier = container.read(testProvider.notifier); notifier.setFirstRun(!_state.isFirstRun); Future.delayed(const Duration(milliseconds: 1000)).then((value) { diff --git a/test/unit_test/state_notifiers/sortable_notifier_test.dart b/test/unit_test/state_notifiers/sortable_notifier_test.dart index 75e29ad3f..142bbeacc 100644 --- a/test/unit_test/state_notifiers/sortable_notifier_test.dart +++ b/test/unit_test/state_notifiers/sortable_notifier_test.dart @@ -7,7 +7,6 @@ import 'package:privacyidea_authenticator/model/token_folder.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/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/sortable_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; @@ -58,7 +57,7 @@ void _testSortableNotifier() { }); final container = ProviderContainer(overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepo)), + settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo)), tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), ]); diff --git a/test/unit_test/state_notifiers/token_notifier_test.dart b/test/unit_test/state_notifiers/token_notifier_test.dart index 3621aad4b..bc9428c8a 100644 --- a/test/unit_test/state_notifiers/token_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_notifier_test.dart @@ -12,7 +12,6 @@ import 'package:privacyidea_authenticator/model/riverpod_states/token_state.dart import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; @@ -20,7 +19,6 @@ import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; import '../../tests_app_wrapper.mocks.dart'; - void main() { _testTokenNotifier(); } @@ -30,9 +28,7 @@ void _testTokenNotifier() { test('loadStateFromRepo', () async { final mockSettingsRepo = MockSettingsRepository(); when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); - final container = ProviderContainer(overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepo)), - ]); + final container = ProviderContainer(overrides: [settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo))]); final mockRepo = MockTokenRepository(); final mockFirebaseUtils = MockFirebaseUtils(); final before = [PushToken(label: 'label', issuer: 'issuer', id: 'id', serial: 'serial', isRolledOut: true)]; @@ -58,9 +54,7 @@ void _testTokenNotifier() { test('getTokenFromId', () async { final mockSettingsRepo = MockSettingsRepository(); when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); - final container = ProviderContainer(overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepo)), - ]); + final container = ProviderContainer(overrides: [settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo))]); final mockRepo = MockTokenRepository(); final mockFirebaseUtils = MockFirebaseUtils(); final before = [HOTPToken(label: 'label', issuer: 'issuer', id: 'id', algorithm: Algorithms.SHA1, digits: 6, secret: 'secret')]; @@ -82,9 +76,7 @@ void _testTokenNotifier() { test('incrementCounter', () async { final mockSettingsRepo = MockSettingsRepository(); when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); - final container = ProviderContainer(overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepo)), - ]); + final container = ProviderContainer(overrides: [settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo))]); final mockRepo = MockTokenRepository(); final mockFirebaseUtils = MockFirebaseUtils(); final before = [ @@ -112,9 +104,7 @@ void _testTokenNotifier() { test('removeToken', () async { final mockSettingsRepo = MockSettingsRepository(); when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); - final container = ProviderContainer(overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepo)), - ]); + final container = ProviderContainer(overrides: [settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo))]); final mockRepo = MockTokenRepository(); final mockFirebaseUtils = MockFirebaseUtils(); final before = [ @@ -144,9 +134,7 @@ void _testTokenNotifier() { test('add Token', () async { final mockSettingsRepo = MockSettingsRepository(); when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); - final container = ProviderContainer(overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepo)), - ]); + final container = ProviderContainer(overrides: [settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo))]); final mockRepo = MockTokenRepository(); final mockFirebaseUtils = MockFirebaseUtils(); final before = [ @@ -175,9 +163,7 @@ void _testTokenNotifier() { test('replace Token', () async { final mockSettingsRepo = MockSettingsRepository(); when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); - final container = ProviderContainer(overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepo)), - ]); + final container = ProviderContainer(overrides: [settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo))]); final mockRepo = MockTokenRepository(); final mockFirebaseUtils = MockFirebaseUtils(); final before = [ @@ -208,9 +194,7 @@ void _testTokenNotifier() { test('addOrReplaceTokens', () async { final mockSettingsRepo = MockSettingsRepository(); when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); - final container = ProviderContainer(overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepo)), - ]); + final container = ProviderContainer(overrides: [settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo))]); final mockRepo = MockTokenRepository(); final mockFirebaseUtils = MockFirebaseUtils(); final before = [ @@ -237,9 +221,7 @@ void _testTokenNotifier() { test('addTokenFromOtpAuth', () async { final mockSettingsRepo = MockSettingsRepository(); when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); - final container = ProviderContainer(overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepo)), - ]); + final container = ProviderContainer(overrides: [settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo))]); final mockRepo = MockTokenRepository(); final mockFirebaseUtils = MockFirebaseUtils(); final before = [ @@ -265,9 +247,7 @@ void _testTokenNotifier() { test('addTokenFromOtpAuth: rolloutPushToken', () async { final mockSettingsRepo = MockSettingsRepository(); when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); - final container = ProviderContainer(overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepo)), - ]); + final container = ProviderContainer(overrides: [settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo))]); final mockTokenRepo = MockTokenRepository(); final mockRsaUtils = MockRsaUtils(); final mockIOClient = MockPrivacyideaIOClient(); @@ -376,9 +356,7 @@ void _testTokenNotifier() { test('rolloutPushToken', () async { final mockSettingsRepo = MockSettingsRepository(); when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); - final container = ProviderContainer(overrides: [ - settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepo)), - ]); + final container = ProviderContainer(overrides: [settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo))]); final mockRepo = MockTokenRepository(); final mockIOClient = MockPrivacyideaIOClient(); final mockFirebaseUtils = MockFirebaseUtils(); From 295fa2d02ff68f75a0bde27c082cf0447d3753e7 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Wed, 7 Aug 2024 11:27:44 +0200 Subject: [PATCH 021/285] refactoring --- .../extensions/enums/introduction_extension.dart | 5 +++-- lib/utils/logger.dart | 16 +++++++--------- .../introduction_provider.dart | 4 ++++ .../settings_provider.dart | 8 +++++--- .../license_push_view_button.dart | 7 ++----- lib/views/splash_screen/splash_screen.dart | 1 - 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lib/model/extensions/enums/introduction_extension.dart b/lib/model/extensions/enums/introduction_extension.dart index 494f7b23c..ab10fbcd1 100644 --- a/lib/model/extensions/enums/introduction_extension.dart +++ b/lib/model/extensions/enums/introduction_extension.dart @@ -29,8 +29,9 @@ extension IntroductionX on Introduction { state.isUncompleted(Introduction.pollForChallenges) && Introduction.dragToken.isConditionFulfilled(ref, state) == false && Introduction.addFolder.isConditionFulfilled(ref, state) == false, - Introduction.hidePushTokens => ref.watch(settingsProvider).whenOrNull(data: (data) => data.hidePushTokens) ?? - SettingsState.hidePushTokensDefault && state.isCompleted(Introduction.pollForChallenges) && state.isUncompleted(Introduction.hidePushTokens), + Introduction.hidePushTokens => (ref.watch(settingsProvider).whenOrNull(data: (data) => data.hidePushTokens) ?? SettingsState.hidePushTokensDefault) && + state.isCompleted(Introduction.pollForChallenges) && + state.isUncompleted(Introduction.hidePushTokens), Introduction.exportTokens => state.isUncompleted(Introduction.exportTokens), }; diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index 45081bddc..a13491d0e 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -81,7 +81,7 @@ class Logger { return file.readAsString(); } - /*----------- INSTANCE MEMBER & GETTER -----------*/ + /*----------- INSTANCE MEMBER & GETTER/SETTER -----------*/ Function? _appRunner; Widget? _app; String _lastError = 'No error Message'; @@ -92,11 +92,9 @@ class Logger { String get _filename => 'logfile.txt'; String? get _fullPath => _logPath != null ? '$_logPath/$_filename' : null; - bool get _verbose { - // if (globalRef == null) return false; - return false; - // return globalRef!.read(settingsProvider).whenOrNull(data: (data) => data.verboseLogging) ?? SettingsState.verboseLoggingDefault; //TODO: fix it - } + static bool _verboseLogging = false; + + static void setVerboseLogging(bool value) => _verboseLogging = value; bool get logfileHasContent { if (_fullPath == null) return false; @@ -135,7 +133,7 @@ class Logger { void logInfo(String message, {dynamic stackTrace, String? name, bool verbose = false}) { String infoString = _convertLogToSingleString(message, stackTrace: stackTrace, name: name, logLevel: LogLevel.INFO); infoString = _textFilter(infoString); - if (_verbose || verbose) { + if (_verboseLogging || verbose) { _logToFile(infoString); } _print(infoString); @@ -147,7 +145,7 @@ class Logger { void logWarning(String message, {dynamic error, dynamic stackTrace, String? name, bool verbose = false}) { String warningString = _convertLogToSingleString(message, error: error, stackTrace: stackTrace, name: name, logLevel: LogLevel.WARNING); warningString = _textFilter(warningString); - if (instance._verbose || verbose) { + if (_verboseLogging || verbose) { instance._logToFile(warningString); } _printWarning(warningString); @@ -158,7 +156,7 @@ class Logger { if (!kDebugMode) return; String debugString = instance._convertLogToSingleString( message, - stackTrace: stackTrace ?? (instance._verbose || verbose) ? StackTrace.current : null, + stackTrace: stackTrace ?? (_verboseLogging || verbose) ? StackTrace.current : null, name: name, logLevel: LogLevel.DEBUG, ); diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart index 77b06e287..9f8552d30 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart @@ -61,18 +61,22 @@ class IntroductionNotifier extends _$IntroductionNotifier { } Future complete(Introduction introduction) async { + Logger.info('Completing introduction: $introduction', name: 'introduction_provider.dart#complete'); final newState = (await future).withCompletedIntroduction(introduction); await _saveToRepo(newState); state = AsyncValue.data(newState); + Logger.debug('New saved state after completion: ${await future}'); } Future uncomplete(Introduction introduction) async { + Logger.info('Uncompleting introduction: $introduction', name: 'introduction_provider.dart#uncomplete'); final newState = (await future).withoutCompletedIntroduction(introduction); await _saveToRepo(newState); state = AsyncValue.data(newState); } Future completeAll() async { + Logger.info('Completing all introductions', name: 'introduction_provider.dart#completeAll'); final newState = (await future).withAllCompleted(); await _saveToRepo(newState); state = AsyncValue.data(newState); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart index 2c2f82986..8af2f8cdb 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart @@ -49,7 +49,7 @@ class SettingsNotifier extends _$SettingsNotifier { Future build({ required SettingsRepository repo, }) async { - Logger.info('New settings notifier created', name: 'settings_notifier.dart#build'); + // Logger.info('New settings notifier created', name: 'settings_notifier.dart#build'); _repo = _repoOverride ?? repo; final newState = await _loadFromRepo(); return newState; @@ -147,9 +147,11 @@ class SettingsNotifier extends _$SettingsNotifier { return updateState((oldState) => oldState.copyWith(localePreference: locale)); } - Future setVerboseLogging(bool value) { + Future setVerboseLogging(bool value) async { Logger.info('Verbose logging set to $value', name: 'settings_notifier.dart#setVerboseLogging'); - return updateState((oldState) => oldState.copyWith(verboseLogging: value)); + final updatedState = await updateState((oldState) => oldState.copyWith(verboseLogging: value)); + Logger.setVerboseLogging(updatedState.verboseLogging); + return updatedState; } Future toggleVerboseLogging() { diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart index 50301156b..e91ecfc0c 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart @@ -37,11 +37,8 @@ class LicensePushViewButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) => (ref.watch(settingsProvider).whenOrNull(data: (data) => data.hidePushTokens) ?? SettingsState.hidePushTokensDefault) ? FocusedItemAsOverlay( - isFocused: ref.watch(introductionNotifierProvider).when( - data: (value) => value.isConditionFulfilled(ref, Introduction.hidePushTokens), - error: (Object error, StackTrace stackTrace) => false, - loading: () => false, - ), + isFocused: + ref.watch(introductionNotifierProvider).whenOrNull(data: (data) => data.isConditionFulfilled(ref, Introduction.hidePushTokens)) ?? false, tooltipWhenFocused: AppLocalizations.of(context)!.introHidePushTokens, onComplete: () => ref.read(introductionNotifierProvider.notifier).complete(Introduction.hidePushTokens), child: AppBarItem( diff --git a/lib/views/splash_screen/splash_screen.dart b/lib/views/splash_screen/splash_screen.dart index 1d4ef44c0..1298a3915 100644 --- a/lib/views/splash_screen/splash_screen.dart +++ b/lib/views/splash_screen/splash_screen.dart @@ -62,7 +62,6 @@ class _SplashScreenState extends ConsumerState { Future.wait( [ Future.delayed(_splashScreenDuration), - // ref.read(settingsProvider.future), ref.read(tokenProvider.notifier).initState, ref.read(tokenFolderProvider.notifier).initState, AppInfoUtils.init(), From ecf220d7bf1b35260a34318c6c3601bc74b94dd8 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:28:33 +0200 Subject: [PATCH 022/285] refactoring --- integration_test/add_tokens_test.dart | 2 +- integration_test/copy_to_clipboard_test.dart | 2 +- integration_test/rename_and_delete_test.dart | 2 +- integration_test/two_step_rollout_test.dart | 2 +- integration_test/views_test.dart | 2 +- lib/mains/main_customizer.dart | 2 +- lib/mains/main_netknights.dart | 3 +- .../enums/introduction_extension.dart | 2 +- lib/utils/push_provider.dart | 4 +- .../introduction_provider.g.dart | 2 +- .../settings_notifier.dart} | 2 +- .../settings_notifier.g.dart} | 4 +- .../sortable_notifier.dart | 55 +++++++ .../sortable_notifier.g.dart | 24 +++ .../sortable_provider.dart | 51 ------- .../state_notifiers/sortable_notifier.dart | 144 +++++++++--------- .../state_notifiers/token_notifier.dart | 2 +- lib/views/main_view/main_view.dart | 2 +- .../token_folder_expandable.dart | 2 +- .../license_push_view_button.dart | 2 +- .../main_view_tokens_list.dart | 6 +- .../day_password_token_widget_tile.dart | 2 +- .../settings_group_language.dart | 2 +- .../settings_group_push_token.dart | 2 +- .../settings_view_widgets/logging_menu.dart | 2 +- .../dialog_widgets/patch_notes_dialog.dart | 2 +- .../settings_notifier_test.dart | 2 +- .../sortable_notifier_test.dart | 14 +- .../state_notifiers/token_notifier_test.dart | 2 +- 29 files changed, 185 insertions(+), 160 deletions(-) rename lib/utils/riverpod/riverpod_providers/{state_notifier_providers/settings_provider.dart => generated_providers/settings_notifier.dart} (99%) rename lib/utils/riverpod/riverpod_providers/{state_notifier_providers/settings_provider.g.dart => generated_providers/settings_notifier.g.dart} (97%) create mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart create mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.g.dart delete mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart diff --git a/integration_test/add_tokens_test.dart b/integration_test/add_tokens_test.dart index c09fdefa4..c58d59f2b 100644 --- a/integration_test/add_tokens_test.dart +++ b/integration_test/add_tokens_test.dart @@ -16,7 +16,7 @@ import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_f import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import 'package:privacyidea_authenticator/views/add_token_manually_view/add_token_manually_view.dart'; diff --git a/integration_test/copy_to_clipboard_test.dart b/integration_test/copy_to_clipboard_test.dart index 9ec907a1d..707538df0 100644 --- a/integration_test/copy_to_clipboard_test.dart +++ b/integration_test/copy_to_clipboard_test.dart @@ -14,7 +14,7 @@ import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_n import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; diff --git a/integration_test/rename_and_delete_test.dart b/integration_test/rename_and_delete_test.dart index 5b11f9f59..ed118ca14 100644 --- a/integration_test/rename_and_delete_test.dart +++ b/integration_test/rename_and_delete_test.dart @@ -15,7 +15,7 @@ import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_n import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.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 f2bb801fc..6b690c581 100644 --- a/integration_test/two_step_rollout_test.dart +++ b/integration_test/two_step_rollout_test.dart @@ -14,7 +14,7 @@ import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view.dart'; diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index 5366ba1a4..56d7f65f7 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -20,7 +20,7 @@ import 'package:privacyidea_authenticator/utils/customization/application_custom import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/push_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; diff --git a/lib/mains/main_customizer.dart b/lib/mains/main_customizer.dart index cce029bac..3d18ce617 100644 --- a/lib/mains/main_customizer.dart +++ b/lib/mains/main_customizer.dart @@ -27,7 +27,7 @@ import '../l10n/app_localizations.dart'; import '../model/enums/app_feature.dart'; import '../model/riverpod_states/settings_state.dart'; import '../utils/globals.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; import '../utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart'; import '../views/add_token_manually_view/add_token_manually_view.dart'; diff --git a/lib/mains/main_netknights.dart b/lib/mains/main_netknights.dart index 153ed2bef..1095f66ad 100644 --- a/lib/mains/main_netknights.dart +++ b/lib/mains/main_netknights.dart @@ -32,7 +32,7 @@ import '../utils/customization/application_customization.dart'; import '../utils/globals.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; import '../views/add_token_manually_view/add_token_manually_view.dart'; import '../views/feedback_view/feedback_view.dart'; @@ -89,6 +89,7 @@ class PrivacyIDEAAuthenticator extends ConsumerWidget { ), debugShowCheckedModeBanner: true, navigatorKey: globalNavigatorKey, + localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, locale: ref.watch(settingsProvider).whenOrNull(data: (data) => data.currentLocale) ?? SettingsState.localeDefault, diff --git a/lib/model/extensions/enums/introduction_extension.dart b/lib/model/extensions/enums/introduction_extension.dart index ab10fbcd1..b2741863b 100644 --- a/lib/model/extensions/enums/introduction_extension.dart +++ b/lib/model/extensions/enums/introduction_extension.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import '../../../l10n/app_localizations.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../enums/introduction.dart'; import '../../riverpod_states/introduction_state.dart'; diff --git a/lib/utils/push_provider.dart b/lib/utils/push_provider.dart index 88891535d..e94ba17d3 100644 --- a/lib/utils/push_provider.dart +++ b/lib/utils/push_provider.dart @@ -37,7 +37,7 @@ import 'firebase_utils.dart'; import 'globals.dart'; import 'logger.dart'; import 'privacyidea_io_client.dart'; -import 'riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import 'riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import 'rsa_utils.dart'; @@ -301,6 +301,8 @@ class PushProvider { final connectivityResult = await (Connectivity().checkConnectivity()); 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/riverpod_providers/generated_providers/introduction_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart index bd89c2af3..f1ca78185 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart @@ -7,7 +7,7 @@ part of 'introduction_provider.dart'; // ************************************************************************** String _$introductionNotifierHash() => - r'2c84c088bf01feb395c187f1671a4c822edf1b42'; + r'ef2eb16345e9723dd63a1c4d4bed72872a447af0'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart similarity index 99% rename from lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart index 8af2f8cdb..cefe96492 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart @@ -29,7 +29,7 @@ import '../../../../model/riverpod_states/settings_state.dart'; import '../../../../model/version.dart'; import '../../../logger.dart'; -part 'settings_provider.g.dart'; +part 'settings_notifier.g.dart'; final settingsProvider = settingsNotifierProviderOf(repo: PreferenceSettingsRepository()); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.g.dart similarity index 97% rename from lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.g.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.g.dart index baa9f9a01..a8c826024 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.g.dart @@ -1,12 +1,12 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'settings_provider.dart'; +part of 'settings_notifier.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$settingsNotifierHash() => r'83f54c5fafceae7ace984ef06ac564bee445771f'; +String _$settingsNotifierHash() => r'cbd75880d5ec70ee5910ac34e5c65eb51099b874'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart new file mode 100644 index 000000000..461a74260 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart @@ -0,0 +1,55 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/widgets.dart'; +import 'package:privacyidea_authenticator/model/extensions/sortable_list.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../../model/mixins/sortable_mixin.dart'; +import '../../../../model/token_folder.dart'; +import '../../../../model/tokens/token.dart'; +import '../state_providers/dragging_sortable_provider.dart'; +import '../state_notifier_providers/token_folder_provider.dart'; +import '../state_notifier_providers/token_provider.dart'; + +part 'sortable_notifier.g.dart'; + +@riverpod +List sortables(SortablesRef ref) { + final tokenFolders = ref.watch(tokenFolderProvider).folders; + final tokens = ref.watch(tokenProvider).tokens; + + var sortablesWithNulls = List.from([...tokens, ...tokenFolders]); + + final sortedSortables = sortablesWithNulls.sorted.fillNullIndices(); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (sortablesWithNulls.any((e) => e is Token) && sortablesWithNulls.any((element) => element.sortIndex == null)) { + ref.read(tokenProvider.notifier).addOrReplaceTokens(sortedSortables.whereType().toList()); + } + if (sortablesWithNulls.any((e) => e is TokenFolder) && sortablesWithNulls.any((element) => element.sortIndex == null)) { + ref.read(tokenFolderProvider.notifier).addOrReplaceFolders(sortedSortables.whereType().toList()); + } + + ref.read(draggingSortableProvider.notifier).state = null; + }); + + return sortedSortables; +} diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.g.dart new file mode 100644 index 000000000..1e396886d --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'sortable_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$sortablesHash() => r'a4463dc0f1e108f07d4714f0df5f178ef9653f49'; + +/// See also [sortables]. +@ProviderFor(sortables) +final sortablesProvider = AutoDisposeProvider>.internal( + sortables, + name: r'sortablesProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$sortablesHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef SortablesRef = AutoDisposeProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart deleted file mode 100644 index f40fec012..000000000 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart +++ /dev/null @@ -1,51 +0,0 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; - -import '../../../../model/mixins/sortable_mixin.dart'; -import '../../../../model/riverpod_states/token_folder_state.dart'; -import '../../../../model/riverpod_states/token_state.dart'; -import '../../state_notifiers/sortable_notifier.dart'; -import '../../../logger.dart'; -import 'token_folder_provider.dart'; -import 'token_provider.dart'; - -final sortableProvider = StateNotifierProvider>( - (ref) { - final SortableNotifier notifier = SortableNotifier(ref: ref); - Logger.info("New sortableProvider created", name: 'sortableProvider'); - ref.listen(tokenProvider, (previous, next) => notifier.handleNewStateList(next.tokens)); - ref.listen(tokenFolderProvider, (previous, next) => notifier.handleNewStateList(next.folders)); - Future.wait( - [ref.read(tokenProvider.notifier).initState, ref.read(tokenFolderProvider.notifier).initState], - ).then((values) { - final sortables = []; - for (final v in values) { - if (v is TokenState) { - sortables.addAll(v.tokens); - } else if (v is TokenFolderState) { - sortables.addAll(v.folders); - } - } - notifier.handleNewStateList(sortables); - }); - return notifier; - }, -); diff --git a/lib/utils/riverpod/state_notifiers/sortable_notifier.dart b/lib/utils/riverpod/state_notifiers/sortable_notifier.dart index 463d31936..6451f54ea 100644 --- a/lib/utils/riverpod/state_notifiers/sortable_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/sortable_notifier.dart @@ -1,76 +1,76 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; -import '../../logger.dart'; +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; +// import '../../logger.dart'; -import '../../../model/extensions/sortable_list.dart'; -import '../../../model/mixins/sortable_mixin.dart'; -import '../../../model/token_folder.dart'; -import '../../../model/tokens/token.dart'; -import '../riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import '../riverpod_providers/state_notifier_providers/token_provider.dart'; -import '../riverpod_providers/state_providers/dragging_sortable_provider.dart'; +// import '../../../model/extensions/sortable_list.dart'; +// import '../../../model/mixins/sortable_mixin.dart'; +// import '../../../model/token_folder.dart'; +// import '../../../model/tokens/token.dart'; +// import '../riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +// import '../riverpod_providers/state_notifier_providers/token_provider.dart'; +// import '../riverpod_providers/state_providers/dragging_sortable_provider.dart'; -class SortableNotifier extends StateNotifier> { - final StateNotifierProviderRef _ref; - Future>? initState; - SortableNotifier({ - required StateNotifierProviderRef ref, - List initState = const [], - }) : _ref = ref, - super(initState); - Future _waitInit() async { - if (initState != null) { - await initState; - return; - } - initState = Future(() async { - final newSortables = []; - newSortables.addAll((await _ref.read(tokenProvider.notifier).initState).tokens.cast()); - newSortables.addAll((await _ref.read(tokenFolderProvider.notifier).initState).folders.cast()); - state = newSortables.sorted.fillNullIndices(); - return state; - }); - await initState; - } +// // class SortableNotifier extends StateNotifier> { +// // final StateNotifierProviderRef _ref; +// // Future>? initState; +// // SortableNotifier({ +// // required StateNotifierProviderRef ref, +// // List initState = const [], +// // }) : _ref = ref, +// // super(initState); +// // Future _waitInit() async { +// // if (initState != null) { +// // await initState; +// // return; +// // } +// // initState = Future(() async { +// // final newSortables = []; +// // newSortables.addAll((await _ref.read(tokenProvider.notifier).initState).tokens.cast()); +// // newSortables.addAll((await _ref.read(tokenFolderProvider.notifier).initState).folders.cast()); +// // state = newSortables.sorted.fillNullIndices(); +// // return state; +// // }); +// // await initState; +// // } - /// Handles a new list of [T]. - /// First removes all elements of type [T] from the current state and then adds the new list. - Future> handleNewStateList(List newList) async { - Logger.info('Handling new state list of type $T', name: 'SortableNotifier#handleNewStateList'); - await _waitInit(); - var newState = List.from(state); - newState.removeWhere((element) => element is T); - newState.addAll(newList); - final listWithNulls = newState.toList(); - newState = newState.sorted.fillNullIndices(); - state = newState; - await Future.wait([ - Future.delayed(const Duration(milliseconds: 50)), - if (listWithNulls.any((e) => e is Token) && listWithNulls.any((element) => element.sortIndex == null)) - _ref.read(tokenProvider.notifier).addOrReplaceTokens(state.whereType().toList()), - if (listWithNulls.any((e) => e is TokenFolder) && listWithNulls.any((element) => element.sortIndex == null)) - _ref.read(tokenFolderProvider.notifier).addOrReplaceFolders(state.whereType().toList()), - ]); +// // /// Handles a new list of [T]. +// // /// First removes all elements of type [T] from the current state and then adds the new list. +// // Future> handleNewStateList(List newList) async { +// // Logger.info('Handling new state list of type $T', name: 'SortableNotifier#handleNewStateList'); +// // await _waitInit(); +// // var newState = List.from(state); +// // newState.removeWhere((element) => element is T); +// // newState.addAll(newList); +// // final listWithNulls = newState.toList(); +// // newState = newState.sorted.fillNullIndices(); +// // state = newState; +// // await Future.wait([ +// // Future.delayed(const Duration(milliseconds: 50)), +// // if (listWithNulls.any((e) => e is Token) && listWithNulls.any((element) => element.sortIndex == null)) +// // _ref.read(tokenProvider.notifier).addOrReplaceTokens(state.whereType().toList()), +// // if (listWithNulls.any((e) => e is TokenFolder) && listWithNulls.any((element) => element.sortIndex == null)) +// // _ref.read(tokenFolderProvider.notifier).addOrReplaceFolders(state.whereType().toList()), +// // ]); - _ref.read(draggingSortableProvider.notifier).state = null; - return newState; - } -} +// // _ref.read(draggingSortableProvider.notifier).state = null; +// // return newState; +// // } +// // } diff --git a/lib/utils/riverpod/state_notifiers/token_notifier.dart b/lib/utils/riverpod/state_notifiers/token_notifier.dart index a6c156ef1..b661c82e8 100644 --- a/lib/utils/riverpod/state_notifiers/token_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_notifier.dart @@ -53,7 +53,7 @@ import '../../identifiers.dart'; import '../../lock_auth.dart'; import '../../logger.dart'; import '../../privacyidea_io_client.dart'; -import '../riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../riverpod_providers/generated_providers/settings_notifier.dart'; import '../riverpod_providers/state_providers/status_message_provider.dart'; import '../../rsa_utils.dart'; import '../../utils.dart'; diff --git a/lib/views/main_view/main_view.dart b/lib/views/main_view/main_view.dart index 3f0abc27b..5f6b1c8f7 100644 --- a/lib/views/main_view/main_view.dart +++ b/lib/views/main_view/main_view.dart @@ -25,7 +25,7 @@ import '../../model/riverpod_states/token_filter.dart'; import '../../utils/globals.dart'; import '../../utils/logger.dart'; import '../../utils/patch_notes_utils.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart'; import '../../widgets/push_request_listener.dart'; import '../../widgets/status_bar.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 236c655bb..44fee270d 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 @@ -35,7 +35,7 @@ import '../../../../model/tokens/token.dart'; import '../../../../utils/customization/action_theme.dart'; import '../../../../utils/globals.dart'; import '../../../../utils/lock_auth.dart'; -import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart index e91ecfc0c..8ebef959d 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/license_push_view_button.dart @@ -24,7 +24,7 @@ import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.d import '../../../../l10n/app_localizations.dart'; import '../../../../model/enums/introduction.dart'; import '../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../../../widgets/focused_item_as_overlay.dart'; import '../../../license_view/license_view.dart'; import '../../../push_token_view/push_tokens_view.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 a52021f50..64ff25f71 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 @@ -27,8 +27,8 @@ import '../../../model/riverpod_states/settings_state.dart'; import '../../../model/token_folder.dart'; import '../../../model/tokens/push_token.dart'; import '../../../model/tokens/token.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../widgets/default_refresh_indicator.dart'; import '../../../widgets/drag_item_scroller.dart'; @@ -83,7 +83,7 @@ class _MainViewTokensListState extends ConsumerState { @override Widget build(BuildContext context) { final draggingSortable = ref.watch(draggingSortableProvider); - final allSortables = ref.watch(sortableProvider); + final allSortables = ref.watch(sortablesProvider); final allowToRefresh = allSortables.any((element) => element is PushToken); final hidePushTokens = ref.watch(settingsProvider).whenOrNull(data: (data) => data.hidePushTokens) ?? SettingsState.hidePushTokensDefault; final filterPushTokens = hidePushTokens && allowToRefresh; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart index e865a3e93..49df7c308 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart @@ -28,7 +28,7 @@ import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/enums/day_password_token_view_mode.dart'; import '../../../../../model/riverpod_states/settings_state.dart'; import '../../../../../model/tokens/day_password_token.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../../utils/utils.dart'; import '../../../../../widgets/custom_texts.dart'; diff --git a/lib/views/settings_view/settings_groups/settings_group_language.dart b/lib/views/settings_view/settings_groups/settings_group_language.dart index fbac7c690..775a7bcc9 100644 --- a/lib/views/settings_view/settings_groups/settings_group_language.dart +++ b/lib/views/settings_view/settings_groups/settings_group_language.dart @@ -22,7 +22,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import '../../../l10n/app_localizations.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../settings_view_widgets/settings_groups.dart'; class SettingsGroupLanguage extends ConsumerWidget { 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 416323bb9..ece20c1cf 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 @@ -23,7 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/riverpod_states/settings_state.dart'; import '../../../model/tokens/push_token.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../settings_view_widgets/settings_groups.dart'; import '../settings_view_widgets/update_firebase_token_dialog.dart'; diff --git a/lib/views/settings_view/settings_view_widgets/logging_menu.dart b/lib/views/settings_view/settings_view_widgets/logging_menu.dart index f0f5392bf..3505f9c69 100644 --- a/lib/views/settings_view/settings_view_widgets/logging_menu.dart +++ b/lib/views/settings_view/settings_view_widgets/logging_menu.dart @@ -22,7 +22,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; import '../../../l10n/app_localizations.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../../widgets/dialog_widgets/default_dialog.dart'; import 'errorlog_buttons/delete_errorlog_button.dart'; import 'errorlog_buttons/send_errorlog_button.dart'; diff --git a/lib/widgets/dialog_widgets/patch_notes_dialog.dart b/lib/widgets/dialog_widgets/patch_notes_dialog.dart index 0f8c3d232..13358f5a7 100644 --- a/lib/widgets/dialog_widgets/patch_notes_dialog.dart +++ b/lib/widgets/dialog_widgets/patch_notes_dialog.dart @@ -8,7 +8,7 @@ import '../../model/extensions/enums/patch_note_type_extension.dart'; import '../../model/version.dart'; import '../../utils/app_info_utils.dart'; import '../../utils/globals.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'default_dialog.dart'; class PatchNotesDialog extends StatelessWidget { diff --git a/test/unit_test/state_notifiers/settings_notifier_test.dart b/test/unit_test/state_notifiers/settings_notifier_test.dart index c88daa3fa..1896107f4 100644 --- a/test/unit_test/state_notifiers/settings_notifier_test.dart +++ b/test/unit_test/state_notifiers/settings_notifier_test.dart @@ -4,7 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../tests_app_wrapper.mocks.dart'; diff --git a/test/unit_test/state_notifiers/sortable_notifier_test.dart b/test/unit_test/state_notifiers/sortable_notifier_test.dart index 142bbeacc..6ce698b82 100644 --- a/test/unit_test/state_notifiers/sortable_notifier_test.dart +++ b/test/unit_test/state_notifiers/sortable_notifier_test.dart @@ -7,11 +7,10 @@ import 'package:privacyidea_authenticator/model/token_folder.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/utils/riverpod/state_notifiers/sortable_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/sortable_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; @@ -62,15 +61,10 @@ void _testSortableNotifier() { tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), ]); - final SortableNotifier sortableNotifier = container.read(sortableProvider.notifier); - final newToken = TOTPToken(period: 30, id: 'Token 4', algorithm: Algorithms.SHA1, digits: 6, secret: 'secret4', folderId: 1, sortIndex: 1); + await container.read(tokenProvider.notifier).addNewToken(newToken); + final newSortableState = container.read(sortablesProvider); - final newTokenState = tokenState.toList()..add(newToken); - final newState = await sortableNotifier.handleNewStateList(newTokenState); - - final newSortableState = container.read(sortableProvider); - expect(newState.length, 6); expect(newSortableState.length, 6); expect(newSortableState[0], isA()); expect((newSortableState[0] as Token).id, 'Token 3'); diff --git a/test/unit_test/state_notifiers/token_notifier_test.dart b/test/unit_test/state_notifiers/token_notifier_test.dart index bc9428c8a..fd9ec01b4 100644 --- a/test/unit_test/state_notifiers/token_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_notifier_test.dart @@ -14,7 +14,7 @@ import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/settings_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; import '../../tests_app_wrapper.mocks.dart'; From 911726a69cd0b3f5707c31267c2ec44b4814b757 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:39:56 +0200 Subject: [PATCH 023/285] refactoring --- .../sortable_notifier.dart | 3 -- .../sortable_notifier.g.dart | 2 +- .../dragging_sortable_provider.dart | 38 ++++++++--------- lib/utils/utils.dart | 42 +++++++++++++++++++ .../drag_target_divider.dart | 38 +---------------- .../token_folder_expandable.dart | 19 +++++---- 6 files changed, 76 insertions(+), 66 deletions(-) diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart index 461a74260..a0f9ac598 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart @@ -25,7 +25,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../../model/mixins/sortable_mixin.dart'; import '../../../../model/token_folder.dart'; import '../../../../model/tokens/token.dart'; -import '../state_providers/dragging_sortable_provider.dart'; import '../state_notifier_providers/token_folder_provider.dart'; import '../state_notifier_providers/token_provider.dart'; @@ -47,8 +46,6 @@ List sortables(SortablesRef ref) { if (sortablesWithNulls.any((e) => e is TokenFolder) && sortablesWithNulls.any((element) => element.sortIndex == null)) { ref.read(tokenFolderProvider.notifier).addOrReplaceFolders(sortedSortables.whereType().toList()); } - - ref.read(draggingSortableProvider.notifier).state = null; }); return sortedSortables; diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.g.dart index 1e396886d..14b306a7d 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.g.dart @@ -6,7 +6,7 @@ part of 'sortable_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$sortablesHash() => r'a4463dc0f1e108f07d4714f0df5f178ef9653f49'; +String _$sortablesHash() => r'dc6e66c07f1beab839737a48ec589058f2a81429'; /// See also [sortables]. @ProviderFor(sortables) diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart index c0f7604a2..f02354693 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart @@ -1,22 +1,22 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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. - */ +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; import '../../../../model/mixins/sortable_mixin.dart'; diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 2759747ff..e2d08f17a 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -23,13 +23,22 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:privacyidea_authenticator/mains/main_netknights.dart'; +import 'package:privacyidea_authenticator/model/extensions/sortable_list.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart'; +import '../model/mixins/sortable_mixin.dart'; +import '../model/token_folder.dart'; +import '../model/tokens/token.dart'; import 'customization/application_customization.dart' show ApplicationCustomization; +import 'riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import 'riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import 'riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; /// Inserts [char] at the position [pos] in the given String ([str]), /// and returns the resulting String. @@ -133,3 +142,36 @@ dynamic tryJsonDecode(String json) { return null; } } + +void dragSortableOnAccept({ + required SortableMixin? previousSortable, + required SortableMixin dragedSortable, + required SortableMixin? nextSortable, + TokenFolder? dependingFolder, + required WidgetRef ref, +}) { + var allSortables = ref.read(sortablesProvider); + + if (dragedSortable is TokenFolder) { + final tokensInFolder = ref.read(tokenProvider).tokens.where((element) => element.folderId == dragedSortable.folderId).toList(); + final allMovingItems = [dragedSortable, ...tokensInFolder]; + allSortables = allSortables.moveAllBetween(moveAfter: previousSortable, movedItems: allMovingItems, moveBefore: nextSortable); + } else if (dragedSortable is Token) { + allSortables = allSortables.moveBetween(moveAfter: previousSortable, movedItem: dragedSortable, moveBefore: nextSortable); + allSortables = allSortables.map((e) { + return e is Token && e.id == dragedSortable.id ? e.copyWith(folderId: () => dependingFolder?.folderId) : e; + }).toList(); + } + final modifiedTokens = allSortables.whereType().toList(); + final modifiedFolders = allSortables.whereType().toList(); + final futures = [ + ref.read(tokenProvider.notifier).addOrReplaceTokens(modifiedTokens), + ref.read(tokenFolderProvider.notifier).addOrReplaceFolders(modifiedFolders), + ]; + final draggingSortableProviderNotifier = ref.read(draggingSortableProvider.notifier); + Future.wait(futures).then((_) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + draggingSortableProviderNotifier.state = null; + }); + }); +} 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 0d9e7b89f..442e5026a 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 @@ -22,12 +22,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../model/extensions/sortable_list.dart'; import '../../../model/mixins/sortable_mixin.dart'; import '../../../model/token_folder.dart'; -import '../../../model/tokens/token.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../utils/utils.dart'; import '../../../widgets/drag_item_scroller.dart'; /// DragTargetDivider is used to create a divider that can be used to move a sortable up or down in the list @@ -40,7 +37,6 @@ class DragTargetDivider extends ConsumerStatefulWidget final double dividerExpandedHeight; final double bottomPaddingIfLast; final bool isExpandalbe; - final bool ignoreFolderId; final bool isLastDivider; const DragTargetDivider({ @@ -52,7 +48,6 @@ class DragTargetDivider extends ConsumerStatefulWidget this.dividerBaseHeight = 1.5, this.dividerExpandedHeight = 40, this.isExpandalbe = true, - this.ignoreFolderId = false, this.isLastDivider = false, }); @@ -95,11 +90,10 @@ class _DragTargetDividerState extends ConsumerState element.folderId == dragedSortable.folderId).toList(); - final allMovingItems = [dragedSortable, ...tokensInFolder]; - allSortables = allSortables.moveAllBetween(moveAfter: previousSortable, movedItems: allMovingItems, moveBefore: nextSortable); - } else if (dragedSortable is Token) { - allSortables = allSortables.moveBetween(moveAfter: previousSortable, movedItem: dragedSortable, moveBefore: nextSortable); - allSortables = allSortables.map((e) { - return e is Token && e.id == dragedSortable.id ? e.copyWith(folderId: () => dependingFolder?.folderId) : e; - }).toList(); - } - final modifiedTokens = allSortables.whereType().toList(); - final modifiedFolders = allSortables.whereType().toList(); - ref.read(tokenProvider.notifier).addOrReplaceTokens(modifiedTokens); - ref.read(tokenFolderProvider.notifier).addOrReplaceFolders(modifiedFolders); -} 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 44fee270d..263d1a16f 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 @@ -39,6 +39,7 @@ import '../../../../utils/riverpod/riverpod_providers/generated_providers/settin import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; +import '../../../../utils/utils.dart'; import '../../../../widgets/custom_trailing.dart'; import '../drag_target_divider.dart'; import '../token_widgets/token_widget_builder.dart'; @@ -96,8 +97,9 @@ class _TokenFolderExpandableState extends ConsumerState w @override ExpandablePanel build(BuildContext context) { - final hitePushTokens = ref.watch(settingsProvider).whenOrNull(data: (data) => data.hidePushTokens) ?? SettingsState.hidePushTokensDefault; - final tokens = ref.watch(tokenProvider).tokensInFolder(widget.folder, exclude: hitePushTokens ? [PushToken] : []); + final hidePushTokens = ref.watch(settingsProvider).whenOrNull(data: (data) => data.hidePushTokens) ?? SettingsState.hidePushTokensDefault; + final tokens = ref.watch(tokenProvider).tokensInFolder(widget.folder, exclude: hidePushTokens ? [PushToken] : []); + tokens.sort((a, b) => a.compareTo(b)); final draggingSortable = ref.watch(draggingSortableProvider); if (widget.expandOverride == null) { @@ -143,7 +145,7 @@ class _TokenFolderExpandableState extends ConsumerState w child: DragTarget( onWillAcceptWithDetails: (details) { if (details.data.folderId != widget.folder.folderId) { - if (widget.folder.isLocked) return true; + if (widget.folder.isLocked || tokens.isEmpty) return true; _expandTimer?.cancel(); _expandTimer = Timer(const Duration(milliseconds: 500), () { if (!mounted) return; @@ -156,10 +158,13 @@ class _TokenFolderExpandableState extends ConsumerState w onLeave: (data) => _expandTimer?.cancel(), onAcceptWithDetails: (details) { log('Moving token to folder ${widget.folder.label}', name: 'TokenFolderExpandable'); - ref.read(tokenProvider.notifier).updateToken( - details.data, - (p0) => p0.copyWith(folderId: () => widget.folder.folderId, sortIndex: (widget.folder.sortIndex!) + 1), - ); + dragSortableOnAccept( + previousSortable: widget.folder, + dragedSortable: details.data, + nextSortable: null, + dependingFolder: widget.folder, + ref: ref, + ); }, builder: (context, willAccept, willReject) => Center( child: Container( From 2921c411f486feef0a6516a152ffb636b66a60e2 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:13:05 +0200 Subject: [PATCH 024/285] refactoring --- .../repo/token_folder_repository.dart | 6 +- lib/model/riverpod_states/progress_state.dart | 27 +- .../progress_state.freezed.dart | 408 ++++++++++++++++ .../riverpod_states/token_folder_state.dart | 11 +- ...google_authenticator_qrfile_processor.dart | 4 +- .../preference_token_folder_repository.dart | 17 +- lib/utils/home_widget_utils.dart | 2 +- .../progress_state_provider.dart | 78 +++ .../progress_state_provider.g.dart | 176 +++++++ .../progress_state_provider.dart | 29 -- .../token_folder_provider.dart | 222 ++++++++- .../token_folder_provider.g.dart | 177 +++++++ .../progress_state_notifier.dart | 2 +- .../token_folder_notifier.dart | 189 +------- lib/utils/utils.dart | 1 - .../pages/import_start_page.dart | 4 +- .../link_home_widget_view.dart | 2 +- .../rename_token_folder_action.dart | 2 +- .../token_folder_expandable.dart | 19 +- .../main_view_tokens_list.dart | 12 +- lib/views/splash_screen/splash_screen.dart | 2 - lib/widgets/app_wrapper.dart | 4 +- test/tests_app_wrapper.mocks.dart | 454 +++++++++--------- .../sortable_notifier_test.dart | 18 +- .../token_folder_notifier_test.dart | 66 ++- 25 files changed, 1419 insertions(+), 513 deletions(-) create mode 100644 lib/model/riverpod_states/progress_state.freezed.dart create mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.g.dart delete mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.g.dart diff --git a/lib/interfaces/repo/token_folder_repository.dart b/lib/interfaces/repo/token_folder_repository.dart index 7e6eaf62d..8b60c1f98 100644 --- a/lib/interfaces/repo/token_folder_repository.dart +++ b/lib/interfaces/repo/token_folder_repository.dart @@ -17,11 +17,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import '../../model/token_folder.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart'; abstract class TokenFolderRepository { /// Overwrite the current state with the new folders /// Returns true if the operation is successful, false otherwise - Future saveReplaceList(List folders); - Future> loadFolders(); + Future saveState(TokenFolderState state); + Future loadState(); } diff --git a/lib/model/riverpod_states/progress_state.dart b/lib/model/riverpod_states/progress_state.dart index ede019be8..84942aec1 100644 --- a/lib/model/riverpod_states/progress_state.dart +++ b/lib/model/riverpod_states/progress_state.dart @@ -17,17 +17,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -class ProgressState { - final int max; - final int value; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'progress_state.freezed.dart'; + +@freezed +class ProgressState with _$ProgressState { double get progress => value / max; - ProgressState( - this.max, - this.value, - ) : assert(max >= 0), - assert(value >= 0); + const ProgressState._(); + + const factory ProgressState.uninitialized({ + @Default(0) int max, + @Default(0) int value, + }) = ProgressStateUninitialized; - ProgressState copyWith({int? max, int? value, bool? inProgress}) => ProgressState(max ?? this.max, value ?? this.value); + @Assert('max >= 0', 'max must be greater than or equal to 0') + @Assert('value >= max', 'value must be less than or equal to max') + const factory ProgressState({ + required int max, + required int value, + }) = _ProgressState; } diff --git a/lib/model/riverpod_states/progress_state.freezed.dart b/lib/model/riverpod_states/progress_state.freezed.dart new file mode 100644 index 000000000..7bedbe0db --- /dev/null +++ b/lib/model/riverpod_states/progress_state.freezed.dart @@ -0,0 +1,408 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'progress_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$ProgressState { + int get max => throw _privateConstructorUsedError; + int get value => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when( + TResult Function(int max, int value) $default, { + required TResult Function(int max, int value) uninitialized, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function(int max, int value)? $default, { + TResult? Function(int max, int value)? uninitialized, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen( + TResult Function(int max, int value)? $default, { + TResult Function(int max, int value)? uninitialized, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map( + TResult Function(_ProgressState value) $default, { + required TResult Function(ProgressStateUninitialized value) uninitialized, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ProgressState value)? $default, { + TResult? Function(ProgressStateUninitialized value)? uninitialized, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ProgressState value)? $default, { + TResult Function(ProgressStateUninitialized value)? uninitialized, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $ProgressStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProgressStateCopyWith<$Res> { + factory $ProgressStateCopyWith( + ProgressState value, $Res Function(ProgressState) then) = + _$ProgressStateCopyWithImpl<$Res, ProgressState>; + @useResult + $Res call({int max, int value}); +} + +/// @nodoc +class _$ProgressStateCopyWithImpl<$Res, $Val extends ProgressState> + implements $ProgressStateCopyWith<$Res> { + _$ProgressStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? max = null, + Object? value = null, + }) { + return _then(_value.copyWith( + max: null == max + ? _value.max + : max // ignore: cast_nullable_to_non_nullable + as int, + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ProgressStateUninitializedImplCopyWith<$Res> + implements $ProgressStateCopyWith<$Res> { + factory _$$ProgressStateUninitializedImplCopyWith( + _$ProgressStateUninitializedImpl value, + $Res Function(_$ProgressStateUninitializedImpl) then) = + __$$ProgressStateUninitializedImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int max, int value}); +} + +/// @nodoc +class __$$ProgressStateUninitializedImplCopyWithImpl<$Res> + extends _$ProgressStateCopyWithImpl<$Res, _$ProgressStateUninitializedImpl> + implements _$$ProgressStateUninitializedImplCopyWith<$Res> { + __$$ProgressStateUninitializedImplCopyWithImpl( + _$ProgressStateUninitializedImpl _value, + $Res Function(_$ProgressStateUninitializedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? max = null, + Object? value = null, + }) { + return _then(_$ProgressStateUninitializedImpl( + max: null == max + ? _value.max + : max // ignore: cast_nullable_to_non_nullable + as int, + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$ProgressStateUninitializedImpl extends ProgressStateUninitialized { + const _$ProgressStateUninitializedImpl({this.max = 0, this.value = 0}) + : super._(); + + @override + @JsonKey() + final int max; + @override + @JsonKey() + final int value; + + @override + String toString() { + return 'ProgressState.uninitialized(max: $max, value: $value)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProgressStateUninitializedImpl && + (identical(other.max, max) || other.max == max) && + (identical(other.value, value) || other.value == value)); + } + + @override + int get hashCode => Object.hash(runtimeType, max, value); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ProgressStateUninitializedImplCopyWith<_$ProgressStateUninitializedImpl> + get copyWith => __$$ProgressStateUninitializedImplCopyWithImpl< + _$ProgressStateUninitializedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function(int max, int value) $default, { + required TResult Function(int max, int value) uninitialized, + }) { + return uninitialized(max, value); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function(int max, int value)? $default, { + TResult? Function(int max, int value)? uninitialized, + }) { + return uninitialized?.call(max, value); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function(int max, int value)? $default, { + TResult Function(int max, int value)? uninitialized, + required TResult orElse(), + }) { + if (uninitialized != null) { + return uninitialized(max, value); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_ProgressState value) $default, { + required TResult Function(ProgressStateUninitialized value) uninitialized, + }) { + return uninitialized(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ProgressState value)? $default, { + TResult? Function(ProgressStateUninitialized value)? uninitialized, + }) { + return uninitialized?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ProgressState value)? $default, { + TResult Function(ProgressStateUninitialized value)? uninitialized, + required TResult orElse(), + }) { + if (uninitialized != null) { + return uninitialized(this); + } + return orElse(); + } +} + +abstract class ProgressStateUninitialized extends ProgressState { + const factory ProgressStateUninitialized({final int max, final int value}) = + _$ProgressStateUninitializedImpl; + const ProgressStateUninitialized._() : super._(); + + @override + int get max; + @override + int get value; + @override + @JsonKey(ignore: true) + _$$ProgressStateUninitializedImplCopyWith<_$ProgressStateUninitializedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ProgressStateImplCopyWith<$Res> + implements $ProgressStateCopyWith<$Res> { + factory _$$ProgressStateImplCopyWith( + _$ProgressStateImpl value, $Res Function(_$ProgressStateImpl) then) = + __$$ProgressStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int max, int value}); +} + +/// @nodoc +class __$$ProgressStateImplCopyWithImpl<$Res> + extends _$ProgressStateCopyWithImpl<$Res, _$ProgressStateImpl> + implements _$$ProgressStateImplCopyWith<$Res> { + __$$ProgressStateImplCopyWithImpl( + _$ProgressStateImpl _value, $Res Function(_$ProgressStateImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? max = null, + Object? value = null, + }) { + return _then(_$ProgressStateImpl( + max: null == max + ? _value.max + : max // ignore: cast_nullable_to_non_nullable + as int, + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$ProgressStateImpl extends _ProgressState { + const _$ProgressStateImpl({required this.max, required this.value}) + : assert(max >= 0, 'max must be greater than or equal to 0'), + assert(value >= max, 'value must be less than or equal to max'), + super._(); + + @override + final int max; + @override + final int value; + + @override + String toString() { + return 'ProgressState(max: $max, value: $value)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProgressStateImpl && + (identical(other.max, max) || other.max == max) && + (identical(other.value, value) || other.value == value)); + } + + @override + int get hashCode => Object.hash(runtimeType, max, value); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ProgressStateImplCopyWith<_$ProgressStateImpl> get copyWith => + __$$ProgressStateImplCopyWithImpl<_$ProgressStateImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function(int max, int value) $default, { + required TResult Function(int max, int value) uninitialized, + }) { + return $default(max, value); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function(int max, int value)? $default, { + TResult? Function(int max, int value)? uninitialized, + }) { + return $default?.call(max, value); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function(int max, int value)? $default, { + TResult Function(int max, int value)? uninitialized, + required TResult orElse(), + }) { + if ($default != null) { + return $default(max, value); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_ProgressState value) $default, { + required TResult Function(ProgressStateUninitialized value) uninitialized, + }) { + return $default(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_ProgressState value)? $default, { + TResult? Function(ProgressStateUninitialized value)? uninitialized, + }) { + return $default?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_ProgressState value)? $default, { + TResult Function(ProgressStateUninitialized value)? uninitialized, + required TResult orElse(), + }) { + if ($default != null) { + return $default(this); + } + return orElse(); + } +} + +abstract class _ProgressState extends ProgressState { + const factory _ProgressState( + {required final int max, required final int value}) = _$ProgressStateImpl; + const _ProgressState._() : super._(); + + @override + int get max; + @override + int get value; + @override + @JsonKey(ignore: true) + _$$ProgressStateImplCopyWith<_$ProgressStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/model/riverpod_states/token_folder_state.dart b/lib/model/riverpod_states/token_folder_state.dart index bcf3cd492..faeec3ace 100644 --- a/lib/model/riverpod_states/token_folder_state.dart +++ b/lib/model/riverpod_states/token_folder_state.dart @@ -93,8 +93,17 @@ class TokenFolderState { get newFolderId => folders.fold(0, (previousValue, element) => max(previousValue, element.folderId)) + 1; /// Get the folder by the given id, or null if the folder does not exist - TokenFolder? currentById(int? id) => id == null ? null : folders.firstWhereOrNull((element) => element.folderId == id); + TokenFolder? currentOfId(int? id) => id == null ? null : folders.firstWhereOrNull((element) => element.folderId == id); /// Returns the current folder of the given folder, or null if the folder does not exist TokenFolder? currentOf(TokenFolder folder) => folders.firstWhereOrNull((element) => element.folderId == folder.folderId); + + TokenFolderState update(TokenFolder folder, TokenFolder Function(TokenFolder) updater) { + final newFolders = List.from(folders); + final index = newFolders.indexWhere((element) => element.folderId == folder.folderId); + if (index != -1) { + newFolders[index] = updater(newFolders[index]); + } + return TokenFolderState(folders: newFolders); + } } diff --git a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart index 941a2cc99..3c5554f6e 100644 --- a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart +++ b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart @@ -32,7 +32,7 @@ import '../../model/processor_result.dart'; import '../../model/tokens/token.dart'; import '../../utils/globals.dart'; import '../../utils/logger.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart'; import '../../utils/token_import_origins.dart'; import '../scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart'; import 'token_import_file_processor_interface.dart'; @@ -86,7 +86,7 @@ class GoogleAuthenticatorQrfileProcessor extends TokenImportFileProcessor { } on NotFoundException catch (_) { Logger.info("Qr-Code not detected. Zoom level: $zoomLevel|rotation: $rotation"); } - progress = globalRef?.read(progressStateProvider)?.progress; + progress = globalRef?.read(progressStateProvider).progress; } } if (qrResult == null || progress == null) { diff --git a/lib/repo/preference_token_folder_repository.dart b/lib/repo/preference_token_folder_repository.dart index 3422bc905..84ea64ae3 100644 --- a/lib/repo/preference_token_folder_repository.dart +++ b/lib/repo/preference_token_folder_repository.dart @@ -23,6 +23,7 @@ import 'package:mutex/mutex.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../interfaces/repo/token_folder_repository.dart'; +import '../model/riverpod_states/token_folder_state.dart'; import '../model/token_folder.dart'; import '../utils/logger.dart'; @@ -36,26 +37,26 @@ class PreferenceTokenFolderRepository extends TokenFolderRepository { static Future _protect(Future Function() f) => _m.protect(f); @override - Future> loadFolders() async => _protect(_loadFolders); - Future> _loadFolders() async { + Future loadState() async => _protect(_loadFolders); + Future _loadFolders() async { try { final foldersString = await _prefs.then((prefs) => prefs.getString(_tokenFoldersKey)); - if (foldersString == null) return []; + if (foldersString == null) return const TokenFolderState(folders: []); final jsons = jsonDecode(foldersString) as List; final folders = jsons.map((e) => TokenFolder.fromJson(e)).toList(); Logger.info('Loaded ${folders.length} folders from preferences', name: 'PreferenceTokenFolderRepository#loadFolders'); - return folders; + return TokenFolderState(folders: folders); } catch (e, s) { Logger.error('Failed to load folders', name: 'PreferenceTokenFolderRepository#loadFolders', error: e, stackTrace: s); - - return []; + return const TokenFolderState(folders: []); } } @override - Future saveReplaceList(List folders) => _protect(() => _saveReplaceList(folders)); - Future _saveReplaceList(List folders) async { + Future saveState(TokenFolderState state) => _protect(() => _saveReplaceList(state)); + Future _saveReplaceList(TokenFolderState state) async { + final folders = state.folders; Logger.info('Saving ${folders.length} folders to preferences...', name: 'PreferenceTokenFolderRepository#saveReplaceList'); try { final jsons = folders.map((e) => e.toJson()).toList(); diff --git a/lib/utils/home_widget_utils.dart b/lib/utils/home_widget_utils.dart index f09fef02a..bf738c4c4 100644 --- a/lib/utils/home_widget_utils.dart +++ b/lib/utils/home_widget_utils.dart @@ -183,7 +183,7 @@ class HomeWidgetUtils { static Future? _saveTokensToRepo(List tokens) => _tokenRepository?.saveOrReplaceTokens(tokens); static Future> _loadFoldersFromRepo() async { - return (await _folderRepository?.loadFolders()) ?? []; + return (await _folderRepository?.loadState())?.folders ?? []; } Future getTokenIdOfWidgetId(String widgetId) async { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart new file mode 100644 index 000000000..0d79cacf0 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart @@ -0,0 +1,78 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../../model/riverpod_states/progress_state.dart'; +import '../../../logger.dart'; + +part 'progress_state_provider.g.dart'; + +final progressStateProvider = progressStateNotifierProviderOf(Null); + +@riverpod +class ProgressStateNotifier extends _$ProgressStateNotifier { + @override + ProgressState build(Type type) { + Logger.info('Initializing progress state', name: 'ProgressStateNotifier#initProgress'); + return const ProgressState.uninitialized(); + } + + double? get progress => state.progress; + + ProgressState initProgress(int max, int value) { + Logger.info('Initializing progress state', name: 'ProgressStateNotifier#initProgress'); + final newState = ProgressState(max: max, value: value); + state = newState; + return newState; + } + + void deleteProgress() { + Logger.info('Deleting progress state', name: 'ProgressStateNotifier#deleteProgress'); + state = const ProgressState.uninitialized(); + } + + ProgressState? resetProgress() { + if (state is ProgressStateUninitialized) return state; + Logger.info('Resetting progress state', name: 'ProgressStateNotifier#resetProgress'); + final newState = state.copyWith(value: 0); + state = newState; + return newState; + } + + ProgressState? setProgressMax(int max) { + assert(state is! ProgressStateUninitialized, 'Progress state is uninitialized'); + assert(max > 0, 'Max value must be greater than 0'); + Logger.info('Setting progress max to $max', name: 'ProgressStateNotifier#setProgressMax'); + final newState = state.copyWith(max: max); + state = newState; + return newState; + } + + ProgressState? setProgressValue(int value) { + assert(state is! ProgressStateUninitialized, 'Progress state is uninitialized'); + assert(value >= 0, 'Value must be greater than or equal to 0'); + assert(value <= state.max, 'Value must be less than or equal to max value'); + Logger.info('Setting progress value to $value/${state.max}', name: 'ProgressStateNotifier#setProgressValue'); + final newState = state.copyWith(value: value); + state = newState; + return newState; + } +} diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.g.dart new file mode 100644 index 000000000..e5af71f1f --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.g.dart @@ -0,0 +1,176 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'progress_state_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$progressStateNotifierHash() => + r'f2da79434b36425d91169f5eb0f82cbd48ef8949'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$ProgressStateNotifier + extends BuildlessAutoDisposeNotifier { + late final Type type; + + ProgressState build( + Type type, + ); +} + +/// See also [ProgressStateNotifier]. +@ProviderFor(ProgressStateNotifier) +const progressStateNotifierProviderOf = ProgressStateNotifierFamily(); + +/// See also [ProgressStateNotifier]. +class ProgressStateNotifierFamily extends Family { + /// See also [ProgressStateNotifier]. + const ProgressStateNotifierFamily(); + + /// See also [ProgressStateNotifier]. + ProgressStateNotifierProvider call( + Type type, + ) { + return ProgressStateNotifierProvider( + type, + ); + } + + @override + ProgressStateNotifierProvider getProviderOverride( + covariant ProgressStateNotifierProvider provider, + ) { + return call( + provider.type, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'progressStateNotifierProviderOf'; +} + +/// See also [ProgressStateNotifier]. +class ProgressStateNotifierProvider extends AutoDisposeNotifierProviderImpl< + ProgressStateNotifier, ProgressState> { + /// See also [ProgressStateNotifier]. + ProgressStateNotifierProvider( + Type type, + ) : this._internal( + () => ProgressStateNotifier()..type = type, + from: progressStateNotifierProviderOf, + name: r'progressStateNotifierProviderOf', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$progressStateNotifierHash, + dependencies: ProgressStateNotifierFamily._dependencies, + allTransitiveDependencies: + ProgressStateNotifierFamily._allTransitiveDependencies, + type: type, + ); + + ProgressStateNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.type, + }) : super.internal(); + + final Type type; + + @override + ProgressState runNotifierBuild( + covariant ProgressStateNotifier notifier, + ) { + return notifier.build( + type, + ); + } + + @override + Override overrideWith(ProgressStateNotifier Function() create) { + return ProviderOverride( + origin: this, + override: ProgressStateNotifierProvider._internal( + () => create()..type = type, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + type: type, + ), + ); + } + + @override + AutoDisposeNotifierProviderElement + createElement() { + return _ProgressStateNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ProgressStateNotifierProvider && other.type == type; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, type.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin ProgressStateNotifierRef + on AutoDisposeNotifierProviderRef { + /// The parameter `type` of this provider. + Type get type; +} + +class _ProgressStateNotifierProviderElement + extends AutoDisposeNotifierProviderElement with ProgressStateNotifierRef { + _ProgressStateNotifierProviderElement(super.provider); + + @override + Type get type => (origin as ProgressStateNotifierProvider).type; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart deleted file mode 100644 index 2a3f2f6d3..000000000 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart +++ /dev/null @@ -1,29 +0,0 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; - -import '../../../../model/riverpod_states/progress_state.dart'; -import '../../state_notifiers/progress_state_notifier.dart'; -import '../../../logger.dart'; - -final progressStateProvider = StateNotifierProvider((ref) { - Logger.info("New ProgressStateNotifier created", name: 'progressStateProvider'); - return ProgressStateNotifier(); -}); diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart index 78a112ec0..d4d251f88 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart @@ -17,17 +17,219 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mutex/mutex.dart'; +import 'package:privacyidea_authenticator/repo/preference_token_folder_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../../../../interfaces/repo/token_folder_repository.dart'; import '../../../../model/riverpod_states/token_folder_state.dart'; -import '../../../../repo/preference_token_folder_repository.dart'; -import '../../state_notifiers/token_folder_notifier.dart'; +import '../../../../model/token_folder.dart'; import '../../../logger.dart'; -final tokenFolderProvider = StateNotifierProvider( - (ref) { - Logger.info("New TokenFolderNotifier created", name: 'tokenFolderProvider'); - return TokenFolderNotifier(repository: PreferenceTokenFolderRepository()); - }, - name: 'tokenFolderProvider', -); +part 'token_folder_provider.g.dart'; + +final tokenFolderProvider = tokenFolderNotifierProviderOf(repo: PreferenceTokenFolderRepository()); + +@riverpod +class TokenFolderNotifier extends _$TokenFolderNotifier { + late final Future initState; + final Mutex _repoMutex = Mutex(); + final Mutex _stateMutex = Mutex(); + + @override + TokenFolderRepository get repo => _repo; + final TokenFolderRepository? _repoOverride; + late final TokenFolderRepository _repo; + + TokenFolderNotifier({TokenFolderRepository? repoOverride}) + : _repoOverride = repoOverride, + super(); + + @override + TokenFolderState build({required TokenFolderRepository repo}) { + _repo = _repoOverride ?? repo; + Logger.info('Initializing token folder state', name: 'TokenFolderNotifier#initTokenFolder'); + initState = _loadFromRepo().then((newState) => state = newState); + return const TokenFolderState(folders: []); + } + + Future _loadFromRepo() async { + await _repoMutex.acquire(); + final newState = await _repo.loadState(); + _repoMutex.release(); + return newState; + } + + Future _saveToRepo(TokenFolderState state) async { + await _repoMutex.acquire(); + final success = await _repo.saveState(state); + if (!success) { + Logger.warning( + 'Failed to save folders', + name: 'TokenFolderNotifier#_saveToRepo', + ); + _repoMutex.release(); + return false; + } + _repoMutex.release(); + return true; + } + + // Future _addOrReplaceFolder(TokenFolder folder) async { + // await _repoMutex.acquire(); + // final newState = (state).addOrReplaceFolder(folder); + // final success = await _repo.saveReplaceList(newState.folders); + // if (!success) { + // Logger.warning( + // 'Failed to add or replace folder', + // name: 'TokenFolderNotifier#_addOrReplaceFolder', + // ); + // return false; + // } + // // state = newState; + // _repoMutex.release(); + // return true; + // } + + // Future _addNewFolder(String name) async { + // await _repoMutex.acquire(); + // final newState = (state).addNewFolder(name); + // final success = await _repo.saveReplaceList(newState.folders); + // if (!success) { + // Logger.warning( + // 'Failed to add new folder', + // name: 'TokenFolderNotifier#_addNewFolder', + // ); + // return false; + // } + // // state = newState; + // _repoMutex.release(); + // return true; + // } + + Future addNewFolder(String name) async { + await _stateMutex.acquire(); + final oldState = state; + final newState = oldState.addNewFolder(name); + final success = await _saveToRepo(newState); + if (!success) { + Logger.warning( + 'Failed to add new folder', + name: 'TokenFolderNotifier#_addNewFolder', + ); + _stateMutex.release(); + return oldState; + } + state = newState; + _stateMutex.release(); + return newState; + } + + Future removeFolder(TokenFolder folder) async { + await _stateMutex.acquire(); + final oldState = state; + final newState = oldState.removeFolder(folder); + final success = await _saveToRepo(newState); + if (!success) { + Logger.warning( + 'Failed to remove folder', + name: 'TokenFolderNotifier#_removeFolder', + ); + _stateMutex.release(); + return oldState; + } + state = newState; + _stateMutex.release(); + return newState; + } + + /// Search for the current version of the given folder and update it with the updater function. + /// If the folder is not found, nothing will happen. + /// Returns true if the operation is successful, false otherwise. + Future updateFolder(TokenFolder folder, TokenFolder Function(TokenFolder) updater) async { + await _stateMutex.acquire(); + final oldState = state; + final newState = oldState.update(folder, updater); + final success = await _saveToRepo(newState); + if (!success) { + Logger.warning( + 'Failed to add or replace folders', + name: 'TokenFolderNotifier#addOrReplaceFolders', + ); + _stateMutex.release(); + return oldState; + } + state = newState; + _stateMutex.release(); + return newState; + } + + /// Search for the current version of the given folder and update it with the updater function. + /// If the folder is not found, nothing will happen. + /// Returns true if the operation is successful, false otherwise. + Future updateFolderById(int folderId, TokenFolder Function(TokenFolder) updater) async { + await _stateMutex.acquire(); + final oldState = state; + final folder = oldState.currentOfId(folderId)!; + final newState = oldState.update(folder, updater); + final success = await _saveToRepo(newState); + if (!success) { + Logger.warning( + 'Failed to add or replace folders', + name: 'TokenFolderNotifier#addOrReplaceFolders', + ); + _stateMutex.release(); + return oldState; + } + state = newState; + _stateMutex.release(); + return newState; + } + + Future addOrReplaceFolders(List folders) async { + await _stateMutex.acquire(); + final oldState = state; + final newState = oldState.addOrReplaceFolders(folders); + final success = await _saveToRepo(newState); + if (!success) { + Logger.warning( + 'Failed to add or replace folders', + name: 'TokenFolderNotifier#addOrReplaceFolders', + ); + _stateMutex.release(); + return oldState; + } + state = newState; + _stateMutex.release(); + return newState; + } + + // Future expandFolder(TokenFolder folder) => updateFolder(folder, (p0) => p0.copyWith(isExpanded: true)); + // Future collapseFolder(TokenFolder folder) => updateFolder(folder, (p0) => p0.copyWith(isExpanded: false)); + // Future lockFolder(TokenFolder folder) => updateFolder(folder, (p0) => p0.copyWith(isLocked: true)); + // Future unlockFolder(TokenFolder folder) => updateFolder(folder, (p0) => p0.copyWith(isLocked: false)); + Future toggleFolderLock(TokenFolder folder) => updateFolder(folder, (p0) => p0.copyWith(isLocked: !folder.isLocked)); + Future updateLabel(TokenFolder folder, String label) => updateFolder(folder, (p0) => p0.copyWith(label: label)); + Future expandFolderById(int folderId) => updateFolderById(folderId, (p0) => p0.copyWith(isExpanded: true)); + + Future collapseLockedFolders() async { + await _stateMutex.acquire(); + final lockedFolders = (state).folders.where((element) => element.isLocked).toList(); + for (var i = 0; i < lockedFolders.length; i++) { + lockedFolders[i] = lockedFolders[i].copyWith(isExpanded: false); + } + final oldState = state; + final newState = oldState.addOrReplaceFolders(lockedFolders); + final success = await _saveToRepo(newState); + if (!success) { + Logger.warning( + 'Failed to add or replace folders', + name: 'TokenFolderNotifier#addOrReplaceFolders', + ); + _stateMutex.release(); + return oldState; + } + _stateMutex.release(); + return newState; + } +} diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.g.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.g.dart new file mode 100644 index 000000000..f736b212f --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.g.dart @@ -0,0 +1,177 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'token_folder_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$tokenFolderNotifierHash() => + r'cba0fe40fa6c71aa6c3436b8fd9bde0e7cf94388'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$TokenFolderNotifier + extends BuildlessAutoDisposeNotifier { + late final TokenFolderRepository repo; + + TokenFolderState build({ + required TokenFolderRepository repo, + }); +} + +/// See also [TokenFolderNotifier]. +@ProviderFor(TokenFolderNotifier) +const tokenFolderNotifierProviderOf = TokenFolderNotifierFamily(); + +/// See also [TokenFolderNotifier]. +class TokenFolderNotifierFamily extends Family { + /// See also [TokenFolderNotifier]. + const TokenFolderNotifierFamily(); + + /// See also [TokenFolderNotifier]. + TokenFolderNotifierProvider call({ + required TokenFolderRepository repo, + }) { + return TokenFolderNotifierProvider( + repo: repo, + ); + } + + @override + TokenFolderNotifierProvider getProviderOverride( + covariant TokenFolderNotifierProvider provider, + ) { + return call( + repo: provider.repo, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'tokenFolderNotifierProviderOf'; +} + +/// See also [TokenFolderNotifier]. +class TokenFolderNotifierProvider extends AutoDisposeNotifierProviderImpl< + TokenFolderNotifier, TokenFolderState> { + /// See also [TokenFolderNotifier]. + TokenFolderNotifierProvider({ + required TokenFolderRepository repo, + }) : this._internal( + () => TokenFolderNotifier()..repo = repo, + from: tokenFolderNotifierProviderOf, + name: r'tokenFolderNotifierProviderOf', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$tokenFolderNotifierHash, + dependencies: TokenFolderNotifierFamily._dependencies, + allTransitiveDependencies: + TokenFolderNotifierFamily._allTransitiveDependencies, + repo: repo, + ); + + TokenFolderNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.repo, + }) : super.internal(); + + final TokenFolderRepository repo; + + @override + TokenFolderState runNotifierBuild( + covariant TokenFolderNotifier notifier, + ) { + return notifier.build( + repo: repo, + ); + } + + @override + Override overrideWith(TokenFolderNotifier Function() create) { + return ProviderOverride( + origin: this, + override: TokenFolderNotifierProvider._internal( + () => create()..repo = repo, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + repo: repo, + ), + ); + } + + @override + AutoDisposeNotifierProviderElement + createElement() { + return _TokenFolderNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is TokenFolderNotifierProvider && other.repo == repo; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, repo.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin TokenFolderNotifierRef + on AutoDisposeNotifierProviderRef { + /// The parameter `repo` of this provider. + TokenFolderRepository get repo; +} + +class _TokenFolderNotifierProviderElement + extends AutoDisposeNotifierProviderElement with TokenFolderNotifierRef { + _TokenFolderNotifierProviderElement(super.provider); + + @override + TokenFolderRepository get repo => + (origin as TokenFolderNotifierProvider).repo; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart b/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart index ac4a491ec..ad4e7cf22 100644 --- a/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart @@ -29,7 +29,7 @@ class ProgressStateNotifier extends StateNotifier { ProgressState initProgress(int max, int value) { Logger.info('Initializing progress state', name: 'ProgressStateNotifier#initProgress'); - final newState = ProgressState(max, value); + final newState = ProgressState(max: max, value: value); state = newState; return newState; } diff --git a/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart b/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart index 7eeb9d3c1..67c17d27c 100644 --- a/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_folder_notifier.dart @@ -1,164 +1,27 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; -import 'package:mutex/mutex.dart'; +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; +// import 'package:mutex/mutex.dart'; + +// import '../../../interfaces/repo/token_folder_repository.dart'; +// import '../../../model/riverpod_states/token_folder_state.dart'; +// import '../../../model/token_folder.dart'; +// import '../../logger.dart'; -import '../../../interfaces/repo/token_folder_repository.dart'; -import '../../../model/riverpod_states/token_folder_state.dart'; -import '../../../model/token_folder.dart'; -import '../../logger.dart'; - -class TokenFolderNotifier extends StateNotifier { - late final Future initState; - final Mutex _loadingRepoMutex = Mutex(); - final Mutex _updateFolderMutex = Mutex(); - final TokenFolderRepository _repo; - - TokenFolderNotifier({required TokenFolderRepository repository, TokenFolderState? initialState}) - : _repo = repository, - super(initialState ?? const TokenFolderState(folders: [])) { - _init(); - } - - void _init() { - initState = _loadFromRepo(); - } - - Future _loadFromRepo() async { - _loadingRepoMutex.acquire(); - try { - final folders = await _repo.loadFolders(); - _loadingRepoMutex.release(); - final newState = TokenFolderState(folders: folders); - state = newState; - return newState; - } catch (e, s) { - Logger.error( - 'Failed to load folders', - name: 'TokenFolderNotifier#_loadFromRepo', - error: e, - stackTrace: s, - ); - _loadingRepoMutex.release(); - return state; - } - } - - Future _addOrReplaceFolders(List folders) async { - await _loadingRepoMutex.acquire(); - final success = await _repo.saveReplaceList(folders); - if (!success) { - Logger.warning( - 'Failed to save folders', - name: 'TokenFolderNotifier#_addOrReplaceFolders', - ); - return false; - } - state = state.addOrReplaceFolders(folders); - _loadingRepoMutex.release(); - return true; - } - - Future _addOrReplaceFolder(TokenFolder folder) async { - await _loadingRepoMutex.acquire(); - final newState = state.addOrReplaceFolder(folder); - final success = await _repo.saveReplaceList(newState.folders); - if (!success) { - Logger.warning( - 'Failed to add or replace folder', - name: 'TokenFolderNotifier#_addOrReplaceFolder', - ); - return false; - } - state = newState; - _loadingRepoMutex.release(); - return true; - } - - Future _addNewFolder(String name) async { - await _loadingRepoMutex.acquire(); - final newState = state.addNewFolder(name); - final success = await _repo.saveReplaceList(newState.folders); - if (!success) { - Logger.warning( - 'Failed to add new folder', - name: 'TokenFolderNotifier#_addNewFolder', - ); - return false; - } - state = newState; - _loadingRepoMutex.release(); - return true; - } - - Future _removeFolder(TokenFolder folder) async { - await _loadingRepoMutex.acquire(); - final newState = state.removeFolder(folder); - final success = await _repo.saveReplaceList(newState.folders); - if (!success) { - Logger.warning( - 'Failed to remove folder', - name: 'TokenFolderNotifier#_removeFolder', - ); - return false; - } - state = newState; - _loadingRepoMutex.release(); - return true; - } - - Future addNewFolder(String name) => _addNewFolder(name); - Future removeFolder(TokenFolder folder) => _removeFolder(folder); - - /// Search for the current version of the given folder and update it with the updater function. - /// If the folder is not found, nothing will happen. - /// Returns true if the operation is successful, false otherwise. - Future updateFolder(TokenFolder folder, Function(TokenFolder) updater) async { - await _updateFolderMutex.acquire(); - final curent = state.currentOf(folder); - if (curent == null) return false; - final newFolder = updater(curent); - final success = await _addOrReplaceFolder(newFolder); - _updateFolderMutex.release(); - return success; - } - - Future addOrReplaceFolders(List folders) => _addOrReplaceFolders(folders); - - Future expandFolder(TokenFolder folder) => updateFolder(folder, (p0) => p0.copyWith(isExpanded: true)); - Future expandFolderById(int folderId) => updateFolder(state.currentById(folderId)!, (p0) => p0.copyWith(isExpanded: true)); - Future collapseFolder(TokenFolder folder) => updateFolder(folder, (p0) => p0.copyWith(isExpanded: false)); - Future lockFolder(TokenFolder folder) => updateFolder(folder, (p0) => p0.copyWith(isLocked: true)); - Future unlockFolder(TokenFolder folder) => updateFolder(folder, (p0) => p0.copyWith(isLocked: false)); - Future toggleFolderLock(TokenFolder folder) => updateFolder(folder, (p0) => p0.copyWith(isLocked: !folder.isLocked)); - Future updateLabel(TokenFolder folder, String label) => updateFolder(folder, (p0) => p0.copyWith(label: label)); - - Future collapseLockedFolders() async { - await _updateFolderMutex.acquire(); - final lockedFolders = state.folders.where((element) => element.isLocked).toList(); - for (var i = 0; i < lockedFolders.length; i++) { - lockedFolders[i] = lockedFolders[i].copyWith(isExpanded: false); - } - final newState = state.addOrReplaceFolders(lockedFolders); - final success = _addOrReplaceFolders(newState.folders); - _updateFolderMutex.release(); - return success; - } -} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index e2d08f17a..d591a7a2a 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -151,7 +151,6 @@ void dragSortableOnAccept({ required WidgetRef ref, }) { var allSortables = ref.read(sortablesProvider); - if (dragedSortable is TokenFolder) { final tokensInFolder = ref.read(tokenProvider).tokens.where((element) => element.folderId == dragedSortable.folderId).toList(); final allMovingItems = [dragedSortable, ...tokensInFolder]; 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 df3fce2cd..b055bf613 100644 --- a/lib/views/import_tokens_view/pages/import_start_page.dart +++ b/lib/views/import_tokens_view/pages/import_start_page.dart @@ -39,7 +39,7 @@ import '../../../processors/scheme_processors/token_import_scheme_processors/tok import '../../../processors/token_import_file_processor/token_import_file_processor_interface.dart'; import '../../../utils/globals.dart'; import '../../../utils/logger.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/progress_state_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart'; import '../../qr_scanner_view/qr_scanner_view.dart'; import '../import_tokens_view.dart'; import '../widgets/dialogs/qr_not_found_dialog.dart'; @@ -83,7 +83,7 @@ class _ImportStartPageState extends ConsumerState { @override Widget build(BuildContext context) { final localizations = AppLocalizations.of(context)!; - final currentLoadingProgress = ref.watch(progressStateProvider)?.progress; + final currentLoadingProgress = ref.watch(progressStateProvider).progress; return Scaffold( appBar: AppBar( title: Text(widget.appName), 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 d9fd57936..e4ee4a5fc 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 @@ -55,7 +55,7 @@ class _LinkHomeWidgetViewState extends ConsumerState { body: ListView.builder( itemBuilder: (context, index) { final otpToken = otpTokens[index]; - final folderIsLocked = ref.watch(tokenFolderProvider).currentById(otpToken.folderId)?.isLocked ?? false; + final folderIsLocked = ref.watch(tokenFolderProvider).currentOfId(otpToken.folderId)?.isLocked ?? false; final otpString = otpToken.isLocked || folderIsLocked ? veilingCharacter * otpToken.otpValue.length : otpToken.otpValue; return ListTile( title: Text(otpToken.label), 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 5a51a8abc..2aeb626cb 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 @@ -98,7 +98,7 @@ class RenameTokenFolderAction extends StatelessWidget { final newLabel = nameInputController.text.trim(); if (newLabel.isEmpty) return; final success = await globalRef?.read(tokenFolderProvider.notifier).updateLabel(folder, newLabel); - if (success == true) { + if (success != null) { Logger.info( 'Renamed token:', name: 'token_widget_base.dart#TextButton#renameClicked', 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 263d1a16f..8aeb22aac 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 @@ -18,7 +18,6 @@ * limitations under the License. */ import 'dart:async'; -import 'dart:developer'; import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; @@ -146,6 +145,7 @@ class _TokenFolderExpandableState extends ConsumerState w onWillAcceptWithDetails: (details) { if (details.data.folderId != widget.folder.folderId) { if (widget.folder.isLocked || tokens.isEmpty) return true; + if (expandableController.value) return true; _expandTimer?.cancel(); _expandTimer = Timer(const Duration(milliseconds: 500), () { if (!mounted) return; @@ -156,16 +156,13 @@ class _TokenFolderExpandableState extends ConsumerState w return false; }, onLeave: (data) => _expandTimer?.cancel(), - onAcceptWithDetails: (details) { - log('Moving token to folder ${widget.folder.label}', name: 'TokenFolderExpandable'); - dragSortableOnAccept( - previousSortable: widget.folder, - dragedSortable: details.data, - nextSortable: null, - dependingFolder: widget.folder, - ref: ref, - ); - }, + onAcceptWithDetails: (details) => dragSortableOnAccept( + previousSortable: widget.folder, + dragedSortable: details.data, + nextSortable: null, + dependingFolder: widget.folder, + ref: ref, + ), builder: (context, willAccept, willReject) => Center( child: Container( margin: widget.folder.isExpanded ? null : const EdgeInsets.only(right: 8), 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 64ff25f71..9b1955378 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 @@ -63,10 +63,15 @@ class MainViewTokensList extends ConsumerStatefulWidget { widgets.add(DragTargetDivider(dependingFolder: null, previousSortable: sortables.last, nextSortable: sortables[i])); } if (introductionAdded == false && sortables[i] is Token) { - widgets.add(TokenIntroduction(child: SortableWidgetBuilder.fromSortable(sortables[i]))); + widgets.add( + TokenIntroduction( + key: Key('mainview_introduction_${sortables[i].runtimeType}${sortables[i].sortIndex}'), + child: SortableWidgetBuilder.fromSortable(sortables[i]), + ), + ); introductionAdded = true; } else { - widgets.add(SortableWidgetBuilder.fromSortable(sortables[i])); + widgets.add(SortableWidgetBuilder.fromSortable(sortables[i], key: Key('mainview_${sortables[i].runtimeType}${sortables[i].sortIndex}'))); } } @@ -77,7 +82,6 @@ class MainViewTokensList extends ConsumerStatefulWidget { class _MainViewTokensListState extends ConsumerState { final listViewKey = GlobalKey(); final scrollController = ScrollController(); - Duration? lastTimeStamp; @override @@ -87,8 +91,8 @@ class _MainViewTokensListState extends ConsumerState { final allowToRefresh = allSortables.any((element) => element is PushToken); final hidePushTokens = ref.watch(settingsProvider).whenOrNull(data: (data) => data.hidePushTokens) ?? SettingsState.hidePushTokensDefault; final filterPushTokens = hidePushTokens && allowToRefresh; - final showSortables = []; // List of sortables that should be shown in the list + for (var element in allSortables) { if (element is! Token) { showSortables.add(element); diff --git a/lib/views/splash_screen/splash_screen.dart b/lib/views/splash_screen/splash_screen.dart index 1298a3915..a81be3f3e 100644 --- a/lib/views/splash_screen/splash_screen.dart +++ b/lib/views/splash_screen/splash_screen.dart @@ -26,7 +26,6 @@ import '../../utils/customization/application_customization.dart'; import '../../utils/home_widget_utils.dart'; import '../../utils/logger.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; import '../main_view/main_view.dart'; import '../view_interface.dart'; @@ -63,7 +62,6 @@ class _SplashScreenState extends ConsumerState { [ Future.delayed(_splashScreenDuration), ref.read(tokenProvider.notifier).initState, - ref.read(tokenFolderProvider.notifier).initState, AppInfoUtils.init(), HomeWidgetUtils().homeWidgetInit(), ], diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index 6a27b5c54..6bedb0621 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -29,9 +29,7 @@ class AppWrapper extends StatelessWidget { const AppWrapper({required this.child, super.key}); @override - Widget build(BuildContext context) { - return ProviderScope(child: _AppWrapper(key: key, child: child)); - } + Widget build(BuildContext context) => ProviderScope(child: _AppWrapper(key: key, child: child)); } class _AppWrapper extends ConsumerStatefulWidget { diff --git a/test/tests_app_wrapper.mocks.dart b/test/tests_app_wrapper.mocks.dart index 19882c12c..c6a641ffa 100644 --- a/test/tests_app_wrapper.mocks.dart +++ b/test/tests_app_wrapper.mocks.dart @@ -3,39 +3,40 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i11; +import 'dart:async' as _i12; import 'dart:typed_data' as _i17; import 'package:firebase_messaging/firebase_messaging.dart' as _i19; -import 'package:http/http.dart' as _i3; +import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i16; -import 'package:pointycastle/export.dart' as _i4; +import 'package:pointycastle/export.dart' as _i5; import 'package:privacyidea_authenticator/interfaces/repo/introduction_repository.dart' as _i20; import 'package:privacyidea_authenticator/interfaces/repo/push_request_repository.dart' as _i23; import 'package:privacyidea_authenticator/interfaces/repo/settings_repository.dart' - as _i13; -import 'package:privacyidea_authenticator/interfaces/repo/token_folder_repository.dart' as _i14; +import 'package:privacyidea_authenticator/interfaces/repo/token_folder_repository.dart' + as _i15; import 'package:privacyidea_authenticator/interfaces/repo/token_repository.dart' - as _i10; + as _i11; import 'package:privacyidea_authenticator/model/push_request.dart' as _i22; import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart' - as _i5; + as _i6; import 'package:privacyidea_authenticator/model/riverpod_states/push_request_state.dart' - as _i9; + as _i10; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart' as _i2; -import 'package:privacyidea_authenticator/model/token_folder.dart' as _i15; +import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart' + as _i3; import 'package:privacyidea_authenticator/model/tokens/push_token.dart' as _i18; -import 'package:privacyidea_authenticator/model/tokens/token.dart' as _i12; -import 'package:privacyidea_authenticator/utils/firebase_utils.dart' as _i6; +import 'package:privacyidea_authenticator/model/tokens/token.dart' as _i13; +import 'package:privacyidea_authenticator/utils/firebase_utils.dart' as _i7; import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart' - as _i7; + as _i8; import 'package:privacyidea_authenticator/utils/push_provider.dart' as _i21; -import 'package:privacyidea_authenticator/utils/rsa_utils.dart' as _i8; +import 'package:privacyidea_authenticator/utils/rsa_utils.dart' as _i9; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -60,8 +61,9 @@ class _FakeSettingsState_0 extends _i1.SmartFake implements _i2.SettingsState { ); } -class _FakeResponse_1 extends _i1.SmartFake implements _i3.Response { - _FakeResponse_1( +class _FakeTokenFolderState_1 extends _i1.SmartFake + implements _i3.TokenFolderState { + _FakeTokenFolderState_1( Object parent, Invocation parentInvocation, ) : super( @@ -70,8 +72,8 @@ class _FakeResponse_1 extends _i1.SmartFake implements _i3.Response { ); } -class _FakeRSAPublicKey_2 extends _i1.SmartFake implements _i4.RSAPublicKey { - _FakeRSAPublicKey_2( +class _FakeResponse_2 extends _i1.SmartFake implements _i4.Response { + _FakeResponse_2( Object parent, Invocation parentInvocation, ) : super( @@ -80,8 +82,8 @@ class _FakeRSAPublicKey_2 extends _i1.SmartFake implements _i4.RSAPublicKey { ); } -class _FakeRSAPrivateKey_3 extends _i1.SmartFake implements _i4.RSAPrivateKey { - _FakeRSAPrivateKey_3( +class _FakeRSAPublicKey_3 extends _i1.SmartFake implements _i5.RSAPublicKey { + _FakeRSAPublicKey_3( Object parent, Invocation parentInvocation, ) : super( @@ -90,10 +92,8 @@ class _FakeRSAPrivateKey_3 extends _i1.SmartFake implements _i4.RSAPrivateKey { ); } -class _FakeAsymmetricKeyPair_4 extends _i1.SmartFake - implements _i4.AsymmetricKeyPair { - _FakeAsymmetricKeyPair_4( +class _FakeRSAPrivateKey_4 extends _i1.SmartFake implements _i5.RSAPrivateKey { + _FakeRSAPrivateKey_4( Object parent, Invocation parentInvocation, ) : super( @@ -102,9 +102,10 @@ class _FakeAsymmetricKeyPair_4 extends _i1.SmartFake + implements _i5.AsymmetricKeyPair { + _FakeAsymmetricKeyPair_5( Object parent, Invocation parentInvocation, ) : super( @@ -113,8 +114,9 @@ class _FakeIntroductionState_5 extends _i1.SmartFake ); } -class _FakeFirebaseUtils_6 extends _i1.SmartFake implements _i6.FirebaseUtils { - _FakeFirebaseUtils_6( +class _FakeIntroductionState_6 extends _i1.SmartFake + implements _i6.IntroductionState { + _FakeIntroductionState_6( Object parent, Invocation parentInvocation, ) : super( @@ -123,9 +125,8 @@ class _FakeFirebaseUtils_6 extends _i1.SmartFake implements _i6.FirebaseUtils { ); } -class _FakePrivacyideaIOClient_7 extends _i1.SmartFake - implements _i7.PrivacyideaIOClient { - _FakePrivacyideaIOClient_7( +class _FakeFirebaseUtils_7 extends _i1.SmartFake implements _i7.FirebaseUtils { + _FakeFirebaseUtils_7( Object parent, Invocation parentInvocation, ) : super( @@ -134,8 +135,9 @@ class _FakePrivacyideaIOClient_7 extends _i1.SmartFake ); } -class _FakeRsaUtils_8 extends _i1.SmartFake implements _i8.RsaUtils { - _FakeRsaUtils_8( +class _FakePrivacyideaIOClient_8 extends _i1.SmartFake + implements _i8.PrivacyideaIOClient { + _FakePrivacyideaIOClient_8( Object parent, Invocation parentInvocation, ) : super( @@ -144,9 +146,19 @@ class _FakeRsaUtils_8 extends _i1.SmartFake implements _i8.RsaUtils { ); } -class _FakePushRequestState_9 extends _i1.SmartFake - implements _i9.PushRequestState { - _FakePushRequestState_9( +class _FakeRsaUtils_9 extends _i1.SmartFake implements _i9.RsaUtils { + _FakeRsaUtils_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePushRequestState_10 extends _i1.SmartFake + implements _i10.PushRequestState { + _FakePushRequestState_10( Object parent, Invocation parentInvocation, ) : super( @@ -158,96 +170,96 @@ class _FakePushRequestState_9 extends _i1.SmartFake /// A class which mocks [TokenRepository]. /// /// See the documentation for Mockito's code generation for more information. -class MockTokenRepository extends _i1.Mock implements _i10.TokenRepository { +class MockTokenRepository extends _i1.Mock implements _i11.TokenRepository { @override - _i11.Future<_i12.Token?> loadToken(String? id) => (super.noSuchMethod( + _i12.Future<_i13.Token?> loadToken(String? id) => (super.noSuchMethod( Invocation.method( #loadToken, [id], ), - returnValue: _i11.Future<_i12.Token?>.value(), - returnValueForMissingStub: _i11.Future<_i12.Token?>.value(), - ) as _i11.Future<_i12.Token?>); + returnValue: _i12.Future<_i13.Token?>.value(), + returnValueForMissingStub: _i12.Future<_i13.Token?>.value(), + ) as _i12.Future<_i13.Token?>); @override - _i11.Future> loadTokens() => (super.noSuchMethod( + _i12.Future> loadTokens() => (super.noSuchMethod( Invocation.method( #loadTokens, [], ), - returnValue: _i11.Future>.value(<_i12.Token>[]), + returnValue: _i12.Future>.value(<_i13.Token>[]), returnValueForMissingStub: - _i11.Future>.value(<_i12.Token>[]), - ) as _i11.Future>); + _i12.Future>.value(<_i13.Token>[]), + ) as _i12.Future>); @override - _i11.Future saveOrReplaceToken(_i12.Token? token) => + _i12.Future saveOrReplaceToken(_i13.Token? token) => (super.noSuchMethod( Invocation.method( #saveOrReplaceToken, [token], ), - returnValue: _i11.Future.value(false), - returnValueForMissingStub: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i12.Future.value(false), + returnValueForMissingStub: _i12.Future.value(false), + ) as _i12.Future); @override - _i11.Future> saveOrReplaceTokens( + _i12.Future> saveOrReplaceTokens( List? tokens) => (super.noSuchMethod( Invocation.method( #saveOrReplaceTokens, [tokens], ), - returnValue: _i11.Future>.value([]), - returnValueForMissingStub: _i11.Future>.value([]), - ) as _i11.Future>); + returnValue: _i12.Future>.value([]), + returnValueForMissingStub: _i12.Future>.value([]), + ) as _i12.Future>); @override - _i11.Future deleteToken(_i12.Token? token) => (super.noSuchMethod( + _i12.Future deleteToken(_i13.Token? token) => (super.noSuchMethod( Invocation.method( #deleteToken, [token], ), - returnValue: _i11.Future.value(false), - returnValueForMissingStub: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i12.Future.value(false), + returnValueForMissingStub: _i12.Future.value(false), + ) as _i12.Future); @override - _i11.Future> deleteTokens(List? tokens) => + _i12.Future> deleteTokens(List? tokens) => (super.noSuchMethod( Invocation.method( #deleteTokens, [tokens], ), - returnValue: _i11.Future>.value([]), - returnValueForMissingStub: _i11.Future>.value([]), - ) as _i11.Future>); + returnValue: _i12.Future>.value([]), + returnValueForMissingStub: _i12.Future>.value([]), + ) as _i12.Future>); } /// A class which mocks [SettingsRepository]. /// /// See the documentation for Mockito's code generation for more information. class MockSettingsRepository extends _i1.Mock - implements _i13.SettingsRepository { + implements _i14.SettingsRepository { @override - _i11.Future saveSettings(_i2.SettingsState? settings) => + _i12.Future saveSettings(_i2.SettingsState? settings) => (super.noSuchMethod( Invocation.method( #saveSettings, [settings], ), - returnValue: _i11.Future.value(false), - returnValueForMissingStub: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i12.Future.value(false), + returnValueForMissingStub: _i12.Future.value(false), + ) as _i12.Future); @override - _i11.Future<_i2.SettingsState> loadSettings() => (super.noSuchMethod( + _i12.Future<_i2.SettingsState> loadSettings() => (super.noSuchMethod( Invocation.method( #loadSettings, [], ), - returnValue: _i11.Future<_i2.SettingsState>.value(_FakeSettingsState_0( + returnValue: _i12.Future<_i2.SettingsState>.value(_FakeSettingsState_0( this, Invocation.method( #loadSettings, @@ -255,52 +267,64 @@ class MockSettingsRepository extends _i1.Mock ), )), returnValueForMissingStub: - _i11.Future<_i2.SettingsState>.value(_FakeSettingsState_0( + _i12.Future<_i2.SettingsState>.value(_FakeSettingsState_0( this, Invocation.method( #loadSettings, [], ), )), - ) as _i11.Future<_i2.SettingsState>); + ) as _i12.Future<_i2.SettingsState>); } /// A class which mocks [TokenFolderRepository]. /// /// See the documentation for Mockito's code generation for more information. class MockTokenFolderRepository extends _i1.Mock - implements _i14.TokenFolderRepository { + implements _i15.TokenFolderRepository { @override - _i11.Future saveReplaceList(List<_i15.TokenFolder>? folders) => + _i12.Future saveState(_i3.TokenFolderState? state) => (super.noSuchMethod( Invocation.method( - #saveReplaceList, - [folders], + #saveState, + [state], ), - returnValue: _i11.Future.value(false), - returnValueForMissingStub: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i12.Future.value(false), + returnValueForMissingStub: _i12.Future.value(false), + ) as _i12.Future); @override - _i11.Future> loadFolders() => (super.noSuchMethod( + _i12.Future<_i3.TokenFolderState> loadState() => (super.noSuchMethod( Invocation.method( - #loadFolders, + #loadState, [], ), returnValue: - _i11.Future>.value(<_i15.TokenFolder>[]), + _i12.Future<_i3.TokenFolderState>.value(_FakeTokenFolderState_1( + this, + Invocation.method( + #loadState, + [], + ), + )), returnValueForMissingStub: - _i11.Future>.value(<_i15.TokenFolder>[]), - ) as _i11.Future>); + _i12.Future<_i3.TokenFolderState>.value(_FakeTokenFolderState_1( + this, + Invocation.method( + #loadState, + [], + ), + )), + ) as _i12.Future<_i3.TokenFolderState>); } /// A class which mocks [PrivacyideaIOClient]. /// /// See the documentation for Mockito's code generation for more information. class MockPrivacyideaIOClient extends _i1.Mock - implements _i7.PrivacyideaIOClient { + implements _i8.PrivacyideaIOClient { @override - _i11.Future triggerNetworkAccessPermission({ + _i12.Future triggerNetworkAccessPermission({ required Uri? url, bool? sslVerify = true, bool? isRetry = false, @@ -315,12 +339,12 @@ class MockPrivacyideaIOClient extends _i1.Mock #isRetry: isRetry, }, ), - returnValue: _i11.Future.value(false), - returnValueForMissingStub: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i12.Future.value(false), + returnValueForMissingStub: _i12.Future.value(false), + ) as _i12.Future); @override - _i11.Future<_i3.Response> doPost({ + _i12.Future<_i4.Response> doPost({ required Uri? url, required Map? body, bool? sslVerify = true, @@ -335,7 +359,7 @@ class MockPrivacyideaIOClient extends _i1.Mock #sslVerify: sslVerify, }, ), - returnValue: _i11.Future<_i3.Response>.value(_FakeResponse_1( + returnValue: _i12.Future<_i4.Response>.value(_FakeResponse_2( this, Invocation.method( #doPost, @@ -348,7 +372,7 @@ class MockPrivacyideaIOClient extends _i1.Mock ), )), returnValueForMissingStub: - _i11.Future<_i3.Response>.value(_FakeResponse_1( + _i12.Future<_i4.Response>.value(_FakeResponse_2( this, Invocation.method( #doPost, @@ -360,10 +384,10 @@ class MockPrivacyideaIOClient extends _i1.Mock }, ), )), - ) as _i11.Future<_i3.Response>); + ) as _i12.Future<_i4.Response>); @override - _i11.Future<_i3.Response> doGet({ + _i12.Future<_i4.Response> doGet({ required Uri? url, required Map? parameters, bool? sslVerify = true, @@ -378,7 +402,7 @@ class MockPrivacyideaIOClient extends _i1.Mock #sslVerify: sslVerify, }, ), - returnValue: _i11.Future<_i3.Response>.value(_FakeResponse_1( + returnValue: _i12.Future<_i4.Response>.value(_FakeResponse_2( this, Invocation.method( #doGet, @@ -391,7 +415,7 @@ class MockPrivacyideaIOClient extends _i1.Mock ), )), returnValueForMissingStub: - _i11.Future<_i3.Response>.value(_FakeResponse_1( + _i12.Future<_i4.Response>.value(_FakeResponse_2( this, Invocation.method( #doGet, @@ -403,38 +427,38 @@ class MockPrivacyideaIOClient extends _i1.Mock }, ), )), - ) as _i11.Future<_i3.Response>); + ) as _i12.Future<_i4.Response>); } /// A class which mocks [RsaUtils]. /// /// See the documentation for Mockito's code generation for more information. -class MockRsaUtils extends _i1.Mock implements _i8.RsaUtils { +class MockRsaUtils extends _i1.Mock implements _i9.RsaUtils { @override - _i4.RSAPublicKey deserializeRSAPublicKeyPKCS1(String? keyStr) => + _i5.RSAPublicKey deserializeRSAPublicKeyPKCS1(String? keyStr) => (super.noSuchMethod( Invocation.method( #deserializeRSAPublicKeyPKCS1, [keyStr], ), - returnValue: _FakeRSAPublicKey_2( + returnValue: _FakeRSAPublicKey_3( this, Invocation.method( #deserializeRSAPublicKeyPKCS1, [keyStr], ), ), - returnValueForMissingStub: _FakeRSAPublicKey_2( + returnValueForMissingStub: _FakeRSAPublicKey_3( this, Invocation.method( #deserializeRSAPublicKeyPKCS1, [keyStr], ), ), - ) as _i4.RSAPublicKey); + ) as _i5.RSAPublicKey); @override - String serializeRSAPublicKeyPKCS1(_i4.RSAPublicKey? publicKey) => + String serializeRSAPublicKeyPKCS1(_i5.RSAPublicKey? publicKey) => (super.noSuchMethod( Invocation.method( #serializeRSAPublicKeyPKCS1, @@ -457,30 +481,30 @@ class MockRsaUtils extends _i1.Mock implements _i8.RsaUtils { ) as String); @override - _i4.RSAPublicKey deserializeRSAPublicKeyPKCS8(String? keyStr) => + _i5.RSAPublicKey deserializeRSAPublicKeyPKCS8(String? keyStr) => (super.noSuchMethod( Invocation.method( #deserializeRSAPublicKeyPKCS8, [keyStr], ), - returnValue: _FakeRSAPublicKey_2( + returnValue: _FakeRSAPublicKey_3( this, Invocation.method( #deserializeRSAPublicKeyPKCS8, [keyStr], ), ), - returnValueForMissingStub: _FakeRSAPublicKey_2( + returnValueForMissingStub: _FakeRSAPublicKey_3( this, Invocation.method( #deserializeRSAPublicKeyPKCS8, [keyStr], ), ), - ) as _i4.RSAPublicKey); + ) as _i5.RSAPublicKey); @override - String serializeRSAPublicKeyPKCS8(_i4.RSAPublicKey? key) => + String serializeRSAPublicKeyPKCS8(_i5.RSAPublicKey? key) => (super.noSuchMethod( Invocation.method( #serializeRSAPublicKeyPKCS8, @@ -503,7 +527,7 @@ class MockRsaUtils extends _i1.Mock implements _i8.RsaUtils { ) as String); @override - String serializeRSAPrivateKeyPKCS1(_i4.RSAPrivateKey? key) => + String serializeRSAPrivateKeyPKCS1(_i5.RSAPrivateKey? key) => (super.noSuchMethod( Invocation.method( #serializeRSAPrivateKeyPKCS1, @@ -526,31 +550,31 @@ class MockRsaUtils extends _i1.Mock implements _i8.RsaUtils { ) as String); @override - _i4.RSAPrivateKey deserializeRSAPrivateKeyPKCS1(String? keyStr) => + _i5.RSAPrivateKey deserializeRSAPrivateKeyPKCS1(String? keyStr) => (super.noSuchMethod( Invocation.method( #deserializeRSAPrivateKeyPKCS1, [keyStr], ), - returnValue: _FakeRSAPrivateKey_3( + returnValue: _FakeRSAPrivateKey_4( this, Invocation.method( #deserializeRSAPrivateKeyPKCS1, [keyStr], ), ), - returnValueForMissingStub: _FakeRSAPrivateKey_3( + returnValueForMissingStub: _FakeRSAPrivateKey_4( this, Invocation.method( #deserializeRSAPrivateKeyPKCS1, [keyStr], ), ), - ) as _i4.RSAPrivateKey); + ) as _i5.RSAPrivateKey); @override bool verifyRSASignature( - _i4.RSAPublicKey? publicKey, + _i5.RSAPublicKey? publicKey, _i17.Uint8List? signedMessage, _i17.Uint8List? signature, ) => @@ -568,7 +592,7 @@ class MockRsaUtils extends _i1.Mock implements _i8.RsaUtils { ) as bool); @override - _i11.Future trySignWithToken( + _i12.Future trySignWithToken( _i18.PushToken? token, String? message, ) => @@ -580,43 +604,43 @@ class MockRsaUtils extends _i1.Mock implements _i8.RsaUtils { message, ], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); @override - _i11.Future<_i4.AsymmetricKeyPair<_i4.RSAPublicKey, _i4.RSAPrivateKey>> + _i12.Future<_i5.AsymmetricKeyPair<_i5.RSAPublicKey, _i5.RSAPrivateKey>> generateRSAKeyPair() => (super.noSuchMethod( Invocation.method( #generateRSAKeyPair, [], ), - returnValue: _i11.Future< - _i4.AsymmetricKeyPair<_i4.RSAPublicKey, - _i4.RSAPrivateKey>>.value( - _FakeAsymmetricKeyPair_4<_i4.RSAPublicKey, _i4.RSAPrivateKey>( + returnValue: _i12.Future< + _i5.AsymmetricKeyPair<_i5.RSAPublicKey, + _i5.RSAPrivateKey>>.value( + _FakeAsymmetricKeyPair_5<_i5.RSAPublicKey, _i5.RSAPrivateKey>( this, Invocation.method( #generateRSAKeyPair, [], ), )), - returnValueForMissingStub: _i11.Future< - _i4.AsymmetricKeyPair<_i4.RSAPublicKey, - _i4.RSAPrivateKey>>.value( - _FakeAsymmetricKeyPair_4<_i4.RSAPublicKey, _i4.RSAPrivateKey>( + returnValueForMissingStub: _i12.Future< + _i5.AsymmetricKeyPair<_i5.RSAPublicKey, + _i5.RSAPrivateKey>>.value( + _FakeAsymmetricKeyPair_5<_i5.RSAPublicKey, _i5.RSAPrivateKey>( this, Invocation.method( #generateRSAKeyPair, [], ), )), - ) as _i11.Future< - _i4.AsymmetricKeyPair<_i4.RSAPublicKey, _i4.RSAPrivateKey>>); + ) as _i12.Future< + _i5.AsymmetricKeyPair<_i5.RSAPublicKey, _i5.RSAPrivateKey>>); @override String createBase32Signature( - _i4.RSAPrivateKey? privateKey, + _i5.RSAPrivateKey? privateKey, _i17.Uint8List? dataToSign, ) => (super.noSuchMethod( @@ -651,7 +675,7 @@ class MockRsaUtils extends _i1.Mock implements _i8.RsaUtils { @override _i17.Uint8List createRSASignature( - _i4.RSAPrivateKey? privateKey, + _i5.RSAPrivateKey? privateKey, _i17.Uint8List? dataToSign, ) => (super.noSuchMethod( @@ -670,11 +694,11 @@ class MockRsaUtils extends _i1.Mock implements _i8.RsaUtils { /// A class which mocks [FirebaseUtils]. /// /// See the documentation for Mockito's code generation for more information. -class MockFirebaseUtils extends _i1.Mock implements _i6.FirebaseUtils { +class MockFirebaseUtils extends _i1.Mock implements _i7.FirebaseUtils { @override - _i11.Future initFirebase({ - required _i11.Future Function(_i19.RemoteMessage)? foregroundHandler, - required _i11.Future Function(_i19.RemoteMessage)? backgroundHandler, + _i12.Future initFirebase({ + required _i12.Future Function(_i19.RemoteMessage)? foregroundHandler, + required _i12.Future Function(_i19.RemoteMessage)? backgroundHandler, required dynamic Function(String?)? updateFirebaseToken, }) => (super.noSuchMethod( @@ -687,69 +711,69 @@ class MockFirebaseUtils extends _i1.Mock implements _i6.FirebaseUtils { #updateFirebaseToken: updateFirebaseToken, }, ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); @override - _i11.Future getFBToken() => (super.noSuchMethod( + _i12.Future getFBToken() => (super.noSuchMethod( Invocation.method( #getFBToken, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); @override - _i11.Future deleteFirebaseToken() => (super.noSuchMethod( + _i12.Future deleteFirebaseToken() => (super.noSuchMethod( Invocation.method( #deleteFirebaseToken, [], ), - returnValue: _i11.Future.value(false), - returnValueForMissingStub: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i12.Future.value(false), + returnValueForMissingStub: _i12.Future.value(false), + ) as _i12.Future); @override - _i11.Future setCurrentFirebaseToken(String? str) => (super.noSuchMethod( + _i12.Future setCurrentFirebaseToken(String? str) => (super.noSuchMethod( Invocation.method( #setCurrentFirebaseToken, [str], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); @override - _i11.Future getCurrentFirebaseToken() => (super.noSuchMethod( + _i12.Future getCurrentFirebaseToken() => (super.noSuchMethod( Invocation.method( #getCurrentFirebaseToken, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); @override - _i11.Future setNewFirebaseToken(String? str) => (super.noSuchMethod( + _i12.Future setNewFirebaseToken(String? str) => (super.noSuchMethod( Invocation.method( #setNewFirebaseToken, [str], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); @override - _i11.Future getNewFirebaseToken() => (super.noSuchMethod( + _i12.Future getNewFirebaseToken() => (super.noSuchMethod( Invocation.method( #getNewFirebaseToken, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); } /// A class which mocks [IntroductionRepository]. @@ -758,26 +782,26 @@ class MockFirebaseUtils extends _i1.Mock implements _i6.FirebaseUtils { class MockIntroductionRepository extends _i1.Mock implements _i20.IntroductionRepository { @override - _i11.Future saveCompletedIntroductions( - _i5.IntroductionState? introductions) => + _i12.Future saveCompletedIntroductions( + _i6.IntroductionState? introductions) => (super.noSuchMethod( Invocation.method( #saveCompletedIntroductions, [introductions], ), - returnValue: _i11.Future.value(false), - returnValueForMissingStub: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i12.Future.value(false), + returnValueForMissingStub: _i12.Future.value(false), + ) as _i12.Future); @override - _i11.Future<_i5.IntroductionState> loadCompletedIntroductions() => + _i12.Future<_i6.IntroductionState> loadCompletedIntroductions() => (super.noSuchMethod( Invocation.method( #loadCompletedIntroductions, [], ), returnValue: - _i11.Future<_i5.IntroductionState>.value(_FakeIntroductionState_5( + _i12.Future<_i6.IntroductionState>.value(_FakeIntroductionState_6( this, Invocation.method( #loadCompletedIntroductions, @@ -785,14 +809,14 @@ class MockIntroductionRepository extends _i1.Mock ), )), returnValueForMissingStub: - _i11.Future<_i5.IntroductionState>.value(_FakeIntroductionState_5( + _i12.Future<_i6.IntroductionState>.value(_FakeIntroductionState_6( this, Invocation.method( #loadCompletedIntroductions, [], ), )), - ) as _i11.Future<_i5.IntroductionState>); + ) as _i12.Future<_i6.IntroductionState>); } /// A class which mocks [PushProvider]. @@ -816,43 +840,43 @@ class MockPushProvider extends _i1.Mock implements _i21.PushProvider { ); @override - _i6.FirebaseUtils get firebaseUtils => (super.noSuchMethod( + _i7.FirebaseUtils get firebaseUtils => (super.noSuchMethod( Invocation.getter(#firebaseUtils), - returnValue: _FakeFirebaseUtils_6( + returnValue: _FakeFirebaseUtils_7( this, Invocation.getter(#firebaseUtils), ), - returnValueForMissingStub: _FakeFirebaseUtils_6( + returnValueForMissingStub: _FakeFirebaseUtils_7( this, Invocation.getter(#firebaseUtils), ), - ) as _i6.FirebaseUtils); + ) as _i7.FirebaseUtils); @override - _i7.PrivacyideaIOClient get ioClient => (super.noSuchMethod( + _i8.PrivacyideaIOClient get ioClient => (super.noSuchMethod( Invocation.getter(#ioClient), - returnValue: _FakePrivacyideaIOClient_7( + returnValue: _FakePrivacyideaIOClient_8( this, Invocation.getter(#ioClient), ), - returnValueForMissingStub: _FakePrivacyideaIOClient_7( + returnValueForMissingStub: _FakePrivacyideaIOClient_8( this, Invocation.getter(#ioClient), ), - ) as _i7.PrivacyideaIOClient); + ) as _i8.PrivacyideaIOClient); @override - _i8.RsaUtils get rsaUtils => (super.noSuchMethod( + _i9.RsaUtils get rsaUtils => (super.noSuchMethod( Invocation.getter(#rsaUtils), - returnValue: _FakeRsaUtils_8( + returnValue: _FakeRsaUtils_9( this, Invocation.getter(#rsaUtils), ), - returnValueForMissingStub: _FakeRsaUtils_8( + returnValueForMissingStub: _FakeRsaUtils_9( this, Invocation.getter(#rsaUtils), ), - ) as _i8.RsaUtils); + ) as _i9.RsaUtils); @override void setPollingEnabled(bool? enablePolling) => super.noSuchMethod( @@ -864,19 +888,19 @@ class MockPushProvider extends _i1.Mock implements _i21.PushProvider { ); @override - _i11.Future pollForChallenges({required bool? isManually}) => + _i12.Future pollForChallenges({required bool? isManually}) => (super.noSuchMethod( Invocation.method( #pollForChallenges, [], {#isManually: isManually}, ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); @override - _i11.Future pollForChallenge( + _i12.Future pollForChallenge( _i18.PushToken? token, { bool? isManually = true, }) => @@ -886,12 +910,12 @@ class MockPushProvider extends _i1.Mock implements _i21.PushProvider { [token], {#isManually: isManually}, ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); @override - _i11.Future< + _i12.Future< (List<_i18.PushToken>, List<_i18.PushToken>)?> updateFirebaseToken( [String? firebaseToken]) => (super.noSuchMethod( @@ -900,10 +924,10 @@ class MockPushProvider extends _i1.Mock implements _i21.PushProvider { [firebaseToken], ), returnValue: - _i11.Future<(List<_i18.PushToken>, List<_i18.PushToken>)?>.value(), + _i12.Future<(List<_i18.PushToken>, List<_i18.PushToken>)?>.value(), returnValueForMissingStub: - _i11.Future<(List<_i18.PushToken>, List<_i18.PushToken>)?>.value(), - ) as _i11.Future<(List<_i18.PushToken>, List<_i18.PushToken>)?>); + _i12.Future<(List<_i18.PushToken>, List<_i18.PushToken>)?>.value(), + ) as _i12.Future<(List<_i18.PushToken>, List<_i18.PushToken>)?>); @override void unsubscribe(void Function(_i22.PushRequest)? newRequest) => @@ -932,13 +956,13 @@ class MockPushProvider extends _i1.Mock implements _i21.PushProvider { class MockPushRequestRepository extends _i1.Mock implements _i23.PushRequestRepository { @override - _i11.Future<_i9.PushRequestState> loadState() => (super.noSuchMethod( + _i12.Future<_i10.PushRequestState> loadState() => (super.noSuchMethod( Invocation.method( #loadState, [], ), returnValue: - _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( + _i12.Future<_i10.PushRequestState>.value(_FakePushRequestState_10( this, Invocation.method( #loadState, @@ -946,40 +970,40 @@ class MockPushRequestRepository extends _i1.Mock ), )), returnValueForMissingStub: - _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( + _i12.Future<_i10.PushRequestState>.value(_FakePushRequestState_10( this, Invocation.method( #loadState, [], ), )), - ) as _i11.Future<_i9.PushRequestState>); + ) as _i12.Future<_i10.PushRequestState>); @override - _i11.Future saveState(_i9.PushRequestState? pushRequestState) => + _i12.Future saveState(_i10.PushRequestState? pushRequestState) => (super.noSuchMethod( Invocation.method( #saveState, [pushRequestState], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); @override - _i11.Future clearState() => (super.noSuchMethod( + _i12.Future clearState() => (super.noSuchMethod( Invocation.method( #clearState, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); @override - _i11.Future<_i9.PushRequestState> addRequest( + _i12.Future<_i10.PushRequestState> addRequest( _i22.PushRequest? pushRequest, { - _i9.PushRequestState? state, + _i10.PushRequestState? state, }) => (super.noSuchMethod( Invocation.method( @@ -988,7 +1012,7 @@ class MockPushRequestRepository extends _i1.Mock {#state: state}, ), returnValue: - _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( + _i12.Future<_i10.PushRequestState>.value(_FakePushRequestState_10( this, Invocation.method( #addRequest, @@ -997,7 +1021,7 @@ class MockPushRequestRepository extends _i1.Mock ), )), returnValueForMissingStub: - _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( + _i12.Future<_i10.PushRequestState>.value(_FakePushRequestState_10( this, Invocation.method( #addRequest, @@ -1005,12 +1029,12 @@ class MockPushRequestRepository extends _i1.Mock {#state: state}, ), )), - ) as _i11.Future<_i9.PushRequestState>); + ) as _i12.Future<_i10.PushRequestState>); @override - _i11.Future<_i9.PushRequestState> removeRequest( + _i12.Future<_i10.PushRequestState> removeRequest( _i22.PushRequest? pushRequest, { - _i9.PushRequestState? state, + _i10.PushRequestState? state, }) => (super.noSuchMethod( Invocation.method( @@ -1019,7 +1043,7 @@ class MockPushRequestRepository extends _i1.Mock {#state: state}, ), returnValue: - _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( + _i12.Future<_i10.PushRequestState>.value(_FakePushRequestState_10( this, Invocation.method( #removeRequest, @@ -1028,7 +1052,7 @@ class MockPushRequestRepository extends _i1.Mock ), )), returnValueForMissingStub: - _i11.Future<_i9.PushRequestState>.value(_FakePushRequestState_9( + _i12.Future<_i10.PushRequestState>.value(_FakePushRequestState_10( this, Invocation.method( #removeRequest, @@ -1036,5 +1060,5 @@ class MockPushRequestRepository extends _i1.Mock {#state: state}, ), )), - ) as _i11.Future<_i9.PushRequestState>); + ) as _i12.Future<_i10.PushRequestState>); } diff --git a/test/unit_test/state_notifiers/sortable_notifier_test.dart b/test/unit_test/state_notifiers/sortable_notifier_test.dart index 6ce698b82..3460f4980 100644 --- a/test/unit_test/state_notifiers/sortable_notifier_test.dart +++ b/test/unit_test/state_notifiers/sortable_notifier_test.dart @@ -3,12 +3,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart'; import 'package:privacyidea_authenticator/model/token_folder.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/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; @@ -27,13 +27,13 @@ void _testSortableNotifier() { when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); final MockTokenFolderRepository mockTokenFolderRepository = MockTokenFolderRepository(); final MockTokenRepository mockTokenRepository = MockTokenRepository(); - List tokenFolderState = [ - const TokenFolder(label: 'Folder 1', folderId: 1, sortIndex: null), - const TokenFolder(label: 'Folder 2', folderId: 2, sortIndex: 2), - ]; - when(mockTokenFolderRepository.loadFolders()).thenAnswer((_) async => tokenFolderState); - when(mockTokenFolderRepository.saveReplaceList(any)).thenAnswer((newState) async { - tokenFolderState = newState.positionalArguments.first as List; + TokenFolderState tokenFolderState = const TokenFolderState(folders: [ + TokenFolder(label: 'Folder 1', folderId: 1, sortIndex: null), + TokenFolder(label: 'Folder 2', folderId: 2, sortIndex: 2), + ]); + when(mockTokenFolderRepository.loadState()).thenAnswer((_) async => tokenFolderState); + when(mockTokenFolderRepository.saveState(any)).thenAnswer((newState) async { + tokenFolderState = newState.positionalArguments.first as TokenFolderState; return true; }); List tokenState = [ @@ -57,7 +57,7 @@ void _testSortableNotifier() { final container = ProviderContainer(overrides: [ settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo)), - tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), + tokenFolderProvider.overrideWith(() => TokenFolderNotifier(repoOverride: mockTokenFolderRepository)), tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), ]); diff --git a/test/unit_test/state_notifiers/token_folder_notifier_test.dart b/test/unit_test/state_notifiers/token_folder_notifier_test.dart index df732e3d3..3e09f7051 100644 --- a/test/unit_test/state_notifiers/token_folder_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_folder_notifier_test.dart @@ -3,7 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart'; import 'package:privacyidea_authenticator/model/token_folder.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; import '../../tests_app_wrapper.mocks.dart'; @@ -16,77 +16,69 @@ void _testTokenFolderNotifier() { test('addFolder', () async { final mockRepo = MockTokenFolderRepository(); final container = ProviderContainer(); - const before = []; - const after = [TokenFolder(label: 'test', folderId: 1, isExpanded: true, isLocked: false, sortIndex: null)]; - when(mockRepo.loadFolders()).thenAnswer((_) async => before); - when(mockRepo.saveReplaceList(after)).thenAnswer((_) async => true); - final testProvider = StateNotifierProvider((ref) => TokenFolderNotifier( - repository: mockRepo, - )); + const TokenFolderState before = TokenFolderState(folders: []); + const TokenFolderState after = TokenFolderState(folders: [TokenFolder(label: 'test', folderId: 1, isExpanded: true, isLocked: false, sortIndex: null)]); + when(mockRepo.loadState()).thenAnswer((_) async => before); + when(mockRepo.saveState(after)).thenAnswer((_) async => true); + final testProvider = tokenFolderNotifierProviderOf(repo: mockRepo); final notifier = container.read(testProvider.notifier); await notifier.initState; await notifier.addNewFolder('test'); final state = container.read(testProvider); expect(state.folders, after); - verify(mockRepo.saveReplaceList(after)).called(1); + verify(mockRepo.saveState(after)).called(1); }); test('removeFolder', () async { final mockRepo = MockTokenFolderRepository(); final container = ProviderContainer(); - const before = [TokenFolder(label: 'test', folderId: 1, isExpanded: true, isLocked: false, sortIndex: null)]; - const after = []; - when(mockRepo.loadFolders()).thenAnswer((_) async => before); - when(mockRepo.saveReplaceList(after)).thenAnswer((_) async => true); - final testProvider = StateNotifierProvider((ref) => TokenFolderNotifier( - repository: mockRepo, - )); + const before = TokenFolderState(folders: [TokenFolder(label: 'test', folderId: 1, isExpanded: true, isLocked: false, sortIndex: null)]); + const after = TokenFolderState(folders: []); + when(mockRepo.loadState()).thenAnswer((_) async => before); + when(mockRepo.saveState(after)).thenAnswer((_) async => true); + final testProvider = tokenFolderNotifierProviderOf(repo: mockRepo); final notifier = container.read(testProvider.notifier); await notifier.initState; await notifier.removeFolder(const TokenFolder(label: 'test', folderId: 1)); final state = container.read(testProvider); expect(state.folders, after); - verify(mockRepo.saveReplaceList(after)).called(1); + verify(mockRepo.saveState(after)).called(1); }); test('updateFolder', () async { final mockRepo = MockTokenFolderRepository(); final container = ProviderContainer(); - const before = [TokenFolder(label: 'test', folderId: 1, isExpanded: true, isLocked: false, sortIndex: null)]; - const after = [TokenFolder(label: 'testUpdated', folderId: 1, isExpanded: true, isLocked: false, sortIndex: null)]; - when(mockRepo.loadFolders()).thenAnswer((_) async => before); - when(mockRepo.saveReplaceList(after)).thenAnswer((_) async => true); - final testProvider = StateNotifierProvider((ref) => TokenFolderNotifier( - repository: mockRepo, - )); + const before = TokenFolderState(folders: [TokenFolder(label: 'test', folderId: 1, isExpanded: true, isLocked: false, sortIndex: null)]); + const after = TokenFolderState(folders: [TokenFolder(label: 'testUpdated', folderId: 1, isExpanded: true, isLocked: false, sortIndex: null)]); + when(mockRepo.loadState()).thenAnswer((_) async => before); + when(mockRepo.saveState(after)).thenAnswer((_) async => true); + final testProvider = tokenFolderNotifierProviderOf(repo: mockRepo); final notifier = container.read(testProvider.notifier); await notifier.initState; - await notifier.updateFolder(before.first, (p0) => after.first); + await notifier.updateFolder(before.folders.first, (p0) => after.folders.first); final state = container.read(testProvider); expect(state.folders, after); - verify(mockRepo.saveReplaceList(after)).called(1); + verify(mockRepo.saveState(after)).called(1); }); test('updateFolders', () async { final mockRepo = MockTokenFolderRepository(); final container = ProviderContainer(); - const before = [ + const before = TokenFolderState(folders: [ TokenFolder(label: 'test1', folderId: 1, isExpanded: true, isLocked: false, sortIndex: null), TokenFolder(label: 'test2', folderId: 2, isExpanded: true, isLocked: false, sortIndex: null), - ]; - const after = [ + ]); + const after = TokenFolderState(folders: [ TokenFolder(label: 'test1Updated', folderId: 1, isExpanded: true, isLocked: false, sortIndex: null), TokenFolder(label: 'test2Updated', folderId: 2, isExpanded: true, isLocked: false, sortIndex: null), - ]; - when(mockRepo.loadFolders()).thenAnswer((_) async => before); - when(mockRepo.saveReplaceList(after)).thenAnswer((_) async => true); - final testProvider = StateNotifierProvider((ref) => TokenFolderNotifier( - repository: mockRepo, - )); + ]); + when(mockRepo.loadState()).thenAnswer((_) async => before); + when(mockRepo.saveState(after)).thenAnswer((_) async => true); + final testProvider = tokenFolderNotifierProviderOf(repo: mockRepo); final notifier = container.read(testProvider.notifier); await notifier.initState; - await notifier.addOrReplaceFolders(after); + await notifier.addOrReplaceFolders(after.folders); final state = container.read(testProvider); expect(state.folders, after); - verify(mockRepo.saveReplaceList(after)).called(1); + verify(mockRepo.saveState(after)).called(1); }); }); } From 16c8e4efd85bcb986dc048fceae194495a3a0ce4 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:09:03 +0200 Subject: [PATCH 025/285] refactoring --- integration_test/add_tokens_test.dart | 4 +- integration_test/copy_to_clipboard_test.dart | 4 +- integration_test/rename_and_delete_test.dart | 4 +- integration_test/two_step_rollout_test.dart | 4 +- integration_test/views_test.dart | 4 +- .../riverpod/buildless_listener.dart | 41 + .../token_state_listener.dart | 12 +- .../enums/introduction_extension.dart | 2 +- lib/model/push_request.dart | 2 +- lib/model/riverpod_states/token_state.dart | 9 +- .../home_widget_navigate_processor.dart | 4 +- lib/repo/secure_token_repository.dart | 2 +- lib/utils/home_widget_utils.dart | 2 +- lib/utils/push_provider.dart | 4 +- .../sortable_notifier.dart | 4 +- .../token_container_notifier.dart | 2 +- .../token_folder_notifier.dart} | 34 +- .../token_folder_notifier.g.dart} | 2 +- .../token_notifier.dart | 1022 +++++++++ .../token_notifier.g.dart | 237 +++ .../token_provider.dart | 44 - .../state_providers/home_widget_provider.dart | 2 +- .../connectivity_provider.dart | 2 +- .../home_widget_token_state_listener.dart | 2 +- .../token_container_token_state_listener.dart | 2 +- .../state_notifiers/token_notifier.dart | 1884 ++++++++--------- lib/utils/utils.dart | 4 +- .../add_token_manually_view.dart | 2 +- .../import_tokens_view.dart | 2 +- .../pages/import_plain_tokens_page.dart | 2 +- .../link_home_widget_view.dart | 4 +- .../connectivity_listener.dart | 2 +- .../add_token_folder_dialog.dart | 2 +- .../delete_token_folder_action.dart | 4 +- .../lock_token_folder_action.dart | 2 +- .../rename_token_folder_action.dart | 2 +- .../token_folder_expandable.dart | 4 +- .../qr_scanner_button.dart | 2 +- .../main_view_tokens_list_filtered.dart | 4 +- .../day_password_token_widget_tile.dart | 2 +- .../default_delete_action.dart | 2 +- .../default_edit_action.dart | 2 +- .../default_edit_action_dialog.dart | 2 +- .../default_lock_action.dart | 2 +- .../hotp_token_widget_tile.dart | 2 +- .../actions/edit_push_token_action.dart | 2 +- .../rollout_failed_widget.dart | 2 +- .../totp_token_widget_tile.dart | 2 +- .../widgets/push_tokens_view_list.dart | 2 +- .../dialogs/select_tokens_dialog.dart | 2 +- .../settings_group_push_token.dart | 2 +- lib/views/settings_view/settings_view.dart | 2 +- .../update_firebase_token_dialog.dart | 2 +- lib/views/splash_screen/splash_screen.dart | 2 +- lib/widgets/app_wrapper.dart | 11 +- lib/widgets/app_wrappers/state_observer.dart | 6 + .../dialog_widgets/push_request_dialog.dart | 2 +- lib/widgets/hideable_widget_.dart | 2 +- .../sortable_notifier_test.dart | 7 +- .../token_folder_notifier_test.dart | 2 +- .../state_notifiers/token_notifier_test.dart | 93 +- 61 files changed, 2388 insertions(+), 1136 deletions(-) create mode 100644 lib/interfaces/riverpod/buildless_listener.dart rename lib/utils/riverpod/riverpod_providers/{state_notifier_providers/token_folder_provider.dart => generated_providers/token_folder_notifier.dart} (87%) rename lib/utils/riverpod/riverpod_providers/{state_notifier_providers/token_folder_provider.g.dart => generated_providers/token_folder_notifier.g.dart} (99%) create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart create mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.g.dart delete mode 100644 lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart diff --git a/integration_test/add_tokens_test.dart b/integration_test/add_tokens_test.dart index c58d59f2b..33164f6ed 100644 --- a/integration_test/add_tokens_test.dart +++ b/integration_test/add_tokens_test.dart @@ -17,8 +17,8 @@ import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_n import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.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'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/app_bar_item.dart'; diff --git a/integration_test/copy_to_clipboard_test.dart b/integration_test/copy_to_clipboard_test.dart index 707538df0..d065eef4c 100644 --- a/integration_test/copy_to_clipboard_test.dart +++ b/integration_test/copy_to_clipboard_test.dart @@ -15,8 +15,8 @@ import 'package:privacyidea_authenticator/utils/customization/application_custom import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../test/tests_app_wrapper.dart'; import '../test/tests_app_wrapper.mocks.dart'; diff --git a/integration_test/rename_and_delete_test.dart b/integration_test/rename_and_delete_test.dart index ed118ca14..c935916e4 100644 --- a/integration_test/rename_and_delete_test.dart +++ b/integration_test/rename_and_delete_test.dart @@ -16,8 +16,8 @@ import 'package:privacyidea_authenticator/utils/customization/application_custom import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_delete_action.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart'; diff --git a/integration_test/two_step_rollout_test.dart b/integration_test/two_step_rollout_test.dart index 6b690c581..7e2bf0142 100644 --- a/integration_test/two_step_rollout_test.dart +++ b/integration_test/two_step_rollout_test.dart @@ -15,8 +15,8 @@ import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart'; diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index 56d7f65f7..57668f32a 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -21,8 +21,8 @@ import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/push_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/views/settings_view/settings_view_widgets/settings_groups.dart'; diff --git a/lib/interfaces/riverpod/buildless_listener.dart b/lib/interfaces/riverpod/buildless_listener.dart new file mode 100644 index 000000000..cd67bdc4c --- /dev/null +++ b/lib/interfaces/riverpod/buildless_listener.dart @@ -0,0 +1,41 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ + +// ignore: invalid_use_of_internal_member +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../utils/logger.dart'; + +// ignore: invalid_use_of_internal_member +abstract class BuildlessListener, S> { + final String listenerName; + // ignore: invalid_use_of_internal_member + final NotifierProviderImpl provider; + final void Function(S? previous, S next) onNewState; + const BuildlessListener({required this.provider, required this.onNewState, required this.listenerName}); + void buildListen(WidgetRef ref) { + Logger.debug('("$listenerName") listening to provider ("$provider")', name: 'StateNotifierProviderListener#buildListen'); + ref.listen(provider, (previous, next) { + WidgetsBinding.instance.addPostFrameCallback((_) => onNewState(previous, next)); + }); + } +} diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart index 90d09040b..082b155e8 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart @@ -17,16 +17,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/riverpod_states/token_state.dart'; -import '../../../../utils/riverpod/state_notifiers/token_notifier.dart'; -import '../state_notifier_provider_listener.dart'; -abstract class TokenStateListener extends StateNotifierProviderListener { +import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../buildless_listener.dart'; + +abstract class TokenStateListener extends BuildlessListener { const TokenStateListener({ - required StateNotifierProvider tokenProvider, + required super.provider, required super.onNewState, required super.listenerName, - }) : super(provider: tokenProvider); + }); } diff --git a/lib/model/extensions/enums/introduction_extension.dart b/lib/model/extensions/enums/introduction_extension.dart index b2741863b..a9871adca 100644 --- a/lib/model/extensions/enums/introduction_extension.dart +++ b/lib/model/extensions/enums/introduction_extension.dart @@ -3,7 +3,7 @@ import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.d import '../../../l10n/app_localizations.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../enums/introduction.dart'; import '../../riverpod_states/introduction_state.dart'; diff --git a/lib/model/push_request.dart b/lib/model/push_request.dart index 89ccccc38..8291f8412 100644 --- a/lib/model/push_request.dart +++ b/lib/model/push_request.dart @@ -25,7 +25,7 @@ import 'package:json_annotation/json_annotation.dart'; import '../utils/globals.dart'; import '../utils/identifiers.dart'; import '../utils/logger.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../utils/rsa_utils.dart'; import 'tokens/push_token.dart'; diff --git a/lib/model/riverpod_states/token_state.dart b/lib/model/riverpod_states/token_state.dart index c5f656cfc..f03a9dd41 100644 --- a/lib/model/riverpod_states/token_state.dart +++ b/lib/model/riverpod_states/token_state.dart @@ -47,13 +47,12 @@ class TokenState { List get maybePiTokens => tokens.maybePiTokens; - TokenState({ - required List tokens, + const TokenState({ + required this.tokens, List? lastlyUpdatedTokens, List? lastlyDeletedTokens, - }) : tokens = List.from(tokens), - lastlyUpdatedTokens = List.from(lastlyUpdatedTokens ?? tokens), - lastlyDeletedTokens = List.from(lastlyDeletedTokens ?? []); + }) : lastlyUpdatedTokens = lastlyUpdatedTokens ?? tokens, + lastlyDeletedTokens = lastlyDeletedTokens ?? const []; List get tokensNotInContainer { final tokensNotInContainer = tokens.maybePiTokens.where((token) => token.containerSerial != null).toList(); diff --git a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart index b872e6ff0..33ef20c33 100644 --- a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart +++ b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart @@ -22,8 +22,8 @@ import 'package:flutter/material.dart'; import '../../../utils/globals.dart'; import '../../../utils/home_widget_utils.dart'; import '../../../utils/logger.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../views/link_home_widget_view/link_home_widget_view.dart'; import 'navigation_scheme_processor_interface.dart'; diff --git a/lib/repo/secure_token_repository.dart b/lib/repo/secure_token_repository.dart index 4bd660cdb..bf256bfcc 100644 --- a/lib/repo/secure_token_repository.dart +++ b/lib/repo/secure_token_repository.dart @@ -34,7 +34,7 @@ import '../model/tokens/token.dart'; import '../utils/globals.dart'; import '../utils/identifiers.dart'; import '../utils/logger.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../utils/view_utils.dart'; import '../views/settings_view/settings_view_widgets/send_error_dialog.dart'; import '../widgets/dialog_widgets/default_dialog.dart'; diff --git a/lib/utils/home_widget_utils.dart b/lib/utils/home_widget_utils.dart index bf738c4c4..5a4a330b0 100644 --- a/lib/utils/home_widget_utils.dart +++ b/lib/utils/home_widget_utils.dart @@ -50,7 +50,7 @@ import '../widgets/home_widgets/home_widget_otp.dart'; import '../widgets/home_widgets/home_widget_unlinked.dart'; import 'globals.dart'; import 'logger.dart'; -import 'riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import 'riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import 'riverpod/riverpod_providers/state_providers/home_widget_provider.dart'; const appGroupId = 'group.authenticator_home_widget_group'; diff --git a/lib/utils/push_provider.dart b/lib/utils/push_provider.dart index e94ba17d3..773084b6e 100644 --- a/lib/utils/push_provider.dart +++ b/lib/utils/push_provider.dart @@ -38,7 +38,7 @@ import 'globals.dart'; import 'logger.dart'; import 'privacyidea_io_client.dart'; import 'riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import 'riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import 'riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import 'riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import 'rsa_utils.dart'; import 'utils.dart'; @@ -301,8 +301,6 @@ class PushProvider { final connectivityResult = await (Connectivity().checkConnectivity()); 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/riverpod_providers/generated_providers/sortable_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart index a0f9ac598..eda7701e5 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart @@ -25,8 +25,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../../model/mixins/sortable_mixin.dart'; import '../../../../model/token_folder.dart'; import '../../../../model/tokens/token.dart'; -import '../state_notifier_providers/token_folder_provider.dart'; -import '../state_notifier_providers/token_provider.dart'; +import 'token_folder_notifier.dart'; +import '../state_notifier_providers/token_notifier.dart'; part 'sortable_notifier.g.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart index c44ad7b11..a60733c1a 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -29,7 +29,7 @@ import '../../../../repo/token_container_state_repositorys/hybrid_token_containe import '../../../../repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; import '../../../../repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart'; import '../../../../model/tokens/container_credentials.dart'; -import '../state_notifier_providers/token_provider.dart'; +import '../state_notifier_providers/token_notifier.dart'; part 'token_container_notifier.g.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart similarity index 87% rename from lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart index d4d251f88..6fe3d9866 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart @@ -26,7 +26,7 @@ import '../../../../model/riverpod_states/token_folder_state.dart'; import '../../../../model/token_folder.dart'; import '../../../logger.dart'; -part 'token_folder_provider.g.dart'; +part 'token_folder_notifier.g.dart'; final tokenFolderProvider = tokenFolderNotifierProviderOf(repo: PreferenceTokenFolderRepository()); @@ -75,38 +75,6 @@ class TokenFolderNotifier extends _$TokenFolderNotifier { return true; } - // Future _addOrReplaceFolder(TokenFolder folder) async { - // await _repoMutex.acquire(); - // final newState = (state).addOrReplaceFolder(folder); - // final success = await _repo.saveReplaceList(newState.folders); - // if (!success) { - // Logger.warning( - // 'Failed to add or replace folder', - // name: 'TokenFolderNotifier#_addOrReplaceFolder', - // ); - // return false; - // } - // // state = newState; - // _repoMutex.release(); - // return true; - // } - - // Future _addNewFolder(String name) async { - // await _repoMutex.acquire(); - // final newState = (state).addNewFolder(name); - // final success = await _repo.saveReplaceList(newState.folders); - // if (!success) { - // Logger.warning( - // 'Failed to add new folder', - // name: 'TokenFolderNotifier#_addNewFolder', - // ); - // return false; - // } - // // state = newState; - // _repoMutex.release(); - // return true; - // } - Future addNewFolder(String name) async { await _stateMutex.acquire(); final oldState = state; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.g.dart similarity index 99% rename from lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.g.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.g.dart index f736b212f..2f67c8c8a 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'token_folder_provider.dart'; +part of 'token_folder_notifier.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart new file mode 100644 index 000000000..533c97763 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart @@ -0,0 +1,1022 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:http/http.dart'; +import 'package:mutex/mutex.dart'; +import 'package:pointycastle/asymmetric/api.dart'; +import 'package:privacyidea_authenticator/interfaces/repo/token_repository.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 'package:privacyidea_authenticator/repo/secure_token_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../../l10n/app_localizations.dart'; +import '../../../../model/enums/push_token_rollout_state.dart'; +import '../../../../model/enums/token_import_type.dart'; +import '../../../../model/enums/token_origin_source_type.dart'; +import '../../../../model/processor_result.dart'; +import '../../../../model/riverpod_states/token_state.dart'; +import '../../../../model/token_container.dart'; +import '../../../../model/tokens/hotp_token.dart'; +import '../../../../model/tokens/otp_token.dart'; +import '../../../../model/tokens/push_token.dart'; +import '../../../../model/tokens/token.dart'; +import '../../../../processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart'; +import '../../../../views/import_tokens_view/pages/import_plain_tokens_page.dart'; +import '../../../firebase_utils.dart'; +import '../../../globals.dart'; +import '../../../identifiers.dart'; +import '../../../lock_auth.dart'; +import '../../../logger.dart'; +import '../../../privacyidea_io_client.dart'; +import '../../../rsa_utils.dart'; +import '../../../utils.dart'; +import '../../../view_utils.dart'; +import '../generated_providers/settings_notifier.dart'; +import '../state_providers/status_message_provider.dart'; + +part 'token_notifier.g.dart'; + +final tokenProvider = tokenNotifierProviderOf( + firebaseUtils: FirebaseUtils(), + ioClient: const PrivacyideaIOClient(), + rsaUtils: const RsaUtils(), + repo: const SecureTokenRepository(), +); + +// StateNotifierProvider( +// (ref) { +// Logger.info("New TokenNotifier created"); +// final newTokenNotifier = TokenNotifier(ref: ref); + +// ref.listen(deeplinkNotifierProvider, (previous, newLink) { +// newLink.whenData( +// (data) { +// Logger.info("Received new deeplink with data: $data", name: 'tokenProvider#deeplinkProvider'); +// newTokenNotifier.handleLink(data.uri); +// }, +// ); +// }); + +// return newTokenNotifier; +// }, +// name: 'tokenProvider', +// ); + +@Riverpod(keepAlive: true) +class TokenNotifier extends _$TokenNotifier { + static final Map _hidingTimers = {}; + late final Future initState; + // final StateNotifierProviderRef ref; + final _repoMutex = Mutex(); + final _stateMutex = Mutex(); + + TokenNotifier({ + TokenRepository? repoOverride, + RsaUtils? rsaUtilsOverride, + PrivacyideaIOClient? ioClientOverride, + FirebaseUtils? firebaseUtilsOverride, + }) : _repoOverride = repoOverride, + _rsaUtilsOverride = rsaUtilsOverride, + _ioClientOverride = ioClientOverride, + _firebaseUtilsOverride = firebaseUtilsOverride; + + @override + TokenRepository get repo => _repo; + late final TokenRepository _repo; + final TokenRepository? _repoOverride; + + @override + RsaUtils get rsaUtils => _rsaUtils; + late final RsaUtils _rsaUtils; + final RsaUtils? _rsaUtilsOverride; + + @override + PrivacyideaIOClient get ioClient => _ioClient; + late final PrivacyideaIOClient _ioClient; + final PrivacyideaIOClient? _ioClientOverride; + + @override + FirebaseUtils get firebaseUtils => _firebaseUtils; + late final FirebaseUtils _firebaseUtils; + final FirebaseUtils? _firebaseUtilsOverride; + + @override + TokenState build({ + required TokenRepository repo, + required RsaUtils rsaUtils, + required PrivacyideaIOClient ioClient, + required FirebaseUtils firebaseUtils, + }) { + _repo = _repoOverride ?? repo; + _rsaUtils = _rsaUtilsOverride ?? rsaUtils; + _ioClient = _ioClientOverride ?? ioClient; + _firebaseUtils = _firebaseUtilsOverride ?? firebaseUtils; + initState = _loadStateFromRepo().then((newState) => state = newState); + return const TokenState(tokens: [], lastlyUpdatedTokens: []); + } + // /* + // ///////////////////////////////////////////////////////////////////////////// + // /////////////////////// Repository and Token Handling /////////////////////// + // ///////////////////////////////////////////////////////////////////////////// + // /// Repository layer is always use loadingRepoMutex for the latest state + // */ + + Future _loadStateFromRepo() async { + await _repoMutex.acquire(); + final tokens = await _repo.loadTokens(); + final newState = TokenState(tokens: tokens, lastlyUpdatedTokens: tokens); + _repoMutex.release(); + return newState; + } + + /// Adds a token and returns true if successful, false if not. + Future _addOrReplaceToken(Token token) async { + await _repoMutex.acquire(); + final success = await _repo.saveOrReplaceToken(token); + if (!success) { + Logger.warning( + 'Saving token failed. Token: ${token.id}', + name: 'token_notifier.dart#_addOrReplaceToken', + ); + _repoMutex.release(); + return false; + } + state = state.addOrReplaceToken(token); + _repoMutex.release(); + return true; + } + + /// Adds a list of tokens and returns the tokens that could not be added or replaced. + Future> _addOrReplaceTokens(List tokens) async { + await _repoMutex.acquire(); + final failedTokens = await _repo.saveOrReplaceTokens(tokens); + if (failedTokens.isNotEmpty) { + Logger.warning( + 'Saving tokens failed. Failed Tokens: ${failedTokens.length}', + name: 'token_notifier.dart#_saveOrReplaceTokens', + ); + // Every token that is saved should not be in the failedTokens list + final savedTokens = tokens.where((element) => !failedTokens.contains(element)).toList(); + state = state.addOrReplaceTokens(savedTokens); + return failedTokens; + } + // [failedTokens] is empty, so every token was saved successfully and we dont need to filter the tokens + Logger.info('Saved ${tokens.length} Tokens to storage.', name: 'token_notifier.dart#_saveOrReplaceTokens'); + state = state.addOrReplaceTokens(tokens); + Logger.debug('New State: ${state.tokens.length} Tokens', name: 'token_notifier.dart#_saveOrReplaceTokens'); + _repoMutex.release(); + return []; + } + + /// Replaces a token if it exists and returns true if successful, false if not. + Future _replaceToken(Token token) async { + await _repoMutex.acquire(); + final (newState, replaced) = state.replaceToken(token); + if (!replaced) { + Logger.warning('Tried to replace a token that does not exist.', name: 'token_notifier.dart#_replaceToken'); + _repoMutex.release(); + return false; + } + final saved = await _repo.saveOrReplaceToken(token); + if (!saved) { + Logger.warning( + 'Saving token failed. Token: ${token.id}', + name: 'token_notifier.dart#_replaceToken', + ); + _repoMutex.release(); + return false; + } + state = newState; + _repoMutex.release(); + return true; + } + + /// Returns a list of tokens that could not be replaced + Future> _replaceTokens(List tokens) async { + await _repoMutex.acquire(); + final oldState = state; + final (newState, failedToReplace) = state.replaceTokens(tokens); + state = newState; + for (var e in failedToReplace) { + tokens.remove(e); + } + final failedToSave = await _repo.saveOrReplaceTokens(tokens); + if (failedToSave.isNotEmpty) { + Logger.warning( + 'Saving tokens failed. Failed Tokens: ${failedToSave.length}', + name: 'token_notifier.dart#_saveOrReplaceTokens', + ); + final recovered = oldState.tokens.whereType().where((oldToken) => failedToSave.contains(oldToken)).toList(); + state = state.addOrReplaceTokens(recovered); + _repoMutex.release(); + return failedToSave; + } + _repoMutex.release(); + return []; + } + + /// Removes a token and returns true if successful, false if not. + Future _removeToken(Token token) async { + await _repoMutex.acquire(); + state = state.withoutToken(token); + + final success = await _repo.deleteToken(token); + if (!success) { + Logger.warning( + 'Deleting token failed. Token: ${token.id}', + name: 'token_notifier.dart#_deleteTokensRepo', + ); + state = state.addOrReplaceToken(token); + _repoMutex.release(); + return false; + } + _repoMutex.release(); + _handlePushTokensIfExist(); + return true; + } + + /// Removes a list of tokens and returns the tokens that could not be removed. + Future> _removeTokens(List tokens) async { + Logger.info('Removing ${tokens.length} tokens.', name: 'token_notifier.dart#_removeTokens'); + await _repoMutex.acquire(); + final oldState = state; + state = state.withoutTokens(tokens); + + final failedTokens = await _repo.deleteTokens(tokens); + if (failedTokens.isNotEmpty) { + Logger.warning( + 'Deleting tokens failed. Failed Tokens: ${failedTokens.length}', + name: 'token_notifier.dart#_deleteTokensRepo', + ); + final recoveredTokens = oldState.tokens.where((oldToken) => failedTokens.contains(oldToken)).toList(); + state = state.addOrReplaceTokens(recoveredTokens); + _repoMutex.release(); + return failedTokens; + } + _repoMutex.release(); + _handlePushTokensIfExist(); + return []; + } + + /// Loads the tokens from the repository sets it as the new state and returns the new state. + Future _loadFromRepo() async { + await _repoMutex.acquire(); + TokenState newState; + try { + List tokens; + tokens = await _repo.loadTokens(); + newState = TokenState(tokens: tokens, lastlyUpdatedTokens: tokens); + state = newState; + } catch (e) { + Logger.error( + 'Loading tokens from storage failed.', + name: 'token_notifier.dart#_loadFromRepo', + error: e, + ); + _repoMutex.release(); + return state; + } + _repoMutex.release(); + _handlePushTokensIfExist(); + return newState; + } + + Future _saveStateToRepo(TokenState state) async { + await _repoMutex.acquire(); + try { + await _repo.saveOrReplaceTokens(state.tokens); + } catch (e) { + Logger.error( + 'Saving tokens to storage failed.', + name: 'token_notifier.dart#_saveStateToRepo', + error: e, + ); + _repoMutex.release(); + return false; + } + _repoMutex.release(); + return true; + } + + /* + ////////////////////////////////////////////////////////////////////////////// + ///////////////////////// Update Token Methods /////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + /// Updating layer is always use updatingTokensMutex for the latest state + */ + + /// Updates a token and returns the updated token if successful, the old token if not and null if the token does not exist. + Future _updateToken(T token, T Function(T) updater) async { + await _stateMutex.acquire(); + await _repoMutex.acquire(); + _repoMutex.release(); + final current = state.currentOf(token); + if (current == null) { + Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#updateToken'); + _stateMutex.release(); + return null; + } + final updated = updater(current); + final replaced = await _replaceToken(updated); + _stateMutex.release(); + return replaced ? updated : current; + } + + /// Updates a list of tokens and returns the updated tokens if successful, the old tokens if not and an empty list if the tokens does not exist. + Future> _updateTokens(List tokens, T Function(T) updater) async { + if (tokens.isEmpty) return []; + await _stateMutex.acquire(); + final oldState = state; + + List updatedTokens = []; + for (final token in tokens) { + final current = state.currentOf(token) ?? token; + updatedTokens.add(updater(current)); + } + final failed = await _replaceTokens(updatedTokens); + final recoveredTokens = oldState.tokens.whereType().where((oldToken) => failed.contains(oldToken)).toList(); + + // Merge the updated tokens with the recovered tokens, so the returned list has the same tokens as the repository. + final mergedTokens = updatedTokens + .map((updated) => recoveredTokens.firstWhere( + (recoveredToken) => recoveredToken == updated, + orElse: () => updated, + )) + .toList(); + _stateMutex.release(); + return mergedTokens; + } + + /* + ////////////////////////////////////////////////////////////////////////////// + //////////////////////// UI Interaction Methods ////////////////////////////// + /////// These methods are used to interact with the UI and the user. ///////// + ////////////////////////////////////////////////////////////////////////////// + /// There is no need to use mutexes because the updating functions are always using the latest version of the updating tokens. + */ + + /// Adds a new token and returns true if successful, false if not. + Future addNewToken(Token token) async { + final success = await _addOrReplaceToken(token); + await _handlePushTokensIfExist(); + return success; + } + + /// Adds or replaces a token and returns true if successful, false if not. + Future addOrReplaceToken(Token token) => _addOrReplaceToken(token); + + /// Adds new tokens and returns the tokens that could not be added. + Future> addTokens(List tokens) async { + final failedTokens = await _addOrReplaceTokens(tokens); + await _handlePushTokensIfExist(); + return failedTokens; + } + + /// Adds or replaces a list of tokens and returns the tokens that could not be added or replaced. + Future> addOrReplaceTokens(List tokens) => _addOrReplaceTokens(tokens); + + /// Updates a token and returns the updated token if successful, the old token if not and null if the token does not exist. + Future updateToken(T token, T Function(T) updater) async => _updateToken(token, updater); + + /// Updates a list of tokens and returns the updated tokens if successful, the old tokens if not and an empty list if the tokens does not exist. + Future> updateTokens(List tokens, T Function(T) updater) async => _updateTokens(tokens, updater); + + /// Adds, Updates or Removes tokens based on the [TokenContainer] and returns the updated tokens. + Future updateContainerTokens(TokenContainer container) async { + await initState; + Logger.info('Updating tokens from container.', name: 'token_notifier.dart#updateContainerTokens'); + final templatesToAdd = []; + final templatesToUpdate = []; + final templatesToRemove = []; + + final knownContainerTokens = state.tokens.fromContainer(container.serial)..addAll(state.maybePiTokens); + final serverTokenTemplates = container.syncedTokenTemplates; + Logger.debug( + 'App knows ${knownContainerTokens.length} of ${serverTokenTemplates.length} server tokens', + name: 'token_notifier.dart#updateContainerTokens', + ); + final appTokenTemplates = knownContainerTokens.toTemplates(); + Logger.debug('All server templates: ${serverTokenTemplates.join('\n')}', name: 'token_notifier.dart#updateContainerTokens'); + for (var serverTokenTemplate in serverTokenTemplates) { + Logger.debug( + 'Checking server token template: $serverTokenTemplate', + name: 'token_notifier.dart#updateContainerTokens', + ); + // Searches for tokens that are in the container but not in the app to add them. + // If the token is already in the app, it will be updated. + // Reduces the [tokenTemplatesApp] list to only the tokens that are in the app but not in the container. These tokens will be removed. + final appTemplate = appTokenTemplates.firstWhereOrNull( + (templateApp) => templateApp.isSameTokenAs(serverTokenTemplate), + ); + if (appTemplate == null) { + Logger.debug( + 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} not found in app. Adding them.', + name: 'token_notifier.dart#updateContainerTokens', + ); + templatesToAdd.add(serverTokenTemplate); + } else { + Logger.debug( + 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app. Checking for update.', + name: 'token_notifier.dart#updateContainerTokens', + ); + appTokenTemplates.remove(appTemplate); + if (!appTemplate.hasSameValuesAs(serverTokenTemplate)) { + Logger.debug( + 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app the Template but is different. Check update.', + name: 'token_notifier.dart#updateContainerTokens', + ); + // Only update the token if the template is different + templatesToUpdate.add(serverTokenTemplate); + } else { + Logger.debug( + 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app. And is the same. Skipping.', + name: 'token_notifier.dart#updateContainerTokens', + ); + } + } + } + // Removes all tokens that are in the app but not in the container. + final remainingTokenTemplatesOfContainer = appTokenTemplates.where((template) => template.containerSerial == container.serial); + templatesToRemove.addAll(remainingTokenTemplatesOfContainer); + + Logger.debug( + 'Add(${templatesToAdd.length}) | Check update(${templatesToUpdate.length}) | Remove(${templatesToRemove.length})', + name: 'token_notifier.dart#updateContainerTokens', + ); + + final tokensToAdd = templatesToAdd.map((e) => e.toToken(container)).toList(); + final tokensToUpdate = []; + for (var template in templatesToUpdate) { + final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id || token.serial == template.serial); + if (token == null) continue; + final needsUpdate = template.tokenWouldBeUpdated(token); + if (needsUpdate) { + tokensToUpdate.add(token); + } + } + final tokensToRemove = []; + for (var template in templatesToRemove) { + final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id || token.serial == template.serial); + if (token == null) continue; + tokensToRemove.add(token); + } + + Logger.debug( + 'Add(${tokensToAdd.length}) | Update(${tokensToUpdate.length}) | Remove(${tokensToRemove.length})', + name: 'token_notifier.dart#updateContainerTokens', + ); + + if (tokensToAdd.isNotEmpty) { + await addOrReplaceTokens(tokensToAdd); + } + if (tokensToUpdate.isNotEmpty) { + await updateTokens(tokensToUpdate, (token) { + final template = templatesToUpdate.firstWhereOrNull((template) => template.id == token.id || template.serial == token.serial); + if (template == null) { + Logger.debug( + 'Not able to update token with id ${token.id} or serial ${token.serial}. No token serial/id matches the templates serial/id.', + name: 'token_notifier.dart#updateContainerTokens', + ); + return token; + } + Logger.debug( + 'Updating token with id:"${token.id}"/serial:"${token.serial}".', + name: 'token_notifier.dart#updateContainerTokens', + ); + return token.copyWithFromTemplate(template); + }); + } + if (tokensToRemove.isNotEmpty) { + await removeTokens(tokensToRemove); + } + } + + /// Increments the counter of a HOTPToken and returns the updated token if successful, the old token if not and null if the token does not exist. + Future incrementCounter(HOTPToken token) => _updateToken(token, (p0) => p0.copyWith(counter: token.counter + 1)); + + /// Hides a token and returns the updated token if successful, the old token if not and null if the token does not exist. + Future hideToken(T token) => _updateToken(token, (p0) => p0.copyWith(isHidden: true) as T); + + /// Shows a token and returns the updated token if successful, the old token if not and null if the token does not exist or the user is not authenticated. + Future showToken(T token) async { + final authenticated = await lockAuth(localizedReason: AppLocalizations.of(globalNavigatorKey.currentContext!)!.authenticateToShowOtp); + if (!authenticated) return null; + final updated = await _updateToken(token, (p0) => p0.copyWith(isHidden: false) as T); + if (updated?.isHidden == false) { + _hidingTimers[token.id]?.cancel(); + _hidingTimers[token.id] = Timer(token.showDuration, () async { + await hideToken(token); + }); + } + return updated; + } + + /// Shows a token and returns the updated token if successful, the old token if not and null if the token does not exist or the user is not authenticated. + Future showTokenById(String tokenId) { + final token = getTokenById(tokenId); + if (token == null) { + Logger.warning('Tried to show a token that does not exist.', name: 'token_notifier.dart#showTokenById'); + return Future.value(null); + } + if (token is! OTPToken) { + Logger.warning('Tried to show a token that is not an OTPToken.', name: 'token_notifier.dart#showTokenById'); + return Future.value(null); + } + return showToken(token); + } + + Future loadStateFromRepo() async { + try { + return await _loadFromRepo(); + } catch (_) { + Logger.warning('Loading tokens from storage failed.', name: 'token_notifier.dart#loadStateFromRepo'); + return null; + } + } + + Future saveStateToRepo() async { + try { + await _saveStateToRepo(state); + Logger.info('Saved ${state.tokens.length} Tokens to storage.', name: 'token_notifier.dart#saveStateToRepo'); + return true; + } catch (_) { + Logger.error('Saving tokens to storage failed.', name: 'token_notifier.dart#saveStateToRepo'); + return false; + } + } + + /// Minimizing the app needs to cancel all timers and save the state to the repository. + Future saveStateOnMinimizeApp() async { + _cancelTimers(); + await hideLockedTokens(); + return _saveStateToRepo(state); + } + + Future> hideLockedTokens() async { + final hideLockedTokens = []; + for (var token in state.tokens) { + if (token.isLocked && !token.isHidden) { + hideLockedTokens.add(token); + } + } + return await updateTokens(hideLockedTokens, (p0) => p0.copyWith(isHidden: true)); + } + + /// Removes a token from the state and the repository. + Future removeToken(Token token) async { + if (token is PushToken) { + await _removePushToken(token); + return; + } + await _removeToken(token); + } + + /// Removes a list of tokens from the state and the repository. + Future removeTokens(List tokens) async { + Logger.info('Removing ${tokens.length} tokens.', name: 'token_notifier.dart#removeTokens'); + final pushTokens = tokens.whereType().toList(); + final otherTokens = tokens.whereType().toList(); + await _removeTokens(otherTokens); + for (var token in pushTokens) { + await _removePushToken(token); + } + } + + Future _removePushToken(PushToken token) async { + try { + await _firebaseUtils.deleteFirebaseToken(); + } on SocketException { + Logger.warning('Could not delete firebase token.', name: 'token_notifier.dart#_removePushToken'); + ref.read(statusMessageProvider.notifier).state = ( + AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorUnlinkingPushToken(token.label), + AppLocalizations.of(globalNavigatorKey.currentContext!)!.checkYourNetwork, + ); + } + + _firebaseUtils.getFBToken().then((fbToken) async { + if (fbToken == null) { + await _updateTokens(state.pushTokens, (p0) => p0.copyWith(fbToken: null)); + Logger.warning('Could not update firebase token because no firebase token is available.', name: 'token_notifier.dart#_removePushToken'); + ref.read(statusMessageProvider.notifier).state = ( + AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorSynchronizationNoNetworkConnection, + AppLocalizations.of(globalNavigatorKey.currentContext!)!.pleaseSyncManuallyWhenNetworkIsAvailable, + ); + } + final (notUpdated, _) = (await updateFirebaseToken(fbToken)) ?? ([], []); + await _updateTokens(notUpdated, (p0) => p0.copyWith(fbToken: null)); + return; + }); + await _removeToken(token); + Logger.info('Push token "${token.id}" removed successfully.', name: 'token_notifier.dart#_removePushToken'); + } + + Future rolloutPushToken(PushToken token) async { + PushToken? pushToken; + pushToken = (getTokenById(token.id)) as PushToken?; + if (pushToken == null) { + Logger.warning('Tried to rollout a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + return false; + } + + assert(pushToken.url != null, 'Token url is null. Cannot rollout token without url.'); + Logger.info('Rolling out token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); + if (pushToken.isRolledOut) { + Logger.info('Ignoring rollout request: Token "${pushToken.id}" already rolled out.', name: 'token_notifier.dart#rolloutPushToken'); + return true; + } + if (pushToken.rolloutState.rollOutInProgress) { + Logger.info('Ignoring rollout request: Rollout of token "${pushToken.id}" already started. Tokenstate: ${pushToken.rolloutState} ', + name: 'token_notifier.dart#rolloutPushToken'); + return false; + } + if (pushToken.expirationDate?.isBefore(DateTime.now()) == true) { + Logger.info('Ignoring rollout request: Token "${pushToken.id}" is expired. ', name: 'token_notifier.dart#rolloutPushToken'); + + if (globalNavigatorKey.currentContext != null) { + ref.read(statusMessageProvider.notifier).state = ( + AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutNotPossibleAnymore, + AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorTokenExpired(pushToken.label), + ); + } + await _removeToken(pushToken); + return false; + } + + if (pushToken.privateTokenKey == null) { + Logger.info('Updating rollout state of token "${pushToken.id}" to generatingRSAKeyPair', name: 'token_notifier.dart#rolloutPushToken'); + pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.generatingRSAKeyPair)); + if (pushToken == null) { + Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + return false; + } + Logger.info('Updated token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); + try { + final keyPair = await _rsaUtils.generateRSAKeyPair(); + pushToken = pushToken.withPrivateTokenKey(keyPair.privateKey); + pushToken = pushToken.withPublicTokenKey(keyPair.publicKey); + pushToken = await _updateToken(pushToken, (p0) { + p0 = p0.withPrivateTokenKey(keyPair.privateKey); + return p0.withPublicTokenKey(keyPair.publicKey); + }) ?? + pushToken; + Logger.info('Updated token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); + } catch (e, s) { + Logger.error('Error while generating RSA key pair.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); + if (pushToken == null) { + Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + return false; + } + pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.generatingRSAKeyPairFailed)); + return false; + } + } + + pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKey)); + if (pushToken == null) { + Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + return false; + } + if (!kIsWeb && Platform.isIOS) { + Logger.warning('Triggering network access permission for token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); + if (await _ioClient.triggerNetworkAccessPermission(url: pushToken.url!, sslVerify: pushToken.sslVerify) == false) { + Logger.warning('Network access permission for token "${pushToken.id}" failed.', name: 'token_notifier.dart#rolloutPushToken'); + _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKeyFailed)); + return false; + } + Logger.warning('Network access permission for token "${pushToken.id}" successful.', name: 'token_notifier.dart#rolloutPushToken'); + } + try { + // TODO What to do with poll only tokens if google-services is used? + + Logger.warning('SSLVerify: ${pushToken.sslVerify}', name: 'token_notifier.dart#rolloutPushToken'); + final fbToken = await _firebaseUtils.getFBToken(); + Response response = await _ioClient.doPost( + sslVerify: pushToken.sslVerify, + url: pushToken.url!, + body: { + 'enrollment_credential': pushToken.enrollmentCredentials, + 'serial': pushToken.serial, + 'fbtoken': fbToken, + 'pubkey': _rsaUtils.serializeRSAPublicKeyPKCS8(pushToken.rsaPublicTokenKey!), + }, + ); + + if (response.statusCode == 200) { + pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.parsingResponse, fbToken: fbToken)); + if (pushToken == null) { + Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + return false; + } + try { + RSAPublicKey publicServerKey = await _parseRollOutResponse(response); + pushToken = await _updateToken(pushToken, (p0) => p0.withPublicServerKey(publicServerKey)); + if (pushToken == null) { + Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + return false; + } + } on FormatException catch (e, s) { + showMessage(message: "Couldn't parsing RSA public key: ${e.message}", duration: const Duration(seconds: 3)); + + Logger.warning('Error while parsing RSA public key.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); + if (pushToken == null) { + Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + return false; + } + pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.parsingResponseFailed)); + return false; + } + Logger.info('Roll out successful', name: 'token_notifier.dart#rolloutPushToken'); + pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(isRolledOut: true, rolloutState: PushTokenRollOutState.rolloutComplete)); + checkNotificationPermission(); + + return true; + } else { + Logger.warning('Post request on roll out failed.', + name: 'token_notifier.dart#rolloutPushToken', + error: 'Token: ${pushToken.serial}\nStatus code: ${response.statusCode},\nURL:${response.request?.url}\nBody: ${response.body}'); + + try { + final message = response.body.isNotEmpty ? (json.decode(response.body)['result']?['error']?['message']) : ''; + ref.read(statusMessageProvider.notifier).state = ( + AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutFailed(pushToken.label), + message, + ); + } on FormatException { + // Format Exception is thrown if the response body is not a valid json. This happens if the server is not reachable. + + ref.read(statusMessageProvider.notifier).state = ( + AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutFailed(pushToken.label), + AppLocalizations.of(globalNavigatorKey.currentContext!)!.statusCode(response.statusCode) + ); + } + + pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKeyFailed)); + return false; + } + } catch (e, s) { + if (pushToken == null) { + Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + return false; + } + pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKeyFailed)); + if (pushToken == null) { + Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + return false; + } + if (e is PlatformException && e.code == FIREBASE_TOKEN_ERROR_CODE || e is SocketException || e is TimeoutException || e is FirebaseException) { + Logger.warning('Connection error: Roll out push token failed.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); + showMessage( + message: AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutNoConnectionToServer(pushToken.label), + duration: const Duration(seconds: 3), + ); + } else if (e is HandshakeException) { + Logger.warning('SSL error: Roll out push token failed.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); + showMessage( + message: AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutSSLHandshakeFailed, + duration: const Duration(seconds: 3), + ); + } else { + if (globalNavigatorKey.currentContext != null) { + showMessage( + message: AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutUnknownError(e), + duration: const Duration(seconds: 3), + ); + } + Logger.error('Roll out push token failed.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); + } + return false; + } + } + + /// This method attempts to update the fbToken for all PushTokens that can be + /// updated. I.e. all tokens that know the url of their respective privacyIDEA + /// server. + /// If the fbToken is not provided, it will be fetched from the firebase instance. + /// If the fbToken is not available, this method will return null. + /// Returns a tuple of two lists. The first list contains all tokens that + /// could not be updated. The second list contains all tokens that do not + /// support updating the fbToken. + /// + /// This should only be used to attempt to update the fbToken automatically, + /// as this can not be guaranteed to work. There is a manual option available + /// through the settings also. + Future<(List, List)?> updateFirebaseToken([String? firebaseToken]) async { + Logger.info('Updating firebase token for all push tokens.', name: 'push_provider.dart#updateFirebaseToken'); + firebaseToken ??= await _firebaseUtils.getFBToken(); + if (firebaseToken == null) { + Logger.warning('Could not update firebase token because no firebase token is available.', name: 'push_provider.dart#updateFirebaseToken'); + return null; + } + List tokenList = state.pushTokens.where((t) => t.isRolledOut && t.fbToken != firebaseToken).toList(); + Logger.info('Updating firebase token for ${tokenList.length} push tokens.', name: 'push_provider.dart#updateFirebaseToken'); + bool allUpdated = true; + final List failedTokens = []; + final List unsuportedTokens = []; + + for (PushToken p in tokenList) { + if (p.url == null) { + unsuportedTokens.add(p); + continue; + } + // POST /ttype/push HTTP/1.1 + //Host: example.com + // + //new_fb_token= + //serial=element + //timestamp= + //signature=SIGNATURE(||) + Logger.warning('Updating firebase token for push token "${p.serial}"', name: 'push_provider.dart#updateFirebaseToken'); + String timestamp = DateTime.now().toUtc().toIso8601String(); + String message = '$firebaseToken|${p.serial}|$timestamp'; + String? signature = await _rsaUtils.trySignWithToken(p, message); + if (signature == null) { + failedTokens.add(p); + allUpdated = false; + continue; + } + Response response = await _ioClient.doPost( + url: p.url!, + body: {'new_fb_token': firebaseToken, 'serial': p.serial, 'timestamp': timestamp, 'signature': signature}, + sslVerify: p.sslVerify, + ); + if (response.statusCode == 200) { + Logger.info('Updating firebase token for push token succeeded!', name: 'push_provider.dart#updateFirebaseToken'); + _updateToken(p, (p0) => p0.copyWith(fbToken: firebaseToken)); + } else { + Logger.warning('Updating firebase token for push token failed!', name: 'push_provider.dart#updateFirebaseToken'); + failedTokens.add(p); + allUpdated = false; + } + } + + if (allUpdated) { + await _firebaseUtils.setCurrentFirebaseToken(firebaseToken); + } + return (failedTokens, unsuportedTokens); + } + + /* //////////////////////////////////////////////////////////////////////////// + ///////////////////////// Add New Tokens Methods ////////////////////////////// + /////////////////////////////////////////////////////////////////////////////// + /// Does not need to wait for updating functions because they doesn't depend on any state */ + + /// The return value of a qrCode could be any object. In this case should be a String that is a valid URI. + /// If it is not a valid URI, the user will be informed. + Future handleQrCode(Object? qrCode) async { + Uri uri; + try { + qrCode as String; + uri = Uri.parse(qrCode); + } catch (_) { + showMessage(message: 'The scanned QR code is not a valid URI.', duration: const Duration(seconds: 3)); + Logger.warning('Scanned Data: $qrCode', error: 'Scanned QR code is not a valid URI.', name: 'token_notifier.dart#handleQrCode'); + return; + } + List tokens = await _tokensFromUri(uri); + tokens = tokens + .map( + (e) => e.copyWith( + origin: e.origin?.copyWith(source: TokenOriginSourceType.qrScan) ?? + TokenOriginSourceType.qrScan.toTokenOrigin(data: uri.toString(), isPrivacyIdeaToken: null), + ), + ) + .toList(); + + await _addOrReplaceTokens(tokens); + await _handlePushTokensIfExist(); + } + + Future handleLink(Uri uri) async { + List tokens = await _tokensFromUri(uri); + tokens = tokens + .map((e) => e.copyWith( + origin: e.origin?.copyWith(source: TokenOriginSourceType.link) ?? + TokenOriginSourceType.link.toTokenOrigin(data: uri.toString(), isPrivacyIdeaToken: null))) + .toList(); + await _addOrReplaceTokens(tokens); + await _handlePushTokensIfExist(); + } + + Future> _tokensFromUri(Uri uri) async { + if (!TokenImportSchemeProcessor.allSupportedSchemes.contains(uri.scheme)) { + return Future.value([]); + } + try { + final results = await TokenImportSchemeProcessor.processUriByAny(uri); + if (results == null || results.isEmpty) { + showMessage(message: 'The scanned QR code is not a valid URI.', duration: const Duration(seconds: 3)); + Logger.warning('Scanned Data: $uri', error: 'Scanned QR code is not a valid URI.', name: 'token_notifier.dart#handleQrCode'); + return []; + } + final failedResults = results.whereType().toList(); + for (var failedResult in failedResults) { + ref.read(statusMessageProvider.notifier).state = (AppLocalizations.of((await globalContext))!.malformedData, failedResult.message); + } + final successResults = results.whereType>().toList(); + if (successResults.isEmpty) { + return []; + } + if (successResults.length > 1 || state.tokens.any((e) => successResults.first.resultData.isSameTokenAs(e) == true)) { + Navigator.of(globalNavigatorKey.currentContext!).popUntil((route) => route.isFirst); + final tokensToKeep = await Navigator.of(globalNavigatorKey.currentContext!).push>( + MaterialPageRoute>( + builder: (context) => ImportPlainTokensPage( + titleName: AppLocalizations.of(context)!.importTokens, + processorResults: results, + selectedType: TokenImportType.qrScan, + ), + ), + ); + return tokensToKeep ?? []; + } + return successResults.map((e) => e.resultData).toList(); + } catch (error, stackTrace) { + Logger.error('Error while processing QR code.', name: 'token_notifier.dart#handleQrCode', error: error, stackTrace: stackTrace); + return []; + } + } + + /* ///////////////////////////////////////////////////////////////////////////// + /////////////////////////// Helper Methods ///////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////// */ + + Future _parseRollOutResponse(Response response) async { + Logger.info('Parsing rollout response, try to extract public_key.', name: 'token_notifier.dart#_parseRollOutResponse'); + try { + String key = json.decode(response.body)['detail']['public_key']; + key = key.replaceAll('\n', ''); + + Logger.info('Extracting public key was successful.', name: 'token_notifier.dart#_parseRollOutResponse'); + + return _rsaUtils.deserializeRSAPublicKeyPKCS1(key); + } on FormatException catch (e) { + throw FormatException('Response body does not contain RSA public key.', e); + } + } + + Future _handlePushTokensIfExist() async { + Logger.info('Handling push tokens if they exist.', name: 'token_notifier.dart#_handlePushTokensIfExist'); + final pushTokens = state.pushTokens; + if (pushTokens.isEmpty || state.pushTokens.isEmpty) { + if ((await ref.read(settingsProvider.future)).hidePushTokens == true) { + ref.read(settingsProvider.notifier).setHidePushTokens(false); + } + } + if (pushTokens.firstWhereOrNull((element) => element.isRolledOut && element.fbToken == null) != null) { + // If there is a push token without fbToken, then update the fbToken + await updateFirebaseToken(); + } + if (state.hasRolledOutPushTokens) { + checkNotificationPermission(); + } + for (final element in state.pushTokensToRollOut) { + Logger.info('Handling push token "${element.id}"', name: 'token_notifier.dart#_handlePushTokensIfExist'); + await rolloutPushToken(element); + } + } + + Token? getTokenById(String id) { + return state.tokens.firstWhereOrNull((element) => element.id == id); + } + + void _cancelTimers() { + for (final key in _hidingTimers.keys) { + _hidingTimers[key]?.cancel(); + } + _hidingTimers.clear(); + } +} + + + + + + + +// } diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.g.dart new file mode 100644 index 000000000..26ff19f6b --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.g.dart @@ -0,0 +1,237 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'token_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$tokenNotifierHash() => r'056041b8761574fb7ff5682ddee48012d15bb2ae'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$TokenNotifier extends BuildlessNotifier { + late final TokenRepository repo; + late final RsaUtils rsaUtils; + late final PrivacyideaIOClient ioClient; + late final FirebaseUtils firebaseUtils; + + TokenState build({ + required TokenRepository repo, + required RsaUtils rsaUtils, + required PrivacyideaIOClient ioClient, + required FirebaseUtils firebaseUtils, + }); +} + +/// See also [TokenNotifier]. +@ProviderFor(TokenNotifier) +const tokenNotifierProviderOf = TokenNotifierFamily(); + +/// See also [TokenNotifier]. +class TokenNotifierFamily extends Family { + /// See also [TokenNotifier]. + const TokenNotifierFamily(); + + /// See also [TokenNotifier]. + TokenNotifierProvider call({ + required TokenRepository repo, + required RsaUtils rsaUtils, + required PrivacyideaIOClient ioClient, + required FirebaseUtils firebaseUtils, + }) { + return TokenNotifierProvider( + repo: repo, + rsaUtils: rsaUtils, + ioClient: ioClient, + firebaseUtils: firebaseUtils, + ); + } + + @override + TokenNotifierProvider getProviderOverride( + covariant TokenNotifierProvider provider, + ) { + return call( + repo: provider.repo, + rsaUtils: provider.rsaUtils, + ioClient: provider.ioClient, + firebaseUtils: provider.firebaseUtils, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'tokenNotifierProviderOf'; +} + +/// See also [TokenNotifier]. +class TokenNotifierProvider + extends NotifierProviderImpl { + /// See also [TokenNotifier]. + TokenNotifierProvider({ + required TokenRepository repo, + required RsaUtils rsaUtils, + required PrivacyideaIOClient ioClient, + required FirebaseUtils firebaseUtils, + }) : this._internal( + () => TokenNotifier() + ..repo = repo + ..rsaUtils = rsaUtils + ..ioClient = ioClient + ..firebaseUtils = firebaseUtils, + from: tokenNotifierProviderOf, + name: r'tokenNotifierProviderOf', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$tokenNotifierHash, + dependencies: TokenNotifierFamily._dependencies, + allTransitiveDependencies: + TokenNotifierFamily._allTransitiveDependencies, + repo: repo, + rsaUtils: rsaUtils, + ioClient: ioClient, + firebaseUtils: firebaseUtils, + ); + + TokenNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.repo, + required this.rsaUtils, + required this.ioClient, + required this.firebaseUtils, + }) : super.internal(); + + final TokenRepository repo; + final RsaUtils rsaUtils; + final PrivacyideaIOClient ioClient; + final FirebaseUtils firebaseUtils; + + @override + TokenState runNotifierBuild( + covariant TokenNotifier notifier, + ) { + return notifier.build( + repo: repo, + rsaUtils: rsaUtils, + ioClient: ioClient, + firebaseUtils: firebaseUtils, + ); + } + + @override + Override overrideWith(TokenNotifier Function() create) { + return ProviderOverride( + origin: this, + override: TokenNotifierProvider._internal( + () => create() + ..repo = repo + ..rsaUtils = rsaUtils + ..ioClient = ioClient + ..firebaseUtils = firebaseUtils, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + repo: repo, + rsaUtils: rsaUtils, + ioClient: ioClient, + firebaseUtils: firebaseUtils, + ), + ); + } + + @override + NotifierProviderElement createElement() { + return _TokenNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is TokenNotifierProvider && + other.repo == repo && + other.rsaUtils == rsaUtils && + other.ioClient == ioClient && + other.firebaseUtils == firebaseUtils; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, repo.hashCode); + hash = _SystemHash.combine(hash, rsaUtils.hashCode); + hash = _SystemHash.combine(hash, ioClient.hashCode); + hash = _SystemHash.combine(hash, firebaseUtils.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin TokenNotifierRef on NotifierProviderRef { + /// The parameter `repo` of this provider. + TokenRepository get repo; + + /// The parameter `rsaUtils` of this provider. + RsaUtils get rsaUtils; + + /// The parameter `ioClient` of this provider. + PrivacyideaIOClient get ioClient; + + /// The parameter `firebaseUtils` of this provider. + FirebaseUtils get firebaseUtils; +} + +class _TokenNotifierProviderElement + extends NotifierProviderElement + with TokenNotifierRef { + _TokenNotifierProviderElement(super.provider); + + @override + TokenRepository get repo => (origin as TokenNotifierProvider).repo; + @override + RsaUtils get rsaUtils => (origin as TokenNotifierProvider).rsaUtils; + @override + PrivacyideaIOClient get ioClient => + (origin as TokenNotifierProvider).ioClient; + @override + FirebaseUtils get firebaseUtils => + (origin as TokenNotifierProvider).firebaseUtils; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart b/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart deleted file mode 100644 index e70ed9b0b..000000000 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart +++ /dev/null @@ -1,44 +0,0 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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_riverpod/flutter_riverpod.dart'; - -import '../../../../model/riverpod_states/token_state.dart'; -import '../../state_notifiers/token_notifier.dart'; -import '../../../logger.dart'; -import '../generated_providers/deeplink_notifier.dart'; - -final tokenProvider = StateNotifierProvider( - (ref) { - Logger.info("New TokenNotifier created"); - final newTokenNotifier = TokenNotifier(ref: ref); - - ref.listen(deeplinkNotifierProvider, (previous, newLink) { - newLink.whenData( - (data) { - Logger.info("Received new deeplink with data: $data", name: 'tokenProvider#deeplinkProvider'); - newTokenNotifier.handleLink(data.uri); - }, - ); - }); - - return newTokenNotifier; - }, - name: 'tokenProvider', -); diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart index 6eb9b40fe..1abb5aad8 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart @@ -22,7 +22,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/tokens/otp_token.dart'; import '../../../home_widget_utils.dart'; import '../../../logger.dart'; -import '../state_notifier_providers/token_provider.dart'; +import '../state_notifier_providers/token_notifier.dart'; final homeWidgetProvider = StateProvider>( (ref) { diff --git a/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart b/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart index caea99c07..8415ae790 100644 --- a/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart @@ -23,7 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../globals.dart'; import '../../../logger.dart'; -import '../state_notifier_providers/token_provider.dart'; +import '../state_notifier_providers/token_notifier.dart'; import '../state_providers/status_message_provider.dart'; final connectivityProvider = StreamProvider>( diff --git a/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart index b1c91f65e..5fe16f5a5 100644 --- a/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/home_widget_token_state_listener.dart @@ -26,7 +26,7 @@ import '../../../model/tokens/token.dart'; import '../../home_widget_utils.dart'; class HomeWidgetTokenStateListener extends TokenStateListener { - const HomeWidgetTokenStateListener({required super.tokenProvider}) + const HomeWidgetTokenStateListener({required super.provider}) : super( onNewState: _onNewState, listenerName: 'HomeWidgetUtils().updateTokensIfLinked', diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index f95abf188..f66ca582e 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -28,7 +28,7 @@ import '../riverpod_providers/generated_providers/token_container_notifier.dart' class ContainerListensToTokenState extends TokenStateListener { ContainerListensToTokenState({ - required super.tokenProvider, + required super.provider, required WidgetRef ref, }) : super( onNewState: (TokenState? previous, TokenState next) => WidgetsBinding.instance.addPostFrameCallback((_) { diff --git a/lib/utils/riverpod/state_notifiers/token_notifier.dart b/lib/utils/riverpod/state_notifiers/token_notifier.dart index b661c82e8..f6be22c8c 100644 --- a/lib/utils/riverpod/state_notifiers/token_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_notifier.dart @@ -17,945 +17,945 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:collection/collection.dart'; -import 'package:firebase_core/firebase_core.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:http/http.dart'; -import 'package:mutex/mutex.dart'; -import 'package:pointycastle/asymmetric/api.dart'; - -import '../../../interfaces/repo/token_repository.dart'; -import '../../../l10n/app_localizations.dart'; -import '../../../model/enums/push_token_rollout_state.dart'; -import '../../../model/enums/token_import_type.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/riverpod_states/token_state.dart'; -import '../../../model/token_container.dart'; -import '../../../model/tokens/hotp_token.dart'; -import '../../../model/tokens/otp_token.dart'; -import '../../../model/tokens/push_token.dart'; -import '../../../model/tokens/token.dart'; -import '../../../processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart'; -import '../../../repo/secure_token_repository.dart'; -import '../../firebase_utils.dart'; -import '../../globals.dart'; -import '../../identifiers.dart'; -import '../../lock_auth.dart'; -import '../../logger.dart'; -import '../../privacyidea_io_client.dart'; -import '../riverpod_providers/generated_providers/settings_notifier.dart'; -import '../riverpod_providers/state_providers/status_message_provider.dart'; -import '../../rsa_utils.dart'; -import '../../utils.dart'; -import '../../view_utils.dart'; -import '../../../views/import_tokens_view/pages/import_plain_tokens_page.dart'; - -class TokenNotifier extends StateNotifier { - static final Map _hidingTimers = {}; - late final Future initState; - final StateNotifierProviderRef ref; - final _loadingRepoMutex = Mutex(); - final _updatingTokensMutex = Mutex(); - final TokenRepository _repo; - final RsaUtils _rsaUtils; - final PrivacyideaIOClient _ioClient; - final FirebaseUtils _firebaseUtils; - - TokenNotifier({ - required this.ref, - TokenState? initialState, - TokenRepository? repository, - RsaUtils? rsaUtils, - PrivacyideaIOClient? ioClient, - FirebaseUtils? firebaseUtils, - }) : _rsaUtils = rsaUtils ?? const RsaUtils(), - _repo = repository ?? const SecureTokenRepository(), - _ioClient = ioClient ?? const PrivacyideaIOClient(), - _firebaseUtils = firebaseUtils ?? FirebaseUtils(), - super( - initialState ?? TokenState(tokens: const [], lastlyUpdatedTokens: const []), - ) { - _init(initialState); - } - - Future _init(TokenState? initialState) async { - initState = initialState != null ? Future.value(initialState) : _loadFromRepo(); - await initState; - await hideLockedTokens(); - Logger.info('TokenNotifier initialized.', name: 'token_notifier.dart#_init'); - } - - /* - ///////////////////////////////////////////////////////////////////////////// - /////////////////////// Repository and Token Handling /////////////////////// - ///////////////////////////////////////////////////////////////////////////// - /// Repository layer is always use loadingRepoMutex for the latest state - */ - - /// Adds a token and returns true if successful, false if not. - Future _addOrReplaceToken(Token token) async { - await _loadingRepoMutex.acquire(); - final success = await _repo.saveOrReplaceToken(token); - if (!success) { - Logger.warning( - 'Saving token failed. Token: ${token.id}', - name: 'token_notifier.dart#_addOrReplaceToken', - ); - _loadingRepoMutex.release(); - return false; - } - state = state.addOrReplaceToken(token); - _loadingRepoMutex.release(); - return true; - } - - /// Adds a list of tokens and returns the tokens that could not be added or replaced. - Future> _addOrReplaceTokens(List tokens) async { - await _loadingRepoMutex.acquire(); - final failedTokens = await _repo.saveOrReplaceTokens(tokens); - if (failedTokens.isNotEmpty) { - Logger.warning( - 'Saving tokens failed. Failed Tokens: ${failedTokens.length}', - name: 'token_notifier.dart#_saveOrReplaceTokens', - ); - // Every token that is saved should not be in the failedTokens list - final savedTokens = tokens.where((element) => !failedTokens.contains(element)).toList(); - state = state.addOrReplaceTokens(savedTokens); - return failedTokens; - } - // [failedTokens] is empty, so every token was saved successfully and we dont need to filter the tokens - Logger.info('Saved ${tokens.length} Tokens to storage.', name: 'token_notifier.dart#_saveOrReplaceTokens'); - state = state.addOrReplaceTokens(tokens); - Logger.debug('New State: ${state.tokens.length} Tokens', name: 'token_notifier.dart#_saveOrReplaceTokens'); - _loadingRepoMutex.release(); - return []; - } - - /// Replaces a token if it exists and returns true if successful, false if not. - Future _replaceToken(Token token) async { - await _loadingRepoMutex.acquire(); - final (newState, replaced) = state.replaceToken(token); - if (!replaced) { - Logger.warning('Tried to replace a token that does not exist.', name: 'token_notifier.dart#_replaceToken'); - _loadingRepoMutex.release(); - return false; - } - final saved = await _repo.saveOrReplaceToken(token); - if (!saved) { - Logger.warning( - 'Saving token failed. Token: ${token.id}', - name: 'token_notifier.dart#_replaceToken', - ); - _loadingRepoMutex.release(); - return false; - } - state = newState; - _loadingRepoMutex.release(); - return true; - } - - /// Returns a list of tokens that could not be replaced - Future> _replaceTokens(List tokens) async { - await _loadingRepoMutex.acquire(); - final oldState = state; - final (newState, failedToReplace) = state.replaceTokens(tokens); - state = newState; - for (var e in failedToReplace) { - tokens.remove(e); - } - final failedToSave = await _repo.saveOrReplaceTokens(tokens); - if (failedToSave.isNotEmpty) { - Logger.warning( - 'Saving tokens failed. Failed Tokens: ${failedToSave.length}', - name: 'token_notifier.dart#_saveOrReplaceTokens', - ); - final recovered = oldState.tokens.whereType().where((oldToken) => failedToSave.contains(oldToken)).toList(); - state = state.addOrReplaceTokens(recovered); - _loadingRepoMutex.release(); - return failedToSave; - } - _loadingRepoMutex.release(); - return []; - } - - /// Removes a token and returns true if successful, false if not. - Future _removeToken(Token token) async { - await _loadingRepoMutex.acquire(); - state = state.withoutToken(token); - - final success = await _repo.deleteToken(token); - if (!success) { - Logger.warning( - 'Deleting token failed. Token: ${token.id}', - name: 'token_notifier.dart#_deleteTokensRepo', - ); - state = state.addOrReplaceToken(token); - _loadingRepoMutex.release(); - return false; - } - _loadingRepoMutex.release(); - _handlePushTokensIfExist(); - return true; - } - - /// Removes a list of tokens and returns the tokens that could not be removed. - Future> _removeTokens(List tokens) async { - Logger.info('Removing ${tokens.length} tokens.', name: 'token_notifier.dart#_removeTokens'); - await _loadingRepoMutex.acquire(); - final oldState = state; - state = state.withoutTokens(tokens); - - final failedTokens = await _repo.deleteTokens(tokens); - if (failedTokens.isNotEmpty) { - Logger.warning( - 'Deleting tokens failed. Failed Tokens: ${failedTokens.length}', - name: 'token_notifier.dart#_deleteTokensRepo', - ); - final recoveredTokens = oldState.tokens.where((oldToken) => failedTokens.contains(oldToken)).toList(); - state = state.addOrReplaceTokens(recoveredTokens); - _loadingRepoMutex.release(); - return failedTokens; - } - _loadingRepoMutex.release(); - _handlePushTokensIfExist(); - return []; - } - - /// Loads the tokens from the repository sets it as the new state and returns the new state. - Future _loadFromRepo() async { - await _loadingRepoMutex.acquire(); - TokenState newState; - try { - List tokens; - tokens = await _repo.loadTokens(); - newState = TokenState(tokens: tokens, lastlyUpdatedTokens: tokens); - state = newState; - } catch (e) { - Logger.error( - 'Loading tokens from storage failed.', - name: 'token_notifier.dart#_loadFromRepo', - error: e, - ); - _loadingRepoMutex.release(); - return state; - } - _loadingRepoMutex.release(); - _handlePushTokensIfExist(); - return newState; - } - - Future _saveStateToRepo(TokenState state) async { - await _loadingRepoMutex.acquire(); - try { - await _repo.saveOrReplaceTokens(state.tokens); - } catch (e) { - Logger.error( - 'Saving tokens to storage failed.', - name: 'token_notifier.dart#_saveStateToRepo', - error: e, - ); - _loadingRepoMutex.release(); - return false; - } - _loadingRepoMutex.release(); - return true; - } - - /* - ////////////////////////////////////////////////////////////////////////////// - ///////////////////////// Update Token Methods /////////////////////////////// - ////////////////////////////////////////////////////////////////////////////// - /// Updating layer is always use updatingTokensMutex for the latest state - */ - - /// Updates a token and returns the updated token if successful, the old token if not and null if the token does not exist. - Future _updateToken(T token, T Function(T) updater) async { - await _updatingTokensMutex.acquire(); - await _loadingRepoMutex.acquire(); - _loadingRepoMutex.release(); - final current = state.currentOf(token); - if (current == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#updateToken'); - _updatingTokensMutex.release(); - return null; - } - final updated = updater(current); - final replaced = await _replaceToken(updated); - _updatingTokensMutex.release(); - return replaced ? updated : current; - } - - /// Updates a list of tokens and returns the updated tokens if successful, the old tokens if not and an empty list if the tokens does not exist. - Future> _updateTokens(List tokens, T Function(T) updater) async { - if (tokens.isEmpty) return []; - await _updatingTokensMutex.acquire(); - final oldState = state; - - List updatedTokens = []; - for (final token in tokens) { - final current = state.currentOf(token) ?? token; - updatedTokens.add(updater(current)); - } - final failed = await _replaceTokens(updatedTokens); - final recoveredTokens = oldState.tokens.whereType().where((oldToken) => failed.contains(oldToken)).toList(); - - // Merge the updated tokens with the recovered tokens, so the returned list has the same tokens as the repository. - final mergedTokens = updatedTokens - .map((updated) => recoveredTokens.firstWhere( - (recoveredToken) => recoveredToken == updated, - orElse: () => updated, - )) - .toList(); - _updatingTokensMutex.release(); - return mergedTokens; - } - - /* - ////////////////////////////////////////////////////////////////////////////// - //////////////////////// UI Interaction Methods ////////////////////////////// - /////// These methods are used to interact with the UI and the user. ///////// - ////////////////////////////////////////////////////////////////////////////// - /// There is no need to use mutexes because the updating functions are always using the latest version of the updating tokens. - */ - - /// Adds a new token and returns true if successful, false if not. - Future addNewToken(Token token) async { - final success = await _addOrReplaceToken(token); - await _handlePushTokensIfExist(); - return success; - } - - /// Adds or replaces a token and returns true if successful, false if not. - Future addOrReplaceToken(Token token) => _addOrReplaceToken(token); - - /// Adds new tokens and returns the tokens that could not be added. - Future> addTokens(List tokens) async { - final failedTokens = await _addOrReplaceTokens(tokens); - await _handlePushTokensIfExist(); - return failedTokens; - } - - /// Adds or replaces a list of tokens and returns the tokens that could not be added or replaced. - Future> addOrReplaceTokens(List tokens) => _addOrReplaceTokens(tokens); - - /// Updates a token and returns the updated token if successful, the old token if not and null if the token does not exist. - Future updateToken(T token, T Function(T) updater) async => _updateToken(token, updater); - - /// Updates a list of tokens and returns the updated tokens if successful, the old tokens if not and an empty list if the tokens does not exist. - Future> updateTokens(List tokens, T Function(T) updater) async => _updateTokens(tokens, updater); - - /// Adds, Updates or Removes tokens based on the [TokenContainer] and returns the updated tokens. - Future updateContainerTokens(TokenContainer container) async { - await initState; - Logger.info('Updating tokens from container.', name: 'token_notifier.dart#updateContainerTokens'); - final templatesToAdd = []; - final templatesToUpdate = []; - final templatesToRemove = []; - - final knownContainerTokens = state.tokens.fromContainer(container.serial)..addAll(state.maybePiTokens); - final serverTokenTemplates = container.syncedTokenTemplates; - Logger.debug( - 'App knows ${knownContainerTokens.length} of ${serverTokenTemplates.length} server tokens', - name: 'token_notifier.dart#updateContainerTokens', - ); - final appTokenTemplates = knownContainerTokens.toTemplates(); - Logger.debug('All server templates: ${serverTokenTemplates.join('\n')}', name: 'token_notifier.dart#updateContainerTokens'); - for (var serverTokenTemplate in serverTokenTemplates) { - Logger.debug( - 'Checking server token template: $serverTokenTemplate', - name: 'token_notifier.dart#updateContainerTokens', - ); - // Searches for tokens that are in the container but not in the app to add them. - // If the token is already in the app, it will be updated. - // Reduces the [tokenTemplatesApp] list to only the tokens that are in the app but not in the container. These tokens will be removed. - final appTemplate = appTokenTemplates.firstWhereOrNull( - (templateApp) => templateApp.isSameTokenAs(serverTokenTemplate), - ); - if (appTemplate == null) { - Logger.debug( - 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} not found in app. Adding them.', - name: 'token_notifier.dart#updateContainerTokens', - ); - templatesToAdd.add(serverTokenTemplate); - } else { - Logger.debug( - 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app. Checking for update.', - name: 'token_notifier.dart#updateContainerTokens', - ); - appTokenTemplates.remove(appTemplate); - if (!appTemplate.hasSameValuesAs(serverTokenTemplate)) { - Logger.debug( - 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app the Template but is different. Check update.', - name: 'token_notifier.dart#updateContainerTokens', - ); - // Only update the token if the template is different - templatesToUpdate.add(serverTokenTemplate); - } else { - Logger.debug( - 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app. And is the same. Skipping.', - name: 'token_notifier.dart#updateContainerTokens', - ); - } - } - } - // Removes all tokens that are in the app but not in the container. - final remainingTokenTemplatesOfContainer = appTokenTemplates.where((template) => template.containerSerial == container.serial); - templatesToRemove.addAll(remainingTokenTemplatesOfContainer); - - Logger.debug( - 'Add(${templatesToAdd.length}) | Check update(${templatesToUpdate.length}) | Remove(${templatesToRemove.length})', - name: 'token_notifier.dart#updateContainerTokens', - ); - - final tokensToAdd = templatesToAdd.map((e) => e.toToken(container)).toList(); - final tokensToUpdate = []; - for (var template in templatesToUpdate) { - final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id || token.serial == template.serial); - if (token == null) continue; - final needsUpdate = template.tokenWouldBeUpdated(token); - if (needsUpdate) { - tokensToUpdate.add(token); - } - } - final tokensToRemove = []; - for (var template in templatesToRemove) { - final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id || token.serial == template.serial); - if (token == null) continue; - tokensToRemove.add(token); - } - - Logger.debug( - 'Add(${tokensToAdd.length}) | Update(${tokensToUpdate.length}) | Remove(${tokensToRemove.length})', - name: 'token_notifier.dart#updateContainerTokens', - ); - - if (tokensToAdd.isNotEmpty) { - await addOrReplaceTokens(tokensToAdd); - } - if (tokensToUpdate.isNotEmpty) { - await updateTokens(tokensToUpdate, (token) { - final template = templatesToUpdate.firstWhereOrNull((template) => template.id == token.id || template.serial == token.serial); - if (template == null) { - Logger.debug( - 'Not able to update token with id ${token.id} or serial ${token.serial}. No token serial/id matches the templates serial/id.', - name: 'token_notifier.dart#updateContainerTokens', - ); - return token; - } - Logger.debug( - 'Updating token with id:"${token.id}"/serial:"${token.serial}".', - name: 'token_notifier.dart#updateContainerTokens', - ); - return token.copyWithFromTemplate(template); - }); - } - if (tokensToRemove.isNotEmpty) { - await removeTokens(tokensToRemove); - } - } - - /// Increments the counter of a HOTPToken and returns the updated token if successful, the old token if not and null if the token does not exist. - Future incrementCounter(HOTPToken token) => _updateToken(token, (p0) => p0.copyWith(counter: token.counter + 1)); - - /// Hides a token and returns the updated token if successful, the old token if not and null if the token does not exist. - Future hideToken(T token) => _updateToken(token, (p0) => p0.copyWith(isHidden: true) as T); - - /// Shows a token and returns the updated token if successful, the old token if not and null if the token does not exist or the user is not authenticated. - Future showToken(T token) async { - final authenticated = await lockAuth(localizedReason: AppLocalizations.of(globalNavigatorKey.currentContext!)!.authenticateToShowOtp); - if (!authenticated) return null; - final updated = await _updateToken(token, (p0) => p0.copyWith(isHidden: false) as T); - if (updated?.isHidden == false) { - _hidingTimers[token.id]?.cancel(); - _hidingTimers[token.id] = Timer(token.showDuration, () async { - await hideToken(token); - }); - } - return updated; - } - - /// Shows a token and returns the updated token if successful, the old token if not and null if the token does not exist or the user is not authenticated. - Future showTokenById(String tokenId) { - final token = getTokenById(tokenId); - if (token == null) { - Logger.warning('Tried to show a token that does not exist.', name: 'token_notifier.dart#showTokenById'); - return Future.value(null); - } - if (token is! OTPToken) { - Logger.warning('Tried to show a token that is not an OTPToken.', name: 'token_notifier.dart#showTokenById'); - return Future.value(null); - } - return showToken(token); - } - - Future loadStateFromRepo() async { - try { - return await _loadFromRepo(); - } catch (_) { - Logger.warning('Loading tokens from storage failed.', name: 'token_notifier.dart#loadStateFromRepo'); - return null; - } - } - - Future saveStateToRepo() async { - try { - await _saveStateToRepo(state); - Logger.info('Saved ${state.tokens.length} Tokens to storage.', name: 'token_notifier.dart#saveStateToRepo'); - return true; - } catch (_) { - Logger.error('Saving tokens to storage failed.', name: 'token_notifier.dart#saveStateToRepo'); - return false; - } - } - - /// Minimizing the app needs to cancel all timers and save the state to the repository. - Future saveStateOnMinimizeApp() async { - _cancelTimers(); - await hideLockedTokens(); - return _saveStateToRepo(state); - } - - Future> hideLockedTokens() async { - final hideLockedTokens = []; - for (var token in state.tokens) { - if (token.isLocked && !token.isHidden) { - hideLockedTokens.add(token); - } - } - return await updateTokens(hideLockedTokens, (p0) => p0.copyWith(isHidden: true)); - } - - /// Removes a token from the state and the repository. - Future removeToken(Token token) async { - if (token is PushToken) { - await _removePushToken(token); - return; - } - await _removeToken(token); - } - - /// Removes a list of tokens from the state and the repository. - Future removeTokens(List tokens) async { - Logger.info('Removing ${tokens.length} tokens.', name: 'token_notifier.dart#removeTokens'); - final pushTokens = tokens.whereType().toList(); - final otherTokens = tokens.whereType().toList(); - await _removeTokens(otherTokens); - for (var token in pushTokens) { - await _removePushToken(token); - } - } - - Future _removePushToken(PushToken token) async { - try { - await _firebaseUtils.deleteFirebaseToken(); - } on SocketException { - Logger.warning('Could not delete firebase token.', name: 'token_notifier.dart#_removePushToken'); - ref.read(statusMessageProvider.notifier).state = ( - AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorUnlinkingPushToken(token.label), - AppLocalizations.of(globalNavigatorKey.currentContext!)!.checkYourNetwork, - ); - } - - _firebaseUtils.getFBToken().then((fbToken) async { - if (fbToken == null) { - await _updateTokens(state.pushTokens, (p0) => p0.copyWith(fbToken: null)); - Logger.warning('Could not update firebase token because no firebase token is available.', name: 'token_notifier.dart#_removePushToken'); - ref.read(statusMessageProvider.notifier).state = ( - AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorSynchronizationNoNetworkConnection, - AppLocalizations.of(globalNavigatorKey.currentContext!)!.pleaseSyncManuallyWhenNetworkIsAvailable, - ); - } - final (notUpdated, _) = (await updateFirebaseToken(fbToken)) ?? ([], []); - await _updateTokens(notUpdated, (p0) => p0.copyWith(fbToken: null)); - return; - }); - await _removeToken(token); - Logger.info('Push token "${token.id}" removed successfully.', name: 'token_notifier.dart#_removePushToken'); - } - - Future rolloutPushToken(PushToken token) async { - PushToken? pushToken; - pushToken = (getTokenById(token.id)) as PushToken?; - if (pushToken == null) { - Logger.warning('Tried to rollout a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); - return false; - } - - assert(pushToken.url != null, 'Token url is null. Cannot rollout token without url.'); - Logger.info('Rolling out token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); - if (pushToken.isRolledOut) { - Logger.info('Ignoring rollout request: Token "${pushToken.id}" already rolled out.', name: 'token_notifier.dart#rolloutPushToken'); - return true; - } - if (pushToken.rolloutState.rollOutInProgress) { - Logger.info('Ignoring rollout request: Rollout of token "${pushToken.id}" already started. Tokenstate: ${pushToken.rolloutState} ', - name: 'token_notifier.dart#rolloutPushToken'); - return false; - } - if (pushToken.expirationDate?.isBefore(DateTime.now()) == true) { - Logger.info('Ignoring rollout request: Token "${pushToken.id}" is expired. ', name: 'token_notifier.dart#rolloutPushToken'); - - if (globalNavigatorKey.currentContext != null) { - ref.read(statusMessageProvider.notifier).state = ( - AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutNotPossibleAnymore, - AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorTokenExpired(pushToken.label), - ); - } - await _removeToken(pushToken); - return false; - } - - if (pushToken.privateTokenKey == null) { - Logger.info('Updating rollout state of token "${pushToken.id}" to generatingRSAKeyPair', name: 'token_notifier.dart#rolloutPushToken'); - pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.generatingRSAKeyPair)); - if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); - return false; - } - Logger.info('Updated token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); - try { - final keyPair = await _rsaUtils.generateRSAKeyPair(); - pushToken = pushToken.withPrivateTokenKey(keyPair.privateKey); - pushToken = pushToken.withPublicTokenKey(keyPair.publicKey); - pushToken = await _updateToken(pushToken, (p0) { - p0 = p0.withPrivateTokenKey(keyPair.privateKey); - return p0.withPublicTokenKey(keyPair.publicKey); - }) ?? - pushToken; - Logger.info('Updated token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); - } catch (e, s) { - Logger.error('Error while generating RSA key pair.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); - if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); - return false; - } - pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.generatingRSAKeyPairFailed)); - return false; - } - } - - pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKey)); - if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); - return false; - } - if (!kIsWeb && Platform.isIOS) { - Logger.warning('Triggering network access permission for token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); - if (await _ioClient.triggerNetworkAccessPermission(url: pushToken.url!, sslVerify: pushToken.sslVerify) == false) { - Logger.warning('Network access permission for token "${pushToken.id}" failed.', name: 'token_notifier.dart#rolloutPushToken'); - _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKeyFailed)); - return false; - } - Logger.warning('Network access permission for token "${pushToken.id}" successful.', name: 'token_notifier.dart#rolloutPushToken'); - } - try { - // TODO What to do with poll only tokens if google-services is used? - - Logger.warning('SSLVerify: ${pushToken.sslVerify}', name: 'token_notifier.dart#rolloutPushToken'); - final fbToken = await _firebaseUtils.getFBToken(); - Response response = await _ioClient.doPost( - sslVerify: pushToken.sslVerify, - url: pushToken.url!, - body: { - 'enrollment_credential': pushToken.enrollmentCredentials, - 'serial': pushToken.serial, - 'fbtoken': fbToken, - 'pubkey': _rsaUtils.serializeRSAPublicKeyPKCS8(pushToken.rsaPublicTokenKey!), - }, - ); - - if (response.statusCode == 200) { - pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.parsingResponse, fbToken: fbToken)); - if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); - return false; - } - try { - RSAPublicKey publicServerKey = await _parseRollOutResponse(response); - pushToken = await _updateToken(pushToken, (p0) => p0.withPublicServerKey(publicServerKey)); - if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); - return false; - } - } on FormatException catch (e, s) { - showMessage(message: "Couldn't parsing RSA public key: ${e.message}", duration: const Duration(seconds: 3)); - - Logger.warning('Error while parsing RSA public key.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); - if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); - return false; - } - pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.parsingResponseFailed)); - return false; - } - Logger.info('Roll out successful', name: 'token_notifier.dart#rolloutPushToken'); - pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(isRolledOut: true, rolloutState: PushTokenRollOutState.rolloutComplete)); - checkNotificationPermission(); - - return true; - } else { - Logger.warning('Post request on roll out failed.', - name: 'token_notifier.dart#rolloutPushToken', - error: 'Token: ${pushToken.serial}\nStatus code: ${response.statusCode},\nURL:${response.request?.url}\nBody: ${response.body}'); - - try { - final message = response.body.isNotEmpty ? (json.decode(response.body)['result']?['error']?['message']) : ''; - ref.read(statusMessageProvider.notifier).state = ( - AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutFailed(pushToken.label), - message, - ); - } on FormatException { - // Format Exception is thrown if the response body is not a valid json. This happens if the server is not reachable. - - ref.read(statusMessageProvider.notifier).state = ( - AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutFailed(pushToken.label), - AppLocalizations.of(globalNavigatorKey.currentContext!)!.statusCode(response.statusCode) - ); - } - - pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKeyFailed)); - return false; - } - } catch (e, s) { - if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); - return false; - } - pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKeyFailed)); - if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); - return false; - } - if (e is PlatformException && e.code == FIREBASE_TOKEN_ERROR_CODE || e is SocketException || e is TimeoutException || e is FirebaseException) { - Logger.warning('Connection error: Roll out push token failed.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); - showMessage( - message: AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutNoConnectionToServer(pushToken.label), - duration: const Duration(seconds: 3), - ); - } else if (e is HandshakeException) { - Logger.warning('SSL error: Roll out push token failed.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); - showMessage( - message: AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutSSLHandshakeFailed, - duration: const Duration(seconds: 3), - ); - } else { - if (globalNavigatorKey.currentContext != null) { - showMessage( - message: AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutUnknownError(e), - duration: const Duration(seconds: 3), - ); - } - Logger.error('Roll out push token failed.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); - } - return false; - } - } - - /// This method attempts to update the fbToken for all PushTokens that can be - /// updated. I.e. all tokens that know the url of their respective privacyIDEA - /// server. - /// If the fbToken is not provided, it will be fetched from the firebase instance. - /// If the fbToken is not available, this method will return null. - /// Returns a tuple of two lists. The first list contains all tokens that - /// could not be updated. The second list contains all tokens that do not - /// support updating the fbToken. - /// - /// This should only be used to attempt to update the fbToken automatically, - /// as this can not be guaranteed to work. There is a manual option available - /// through the settings also. - Future<(List, List)?> updateFirebaseToken([String? firebaseToken]) async { - Logger.info('Updating firebase token for all push tokens.', name: 'push_provider.dart#updateFirebaseToken'); - firebaseToken ??= await _firebaseUtils.getFBToken(); - if (firebaseToken == null) { - Logger.warning('Could not update firebase token because no firebase token is available.', name: 'push_provider.dart#updateFirebaseToken'); - return null; - } - List tokenList = state.pushTokens.where((t) => t.isRolledOut && t.fbToken != firebaseToken).toList(); - Logger.info('Updating firebase token for ${tokenList.length} push tokens.', name: 'push_provider.dart#updateFirebaseToken'); - bool allUpdated = true; - final List failedTokens = []; - final List unsuportedTokens = []; - - for (PushToken p in tokenList) { - if (p.url == null) { - unsuportedTokens.add(p); - continue; - } - // POST /ttype/push HTTP/1.1 - //Host: example.com - // - //new_fb_token= - //serial=element - //timestamp= - //signature=SIGNATURE(||) - Logger.warning('Updating firebase token for push token "${p.serial}"', name: 'push_provider.dart#updateFirebaseToken'); - String timestamp = DateTime.now().toUtc().toIso8601String(); - String message = '$firebaseToken|${p.serial}|$timestamp'; - String? signature = await _rsaUtils.trySignWithToken(p, message); - if (signature == null) { - failedTokens.add(p); - allUpdated = false; - continue; - } - Response response = await _ioClient.doPost( - url: p.url!, - body: {'new_fb_token': firebaseToken, 'serial': p.serial, 'timestamp': timestamp, 'signature': signature}, - sslVerify: p.sslVerify, - ); - if (response.statusCode == 200) { - Logger.info('Updating firebase token for push token succeeded!', name: 'push_provider.dart#updateFirebaseToken'); - _updateToken(p, (p0) => p0.copyWith(fbToken: firebaseToken)); - } else { - Logger.warning('Updating firebase token for push token failed!', name: 'push_provider.dart#updateFirebaseToken'); - failedTokens.add(p); - allUpdated = false; - } - } - - if (allUpdated) { - await _firebaseUtils.setCurrentFirebaseToken(firebaseToken); - } - return (failedTokens, unsuportedTokens); - } - - /* //////////////////////////////////////////////////////////////////////////// - ///////////////////////// Add New Tokens Methods ////////////////////////////// - /////////////////////////////////////////////////////////////////////////////// - /// Does not need to wait for updating functions because they doesn't depend on any state */ - - /// The return value of a qrCode could be any object. In this case should be a String that is a valid URI. - /// If it is not a valid URI, the user will be informed. - Future handleQrCode(Object? qrCode) async { - Uri uri; - try { - qrCode as String; - uri = Uri.parse(qrCode); - } catch (_) { - showMessage(message: 'The scanned QR code is not a valid URI.', duration: const Duration(seconds: 3)); - Logger.warning('Scanned Data: $qrCode', error: 'Scanned QR code is not a valid URI.', name: 'token_notifier.dart#handleQrCode'); - return; - } - List tokens = await _tokensFromUri(uri); - tokens = tokens - .map( - (e) => e.copyWith( - origin: e.origin?.copyWith(source: TokenOriginSourceType.qrScan) ?? - TokenOriginSourceType.qrScan.toTokenOrigin(data: uri.toString(), isPrivacyIdeaToken: null), - ), - ) - .toList(); - - await _addOrReplaceTokens(tokens); - await _handlePushTokensIfExist(); - } - - Future handleLink(Uri uri) async { - List tokens = await _tokensFromUri(uri); - tokens = tokens - .map((e) => e.copyWith( - origin: e.origin?.copyWith(source: TokenOriginSourceType.link) ?? - TokenOriginSourceType.link.toTokenOrigin(data: uri.toString(), isPrivacyIdeaToken: null))) - .toList(); - await _addOrReplaceTokens(tokens); - await _handlePushTokensIfExist(); - } - - Future> _tokensFromUri(Uri uri) async { - if (!TokenImportSchemeProcessor.allSupportedSchemes.contains(uri.scheme)) { - return Future.value([]); - } - try { - final results = await TokenImportSchemeProcessor.processUriByAny(uri); - if (results == null || results.isEmpty) { - showMessage(message: 'The scanned QR code is not a valid URI.', duration: const Duration(seconds: 3)); - Logger.warning('Scanned Data: $uri', error: 'Scanned QR code is not a valid URI.', name: 'token_notifier.dart#handleQrCode'); - return []; - } - final failedResults = results.whereType().toList(); - for (var failedResult in failedResults) { - ref.read(statusMessageProvider.notifier).state = (AppLocalizations.of((await globalContext))!.malformedData, failedResult.message); - } - final successResults = results.whereType>().toList(); - if (successResults.isEmpty) { - return []; - } - if (successResults.length > 1 || state.tokens.any((e) => successResults.first.resultData.isSameTokenAs(e) == true)) { - Navigator.of(globalNavigatorKey.currentContext!).popUntil((route) => route.isFirst); - final tokensToKeep = await Navigator.of(globalNavigatorKey.currentContext!).push>( - MaterialPageRoute>( - builder: (context) => ImportPlainTokensPage( - titleName: AppLocalizations.of(context)!.importTokens, - processorResults: results, - selectedType: TokenImportType.qrScan, - ), - ), - ); - return tokensToKeep ?? []; - } - return successResults.map((e) => e.resultData).toList(); - } catch (error, stackTrace) { - Logger.error('Error while processing QR code.', name: 'token_notifier.dart#handleQrCode', error: error, stackTrace: stackTrace); - return []; - } - } - - /* ///////////////////////////////////////////////////////////////////////////// - /////////////////////////// Helper Methods ///////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////// */ - - Future _parseRollOutResponse(Response response) async { - Logger.info('Parsing rollout response, try to extract public_key.', name: 'token_notifier.dart#_parseRollOutResponse'); - try { - String key = json.decode(response.body)['detail']['public_key']; - key = key.replaceAll('\n', ''); - - Logger.info('Extracting public key was successful.', name: 'token_notifier.dart#_parseRollOutResponse'); - - return _rsaUtils.deserializeRSAPublicKeyPKCS1(key); - } on FormatException catch (e) { - throw FormatException('Response body does not contain RSA public key.', e); - } - } - - Future _handlePushTokensIfExist() async { - Logger.info('Handling push tokens if they exist.', name: 'token_notifier.dart#_handlePushTokensIfExist'); - final pushTokens = state.pushTokens; - if (pushTokens.isEmpty || state.pushTokens.isEmpty) { - if ((await ref.read(settingsProvider.future)).hidePushTokens == true) { - ref.read(settingsProvider.notifier).setHidePushTokens(false); - } - } - if (pushTokens.firstWhereOrNull((element) => element.isRolledOut && element.fbToken == null) != null) { - // If there is a push token without fbToken, then update the fbToken - await updateFirebaseToken(); - } - if (state.hasRolledOutPushTokens) { - checkNotificationPermission(); - } - for (final element in state.pushTokensToRollOut) { - Logger.info('Handling push token "${element.id}"', name: 'token_notifier.dart#_handlePushTokensIfExist'); - await rolloutPushToken(element); - } - } - - Token? getTokenById(String id) { - return state.tokens.firstWhereOrNull((element) => element.id == id); - } - - void _cancelTimers() { - for (final key in _hidingTimers.keys) { - _hidingTimers[key]?.cancel(); - } - _hidingTimers.clear(); - } -} +// import 'dart:async'; +// import 'dart:convert'; +// import 'dart:io'; + +// import 'package:collection/collection.dart'; +// import 'package:firebase_core/firebase_core.dart'; +// import 'package:flutter/foundation.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter/services.dart'; +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:http/http.dart'; +// import 'package:mutex/mutex.dart'; +// import 'package:pointycastle/asymmetric/api.dart'; + +// import '../../../interfaces/repo/token_repository.dart'; +// import '../../../l10n/app_localizations.dart'; +// import '../../../model/enums/push_token_rollout_state.dart'; +// import '../../../model/enums/token_import_type.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/riverpod_states/token_state.dart'; +// import '../../../model/token_container.dart'; +// import '../../../model/tokens/hotp_token.dart'; +// import '../../../model/tokens/otp_token.dart'; +// import '../../../model/tokens/push_token.dart'; +// import '../../../model/tokens/token.dart'; +// import '../../../processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart'; +// import '../../../repo/secure_token_repository.dart'; +// import '../../firebase_utils.dart'; +// import '../../globals.dart'; +// import '../../identifiers.dart'; +// import '../../lock_auth.dart'; +// import '../../logger.dart'; +// import '../../privacyidea_io_client.dart'; +// import '../riverpod_providers/generated_providers/settings_notifier.dart'; +// import '../riverpod_providers/state_providers/status_message_provider.dart'; +// import '../../rsa_utils.dart'; +// import '../../utils.dart'; +// import '../../view_utils.dart'; +// import '../../../views/import_tokens_view/pages/import_plain_tokens_page.dart'; + +// class TokenNotifier extends StateNotifier { +// static final Map _hidingTimers = {}; +// late final Future initState; +// final StateNotifierProviderRef ref; +// final _loadingRepoMutex = Mutex(); +// final _updatingTokensMutex = Mutex(); +// final TokenRepository _repo; +// final RsaUtils _rsaUtils; +// final PrivacyideaIOClient _ioClient; +// final FirebaseUtils _firebaseUtils; + +// TokenNotifier({ +// required this.ref, +// TokenState? initialState, +// TokenRepository? repository, +// RsaUtils? rsaUtils, +// PrivacyideaIOClient? ioClient, +// FirebaseUtils? firebaseUtils, +// }) : _rsaUtils = rsaUtils ?? const RsaUtils(), +// _repo = repository ?? const SecureTokenRepository(), +// _ioClient = ioClient ?? const PrivacyideaIOClient(), +// _firebaseUtils = firebaseUtils ?? FirebaseUtils(), +// super( +// initialState ?? TokenState(tokens: const [], lastlyUpdatedTokens: const []), +// ) { +// _init(initialState); +// } + +// Future _init(TokenState? initialState) async { +// initState = initialState != null ? Future.value(initialState) : _loadFromRepo(); +// await initState; +// await hideLockedTokens(); +// Logger.info('TokenNotifier initialized.', name: 'token_notifier.dart#_init'); +// } + +// /* +// ///////////////////////////////////////////////////////////////////////////// +// /////////////////////// Repository and Token Handling /////////////////////// +// ///////////////////////////////////////////////////////////////////////////// +// /// Repository layer is always use loadingRepoMutex for the latest state +// */ + +// /// Adds a token and returns true if successful, false if not. +// Future _addOrReplaceToken(Token token) async { +// await _loadingRepoMutex.acquire(); +// final success = await _repo.saveOrReplaceToken(token); +// if (!success) { +// Logger.warning( +// 'Saving token failed. Token: ${token.id}', +// name: 'token_notifier.dart#_addOrReplaceToken', +// ); +// _loadingRepoMutex.release(); +// return false; +// } +// state = state.addOrReplaceToken(token); +// _loadingRepoMutex.release(); +// return true; +// } + +// /// Adds a list of tokens and returns the tokens that could not be added or replaced. +// Future> _addOrReplaceTokens(List tokens) async { +// await _loadingRepoMutex.acquire(); +// final failedTokens = await _repo.saveOrReplaceTokens(tokens); +// if (failedTokens.isNotEmpty) { +// Logger.warning( +// 'Saving tokens failed. Failed Tokens: ${failedTokens.length}', +// name: 'token_notifier.dart#_saveOrReplaceTokens', +// ); +// // Every token that is saved should not be in the failedTokens list +// final savedTokens = tokens.where((element) => !failedTokens.contains(element)).toList(); +// state = state.addOrReplaceTokens(savedTokens); +// return failedTokens; +// } +// // [failedTokens] is empty, so every token was saved successfully and we dont need to filter the tokens +// Logger.info('Saved ${tokens.length} Tokens to storage.', name: 'token_notifier.dart#_saveOrReplaceTokens'); +// state = state.addOrReplaceTokens(tokens); +// Logger.debug('New State: ${state.tokens.length} Tokens', name: 'token_notifier.dart#_saveOrReplaceTokens'); +// _loadingRepoMutex.release(); +// return []; +// } + +// /// Replaces a token if it exists and returns true if successful, false if not. +// Future _replaceToken(Token token) async { +// await _loadingRepoMutex.acquire(); +// final (newState, replaced) = state.replaceToken(token); +// if (!replaced) { +// Logger.warning('Tried to replace a token that does not exist.', name: 'token_notifier.dart#_replaceToken'); +// _loadingRepoMutex.release(); +// return false; +// } +// final saved = await _repo.saveOrReplaceToken(token); +// if (!saved) { +// Logger.warning( +// 'Saving token failed. Token: ${token.id}', +// name: 'token_notifier.dart#_replaceToken', +// ); +// _loadingRepoMutex.release(); +// return false; +// } +// state = newState; +// _loadingRepoMutex.release(); +// return true; +// } + +// /// Returns a list of tokens that could not be replaced +// Future> _replaceTokens(List tokens) async { +// await _loadingRepoMutex.acquire(); +// final oldState = state; +// final (newState, failedToReplace) = state.replaceTokens(tokens); +// state = newState; +// for (var e in failedToReplace) { +// tokens.remove(e); +// } +// final failedToSave = await _repo.saveOrReplaceTokens(tokens); +// if (failedToSave.isNotEmpty) { +// Logger.warning( +// 'Saving tokens failed. Failed Tokens: ${failedToSave.length}', +// name: 'token_notifier.dart#_saveOrReplaceTokens', +// ); +// final recovered = oldState.tokens.whereType().where((oldToken) => failedToSave.contains(oldToken)).toList(); +// state = state.addOrReplaceTokens(recovered); +// _loadingRepoMutex.release(); +// return failedToSave; +// } +// _loadingRepoMutex.release(); +// return []; +// } + +// /// Removes a token and returns true if successful, false if not. +// Future _removeToken(Token token) async { +// await _loadingRepoMutex.acquire(); +// state = state.withoutToken(token); + +// final success = await _repo.deleteToken(token); +// if (!success) { +// Logger.warning( +// 'Deleting token failed. Token: ${token.id}', +// name: 'token_notifier.dart#_deleteTokensRepo', +// ); +// state = state.addOrReplaceToken(token); +// _loadingRepoMutex.release(); +// return false; +// } +// _loadingRepoMutex.release(); +// _handlePushTokensIfExist(); +// return true; +// } + +// /// Removes a list of tokens and returns the tokens that could not be removed. +// Future> _removeTokens(List tokens) async { +// Logger.info('Removing ${tokens.length} tokens.', name: 'token_notifier.dart#_removeTokens'); +// await _loadingRepoMutex.acquire(); +// final oldState = state; +// state = state.withoutTokens(tokens); + +// final failedTokens = await _repo.deleteTokens(tokens); +// if (failedTokens.isNotEmpty) { +// Logger.warning( +// 'Deleting tokens failed. Failed Tokens: ${failedTokens.length}', +// name: 'token_notifier.dart#_deleteTokensRepo', +// ); +// final recoveredTokens = oldState.tokens.where((oldToken) => failedTokens.contains(oldToken)).toList(); +// state = state.addOrReplaceTokens(recoveredTokens); +// _loadingRepoMutex.release(); +// return failedTokens; +// } +// _loadingRepoMutex.release(); +// _handlePushTokensIfExist(); +// return []; +// } + +// /// Loads the tokens from the repository sets it as the new state and returns the new state. +// Future _loadFromRepo() async { +// await _loadingRepoMutex.acquire(); +// TokenState newState; +// try { +// List tokens; +// tokens = await _repo.loadTokens(); +// newState = TokenState(tokens: tokens, lastlyUpdatedTokens: tokens); +// state = newState; +// } catch (e) { +// Logger.error( +// 'Loading tokens from storage failed.', +// name: 'token_notifier.dart#_loadFromRepo', +// error: e, +// ); +// _loadingRepoMutex.release(); +// return state; +// } +// _loadingRepoMutex.release(); +// _handlePushTokensIfExist(); +// return newState; +// } + +// Future _saveStateToRepo(TokenState state) async { +// await _loadingRepoMutex.acquire(); +// try { +// await _repo.saveOrReplaceTokens(state.tokens); +// } catch (e) { +// Logger.error( +// 'Saving tokens to storage failed.', +// name: 'token_notifier.dart#_saveStateToRepo', +// error: e, +// ); +// _loadingRepoMutex.release(); +// return false; +// } +// _loadingRepoMutex.release(); +// return true; +// } + +// /* +// ////////////////////////////////////////////////////////////////////////////// +// ///////////////////////// Update Token Methods /////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////// +// /// Updating layer is always use updatingTokensMutex for the latest state +// */ + +// /// Updates a token and returns the updated token if successful, the old token if not and null if the token does not exist. +// Future _updateToken(T token, T Function(T) updater) async { +// await _updatingTokensMutex.acquire(); +// await _loadingRepoMutex.acquire(); +// _loadingRepoMutex.release(); +// final current = state.currentOf(token); +// if (current == null) { +// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#updateToken'); +// _updatingTokensMutex.release(); +// return null; +// } +// final updated = updater(current); +// final replaced = await _replaceToken(updated); +// _updatingTokensMutex.release(); +// return replaced ? updated : current; +// } + +// /// Updates a list of tokens and returns the updated tokens if successful, the old tokens if not and an empty list if the tokens does not exist. +// Future> _updateTokens(List tokens, T Function(T) updater) async { +// if (tokens.isEmpty) return []; +// await _updatingTokensMutex.acquire(); +// final oldState = state; + +// List updatedTokens = []; +// for (final token in tokens) { +// final current = state.currentOf(token) ?? token; +// updatedTokens.add(updater(current)); +// } +// final failed = await _replaceTokens(updatedTokens); +// final recoveredTokens = oldState.tokens.whereType().where((oldToken) => failed.contains(oldToken)).toList(); + +// // Merge the updated tokens with the recovered tokens, so the returned list has the same tokens as the repository. +// final mergedTokens = updatedTokens +// .map((updated) => recoveredTokens.firstWhere( +// (recoveredToken) => recoveredToken == updated, +// orElse: () => updated, +// )) +// .toList(); +// _updatingTokensMutex.release(); +// return mergedTokens; +// } + +// /* +// ////////////////////////////////////////////////////////////////////////////// +// //////////////////////// UI Interaction Methods ////////////////////////////// +// /////// These methods are used to interact with the UI and the user. ///////// +// ////////////////////////////////////////////////////////////////////////////// +// /// There is no need to use mutexes because the updating functions are always using the latest version of the updating tokens. +// */ + +// /// Adds a new token and returns true if successful, false if not. +// Future addNewToken(Token token) async { +// final success = await _addOrReplaceToken(token); +// await _handlePushTokensIfExist(); +// return success; +// } + +// /// Adds or replaces a token and returns true if successful, false if not. +// Future addOrReplaceToken(Token token) => _addOrReplaceToken(token); + +// /// Adds new tokens and returns the tokens that could not be added. +// Future> addTokens(List tokens) async { +// final failedTokens = await _addOrReplaceTokens(tokens); +// await _handlePushTokensIfExist(); +// return failedTokens; +// } + +// /// Adds or replaces a list of tokens and returns the tokens that could not be added or replaced. +// Future> addOrReplaceTokens(List tokens) => _addOrReplaceTokens(tokens); + +// /// Updates a token and returns the updated token if successful, the old token if not and null if the token does not exist. +// Future updateToken(T token, T Function(T) updater) async => _updateToken(token, updater); + +// /// Updates a list of tokens and returns the updated tokens if successful, the old tokens if not and an empty list if the tokens does not exist. +// Future> updateTokens(List tokens, T Function(T) updater) async => _updateTokens(tokens, updater); + +// /// Adds, Updates or Removes tokens based on the [TokenContainer] and returns the updated tokens. +// Future updateContainerTokens(TokenContainer container) async { +// await initState; +// Logger.info('Updating tokens from container.', name: 'token_notifier.dart#updateContainerTokens'); +// final templatesToAdd = []; +// final templatesToUpdate = []; +// final templatesToRemove = []; + +// final knownContainerTokens = state.tokens.fromContainer(container.serial)..addAll(state.maybePiTokens); +// final serverTokenTemplates = container.syncedTokenTemplates; +// Logger.debug( +// 'App knows ${knownContainerTokens.length} of ${serverTokenTemplates.length} server tokens', +// name: 'token_notifier.dart#updateContainerTokens', +// ); +// final appTokenTemplates = knownContainerTokens.toTemplates(); +// Logger.debug('All server templates: ${serverTokenTemplates.join('\n')}', name: 'token_notifier.dart#updateContainerTokens'); +// for (var serverTokenTemplate in serverTokenTemplates) { +// Logger.debug( +// 'Checking server token template: $serverTokenTemplate', +// name: 'token_notifier.dart#updateContainerTokens', +// ); +// // Searches for tokens that are in the container but not in the app to add them. +// // If the token is already in the app, it will be updated. +// // Reduces the [tokenTemplatesApp] list to only the tokens that are in the app but not in the container. These tokens will be removed. +// final appTemplate = appTokenTemplates.firstWhereOrNull( +// (templateApp) => templateApp.isSameTokenAs(serverTokenTemplate), +// ); +// if (appTemplate == null) { +// Logger.debug( +// 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} not found in app. Adding them.', +// name: 'token_notifier.dart#updateContainerTokens', +// ); +// templatesToAdd.add(serverTokenTemplate); +// } else { +// Logger.debug( +// 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app. Checking for update.', +// name: 'token_notifier.dart#updateContainerTokens', +// ); +// appTokenTemplates.remove(appTemplate); +// if (!appTemplate.hasSameValuesAs(serverTokenTemplate)) { +// Logger.debug( +// 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app the Template but is different. Check update.', +// name: 'token_notifier.dart#updateContainerTokens', +// ); +// // Only update the token if the template is different +// templatesToUpdate.add(serverTokenTemplate); +// } else { +// Logger.debug( +// 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app. And is the same. Skipping.', +// name: 'token_notifier.dart#updateContainerTokens', +// ); +// } +// } +// } +// // Removes all tokens that are in the app but not in the container. +// final remainingTokenTemplatesOfContainer = appTokenTemplates.where((template) => template.containerSerial == container.serial); +// templatesToRemove.addAll(remainingTokenTemplatesOfContainer); + +// Logger.debug( +// 'Add(${templatesToAdd.length}) | Check update(${templatesToUpdate.length}) | Remove(${templatesToRemove.length})', +// name: 'token_notifier.dart#updateContainerTokens', +// ); + +// final tokensToAdd = templatesToAdd.map((e) => e.toToken(container)).toList(); +// final tokensToUpdate = []; +// for (var template in templatesToUpdate) { +// final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id || token.serial == template.serial); +// if (token == null) continue; +// final needsUpdate = template.tokenWouldBeUpdated(token); +// if (needsUpdate) { +// tokensToUpdate.add(token); +// } +// } +// final tokensToRemove = []; +// for (var template in templatesToRemove) { +// final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id || token.serial == template.serial); +// if (token == null) continue; +// tokensToRemove.add(token); +// } + +// Logger.debug( +// 'Add(${tokensToAdd.length}) | Update(${tokensToUpdate.length}) | Remove(${tokensToRemove.length})', +// name: 'token_notifier.dart#updateContainerTokens', +// ); + +// if (tokensToAdd.isNotEmpty) { +// await addOrReplaceTokens(tokensToAdd); +// } +// if (tokensToUpdate.isNotEmpty) { +// await updateTokens(tokensToUpdate, (token) { +// final template = templatesToUpdate.firstWhereOrNull((template) => template.id == token.id || template.serial == token.serial); +// if (template == null) { +// Logger.debug( +// 'Not able to update token with id ${token.id} or serial ${token.serial}. No token serial/id matches the templates serial/id.', +// name: 'token_notifier.dart#updateContainerTokens', +// ); +// return token; +// } +// Logger.debug( +// 'Updating token with id:"${token.id}"/serial:"${token.serial}".', +// name: 'token_notifier.dart#updateContainerTokens', +// ); +// return token.copyWithFromTemplate(template); +// }); +// } +// if (tokensToRemove.isNotEmpty) { +// await removeTokens(tokensToRemove); +// } +// } + +// /// Increments the counter of a HOTPToken and returns the updated token if successful, the old token if not and null if the token does not exist. +// Future incrementCounter(HOTPToken token) => _updateToken(token, (p0) => p0.copyWith(counter: token.counter + 1)); + +// /// Hides a token and returns the updated token if successful, the old token if not and null if the token does not exist. +// Future hideToken(T token) => _updateToken(token, (p0) => p0.copyWith(isHidden: true) as T); + +// /// Shows a token and returns the updated token if successful, the old token if not and null if the token does not exist or the user is not authenticated. +// Future showToken(T token) async { +// final authenticated = await lockAuth(localizedReason: AppLocalizations.of(globalNavigatorKey.currentContext!)!.authenticateToShowOtp); +// if (!authenticated) return null; +// final updated = await _updateToken(token, (p0) => p0.copyWith(isHidden: false) as T); +// if (updated?.isHidden == false) { +// _hidingTimers[token.id]?.cancel(); +// _hidingTimers[token.id] = Timer(token.showDuration, () async { +// await hideToken(token); +// }); +// } +// return updated; +// } + +// /// Shows a token and returns the updated token if successful, the old token if not and null if the token does not exist or the user is not authenticated. +// Future showTokenById(String tokenId) { +// final token = getTokenById(tokenId); +// if (token == null) { +// Logger.warning('Tried to show a token that does not exist.', name: 'token_notifier.dart#showTokenById'); +// return Future.value(null); +// } +// if (token is! OTPToken) { +// Logger.warning('Tried to show a token that is not an OTPToken.', name: 'token_notifier.dart#showTokenById'); +// return Future.value(null); +// } +// return showToken(token); +// } + +// Future loadStateFromRepo() async { +// try { +// return await _loadFromRepo(); +// } catch (_) { +// Logger.warning('Loading tokens from storage failed.', name: 'token_notifier.dart#loadStateFromRepo'); +// return null; +// } +// } + +// Future saveStateToRepo() async { +// try { +// await _saveStateToRepo(state); +// Logger.info('Saved ${state.tokens.length} Tokens to storage.', name: 'token_notifier.dart#saveStateToRepo'); +// return true; +// } catch (_) { +// Logger.error('Saving tokens to storage failed.', name: 'token_notifier.dart#saveStateToRepo'); +// return false; +// } +// } + +// /// Minimizing the app needs to cancel all timers and save the state to the repository. +// Future saveStateOnMinimizeApp() async { +// _cancelTimers(); +// await hideLockedTokens(); +// return _saveStateToRepo(state); +// } + +// Future> hideLockedTokens() async { +// final hideLockedTokens = []; +// for (var token in state.tokens) { +// if (token.isLocked && !token.isHidden) { +// hideLockedTokens.add(token); +// } +// } +// return await updateTokens(hideLockedTokens, (p0) => p0.copyWith(isHidden: true)); +// } + +// /// Removes a token from the state and the repository. +// Future removeToken(Token token) async { +// if (token is PushToken) { +// await _removePushToken(token); +// return; +// } +// await _removeToken(token); +// } + +// /// Removes a list of tokens from the state and the repository. +// Future removeTokens(List tokens) async { +// Logger.info('Removing ${tokens.length} tokens.', name: 'token_notifier.dart#removeTokens'); +// final pushTokens = tokens.whereType().toList(); +// final otherTokens = tokens.whereType().toList(); +// await _removeTokens(otherTokens); +// for (var token in pushTokens) { +// await _removePushToken(token); +// } +// } + +// Future _removePushToken(PushToken token) async { +// try { +// await _firebaseUtils.deleteFirebaseToken(); +// } on SocketException { +// Logger.warning('Could not delete firebase token.', name: 'token_notifier.dart#_removePushToken'); +// ref.read(statusMessageProvider.notifier).state = ( +// AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorUnlinkingPushToken(token.label), +// AppLocalizations.of(globalNavigatorKey.currentContext!)!.checkYourNetwork, +// ); +// } + +// _firebaseUtils.getFBToken().then((fbToken) async { +// if (fbToken == null) { +// await _updateTokens(state.pushTokens, (p0) => p0.copyWith(fbToken: null)); +// Logger.warning('Could not update firebase token because no firebase token is available.', name: 'token_notifier.dart#_removePushToken'); +// ref.read(statusMessageProvider.notifier).state = ( +// AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorSynchronizationNoNetworkConnection, +// AppLocalizations.of(globalNavigatorKey.currentContext!)!.pleaseSyncManuallyWhenNetworkIsAvailable, +// ); +// } +// final (notUpdated, _) = (await updateFirebaseToken(fbToken)) ?? ([], []); +// await _updateTokens(notUpdated, (p0) => p0.copyWith(fbToken: null)); +// return; +// }); +// await _removeToken(token); +// Logger.info('Push token "${token.id}" removed successfully.', name: 'token_notifier.dart#_removePushToken'); +// } + +// Future rolloutPushToken(PushToken token) async { +// PushToken? pushToken; +// pushToken = (getTokenById(token.id)) as PushToken?; +// if (pushToken == null) { +// Logger.warning('Tried to rollout a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// return false; +// } + +// assert(pushToken.url != null, 'Token url is null. Cannot rollout token without url.'); +// Logger.info('Rolling out token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); +// if (pushToken.isRolledOut) { +// Logger.info('Ignoring rollout request: Token "${pushToken.id}" already rolled out.', name: 'token_notifier.dart#rolloutPushToken'); +// return true; +// } +// if (pushToken.rolloutState.rollOutInProgress) { +// Logger.info('Ignoring rollout request: Rollout of token "${pushToken.id}" already started. Tokenstate: ${pushToken.rolloutState} ', +// name: 'token_notifier.dart#rolloutPushToken'); +// return false; +// } +// if (pushToken.expirationDate?.isBefore(DateTime.now()) == true) { +// Logger.info('Ignoring rollout request: Token "${pushToken.id}" is expired. ', name: 'token_notifier.dart#rolloutPushToken'); + +// if (globalNavigatorKey.currentContext != null) { +// ref.read(statusMessageProvider.notifier).state = ( +// AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutNotPossibleAnymore, +// AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorTokenExpired(pushToken.label), +// ); +// } +// await _removeToken(pushToken); +// return false; +// } + +// if (pushToken.privateTokenKey == null) { +// Logger.info('Updating rollout state of token "${pushToken.id}" to generatingRSAKeyPair', name: 'token_notifier.dart#rolloutPushToken'); +// pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.generatingRSAKeyPair)); +// if (pushToken == null) { +// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// return false; +// } +// Logger.info('Updated token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); +// try { +// final keyPair = await _rsaUtils.generateRSAKeyPair(); +// pushToken = pushToken.withPrivateTokenKey(keyPair.privateKey); +// pushToken = pushToken.withPublicTokenKey(keyPair.publicKey); +// pushToken = await _updateToken(pushToken, (p0) { +// p0 = p0.withPrivateTokenKey(keyPair.privateKey); +// return p0.withPublicTokenKey(keyPair.publicKey); +// }) ?? +// pushToken; +// Logger.info('Updated token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); +// } catch (e, s) { +// Logger.error('Error while generating RSA key pair.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); +// if (pushToken == null) { +// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// return false; +// } +// pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.generatingRSAKeyPairFailed)); +// return false; +// } +// } + +// pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKey)); +// if (pushToken == null) { +// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// return false; +// } +// if (!kIsWeb && Platform.isIOS) { +// Logger.warning('Triggering network access permission for token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); +// if (await _ioClient.triggerNetworkAccessPermission(url: pushToken.url!, sslVerify: pushToken.sslVerify) == false) { +// Logger.warning('Network access permission for token "${pushToken.id}" failed.', name: 'token_notifier.dart#rolloutPushToken'); +// _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKeyFailed)); +// return false; +// } +// Logger.warning('Network access permission for token "${pushToken.id}" successful.', name: 'token_notifier.dart#rolloutPushToken'); +// } +// try { +// // TODO What to do with poll only tokens if google-services is used? + +// Logger.warning('SSLVerify: ${pushToken.sslVerify}', name: 'token_notifier.dart#rolloutPushToken'); +// final fbToken = await _firebaseUtils.getFBToken(); +// Response response = await _ioClient.doPost( +// sslVerify: pushToken.sslVerify, +// url: pushToken.url!, +// body: { +// 'enrollment_credential': pushToken.enrollmentCredentials, +// 'serial': pushToken.serial, +// 'fbtoken': fbToken, +// 'pubkey': _rsaUtils.serializeRSAPublicKeyPKCS8(pushToken.rsaPublicTokenKey!), +// }, +// ); + +// if (response.statusCode == 200) { +// pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.parsingResponse, fbToken: fbToken)); +// if (pushToken == null) { +// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// return false; +// } +// try { +// RSAPublicKey publicServerKey = await _parseRollOutResponse(response); +// pushToken = await _updateToken(pushToken, (p0) => p0.withPublicServerKey(publicServerKey)); +// if (pushToken == null) { +// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// return false; +// } +// } on FormatException catch (e, s) { +// showMessage(message: "Couldn't parsing RSA public key: ${e.message}", duration: const Duration(seconds: 3)); + +// Logger.warning('Error while parsing RSA public key.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); +// if (pushToken == null) { +// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// return false; +// } +// pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.parsingResponseFailed)); +// return false; +// } +// Logger.info('Roll out successful', name: 'token_notifier.dart#rolloutPushToken'); +// pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(isRolledOut: true, rolloutState: PushTokenRollOutState.rolloutComplete)); +// checkNotificationPermission(); + +// return true; +// } else { +// Logger.warning('Post request on roll out failed.', +// name: 'token_notifier.dart#rolloutPushToken', +// error: 'Token: ${pushToken.serial}\nStatus code: ${response.statusCode},\nURL:${response.request?.url}\nBody: ${response.body}'); + +// try { +// final message = response.body.isNotEmpty ? (json.decode(response.body)['result']?['error']?['message']) : ''; +// ref.read(statusMessageProvider.notifier).state = ( +// AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutFailed(pushToken.label), +// message, +// ); +// } on FormatException { +// // Format Exception is thrown if the response body is not a valid json. This happens if the server is not reachable. + +// ref.read(statusMessageProvider.notifier).state = ( +// AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutFailed(pushToken.label), +// AppLocalizations.of(globalNavigatorKey.currentContext!)!.statusCode(response.statusCode) +// ); +// } + +// pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKeyFailed)); +// return false; +// } +// } catch (e, s) { +// if (pushToken == null) { +// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// return false; +// } +// pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKeyFailed)); +// if (pushToken == null) { +// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// return false; +// } +// if (e is PlatformException && e.code == FIREBASE_TOKEN_ERROR_CODE || e is SocketException || e is TimeoutException || e is FirebaseException) { +// Logger.warning('Connection error: Roll out push token failed.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); +// showMessage( +// message: AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutNoConnectionToServer(pushToken.label), +// duration: const Duration(seconds: 3), +// ); +// } else if (e is HandshakeException) { +// Logger.warning('SSL error: Roll out push token failed.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); +// showMessage( +// message: AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutSSLHandshakeFailed, +// duration: const Duration(seconds: 3), +// ); +// } else { +// if (globalNavigatorKey.currentContext != null) { +// showMessage( +// message: AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutUnknownError(e), +// duration: const Duration(seconds: 3), +// ); +// } +// Logger.error('Roll out push token failed.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); +// } +// return false; +// } +// } + +// /// This method attempts to update the fbToken for all PushTokens that can be +// /// updated. I.e. all tokens that know the url of their respective privacyIDEA +// /// server. +// /// If the fbToken is not provided, it will be fetched from the firebase instance. +// /// If the fbToken is not available, this method will return null. +// /// Returns a tuple of two lists. The first list contains all tokens that +// /// could not be updated. The second list contains all tokens that do not +// /// support updating the fbToken. +// /// +// /// This should only be used to attempt to update the fbToken automatically, +// /// as this can not be guaranteed to work. There is a manual option available +// /// through the settings also. +// Future<(List, List)?> updateFirebaseToken([String? firebaseToken]) async { +// Logger.info('Updating firebase token for all push tokens.', name: 'push_provider.dart#updateFirebaseToken'); +// firebaseToken ??= await _firebaseUtils.getFBToken(); +// if (firebaseToken == null) { +// Logger.warning('Could not update firebase token because no firebase token is available.', name: 'push_provider.dart#updateFirebaseToken'); +// return null; +// } +// List tokenList = state.pushTokens.where((t) => t.isRolledOut && t.fbToken != firebaseToken).toList(); +// Logger.info('Updating firebase token for ${tokenList.length} push tokens.', name: 'push_provider.dart#updateFirebaseToken'); +// bool allUpdated = true; +// final List failedTokens = []; +// final List unsuportedTokens = []; + +// for (PushToken p in tokenList) { +// if (p.url == null) { +// unsuportedTokens.add(p); +// continue; +// } +// // POST /ttype/push HTTP/1.1 +// //Host: example.com +// // +// //new_fb_token= +// //serial=element +// //timestamp= +// //signature=SIGNATURE(||) +// Logger.warning('Updating firebase token for push token "${p.serial}"', name: 'push_provider.dart#updateFirebaseToken'); +// String timestamp = DateTime.now().toUtc().toIso8601String(); +// String message = '$firebaseToken|${p.serial}|$timestamp'; +// String? signature = await _rsaUtils.trySignWithToken(p, message); +// if (signature == null) { +// failedTokens.add(p); +// allUpdated = false; +// continue; +// } +// Response response = await _ioClient.doPost( +// url: p.url!, +// body: {'new_fb_token': firebaseToken, 'serial': p.serial, 'timestamp': timestamp, 'signature': signature}, +// sslVerify: p.sslVerify, +// ); +// if (response.statusCode == 200) { +// Logger.info('Updating firebase token for push token succeeded!', name: 'push_provider.dart#updateFirebaseToken'); +// _updateToken(p, (p0) => p0.copyWith(fbToken: firebaseToken)); +// } else { +// Logger.warning('Updating firebase token for push token failed!', name: 'push_provider.dart#updateFirebaseToken'); +// failedTokens.add(p); +// allUpdated = false; +// } +// } + +// if (allUpdated) { +// await _firebaseUtils.setCurrentFirebaseToken(firebaseToken); +// } +// return (failedTokens, unsuportedTokens); +// } + +// /* //////////////////////////////////////////////////////////////////////////// +// ///////////////////////// Add New Tokens Methods ////////////////////////////// +// /////////////////////////////////////////////////////////////////////////////// +// /// Does not need to wait for updating functions because they doesn't depend on any state */ + +// /// The return value of a qrCode could be any object. In this case should be a String that is a valid URI. +// /// If it is not a valid URI, the user will be informed. +// Future handleQrCode(Object? qrCode) async { +// Uri uri; +// try { +// qrCode as String; +// uri = Uri.parse(qrCode); +// } catch (_) { +// showMessage(message: 'The scanned QR code is not a valid URI.', duration: const Duration(seconds: 3)); +// Logger.warning('Scanned Data: $qrCode', error: 'Scanned QR code is not a valid URI.', name: 'token_notifier.dart#handleQrCode'); +// return; +// } +// List tokens = await _tokensFromUri(uri); +// tokens = tokens +// .map( +// (e) => e.copyWith( +// origin: e.origin?.copyWith(source: TokenOriginSourceType.qrScan) ?? +// TokenOriginSourceType.qrScan.toTokenOrigin(data: uri.toString(), isPrivacyIdeaToken: null), +// ), +// ) +// .toList(); + +// await _addOrReplaceTokens(tokens); +// await _handlePushTokensIfExist(); +// } + +// Future handleLink(Uri uri) async { +// List tokens = await _tokensFromUri(uri); +// tokens = tokens +// .map((e) => e.copyWith( +// origin: e.origin?.copyWith(source: TokenOriginSourceType.link) ?? +// TokenOriginSourceType.link.toTokenOrigin(data: uri.toString(), isPrivacyIdeaToken: null))) +// .toList(); +// await _addOrReplaceTokens(tokens); +// await _handlePushTokensIfExist(); +// } + +// Future> _tokensFromUri(Uri uri) async { +// if (!TokenImportSchemeProcessor.allSupportedSchemes.contains(uri.scheme)) { +// return Future.value([]); +// } +// try { +// final results = await TokenImportSchemeProcessor.processUriByAny(uri); +// if (results == null || results.isEmpty) { +// showMessage(message: 'The scanned QR code is not a valid URI.', duration: const Duration(seconds: 3)); +// Logger.warning('Scanned Data: $uri', error: 'Scanned QR code is not a valid URI.', name: 'token_notifier.dart#handleQrCode'); +// return []; +// } +// final failedResults = results.whereType().toList(); +// for (var failedResult in failedResults) { +// ref.read(statusMessageProvider.notifier).state = (AppLocalizations.of((await globalContext))!.malformedData, failedResult.message); +// } +// final successResults = results.whereType>().toList(); +// if (successResults.isEmpty) { +// return []; +// } +// if (successResults.length > 1 || state.tokens.any((e) => successResults.first.resultData.isSameTokenAs(e) == true)) { +// Navigator.of(globalNavigatorKey.currentContext!).popUntil((route) => route.isFirst); +// final tokensToKeep = await Navigator.of(globalNavigatorKey.currentContext!).push>( +// MaterialPageRoute>( +// builder: (context) => ImportPlainTokensPage( +// titleName: AppLocalizations.of(context)!.importTokens, +// processorResults: results, +// selectedType: TokenImportType.qrScan, +// ), +// ), +// ); +// return tokensToKeep ?? []; +// } +// return successResults.map((e) => e.resultData).toList(); +// } catch (error, stackTrace) { +// Logger.error('Error while processing QR code.', name: 'token_notifier.dart#handleQrCode', error: error, stackTrace: stackTrace); +// return []; +// } +// } + +// /* ///////////////////////////////////////////////////////////////////////////// +// /////////////////////////// Helper Methods ///////////////////////////////////// +// ///////////////////////////////////////////////////////////////////////////// */ + +// Future _parseRollOutResponse(Response response) async { +// Logger.info('Parsing rollout response, try to extract public_key.', name: 'token_notifier.dart#_parseRollOutResponse'); +// try { +// String key = json.decode(response.body)['detail']['public_key']; +// key = key.replaceAll('\n', ''); + +// Logger.info('Extracting public key was successful.', name: 'token_notifier.dart#_parseRollOutResponse'); + +// return _rsaUtils.deserializeRSAPublicKeyPKCS1(key); +// } on FormatException catch (e) { +// throw FormatException('Response body does not contain RSA public key.', e); +// } +// } + +// Future _handlePushTokensIfExist() async { +// Logger.info('Handling push tokens if they exist.', name: 'token_notifier.dart#_handlePushTokensIfExist'); +// final pushTokens = state.pushTokens; +// if (pushTokens.isEmpty || state.pushTokens.isEmpty) { +// if ((await ref.read(settingsProvider.future)).hidePushTokens == true) { +// ref.read(settingsProvider.notifier).setHidePushTokens(false); +// } +// } +// if (pushTokens.firstWhereOrNull((element) => element.isRolledOut && element.fbToken == null) != null) { +// // If there is a push token without fbToken, then update the fbToken +// await updateFirebaseToken(); +// } +// if (state.hasRolledOutPushTokens) { +// checkNotificationPermission(); +// } +// for (final element in state.pushTokensToRollOut) { +// Logger.info('Handling push token "${element.id}"', name: 'token_notifier.dart#_handlePushTokensIfExist'); +// await rolloutPushToken(element); +// } +// } + +// Token? getTokenById(String id) { +// return state.tokens.firstWhereOrNull((element) => element.id == id); +// } + +// void _cancelTimers() { +// for (final key in _hidingTimers.keys) { +// _hidingTimers[key]?.cancel(); +// } +// _hidingTimers.clear(); +// } +// } diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index d591a7a2a..8883b2ff9 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -36,8 +36,8 @@ import '../model/mixins/sortable_mixin.dart'; import '../model/token_folder.dart'; import '../model/tokens/token.dart'; import 'customization/application_customization.dart' show ApplicationCustomization; -import 'riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import 'riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import 'riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; +import 'riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import 'riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; /// Inserts [char] at the position [pos] in the given String ([str]), 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 866220213..bdaf397f8 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 @@ -33,7 +33,7 @@ import '../../model/extensions/enums/token_origin_source_type.dart'; import '../../model/tokens/token.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import 'add_token_manually_view_widgets/labeled_dropdown_button.dart'; class AddTokenManuallyView extends ConsumerStatefulWidget { diff --git a/lib/views/import_tokens_view/import_tokens_view.dart b/lib/views/import_tokens_view/import_tokens_view.dart index f92639144..bc073b843 100644 --- a/lib/views/import_tokens_view/import_tokens_view.dart +++ b/lib/views/import_tokens_view/import_tokens_view.dart @@ -23,7 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../l10n/app_localizations.dart'; import '../../model/token_import/token_import_origin.dart'; import '../../model/tokens/token.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../utils/token_import_origins.dart'; import '../view_interface.dart'; import 'pages/import_start_page.dart'; 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 e5c6f99ca..e9256a404 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 @@ -26,7 +26,7 @@ import '../../../model/extensions/enums/token_import_type_extension.dart'; import '../../../model/processor_result.dart'; import '../../../model/token_import/token_import_entry.dart'; import '../../../model/tokens/token.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../import_tokens_view.dart'; import '../widgets/conflicted_import_tokens_list.dart'; import '../widgets/failed_imports_list.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 e4ee4a5fc..d6aa1c780 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 @@ -23,8 +23,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../utils/customization/extended_text_theme.dart'; import '../../utils/home_widget_utils.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../utils/utils.dart'; import '../view_interface.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 7b5dacf25..96d489594 100644 --- a/lib/views/main_view/main_view_widgets/connectivity_listener.dart +++ b/lib/views/main_view/main_view_widgets/connectivity_listener.dart @@ -23,7 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; import '../../../utils/logger.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import '../../../utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart'; diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart b/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart index 900fd7a05..e53ed3c37 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/add_token_folder_dialog.dart @@ -23,7 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../model/enums/introduction.dart'; import '../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; import '../../../../widgets/dialog_widgets/default_dialog.dart'; class AddTokenFolderDialog extends ConsumerWidget { 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 1b5f79974..74ed01add 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 @@ -25,8 +25,8 @@ import '../../../../../model/token_folder.dart'; import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; class DeleteTokenFolderAction extends StatelessWidget { 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 80e6325f9..b8c12dbe1 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 @@ -25,7 +25,7 @@ import '../../../../../model/token_folder.dart'; import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; class LockTokenFolderAction extends StatelessWidget { final TokenFolder folder; 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 2aeb626cb..a76d9ae7e 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 @@ -26,7 +26,7 @@ import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; class RenameTokenFolderAction extends StatelessWidget { 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 8aeb22aac..aa9a4ea0e 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 @@ -35,8 +35,8 @@ import '../../../../utils/customization/action_theme.dart'; import '../../../../utils/globals.dart'; import '../../../../utils/lock_auth.dart'; import '../../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; +import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../../utils/utils.dart'; import '../../../../widgets/custom_trailing.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart index 3d963f546..6c72becd3 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart @@ -23,7 +23,7 @@ import 'package:permission_handler/permission_handler.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../utils/globals.dart'; -import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../../utils/view_utils.dart'; import '../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../qr_scanner_view/qr_scanner_view.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart index d89818a53..ed4c76819 100644 --- a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart +++ b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart @@ -25,8 +25,8 @@ import '../../../model/riverpod_states/token_filter.dart'; import '../../../model/token_folder.dart'; import '../../../model/tokens/token.dart'; import '../../../utils/logger.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart'; import 'folder_widgets/token_folder_expandable.dart'; import 'token_widgets/token_widget_builder.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart index 49df7c308..43dd2f0b2 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart @@ -29,7 +29,7 @@ import '../../../../../model/enums/day_password_token_view_mode.dart'; import '../../../../../model/riverpod_states/settings_state.dart'; import '../../../../../model/tokens/day_password_token.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../../../utils/utils.dart'; import '../../../../../widgets/custom_texts.dart'; import '../../../../../widgets/custom_trailing.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 31d690e03..0a40f2766 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 @@ -25,7 +25,7 @@ import '../../../../../model/tokens/token.dart'; import '../../../../../utils/customization/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../loading_indicator.dart'; import '../token_action.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 69c3b2c9d..2fdc4027a 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 @@ -29,7 +29,7 @@ import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../../../widgets/focused_item_as_overlay.dart'; import '../token_action.dart'; import 'default_edit_action_dialog.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart index c1e1a4532..156cf6317 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart @@ -26,7 +26,7 @@ import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/logger.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; class DefaultEditActionDialog extends ConsumerStatefulWidget { 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 fc3726335..1b8bccf64 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 @@ -29,7 +29,7 @@ import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../../../widgets/focused_item_as_overlay.dart'; import '../token_action.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart index 163ec6435..5c71591f5 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart @@ -24,7 +24,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/hotp_token.dart'; import '../../../../../utils/globals.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../../../utils/utils.dart'; import '../../../../../widgets/custom_texts.dart'; import '../../../../../widgets/custom_trailing.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 597af44bb..8bdbac16b 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 @@ -28,7 +28,7 @@ import '../../../../../../utils/customization/action_theme.dart'; import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../../../../widgets/enable_text_edit_after_many_taps.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../default_token_actions/default_edit_action_dialog.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 e413eeffe..fa059c015 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 @@ -24,7 +24,7 @@ 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/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../../widgets/press_button.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart index 027fe28e4..e16729405 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart @@ -24,7 +24,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/totp_token.dart'; import '../../../../../utils/globals.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../../../utils/utils.dart'; import '../../../../../widgets/custom_texts.dart'; import '../../../../../widgets/custom_trailing.dart'; diff --git a/lib/views/push_token_view/widgets/push_tokens_view_list.dart b/lib/views/push_token_view/widgets/push_tokens_view_list.dart index e4de742d4..27b931378 100644 --- a/lib/views/push_token_view/widgets/push_tokens_view_list.dart +++ b/lib/views/push_token_view/widgets/push_tokens_view_list.dart @@ -22,7 +22,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../model/mixins/sortable_mixin.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../widgets/default_refresh_indicator.dart'; import '../../../widgets/drag_item_scroller.dart'; diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart index 0386ebba1..3a004d23d 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart @@ -23,7 +23,7 @@ import '../../../../../model/riverpod_states/token_state.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../main_view/main_view_widgets/token_widgets/token_widget_builder.dart'; 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 ece20c1cf..9afbd079c 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 @@ -24,7 +24,7 @@ import '../../../l10n/app_localizations.dart'; import '../../../model/riverpod_states/settings_state.dart'; import '../../../model/tokens/push_token.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../settings_view_widgets/settings_groups.dart'; import '../settings_view_widgets/update_firebase_token_dialog.dart'; diff --git a/lib/views/settings_view/settings_view.dart b/lib/views/settings_view/settings_view.dart index 49e87d623..74fe18c2f 100644 --- a/lib/views/settings_view/settings_view.dart +++ b/lib/views/settings_view/settings_view.dart @@ -22,7 +22,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../l10n/app_localizations.dart'; import '../../model/tokens/push_token.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../widgets/push_request_listener.dart'; import '../view_interface.dart'; import 'settings_groups/settings_group_error_log.dart'; diff --git a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart index cb0f38774..f1a434ccf 100644 --- a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart +++ b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart @@ -25,7 +25,7 @@ import '../../../l10n/app_localizations.dart'; import '../../../model/tokens/push_token.dart'; import '../../../utils/globals.dart'; import '../../../utils/logger.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../../utils/view_utils.dart'; import '../../../widgets/dialog_widgets/default_dialog.dart'; diff --git a/lib/views/splash_screen/splash_screen.dart b/lib/views/splash_screen/splash_screen.dart index a81be3f3e..05eeffc0b 100644 --- a/lib/views/splash_screen/splash_screen.dart +++ b/lib/views/splash_screen/splash_screen.dart @@ -26,7 +26,7 @@ import '../../utils/customization/application_customization.dart'; import '../../utils/home_widget_utils.dart'; import '../../utils/logger.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../main_view/main_view.dart'; import '../view_interface.dart'; diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index 6bedb0621..761b95a11 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -14,8 +14,8 @@ import '../utils/riverpod/riverpod_providers/generated_providers/credential_noti import '../utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../utils/riverpod/state_listeners/home_widget_deep_link_listener.dart'; import '../utils/riverpod/state_listeners/home_widget_token_state_listener.dart'; import '../utils/riverpod/state_listeners/navigation_deep_link_listener.dart'; @@ -91,9 +91,10 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { Logger.debug('Credentials: $credentials', name: 'AppWrapper#build'); return SingleTouchRecognizer( child: StateObserver( - stateNotifierProviderListeners: [ - HomeWidgetTokenStateListener(tokenProvider: tokenProvider), - ContainerListensToTokenState(tokenProvider: tokenProvider, ref: ref), + stateNotifierProviderListeners: [], + buildlessProviderListener: [ + HomeWidgetTokenStateListener(provider: tokenProvider), + ContainerListensToTokenState(provider: tokenProvider, ref: ref), ], streamNotifierProviderListeners: [ NavigationDeepLinkListener(deeplinkProvider: deeplinkNotifierProvider), diff --git a/lib/widgets/app_wrappers/state_observer.dart b/lib/widgets/app_wrappers/state_observer.dart index 57a5e5365..de75d5512 100644 --- a/lib/widgets/app_wrappers/state_observer.dart +++ b/lib/widgets/app_wrappers/state_observer.dart @@ -19,6 +19,7 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../interfaces/riverpod/buildless_listener.dart'; import '../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart'; import '../../interfaces/riverpod/state_listeners/notifier_provider_listener.dart'; @@ -26,6 +27,7 @@ import '../../interfaces/riverpod/state_listeners/state_notifier_provider_listen class StateObserver extends ConsumerWidget { final List stateNotifierProviderListeners; + final List buildlessProviderListener; final List asyncNotifierProviderListeners; final List streamNotifierProviderListeners; final Widget child; @@ -33,6 +35,7 @@ class StateObserver extends ConsumerWidget { const StateObserver({ super.key, this.asyncNotifierProviderListeners = const [], + this.buildlessProviderListener = const [], this.stateNotifierProviderListeners = const [], this.streamNotifierProviderListeners = const [], required this.child, @@ -43,6 +46,9 @@ class StateObserver extends ConsumerWidget { for (final listener in stateNotifierProviderListeners) { listener.buildListen(ref); } + for (final listener in buildlessProviderListener) { + listener.buildListen(ref); + } for (final listener in asyncNotifierProviderListeners) { listener.buildListen(ref); } diff --git a/lib/widgets/dialog_widgets/push_request_dialog.dart b/lib/widgets/dialog_widgets/push_request_dialog.dart index 83993b7b8..e212d3c68 100644 --- a/lib/widgets/dialog_widgets/push_request_dialog.dart +++ b/lib/widgets/dialog_widgets/push_request_dialog.dart @@ -11,7 +11,7 @@ import '../../utils/globals.dart'; import '../../utils/lock_auth.dart'; import '../../utils/logger.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../press_button.dart'; import 'default_dialog.dart'; diff --git a/lib/widgets/hideable_widget_.dart b/lib/widgets/hideable_widget_.dart index 05966cf36..5bc05b080 100644 --- a/lib/widgets/hideable_widget_.dart +++ b/lib/widgets/hideable_widget_.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../l10n/app_localizations.dart'; import '../model/tokens/otp_token.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; class HideableWidget extends ConsumerWidget { final OTPToken token; diff --git a/test/unit_test/state_notifiers/sortable_notifier_test.dart b/test/unit_test/state_notifiers/sortable_notifier_test.dart index 3460f4980..47e6ce7f1 100644 --- a/test/unit_test/state_notifiers/sortable_notifier_test.dart +++ b/test/unit_test/state_notifiers/sortable_notifier_test.dart @@ -9,10 +9,9 @@ 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/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import '../../tests_app_wrapper.mocks.dart'; @@ -58,7 +57,7 @@ void _testSortableNotifier() { final container = ProviderContainer(overrides: [ settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo)), tokenFolderProvider.overrideWith(() => TokenFolderNotifier(repoOverride: mockTokenFolderRepository)), - tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), + tokenProvider.overrideWith(() => TokenNotifier(repoOverride: mockTokenRepository)), ]); final newToken = TOTPToken(period: 30, id: 'Token 4', algorithm: Algorithms.SHA1, digits: 6, secret: 'secret4', folderId: 1, sortIndex: 1); diff --git a/test/unit_test/state_notifiers/token_folder_notifier_test.dart b/test/unit_test/state_notifiers/token_folder_notifier_test.dart index 3e09f7051..727ce7a0f 100644 --- a/test/unit_test/state_notifiers/token_folder_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_folder_notifier_test.dart @@ -3,7 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart'; import 'package:privacyidea_authenticator/model/token_folder.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_folder_provider.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; import '../../tests_app_wrapper.mocks.dart'; diff --git a/test/unit_test/state_notifiers/token_notifier_test.dart b/test/unit_test/state_notifiers/token_notifier_test.dart index fd9ec01b4..d24f30ead 100644 --- a/test/unit_test/state_notifiers/token_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_notifier_test.dart @@ -8,11 +8,11 @@ import 'package:privacyidea_authenticator/model/enums/push_token_rollout_state.d import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; -import 'package:privacyidea_authenticator/model/riverpod_states/token_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; +import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; @@ -41,8 +41,11 @@ void _testTokenNotifier() { when(mockRepo.loadTokens()).thenAnswer((_) async => responses.removeAt(0)); when(mockRepo.saveOrReplaceTokens(any)).thenAnswer((_) async => []); when(mockFirebaseUtils.getFBToken()).thenAnswer((_) async => 'mockFbToken'); - final testProvider = StateNotifierProvider( - (ref) => TokenNotifier(ref: ref, repository: mockRepo, firebaseUtils: mockFirebaseUtils), + final testProvider = tokenNotifierProviderOf( + repo: mockRepo, + rsaUtils: const RsaUtils(), + ioClient: const PrivacyideaIOClient(), + firebaseUtils: mockFirebaseUtils, ); final notifier = container.read(testProvider.notifier); expect((await notifier.loadStateFromRepo())?.tokens, after); @@ -63,8 +66,11 @@ void _testTokenNotifier() { when(mockRepo.loadTokens()).thenAnswer((_) async => before); when(mockRepo.saveOrReplaceTokens(any)).thenAnswer((_) async => []); when(mockFirebaseUtils.getFBToken()).thenAnswer((_) async => 'mockFbToken'); - final testProvider = StateNotifierProvider( - (ref) => TokenNotifier(ref: ref, repository: mockRepo, firebaseUtils: mockFirebaseUtils), + final testProvider = tokenNotifierProviderOf( + repo: mockRepo, + rsaUtils: const RsaUtils(), + ioClient: const PrivacyideaIOClient(), + firebaseUtils: mockFirebaseUtils, ); final notifier = container.read(testProvider.notifier); await notifier.initState; @@ -89,8 +95,11 @@ void _testTokenNotifier() { when(mockRepo.saveOrReplaceToken(after.first)).thenAnswer((_) async => true); when(mockRepo.saveOrReplaceTokens(any)).thenAnswer((_) async => []); when(mockFirebaseUtils.getFBToken()).thenAnswer((_) async => 'mockFbToken'); - final testProvider = StateNotifierProvider( - (ref) => TokenNotifier(ref: ref, repository: mockRepo, firebaseUtils: mockFirebaseUtils), + final testProvider = tokenNotifierProviderOf( + repo: mockRepo, + rsaUtils: const RsaUtils(), + ioClient: const PrivacyideaIOClient(), + firebaseUtils: mockFirebaseUtils, ); final notifier = container.read(testProvider.notifier); final initState = await notifier.initState; @@ -118,8 +127,11 @@ void _testTokenNotifier() { when(mockRepo.deleteToken(before.last)).thenAnswer((_) async => true); when(mockRepo.saveOrReplaceTokens(any)).thenAnswer((_) async => []); when(mockFirebaseUtils.getFBToken()).thenAnswer((_) async => 'mockFbToken'); - final testProvider = StateNotifierProvider( - (ref) => TokenNotifier(ref: ref, repository: mockRepo, firebaseUtils: mockFirebaseUtils), + final testProvider = tokenNotifierProviderOf( + repo: mockRepo, + rsaUtils: const RsaUtils(), + ioClient: const PrivacyideaIOClient(), + firebaseUtils: mockFirebaseUtils, ); final notifier = container.read(testProvider.notifier); final initState = await notifier.initState; @@ -148,8 +160,11 @@ void _testTokenNotifier() { when(mockRepo.saveOrReplaceToken(after.last)).thenAnswer((_) async => true); when(mockRepo.saveOrReplaceTokens(any)).thenAnswer((_) async => []); when(mockFirebaseUtils.getFBToken()).thenAnswer((_) async => 'mockFbToken'); - final testProvider = StateNotifierProvider( - (ref) => TokenNotifier(ref: ref, repository: mockRepo, firebaseUtils: mockFirebaseUtils), + final testProvider = tokenNotifierProviderOf( + repo: mockRepo, + rsaUtils: const RsaUtils(), + ioClient: const PrivacyideaIOClient(), + firebaseUtils: mockFirebaseUtils, ); final notifier = container.read(testProvider.notifier); final initState = await notifier.initState; @@ -178,8 +193,11 @@ void _testTokenNotifier() { when(mockRepo.saveOrReplaceToken(after.last)).thenAnswer((_) async => true); when(mockRepo.saveOrReplaceTokens(any)).thenAnswer((_) async => []); when(mockFirebaseUtils.getFBToken()).thenAnswer((_) async => 'mockFbToken'); - final testProvider = StateNotifierProvider( - (ref) => TokenNotifier(ref: ref, repository: mockRepo, firebaseUtils: mockFirebaseUtils), + final testProvider = tokenNotifierProviderOf( + repo: mockRepo, + rsaUtils: const RsaUtils(), + ioClient: const PrivacyideaIOClient(), + firebaseUtils: mockFirebaseUtils, ); final notifier = container.read(testProvider.notifier); final initState = await notifier.initState; @@ -209,8 +227,11 @@ void _testTokenNotifier() { when(mockRepo.saveOrReplaceTokens(any)).thenAnswer((_) async => []); when(mockRepo.saveOrReplaceTokens([...after])).thenAnswer((_) async => []); when(mockFirebaseUtils.getFBToken()).thenAnswer((_) async => 'mockFbToken'); - final testProvider = StateNotifierProvider( - (ref) => TokenNotifier(ref: ref, repository: mockRepo, firebaseUtils: mockFirebaseUtils), + final testProvider = tokenNotifierProviderOf( + repo: mockRepo, + rsaUtils: const RsaUtils(), + ioClient: const PrivacyideaIOClient(), + firebaseUtils: mockFirebaseUtils, ); final notifier = container.read(testProvider.notifier); await notifier.addOrReplaceTokens([...after]); @@ -233,8 +254,11 @@ void _testTokenNotifier() { ]; when(mockRepo.loadTokens()).thenAnswer((_) async => before); when(mockRepo.saveOrReplaceTokens(any)).thenAnswer((_) async => []); - final testProvider = StateNotifierProvider( - (ref) => TokenNotifier(ref: ref, repository: mockRepo, firebaseUtils: mockFirebaseUtils), + final testProvider = tokenNotifierProviderOf( + repo: mockRepo, + rsaUtils: const RsaUtils(), + ioClient: const PrivacyideaIOClient(), + firebaseUtils: mockFirebaseUtils, ); final notifier = container.read(testProvider.notifier); await notifier.handleQrCode('otpauth://totp/issuer2:label2?secret=secret2&issuer=issuer2&algorithm=SHA256&digits=6&period=30'); @@ -315,13 +339,12 @@ void _testTokenNotifier() { ), ), ); - final testProvider = StateNotifierProvider((ref) => TokenNotifier( - ref: ref, - repository: mockTokenRepo, - rsaUtils: mockRsaUtils, - ioClient: mockIOClient, - firebaseUtils: mockFirebaseUtils, - )); + final testProvider = tokenNotifierProviderOf( + repo: mockTokenRepo, + ioClient: mockIOClient, + rsaUtils: mockRsaUtils, + firebaseUtils: mockFirebaseUtils, + ); final notifier = container.read(testProvider.notifier); final initState = await notifier.initState; expect(initState.tokens, before); @@ -381,14 +404,11 @@ void _testTokenNotifier() { body: anyNamed('body'), sslVerify: anyNamed('sslVerify'), )).thenAnswer((_) => Future.value(Response('{"detail": {"public_key": "publicKey"}}', 200))); - final testProvider = StateNotifierProvider( - (ref) => TokenNotifier( - ref: ref, - repository: mockRepo, - rsaUtils: mockRsaUtils, - ioClient: mockIOClient, - firebaseUtils: mockFirebaseUtils, - ), + final testProvider = tokenNotifierProviderOf( + repo: mockRepo, + ioClient: mockIOClient, + rsaUtils: mockRsaUtils, + firebaseUtils: mockFirebaseUtils, ); final notifier = container.read(testProvider.notifier); final initState = await notifier.initState; @@ -420,7 +440,12 @@ void _testTokenNotifier() { when(mockRepo.loadTokens()).thenAnswer((_) => Future.value(before)); when(mockRepo.saveOrReplaceTokens(any)).thenAnswer((_) async => []); when(mockFirebaseUtils.getFBToken()).thenAnswer((_) async => 'mockFbToken'); - final testProvider = StateNotifierProvider((ref) => TokenNotifier(ref: ref, repository: mockRepo)); + final testProvider = tokenNotifierProviderOf( + repo: mockRepo, + rsaUtils: const RsaUtils(), + ioClient: const PrivacyideaIOClient(), + firebaseUtils: mockFirebaseUtils, + ); final notifier = container.read(testProvider.notifier); Logger.info('before loadFromRepo'); final newState = await notifier.loadStateFromRepo(); From 059dab2a45bb678024f23ad4a5146da75e04e316 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:21:09 +0200 Subject: [PATCH 026/285] refactoring --- integration_test/add_tokens_test.dart | 11 +- integration_test/copy_to_clipboard_test.dart | 11 +- integration_test/rename_and_delete_test.dart | 11 +- integration_test/two_step_rollout_test.dart | 11 +- integration_test/views_test.dart | 21 ++- lib/mains/main_customizer.dart | 6 +- lib/mains/main_netknights.dart | 8 +- .../riverpod_states/credentials_state.dart | 3 +- .../credentials_state.freezed.dart | 21 ++- .../riverpod_states/credentials_state.g.dart | 10 +- .../riverpod_states/introduction_state.dart | 27 ++- .../introduction_state.freezed.dart | 168 ++++++++++++++++++ .../riverpod_states/introduction_state.g.dart | 8 +- .../introduction_provider.dart | 2 +- .../introduction_provider.g.dart | 2 +- ...der.dart => app_constraints_notifier.dart} | 21 ++- .../app_constraints_notifier.g.dart | 27 +++ .../main_view_navigation_bar.dart | 4 +- .../rollout_failed_widget.dart | 4 +- .../dialogs/select_tokens_dialog.dart | 4 +- .../dialogs/show_qr_code_dialog.dart | 4 +- lib/widgets/focused_item_as_overlay.dart | 18 +- 22 files changed, 307 insertions(+), 95 deletions(-) create mode 100644 lib/model/riverpod_states/introduction_state.freezed.dart rename lib/utils/riverpod/riverpod_providers/state_providers/{app_constraints_provider.dart => app_constraints_notifier.dart} (62%) create mode 100644 lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.g.dart diff --git a/integration_test/add_tokens_test.dart b/integration_test/add_tokens_test.dart index 33164f6ed..ac30480f9 100644 --- a/integration_test/add_tokens_test.dart +++ b/integration_test/add_tokens_test.dart @@ -10,10 +10,9 @@ import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/enums/token_types.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/model/version.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; @@ -63,8 +62,8 @@ void main() { return true; }); mockTokenFolderRepository = MockTokenFolderRepository(); - when(mockTokenFolderRepository.loadFolders()).thenAnswer((_) async => []); - when(mockTokenFolderRepository.saveReplaceList(any)).thenAnswer((_) async => true); + when(mockTokenFolderRepository.loadState()).thenAnswer((_) async => const TokenFolderState(folders: [])); + when(mockTokenFolderRepository.saveState(any)).thenAnswer((_) async => true); mockIntroductionRepository = MockIntroductionRepository(); when(mockIntroductionRepository.loadCompletedIntroductions()) .thenAnswer((_) async => const IntroductionState(completedIntroductions: {...Introduction.values})); @@ -75,8 +74,8 @@ void main() { await tester.pumpWidget(TestsAppWrapper( overrides: [ settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepository)), - tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), - tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), + tokenProvider.overrideWith(() => TokenNotifier(repoOverride: mockTokenRepository)), + tokenFolderProvider.overrideWith(() => TokenFolderNotifier(repoOverride: mockTokenFolderRepository)), introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), diff --git a/integration_test/copy_to_clipboard_test.dart b/integration_test/copy_to_clipboard_test.dart index d065eef4c..7576f8ffb 100644 --- a/integration_test/copy_to_clipboard_test.dart +++ b/integration_test/copy_to_clipboard_test.dart @@ -8,9 +8,8 @@ import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; @@ -39,8 +38,8 @@ void main() { when(mockTokenRepository.saveOrReplaceTokens(any)).thenAnswer((_) async => []); when(mockTokenRepository.deleteTokens(any)).thenAnswer((_) async => []); mockTokenFolderRepository = MockTokenFolderRepository(); - when(mockTokenFolderRepository.loadFolders()).thenAnswer((_) async => []); - when(mockTokenFolderRepository.saveReplaceList(any)).thenAnswer((_) async => true); + when(mockTokenFolderRepository.loadState()).thenAnswer((_) async => const TokenFolderState(folders: [])); + when(mockTokenFolderRepository.saveState(any)).thenAnswer((_) async => true); mockIntroductionRepository = MockIntroductionRepository(); when(mockIntroductionRepository.loadCompletedIntroductions()) .thenAnswer((_) async => const IntroductionState(completedIntroductions: {...Introduction.values})); @@ -49,8 +48,8 @@ void main() { await tester.pumpWidget(TestsAppWrapper( overrides: [ settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepository)), - tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), - tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), + tokenProvider.overrideWith(() => TokenNotifier(repoOverride: mockTokenRepository)), + tokenFolderProvider.overrideWith(() => TokenFolderNotifier(repoOverride: mockTokenFolderRepository)), introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), diff --git a/integration_test/rename_and_delete_test.dart b/integration_test/rename_and_delete_test.dart index c935916e4..09028f7a9 100644 --- a/integration_test/rename_and_delete_test.dart +++ b/integration_test/rename_and_delete_test.dart @@ -8,10 +8,9 @@ import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; @@ -54,8 +53,8 @@ void main() { return true; }); mockTokenFolderRepository = MockTokenFolderRepository(); - when(mockTokenFolderRepository.loadFolders()).thenAnswer((_) async => []); - when(mockTokenFolderRepository.saveReplaceList(any)).thenAnswer((_) async => true); + when(mockTokenFolderRepository.loadState()).thenAnswer((_) async => const TokenFolderState(folders: [])); + when(mockTokenFolderRepository.saveState(any)).thenAnswer((_) async => true); mockIntroductionRepository = MockIntroductionRepository(); when(mockIntroductionRepository.loadCompletedIntroductions()) .thenAnswer((_) async => const IntroductionState(completedIntroductions: {...Introduction.values})); @@ -64,8 +63,8 @@ void main() { await tester.pumpWidget(TestsAppWrapper( overrides: [ settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepository)), - tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), - tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), + tokenProvider.overrideWith(() => TokenNotifier(repoOverride: mockTokenRepository)), + tokenFolderProvider.overrideWith(() => TokenFolderNotifier(repoOverride: mockTokenFolderRepository)), introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), diff --git a/integration_test/two_step_rollout_test.dart b/integration_test/two_step_rollout_test.dart index 7e2bf0142..406e590d1 100644 --- a/integration_test/two_step_rollout_test.dart +++ b/integration_test/two_step_rollout_test.dart @@ -7,8 +7,7 @@ import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/enums/introduction.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; @@ -41,8 +40,8 @@ void main() { when(mockTokenRepository.saveOrReplaceTokens(any)).thenAnswer((_) async => []); when(mockTokenRepository.deleteTokens(any)).thenAnswer((_) async => []); mockTokenFolderRepository = MockTokenFolderRepository(); - when(mockTokenFolderRepository.loadFolders()).thenAnswer((_) async => []); - when(mockTokenFolderRepository.saveReplaceList(any)).thenAnswer((_) async => true); + when(mockTokenFolderRepository.loadState()).thenAnswer((_) async => const TokenFolderState(folders: [])); + when(mockTokenFolderRepository.saveState(any)).thenAnswer((_) async => true); mockIntroductionRepository = MockIntroductionRepository(); when(mockIntroductionRepository.loadCompletedIntroductions()) .thenAnswer((_) async => const IntroductionState(completedIntroductions: {...Introduction.values})); @@ -53,8 +52,8 @@ void main() { await tester.pumpWidget(TestsAppWrapper( overrides: [ settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepository)), - tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository, ref: ref)), - tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), + tokenProvider.overrideWith(() => TokenNotifier(repoOverride: mockTokenRepository)), + tokenFolderProvider.overrideWith(() => TokenFolderNotifier(repoOverride: mockTokenFolderRepository)), introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index 57668f32a..2d102df75 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -11,11 +11,11 @@ import 'package:privacyidea_authenticator/model/push_request.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/introduction_state.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/push_request_state.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; +import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/utils/custom_int_buffer.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_folder_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/token_notifier.dart'; + import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/push_provider.dart'; @@ -62,8 +62,8 @@ void main() { }); mockTokenFolderRepository = MockTokenFolderRepository(); - when(mockTokenFolderRepository.loadFolders()).thenAnswer((_) async => []); - when(mockTokenFolderRepository.saveReplaceList(any)).thenAnswer((_) async => true); + when(mockTokenFolderRepository.loadState()).thenAnswer((_) async => const TokenFolderState(folders: [])); + when(mockTokenFolderRepository.saveState(any)).thenAnswer((_) async => true); mockRsaUtils = MockRsaUtils(); when(mockRsaUtils.serializeRSAPublicKeyPKCS8(any)).thenAnswer((_) => 'publicKey'); when(mockRsaUtils.generateRSAKeyPair()).thenAnswer((_) => const RsaUtils() @@ -108,12 +108,11 @@ void main() { await tester.pumpWidget(TestsAppWrapper( overrides: [ settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepository)), - tokenProvider.overrideWith((ref) => TokenNotifier( - repository: mockTokenRepository, - rsaUtils: mockRsaUtils, - firebaseUtils: mockFirebaseUtils, - ioClient: mockIOClient, - ref: ref, + tokenProvider.overrideWith(() => TokenNotifier( + repoOverride: mockTokenRepository, + rsaUtilsOverride: mockRsaUtils, + firebaseUtilsOverride: mockFirebaseUtils, + ioClientOverride: mockIOClient, )), pushRequestProvider.overrideWith( () => PushRequestNotifier( @@ -127,7 +126,7 @@ void main() { ), ), ), - tokenFolderProvider.overrideWith((ref) => TokenFolderNotifier(repository: mockTokenFolderRepository)), + tokenFolderProvider.overrideWith(() => TokenFolderNotifier(repoOverride: mockTokenFolderRepository)), introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)), ], child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization), diff --git a/lib/mains/main_customizer.dart b/lib/mains/main_customizer.dart index 3d18ce617..d14a9c1e6 100644 --- a/lib/mains/main_customizer.dart +++ b/lib/mains/main_customizer.dart @@ -28,7 +28,7 @@ import '../model/enums/app_feature.dart'; import '../model/riverpod_states/settings_state.dart'; import '../utils/globals.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; import '../utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart'; import '../views/add_token_manually_view/add_token_manually_view.dart'; import '../views/feedback_view/feedback_view.dart'; @@ -54,9 +54,7 @@ class CustomizationAuthenticator extends ConsumerWidget { final applicationCustomizer = ref.watch(applicationCustomizerProvider); return LayoutBuilder( builder: (context, constraints) { - WidgetsBinding.instance.addPostFrameCallback((_) async { - ref.read(appConstraintsProvider.notifier).state = constraints; - }); + WidgetsBinding.instance.addPostFrameCallback((_) async => ref.read(appConstraintsNotifierProvider.notifier).update(constraints)); return MaterialApp( scrollBehavior: ScrollConfiguration.of(context).copyWith( physics: const ClampingScrollPhysics(), diff --git a/lib/mains/main_netknights.dart b/lib/mains/main_netknights.dart index 1095f66ad..746896f5e 100644 --- a/lib/mains/main_netknights.dart +++ b/lib/mains/main_netknights.dart @@ -33,7 +33,7 @@ import '../utils/globals.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; import '../views/add_token_manually_view/add_token_manually_view.dart'; import '../views/feedback_view/feedback_view.dart'; import '../views/import_tokens_view/import_tokens_view.dart'; @@ -79,9 +79,7 @@ class PrivacyIDEAAuthenticator extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { globalRef = ref; return LayoutBuilder(builder: (context, constraints) { - WidgetsBinding.instance.addPostFrameCallback((_) { - ref.read(appConstraintsProvider.notifier).state = constraints; - }); + WidgetsBinding.instance.addPostFrameCallback((_) => ref.read(appConstraintsNotifierProvider.notifier).update(constraints)); return MaterialApp( scrollBehavior: ScrollConfiguration.of(context).copyWith( physics: const ClampingScrollPhysics(), @@ -89,7 +87,7 @@ class PrivacyIDEAAuthenticator extends ConsumerWidget { ), debugShowCheckedModeBanner: true, navigatorKey: globalNavigatorKey, - + localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, locale: ref.watch(settingsProvider).whenOrNull(data: (data) => data.currentLocale) ?? SettingsState.localeDefault, diff --git a/lib/model/riverpod_states/credentials_state.dart b/lib/model/riverpod_states/credentials_state.dart index eda0784f7..624874442 100644 --- a/lib/model/riverpod_states/credentials_state.dart +++ b/lib/model/riverpod_states/credentials_state.dart @@ -28,7 +28,6 @@ part 'credentials_state.freezed.dart'; part 'credentials_state.g.dart'; @Freezed() -@JsonSerializable(explicitToJson: true) class CredentialsState with _$CredentialsState { const CredentialsState._(); const factory CredentialsState({ @@ -45,4 +44,6 @@ class CredentialsState with _$CredentialsState { final credentials = jsonStrings.map((jsonString) => ContainerCredential.fromJson(jsonDecode(jsonString))).toList(); return CredentialsState(credentials: credentials); } + + factory CredentialsState.fromJson(Map json) => _$CredentialsStateFromJson(json); } diff --git a/lib/model/riverpod_states/credentials_state.freezed.dart b/lib/model/riverpod_states/credentials_state.freezed.dart index 4b7988cd2..47aaf1de5 100644 --- a/lib/model/riverpod_states/credentials_state.freezed.dart +++ b/lib/model/riverpod_states/credentials_state.freezed.dart @@ -14,11 +14,16 @@ T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); +CredentialsState _$CredentialsStateFromJson(Map json) { + return _CredentialsState.fromJson(json); +} + /// @nodoc mixin _$CredentialsState { List get credentials => throw _privateConstructorUsedError; + Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) $CredentialsStateCopyWith get copyWith => throw _privateConstructorUsedError; @@ -91,13 +96,16 @@ class __$$CredentialsStateImplCopyWithImpl<$Res> } /// @nodoc - +@JsonSerializable() class _$CredentialsStateImpl extends _CredentialsState { const _$CredentialsStateImpl( {required final List credentials}) : _credentials = credentials, super._(); + factory _$CredentialsStateImpl.fromJson(Map json) => + _$$CredentialsStateImplFromJson(json); + final List _credentials; @override List get credentials { @@ -115,6 +123,7 @@ class _$CredentialsStateImpl extends _CredentialsState { .equals(other._credentials, _credentials)); } + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, const DeepCollectionEquality().hash(_credentials)); @@ -125,6 +134,13 @@ class _$CredentialsStateImpl extends _CredentialsState { _$$CredentialsStateImplCopyWith<_$CredentialsStateImpl> get copyWith => __$$CredentialsStateImplCopyWithImpl<_$CredentialsStateImpl>( this, _$identity); + + @override + Map toJson() { + return _$$CredentialsStateImplToJson( + this, + ); + } } abstract class _CredentialsState extends CredentialsState { @@ -133,6 +149,9 @@ abstract class _CredentialsState extends CredentialsState { _$CredentialsStateImpl; const _CredentialsState._() : super._(); + factory _CredentialsState.fromJson(Map json) = + _$CredentialsStateImpl.fromJson; + @override List get credentials; @override diff --git a/lib/model/riverpod_states/credentials_state.g.dart b/lib/model/riverpod_states/credentials_state.g.dart index 361f8addb..2fdd4d4cc 100644 --- a/lib/model/riverpod_states/credentials_state.g.dart +++ b/lib/model/riverpod_states/credentials_state.g.dart @@ -6,14 +6,16 @@ part of 'credentials_state.dart'; // JsonSerializableGenerator // ************************************************************************** -CredentialsState _$CredentialsStateFromJson(Map json) => - CredentialsState( +_$CredentialsStateImpl _$$CredentialsStateImplFromJson( + Map json) => + _$CredentialsStateImpl( credentials: (json['credentials'] as List) .map((e) => ContainerCredential.fromJson(e as Map)) .toList(), ); -Map _$CredentialsStateToJson(CredentialsState instance) => +Map _$$CredentialsStateImplToJson( + _$CredentialsStateImpl instance) => { - 'credentials': instance.credentials.map((e) => e.toJson()).toList(), + 'credentials': instance.credentials, }; diff --git a/lib/model/riverpod_states/introduction_state.dart b/lib/model/riverpod_states/introduction_state.dart index 8e13a872c..b6b4ea5f8 100644 --- a/lib/model/riverpod_states/introduction_state.dart +++ b/lib/model/riverpod_states/introduction_state.dart @@ -18,25 +18,27 @@ * limitations under the License. */ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:json_annotation/json_annotation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import '../enums/introduction.dart'; import '../extensions/enums/introduction_extension.dart'; +part 'introduction_state.freezed.dart'; part 'introduction_state.g.dart'; -@JsonSerializable() -class IntroductionState { - final Set completedIntroductions; +@Freezed() +class IntroductionState with _$IntroductionState { + const IntroductionState._(); Set get uncompletedIntroductions => Introduction.values.toSet().difference(completedIntroductions); + const factory IntroductionState({ + @Default({}) Set completedIntroductions, + }) = _IntroductionState; + static IntroductionState withAllCompleted() => IntroductionState(completedIntroductions: Introduction.values.toSet()); + bool isCompleted(Introduction introduction) => completedIntroductions.contains(introduction); bool isUncompleted(Introduction introduction) => !isCompleted(introduction); - - const IntroductionState({this.completedIntroductions = const {}}); - - factory IntroductionState.fromJson(Map json) => _$IntroductionStateFromJson(json); - Map toJson() => _$IntroductionStateToJson(this); + bool isConditionFulfilled(WidgetRef ref, Introduction introduction) => introduction.isConditionFulfilled(ref, this); IntroductionState withCompletedIntroduction(Introduction introduction) { final newCompletedIntroductions = {...completedIntroductions}; @@ -50,10 +52,5 @@ class IntroductionState { return IntroductionState(completedIntroductions: newCompletedIntroductions); } - IntroductionState withAllCompleted() => IntroductionState(completedIntroductions: Introduction.values.toSet()); - - bool isConditionFulfilled(WidgetRef ref, Introduction introduction) => introduction.isConditionFulfilled(ref, this); - - @override - String toString() => 'IntroductionState{completedIntroductions: $completedIntroductions}'; + factory IntroductionState.fromJson(Map json) => _$IntroductionStateFromJson(json); } diff --git a/lib/model/riverpod_states/introduction_state.freezed.dart b/lib/model/riverpod_states/introduction_state.freezed.dart new file mode 100644 index 000000000..ba708259d --- /dev/null +++ b/lib/model/riverpod_states/introduction_state.freezed.dart @@ -0,0 +1,168 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'introduction_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +IntroductionState _$IntroductionStateFromJson(Map json) { + return _IntroductionState.fromJson(json); +} + +/// @nodoc +mixin _$IntroductionState { + Set get completedIntroductions => + throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $IntroductionStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $IntroductionStateCopyWith<$Res> { + factory $IntroductionStateCopyWith( + IntroductionState value, $Res Function(IntroductionState) then) = + _$IntroductionStateCopyWithImpl<$Res, IntroductionState>; + @useResult + $Res call({Set completedIntroductions}); +} + +/// @nodoc +class _$IntroductionStateCopyWithImpl<$Res, $Val extends IntroductionState> + implements $IntroductionStateCopyWith<$Res> { + _$IntroductionStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? completedIntroductions = null, + }) { + return _then(_value.copyWith( + completedIntroductions: null == completedIntroductions + ? _value.completedIntroductions + : completedIntroductions // ignore: cast_nullable_to_non_nullable + as Set, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$IntroductionStateImplCopyWith<$Res> + implements $IntroductionStateCopyWith<$Res> { + factory _$$IntroductionStateImplCopyWith(_$IntroductionStateImpl value, + $Res Function(_$IntroductionStateImpl) then) = + __$$IntroductionStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Set completedIntroductions}); +} + +/// @nodoc +class __$$IntroductionStateImplCopyWithImpl<$Res> + extends _$IntroductionStateCopyWithImpl<$Res, _$IntroductionStateImpl> + implements _$$IntroductionStateImplCopyWith<$Res> { + __$$IntroductionStateImplCopyWithImpl(_$IntroductionStateImpl _value, + $Res Function(_$IntroductionStateImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? completedIntroductions = null, + }) { + return _then(_$IntroductionStateImpl( + completedIntroductions: null == completedIntroductions + ? _value._completedIntroductions + : completedIntroductions // ignore: cast_nullable_to_non_nullable + as Set, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$IntroductionStateImpl extends _IntroductionState { + const _$IntroductionStateImpl( + {final Set completedIntroductions = const {}}) + : _completedIntroductions = completedIntroductions, + super._(); + + factory _$IntroductionStateImpl.fromJson(Map json) => + _$$IntroductionStateImplFromJson(json); + + final Set _completedIntroductions; + @override + @JsonKey() + Set get completedIntroductions { + if (_completedIntroductions is EqualUnmodifiableSetView) + return _completedIntroductions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableSetView(_completedIntroductions); + } + + @override + String toString() { + return 'IntroductionState(completedIntroductions: $completedIntroductions)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$IntroductionStateImpl && + const DeepCollectionEquality().equals( + other._completedIntroductions, _completedIntroductions)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, + const DeepCollectionEquality().hash(_completedIntroductions)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$IntroductionStateImplCopyWith<_$IntroductionStateImpl> get copyWith => + __$$IntroductionStateImplCopyWithImpl<_$IntroductionStateImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$IntroductionStateImplToJson( + this, + ); + } +} + +abstract class _IntroductionState extends IntroductionState { + const factory _IntroductionState( + {final Set completedIntroductions}) = + _$IntroductionStateImpl; + const _IntroductionState._() : super._(); + + factory _IntroductionState.fromJson(Map json) = + _$IntroductionStateImpl.fromJson; + + @override + Set get completedIntroductions; + @override + @JsonKey(ignore: true) + _$$IntroductionStateImplCopyWith<_$IntroductionStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/model/riverpod_states/introduction_state.g.dart b/lib/model/riverpod_states/introduction_state.g.dart index 317fdf484..0256917e3 100644 --- a/lib/model/riverpod_states/introduction_state.g.dart +++ b/lib/model/riverpod_states/introduction_state.g.dart @@ -6,15 +6,17 @@ part of 'introduction_state.dart'; // JsonSerializableGenerator // ************************************************************************** -IntroductionState _$IntroductionStateFromJson(Map json) => - IntroductionState( +_$IntroductionStateImpl _$$IntroductionStateImplFromJson( + Map json) => + _$IntroductionStateImpl( completedIntroductions: (json['completedIntroductions'] as List?) ?.map((e) => $enumDecode(_$IntroductionEnumMap, e)) .toSet() ?? const {}, ); -Map _$IntroductionStateToJson(IntroductionState instance) => +Map _$$IntroductionStateImplToJson( + _$IntroductionStateImpl instance) => { 'completedIntroductions': instance.completedIntroductions .map((e) => _$IntroductionEnumMap[e]!) diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart index 9f8552d30..9a7fc206e 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart @@ -77,7 +77,7 @@ class IntroductionNotifier extends _$IntroductionNotifier { Future completeAll() async { Logger.info('Completing all introductions', name: 'introduction_provider.dart#completeAll'); - final newState = (await future).withAllCompleted(); + final newState = IntroductionState.withAllCompleted(); await _saveToRepo(newState); state = AsyncValue.data(newState); } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart index f1ca78185..d2fc1de95 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart @@ -7,7 +7,7 @@ part of 'introduction_provider.dart'; // ************************************************************************** String _$introductionNotifierHash() => - r'ef2eb16345e9723dd63a1c4d4bed72872a447af0'; + r'ea8251fe045cfdcf4e0ed40ed3e5dae135bb58d5'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart similarity index 62% rename from lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart rename to lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart index d3e8d634c..dfeaba79a 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart @@ -18,13 +18,22 @@ * limitations under the License. */ import 'package:flutter/rendering.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../logger.dart'; -final appConstraintsProvider = StateProvider( - (ref) { - Logger.info("New constraintsProvider created", name: 'appConstraintsProvider'); +part 'app_constraints_notifier.g.dart'; + +@riverpod +class AppConstraintsNotifier extends _$AppConstraintsNotifier { + @override + BoxConstraints? build() { + Logger.info("New AppConstraints created", name: 'AppConstraintsNotifier#build'); return null; - }, -); + } + + void update(BoxConstraints constraints) { + Logger.info("AppConstraints updated", name: 'AppConstraintsNotifier#update'); + state = constraints; + } +} diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.g.dart new file mode 100644 index 000000000..c96e565a8 --- /dev/null +++ b/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_constraints_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$appConstraintsNotifierHash() => + r'6b6633ada94116eb933767ab1e29aeecf3e4397c'; + +/// See also [AppConstraintsNotifier]. +@ProviderFor(AppConstraintsNotifier) +final appConstraintsNotifierProvider = AutoDisposeNotifierProvider< + AppConstraintsNotifier, BoxConstraints?>.internal( + AppConstraintsNotifier.new, + name: r'appConstraintsNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$appConstraintsNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AppConstraintsNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart index fa48c5f81..790439e26 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart @@ -23,7 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/introduction.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; import '../../../widgets/focused_item_as_overlay.dart'; import '../../add_token_manually_view/add_token_manually_view.dart'; import '../../settings_view/settings_view.dart'; @@ -38,7 +38,7 @@ class MainViewNavigationBar extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - BoxConstraints? constraints = ref.watch(appConstraintsProvider); + BoxConstraints? constraints = ref.watch(appConstraintsNotifierProvider); final introProv = ref.watch(introductionNotifierProvider); constraints ??= const BoxConstraints(); final navWidth = constraints.maxWidth; 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 fa059c015..2ab5a9bde 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 @@ -25,7 +25,7 @@ import '../../../../../model/extensions/enums/push_token_rollout_state_extension import '../../../../../model/tokens/push_token.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../../widgets/press_button.dart'; @@ -36,7 +36,7 @@ class RolloutFailedWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final width = ref.read(appConstraintsProvider)?.maxWidth ?? 0; + final width = ref.read(appConstraintsNotifierProvider)?.maxWidth ?? 0; final localizations = AppLocalizations.of(context)!; return SingleChildScrollView( child: Column( diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart index 3a004d23d..52a1b959c 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart @@ -24,7 +24,7 @@ import '../../../../../model/riverpod_states/token_state.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../main_view/main_view_widgets/token_widgets/token_widget_builder.dart'; @@ -60,7 +60,7 @@ class _SelectTokensDialogState extends ConsumerState { ], ), content: SizedBox( - width: ref.watch(appConstraintsProvider)!.maxWidth * 0.8, + width: ref.watch(appConstraintsNotifierProvider)!.maxWidth * 0.8, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: (tokens.isEmpty) 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 935d21637..3de8e2f2a 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 @@ -28,7 +28,7 @@ import 'package:zxing2/qrcode.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; import '../../../../../utils/encryption/token_encryption.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; class ShowQrCodeDialog extends ConsumerWidget { @@ -37,7 +37,7 @@ class ShowQrCodeDialog extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final appConstraits = ref.watch(appConstraintsProvider)!; + final appConstraits = ref.watch(appConstraintsNotifierProvider)!; final qrSize = min(appConstraits.maxWidth, appConstraits.maxHeight) * 0.85; final qrImage = Image.memory(_generateQrCodeImage(data: TokenEncryption.generateExportUri(token: token).toString())); return DefaultDialog( diff --git a/lib/widgets/focused_item_as_overlay.dart b/lib/widgets/focused_item_as_overlay.dart index b3a5a2238..2a6308a52 100644 --- a/lib/widgets/focused_item_as_overlay.dart +++ b/lib/widgets/focused_item_as_overlay.dart @@ -7,7 +7,7 @@ import 'package:flutter/material.dart'; import '../l10n/app_localizations.dart'; import '../utils/globals.dart'; import '../utils/logger.dart'; -import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_provider.dart'; +import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; import '../utils/utils.dart'; import 'pulse_icon.dart'; import 'tooltip_container.dart'; @@ -135,14 +135,13 @@ class _FocusedItemOverlayState extends State<_FocusedItemOverlay> { return; } _disposeOverlay(); - final screenSize = (globalRef?.read(appConstraintsProvider) ?? const BoxConstraints()).biggest; + final screenSize = (globalRef?.read(appConstraintsNotifierProvider) ?? const BoxConstraints()).biggest; final textScaler = MediaQuery.of(context).textScaler; if (widget.tooltipWhenFocused != null) { final textSize = textSizeOf( text: widget.tooltipWhenFocused!, style: Theme.of(context).textTheme.bodyLarge!, - maxWidth: screenSize.width / 3 * 2 - - (tooltipPadding.left + tooltipPadding.right + tooltipMargin.left + tooltipMargin.right + tooltipBorderWidth * 2), + maxWidth: screenSize.width / 3 * 2 - (tooltipPadding.left + tooltipPadding.right + tooltipMargin.left + tooltipMargin.right + tooltipBorderWidth * 2), textScaler: textScaler, ); @@ -218,18 +217,15 @@ class _FocusedItemOverlayState extends State<_FocusedItemOverlay> { ), ), Positioned.fill( - - child: GestureDetector( - - onTapDown: (details) { - widget.onComplete?.call(); - }, + child: GestureDetector( + onTapDown: (details) { + widget.onComplete?.call(); + }, child: Text( AppLocalizations.of(context)!.continueButton, style: const TextStyle(fontSize: 0), ), ), - ), ], ), From 98536d58f6f9cbd64d944cffd56d83914ceca110 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:06:49 +0200 Subject: [PATCH 027/285] merge error --- lib/mains/main_customizer.dart | 3 ++- lib/mains/main_netknights.dart | 7 +++++-- .../app_constraints_notifier.g.dart | 10 +++------- lib/views/main_view/main_view.dart | 5 +++-- .../main_view_navigation_bar.dart | 14 ++++++-------- lib/views/splash_screen/splash_screen.dart | 4 +++- 6 files changed, 22 insertions(+), 21 deletions(-) diff --git a/lib/mains/main_customizer.dart b/lib/mains/main_customizer.dart index 2fe342617..83948ab2e 100644 --- a/lib/mains/main_customizer.dart +++ b/lib/mains/main_customizer.dart @@ -90,10 +90,11 @@ class CustomizationAuthenticator extends ConsumerWidget { appIcon: applicationCustomizer.appIcon.getWidget, appName: applicationCustomizer.appName, disablePatchNotes: applicationCustomizer.disabledFeatures.contains(AppFeature.patchNotes), + appConstraints: constraints, ), PushTokensView.routeName: (context) => const PushTokensView(), SettingsView.routeName: (context) => const SettingsView(), - SplashScreen.routeName: (context) => SplashScreen(customization: applicationCustomizer), + SplashScreen.routeName: (context) => SplashScreen(customization: applicationCustomizer, appConstraints: constraints), QRScannerView.routeName: (context) => const QRScannerView(), }, ); diff --git a/lib/mains/main_netknights.dart b/lib/mains/main_netknights.dart index 6b2d8f9b9..71fb53703 100644 --- a/lib/mains/main_netknights.dart +++ b/lib/mains/main_netknights.dart @@ -78,7 +78,9 @@ class PrivacyIDEAAuthenticator extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { globalRef = ref; return LayoutBuilder(builder: (context, constraints) { - WidgetsBinding.instance.addPostFrameCallback((_) => ref.read(appConstraintsNotifierProvider.notifier).update(constraints)); + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(appConstraintsNotifierProvider.notifier).update(constraints); + }); return MaterialApp( scrollBehavior: ScrollConfiguration.of(context).copyWith( physics: const ClampingScrollPhysics(), @@ -109,10 +111,11 @@ class PrivacyIDEAAuthenticator extends ConsumerWidget { appIcon: _customization.appIcon.getWidget, appName: _customization.appName, disablePatchNotes: _customization.disabledFeatures.contains(AppFeature.patchNotes), + appConstraints: constraints, ), PushTokensView.routeName: (context) => const PushTokensView(), SettingsView.routeName: (context) => const SettingsView(), - SplashScreen.routeName: (context) => SplashScreen(customization: _customization), + SplashScreen.routeName: (context) => SplashScreen(customization: _customization, appConstraints: constraints), QRScannerView.routeName: (context) => const QRScannerView(), }, ); diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.g.dart index c96e565a8..bdb32253e 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.g.dart @@ -6,18 +6,14 @@ part of 'app_constraints_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$appConstraintsNotifierHash() => - r'6b6633ada94116eb933767ab1e29aeecf3e4397c'; +String _$appConstraintsNotifierHash() => r'6b6633ada94116eb933767ab1e29aeecf3e4397c'; /// See also [AppConstraintsNotifier]. @ProviderFor(AppConstraintsNotifier) -final appConstraintsNotifierProvider = AutoDisposeNotifierProvider< - AppConstraintsNotifier, BoxConstraints?>.internal( +final appConstraintsNotifierProvider = AutoDisposeNotifierProvider.internal( AppConstraintsNotifier.new, name: r'appConstraintsNotifierProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$appConstraintsNotifierHash, + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null : _$appConstraintsNotifierHash, dependencies: null, allTransitiveDependencies: null, ); diff --git a/lib/views/main_view/main_view.dart b/lib/views/main_view/main_view.dart index 5f6b1c8f7..ba8212bfa 100644 --- a/lib/views/main_view/main_view.dart +++ b/lib/views/main_view/main_view.dart @@ -48,8 +48,9 @@ class MainView extends ConsumerStatefulView { final Widget appIcon; final String appName; final bool disablePatchNotes; + final BoxConstraints appConstraints; - const MainView({required this.appIcon, required this.appName, required this.disablePatchNotes, super.key}); + const MainView({required this.appIcon, required this.appName, required this.disablePatchNotes, super.key, required this.appConstraints}); @override ConsumerState createState() => _MainViewState(); @@ -114,7 +115,7 @@ class _MainViewState extends ConsumerState { ? Stack( children: [ MainViewTokensList(nestedScrollViewKey: globalKey), - const MainViewNavigationBar(), + MainViewNavigationBar(appConstraints: widget.appConstraints), ], ) : const MainViewTokensListFiltered(), diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart index 790439e26..f0565dbc8 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart @@ -23,7 +23,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/introduction.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; import '../../../widgets/focused_item_as_overlay.dart'; import '../../add_token_manually_view/add_token_manually_view.dart'; import '../../settings_view/settings_view.dart'; @@ -34,18 +33,17 @@ import 'main_view_navigation_buttons/license_push_view_button.dart'; import 'main_view_navigation_buttons/qr_scanner_button.dart'; class MainViewNavigationBar extends ConsumerWidget { - const MainViewNavigationBar({super.key}); + final BoxConstraints appConstraints; + const MainViewNavigationBar({super.key, required this.appConstraints}); @override Widget build(BuildContext context, WidgetRef ref) { - BoxConstraints? constraints = ref.watch(appConstraintsNotifierProvider); final introProv = ref.watch(introductionNotifierProvider); - constraints ??= const BoxConstraints(); - final navWidth = constraints.maxWidth; - final navHeight = constraints.maxHeight * 0.10; + final navWidth = appConstraints.maxWidth; + final navHeight = appConstraints.maxHeight * 0.10; return SizedBox( - width: constraints.maxWidth, - height: constraints.maxHeight, + width: appConstraints.maxWidth, + height: appConstraints.maxHeight, child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ diff --git a/lib/views/splash_screen/splash_screen.dart b/lib/views/splash_screen/splash_screen.dart index dce8aeb1a..9911b5e15 100644 --- a/lib/views/splash_screen/splash_screen.dart +++ b/lib/views/splash_screen/splash_screen.dart @@ -33,7 +33,8 @@ import '../view_interface.dart'; class SplashScreen extends ConsumerStatefulWidget { static const routeName = '/'; final ApplicationCustomization customization; - const SplashScreen({required this.customization, super.key}); + final BoxConstraints appConstraints; + const SplashScreen({required this.customization, super.key, required this.appConstraints}); @override ConsumerState createState() => _SplashScreenState(); } @@ -100,6 +101,7 @@ class _SplashScreenState extends ConsumerState { appName: _customization.appName, appIcon: _customization.appIcon.getWidget, disablePatchNotes: _customization.disabledFeatures.contains(AppFeature.patchNotes), + appConstraints: widget.appConstraints, ); final routeBuilder = PageRouteBuilder(pageBuilder: (_, __, ___) => nextView); // Idle until the splash screen is the top route. From 7b5bcb6fc4f31256c1b3f041f65644b296ada4b8 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:12:06 +0200 Subject: [PATCH 028/285] merge error --- .../main_view_widgets/main_view_navigation_bar.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart index f0565dbc8..ef8f0d27f 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart @@ -23,6 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/introduction.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; +import '../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; import '../../../widgets/focused_item_as_overlay.dart'; import '../../add_token_manually_view/add_token_manually_view.dart'; import '../../settings_view/settings_view.dart'; @@ -39,11 +40,12 @@ class MainViewNavigationBar extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final introProv = ref.watch(introductionNotifierProvider); - final navWidth = appConstraints.maxWidth; - final navHeight = appConstraints.maxHeight * 0.10; + final constraints = ref.watch(appConstraintsNotifierProvider) ?? appConstraints; + final navWidth = constraints.maxWidth; + final navHeight = constraints.maxHeight * 0.10; return SizedBox( - width: appConstraints.maxWidth, - height: appConstraints.maxHeight, + width: constraints.maxWidth, + height: constraints.maxHeight, child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ From 6a6f7a20b0a2f9d0984386601d08c544d07e13b0 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:06:46 +0200 Subject: [PATCH 029/285] refactoring --- integration_test/add_tokens_test.dart | 2 +- integration_test/copy_to_clipboard_test.dart | 2 +- integration_test/rename_and_delete_test.dart | 2 +- integration_test/two_step_rollout_test.dart | 13 +- integration_test/views_test.dart | 9 +- .../container_credentials_repository.dart | 4 +- .../token_state_listener.dart | 2 +- lib/mains/main_customizer.dart | 4 +- lib/mains/main_netknights.dart | 2 +- .../enums/introduction_extension.dart | 2 +- lib/model/processor_result.dart | 65 +- lib/model/processor_result.freezed.dart | 463 +++++++ lib/model/push_request.dart | 2 +- .../riverpod_states/credentials_state.dart | 7 +- .../credentials_state.freezed.dart | 26 +- .../introduction_state.freezed.dart | 21 +- .../progress_state.freezed.dart | 28 +- lib/model/token_container.dart | 8 +- lib/model/token_container.freezed.dart | 136 +- lib/model/tokens/container_credentials.dart | 294 +++- .../tokens/container_credentials.freezed.dart | 1181 +++++++++++++++++ lib/model/tokens/container_credentials.g.dart | 139 +- lib/model/tokens/otp_token.dart | 5 +- .../mixins/token_import_processor.dart | 5 +- .../container_credentials_processor.dart | 48 +- .../home_widget_processor.dart | 87 +- .../home_widget_navigate_processor.dart | 96 +- ...navigation_scheme_processor_interface.dart | 3 +- .../scheme_processor_interface.dart | 14 +- .../free_otp_plus_qr_processor.dart | 2 + .../google_authenticator_qr_processor.dart | 4 +- .../otp_auth_processor.dart | 59 +- ...rivacyidea_authenticator_qr_processor.dart | 26 +- ...ken_import_scheme_processor_interface.dart | 9 +- .../aegis_import_file_processor.dart | 42 +- ...thenticator_pro_import_file_processor.dart | 40 +- .../free_otp_plus_import_file_processor.dart | 35 +- ...google_authenticator_qrfile_processor.dart | 2 + ...a_authenticator_import_file_processor.dart | 15 +- ...token_import_file_processor_interface.dart | 1 + .../two_fas_import_file_processor.dart | 15 +- ...cure_container_credentials_repository.dart | 21 +- lib/repo/secure_token_repository.dart | 2 +- .../application_customization.dart | 1 + lib/utils/home_widget_utils.dart | 2 +- lib/utils/identifiers.dart | 32 +- lib/utils/push_provider.dart | 2 +- .../app_constraints_notifier.dart | 0 .../app_constraints_notifier.g.dart | 10 +- .../application_customizer_provider.dart | 0 .../application_customizer_provider.g.dart | 0 .../credential_notifier.dart | 123 +- .../credential_notifier.g.dart | 2 +- .../sortable_notifier.dart | 2 +- .../token_container_notifier.dart | 2 +- .../token_notifier.dart | 142 +- .../token_notifier.g.dart | 2 +- .../state_providers/home_widget_provider.dart | 2 +- .../connectivity_provider.dart | 2 +- lib/utils/utils.dart | 68 +- .../add_token_manually_view.dart | 2 +- .../import_tokens_view.dart | 2 +- .../pages/import_encrypted_data_page.dart | 1 + .../pages/import_plain_tokens_page.dart | 2 +- .../pages/import_start_page.dart | 26 +- .../link_home_widget_view.dart | 2 +- .../connectivity_listener.dart | 2 +- .../delete_token_folder_action.dart | 2 +- .../token_folder_expandable.dart | 2 +- .../main_view_navigation_bar.dart | 2 +- .../qr_scanner_button.dart | 11 +- .../main_view_tokens_list_filtered.dart | 2 +- .../day_password_token_widget_tile.dart | 2 +- .../default_delete_action.dart | 2 +- .../default_edit_action.dart | 2 +- .../default_edit_action_dialog.dart | 2 +- .../default_lock_action.dart | 2 +- .../hotp_token_widget_tile.dart | 2 +- .../actions/edit_push_token_action.dart | 2 +- .../push_token_widgets/push_token_widget.dart | 34 +- .../rollout_failed_widget.dart | 4 +- .../totp_token_widget_tile.dart | 2 +- .../widgets/push_tokens_view_list.dart | 2 +- .../dialogs/select_tokens_dialog.dart | 6 +- .../dialogs/show_qr_code_dialog.dart | 2 +- .../settings_group_push_token.dart | 2 +- lib/views/settings_view/settings_view.dart | 2 +- .../update_firebase_token_dialog.dart | 2 +- lib/views/splash_screen/splash_screen.dart | 2 +- lib/widgets/app_wrapper.dart | 2 +- .../enter_passphrase_dialog.dart | 60 + .../dialog_widgets/push_request_dialog.dart | 2 +- lib/widgets/focused_item_as_overlay.dart | 2 +- lib/widgets/hideable_widget_.dart | 2 +- pubspec.lock | 12 +- pubspec.yaml | 1 + .../sortable_notifier_test.dart | 2 +- .../state_notifiers/token_notifier_test.dart | 10 +- 98 files changed, 3146 insertions(+), 410 deletions(-) create mode 100644 lib/model/processor_result.freezed.dart create mode 100644 lib/model/tokens/container_credentials.freezed.dart rename lib/utils/riverpod/riverpod_providers/{state_providers => generated_providers}/app_constraints_notifier.dart (100%) rename lib/utils/riverpod/riverpod_providers/{state_providers => generated_providers}/app_constraints_notifier.g.dart (79%) rename lib/utils/riverpod/riverpod_providers/{state_providers => generated_providers}/application_customizer_provider.dart (100%) rename lib/utils/riverpod/riverpod_providers/{state_providers => generated_providers}/application_customizer_provider.g.dart (100%) rename lib/utils/riverpod/riverpod_providers/{state_notifier_providers => generated_providers}/token_notifier.dart (91%) rename lib/utils/riverpod/riverpod_providers/{state_notifier_providers => generated_providers}/token_notifier.g.dart (98%) create mode 100644 lib/widgets/dialog_widgets/enter_passphrase_dialog.dart diff --git a/integration_test/add_tokens_test.dart b/integration_test/add_tokens_test.dart index ac30480f9..23707b2ac 100644 --- a/integration_test/add_tokens_test.dart +++ b/integration_test/add_tokens_test.dart @@ -17,7 +17,7 @@ import 'package:privacyidea_authenticator/utils/customization/application_custom import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.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'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/app_bar_item.dart'; diff --git a/integration_test/copy_to_clipboard_test.dart b/integration_test/copy_to_clipboard_test.dart index 7576f8ffb..5812957b0 100644 --- a/integration_test/copy_to_clipboard_test.dart +++ b/integration_test/copy_to_clipboard_test.dart @@ -15,7 +15,7 @@ import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../test/tests_app_wrapper.dart'; import '../test/tests_app_wrapper.mocks.dart'; diff --git a/integration_test/rename_and_delete_test.dart b/integration_test/rename_and_delete_test.dart index 09028f7a9..bef7e398c 100644 --- a/integration_test/rename_and_delete_test.dart +++ b/integration_test/rename_and_delete_test.dart @@ -16,7 +16,7 @@ import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_delete_action.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart'; diff --git a/integration_test/two_step_rollout_test.dart b/integration_test/two_step_rollout_test.dart index 406e590d1..3b29e2a87 100644 --- a/integration_test/two_step_rollout_test.dart +++ b/integration_test/two_step_rollout_test.dart @@ -15,7 +15,8 @@ import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; +import 'package:privacyidea_authenticator/utils/utils.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart'; @@ -67,8 +68,9 @@ void main() { Future _addTwoStepHotpTokenTest(WidgetTester tester) async { await pumpUntilFindNWidgets(tester, find.byType(MainView), 1, const Duration(seconds: 10)); - globalRef!.read(tokenProvider.notifier).handleQrCode( - 'otpauth://hotp/OATH0001DBD0?secret=AALIBQJMOGEE7SAVEZ5D3K2ADO7MVFQD&counter=1&digits=6&issuer=privacyIDEA&2step_salt=8&2step_output=20&2step_difficulty=10000'); + const qrCode = + 'otpauth://hotp/OATH0001DBD0?secret=AALIBQJMOGEE7SAVEZ5D3K2ADO7MVFQD&counter=1&digits=6&issuer=privacyIDEA&2step_salt=8&2step_output=20&2step_difficulty=10000'; + await scanQrCode(globalRef!, qrCode); Logger.info('Finding phone part dialog'); await pumpUntilFindNWidgets(tester, find.text(AppLocalizationsEn().phonePart), 1, const Duration(seconds: 20)); expect(find.text(AppLocalizationsEn().phonePart), findsOneWidget); @@ -89,8 +91,9 @@ Future _addTwoStepHotpTokenTest(WidgetTester tester) async { Future _addTwoStepTotpTokenTest(WidgetTester tester) async { await pumpUntilFindNWidgets(tester, find.byType(MainView), 1, const Duration(seconds: 10)); - globalRef!.read(tokenProvider.notifier).handleQrCode( - 'otpauth://totp/TOTP00009D5F?secret=NZ4OPONKAAGDFN2QHV26ZWYVTLFER4C6&period=30&digits=6&issuer=privacyIDEA&2step_salt=8&2step_output=20&2step_difficulty=10000'); + const qrCode = + 'otpauth://totp/TOTP00009D5F?secret=NZ4OPONKAAGDFN2QHV26ZWYVTLFER4C6&period=30&digits=6&issuer=privacyIDEA&2step_salt=8&2step_output=20&2step_difficulty=10000'; + await scanQrCode(globalRef!, qrCode); Logger.info('Finding phone part dialog'); await pumpUntilFindNWidgets(tester, find.text(AppLocalizationsEn().phonePart), 1, const Duration(seconds: 20)); expect(find.text(AppLocalizationsEn().phonePart), findsOneWidget); diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index 2d102df75..0ba04af63 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -22,9 +22,10 @@ import 'package:privacyidea_authenticator/utils/push_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; import 'package:privacyidea_authenticator/model/version.dart'; +import 'package:privacyidea_authenticator/utils/utils.dart'; import 'package:privacyidea_authenticator/views/settings_view/settings_view_widgets/settings_groups.dart'; import '../test/tests_app_wrapper.dart'; @@ -166,8 +167,10 @@ Future _settingsViewTest(WidgetTester tester) async { expect(find.text(AppLocalizationsEn().language), findsOneWidget); expect(find.text(AppLocalizationsEn().errorLogTitle), findsOneWidget); expect(find.byType(SettingsGroup), findsNWidgets(6)); - globalRef!.read(tokenProvider.notifier).handleQrCode( - 'otpauth://pipush/label?url=http%3A%2F%2Fwww.example.com&ttl=10&issuer=issuer&enrollment_credential=enrollmentCredentials&v=1&serial=serial&serial=serial&sslverify=0'); + const qrCode = + 'otpauth://pipush/label?url=http%3A%2F%2Fwww.example.com&ttl=10&issuer=issuer&enrollment_credential=enrollmentCredentials&v=1&serial=serial&serial=serial&sslverify=0'; + await scanQrCode(globalRef!, qrCode); + await pumpUntilFindNWidgets(tester, find.text(AppLocalizationsEn().pushToken), 1, const Duration(minutes: 5)); expect(find.text(AppLocalizationsEn().pushToken), findsOneWidget); expect(find.byType(SettingsGroup), findsNWidgets(6)); diff --git a/lib/interfaces/repo/container_credentials_repository.dart b/lib/interfaces/repo/container_credentials_repository.dart index 33d833a82..08a3e4ad6 100644 --- a/lib/interfaces/repo/container_credentials_repository.dart +++ b/lib/interfaces/repo/container_credentials_repository.dart @@ -24,7 +24,7 @@ abstract class ContainerCredentialsRepository { Future saveCredential(ContainerCredential credential); Future saveCredentialsState(CredentialsState credentialsState); Future loadCredentialsState(); - Future loadCredential(String id); + Future loadCredential(String serial); Future deleteAllCredentials(); - Future deleteCredential(String id); + Future deleteCredential(String serial); } diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart index 41d025663..98eac1c13 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart @@ -19,7 +19,7 @@ */ import '../../../../model/riverpod_states/token_state.dart'; -import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../buildless_listener.dart'; abstract class TokenStateListener extends BuildlessListener { diff --git a/lib/mains/main_customizer.dart b/lib/mains/main_customizer.dart index 83948ab2e..a0a952932 100644 --- a/lib/mains/main_customizer.dart +++ b/lib/mains/main_customizer.dart @@ -28,9 +28,9 @@ import '../l10n/app_localizations.dart'; import '../model/enums/app_feature.dart'; import '../model/riverpod_states/settings_state.dart'; import '../utils/globals.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/application_customizer_provider.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; -import '../utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart'; import '../views/add_token_manually_view/add_token_manually_view.dart'; import '../views/feedback_view/feedback_view.dart'; import '../views/import_tokens_view/import_tokens_view.dart'; diff --git a/lib/mains/main_netknights.dart b/lib/mains/main_netknights.dart index 71fb53703..c6d7c69ac 100644 --- a/lib/mains/main_netknights.dart +++ b/lib/mains/main_netknights.dart @@ -33,7 +33,7 @@ import '../utils/globals.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart'; import '../views/add_token_manually_view/add_token_manually_view.dart'; import '../views/feedback_view/feedback_view.dart'; import '../views/import_tokens_view/import_tokens_view.dart'; diff --git a/lib/model/extensions/enums/introduction_extension.dart b/lib/model/extensions/enums/introduction_extension.dart index 0836e7c89..856d9abbe 100644 --- a/lib/model/extensions/enums/introduction_extension.dart +++ b/lib/model/extensions/enums/introduction_extension.dart @@ -3,7 +3,7 @@ import '../../riverpod_states/settings_state.dart'; import '../../../l10n/app_localizations.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../enums/introduction.dart'; import '../../riverpod_states/introduction_state.dart'; diff --git a/lib/model/processor_result.dart b/lib/model/processor_result.dart index 6f78b073a..a10c0d392 100644 --- a/lib/model/processor_result.dart +++ b/lib/model/processor_result.dart @@ -17,29 +17,62 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -abstract class ProcessorResult { - const ProcessorResult(); - factory ProcessorResult.success(T data) => ProcessorResultSuccess(data); - factory ProcessorResult.failed(String errorMessage) => ProcessorResultFailed(errorMessage); +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; + +import '../l10n/app_localizations.dart'; +import '../utils/globals.dart'; +import '../utils/logger.dart'; +import '../utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; +import '../utils/view_utils.dart'; + +part 'processor_result.freezed.dart'; + +@freezed +abstract class ProcessorResult with _$ProcessorResult { + const ProcessorResult._(); + factory ProcessorResult.success( + T resultData, { + required TypeMatcher? resultHandlerType, + }) = ProcessorResultSuccess; + factory ProcessorResult.failed( + String message, { + required TypeMatcher? resultHandlerType, + }) = ProcessorResultFailed; + bool get isSuccess => this is ProcessorResultSuccess; + bool get isFailed => this is ProcessorResultFailed; ProcessorResultSuccess? get asSuccess => this is ProcessorResultSuccess ? this as ProcessorResultSuccess : null; ProcessorResultFailed? get asFailed => this is ProcessorResultFailed ? this as ProcessorResultFailed : null; } -class ProcessorResultSuccess extends ProcessorResult { - final T resultData; - const ProcessorResultSuccess(this.resultData); +extension ListProcessorResult on List> { + List> get successResults => where((element) => element.isSuccess).map((e) => e.asSuccess!).toList(); + List> get failedResults => where((element) => !element.isSuccess).map((e) => e.asFailed!).toList(); - @override - String toString() { - return 'ProcessorResultSuccess(data: $resultData)'; + List getData() { + final results = toList(); + if (results.isEmpty) { + showMessage(message: 'No data found in QR code.', duration: const Duration(seconds: 3)); + Logger.warning('No data found in QR code.', name: 'token_notifier.dart#_getData'); + return []; + } + final failedResults = results.whereType().toList(); + // add postframe callback to show the message after the current frame + + WidgetsBinding.instance.addPostFrameCallback((_) async { + for (var failedResult in failedResults) { + globalRef?.read(statusMessageProvider.notifier).state = (AppLocalizations.of((await globalContext))!.malformedData, failedResult.message); + } + }); + + final successData = results.where((result) => result.isSuccess).map((e) => e.asSuccess!.resultData).toList(); + return successData; } } -class ProcessorResultFailed extends ProcessorResult { - final String message; - const ProcessorResultFailed(this.message); - - @override - String toString() => '$runtimeType(message: $message)'; +mixin ResultHandler { + Future handleProcessorResult(ProcessorResult result, Map args); + Future handleProcessorResults(List results, Map args); } diff --git a/lib/model/processor_result.freezed.dart b/lib/model/processor_result.freezed.dart new file mode 100644 index 000000000..327c3355e --- /dev/null +++ b/lib/model/processor_result.freezed.dart @@ -0,0 +1,463 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'processor_result.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$ProcessorResult { + TypeMatcher? get resultHandlerType => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function( + T resultData, TypeMatcher? resultHandlerType) + success, + required TResult Function( + String message, TypeMatcher? resultHandlerType) + failed, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + T resultData, TypeMatcher? resultHandlerType)? + success, + TResult? Function( + String message, TypeMatcher? resultHandlerType)? + failed, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + T resultData, TypeMatcher? resultHandlerType)? + success, + TResult Function( + String message, TypeMatcher? resultHandlerType)? + failed, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(ProcessorResultSuccess value) success, + required TResult Function(ProcessorResultFailed value) failed, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(ProcessorResultSuccess value)? success, + TResult? Function(ProcessorResultFailed value)? failed, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(ProcessorResultSuccess value)? success, + TResult Function(ProcessorResultFailed value)? failed, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Create a copy of ProcessorResult + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProcessorResultCopyWith> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProcessorResultCopyWith { + factory $ProcessorResultCopyWith( + ProcessorResult value, $Res Function(ProcessorResult) then) = + _$ProcessorResultCopyWithImpl>; + @useResult + $Res call({TypeMatcher? resultHandlerType}); +} + +/// @nodoc +class _$ProcessorResultCopyWithImpl> + implements $ProcessorResultCopyWith { + _$ProcessorResultCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProcessorResult + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? resultHandlerType = freezed, + }) { + return _then(_value.copyWith( + resultHandlerType: freezed == resultHandlerType + ? _value.resultHandlerType + : resultHandlerType // ignore: cast_nullable_to_non_nullable + as TypeMatcher?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ProcessorResultSuccessImplCopyWith + implements $ProcessorResultCopyWith { + factory _$$ProcessorResultSuccessImplCopyWith( + _$ProcessorResultSuccessImpl value, + $Res Function(_$ProcessorResultSuccessImpl) then) = + __$$ProcessorResultSuccessImplCopyWithImpl; + @override + @useResult + $Res call({T resultData, TypeMatcher? resultHandlerType}); +} + +/// @nodoc +class __$$ProcessorResultSuccessImplCopyWithImpl + extends _$ProcessorResultCopyWithImpl> + implements _$$ProcessorResultSuccessImplCopyWith { + __$$ProcessorResultSuccessImplCopyWithImpl( + _$ProcessorResultSuccessImpl _value, + $Res Function(_$ProcessorResultSuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of ProcessorResult + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? resultData = freezed, + Object? resultHandlerType = freezed, + }) { + return _then(_$ProcessorResultSuccessImpl( + freezed == resultData + ? _value.resultData + : resultData // ignore: cast_nullable_to_non_nullable + as T, + resultHandlerType: freezed == resultHandlerType + ? _value.resultHandlerType + : resultHandlerType // ignore: cast_nullable_to_non_nullable + as TypeMatcher?, + )); + } +} + +/// @nodoc + +class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { + _$ProcessorResultSuccessImpl(this.resultData, + {required this.resultHandlerType}) + : super._(); + + @override + final T resultData; + @override + final TypeMatcher? resultHandlerType; + + @override + String toString() { + return 'ProcessorResult<$T>.success(resultData: $resultData, resultHandlerType: $resultHandlerType)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProcessorResultSuccessImpl && + const DeepCollectionEquality() + .equals(other.resultData, resultData) && + (identical(other.resultHandlerType, resultHandlerType) || + other.resultHandlerType == resultHandlerType)); + } + + @override + int get hashCode => Object.hash(runtimeType, + const DeepCollectionEquality().hash(resultData), resultHandlerType); + + /// Create a copy of ProcessorResult + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProcessorResultSuccessImplCopyWith> + get copyWith => __$$ProcessorResultSuccessImplCopyWithImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + T resultData, TypeMatcher? resultHandlerType) + success, + required TResult Function( + String message, TypeMatcher? resultHandlerType) + failed, + }) { + return success(resultData, resultHandlerType); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + T resultData, TypeMatcher? resultHandlerType)? + success, + TResult? Function( + String message, TypeMatcher? resultHandlerType)? + failed, + }) { + return success?.call(resultData, resultHandlerType); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + T resultData, TypeMatcher? resultHandlerType)? + success, + TResult Function( + String message, TypeMatcher? resultHandlerType)? + failed, + required TResult orElse(), + }) { + if (success != null) { + return success(resultData, resultHandlerType); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(ProcessorResultSuccess value) success, + required TResult Function(ProcessorResultFailed value) failed, + }) { + return success(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(ProcessorResultSuccess value)? success, + TResult? Function(ProcessorResultFailed value)? failed, + }) { + return success?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(ProcessorResultSuccess value)? success, + TResult Function(ProcessorResultFailed value)? failed, + required TResult orElse(), + }) { + if (success != null) { + return success(this); + } + return orElse(); + } +} + +abstract class ProcessorResultSuccess extends ProcessorResult { + factory ProcessorResultSuccess(final T resultData, + {required final TypeMatcher? resultHandlerType}) = + _$ProcessorResultSuccessImpl; + ProcessorResultSuccess._() : super._(); + + T get resultData; + @override + TypeMatcher? get resultHandlerType; + + /// Create a copy of ProcessorResult + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProcessorResultSuccessImplCopyWith> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ProcessorResultFailedImplCopyWith + implements $ProcessorResultCopyWith { + factory _$$ProcessorResultFailedImplCopyWith( + _$ProcessorResultFailedImpl value, + $Res Function(_$ProcessorResultFailedImpl) then) = + __$$ProcessorResultFailedImplCopyWithImpl; + @override + @useResult + $Res call({String message, TypeMatcher? resultHandlerType}); +} + +/// @nodoc +class __$$ProcessorResultFailedImplCopyWithImpl + extends _$ProcessorResultCopyWithImpl> + implements _$$ProcessorResultFailedImplCopyWith { + __$$ProcessorResultFailedImplCopyWithImpl( + _$ProcessorResultFailedImpl _value, + $Res Function(_$ProcessorResultFailedImpl) _then) + : super(_value, _then); + + /// Create a copy of ProcessorResult + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? message = null, + Object? resultHandlerType = freezed, + }) { + return _then(_$ProcessorResultFailedImpl( + null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + resultHandlerType: freezed == resultHandlerType + ? _value.resultHandlerType + : resultHandlerType // ignore: cast_nullable_to_non_nullable + as TypeMatcher?, + )); + } +} + +/// @nodoc + +class _$ProcessorResultFailedImpl extends ProcessorResultFailed { + _$ProcessorResultFailedImpl(this.message, {required this.resultHandlerType}) + : super._(); + + @override + final String message; + @override + final TypeMatcher? resultHandlerType; + + @override + String toString() { + return 'ProcessorResult<$T>.failed(message: $message, resultHandlerType: $resultHandlerType)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProcessorResultFailedImpl && + (identical(other.message, message) || other.message == message) && + (identical(other.resultHandlerType, resultHandlerType) || + other.resultHandlerType == resultHandlerType)); + } + + @override + int get hashCode => Object.hash(runtimeType, message, resultHandlerType); + + /// Create a copy of ProcessorResult + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProcessorResultFailedImplCopyWith> + get copyWith => __$$ProcessorResultFailedImplCopyWithImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + T resultData, TypeMatcher? resultHandlerType) + success, + required TResult Function( + String message, TypeMatcher? resultHandlerType) + failed, + }) { + return failed(message, resultHandlerType); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + T resultData, TypeMatcher? resultHandlerType)? + success, + TResult? Function( + String message, TypeMatcher? resultHandlerType)? + failed, + }) { + return failed?.call(message, resultHandlerType); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + T resultData, TypeMatcher? resultHandlerType)? + success, + TResult Function( + String message, TypeMatcher? resultHandlerType)? + failed, + required TResult orElse(), + }) { + if (failed != null) { + return failed(message, resultHandlerType); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(ProcessorResultSuccess value) success, + required TResult Function(ProcessorResultFailed value) failed, + }) { + return failed(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(ProcessorResultSuccess value)? success, + TResult? Function(ProcessorResultFailed value)? failed, + }) { + return failed?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(ProcessorResultSuccess value)? success, + TResult Function(ProcessorResultFailed value)? failed, + required TResult orElse(), + }) { + if (failed != null) { + return failed(this); + } + return orElse(); + } +} + +abstract class ProcessorResultFailed extends ProcessorResult { + factory ProcessorResultFailed(final String message, + {required final TypeMatcher? resultHandlerType}) = + _$ProcessorResultFailedImpl; + ProcessorResultFailed._() : super._(); + + String get message; + @override + TypeMatcher? get resultHandlerType; + + /// Create a copy of ProcessorResult + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProcessorResultFailedImplCopyWith> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/model/push_request.dart b/lib/model/push_request.dart index 8291f8412..eccd5d8ca 100644 --- a/lib/model/push_request.dart +++ b/lib/model/push_request.dart @@ -25,7 +25,7 @@ import 'package:json_annotation/json_annotation.dart'; import '../utils/globals.dart'; import '../utils/identifiers.dart'; import '../utils/logger.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../utils/rsa_utils.dart'; import 'tokens/push_token.dart'; diff --git a/lib/model/riverpod_states/credentials_state.dart b/lib/model/riverpod_states/credentials_state.dart index 624874442..52e82a763 100644 --- a/lib/model/riverpod_states/credentials_state.dart +++ b/lib/model/riverpod_states/credentials_state.dart @@ -27,18 +27,13 @@ import '../tokens/container_credentials.dart'; part 'credentials_state.freezed.dart'; part 'credentials_state.g.dart'; -@Freezed() +@freezed class CredentialsState with _$CredentialsState { const CredentialsState._(); const factory CredentialsState({ required List credentials, }) = _CredentialsState; - @override - String toString() { - return 'CredentialsState{credentials: $credentials}'; - } - ContainerCredential? credentialsOf(String containerSerial) => credentials.firstWhereOrNull((credential) => credential.serial == containerSerial); static CredentialsState fromJsonStringList(List jsonStrings) { final credentials = jsonStrings.map((jsonString) => ContainerCredential.fromJson(jsonDecode(jsonString))).toList(); diff --git a/lib/model/riverpod_states/credentials_state.freezed.dart b/lib/model/riverpod_states/credentials_state.freezed.dart index 47aaf1de5..09deac54c 100644 --- a/lib/model/riverpod_states/credentials_state.freezed.dart +++ b/lib/model/riverpod_states/credentials_state.freezed.dart @@ -23,8 +23,12 @@ mixin _$CredentialsState { List get credentials => throw _privateConstructorUsedError; + /// Serializes this CredentialsState to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of CredentialsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $CredentialsStateCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -48,6 +52,8 @@ class _$CredentialsStateCopyWithImpl<$Res, $Val extends CredentialsState> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of CredentialsState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -81,6 +87,8 @@ class __$$CredentialsStateImplCopyWithImpl<$Res> $Res Function(_$CredentialsStateImpl) _then) : super(_value, _then); + /// Create a copy of CredentialsState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -114,6 +122,11 @@ class _$CredentialsStateImpl extends _CredentialsState { return EqualUnmodifiableListView(_credentials); } + @override + String toString() { + return 'CredentialsState(credentials: $credentials)'; + } + @override bool operator ==(Object other) { return identical(this, other) || @@ -123,12 +136,14 @@ class _$CredentialsStateImpl extends _CredentialsState { .equals(other._credentials, _credentials)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, const DeepCollectionEquality().hash(_credentials)); - @JsonKey(ignore: true) + /// Create a copy of CredentialsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$CredentialsStateImplCopyWith<_$CredentialsStateImpl> get copyWith => @@ -154,8 +169,11 @@ abstract class _CredentialsState extends CredentialsState { @override List get credentials; + + /// Create a copy of CredentialsState + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$CredentialsStateImplCopyWith<_$CredentialsStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/model/riverpod_states/introduction_state.freezed.dart b/lib/model/riverpod_states/introduction_state.freezed.dart index ba708259d..df478fd59 100644 --- a/lib/model/riverpod_states/introduction_state.freezed.dart +++ b/lib/model/riverpod_states/introduction_state.freezed.dart @@ -23,8 +23,12 @@ mixin _$IntroductionState { Set get completedIntroductions => throw _privateConstructorUsedError; + /// Serializes this IntroductionState to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of IntroductionState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $IntroductionStateCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -48,6 +52,8 @@ class _$IntroductionStateCopyWithImpl<$Res, $Val extends IntroductionState> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of IntroductionState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -81,6 +87,8 @@ class __$$IntroductionStateImplCopyWithImpl<$Res> $Res Function(_$IntroductionStateImpl) _then) : super(_value, _then); + /// Create a copy of IntroductionState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -130,12 +138,14 @@ class _$IntroductionStateImpl extends _IntroductionState { other._completedIntroductions, _completedIntroductions)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(_completedIntroductions)); - @JsonKey(ignore: true) + /// Create a copy of IntroductionState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$IntroductionStateImplCopyWith<_$IntroductionStateImpl> get copyWith => @@ -161,8 +171,11 @@ abstract class _IntroductionState extends IntroductionState { @override Set get completedIntroductions; + + /// Create a copy of IntroductionState + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$IntroductionStateImplCopyWith<_$IntroductionStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/model/riverpod_states/progress_state.freezed.dart b/lib/model/riverpod_states/progress_state.freezed.dart index 7bedbe0db..ae8d6890e 100644 --- a/lib/model/riverpod_states/progress_state.freezed.dart +++ b/lib/model/riverpod_states/progress_state.freezed.dart @@ -57,7 +57,9 @@ mixin _$ProgressState { }) => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of ProgressState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ProgressStateCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -81,6 +83,8 @@ class _$ProgressStateCopyWithImpl<$Res, $Val extends ProgressState> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ProgressState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -121,6 +125,8 @@ class __$$ProgressStateUninitializedImplCopyWithImpl<$Res> $Res Function(_$ProgressStateUninitializedImpl) _then) : super(_value, _then); + /// Create a copy of ProgressState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -170,7 +176,9 @@ class _$ProgressStateUninitializedImpl extends ProgressStateUninitialized { @override int get hashCode => Object.hash(runtimeType, max, value); - @JsonKey(ignore: true) + /// Create a copy of ProgressState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ProgressStateUninitializedImplCopyWith<_$ProgressStateUninitializedImpl> @@ -249,8 +257,11 @@ abstract class ProgressStateUninitialized extends ProgressState { int get max; @override int get value; + + /// Create a copy of ProgressState + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ProgressStateUninitializedImplCopyWith<_$ProgressStateUninitializedImpl> get copyWith => throw _privateConstructorUsedError; } @@ -274,6 +285,8 @@ class __$$ProgressStateImplCopyWithImpl<$Res> _$ProgressStateImpl _value, $Res Function(_$ProgressStateImpl) _then) : super(_value, _then); + /// Create a copy of ProgressState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -323,7 +336,9 @@ class _$ProgressStateImpl extends _ProgressState { @override int get hashCode => Object.hash(runtimeType, max, value); - @JsonKey(ignore: true) + /// Create a copy of ProgressState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ProgressStateImplCopyWith<_$ProgressStateImpl> get copyWith => @@ -401,8 +416,11 @@ abstract class _ProgressState extends ProgressState { int get max; @override int get value; + + /// Create a copy of ProgressState + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ProgressStateImplCopyWith<_$ProgressStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index 3894e3581..76181abdc 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -57,7 +57,7 @@ sealed class TokenContainer with _$TokenContainer { const factory TokenContainer.uninitialized({ // Base fields @Default('PrivacyIDEA') String serverName, - @Default(null) DateTime? lastSyncAt, + DateTime? lastSyncAt, @Default('none') String serial, @Default('Uninitialized') String description, @Default([]) List syncedTokenTemplates, @@ -77,7 +77,7 @@ sealed class TokenContainer with _$TokenContainer { const factory TokenContainer.modified({ // Base fields @Default('PrivacyIDEA') String serverName, - @Default(null) DateTime? lastSyncAt, + DateTime? lastSyncAt, required String serial, required String description, required List syncedTokenTemplates, @@ -88,7 +88,7 @@ sealed class TokenContainer with _$TokenContainer { const factory TokenContainer.unsynced({ // Base fields @Default('PrivacyIDEA') String serverName, - @Default(null) DateTime? lastSyncAt, + DateTime? lastSyncAt, required String serial, required String description, required List syncedTokenTemplates, @@ -110,7 +110,7 @@ sealed class TokenContainer with _$TokenContainer { const factory TokenContainer.error({ // Base fields @Default('PrivacyIDEA') String serverName, - @Default(null) DateTime? lastSyncAt, + DateTime? lastSyncAt, @Default('none') String serial, @Default('Error') String description, @Default([]) List syncedTokenTemplates, diff --git a/lib/model/token_container.freezed.dart b/lib/model/token_container.freezed.dart index 0df17a59d..b52533a13 100644 --- a/lib/model/token_container.freezed.dart +++ b/lib/model/token_container.freezed.dart @@ -246,8 +246,13 @@ mixin _$TokenContainer { required TResult orElse(), }) => throw _privateConstructorUsedError; + + /// Serializes this TokenContainer to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $TokenContainerCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -277,6 +282,8 @@ class _$TokenContainerCopyWithImpl<$Res, $Val extends TokenContainer> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -344,6 +351,8 @@ class __$$TokenContainerUninitializedImplCopyWithImpl<$Res> $Res Function(_$TokenContainerUninitializedImpl) _then) : super(_value, _then); + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -389,7 +398,7 @@ class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized with DiagnosticableTreeMixin { const _$TokenContainerUninitializedImpl( {this.serverName = 'PrivacyIDEA', - this.lastSyncAt = null, + this.lastSyncAt, this.serial = 'none', this.description = 'Uninitialized', final List syncedTokenTemplates = const [], @@ -409,7 +418,6 @@ class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized @JsonKey() final String serverName; @override - @JsonKey() final DateTime? lastSyncAt; @override @JsonKey() @@ -476,7 +484,7 @@ class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized .equals(other._localTokenTemplates, _localTokenTemplates)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -487,7 +495,9 @@ class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized const DeepCollectionEquality().hash(_syncedTokenTemplates), const DeepCollectionEquality().hash(_localTokenTemplates)); - @JsonKey(ignore: true) + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$TokenContainerUninitializedImplCopyWith<_$TokenContainerUninitializedImpl> @@ -743,7 +753,8 @@ abstract class TokenContainerUninitialized extends TokenContainer { factory TokenContainerUninitialized.fromJson(Map json) = _$TokenContainerUninitializedImpl.fromJson; - @override // Base fields +// Base fields + @override String get serverName; @override DateTime? get lastSyncAt; @@ -755,8 +766,11 @@ abstract class TokenContainerUninitialized extends TokenContainer { List get syncedTokenTemplates; @override List get localTokenTemplates; + + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$TokenContainerUninitializedImplCopyWith<_$TokenContainerUninitializedImpl> get copyWith => throw _privateConstructorUsedError; } @@ -786,6 +800,8 @@ class __$$TokenContainerSyncedImplCopyWithImpl<$Res> $Res Function(_$TokenContainerSyncedImpl) _then) : super(_value, _then); + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -912,7 +928,7 @@ class _$TokenContainerSyncedImpl extends TokenContainerSynced .equals(other._localTokenTemplates, _localTokenTemplates)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -923,7 +939,9 @@ class _$TokenContainerSyncedImpl extends TokenContainerSynced const DeepCollectionEquality().hash(_syncedTokenTemplates), const DeepCollectionEquality().hash(_localTokenTemplates)); - @JsonKey(ignore: true) + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$TokenContainerSyncedImplCopyWith<_$TokenContainerSyncedImpl> @@ -1180,7 +1198,8 @@ abstract class TokenContainerSynced extends TokenContainer { factory TokenContainerSynced.fromJson(Map json) = _$TokenContainerSyncedImpl.fromJson; - @override // Base fields +// Base fields + @override String get serverName; @override DateTime get lastSyncAt; @@ -1192,8 +1211,11 @@ abstract class TokenContainerSynced extends TokenContainer { List get syncedTokenTemplates; @override List get localTokenTemplates; + + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$TokenContainerSyncedImplCopyWith<_$TokenContainerSyncedImpl> get copyWith => throw _privateConstructorUsedError; } @@ -1226,6 +1248,8 @@ class __$$TokenContainerModifiedImplCopyWithImpl<$Res> $Res Function(_$TokenContainerModifiedImpl) _then) : super(_value, _then); + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -1276,7 +1300,7 @@ class _$TokenContainerModifiedImpl extends TokenContainerModified with DiagnosticableTreeMixin { const _$TokenContainerModifiedImpl( {this.serverName = 'PrivacyIDEA', - this.lastSyncAt = null, + this.lastSyncAt, required this.serial, required this.description, required final List syncedTokenTemplates, @@ -1296,7 +1320,6 @@ class _$TokenContainerModifiedImpl extends TokenContainerModified @JsonKey() final String serverName; @override - @JsonKey() final DateTime? lastSyncAt; @override final String serial; @@ -1366,7 +1389,7 @@ class _$TokenContainerModifiedImpl extends TokenContainerModified other.lastModifiedAt == lastModifiedAt)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -1378,7 +1401,9 @@ class _$TokenContainerModifiedImpl extends TokenContainerModified const DeepCollectionEquality().hash(_localTokenTemplates), lastModifiedAt); - @JsonKey(ignore: true) + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$TokenContainerModifiedImplCopyWith<_$TokenContainerModifiedImpl> @@ -1634,7 +1659,8 @@ abstract class TokenContainerModified extends TokenContainer { factory TokenContainerModified.fromJson(Map json) = _$TokenContainerModifiedImpl.fromJson; - @override // Base fields +// Base fields + @override String get serverName; @override DateTime? get lastSyncAt; @@ -1647,8 +1673,11 @@ abstract class TokenContainerModified extends TokenContainer { @override List get localTokenTemplates; // Base fields end DateTime get lastModifiedAt; + + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$TokenContainerModifiedImplCopyWith<_$TokenContainerModifiedImpl> get copyWith => throw _privateConstructorUsedError; } @@ -1681,6 +1710,8 @@ class __$$TokenContainerUnsyncedImplCopyWithImpl<$Res> $Res Function(_$TokenContainerUnsyncedImpl) _then) : super(_value, _then); + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -1731,7 +1762,7 @@ class _$TokenContainerUnsyncedImpl extends TokenContainerUnsynced with DiagnosticableTreeMixin { const _$TokenContainerUnsyncedImpl( {this.serverName = 'PrivacyIDEA', - this.lastSyncAt = null, + this.lastSyncAt, required this.serial, required this.description, required final List syncedTokenTemplates, @@ -1751,7 +1782,6 @@ class _$TokenContainerUnsyncedImpl extends TokenContainerUnsynced @JsonKey() final String serverName; @override - @JsonKey() final DateTime? lastSyncAt; @override final String serial; @@ -1820,7 +1850,7 @@ class _$TokenContainerUnsyncedImpl extends TokenContainerUnsynced (identical(other.message, message) || other.message == message)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -1832,7 +1862,9 @@ class _$TokenContainerUnsyncedImpl extends TokenContainerUnsynced const DeepCollectionEquality().hash(_localTokenTemplates), message); - @JsonKey(ignore: true) + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$TokenContainerUnsyncedImplCopyWith<_$TokenContainerUnsyncedImpl> @@ -2088,7 +2120,8 @@ abstract class TokenContainerUnsynced extends TokenContainer { factory TokenContainerUnsynced.fromJson(Map json) = _$TokenContainerUnsyncedImpl.fromJson; - @override // Base fields +// Base fields + @override String get serverName; @override DateTime? get lastSyncAt; @@ -2101,8 +2134,11 @@ abstract class TokenContainerUnsynced extends TokenContainer { @override List get localTokenTemplates; // Base fields end String? get message; + + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$TokenContainerUnsyncedImplCopyWith<_$TokenContainerUnsyncedImpl> get copyWith => throw _privateConstructorUsedError; } @@ -2135,6 +2171,8 @@ class __$$TokenContainerNotFoundImplCopyWithImpl<$Res> $Res Function(_$TokenContainerNotFoundImpl) _then) : super(_value, _then); + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -2273,7 +2311,7 @@ class _$TokenContainerNotFoundImpl extends TokenContainerNotFound (identical(other.message, message) || other.message == message)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -2285,7 +2323,9 @@ class _$TokenContainerNotFoundImpl extends TokenContainerNotFound const DeepCollectionEquality().hash(_localTokenTemplates), message); - @JsonKey(ignore: true) + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$TokenContainerNotFoundImplCopyWith<_$TokenContainerNotFoundImpl> @@ -2541,7 +2581,8 @@ abstract class TokenContainerNotFound extends TokenContainer { factory TokenContainerNotFound.fromJson(Map json) = _$TokenContainerNotFoundImpl.fromJson; - @override // Base fields +// Base fields + @override String get serverName; @override DateTime? get lastSyncAt; @@ -2554,8 +2595,11 @@ abstract class TokenContainerNotFound extends TokenContainer { @override List get localTokenTemplates; // Base fields end String get message; + + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$TokenContainerNotFoundImplCopyWith<_$TokenContainerNotFoundImpl> get copyWith => throw _privateConstructorUsedError; } @@ -2586,6 +2630,8 @@ class __$$TokenContainerErrorImplCopyWithImpl<$Res> $Res Function(_$TokenContainerErrorImpl) _then) : super(_value, _then); + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -2636,7 +2682,7 @@ class _$TokenContainerErrorImpl extends TokenContainerError with DiagnosticableTreeMixin { const _$TokenContainerErrorImpl( {this.serverName = 'PrivacyIDEA', - this.lastSyncAt = null, + this.lastSyncAt, this.serial = 'none', this.description = 'Error', final List syncedTokenTemplates = const [], @@ -2656,7 +2702,6 @@ class _$TokenContainerErrorImpl extends TokenContainerError @JsonKey() final String serverName; @override - @JsonKey() final DateTime? lastSyncAt; @override @JsonKey() @@ -2729,7 +2774,7 @@ class _$TokenContainerErrorImpl extends TokenContainerError const DeepCollectionEquality().equals(other.error, error)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -2741,7 +2786,9 @@ class _$TokenContainerErrorImpl extends TokenContainerError const DeepCollectionEquality().hash(_localTokenTemplates), const DeepCollectionEquality().hash(error)); - @JsonKey(ignore: true) + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$TokenContainerErrorImplCopyWith<_$TokenContainerErrorImpl> get copyWith => @@ -2997,7 +3044,8 @@ abstract class TokenContainerError extends TokenContainer { factory TokenContainerError.fromJson(Map json) = _$TokenContainerErrorImpl.fromJson; - @override // Base fields +// Base fields + @override String get serverName; @override DateTime? get lastSyncAt; @@ -3010,8 +3058,11 @@ abstract class TokenContainerError extends TokenContainer { @override List get localTokenTemplates; // Base fields end dynamic get error; + + /// Create a copy of TokenContainer + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$TokenContainerErrorImplCopyWith<_$TokenContainerErrorImpl> get copyWith => throw _privateConstructorUsedError; } @@ -3024,8 +3075,12 @@ TokenTemplate _$TokenTemplateFromJson(Map json) { mixin _$TokenTemplate { Map get data => throw _privateConstructorUsedError; + /// Serializes this TokenTemplate to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $TokenTemplateCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -3049,6 +3104,8 @@ class _$TokenTemplateCopyWithImpl<$Res, $Val extends TokenTemplate> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -3082,6 +3139,8 @@ class __$$TokenTemplateImplCopyWithImpl<$Res> _$TokenTemplateImpl _value, $Res Function(_$TokenTemplateImpl) _then) : super(_value, _then); + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -3127,7 +3186,9 @@ class _$TokenTemplateImpl extends _TokenTemplate with DiagnosticableTreeMixin { ..add(DiagnosticsProperty('data', data)); } - @JsonKey(ignore: true) + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$TokenTemplateImplCopyWith<_$TokenTemplateImpl> get copyWith => @@ -3151,8 +3212,11 @@ abstract class _TokenTemplate extends TokenTemplate { @override Map get data; + + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$TokenTemplateImplCopyWith<_$TokenTemplateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/model/tokens/container_credentials.dart b/lib/model/tokens/container_credentials.dart index d2f32b358..a45c62209 100644 --- a/lib/model/tokens/container_credentials.dart +++ b/lib/model/tokens/container_credentials.dart @@ -1,3 +1,5 @@ +// ignore_for_file: constant_identifier_names + /* * privacyIDEA Authenticator * @@ -17,22 +19,294 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'package:json_annotation/json_annotation.dart'; -import 'push_token.dart'; +import 'dart:typed_data'; + +import 'package:basic_utils/basic_utils.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; + +import '../../utils/logger.dart'; +part 'container_credentials.freezed.dart'; part 'container_credentials.g.dart'; -@JsonSerializable() -class ContainerCredential extends PushToken { - ContainerCredential({required super.serial, required super.id}); +// issuer=privacyIDEA +// &serial=SMPH00134123 +// &nonce=887197025f5fa59b50f33c15196eb97ee651a5d1 +// &time=2024-08-21T07%3A43%3A07.086670%2B00%3A00 +// &url="http://127.0.0.1:5000/container/register/finalize" +// &key_algorithm=secp384r1 +// &hash_algorithm=SHA256 +// &passphrase=Enter%20your%20passphrase + +@freezed +class ContainerCredential with _$ContainerCredential { + static const eccUtils = EccUtils(); + const ContainerCredential._(); + factory ContainerCredential.fromUriMap(Map uriMap) { + validateMap(uriMap, { + URI_ISSUER: const TypeMatcher(), + URI_NONCE: const TypeMatcher(), + URI_TIMESTAMP: const TypeMatcher(), + URI_FINALIZATION_URL: const TypeMatcher(), + URI_SERIAL: const TypeMatcher(), + URI_KEY_ALGORITHM: const TypeMatcher(), + URI_HASH_ALGORITHM: const TypeMatcher(), + }); + return ContainerCredential.unfinalized( + issuer: uriMap[URI_ISSUER], + nonce: uriMap[URI_NONCE], + timestamp: DateTime.parse(uriMap[URI_TIMESTAMP]), + finalizationUrl: Uri.parse(uriMap[URI_FINALIZATION_URL]), + serial: uriMap[URI_SERIAL], + ecKeyAlgorithm: EcKeyAlgorithm.values.byCurveName(uriMap[URI_KEY_ALGORITHM]), + hashAlgorithm: Algorithms.values.byName(uriMap[URI_HASH_ALGORITHM]), + ); + } + + const factory ContainerCredential.unfinalized({ + required String issuer, + required String nonce, + required DateTime timestamp, + required Uri finalizationUrl, + required String serial, + required EcKeyAlgorithm ecKeyAlgorithm, + required Algorithms hashAlgorithm, + @Default(ContainerCredentialState.uninitialized) ContainerCredentialState state, + String? passphrase, + String? publicServerKey, + String? publicClientKey, + String? privateClientKey, + }) = ContainerCredentialUnfinalized; - factory ContainerCredential.fromUriMap(Map uriMap) => throw UnimplementedError(); // PushToken.fromUriMap(uriMap); + const factory ContainerCredential.finalized({ + required String issuer, + required String nonce, + required DateTime timestamp, + required Uri finalizationUrl, + required String serial, + required EcKeyAlgorithm ecKeyAlgorithm, + required Algorithms hashAlgorithm, + @Default(ContainerCredentialState.finalized) ContainerCredentialState state, + String? passphrase, + required String publicServerKey, + required String publicClientKey, + required String privateClientKey, + }) = ContainerCredentialFinalized; + ContainerCredentialFinalized? finalize({ + ECPublicKey? publicServerKey, + AsymmetricKeyPair? clientKeyPair, + }) { + if (this is ContainerCredentialFinalized) return this as ContainerCredentialFinalized; + if (publicServerKey == null && this.publicServerKey == null) { + Logger.warning('Unable to finalize without public server key'); + return null; + } + assert(publicServerKey != null || this.publicServerKey != null, 'Unable to finalize without public server key'); + if (clientKeyPair == null && (publicClientKey == null || privateClientKey == null)) { + Logger.warning('Unable to finalize without client key pair'); + return null; + } + return ContainerCredentialFinalized( + issuer: issuer, + nonce: nonce, + timestamp: timestamp, + finalizationUrl: finalizationUrl, + serial: serial, + ecKeyAlgorithm: ecKeyAlgorithm, + hashAlgorithm: hashAlgorithm, + passphrase: passphrase, + state: ContainerCredentialState.finalized, + publicServerKey: this.publicServerKey ?? eccUtils.serializeECPublicKey(publicServerKey!), + publicClientKey: publicClientKey ?? eccUtils.serializeECPublicKey(clientKeyPair!.publicKey), + privateClientKey: privateClientKey ?? eccUtils.serializeECPrivateKey(clientKeyPair!.privateKey), + ); + } + + ECPublicKey? get ecPublicServerKey => publicServerKey == null ? null : eccUtils.deserializeECPublicKey(publicServerKey!); + ContainerCredential withPublicServerKey(ECPublicKey publicServerKey) => copyWith(publicServerKey: eccUtils.serializeECPublicKey(publicServerKey)); + ECPublicKey? get ecPublicClientKey => publicClientKey == null ? null : eccUtils.deserializeECPublicKey(publicClientKey!); + ECPrivateKey? get ecPrivateClientKey => privateClientKey == null ? null : eccUtils.deserializeECPrivateKey(privateClientKey!); + ContainerCredential withClientKeyPair(AsymmetricKeyPair keyPair) => copyWith( + publicClientKey: eccUtils.serializeECPublicKey(keyPair.publicKey), + privateClientKey: eccUtils.serializeECPrivateKey(keyPair.privateKey), + ); factory ContainerCredential.fromJson(Map json) => _$ContainerCredentialFromJson(json); - @override - Map toJson() => _$ContainerCredentialToJson(this); - @override - String toString() => 'ContainerCredential{serial: $serial, id: $id}'; + // // Sign with private client key + // String signMessage(String message) { + // final signer = Signer(hashAlgorithm.name); + // signer.init(true, PrivateKey(ecPrivateClientKey!.d, ecPrivateClientKey!.parameters)); + // return signer.signMessage(message); + // }; +} + +enum ContainerCredentialState { + uninitialized, + generatingKeyPair, + generatingKeyPairFailed, + sendingPublicKey, + sendingPublicKeyFailed, + parsingResponse, + parsingResponseFailed, + finalized, +} + +/// The following curves are supported: + +enum EcKeyAlgorithm { + brainpoolp160r1, + brainpoolp160t1, + brainpoolp192r1, + brainpoolp192t1, + brainpoolp224r1, + brainpoolp224t1, + brainpoolp256r1, + brainpoolp256t1, + brainpoolp320r1, + brainpoolp320t1, + brainpoolp384r1, + brainpoolp384t1, + brainpoolp512r1, + brainpoolp512t1, + GostR3410_2001_CryptoPro_A, + GostR3410_2001_CryptoPro_B, + GostR3410_2001_CryptoPro_C, + GostR3410_2001_CryptoPro_XchA, + GostR3410_2001_CryptoPro_XchB, + prime192v1, + prime192v2, + prime192v3, + prime239v1, + prime239v2, + prime239v3, + prime256v1, + secp112r1, + secp112r2, + secp128r1, + secp128r2, + secp160k1, + secp160r1, + secp160r2, + secp192k1, + secp192r1, + secp224k1, + secp224r1, + secp256k1, + secp256r1, + secp384r1, + secp521r1, +} + +extension EcKeyAlgorithmList on List { + EcKeyAlgorithm byCurveName(String domainName) => switch (domainName) { + 'brainpoolp160r1' => EcKeyAlgorithm.brainpoolp160r1, + 'brainpoolp160t1' => EcKeyAlgorithm.brainpoolp160t1, + 'brainpoolp192r1' => EcKeyAlgorithm.brainpoolp192r1, + 'brainpoolp192t1' => EcKeyAlgorithm.brainpoolp192t1, + 'brainpoolp224r1' => EcKeyAlgorithm.brainpoolp224r1, + 'brainpoolp224t1' => EcKeyAlgorithm.brainpoolp224t1, + 'brainpoolp256r1' => EcKeyAlgorithm.brainpoolp256r1, + 'brainpoolp256t1' => EcKeyAlgorithm.brainpoolp256t1, + 'brainpoolp320r1' => EcKeyAlgorithm.brainpoolp320r1, + 'brainpoolp320t1' => EcKeyAlgorithm.brainpoolp320t1, + 'brainpoolp384r1' => EcKeyAlgorithm.brainpoolp384r1, + 'brainpoolp384t1' => EcKeyAlgorithm.brainpoolp384t1, + 'brainpoolp512r1' => EcKeyAlgorithm.brainpoolp512r1, + 'brainpoolp512t1' => EcKeyAlgorithm.brainpoolp512t1, + 'GostR3410-2001-CryptoPro-A' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_A, + 'GostR3410-2001-CryptoPro-B' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_B, + 'GostR3410-2001-CryptoPro-C' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_C, + 'GostR3410-2001-CryptoPro-XchA' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchA, + 'GostR3410-2001-CryptoPro-XchB' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchB, + 'prime192v1' => EcKeyAlgorithm.prime192v1, + 'prime192v2' => EcKeyAlgorithm.prime192v2, + 'prime192v3' => EcKeyAlgorithm.prime192v3, + 'prime239v1' => EcKeyAlgorithm.prime239v1, + 'prime239v2' => EcKeyAlgorithm.prime239v2, + 'prime239v3' => EcKeyAlgorithm.prime239v3, + 'prime256v1' => EcKeyAlgorithm.prime256v1, + 'secp112r1' => EcKeyAlgorithm.secp112r1, + 'secp112r2' => EcKeyAlgorithm.secp112r2, + 'secp128r1' => EcKeyAlgorithm.secp128r1, + 'secp128r2' => EcKeyAlgorithm.secp128r2, + 'secp160k1' => EcKeyAlgorithm.secp160k1, + 'secp160r1' => EcKeyAlgorithm.secp160r1, + 'secp160r2' => EcKeyAlgorithm.secp160r2, + 'secp192k1' => EcKeyAlgorithm.secp192k1, + 'secp192r1' => EcKeyAlgorithm.secp192r1, + 'secp224k1' => EcKeyAlgorithm.secp224k1, + 'secp224r1' => EcKeyAlgorithm.secp224r1, + 'secp256k1' => EcKeyAlgorithm.secp256k1, + 'secp256r1' => EcKeyAlgorithm.secp256r1, + 'secp384r1' => EcKeyAlgorithm.secp384r1, + 'secp521r1' => EcKeyAlgorithm.secp521r1, + _ => throw ArgumentError('Unknown domain name: $domainName'), + }; +} + +extension EcKeyAlgorithmX on EcKeyAlgorithm { + String get curveName => switch (this) { + EcKeyAlgorithm.brainpoolp160r1 => 'brainpoolp160r1', + EcKeyAlgorithm.brainpoolp160t1 => 'brainpoolp160t1', + EcKeyAlgorithm.brainpoolp192r1 => 'brainpoolp192r1', + EcKeyAlgorithm.brainpoolp192t1 => 'brainpoolp192t1', + EcKeyAlgorithm.brainpoolp224r1 => 'brainpoolp224r1', + EcKeyAlgorithm.brainpoolp224t1 => 'brainpoolp224t1', + EcKeyAlgorithm.brainpoolp256r1 => 'brainpoolp256r1', + EcKeyAlgorithm.brainpoolp256t1 => 'brainpoolp256t1', + EcKeyAlgorithm.brainpoolp320r1 => 'brainpoolp320r1', + EcKeyAlgorithm.brainpoolp320t1 => 'brainpoolp320t1', + EcKeyAlgorithm.brainpoolp384r1 => 'brainpoolp384r1', + EcKeyAlgorithm.brainpoolp384t1 => 'brainpoolp384t1', + EcKeyAlgorithm.brainpoolp512r1 => 'brainpoolp512r1', + EcKeyAlgorithm.brainpoolp512t1 => 'brainpoolp512t1', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_A => 'GostR3410-2001-CryptoPro-A', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_B => 'GostR3410-2001-CryptoPro-B', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_C => 'GostR3410-2001-CryptoPro-C', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchA => 'GostR3410-2001-CryptoPro-XchA', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchB => 'GostR3410-2001-CryptoPro-XchB', + EcKeyAlgorithm.prime192v1 => 'prime192v1', + EcKeyAlgorithm.prime192v2 => 'prime192v2', + EcKeyAlgorithm.prime192v3 => 'prime192v3', + EcKeyAlgorithm.prime239v1 => 'prime239v1', + EcKeyAlgorithm.prime239v2 => 'prime239v2', + EcKeyAlgorithm.prime239v3 => 'prime239v3', + EcKeyAlgorithm.prime256v1 => 'prime256v1', + EcKeyAlgorithm.secp112r1 => 'secp112r1', + EcKeyAlgorithm.secp112r2 => 'secp112r2', + EcKeyAlgorithm.secp128r1 => 'secp128r1', + EcKeyAlgorithm.secp128r2 => 'secp128r2', + EcKeyAlgorithm.secp160k1 => 'secp160k1', + EcKeyAlgorithm.secp160r1 => 'secp160r1', + EcKeyAlgorithm.secp160r2 => 'secp160r2', + EcKeyAlgorithm.secp192k1 => 'secp192k1', + EcKeyAlgorithm.secp192r1 => 'secp192r1', + EcKeyAlgorithm.secp224k1 => 'secp224k1', + EcKeyAlgorithm.secp224r1 => 'secp224r1', + EcKeyAlgorithm.secp256k1 => 'secp256k1', + EcKeyAlgorithm.secp256r1 => 'secp256r1', + EcKeyAlgorithm.secp384r1 => 'secp384r1', + EcKeyAlgorithm.secp521r1 => 'secp521r1', + }; +} + +class EccUtils { + final String algorithmName = 'EC'; + + const EccUtils(); + + String serializeECPublicKey(ECPublicKey publicKey) => CryptoUtils.encodeEcPublicKeyToPem(publicKey); + ECPublicKey deserializeECPublicKey(String ecPublicKey) => CryptoUtils.ecPublicKeyFromPem(ecPublicKey); + String serializeECPrivateKey(ECPrivateKey ecPrivateKey) => CryptoUtils.encodeEcPrivateKeyToPem(ecPrivateKey); + ECPrivateKey deserializeECPrivateKey(String ecPrivateKey) => CryptoUtils.ecPrivateKeyFromPem(ecPrivateKey); + + String trySignWithPrivateKey(ECPrivateKey privateKey, String message) { + final ecSignature = CryptoUtils.ecSign(privateKey, Uint8List.fromList(message.codeUnits)); + String signatureBase64 = CryptoUtils.ecSignatureToBase64(ecSignature); + return signatureBase64; + } } diff --git a/lib/model/tokens/container_credentials.freezed.dart b/lib/model/tokens/container_credentials.freezed.dart new file mode 100644 index 000000000..7f142b19d --- /dev/null +++ b/lib/model/tokens/container_credentials.freezed.dart @@ -0,0 +1,1181 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'container_credentials.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +ContainerCredential _$ContainerCredentialFromJson(Map json) { + switch (json['runtimeType']) { + case 'unfinalized': + return ContainerCredentialUnfinalized.fromJson(json); + case 'finalized': + return ContainerCredentialFinalized.fromJson(json); + + default: + throw CheckedFromJsonException(json, 'runtimeType', 'ContainerCredential', + 'Invalid union type "${json['runtimeType']}"!'); + } +} + +/// @nodoc +mixin _$ContainerCredential { + String get issuer => throw _privateConstructorUsedError; + String get nonce => throw _privateConstructorUsedError; + DateTime get timestamp => throw _privateConstructorUsedError; + Uri get finalizationUrl => throw _privateConstructorUsedError; + String get serial => throw _privateConstructorUsedError; + EcKeyAlgorithm get ecKeyAlgorithm => throw _privateConstructorUsedError; + Algorithms get hashAlgorithm => throw _privateConstructorUsedError; + ContainerCredentialState get state => throw _privateConstructorUsedError; + String? get passphrase => throw _privateConstructorUsedError; + String? get publicServerKey => throw _privateConstructorUsedError; + String? get publicClientKey => throw _privateConstructorUsedError; + String? get privateClientKey => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String? publicServerKey, + String? publicClientKey, + String? privateClientKey) + unfinalized, + required TResult Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String publicServerKey, + String publicClientKey, + String privateClientKey) + finalized, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String? publicServerKey, + String? publicClientKey, + String? privateClientKey)? + unfinalized, + TResult? Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String publicServerKey, + String publicClientKey, + String privateClientKey)? + finalized, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String? publicServerKey, + String? publicClientKey, + String? privateClientKey)? + unfinalized, + TResult Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String publicServerKey, + String publicClientKey, + String privateClientKey)? + finalized, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(ContainerCredentialUnfinalized value) unfinalized, + required TResult Function(ContainerCredentialFinalized value) finalized, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(ContainerCredentialUnfinalized value)? unfinalized, + TResult? Function(ContainerCredentialFinalized value)? finalized, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(ContainerCredentialUnfinalized value)? unfinalized, + TResult Function(ContainerCredentialFinalized value)? finalized, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Serializes this ContainerCredential to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ContainerCredential + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ContainerCredentialCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ContainerCredentialCopyWith<$Res> { + factory $ContainerCredentialCopyWith( + ContainerCredential value, $Res Function(ContainerCredential) then) = + _$ContainerCredentialCopyWithImpl<$Res, ContainerCredential>; + @useResult + $Res call( + {String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String publicServerKey, + String publicClientKey, + String privateClientKey}); +} + +/// @nodoc +class _$ContainerCredentialCopyWithImpl<$Res, $Val extends ContainerCredential> + implements $ContainerCredentialCopyWith<$Res> { + _$ContainerCredentialCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ContainerCredential + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? issuer = null, + Object? nonce = null, + Object? timestamp = null, + Object? finalizationUrl = null, + Object? serial = null, + Object? ecKeyAlgorithm = null, + Object? hashAlgorithm = null, + Object? state = null, + Object? passphrase = freezed, + Object? publicServerKey = null, + Object? publicClientKey = null, + Object? privateClientKey = null, + }) { + return _then(_value.copyWith( + issuer: null == issuer + ? _value.issuer + : issuer // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + timestamp: null == timestamp + ? _value.timestamp + : timestamp // ignore: cast_nullable_to_non_nullable + as DateTime, + finalizationUrl: null == finalizationUrl + ? _value.finalizationUrl + : finalizationUrl // ignore: cast_nullable_to_non_nullable + as Uri, + serial: null == serial + ? _value.serial + : serial // ignore: cast_nullable_to_non_nullable + as String, + ecKeyAlgorithm: null == ecKeyAlgorithm + ? _value.ecKeyAlgorithm + : ecKeyAlgorithm // ignore: cast_nullable_to_non_nullable + as EcKeyAlgorithm, + hashAlgorithm: null == hashAlgorithm + ? _value.hashAlgorithm + : hashAlgorithm // ignore: cast_nullable_to_non_nullable + as Algorithms, + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as ContainerCredentialState, + passphrase: freezed == passphrase + ? _value.passphrase + : passphrase // ignore: cast_nullable_to_non_nullable + as String?, + publicServerKey: null == publicServerKey + ? _value.publicServerKey! + : publicServerKey // ignore: cast_nullable_to_non_nullable + as String, + publicClientKey: null == publicClientKey + ? _value.publicClientKey! + : publicClientKey // ignore: cast_nullable_to_non_nullable + as String, + privateClientKey: null == privateClientKey + ? _value.privateClientKey! + : privateClientKey // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ContainerCredentialUnfinalizedImplCopyWith<$Res> + implements $ContainerCredentialCopyWith<$Res> { + factory _$$ContainerCredentialUnfinalizedImplCopyWith( + _$ContainerCredentialUnfinalizedImpl value, + $Res Function(_$ContainerCredentialUnfinalizedImpl) then) = + __$$ContainerCredentialUnfinalizedImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String? publicServerKey, + String? publicClientKey, + String? privateClientKey}); +} + +/// @nodoc +class __$$ContainerCredentialUnfinalizedImplCopyWithImpl<$Res> + extends _$ContainerCredentialCopyWithImpl<$Res, + _$ContainerCredentialUnfinalizedImpl> + implements _$$ContainerCredentialUnfinalizedImplCopyWith<$Res> { + __$$ContainerCredentialUnfinalizedImplCopyWithImpl( + _$ContainerCredentialUnfinalizedImpl _value, + $Res Function(_$ContainerCredentialUnfinalizedImpl) _then) + : super(_value, _then); + + /// Create a copy of ContainerCredential + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? issuer = null, + Object? nonce = null, + Object? timestamp = null, + Object? finalizationUrl = null, + Object? serial = null, + Object? ecKeyAlgorithm = null, + Object? hashAlgorithm = null, + Object? state = null, + Object? passphrase = freezed, + Object? publicServerKey = freezed, + Object? publicClientKey = freezed, + Object? privateClientKey = freezed, + }) { + return _then(_$ContainerCredentialUnfinalizedImpl( + issuer: null == issuer + ? _value.issuer + : issuer // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + timestamp: null == timestamp + ? _value.timestamp + : timestamp // ignore: cast_nullable_to_non_nullable + as DateTime, + finalizationUrl: null == finalizationUrl + ? _value.finalizationUrl + : finalizationUrl // ignore: cast_nullable_to_non_nullable + as Uri, + serial: null == serial + ? _value.serial + : serial // ignore: cast_nullable_to_non_nullable + as String, + ecKeyAlgorithm: null == ecKeyAlgorithm + ? _value.ecKeyAlgorithm + : ecKeyAlgorithm // ignore: cast_nullable_to_non_nullable + as EcKeyAlgorithm, + hashAlgorithm: null == hashAlgorithm + ? _value.hashAlgorithm + : hashAlgorithm // ignore: cast_nullable_to_non_nullable + as Algorithms, + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as ContainerCredentialState, + passphrase: freezed == passphrase + ? _value.passphrase + : passphrase // ignore: cast_nullable_to_non_nullable + as String?, + publicServerKey: freezed == publicServerKey + ? _value.publicServerKey + : publicServerKey // ignore: cast_nullable_to_non_nullable + as String?, + publicClientKey: freezed == publicClientKey + ? _value.publicClientKey + : publicClientKey // ignore: cast_nullable_to_non_nullable + as String?, + privateClientKey: freezed == privateClientKey + ? _value.privateClientKey + : privateClientKey // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ContainerCredentialUnfinalizedImpl + extends ContainerCredentialUnfinalized { + const _$ContainerCredentialUnfinalizedImpl( + {required this.issuer, + required this.nonce, + required this.timestamp, + required this.finalizationUrl, + required this.serial, + required this.ecKeyAlgorithm, + required this.hashAlgorithm, + this.state = ContainerCredentialState.uninitialized, + this.passphrase, + this.publicServerKey, + this.publicClientKey, + this.privateClientKey, + final String? $type}) + : $type = $type ?? 'unfinalized', + super._(); + + factory _$ContainerCredentialUnfinalizedImpl.fromJson( + Map json) => + _$$ContainerCredentialUnfinalizedImplFromJson(json); + + @override + final String issuer; + @override + final String nonce; + @override + final DateTime timestamp; + @override + final Uri finalizationUrl; + @override + final String serial; + @override + final EcKeyAlgorithm ecKeyAlgorithm; + @override + final Algorithms hashAlgorithm; + @override + @JsonKey() + final ContainerCredentialState state; + @override + final String? passphrase; + @override + final String? publicServerKey; + @override + final String? publicClientKey; + @override + final String? privateClientKey; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'ContainerCredential.unfinalized(issuer: $issuer, nonce: $nonce, timestamp: $timestamp, finalizationUrl: $finalizationUrl, serial: $serial, ecKeyAlgorithm: $ecKeyAlgorithm, hashAlgorithm: $hashAlgorithm, state: $state, passphrase: $passphrase, publicServerKey: $publicServerKey, publicClientKey: $publicClientKey, privateClientKey: $privateClientKey)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ContainerCredentialUnfinalizedImpl && + (identical(other.issuer, issuer) || other.issuer == issuer) && + (identical(other.nonce, nonce) || other.nonce == nonce) && + (identical(other.timestamp, timestamp) || + other.timestamp == timestamp) && + (identical(other.finalizationUrl, finalizationUrl) || + other.finalizationUrl == finalizationUrl) && + (identical(other.serial, serial) || other.serial == serial) && + (identical(other.ecKeyAlgorithm, ecKeyAlgorithm) || + other.ecKeyAlgorithm == ecKeyAlgorithm) && + (identical(other.hashAlgorithm, hashAlgorithm) || + other.hashAlgorithm == hashAlgorithm) && + (identical(other.state, state) || other.state == state) && + (identical(other.passphrase, passphrase) || + other.passphrase == passphrase) && + (identical(other.publicServerKey, publicServerKey) || + other.publicServerKey == publicServerKey) && + (identical(other.publicClientKey, publicClientKey) || + other.publicClientKey == publicClientKey) && + (identical(other.privateClientKey, privateClientKey) || + other.privateClientKey == privateClientKey)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + issuer, + nonce, + timestamp, + finalizationUrl, + serial, + ecKeyAlgorithm, + hashAlgorithm, + state, + passphrase, + publicServerKey, + publicClientKey, + privateClientKey); + + /// Create a copy of ContainerCredential + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ContainerCredentialUnfinalizedImplCopyWith< + _$ContainerCredentialUnfinalizedImpl> + get copyWith => __$$ContainerCredentialUnfinalizedImplCopyWithImpl< + _$ContainerCredentialUnfinalizedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String? publicServerKey, + String? publicClientKey, + String? privateClientKey) + unfinalized, + required TResult Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String publicServerKey, + String publicClientKey, + String privateClientKey) + finalized, + }) { + return unfinalized( + issuer, + nonce, + timestamp, + finalizationUrl, + serial, + ecKeyAlgorithm, + hashAlgorithm, + state, + passphrase, + publicServerKey, + publicClientKey, + privateClientKey); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String? publicServerKey, + String? publicClientKey, + String? privateClientKey)? + unfinalized, + TResult? Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String publicServerKey, + String publicClientKey, + String privateClientKey)? + finalized, + }) { + return unfinalized?.call( + issuer, + nonce, + timestamp, + finalizationUrl, + serial, + ecKeyAlgorithm, + hashAlgorithm, + state, + passphrase, + publicServerKey, + publicClientKey, + privateClientKey); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String? publicServerKey, + String? publicClientKey, + String? privateClientKey)? + unfinalized, + TResult Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String publicServerKey, + String publicClientKey, + String privateClientKey)? + finalized, + required TResult orElse(), + }) { + if (unfinalized != null) { + return unfinalized( + issuer, + nonce, + timestamp, + finalizationUrl, + serial, + ecKeyAlgorithm, + hashAlgorithm, + state, + passphrase, + publicServerKey, + publicClientKey, + privateClientKey); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(ContainerCredentialUnfinalized value) unfinalized, + required TResult Function(ContainerCredentialFinalized value) finalized, + }) { + return unfinalized(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(ContainerCredentialUnfinalized value)? unfinalized, + TResult? Function(ContainerCredentialFinalized value)? finalized, + }) { + return unfinalized?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(ContainerCredentialUnfinalized value)? unfinalized, + TResult Function(ContainerCredentialFinalized value)? finalized, + required TResult orElse(), + }) { + if (unfinalized != null) { + return unfinalized(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$ContainerCredentialUnfinalizedImplToJson( + this, + ); + } +} + +abstract class ContainerCredentialUnfinalized extends ContainerCredential { + const factory ContainerCredentialUnfinalized( + {required final String issuer, + required final String nonce, + required final DateTime timestamp, + required final Uri finalizationUrl, + required final String serial, + required final EcKeyAlgorithm ecKeyAlgorithm, + required final Algorithms hashAlgorithm, + final ContainerCredentialState state, + final String? passphrase, + final String? publicServerKey, + final String? publicClientKey, + final String? privateClientKey}) = _$ContainerCredentialUnfinalizedImpl; + const ContainerCredentialUnfinalized._() : super._(); + + factory ContainerCredentialUnfinalized.fromJson(Map json) = + _$ContainerCredentialUnfinalizedImpl.fromJson; + + @override + String get issuer; + @override + String get nonce; + @override + DateTime get timestamp; + @override + Uri get finalizationUrl; + @override + String get serial; + @override + EcKeyAlgorithm get ecKeyAlgorithm; + @override + Algorithms get hashAlgorithm; + @override + ContainerCredentialState get state; + @override + String? get passphrase; + @override + String? get publicServerKey; + @override + String? get publicClientKey; + @override + String? get privateClientKey; + + /// Create a copy of ContainerCredential + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ContainerCredentialUnfinalizedImplCopyWith< + _$ContainerCredentialUnfinalizedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ContainerCredentialFinalizedImplCopyWith<$Res> + implements $ContainerCredentialCopyWith<$Res> { + factory _$$ContainerCredentialFinalizedImplCopyWith( + _$ContainerCredentialFinalizedImpl value, + $Res Function(_$ContainerCredentialFinalizedImpl) then) = + __$$ContainerCredentialFinalizedImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String publicServerKey, + String publicClientKey, + String privateClientKey}); +} + +/// @nodoc +class __$$ContainerCredentialFinalizedImplCopyWithImpl<$Res> + extends _$ContainerCredentialCopyWithImpl<$Res, + _$ContainerCredentialFinalizedImpl> + implements _$$ContainerCredentialFinalizedImplCopyWith<$Res> { + __$$ContainerCredentialFinalizedImplCopyWithImpl( + _$ContainerCredentialFinalizedImpl _value, + $Res Function(_$ContainerCredentialFinalizedImpl) _then) + : super(_value, _then); + + /// Create a copy of ContainerCredential + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? issuer = null, + Object? nonce = null, + Object? timestamp = null, + Object? finalizationUrl = null, + Object? serial = null, + Object? ecKeyAlgorithm = null, + Object? hashAlgorithm = null, + Object? state = null, + Object? passphrase = freezed, + Object? publicServerKey = null, + Object? publicClientKey = null, + Object? privateClientKey = null, + }) { + return _then(_$ContainerCredentialFinalizedImpl( + issuer: null == issuer + ? _value.issuer + : issuer // ignore: cast_nullable_to_non_nullable + as String, + nonce: null == nonce + ? _value.nonce + : nonce // ignore: cast_nullable_to_non_nullable + as String, + timestamp: null == timestamp + ? _value.timestamp + : timestamp // ignore: cast_nullable_to_non_nullable + as DateTime, + finalizationUrl: null == finalizationUrl + ? _value.finalizationUrl + : finalizationUrl // ignore: cast_nullable_to_non_nullable + as Uri, + serial: null == serial + ? _value.serial + : serial // ignore: cast_nullable_to_non_nullable + as String, + ecKeyAlgorithm: null == ecKeyAlgorithm + ? _value.ecKeyAlgorithm + : ecKeyAlgorithm // ignore: cast_nullable_to_non_nullable + as EcKeyAlgorithm, + hashAlgorithm: null == hashAlgorithm + ? _value.hashAlgorithm + : hashAlgorithm // ignore: cast_nullable_to_non_nullable + as Algorithms, + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as ContainerCredentialState, + passphrase: freezed == passphrase + ? _value.passphrase + : passphrase // ignore: cast_nullable_to_non_nullable + as String?, + publicServerKey: null == publicServerKey + ? _value.publicServerKey + : publicServerKey // ignore: cast_nullable_to_non_nullable + as String, + publicClientKey: null == publicClientKey + ? _value.publicClientKey + : publicClientKey // ignore: cast_nullable_to_non_nullable + as String, + privateClientKey: null == privateClientKey + ? _value.privateClientKey + : privateClientKey // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { + const _$ContainerCredentialFinalizedImpl( + {required this.issuer, + required this.nonce, + required this.timestamp, + required this.finalizationUrl, + required this.serial, + required this.ecKeyAlgorithm, + required this.hashAlgorithm, + this.state = ContainerCredentialState.finalized, + this.passphrase, + required this.publicServerKey, + required this.publicClientKey, + required this.privateClientKey, + final String? $type}) + : $type = $type ?? 'finalized', + super._(); + + factory _$ContainerCredentialFinalizedImpl.fromJson( + Map json) => + _$$ContainerCredentialFinalizedImplFromJson(json); + + @override + final String issuer; + @override + final String nonce; + @override + final DateTime timestamp; + @override + final Uri finalizationUrl; + @override + final String serial; + @override + final EcKeyAlgorithm ecKeyAlgorithm; + @override + final Algorithms hashAlgorithm; + @override + @JsonKey() + final ContainerCredentialState state; + @override + final String? passphrase; + @override + final String publicServerKey; + @override + final String publicClientKey; + @override + final String privateClientKey; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'ContainerCredential.finalized(issuer: $issuer, nonce: $nonce, timestamp: $timestamp, finalizationUrl: $finalizationUrl, serial: $serial, ecKeyAlgorithm: $ecKeyAlgorithm, hashAlgorithm: $hashAlgorithm, state: $state, passphrase: $passphrase, publicServerKey: $publicServerKey, publicClientKey: $publicClientKey, privateClientKey: $privateClientKey)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ContainerCredentialFinalizedImpl && + (identical(other.issuer, issuer) || other.issuer == issuer) && + (identical(other.nonce, nonce) || other.nonce == nonce) && + (identical(other.timestamp, timestamp) || + other.timestamp == timestamp) && + (identical(other.finalizationUrl, finalizationUrl) || + other.finalizationUrl == finalizationUrl) && + (identical(other.serial, serial) || other.serial == serial) && + (identical(other.ecKeyAlgorithm, ecKeyAlgorithm) || + other.ecKeyAlgorithm == ecKeyAlgorithm) && + (identical(other.hashAlgorithm, hashAlgorithm) || + other.hashAlgorithm == hashAlgorithm) && + (identical(other.state, state) || other.state == state) && + (identical(other.passphrase, passphrase) || + other.passphrase == passphrase) && + (identical(other.publicServerKey, publicServerKey) || + other.publicServerKey == publicServerKey) && + (identical(other.publicClientKey, publicClientKey) || + other.publicClientKey == publicClientKey) && + (identical(other.privateClientKey, privateClientKey) || + other.privateClientKey == privateClientKey)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + issuer, + nonce, + timestamp, + finalizationUrl, + serial, + ecKeyAlgorithm, + hashAlgorithm, + state, + passphrase, + publicServerKey, + publicClientKey, + privateClientKey); + + /// Create a copy of ContainerCredential + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ContainerCredentialFinalizedImplCopyWith< + _$ContainerCredentialFinalizedImpl> + get copyWith => __$$ContainerCredentialFinalizedImplCopyWithImpl< + _$ContainerCredentialFinalizedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String? publicServerKey, + String? publicClientKey, + String? privateClientKey) + unfinalized, + required TResult Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String publicServerKey, + String publicClientKey, + String privateClientKey) + finalized, + }) { + return finalized( + issuer, + nonce, + timestamp, + finalizationUrl, + serial, + ecKeyAlgorithm, + hashAlgorithm, + state, + passphrase, + publicServerKey, + publicClientKey, + privateClientKey); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String? publicServerKey, + String? publicClientKey, + String? privateClientKey)? + unfinalized, + TResult? Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String publicServerKey, + String publicClientKey, + String privateClientKey)? + finalized, + }) { + return finalized?.call( + issuer, + nonce, + timestamp, + finalizationUrl, + serial, + ecKeyAlgorithm, + hashAlgorithm, + state, + passphrase, + publicServerKey, + publicClientKey, + privateClientKey); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String? publicServerKey, + String? publicClientKey, + String? privateClientKey)? + unfinalized, + TResult Function( + String issuer, + String nonce, + DateTime timestamp, + Uri finalizationUrl, + String serial, + EcKeyAlgorithm ecKeyAlgorithm, + Algorithms hashAlgorithm, + ContainerCredentialState state, + String? passphrase, + String publicServerKey, + String publicClientKey, + String privateClientKey)? + finalized, + required TResult orElse(), + }) { + if (finalized != null) { + return finalized( + issuer, + nonce, + timestamp, + finalizationUrl, + serial, + ecKeyAlgorithm, + hashAlgorithm, + state, + passphrase, + publicServerKey, + publicClientKey, + privateClientKey); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(ContainerCredentialUnfinalized value) unfinalized, + required TResult Function(ContainerCredentialFinalized value) finalized, + }) { + return finalized(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(ContainerCredentialUnfinalized value)? unfinalized, + TResult? Function(ContainerCredentialFinalized value)? finalized, + }) { + return finalized?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(ContainerCredentialUnfinalized value)? unfinalized, + TResult Function(ContainerCredentialFinalized value)? finalized, + required TResult orElse(), + }) { + if (finalized != null) { + return finalized(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$ContainerCredentialFinalizedImplToJson( + this, + ); + } +} + +abstract class ContainerCredentialFinalized extends ContainerCredential { + const factory ContainerCredentialFinalized( + {required final String issuer, + required final String nonce, + required final DateTime timestamp, + required final Uri finalizationUrl, + required final String serial, + required final EcKeyAlgorithm ecKeyAlgorithm, + required final Algorithms hashAlgorithm, + final ContainerCredentialState state, + final String? passphrase, + required final String publicServerKey, + required final String publicClientKey, + required final String privateClientKey}) = + _$ContainerCredentialFinalizedImpl; + const ContainerCredentialFinalized._() : super._(); + + factory ContainerCredentialFinalized.fromJson(Map json) = + _$ContainerCredentialFinalizedImpl.fromJson; + + @override + String get issuer; + @override + String get nonce; + @override + DateTime get timestamp; + @override + Uri get finalizationUrl; + @override + String get serial; + @override + EcKeyAlgorithm get ecKeyAlgorithm; + @override + Algorithms get hashAlgorithm; + @override + ContainerCredentialState get state; + @override + String? get passphrase; + @override + String get publicServerKey; + @override + String get publicClientKey; + @override + String get privateClientKey; + + /// Create a copy of ContainerCredential + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ContainerCredentialFinalizedImplCopyWith< + _$ContainerCredentialFinalizedImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/model/tokens/container_credentials.g.dart b/lib/model/tokens/container_credentials.g.dart index 25b4f76c1..1b5666295 100644 --- a/lib/model/tokens/container_credentials.g.dart +++ b/lib/model/tokens/container_credentials.g.dart @@ -6,15 +6,142 @@ part of 'container_credentials.dart'; // JsonSerializableGenerator // ************************************************************************** -ContainerCredential _$ContainerCredentialFromJson(Map json) => - ContainerCredential( +_$ContainerCredentialUnfinalizedImpl + _$$ContainerCredentialUnfinalizedImplFromJson(Map json) => + _$ContainerCredentialUnfinalizedImpl( + issuer: json['issuer'] as String, + nonce: json['nonce'] as String, + timestamp: DateTime.parse(json['timestamp'] as String), + finalizationUrl: Uri.parse(json['finalizationUrl'] as String), + serial: json['serial'] as String, + ecKeyAlgorithm: + $enumDecode(_$EcKeyAlgorithmEnumMap, json['ecKeyAlgorithm']), + hashAlgorithm: + $enumDecode(_$AlgorithmsEnumMap, json['hashAlgorithm']), + state: $enumDecodeNullable( + _$ContainerCredentialStateEnumMap, json['state']) ?? + ContainerCredentialState.uninitialized, + passphrase: json['passphrase'] as String?, + publicServerKey: json['publicServerKey'] as String?, + publicClientKey: json['publicClientKey'] as String?, + privateClientKey: json['privateClientKey'] as String?, + $type: json['runtimeType'] as String?, + ); + +Map _$$ContainerCredentialUnfinalizedImplToJson( + _$ContainerCredentialUnfinalizedImpl instance) => + { + 'issuer': instance.issuer, + 'nonce': instance.nonce, + 'timestamp': instance.timestamp.toIso8601String(), + 'finalizationUrl': instance.finalizationUrl.toString(), + 'serial': instance.serial, + 'ecKeyAlgorithm': _$EcKeyAlgorithmEnumMap[instance.ecKeyAlgorithm]!, + 'hashAlgorithm': _$AlgorithmsEnumMap[instance.hashAlgorithm]!, + 'state': _$ContainerCredentialStateEnumMap[instance.state]!, + 'passphrase': instance.passphrase, + 'publicServerKey': instance.publicServerKey, + 'publicClientKey': instance.publicClientKey, + 'privateClientKey': instance.privateClientKey, + 'runtimeType': instance.$type, + }; + +const _$EcKeyAlgorithmEnumMap = { + EcKeyAlgorithm.brainpoolp160r1: 'brainpoolp160r1', + EcKeyAlgorithm.brainpoolp160t1: 'brainpoolp160t1', + EcKeyAlgorithm.brainpoolp192r1: 'brainpoolp192r1', + EcKeyAlgorithm.brainpoolp192t1: 'brainpoolp192t1', + EcKeyAlgorithm.brainpoolp224r1: 'brainpoolp224r1', + EcKeyAlgorithm.brainpoolp224t1: 'brainpoolp224t1', + EcKeyAlgorithm.brainpoolp256r1: 'brainpoolp256r1', + EcKeyAlgorithm.brainpoolp256t1: 'brainpoolp256t1', + EcKeyAlgorithm.brainpoolp320r1: 'brainpoolp320r1', + EcKeyAlgorithm.brainpoolp320t1: 'brainpoolp320t1', + EcKeyAlgorithm.brainpoolp384r1: 'brainpoolp384r1', + EcKeyAlgorithm.brainpoolp384t1: 'brainpoolp384t1', + EcKeyAlgorithm.brainpoolp512r1: 'brainpoolp512r1', + EcKeyAlgorithm.brainpoolp512t1: 'brainpoolp512t1', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_A: 'GostR3410_2001_CryptoPro_A', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_B: 'GostR3410_2001_CryptoPro_B', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_C: 'GostR3410_2001_CryptoPro_C', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchA: 'GostR3410_2001_CryptoPro_XchA', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchB: 'GostR3410_2001_CryptoPro_XchB', + EcKeyAlgorithm.prime192v1: 'prime192v1', + EcKeyAlgorithm.prime192v2: 'prime192v2', + EcKeyAlgorithm.prime192v3: 'prime192v3', + EcKeyAlgorithm.prime239v1: 'prime239v1', + EcKeyAlgorithm.prime239v2: 'prime239v2', + EcKeyAlgorithm.prime239v3: 'prime239v3', + EcKeyAlgorithm.prime256v1: 'prime256v1', + EcKeyAlgorithm.secp112r1: 'secp112r1', + EcKeyAlgorithm.secp112r2: 'secp112r2', + EcKeyAlgorithm.secp128r1: 'secp128r1', + EcKeyAlgorithm.secp128r2: 'secp128r2', + EcKeyAlgorithm.secp160k1: 'secp160k1', + EcKeyAlgorithm.secp160r1: 'secp160r1', + EcKeyAlgorithm.secp160r2: 'secp160r2', + EcKeyAlgorithm.secp192k1: 'secp192k1', + EcKeyAlgorithm.secp192r1: 'secp192r1', + EcKeyAlgorithm.secp224k1: 'secp224k1', + EcKeyAlgorithm.secp224r1: 'secp224r1', + EcKeyAlgorithm.secp256k1: 'secp256k1', + EcKeyAlgorithm.secp256r1: 'secp256r1', + EcKeyAlgorithm.secp384r1: 'secp384r1', + EcKeyAlgorithm.secp521r1: 'secp521r1', +}; + +const _$AlgorithmsEnumMap = { + Algorithms.SHA1: 'SHA1', + Algorithms.SHA256: 'SHA256', + Algorithms.SHA512: 'SHA512', +}; + +const _$ContainerCredentialStateEnumMap = { + ContainerCredentialState.uninitialized: 'uninitialized', + ContainerCredentialState.generatingKeyPair: 'generatingKeyPair', + ContainerCredentialState.generatingKeyPairFailed: 'generatingKeyPairFailed', + ContainerCredentialState.sendingPublicKey: 'sendingPublicKey', + ContainerCredentialState.sendingPublicKeyFailed: 'sendingPublicKeyFailed', + ContainerCredentialState.parsingResponse: 'parsingResponse', + ContainerCredentialState.parsingResponseFailed: 'parsingResponseFailed', + ContainerCredentialState.finalized: 'finalized', +}; + +_$ContainerCredentialFinalizedImpl _$$ContainerCredentialFinalizedImplFromJson( + Map json) => + _$ContainerCredentialFinalizedImpl( + issuer: json['issuer'] as String, + nonce: json['nonce'] as String, + timestamp: DateTime.parse(json['timestamp'] as String), + finalizationUrl: Uri.parse(json['finalizationUrl'] as String), serial: json['serial'] as String, - id: json['id'] as String, + ecKeyAlgorithm: + $enumDecode(_$EcKeyAlgorithmEnumMap, json['ecKeyAlgorithm']), + hashAlgorithm: $enumDecode(_$AlgorithmsEnumMap, json['hashAlgorithm']), + state: $enumDecodeNullable( + _$ContainerCredentialStateEnumMap, json['state']) ?? + ContainerCredentialState.finalized, + passphrase: json['passphrase'] as String?, + publicServerKey: json['publicServerKey'] as String, + publicClientKey: json['publicClientKey'] as String, + privateClientKey: json['privateClientKey'] as String, + $type: json['runtimeType'] as String?, ); -Map _$ContainerCredentialToJson( - ContainerCredential instance) => +Map _$$ContainerCredentialFinalizedImplToJson( + _$ContainerCredentialFinalizedImpl instance) => { - 'id': instance.id, + 'issuer': instance.issuer, + 'nonce': instance.nonce, + 'timestamp': instance.timestamp.toIso8601String(), + 'finalizationUrl': instance.finalizationUrl.toString(), 'serial': instance.serial, + 'ecKeyAlgorithm': _$EcKeyAlgorithmEnumMap[instance.ecKeyAlgorithm]!, + 'hashAlgorithm': _$AlgorithmsEnumMap[instance.hashAlgorithm]!, + 'state': _$ContainerCredentialStateEnumMap[instance.state]!, + 'passphrase': instance.passphrase, + 'publicServerKey': instance.publicServerKey, + 'publicClientKey': instance.publicClientKey, + 'privateClientKey': instance.privateClientKey, + 'runtimeType': instance.$type, }; diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index 6d2879ebf..305af46e6 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -17,12 +17,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import '../enums/encodings.dart'; -import '../extensions/enums/encodings_extension.dart'; import '../../utils/identifiers.dart'; - import '../../utils/logger.dart'; import '../enums/algorithms.dart'; +import '../enums/encodings.dart'; +import '../extensions/enums/encodings_extension.dart'; import '../token_import/token_origin_data.dart'; import 'token.dart'; diff --git a/lib/processors/mixins/token_import_processor.dart b/lib/processors/mixins/token_import_processor.dart index ed816a78a..22307d3be 100644 --- a/lib/processors/mixins/token_import_processor.dart +++ b/lib/processors/mixins/token_import_processor.dart @@ -19,14 +19,17 @@ */ import '../../model/processor_result.dart'; import '../../model/tokens/token.dart'; +import '../../utils/identifiers.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart'; import '../token_import_file_processor/token_import_file_processor_interface.dart'; mixin TokenImportProcessor { + static const resultHandlerType = TypeMatcher(); static Set implementations = { const GoogleAuthenticatorQrProcessor(), ...TokenImportFileProcessor.implementations, }; - Future>> processTokenMigrate(T data, {V args}); + Future>?> processTokenMigrate(T data, {V args}); } diff --git a/lib/processors/scheme_processors/container_credentials_processor.dart b/lib/processors/scheme_processors/container_credentials_processor.dart index 166449b82..9a5c2d093 100644 --- a/lib/processors/scheme_processors/container_credentials_processor.dart +++ b/lib/processors/scheme_processors/container_credentials_processor.dart @@ -17,26 +17,58 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; + +import '../../model/processor_result.dart'; +import '../../utils/identifiers.dart'; import 'scheme_processor_interface.dart'; -import '../../utils/globals.dart'; import '../../utils/logger.dart'; import '../../model/tokens/container_credentials.dart'; -import '../../utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; class ContainerCredentialsProcessor extends SchemeProcessor { + static const resultHandlerType = TypeMatcher(); + static const scheme = 'pia'; + // static const hosts = {'container': _container}; + @override - Set get supportedSchemes => {'container'}; // TODO: edit supportedSchemes to the real supported schemes - List get supportedHosts => ['credentials']; // TODO: edit supportedHosts to the real supported hosts + Set get supportedSchemes => {scheme}; const ContainerCredentialsProcessor(); @override - Future processUri(Uri uri, {bool fromInit = false}) async { - if (!supportedHosts.contains(uri.host) || !supportedSchemes.contains(uri.scheme)) { - return null; + Future>> processUri(Uri uri, {bool fromInit = false}) async { + if (!supportedSchemes.contains(uri.scheme)) { + Logger.error('Unsupported scheme', name: 'ContainerCredentialsProcessor'); + return []; } final credential = ContainerCredential.fromUriMap(uri.queryParameters); Logger.warning('Adding credential to container', name: 'ContainerCredentialsProcessor'); - globalRef?.read(credentialsNotifierProvider.notifier).addCredential(credential); + return [ + ProcessorResult.success( + credential, + resultHandlerType: resultHandlerType, + ) + ]; } + + // static Future>> _container(Uri uri) async { + // try { + // final credential = ContainerCredential.fromUriMap(uri.queryParameters); + // Logger.info('Processing URI ${uri.scheme} succeded', name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); + // return [ + // ProcessorResult.success( + // credential, + // resultHandlerType: resultHandlerType, + // ) + // ]; + // } catch (e) { + // Logger.error('Error while processing URI ${uri.scheme}', error: e, name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); + // return [ + // ProcessorResult.failed( + // 'Invalid URI', + // resultHandlerType: resultHandlerType, + // ) + // ]; + // } + // } } diff --git a/lib/processors/scheme_processors/home_widget_processor.dart b/lib/processors/scheme_processors/home_widget_processor.dart index 54a05f8b8..d84061fa9 100644 --- a/lib/processors/scheme_processors/home_widget_processor.dart +++ b/lib/processors/scheme_processors/home_widget_processor.dart @@ -17,6 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import '../../model/processor_result.dart'; import '../../utils/home_widget_utils.dart'; import '../../utils/logger.dart'; import 'scheme_processor_interface.dart'; @@ -24,42 +25,96 @@ import 'scheme_processor_interface.dart'; class HomeWidgetProcessor implements SchemeProcessor { const HomeWidgetProcessor(); - static final Map Function(Uri)> _processors = { + static final Map>?> Function(Uri)> _processors = { 'show': _showProcessor, 'copy': _copyProcessor, 'action': _actionProcessor, }; @override - Future processUri(Uri uri, {bool fromInit = false}) async { - if (supportedSchemes.contains(uri.scheme) == false) return; - return _processors[uri.host]?.call(uri); + Future>?> processUri(Uri uri, {bool fromInit = false}) async { + if (supportedSchemes.contains(uri.scheme) == false) return []; + final processor = _processors[uri.host]; + if (processor == null) { + return [ + ProcessorResult.failed( + 'No processor found for host: ${uri.host}', + resultHandlerType: null, + ) + ]; + } + return processor.call(uri); } @override Set get supportedSchemes => {'homewidget'}; - static Future _showProcessor(Uri uri, {bool fromInit = false}) async { + static Future>?> _showProcessor(Uri uri, {bool fromInit = false}) async { Logger.warning('HomeWidgetProcessor: Processing uri show: $uri'); - if (uri.host != 'show') return; + if (uri.host != 'show') { + return [ + ProcessorResult.failed( + 'Invalid host: ${uri.host} for scheme: ${uri.scheme}', + resultHandlerType: null, + ) + ]; + } final widgetId = uri.queryParameters['widgetId']; - if (widgetId == null) return; - return HomeWidgetUtils().showOtp(widgetId); + if (widgetId == null) { + return [ + ProcessorResult.failed( + 'Missing widgetId', + resultHandlerType: null, + ) + ]; + } + HomeWidgetUtils().showOtp(widgetId); + return null; } - static Future _copyProcessor(Uri uri, {bool fromInit = false}) async { + static Future>?> _copyProcessor(Uri uri, {bool fromInit = false}) async { Logger.warning('HomeWidgetProcessor: Processing uri copy: $uri'); - if (uri.host != 'copy') return; + if (uri.host != 'copy') { + return [ + ProcessorResult.failed( + 'Invalid host: ${uri.host} for scheme: ${uri.scheme}', + resultHandlerType: null, + ) + ]; + } final widgetId = uri.queryParameters['widgetId']; - if (widgetId == null) return; - return HomeWidgetUtils().copyOtp(widgetId); + if (widgetId == null) { + return [ + ProcessorResult.failed( + 'Missing widgetId', + resultHandlerType: null, + ) + ]; + } + HomeWidgetUtils().copyOtp(widgetId); + return null; } - static Future _actionProcessor(Uri uri, {bool fromInit = false}) async { + static Future>?> _actionProcessor(Uri uri, {bool fromInit = false}) async { Logger.warning('HomeWidgetProcessor: Processing uri action: $uri'); - if (uri.host != 'action') return; + if (uri.host != 'action') { + return [ + ProcessorResult.failed( + 'Invalid host: ${uri.host} for scheme: ${uri.scheme}', + resultHandlerType: null, + ) + ]; + } final widgetId = uri.queryParameters['widgetId']; - if (widgetId == null) return; - return HomeWidgetUtils().performAction(widgetId); + if (widgetId == null) { + return [ + ProcessorResult.failed( + 'Missing widgetId', + resultHandlerType: null, + ) + ]; + } + HomeWidgetUtils().performAction(widgetId); + return null; } } diff --git a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart index 33ef20c33..a29ded304 100644 --- a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart +++ b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart @@ -19,47 +19,70 @@ */ import 'package:flutter/material.dart'; +import '../../../model/processor_result.dart'; import '../../../utils/globals.dart'; import '../../../utils/home_widget_utils.dart'; +import '../../../utils/identifiers.dart'; import '../../../utils/logger.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../views/link_home_widget_view/link_home_widget_view.dart'; import 'navigation_scheme_processor_interface.dart'; class HomeWidgetNavigateProcessor implements NavigationSchemeProcessor { + static const resultHandlerType = TypeMatcher(); HomeWidgetNavigateProcessor(); - static final Map Function(Uri, BuildContext, {bool fromInit})> _processors = { + static final Map>?> Function(Uri, BuildContext, {bool fromInit})> _processors = { 'link': _linkHomeWidgetProcessor, 'showlocked': _showLockedHomeWidgetProcessor, }; @override - Future processUri(Uri uri, {BuildContext? context, bool fromInit = false}) async { + Future>?> processUri(Uri uri, {BuildContext? context, bool fromInit = false}) async { if (context == null) { Logger.error( 'HomeWidgetNavigateProcessor: Cannot Navigate without context', error: Exception('context is null'), stackTrace: StackTrace.current, ); - return; + return [ + ProcessorResult.failed( + 'Cannot Navigate without context', + resultHandlerType: resultHandlerType, + ) + ]; } Logger.warning('HomeWidgetNavigateProcessor: Processing uri: $uri'); - return _processors[uri.host]?.call(uri, context, fromInit: fromInit); + final processor = _processors[uri.host]; + if (processor == null) { + Logger.warning('HomeWidgetNavigateProcessor: No processor found for host: ${uri.host}'); + return [ + ProcessorResult.failed( + 'No processor found for host: ${uri.host}', + resultHandlerType: resultHandlerType, + ) + ]; + } + return processor.call(uri, context, fromInit: fromInit); } @override Set get supportedSchemes => {'homewidgetnavigate'}; - static Future _linkHomeWidgetProcessor(Uri uri, BuildContext context, {bool fromInit = false}) async { + static Future>?> _linkHomeWidgetProcessor(Uri uri, BuildContext context, {bool fromInit = false}) async { if (uri.host != 'link') { Logger.warning('HomeWidgetNavigateProcessor: Invalid host for link: ${uri.host}'); - return; + return []; } if (uri.queryParameters['id'] == null) { - Logger.warning('HomeWidgetNavigateProcessor: Invalid query parameters for link: ${uri.queryParameters}'); - return; + Logger.warning('HomeWidgetNavigateProcessor: Missing id for link: ${uri.host}'); + return [ + ProcessorResult.failed( + 'Missing id for link: ${uri.host}', + resultHandlerType: resultHandlerType, + ) + ]; } if (fromInit) { Navigator.of(context).push( @@ -71,16 +94,22 @@ class HomeWidgetNavigateProcessor implements NavigationSchemeProcessor { MaterialPageRoute(builder: (context) => LinkHomeWidgetView(homeWidgetId: uri.queryParameters['id']!)), ); } + return null; } - static Future _showLockedHomeWidgetProcessor(Uri uri, BuildContext context, {bool fromInit = false}) async { + static Future>?> _showLockedHomeWidgetProcessor(Uri uri, BuildContext context, {bool fromInit = false}) async { if (uri.host != 'showlocked') { Logger.warning('Invalid host for showlocked: ${uri.host}', name: 'home_widget_processor.dart#_showLockedHomeWidgetProcessor'); - return; + return []; } if (uri.queryParameters['id'] == null) { Logger.warning('Invalid query parameters for showlocked: ${uri.queryParameters}', name: 'home_widget_processor.dart#_showLockedHomeWidgetProcessor'); - return; + return [ + ProcessorResult.failed( + 'Missing id for showlocked: ${uri.host}', + resultHandlerType: resultHandlerType, + ) + ]; } Logger.info('Showing otp of locked Token of homeWidget: ${uri.queryParameters['id']}', name: 'home_widget_processor.dart#_showLockedHomeWidgetProcessor'); Navigator.popUntil(context, (route) => route.isFirst); @@ -88,12 +117,22 @@ class HomeWidgetNavigateProcessor implements NavigationSchemeProcessor { final tokenId = await HomeWidgetUtils().getTokenIdOfWidgetId(uri.queryParameters['id']!); if (tokenId == null) { Logger.warning('Could not find token for widget id: ${uri.queryParameters['id']}', name: 'home_widget_processor.dart#_showLockedHomeWidgetProcessor'); - return; + return [ + ProcessorResult.failed( + 'Could not find token for widget id: ${uri.queryParameters['id}']}', + resultHandlerType: resultHandlerType, + ) + ]; } await Future.delayed(const Duration(milliseconds: 200)); if (globalRef == null) { Logger.warning('Could not find globalRef', name: 'home_widget_processor.dart#_showLockedHomeWidgetProcessor'); - return; + return [ + ProcessorResult.failed( + 'Could not find globalRef', + resultHandlerType: resultHandlerType, + ) + ]; } final showedToken = await globalRef!.read(tokenProvider.notifier).showTokenById(tokenId); @@ -103,5 +142,34 @@ class HomeWidgetNavigateProcessor implements NavigationSchemeProcessor { globalRef!.read(tokenFolderProvider.notifier).expandFolderById(folderId); } } + return null; + } +} + +class NavigationHandler with ResultHandler { + @override + Future handleProcessorResult(ProcessorResult result, Map args) async { + if (result is! ProcessorResult) return null; + if (result.isFailed) return null; + validateMap(args, {'context': const TypeMatcher()}); + final navigation = result.asSuccess!.resultData; + final BuildContext context = args['context']; + return await navigation(context); + } + + @override + Future?> handleProcessorResults(List results, Map args) async { + final successResults = results.whereType>().toList().successResults; + if (successResults.isEmpty) return null; + validateMap(args, {'context': const TypeMatcher()}); + List navigations = successResults.getData(); + final context = args['context']; + final retunValues = []; + for (final navigation in navigations) { + retunValues.add(await navigation(context)); + } + return retunValues; } } + +typedef Navigation = Future Function(BuildContext context); diff --git a/lib/processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart b/lib/processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart index e780e7db1..fc1b13164 100644 --- a/lib/processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart +++ b/lib/processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart @@ -19,6 +19,7 @@ */ import 'package:flutter/material.dart'; +import '../../../model/processor_result.dart'; import '../../../utils/globals.dart'; import '../../../utils/logger.dart'; import '../scheme_processor_interface.dart'; @@ -32,7 +33,7 @@ abstract class NavigationSchemeProcessor implements SchemeProcessor { }; @override - Future processUri(Uri uri, {BuildContext? context, bool fromInit = false}); + Future>?> processUri(Uri uri, {BuildContext? context, bool fromInit = false}); static Future processUriByAny(Uri uri, {BuildContext? context, required bool fromInit}) async { if (context == null) { diff --git a/lib/processors/scheme_processors/scheme_processor_interface.dart b/lib/processors/scheme_processors/scheme_processor_interface.dart index 2fe4be6d0..c2be871f8 100644 --- a/lib/processors/scheme_processors/scheme_processor_interface.dart +++ b/lib/processors/scheme_processors/scheme_processor_interface.dart @@ -17,6 +17,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import 'package:privacyidea_authenticator/model/processor_result.dart'; + +import '../../utils/logger.dart'; import 'container_credentials_processor.dart'; import 'home_widget_processor.dart'; import 'navigation_scheme_processors/navigation_scheme_processor_interface.dart'; @@ -26,7 +29,7 @@ import 'token_import_scheme_processors/token_import_scheme_processor_interface.d abstract class SchemeProcessor { const SchemeProcessor(); Set get supportedSchemes; - Future processUri(Uri uri, {bool fromInit = false}); + Future>?> processUri(Uri uri, {bool fromInit = false}); static final List implementations = [ const HomeWidgetProcessor(), @@ -34,12 +37,17 @@ abstract class SchemeProcessor { ...TokenImportSchemeProcessor.implementations, const ContainerCredentialsProcessor(), ]; - static Future processUriByAny(Uri uri, {bool fromInit = false}) async { + static Future>?> processUriByAny(Uri uri, {bool fromInit = false}) async { for (SchemeProcessor processor in implementations) { if (processor.supportedSchemes.contains(uri.scheme)) { - return await processor.processUri(uri); + Logger.info('Processing URI with processor: $processor', name: 'SchemeProcessor#processUriByAny'); + final result = await processor.processUri(uri, fromInit: fromInit); + if (result != null) { + return result; + } } } + Logger.warning('Unsupported scheme', name: 'SchemeProcessor#processUriByAny'); return null; } } 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 8f1127437..e532c6279 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 @@ -25,6 +25,7 @@ import '../../../utils/token_import_origins.dart'; import 'otp_auth_processor.dart'; class FreeOtpPlusQrProcessor extends OtpAuthProcessor { + static get resultHandlerType => OtpAuthProcessor.resultHandlerType; const FreeOtpPlusQrProcessor(); @override @@ -41,6 +42,7 @@ class FreeOtpPlusQrProcessor extends OtpAuthProcessor { isPrivacyIdeaToken: false, data: uri.toString(), ), + resultHandlerType: resultHandlerType, ); }).toList(); return resultsWithOrigin; diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart b/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart index 7803d7224..6ac05a12f 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart @@ -36,6 +36,7 @@ import 'otp_auth_processor.dart'; import 'token_import_scheme_processor_interface.dart'; class GoogleAuthenticatorQrProcessor extends TokenImportSchemeProcessor { + static get resultHandlerType => OtpAuthProcessor.resultHandlerType; const GoogleAuthenticatorQrProcessor(); static const OtpAuthProcessor otpAuthProcessor = OtpAuthProcessor(); @@ -123,7 +124,7 @@ class GoogleAuthenticatorQrProcessor extends TokenImportSchemeProcessor { error: e, stackTrace: StackTrace.current, ); - results.add(ProcessorResultFailed(e.toString())); + results.add(ProcessorResultFailed(e.toString(), resultHandlerType: resultHandlerType)); continue; } } @@ -137,6 +138,7 @@ class GoogleAuthenticatorQrProcessor extends TokenImportSchemeProcessor { isPrivacyIdeaToken: false, data: base64.encode(decoded), ), + resultHandlerType: resultHandlerType, ); }).toList(); return resultsWithOrigin; 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 66136558c..aa8b10eb1 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 @@ -42,13 +42,21 @@ import '../../../widgets/dialog_widgets/two_step_dialog.dart'; import 'token_import_scheme_processor_interface.dart'; class OtpAuthProcessor extends TokenImportSchemeProcessor { + static get resultHandlerType => TokenImportSchemeProcessor.resultHandlerType; const OtpAuthProcessor(); @override Set get supportedSchemes => {'otpauth'}; @override Future>> processUri(Uri uri, {bool fromInit = false}) async { - if (!supportedSchemes.contains(uri.scheme)) return [ProcessorResultFailed('The scheme [${uri.scheme}] not supported')]; + if (!supportedSchemes.contains(uri.scheme)) { + return [ + ProcessorResultFailed( + 'The scheme [${uri.scheme}] not supported', + resultHandlerType: resultHandlerType, + ) + ]; + } Logger.info('Try to handle otpAuth:', name: 'token_notifier.dart#addTokenFromOtpAuth'); Map uriMap; try { @@ -57,7 +65,12 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { if (e is LocalizedException) { Logger.warning('Error while parsing otpAuth.', name: 'token_notifier.dart#addTokenFromOtpAuth', error: e.unlocalizedMessage, stackTrace: s); final message = globalContextSync != null ? e.localizedMessage(AppLocalizations.of(globalContextSync!)!) : e.unlocalizedMessage; - return [ProcessorResult.failed(message)]; + return [ + ProcessorResult.failed( + message, + resultHandlerType: resultHandlerType, + ) + ]; } String? message; if (e is ArgumentError) { @@ -65,10 +78,20 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { message = '${e.message} - ${e.name}: ${e.invalidValue}'; } message ??= 'An error occurred while parsing the QR code.'; - return [ProcessorResult.failed(globalContextSync != null ? AppLocalizations.of(globalContextSync!)?.tokenDataParseError ?? message : message)]; + return [ + ProcessorResult.failed( + globalContextSync != null ? AppLocalizations.of(globalContextSync!)?.tokenDataParseError ?? message : message, + resultHandlerType: resultHandlerType, + ) + ]; } if (_is2StepURI(uri)) { - validateMap(uriMap, [URI_SECRET, URI_ITERATIONS, URI_OUTPUT_LENGTH_IN_BYTES, URI_SALT_LENGTH]); + validateMap(uriMap, { + URI_SECRET: TypeMatcher(), + URI_ITERATIONS: TypeMatcher(), + URI_OUTPUT_LENGTH_IN_BYTES: TypeMatcher(), + URI_SALT_LENGTH: TypeMatcher(), + }); final secret = uriMap[URI_SECRET] as Uint8List; // Calculate the whole secret. @@ -82,7 +105,12 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { ), )); if (twoStepSecret == null) { - return [const ProcessorResultFailed('The two step secret could not be generated, or was canceled.')]; + return [ + ProcessorResultFailed( + 'The two step secret could not be generated, or was canceled.', + resultHandlerType: resultHandlerType, + ) + ]; } uriMap[URI_SECRET] = twoStepSecret; } @@ -98,13 +126,28 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { ); } on FormatException catch (e) { Logger.warning('Error while parsing otpAuth.', name: 'token_notifier.dart#addTokenFromOtpAuth', error: e); - return [ProcessorResultFailed(e.message)]; + return [ + ProcessorResultFailed( + e.message, + resultHandlerType: resultHandlerType, + ) + ]; } 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)); - return [const ProcessorResultFailed('An error occurred while parsing the QR code.')]; + return [ + ProcessorResultFailed( + 'An error occurred while parsing the QR code.', + resultHandlerType: resultHandlerType, + ) + ]; } - return [ProcessorResultSuccess(newToken)]; + return [ + ProcessorResultSuccess( + newToken, + resultHandlerType: resultHandlerType, + ) + ]; } } 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 c49b97edb..685e0d670 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 @@ -24,6 +24,7 @@ import '../../../utils/logger.dart'; import 'token_import_scheme_processor_interface.dart'; class PrivacyIDEAAuthenticatorQrProcessor extends TokenImportSchemeProcessor { + static get resultHandlerType => TokenImportSchemeProcessor.resultHandlerType; const PrivacyIDEAAuthenticatorQrProcessor(); static const scheme = 'pia'; static const host = 'qrbackup'; @@ -32,19 +33,32 @@ class PrivacyIDEAAuthenticatorQrProcessor extends TokenImportSchemeProcessor { Set get supportedSchemes => {scheme}; @override - Future>> processUri(Uri uri, {bool fromInit = false}) async { - if (!supportedSchemes.contains(uri.scheme) || uri.host != host) { - Logger.warning('Unsupported scheme or host'); - return []; + Future>?> processUri(Uri uri, {bool fromInit = false}) async { + if (!supportedSchemes.contains(uri.scheme)) { + return null; + } + if (uri.host != host) { + Logger.warning('Unsupported scheme or host', name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); + return null; } Logger.info('Processing URI with scheme: ${uri.scheme}', name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); try { final token = TokenEncryption.fromExportUri(uri); Logger.info('Processing URI ${uri.scheme} succeded', name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); - return [ProcessorResult.success(token)]; + return [ + ProcessorResult.success( + token, + resultHandlerType: resultHandlerType, + ) + ]; } catch (e) { Logger.error('Error while processing URI ${uri.scheme}', error: e, name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); - return [ProcessorResult.failed('Invalid URI')]; + return [ + ProcessorResult.failed( + 'Invalid URI', + resultHandlerType: resultHandlerType, + ) + ]; } } } diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart b/lib/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart index 615b38e81..5759107de 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart @@ -13,10 +13,12 @@ * * 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 '../../../model/processor_result.dart'; import '../../../model/tokens/token.dart'; import '../../mixins/token_import_processor.dart'; @@ -26,6 +28,7 @@ import 'otp_auth_processor.dart'; import 'privacyidea_authenticator_qr_processor.dart'; abstract class TokenImportSchemeProcessor with TokenImportProcessor implements SchemeProcessor { + static get resultHandlerType => TokenImportProcessor.resultHandlerType; const TokenImportSchemeProcessor(); static Set get allSupportedSchemes => { @@ -44,10 +47,10 @@ abstract class TokenImportSchemeProcessor with TokenImportProcessor i /// data: [Uri] uri /// args: [bool] fromInit - Future>> processTokenMigrate(Uri data, {bool args = false}) => processUri(data, fromInit: args); + Future>?> processTokenMigrate(Uri data, {bool args = false}) => processUri(data, fromInit: args); @override - Future>> processUri(Uri uri, {bool fromInit = false}); + Future>?> processUri(Uri uri, {bool fromInit = false}); static Future>?> processUriByAny(Uri uri) async { for (TokenImportSchemeProcessor processor in implementations) { @@ -55,6 +58,6 @@ abstract class TokenImportSchemeProcessor with TokenImportProcessor i return await processor.processUri(uri); } } - return null; + return []; } } 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 5939e0e10..ed6e4280b 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 @@ -61,6 +61,8 @@ void _isolatedKdf(List args) { } class AegisImportFileProcessor extends TokenImportFileProcessor { + static get resultHandlerType => TokenImportFileProcessor.resultHandlerType; + const AegisImportFileProcessor(); static const String AEGIS_TYPE = 'type'; @@ -163,7 +165,10 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { 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']) ?? 'Unsupported token type: ${entry['type']}')); + results.add(ProcessorResult.failed( + localization?.unsupported('token type', entry['type']) ?? 'Unsupported token type: ${entry['type']}', + resultHandlerType: resultHandlerType, + )); continue; } Map info = entry['info']; @@ -184,12 +189,21 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { ), }; final token = Token.fromUriMap(entryUriMap); - results.add(ProcessorResult.success(token.copyWith(id: entry[AEGIS_ID]))); + results.add(ProcessorResult.success( + token.copyWith(id: entry[AEGIS_ID]), + resultHandlerType: resultHandlerType, + )); } on LocalizedException catch (e) { - results.add(ProcessorResult.failed(localization != null ? e.localizedMessage(localization) : e.unlocalizedMessage)); + results.add(ProcessorResult.failed( + localization != null ? e.localizedMessage(localization) : e.unlocalizedMessage, + resultHandlerType: resultHandlerType, + )); } catch (e) { Logger.error('Failed to parse token.', name: 'AegisImportFileProcessor#_processPlain', error: e, stackTrace: StackTrace.current); - results.add(ProcessorResult.failed(e.toString())); + results.add(ProcessorResult.failed( + e.toString(), + resultHandlerType: resultHandlerType, + )); } } return Future.value(results); @@ -204,7 +218,10 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { 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']) ?? 'Unsupported token type: ${entry['type']}')); + results.add(ProcessorResult.failed( + localization?.unsupported('token type', entry['type']) ?? 'Unsupported token type: ${entry['type']}', + resultHandlerType: resultHandlerType, + )); continue; } Map info = entry['info']; @@ -224,12 +241,21 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { data: jsonEncode(entry), ), }; - results.add(ProcessorResult.success(Token.fromUriMap(entryUriMap))); + results.add(ProcessorResult.success( + Token.fromUriMap(entryUriMap), + resultHandlerType: resultHandlerType, + )); } on LocalizedException catch (e) { - results.add(ProcessorResultFailed(localization != null ? e.localizedMessage(localization) : e.unlocalizedMessage)); + results.add(ProcessorResultFailed( + localization != null ? e.localizedMessage(localization) : e.unlocalizedMessage, + resultHandlerType: resultHandlerType, + )); } catch (e) { Logger.error('Failed to parse token.', name: 'AegisImportFileProcessor#_processPlain', error: e, stackTrace: StackTrace.current); - results.add(ProcessorResultFailed(e.toString())); + results.add(ProcessorResultFailed( + e.toString(), + resultHandlerType: resultHandlerType, + )); } } 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 a7d7559b1..b9b55290e 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 @@ -45,6 +45,7 @@ import '../../utils/token_import_origins.dart'; import 'token_import_file_processor_interface.dart'; class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { + static get resultHandlerType => TokenImportFileProcessor.resultHandlerType; static const String header = "AUTHENTICATORPRO"; static const String headerLegacy = "AuthenticatorPro"; @@ -160,7 +161,9 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { token: t.resultData, isPrivacyIdeaToken: false, data: t.resultData.origin!.data, - )); + ), + resultHandlerType: resultHandlerType, + ); }).toList(); } @@ -250,10 +253,16 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { final newResults = await const OtpAuthProcessor().processUri(uri); results.addAll(newResults); } on LocalizedException catch (e) { - results.add(ProcessorResultFailed(e.localizedMessage(AppLocalizations.of(await globalContext)!))); + results.add(ProcessorResultFailed( + e.localizedMessage(AppLocalizations.of(await globalContext)!), + resultHandlerType: resultHandlerType, + )); } catch (e) { Logger.error('Failed to parse token.', name: 'authenticator_pro_import_file_processor#_processUriList', error: e, stackTrace: StackTrace.current); - results.add(ProcessorResultFailed(e.toString())); + results.add(ProcessorResultFailed( + e.toString(), + resultHandlerType: resultHandlerType, + )); } } return results; @@ -280,15 +289,22 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { token: newResult.resultData, data: uri.toString(), ), + resultHandlerType: resultHandlerType, ), ); } } } on LocalizedException catch (e) { - results.add(ProcessorResultFailed(e.localizedMessage(AppLocalizations.of(await globalContext)!))); + results.add(ProcessorResultFailed( + e.localizedMessage(AppLocalizations.of(await globalContext)!), + resultHandlerType: resultHandlerType, + )); } catch (e) { Logger.error('Failed to parse token.', name: 'authenticator_pro_import_file_processor#_processHtml', error: e, stackTrace: StackTrace.current); - results.add(ProcessorResultFailed(e.toString())); + results.add(ProcessorResultFailed( + e.toString(), + resultHandlerType: resultHandlerType, + )); } return results; } @@ -321,12 +337,20 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { }; final token = Token.fromUriMap(uriMap); - result.add(ProcessorResultSuccess(token)); + result.add(ProcessorResultSuccess( + token, + resultHandlerType: resultHandlerType, + )); } on LocalizedException catch (e) { - result.add(ProcessorResultFailed(e.localizedMessage(AppLocalizations.of(await globalContext)!))); + result.add(ProcessorResultFailed( + e.localizedMessage(AppLocalizations.of(await globalContext)!), + resultHandlerType: resultHandlerType, + )); } catch (e) { Logger.error('Failed to parse token.', name: 'authenticator_pro_import_file_processor#_processAuthPro', error: e, stackTrace: StackTrace.current); - result.add(ProcessorResultFailed(e.toString())); + result.add(ProcessorResultFailed(e.toString(), + resultHandlerType: resultHandlerType, + )); } } return result; diff --git a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart index 48b6d6b14..404dbfddc 100644 --- a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart @@ -38,6 +38,7 @@ import '../scheme_processors/token_import_scheme_processors/free_otp_plus_qr_pro import 'token_import_file_processor_interface.dart'; class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { + static get resultHandlerType => TokenImportFileProcessor.resultHandlerType; 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'; @@ -88,17 +89,22 @@ class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { results.addAll(await const FreeOtpPlusQrProcessor().processUri(uri)); } catch (e) { Logger.error('Failed to process line: $line', name: 'FreeOtpPlusFileProcessor#processFile', error: e, stackTrace: StackTrace.current); - results.add(ProcessorResultFailed(e.toString())); + results.add(ProcessorResultFailed(e.toString(), + resultHandlerType: resultHandlerType, + )); } } return results.map((t) { if (t is! ProcessorResultSuccess) return t; - return ProcessorResultSuccess(TokenOriginSourceType.backupFile.addOriginToToken( - appName: TokenImportOrigins.freeOtpPlus.appName, - token: t.resultData, - isPrivacyIdeaToken: false, - data: t.resultData.origin!.data, - )); + return ProcessorResultSuccess( + TokenOriginSourceType.backupFile.addOriginToToken( + appName: TokenImportOrigins.freeOtpPlus.appName, + token: t.resultData, + isPrivacyIdeaToken: false, + data: t.resultData.origin!.data, + ), + resultHandlerType: resultHandlerType, + ); }).toList(); } @@ -116,12 +122,21 @@ class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { Future> _processJsonToken(Map tokenJson) async { try { - return ProcessorResultSuccess(Token.fromUriMap(_jsonToUriMap(tokenJson))); + return ProcessorResultSuccess( + Token.fromUriMap(_jsonToUriMap(tokenJson)), + resultHandlerType: resultHandlerType, + ); } on LocalizedException catch (e) { - return ProcessorResultFailed(e.localizedMessage(AppLocalizations.of(await globalContext)!)); + return ProcessorResultFailed( + e.localizedMessage(AppLocalizations.of(await globalContext)!), + resultHandlerType: resultHandlerType, + ); } catch (e) { Logger.error('Failed to parse token.', name: 'FreeOtpPlusFileProcessor#_processJsonToken', error: e, stackTrace: StackTrace.current); - return ProcessorResultFailed(e.toString()); + return ProcessorResultFailed( + e.toString(), + resultHandlerType: resultHandlerType, + ); } } diff --git a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart index 3c5554f6e..f3658c46d 100644 --- a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart +++ b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart @@ -38,6 +38,7 @@ import '../scheme_processors/token_import_scheme_processors/google_authenticator import 'token_import_file_processor_interface.dart'; class GoogleAuthenticatorQrfileProcessor extends TokenImportFileProcessor { + static get resultHandlerType => TokenImportFileProcessor.resultHandlerType; const GoogleAuthenticatorQrfileProcessor(); @override Future fileIsValid(XFile file) async { @@ -115,6 +116,7 @@ class GoogleAuthenticatorQrfileProcessor extends TokenImportFileProcessor { isPrivacyIdeaToken: false, data: t.resultData.origin?.data ?? qrResult!.text, ), + resultHandlerType: resultHandlerType, ); }).toList(); return processorResults; 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 46c64a6e7..9adf79b9b 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 @@ -29,6 +29,7 @@ import 'token_import_file_processor_interface.dart'; import 'two_fas_import_file_processor.dart'; class PrivacyIDEAAuthenticatorImportFileProcessor extends TokenImportFileProcessor { + static get resultHandlerType => TokenImportFileProcessor.resultHandlerType; const PrivacyIDEAAuthenticatorImportFileProcessor(); @override Future fileIsValid(XFile file) async { @@ -59,12 +60,22 @@ class PrivacyIDEAAuthenticatorImportFileProcessor extends TokenImportFileProcess } catch (e) { throw BadDecryptionPasswordException('Invalid password'); } - final results = tokens.map((token) => ProcessorResultSuccess(token)).toList(); + final results = tokens + .map((token) => ProcessorResult.success( + token, + resultHandlerType: resultHandlerType, + )) + .toList(); return results; } catch (e) { if (e is BadDecryptionPasswordException) rethrow; Logger.error('Failed to process file', name: 'PrivacyIDEAAuthenticatorImportFileProcessor#processFile', error: e, stackTrace: StackTrace.current); - return [ProcessorResultFailed(e.toString())]; + return [ + ProcessorResult.failed( + e.toString(), + resultHandlerType: resultHandlerType, + ) + ]; } } } 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 5be6c4810..09fc782c9 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 @@ -27,6 +27,7 @@ import 'aegis_import_file_processor.dart'; import 'two_fas_import_file_processor.dart'; abstract class TokenImportFileProcessor with TokenImportProcessor { + static get resultHandlerType => TokenImportProcessor.resultHandlerType; const TokenImportFileProcessor(); @override 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 4a5be5458..1d2a06e16 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 @@ -40,6 +40,7 @@ import '../../utils/token_import_origins.dart'; import 'token_import_file_processor_interface.dart'; class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { + static get resultHandlerType => TokenImportFileProcessor.resultHandlerType; const TwoFasAuthenticatorImportFileProcessor(); static const String TWOFAS_TYPE = 'tokenType'; static const String TWOFAS_ISSUER = 'name'; @@ -145,12 +146,20 @@ class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { final results = >[]; for (Map twoFasToken in tokensJsonList) { try { - results.add(ProcessorResultSuccess(Token.fromUriMap(_twoFasToUriMap(twoFasToken)))); + results.add(ProcessorResult.success( + Token.fromUriMap(_twoFasToUriMap(twoFasToken)), + resultHandlerType: resultHandlerType, + )); } on LocalizedException catch (e) { - results.add(ProcessorResultFailed(e.localizedMessage(AppLocalizations.of(await globalContext)!))); + results.add(ProcessorResultFailed( + e.localizedMessage(AppLocalizations.of(await globalContext)!), + resultHandlerType: resultHandlerType, + )); } catch (e) { Logger.error('Failed to parse token.', name: 'two_fas_import_file_processor.dart#_processPlainTokens', error: e, stackTrace: StackTrace.current); - results.add(ProcessorResultFailed(e.toString())); + results.add(ProcessorResultFailed(e.toString(), + resultHandlerType: resultHandlerType, + )); } } Logger.info('successfully imported ${results.length} tokens', name: 'two_fas_import_file_processor.dart#processPlainTokens'); diff --git a/lib/repo/secure_container_credentials_repository.dart b/lib/repo/secure_container_credentials_repository.dart index 9b74b5422..e912546ae 100644 --- a/lib/repo/secure_container_credentials_repository.dart +++ b/lib/repo/secure_container_credentials_repository.dart @@ -4,13 +4,14 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:mutex/mutex.dart'; import '../interfaces/repo/container_credentials_repository.dart'; +import '../model/enums/algorithms.dart'; import '../model/riverpod_states/credentials_state.dart'; import '../model/tokens/container_credentials.dart'; import '../utils/logger.dart'; class SecureContainerCredentialsRepository extends ContainerCredentialsRepository { String get containerCredentialsKey => 'containerCredentials'; - String _keyOfId(String id) => '$containerCredentialsKey.$id'; + String _keyOfSerial(String id) => '$containerCredentialsKey.$id'; final Mutex _m = Mutex(); Future _protect(Future Function() f) => _m.protect(f); final FlutterSecureStorage _storage = const FlutterSecureStorage(); @@ -27,9 +28,17 @@ class SecureContainerCredentialsRepository extends ContainerCredentialsRepositor Logger.warning('Loaded credentials: $credentialsJsonString', name: 'SecureContainerCredentialsRepository'); if (credentialsJsonString.isEmpty) { final credentialState = CredentialsState(credentials: [ - ContainerCredential( - id: '123', + ContainerCredential.finalized( serial: '123', + ecKeyAlgorithm: EcKeyAlgorithm.secp256k1, + hashAlgorithm: Algorithms.SHA256, + issuer: '', + nonce: '', + timestamp: DateTime.now(), + finalizationUrl: Uri(), + publicServerKey: '', + publicClientKey: '', + privateClientKey: '', ), ]); Logger.warning('Returning default credentials: $credentialState', name: 'SecureContainerCredentialsRepository'); @@ -51,7 +60,7 @@ class SecureContainerCredentialsRepository extends ContainerCredentialsRepositor @override Future deleteCredential(String id) async { - await _delete(_keyOfId(id)); + await _delete(_keyOfSerial(id)); return await loadCredentialsState(); } @@ -68,7 +77,7 @@ class SecureContainerCredentialsRepository extends ContainerCredentialsRepositor @override Future loadCredential(String id) async { - final credentialJsonString = await _read(_keyOfId(id)); + final credentialJsonString = await _read(_keyOfSerial(id)); if (credentialJsonString == null) return null; return ContainerCredential.fromJson(jsonDecode(credentialJsonString)); } @@ -76,7 +85,7 @@ class SecureContainerCredentialsRepository extends ContainerCredentialsRepositor @override Future saveCredential(ContainerCredential credential) async { final credentialJsonString = jsonEncode(credential.toJson()); - await _write(_keyOfId(credential.id), credentialJsonString); + await _write(_keyOfSerial(credential.serial), credentialJsonString); return await loadCredentialsState(); } } diff --git a/lib/repo/secure_token_repository.dart b/lib/repo/secure_token_repository.dart index bf256bfcc..1ff0aae19 100644 --- a/lib/repo/secure_token_repository.dart +++ b/lib/repo/secure_token_repository.dart @@ -34,7 +34,7 @@ import '../model/tokens/token.dart'; import '../utils/globals.dart'; import '../utils/identifiers.dart'; import '../utils/logger.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../utils/view_utils.dart'; import '../views/settings_view/settings_view_widgets/send_error_dialog.dart'; import '../widgets/dialog_widgets/default_dialog.dart'; diff --git a/lib/utils/customization/application_customization.dart b/lib/utils/customization/application_customization.dart index 03930bfb9..a54a8491f 100644 --- a/lib/utils/customization/application_customization.dart +++ b/lib/utils/customization/application_customization.dart @@ -21,6 +21,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; + import '../../../utils/customization/theme_customization.dart'; import '../../model/enums/app_feature.dart'; import '../../model/enums/image_file_type.dart'; diff --git a/lib/utils/home_widget_utils.dart b/lib/utils/home_widget_utils.dart index 5a4a330b0..8ed054344 100644 --- a/lib/utils/home_widget_utils.dart +++ b/lib/utils/home_widget_utils.dart @@ -50,7 +50,7 @@ import '../widgets/home_widgets/home_widget_otp.dart'; import '../widgets/home_widgets/home_widget_unlinked.dart'; import 'globals.dart'; import 'logger.dart'; -import 'riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import 'riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import 'riverpod/riverpod_providers/state_providers/home_widget_provider.dart'; const appGroupId = 'group.authenticator_home_widget_group'; diff --git a/lib/utils/identifiers.dart b/lib/utils/identifiers.dart index 2925890b1..4dea2674f 100644 --- a/lib/utils/identifiers.dart +++ b/lib/utils/identifiers.dart @@ -27,6 +27,11 @@ const defaultCrashReportRecipient = 'app-crash@netknights.it'; // qr codes: const String URI_ID = 'URI_ID'; const String URI_SERIAL = 'URI_SERIAL'; +const String URI_NONCE = 'URI_NONCE'; +const String URI_TIMESTAMP = 'URI_TIMESTAMP'; +const String URI_FINALIZATION_URL = 'URI_FINALIZATION_URL'; +const String URI_KEY_ALGORITHM = 'URI_KEY_ALGORITHM'; +const String URI_HASH_ALGORITHM = 'URI_HASH_ALGORITHM'; const String URI_CONTAINER_SERIAL = 'URI_CONTAINER_SERIAL'; const String URI_TYPE = 'URI_TYPE'; const String URI_LABEL = 'URI_LABEL'; @@ -67,13 +72,30 @@ const String PUSH_REQUEST_SSL_VERIFY = 'sslverify'; // 6. const String PUSH_REQUEST_SIGNATURE = 'signature'; // 7. const String PUSH_REQUEST_ANSWERS = 'require_presence'; // 8. +// Container registration: +const String PUBLIC_SERVER_KEY = 'PUBLIC_SERVER_KEY'; + 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; +void validateMap(Map map, Map keys) { + for (String key in keys.keys) { + final type = keys[key]!; + if (!type.isTypeOf(map[key])) { + throw ArgumentError('Map does not contain required key "$key" of type $type'); } } - return true; +} + +class TypeMatcher { + const TypeMatcher(); + bool isTypeOf(dynamic value) => value is T; + + @override + String toString() => 'TypeMatcher<${T.runtimeType}>'; + + @override + bool operator ==(Object other) => other is TypeMatcher; + + @override + int get hashCode => toString().hashCode; } diff --git a/lib/utils/push_provider.dart b/lib/utils/push_provider.dart index 773084b6e..b9c53467e 100644 --- a/lib/utils/push_provider.dart +++ b/lib/utils/push_provider.dart @@ -38,7 +38,7 @@ import 'globals.dart'; import 'logger.dart'; import 'privacyidea_io_client.dart'; import 'riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import 'riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import 'riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import 'riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import 'rsa_utils.dart'; import 'utils.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart similarity index 100% rename from lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.g.dart similarity index 79% rename from lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.g.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.g.dart index bdb32253e..c96e565a8 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.g.dart @@ -6,14 +6,18 @@ part of 'app_constraints_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$appConstraintsNotifierHash() => r'6b6633ada94116eb933767ab1e29aeecf3e4397c'; +String _$appConstraintsNotifierHash() => + r'6b6633ada94116eb933767ab1e29aeecf3e4397c'; /// See also [AppConstraintsNotifier]. @ProviderFor(AppConstraintsNotifier) -final appConstraintsNotifierProvider = AutoDisposeNotifierProvider.internal( +final appConstraintsNotifierProvider = AutoDisposeNotifierProvider< + AppConstraintsNotifier, BoxConstraints?>.internal( AppConstraintsNotifier.new, name: r'appConstraintsNotifierProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null : _$appConstraintsNotifierHash, + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$appConstraintsNotifierHash, dependencies: null, allTransitiveDependencies: null, ); diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/application_customizer_provider.dart similarity index 100% rename from lib/utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/application_customizer_provider.dart diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/application_customizer_provider.g.dart similarity index 100% rename from lib/utils/riverpod/riverpod_providers/state_providers/application_customizer_provider.g.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/application_customizer_provider.g.dart diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart index 4b60bf66e..51e97b73e 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart @@ -17,26 +17,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import 'dart:convert'; + +import 'package:basic_utils/basic_utils.dart'; +import 'package:collection/collection.dart'; import 'package:mutex/mutex.dart'; +import 'package:privacyidea_authenticator/model/processor_result.dart'; +import 'package:privacyidea_authenticator/utils/globals.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; +import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../../interfaces/repo/container_credentials_repository.dart'; import '../../../../model/riverpod_states/credentials_state.dart'; import '../../../../model/tokens/container_credentials.dart'; import '../../../../repo/secure_container_credentials_repository.dart'; +import '../../../../widgets/dialog_widgets/enter_passphrase_dialog.dart'; import '../../../logger.dart'; part 'credential_notifier.g.dart'; @Riverpod(keepAlive: true) -class CredentialsNotifier extends _$CredentialsNotifier { +class CredentialsNotifier extends _$CredentialsNotifier with ResultHandler { final _stateMutex = Mutex(); final _repoMutex = Mutex(); + late PrivacyideaIOClient _ioClient; late ContainerCredentialsRepository _repo; @override Future build() async { _repo = SecureContainerCredentialsRepository(); + _ioClient = const PrivacyideaIOClient(); Logger.warning('Building credentialsProvider', name: 'CredentialsNotifier'); return _repo.loadCredentialsState(); } @@ -52,40 +63,110 @@ class CredentialsNotifier extends _$CredentialsNotifier { Future addCredential(ContainerCredential credential) async { await _stateMutex.acquire(); - final newState = await _saveCredentialsToRepo(credential); + final newState = await _saveCredentialToRepo(credential); + state = AsyncValue.data(newState); + _stateMutex.release(); + return newState; + } + + Future addCredentials(List credentials) async { + await _stateMutex.acquire(); + final newCredentials = credentials.toList(); + final oldCredentials = (await future).credentials; + final combinedCredentials = []; + for (var oldCredential in oldCredentials) { + final newCredential = newCredentials.firstWhereOrNull((newCredential) => newCredential.serial == oldCredential.serial); + if (newCredential == null) { + combinedCredentials.add(oldCredential); + } else { + combinedCredentials.add(newCredential); + newCredentials.remove(newCredential); + } + } + combinedCredentials.addAll(newCredentials); + final newState = await _saveCredentialsStateToRepo(CredentialsState(credentials: combinedCredentials)); state = AsyncValue.data(newState); _stateMutex.release(); return newState; } - Future _saveCredentialsToRepo(ContainerCredential credential) async { + Future _saveCredentialToRepo(ContainerCredential credential) async { return await _repoMutex.protect(() async => await _repo.saveCredential(credential)); } -} -class MockContainerCredentialsRepository extends ContainerCredentialsRepository { - final state = CredentialsState(credentials: [ - ContainerCredential( - id: '123', - serial: '123', - ) - ]); + Future _saveCredentialsStateToRepo(CredentialsState credentialsState) async { + return await _repoMutex.protect(() async => await _repo.saveCredentialsState(credentialsState)); + } - @override - Future deleteAllCredentials() => Future.value(state); + Future handleCredentialResults(List> credentialResults) async { + final containerCredentials = credentialResults.getData(); + if (containerCredentials.isEmpty) { + return future; + } + final currentState = await future; + final stateCredentials = currentState.credentials; + final stateCredentialsSerials = stateCredentials.map((e) => e.serial); + final newCredentials = containerCredentials.where((element) => !stateCredentialsSerials.contains(element.serial)).toList(); + return addCredentials(newCredentials); + } - @override - Future deleteCredential(String id) => Future.value(state); + Future finalize(ContainerCredential credential) async { + await _stateMutex.acquire(); + if (credential is! ContainerCredentialUnfinalized) { + throw ArgumentError('Credential must not be finalized'); + } - @override - Future loadCredential(String id) => Future.value(state.credentials.firstOrNull); + final keyPair = CryptoUtils.generateEcKeyPair(curve: credential.ecKeyAlgorithm.curveName); - @override - Future loadCredentialsState() => Future.value(state); + credential = credential.withClientKeyPair(keyPair) as ContainerCredentialUnfinalized; + final ecPrivateClientKey = credential.ecPrivateClientKey!; + //POST /container/register/finalize + // Request: { + // 'container_serial': , + // 'public_client_key': , + // 'signature': )>, + // } + + final passphrase = credential.passphrase != null ? EnterPassphraseDialog.show(await globalContext) : null; + + final message = '${credential.nonce}' + '|${credential.timestamp}' + '|${credential.finalizationUrl}' + '|${credential.serial}' + '${passphrase != null ? '|$passphrase' : ''}'; + + final signature = const EccUtils().trySignWithPrivateKey(ecPrivateClientKey, message); + + final body = { + 'container_serial': credential.serial, + 'public_client_key': credential.publicClientKey, + 'signature': signature, + }; + + final response = await _ioClient.doPost(url: credential.finalizationUrl, body: body); + final Map responseJson; + final ContainerCredentialFinalized finalizedCredential; + try { + responseJson = jsonDecode(response.body); + validateMap(responseJson, {PUBLIC_SERVER_KEY: TypeMatcher()}); + final publicServerKey = const EccUtils().deserializeECPublicKey(responseJson[PUBLIC_SERVER_KEY]); + finalizedCredential = credential.finalize(publicServerKey: publicServerKey)!; + } catch (e) { + Logger.error('Failed to decode response body', error: e, name: 'CredentialsNotifier#finalize'); + return null; + } + return await addCredential(finalizedCredential); + } @override - Future saveCredential(ContainerCredential credential) => Future.value(state); + Future handleProcessorResult(ProcessorResult result, Map args) { + // TODO: implement handleResult + throw UnimplementedError(); + } @override - Future saveCredentialsState(CredentialsState credentialsState) => Future.value(state); + Future handleProcessorResults(List results, Map args) { + // TODO: implement handleResults + throw UnimplementedError(); + } } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart index 423e3cd42..97911d4b9 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart @@ -7,7 +7,7 @@ part of 'credential_notifier.dart'; // ************************************************************************** String _$credentialsNotifierHash() => - r'459a5bf82645911570b3c7947743ff2f959fa751'; + r'f2f6e55487b59136bb7ce6aa3e05df96d3d8fd82'; /// See also [CredentialsNotifier]. @ProviderFor(CredentialsNotifier) diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart index eda7701e5..a3c21ff4f 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart @@ -26,7 +26,7 @@ import '../../../../model/mixins/sortable_mixin.dart'; import '../../../../model/token_folder.dart'; import '../../../../model/tokens/token.dart'; import 'token_folder_notifier.dart'; -import '../state_notifier_providers/token_notifier.dart'; +import 'token_notifier.dart'; part 'sortable_notifier.g.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart index a60733c1a..1ac5ca1bb 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -29,7 +29,7 @@ import '../../../../repo/token_container_state_repositorys/hybrid_token_containe import '../../../../repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; import '../../../../repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart'; import '../../../../model/tokens/container_credentials.dart'; -import '../state_notifier_providers/token_notifier.dart'; +import 'token_notifier.dart'; part 'token_container_notifier.g.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart similarity index 91% rename from lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart index 533c97763..3b6749e30 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart @@ -57,7 +57,7 @@ import '../../../privacyidea_io_client.dart'; import '../../../rsa_utils.dart'; import '../../../utils.dart'; import '../../../view_utils.dart'; -import '../generated_providers/settings_notifier.dart'; +import 'settings_notifier.dart'; import '../state_providers/status_message_provider.dart'; part 'token_notifier.g.dart'; @@ -69,27 +69,8 @@ final tokenProvider = tokenNotifierProviderOf( repo: const SecureTokenRepository(), ); -// StateNotifierProvider( -// (ref) { -// Logger.info("New TokenNotifier created"); -// final newTokenNotifier = TokenNotifier(ref: ref); - -// ref.listen(deeplinkNotifierProvider, (previous, newLink) { -// newLink.whenData( -// (data) { -// Logger.info("Received new deeplink with data: $data", name: 'tokenProvider#deeplinkProvider'); -// newTokenNotifier.handleLink(data.uri); -// }, -// ); -// }); - -// return newTokenNotifier; -// }, -// name: 'tokenProvider', -// ); - @Riverpod(keepAlive: true) -class TokenNotifier extends _$TokenNotifier { +class TokenNotifier extends _$TokenNotifier with ResultHandler { static final Map _hidingTimers = {}; late final Future initState; // final StateNotifierProviderRef ref; @@ -257,7 +238,7 @@ class TokenNotifier extends _$TokenNotifier { return false; } _repoMutex.release(); - _handlePushTokensIfExist(); + handlePushTokensIfExist(); return true; } @@ -280,7 +261,7 @@ class TokenNotifier extends _$TokenNotifier { return failedTokens; } _repoMutex.release(); - _handlePushTokensIfExist(); + handlePushTokensIfExist(); return []; } @@ -303,7 +284,7 @@ class TokenNotifier extends _$TokenNotifier { return state; } _repoMutex.release(); - _handlePushTokensIfExist(); + handlePushTokensIfExist(); return newState; } @@ -384,17 +365,23 @@ class TokenNotifier extends _$TokenNotifier { /// Adds a new token and returns true if successful, false if not. Future addNewToken(Token token) async { final success = await _addOrReplaceToken(token); - await _handlePushTokensIfExist(); + await handlePushTokensIfExist(); return success; } + Future addNewTokens(List tokens) async { + final failedTokens = await _addOrReplaceTokens(tokens); + await handlePushTokensIfExist(); + return failedTokens.isEmpty; + } + /// Adds or replaces a token and returns true if successful, false if not. Future addOrReplaceToken(Token token) => _addOrReplaceToken(token); /// Adds new tokens and returns the tokens that could not be added. Future> addTokens(List tokens) async { final failedTokens = await _addOrReplaceTokens(tokens); - await _handlePushTokensIfExist(); + await handlePushTokensIfExist(); return failedTokens; } @@ -886,83 +873,58 @@ class TokenNotifier extends _$TokenNotifier { /////////////////////////////////////////////////////////////////////////////// /// Does not need to wait for updating functions because they doesn't depend on any state */ - /// The return value of a qrCode could be any object. In this case should be a String that is a valid URI. - /// If it is not a valid URI, the user will be informed. - Future handleQrCode(Object? qrCode) async { - Uri uri; - try { - qrCode as String; - uri = Uri.parse(qrCode); - } catch (_) { - showMessage(message: 'The scanned QR code is not a valid URI.', duration: const Duration(seconds: 3)); - Logger.warning('Scanned Data: $qrCode', error: 'Scanned QR code is not a valid URI.', name: 'token_notifier.dart#handleQrCode'); - return; - } - List tokens = await _tokensFromUri(uri); - tokens = tokens - .map( - (e) => e.copyWith( - origin: e.origin?.copyWith(source: TokenOriginSourceType.qrScan) ?? - TokenOriginSourceType.qrScan.toTokenOrigin(data: uri.toString(), isPrivacyIdeaToken: null), - ), - ) - .toList(); - - await _addOrReplaceTokens(tokens); - await _handlePushTokensIfExist(); + Future handleLink(Uri uri) async { + final tokenResults = await TokenImportSchemeProcessor.processUriByAny(uri); + if (tokenResults == null) return; + await handleProcessorResults(tokenResults, {'TokenOriginSourceType': TokenOriginSourceType.link}); } - Future handleLink(Uri uri) async { - List tokens = await _tokensFromUri(uri); - tokens = tokens - .map((e) => e.copyWith( - origin: e.origin?.copyWith(source: TokenOriginSourceType.link) ?? - TokenOriginSourceType.link.toTokenOrigin(data: uri.toString(), isPrivacyIdeaToken: null))) - .toList(); - await _addOrReplaceTokens(tokens); - await _handlePushTokensIfExist(); + @override + Future handleProcessorResult(ProcessorResult result, Map args) { + // TODO: implement handleResult + throw UnimplementedError(); } - Future> _tokensFromUri(Uri uri) async { - if (!TokenImportSchemeProcessor.allSupportedSchemes.contains(uri.scheme)) { - return Future.value([]); - } + @override + Future handleProcessorResults(List results, Map args) async { + final List> tokenResults = results.whereType>().toList(); + if (tokenResults.isEmpty) return null; + final List resultTokens = tokenResults.getData(); + final stateTokens = state.tokens; + List? tokensToKeep; + final selectedType = (args['TokenImportType'] as TokenImportType?) ?? TokenImportType.qrScan; + try { - final results = await TokenImportSchemeProcessor.processUriByAny(uri); - if (results == null || results.isEmpty) { - showMessage(message: 'The scanned QR code is not a valid URI.', duration: const Duration(seconds: 3)); - Logger.warning('Scanned Data: $uri', error: 'Scanned QR code is not a valid URI.', name: 'token_notifier.dart#handleQrCode'); - return []; - } - final failedResults = results.whereType().toList(); - for (var failedResult in failedResults) { - ref.read(statusMessageProvider.notifier).state = (AppLocalizations.of((await globalContext))!.malformedData, failedResult.message); - } - final successResults = results.whereType>().toList(); - if (successResults.isEmpty) { - return []; - } - if (successResults.length > 1 || state.tokens.any((e) => successResults.first.resultData.isSameTokenAs(e) == true)) { + if (resultTokens.length > 1 || stateTokens.any((e) => resultTokens.first.isSameTokenAs(e) == true)) { Navigator.of(globalNavigatorKey.currentContext!).popUntil((route) => route.isFirst); - final tokensToKeep = await Navigator.of(globalNavigatorKey.currentContext!).push>( + tokensToKeep = await Navigator.of(globalNavigatorKey.currentContext!).push>( MaterialPageRoute>( builder: (context) => ImportPlainTokensPage( titleName: AppLocalizations.of(context)!.importTokens, - processorResults: results, - selectedType: TokenImportType.qrScan, + processorResults: tokenResults, + selectedType: selectedType, ), ), ); - return tokensToKeep ?? []; + } else { + tokensToKeep = resultTokens; } - return successResults.map((e) => e.resultData).toList(); } catch (error, stackTrace) { Logger.error('Error while processing QR code.', name: 'token_notifier.dart#handleQrCode', error: error, stackTrace: stackTrace); - return []; + return null; } + if (tokensToKeep == null) return null; + tokensToKeep = tokensToKeep + .map((e) => e.copyWith( + origin: e.origin?.copyWith(source: TokenOriginSourceType.link) ?? + TokenOriginSourceType.link.toTokenOrigin(data: 'No Origindata available', isPrivacyIdeaToken: null))) + .toList(); + await handlePushTokensIfExist(); + await addNewTokens(tokensToKeep); + return null; } - /* ///////////////////////////////////////////////////////////////////////////// +/* ///////////////////////////////////////////////////////////////////////////// /////////////////////////// Helper Methods ///////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// */ @@ -980,7 +942,7 @@ class TokenNotifier extends _$TokenNotifier { } } - Future _handlePushTokensIfExist() async { + Future handlePushTokensIfExist() async { Logger.info('Handling push tokens if they exist.', name: 'token_notifier.dart#_handlePushTokensIfExist'); final pushTokens = state.pushTokens; if (pushTokens.isEmpty || state.pushTokens.isEmpty) { @@ -1012,11 +974,3 @@ class TokenNotifier extends _$TokenNotifier { _hidingTimers.clear(); } } - - - - - - - -// } diff --git a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart similarity index 98% rename from lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.g.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart index 26ff19f6b..83c5c0f93 100644 --- a/lib/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart @@ -6,7 +6,7 @@ part of 'token_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$tokenNotifierHash() => r'056041b8761574fb7ff5682ddee48012d15bb2ae'; +String _$tokenNotifierHash() => r'f367f6acf72448089aa9f5b45b2f1e1dba5d0097'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart index 1abb5aad8..7a6dcfe16 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart @@ -22,7 +22,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../model/tokens/otp_token.dart'; import '../../../home_widget_utils.dart'; import '../../../logger.dart'; -import '../state_notifier_providers/token_notifier.dart'; +import '../generated_providers/token_notifier.dart'; final homeWidgetProvider = StateProvider>( (ref) { diff --git a/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart b/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart index 8415ae790..06803aa12 100644 --- a/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart @@ -23,7 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../globals.dart'; import '../../../logger.dart'; -import '../state_notifier_providers/token_notifier.dart'; +import '../generated_providers/token_notifier.dart'; import '../state_providers/status_message_provider.dart'; final connectivityProvider = StreamProvider>( diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 8883b2ff9..967230394 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -21,6 +21,7 @@ import 'dart:convert'; import 'dart:io'; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -29,16 +30,21 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/extensions/sortable_list.dart'; +import 'package:privacyidea_authenticator/processors/scheme_processors/scheme_processor_interface.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart'; +import '../model/enums/token_origin_source_type.dart'; import '../model/mixins/sortable_mixin.dart'; +import '../model/processor_result.dart'; import '../model/token_folder.dart'; import '../model/tokens/token.dart'; import 'customization/application_customization.dart' show ApplicationCustomization; import 'riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import 'riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import 'riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import 'riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; +import 'view_utils.dart'; /// Inserts [char] at the position [pos] in the given String ([str]), /// and returns the resulting String. @@ -174,3 +180,63 @@ void dragSortableOnAccept({ }); }); } + +ByteData bigIntToByteData(BigInt bigInt) { + final data = ByteData((bigInt.bitLength / 8).ceil()); + + for (var i = 1; i <= data.lengthInBytes; i++) { + data.setUint8(data.lengthInBytes - i, bigInt.toUnsigned(8).toInt()); + bigInt = bigInt >> 8; + } + + return data; +} + +BigInt byteDataToBigInt(ByteData data) { + BigInt result = BigInt.zero; + for (var i = 0; i < data.lengthInBytes; i++) { + result = result << 8; + result = result | BigInt.from(data.getUint8(i)); + } + return result; +} + +Uint8List bigIntToBytes(BigInt bigInt) => bigIntToByteData(bigInt).buffer.asUint8List(); + +BigInt bytesToBigInt(Uint8List bytes) => byteDataToBigInt(ByteData.sublistView(bytes)); + +Future scanQrCode(List resultHandlerList, Object? qrCode) async { + Uri uri; + try { + uri = switch (qrCode.runtimeType) { + const (String) => Uri.parse(qrCode as String), + const (Uri) => qrCode as Uri, + _ => throw ArgumentError('Invalid type for qrCode: $qrCode'), + }; + } catch (e) { + showMessage(message: 'The scanned QR code is not a valid URI.', duration: const Duration(seconds: 3)); + Logger.warning('Scanned Data: $qrCode', error: 'Scanned QR code is not a valid URI: $e', name: 'utils.dart#scanQrCode'); + return; + } + final processorResults = await SchemeProcessor.processUriByAny(uri); + if (processorResults == null) return; + final resultHandlerTypeMap = , List>{}; + + for (var result in processorResults) { + final typeMatcher = result.resultHandlerType; + if (typeMatcher == null) continue; + if (resultHandlerTypeMap.containsKey(result.resultHandlerType)) { + resultHandlerTypeMap[typeMatcher]!.add(result); + } else { + resultHandlerTypeMap[typeMatcher] = [result]; + } + } + + for (var resultHandlerType in resultHandlerTypeMap.keys) { + final results = resultHandlerTypeMap[resultHandlerType]!; + final resultHandler = resultHandlerList.firstWhereOrNull((resultHandler) => resultHandlerType.isTypeOf(resultHandler)); + if (resultHandler != null) { + await resultHandler.handleProcessorResults(results, {'TokenOriginSourceType': TokenOriginSourceType.qrScan}); + } + } +} 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 bdaf397f8..6d837409e 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 @@ -33,7 +33,7 @@ import '../../model/extensions/enums/token_origin_source_type.dart'; import '../../model/tokens/token.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import 'add_token_manually_view_widgets/labeled_dropdown_button.dart'; class AddTokenManuallyView extends ConsumerStatefulWidget { diff --git a/lib/views/import_tokens_view/import_tokens_view.dart b/lib/views/import_tokens_view/import_tokens_view.dart index bc073b843..83959eccc 100644 --- a/lib/views/import_tokens_view/import_tokens_view.dart +++ b/lib/views/import_tokens_view/import_tokens_view.dart @@ -23,7 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../l10n/app_localizations.dart'; import '../../model/token_import/token_import_origin.dart'; import '../../model/tokens/token.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../utils/token_import_origins.dart'; import '../view_interface.dart'; import 'pages/import_start_page.dart'; diff --git a/lib/views/import_tokens_view/pages/import_encrypted_data_page.dart b/lib/views/import_tokens_view/pages/import_encrypted_data_page.dart index ee73e5502..926f52979 100644 --- a/lib/views/import_tokens_view/pages/import_encrypted_data_page.dart +++ b/lib/views/import_tokens_view/pages/import_encrypted_data_page.dart @@ -134,6 +134,7 @@ class _ImportEncryptedDataPageState extends State { () async { try { final processorResults = await widget.processor.processTokenMigrate(widget.data, args: _passwordController.text); + if (processorResults == null) return; _pushImportPlainTokensPage(processorResults); } on BadDecryptionPasswordException catch (_) { setState(() { 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 e9256a404..9e254bb1b 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 @@ -26,7 +26,7 @@ import '../../../model/extensions/enums/token_import_type_extension.dart'; import '../../../model/processor_result.dart'; import '../../../model/token_import/token_import_entry.dart'; import '../../../model/tokens/token.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../import_tokens_view.dart'; import '../widgets/conflicted_import_tokens_list.dart'; import '../widgets/failed_imports_list.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 b055bf613..42d72cc95 100644 --- a/lib/views/import_tokens_view/pages/import_start_page.dart +++ b/lib/views/import_tokens_view/pages/import_start_page.dart @@ -23,6 +23,8 @@ import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import 'package:zxing2/qrcode.dart'; import 'package:zxing2/zxing2.dart'; @@ -228,6 +230,7 @@ class _ImportStartPageState extends ConsumerState { isPrivacyIdeaToken: false, data: t.resultData.origin?.data ?? fileString, ), + resultHandlerType: const TypeMatcher(), ); }).toList(); @@ -250,6 +253,11 @@ class _ImportStartPageState extends ConsumerState { return; } var results = await schemeProcessor.processUri(uri); + if (results == null || results.isEmpty) { + if (mounted == false) return; + setState(() => _errorText = localizations.invalidQrScan(widget.appName)); + return; + } results = results.map>((t) { if (t is! ProcessorResultSuccess) return t; return ProcessorResultSuccess( @@ -259,6 +267,7 @@ class _ImportStartPageState extends ConsumerState { token: t.resultData, data: t.resultData.origin?.data ?? uri.toString(), ), + resultHandlerType: const TypeMatcher(), ); }).toList(); Logger.info("QR code scanned successfully", name: "_scanQrCode#ImportStartPage"); @@ -340,19 +349,22 @@ class _ImportStartPageState extends ConsumerState { return; } var results = await schemeProcessor.processUri(uri); - if (results.isEmpty) { + if (results == null || results.isEmpty) { if (!mounted) return; setState(() => _errorText = localizations.invalidLink(widget.appName)); return; } results = results.map>((t) { if (t is! ProcessorResultSuccess) return t; - return ProcessorResultSuccess(TokenOriginSourceType.linkImport.addOriginToToken( - appName: widget.appName, - token: t.resultData, - isPrivacyIdeaToken: false, - data: _linkController.text, - )); + return ProcessorResultSuccess( + TokenOriginSourceType.linkImport.addOriginToToken( + appName: widget.appName, + token: t.resultData, + isPrivacyIdeaToken: false, + data: _linkController.text, + ), + resultHandlerType: const TypeMatcher(), + ); }).toList(); if (!mounted) return; setState(() => FocusScope.of(context).unfocus()); 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 d12ad2414..11a2e9820 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 @@ -24,7 +24,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../utils/customization/theme_extentions/extended_text_theme.dart'; import '../../utils/home_widget_utils.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../utils/utils.dart'; import '../view_interface.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 96d489594..da1eef86b 100644 --- a/lib/views/main_view/main_view_widgets/connectivity_listener.dart +++ b/lib/views/main_view/main_view_widgets/connectivity_listener.dart @@ -23,7 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; import '../../../utils/logger.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import '../../../utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.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 5ba7758df..dfb16ed79 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 @@ -26,7 +26,7 @@ import '../../../../../utils/customization/theme_extentions/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; class DeleteTokenFolderAction extends StatelessWidget { 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 93bffbbd2..6524eb07c 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 @@ -36,7 +36,7 @@ import '../../../../utils/globals.dart'; import '../../../../utils/lock_auth.dart'; import '../../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../../utils/utils.dart'; import '../../../../widgets/custom_trailing.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart index ef8f0d27f..115785088 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart @@ -23,7 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/introduction.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart'; import '../../../widgets/focused_item_as_overlay.dart'; import '../../add_token_manually_view/add_token_manually_view.dart'; import '../../settings_view/settings_view.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart index 6c72becd3..c863381e5 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart @@ -20,10 +20,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../l10n/app_localizations.dart'; +import '../../../../model/processor_result.dart'; import '../../../../utils/globals.dart'; -import '../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../../utils/utils.dart'; import '../../../../utils/view_utils.dart'; import '../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../qr_scanner_view/qr_scanner_view.dart'; @@ -47,7 +50,11 @@ class QrScannerButton extends ConsumerWidget { /// Open the QR-code scanner and call `handleQrCode`, with the scanned code as the argument. Navigator.pushNamed(globalNavigatorKey.currentContext!, QRScannerView.routeName).then((qrCode) { - if (qrCode != null) ref.read(tokenProvider.notifier).handleQrCode(qrCode); + final resultHandlers = [ + ref.read(tokenProvider.notifier), + ref.read(credentialsNotifierProvider.notifier), + ]; + scanQrCode(resultHandlers, qrCode); }); }, tooltip: AppLocalizations.of(context)!.scanQrCode, diff --git a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart index ed4c76819..f050de719 100644 --- a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart +++ b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart @@ -26,7 +26,7 @@ import '../../../model/token_folder.dart'; import '../../../model/tokens/token.dart'; import '../../../utils/logger.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/state_providers/token_filter_provider.dart'; import 'folder_widgets/token_folder_expandable.dart'; import 'token_widgets/token_widget_builder.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart index 43dd2f0b2..7373d8ae5 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart @@ -29,7 +29,7 @@ import '../../../../../model/enums/day_password_token_view_mode.dart'; import '../../../../../model/riverpod_states/settings_state.dart'; import '../../../../../model/tokens/day_password_token.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../utils/utils.dart'; import '../../../../../widgets/custom_texts.dart'; import '../../../../../widgets/custom_trailing.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 f8fc74b74..bc6943fed 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 @@ -25,7 +25,7 @@ import '../../../../../model/tokens/token.dart'; import '../../../../../utils/customization/theme_extentions/action_theme.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../loading_indicator.dart'; import '../token_action.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 3efc4d557..85ba8ba2f 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 @@ -29,7 +29,7 @@ import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../widgets/focused_item_as_overlay.dart'; import '../token_action.dart'; import 'default_edit_action_dialog.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart index 156cf6317..1569e4d87 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart @@ -26,7 +26,7 @@ import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/logger.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; class DefaultEditActionDialog extends ConsumerStatefulWidget { 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 b2b4ea986..0adfe03a5 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 @@ -29,7 +29,7 @@ import '../../../../../utils/customization/theme_extentions/action_theme.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../widgets/focused_item_as_overlay.dart'; import '../token_action.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart index 5c71591f5..4ec8c0bf0 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart @@ -24,7 +24,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/hotp_token.dart'; import '../../../../../utils/globals.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../utils/utils.dart'; import '../../../../../widgets/custom_texts.dart'; import '../../../../../widgets/custom_trailing.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 02b2e1c59..e2d2caa17 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 @@ -28,7 +28,7 @@ import '../../../../../../utils/customization/theme_extentions/action_theme.dart import '../../../../../../utils/globals.dart'; import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../../widgets/enable_text_edit_after_many_taps.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../default_token_actions/default_edit_action_dialog.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart index 889233aa5..23e28b70c 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart @@ -50,24 +50,22 @@ class PushTokenWidget extends TokenWidget { }); @override - TokenWidgetBase build(BuildContext context) { - return TokenWidgetBase( - key: Key(token.id), - token: token, - tile: PushTokenWidgetTile(token), - dragIcon: Icons.notifications, - editAction: EditPushTokenAction(token: token, key: Key('${token.id}editAction')), - stack: [ - if (!token.isRolledOut) - Positioned.fill( - child: ClipRect( - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), - child: rolloutFailed ? RolloutFailedWidget(token: token) : RolloutWidget(token: token), + TokenWidgetBase build(BuildContext context) => TokenWidgetBase( + key: Key(token.id), + token: token, + tile: PushTokenWidgetTile(token), + dragIcon: Icons.notifications, + editAction: EditPushTokenAction(token: token, key: Key('${token.id}editAction')), + stack: [ + if (!token.isRolledOut) + Positioned.fill( + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: rolloutFailed ? RolloutFailedWidget(token: token) : RolloutWidget(token: token), + ), ), ), - ), - ], - ); - } + ], + ); } 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 2ab5a9bde..bb970b040 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 @@ -24,8 +24,8 @@ 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/riverpod_providers/state_notifier_providers/token_notifier.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../../widgets/press_button.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart index e16729405..1c3544750 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart @@ -24,7 +24,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/totp_token.dart'; import '../../../../../utils/globals.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../utils/utils.dart'; import '../../../../../widgets/custom_texts.dart'; import '../../../../../widgets/custom_trailing.dart'; diff --git a/lib/views/push_token_view/widgets/push_tokens_view_list.dart b/lib/views/push_token_view/widgets/push_tokens_view_list.dart index 27b931378..28283625f 100644 --- a/lib/views/push_token_view/widgets/push_tokens_view_list.dart +++ b/lib/views/push_token_view/widgets/push_tokens_view_list.dart @@ -22,7 +22,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../model/mixins/sortable_mixin.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../widgets/default_refresh_indicator.dart'; import '../../../widgets/drag_item_scroller.dart'; diff --git a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart index 52a1b959c..d14683d80 100644 --- a/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart +++ b/lib/views/settings_view/settings_groups/import_export_tokens_widgets/dialogs/select_tokens_dialog.dart @@ -19,12 +19,12 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../../model/riverpod_states/token_state.dart'; import '../../../../../l10n/app_localizations.dart'; +import '../../../../../model/riverpod_states/token_state.dart'; import '../../../../../model/tokens/token.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../main_view/main_view_widgets/token_widgets/token_widget_builder.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 3de8e2f2a..eeef6e2c4 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 @@ -28,7 +28,7 @@ import 'package:zxing2/qrcode.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; import '../../../../../utils/encryption/token_encryption.dart'; -import '../../../../../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; +import '../../../../../utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; class ShowQrCodeDialog extends ConsumerWidget { 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 9afbd079c..e8915c967 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 @@ -24,7 +24,7 @@ import '../../../l10n/app_localizations.dart'; import '../../../model/riverpod_states/settings_state.dart'; import '../../../model/tokens/push_token.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../settings_view_widgets/settings_groups.dart'; import '../settings_view_widgets/update_firebase_token_dialog.dart'; diff --git a/lib/views/settings_view/settings_view.dart b/lib/views/settings_view/settings_view.dart index 74fe18c2f..bff4e741d 100644 --- a/lib/views/settings_view/settings_view.dart +++ b/lib/views/settings_view/settings_view.dart @@ -22,7 +22,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../l10n/app_localizations.dart'; import '../../model/tokens/push_token.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../widgets/push_request_listener.dart'; import '../view_interface.dart'; import 'settings_groups/settings_group_error_log.dart'; diff --git a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart index f1a434ccf..679749edd 100644 --- a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart +++ b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart @@ -25,7 +25,7 @@ import '../../../l10n/app_localizations.dart'; import '../../../model/tokens/push_token.dart'; import '../../../utils/globals.dart'; import '../../../utils/logger.dart'; -import '../../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../utils/view_utils.dart'; import '../../../widgets/dialog_widgets/default_dialog.dart'; diff --git a/lib/views/splash_screen/splash_screen.dart b/lib/views/splash_screen/splash_screen.dart index 9911b5e15..9f4d79c30 100644 --- a/lib/views/splash_screen/splash_screen.dart +++ b/lib/views/splash_screen/splash_screen.dart @@ -26,7 +26,7 @@ import '../../utils/customization/application_customization.dart'; import '../../utils/home_widget_utils.dart'; import '../../utils/logger.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../main_view/main_view.dart'; import '../view_interface.dart'; diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index e16b1b547..1259ca7e9 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -14,7 +14,7 @@ import '../utils/riverpod/riverpod_providers/generated_providers/deeplink_notifi import '../utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../utils/riverpod/state_listeners/home_widget_deep_link_listener.dart'; import '../utils/riverpod/state_listeners/home_widget_token_state_listener.dart'; import '../utils/riverpod/state_listeners/navigation_deep_link_listener.dart'; diff --git a/lib/widgets/dialog_widgets/enter_passphrase_dialog.dart b/lib/widgets/dialog_widgets/enter_passphrase_dialog.dart new file mode 100644 index 000000000..f6534c7b1 --- /dev/null +++ b/lib/widgets/dialog_widgets/enter_passphrase_dialog.dart @@ -0,0 +1,60 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:ui'; + +import 'package:flutter/material.dart'; + +class EnterPassphraseDialog extends StatefulWidget { + static Future show(BuildContext context) => showDialog( + context: context, + builder: (context) => BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: const EnterPassphraseDialog(), + ), + ); + + const EnterPassphraseDialog({super.key}); + + @override + State createState() => _EnterPassphraseDialogState(); +} + +class _EnterPassphraseDialogState extends State { + String text = ''; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Enter your passphrase'), + content: TextField( + decoration: const InputDecoration(hintText: 'Passphrase'), + onChanged: (value) => setState(() { + text = value; + }), + ), + actions: [ + TextButton( + onPressed: text.isNotEmpty ? () => Navigator.of(context).pop(text) : null, + child: const Text('OK'), + ), + ], + ); + } +} diff --git a/lib/widgets/dialog_widgets/push_request_dialog.dart b/lib/widgets/dialog_widgets/push_request_dialog.dart index 90a259e75..c4cb84092 100644 --- a/lib/widgets/dialog_widgets/push_request_dialog.dart +++ b/lib/widgets/dialog_widgets/push_request_dialog.dart @@ -12,7 +12,7 @@ import '../../utils/globals.dart'; import '../../utils/lock_auth.dart'; import '../../utils/logger.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart'; -import '../../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../press_button.dart'; import 'default_dialog.dart'; diff --git a/lib/widgets/focused_item_as_overlay.dart b/lib/widgets/focused_item_as_overlay.dart index 2a6308a52..4b72ddc10 100644 --- a/lib/widgets/focused_item_as_overlay.dart +++ b/lib/widgets/focused_item_as_overlay.dart @@ -7,7 +7,7 @@ import 'package:flutter/material.dart'; import '../l10n/app_localizations.dart'; import '../utils/globals.dart'; import '../utils/logger.dart'; -import '../utils/riverpod/riverpod_providers/state_providers/app_constraints_notifier.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart'; import '../utils/utils.dart'; import 'pulse_icon.dart'; import 'tooltip_container.dart'; diff --git a/lib/widgets/hideable_widget_.dart b/lib/widgets/hideable_widget_.dart index 5bc05b080..bc546e8ea 100644 --- a/lib/widgets/hideable_widget_.dart +++ b/lib/widgets/hideable_widget_.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../l10n/app_localizations.dart'; import '../model/tokens/otp_token.dart'; -import '../utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; class HideableWidget extends ConsumerWidget { final OTPToken token; diff --git a/pubspec.lock b/pubspec.lock index f6bf9af1f..c43101af6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -118,6 +118,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + basic_utils: + dependency: "direct main" + description: + name: basic_utils + sha256: "2064b21d3c41ed7654bc82cc476fd65542e04d60059b74d5eed490a4da08fc6c" + url: "https://pub.dev" + source: hosted + version: "5.7.0" boolean_selector: dependency: transitive description: @@ -1817,10 +1825,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.5" watcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index be5e132c8..528428d20 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -95,6 +95,7 @@ dependencies: riverpod_annotation: ^2.3.5 freezed_annotation: ^2.4.4 async: ^2.11.0 + basic_utils: ^5.7.0 diff --git a/test/unit_test/state_notifiers/sortable_notifier_test.dart b/test/unit_test/state_notifiers/sortable_notifier_test.dart index 47e6ce7f1..f79010932 100644 --- a/test/unit_test/state_notifiers/sortable_notifier_test.dart +++ b/test/unit_test/state_notifiers/sortable_notifier_test.dart @@ -11,7 +11,7 @@ import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../tests_app_wrapper.mocks.dart'; diff --git a/test/unit_test/state_notifiers/token_notifier_test.dart b/test/unit_test/state_notifiers/token_notifier_test.dart index d24f30ead..dc79e9447 100644 --- a/test/unit_test/state_notifiers/token_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_notifier_test.dart @@ -12,10 +12,11 @@ import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_notifier_providers/token_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; +import 'package:privacyidea_authenticator/utils/utils.dart'; import '../../tests_app_wrapper.mocks.dart'; @@ -260,8 +261,9 @@ void _testTokenNotifier() { ioClient: const PrivacyideaIOClient(), firebaseUtils: mockFirebaseUtils, ); - final notifier = container.read(testProvider.notifier); - await notifier.handleQrCode('otpauth://totp/issuer2:label2?secret=secret2&issuer=issuer2&algorithm=SHA256&digits=6&period=30'); + final testNotifier = container.read(tokenProvider.notifier); + const qrCode = 'otpauth://totp/issuer2:label2?secret=secret2&issuer=issuer2&algorithm=SHA256&digits=6&period=30'; + await scanQrCode([testNotifier], qrCode); final state = container.read(testProvider); expect(state, isNotNull); after.last = after.last.copyWith(id: state.tokens.last.id); @@ -348,7 +350,7 @@ void _testTokenNotifier() { final notifier = container.read(testProvider.notifier); final initState = await notifier.initState; expect(initState.tokens, before); - await notifier.handleQrCode(otpAuth); + await scanQrCode([notifier], otpAuth); final tokenState = container.read(testProvider); expect(tokenState, isNotNull); expect(tokenState.tokens, after); From 34a9c70277650c8a17588b662832a4c318e75ecd Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:54:16 +0200 Subject: [PATCH 030/285] finalize container --- integration_test/two_step_rollout_test.dart | 6 +- integration_test/views_test.dart | 3 +- lib/model/processor_result.dart | 8 +- lib/model/processor_result.freezed.dart | 17 +- .../riverpod_states/credentials_state.dart | 2 + lib/model/tokens/container_credentials.dart | 30 ++- .../tokens/container_credentials.freezed.dart | 117 +++++----- lib/model/tokens/container_credentials.g.dart | 43 ++-- .../container_credentials_processor.dart | 66 +++++- .../home_widget_processor.dart | 6 +- .../home_widget_navigate_processor.dart | 4 +- .../otp_auth_processor.dart | 8 +- ...rivacyidea_authenticator_qr_processor.dart | 11 +- ...cure_container_credentials_repository.dart | 8 +- ...token_container_state_repository.dart.dart | 12 +- lib/utils/identifiers.dart | 15 +- .../credential_notifier.dart | 171 +++++++++++--- .../credential_notifier.g.dart | 210 ++++++++++++++++-- .../token_container_token_state_listener.dart | 2 +- lib/utils/utils.dart | 1 + .../connectivity_listener.dart | 1 + .../qr_scanner_button.dart | 2 +- .../main_view_tokens_list.dart | 20 +- lib/widgets/app_wrapper.dart | 4 +- lib/widgets/default_refresh_indicator.dart | 2 +- .../example/lib/main.dart | 10 +- .../lib/identifiers.dart | 4 +- .../lib/pi_authenticator_legacy.dart | 4 +- .../model/processor_result_test.dart | 4 +- ...yidea_authenticator_qr_processor_test.dart | 4 +- 30 files changed, 582 insertions(+), 213 deletions(-) diff --git a/integration_test/two_step_rollout_test.dart b/integration_test/two_step_rollout_test.dart index 3b29e2a87..b3a75711d 100644 --- a/integration_test/two_step_rollout_test.dart +++ b/integration_test/two_step_rollout_test.dart @@ -70,7 +70,8 @@ Future _addTwoStepHotpTokenTest(WidgetTester tester) async { await pumpUntilFindNWidgets(tester, find.byType(MainView), 1, const Duration(seconds: 10)); const qrCode = 'otpauth://hotp/OATH0001DBD0?secret=AALIBQJMOGEE7SAVEZ5D3K2ADO7MVFQD&counter=1&digits=6&issuer=privacyIDEA&2step_salt=8&2step_output=20&2step_difficulty=10000'; - await scanQrCode(globalRef!, qrCode); + final notifier = globalRef!.read(tokenProvider.notifier); + await scanQrCode([notifier], qrCode); Logger.info('Finding phone part dialog'); await pumpUntilFindNWidgets(tester, find.text(AppLocalizationsEn().phonePart), 1, const Duration(seconds: 20)); expect(find.text(AppLocalizationsEn().phonePart), findsOneWidget); @@ -93,7 +94,8 @@ Future _addTwoStepTotpTokenTest(WidgetTester tester) async { await pumpUntilFindNWidgets(tester, find.byType(MainView), 1, const Duration(seconds: 10)); const qrCode = 'otpauth://totp/TOTP00009D5F?secret=NZ4OPONKAAGDFN2QHV26ZWYVTLFER4C6&period=30&digits=6&issuer=privacyIDEA&2step_salt=8&2step_output=20&2step_difficulty=10000'; - await scanQrCode(globalRef!, qrCode); + final notifier = globalRef!.read(tokenProvider.notifier); + await scanQrCode([notifier], qrCode); Logger.info('Finding phone part dialog'); await pumpUntilFindNWidgets(tester, find.text(AppLocalizationsEn().phonePart), 1, const Duration(seconds: 20)); expect(find.text(AppLocalizationsEn().phonePart), findsOneWidget); diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index 0ba04af63..2457e55c7 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -169,7 +169,8 @@ Future _settingsViewTest(WidgetTester tester) async { expect(find.byType(SettingsGroup), findsNWidgets(6)); const qrCode = 'otpauth://pipush/label?url=http%3A%2F%2Fwww.example.com&ttl=10&issuer=issuer&enrollment_credential=enrollmentCredentials&v=1&serial=serial&serial=serial&sslverify=0'; - await scanQrCode(globalRef!, qrCode); + final notifier = globalRef!.read(tokenProvider.notifier); + await scanQrCode([notifier], qrCode); await pumpUntilFindNWidgets(tester, find.text(AppLocalizationsEn().pushToken), 1, const Duration(minutes: 5)); expect(find.text(AppLocalizationsEn().pushToken), findsOneWidget); diff --git a/lib/model/processor_result.dart b/lib/model/processor_result.dart index a10c0d392..629eb6408 100644 --- a/lib/model/processor_result.dart +++ b/lib/model/processor_result.dart @@ -32,13 +32,13 @@ part 'processor_result.freezed.dart'; @freezed abstract class ProcessorResult with _$ProcessorResult { const ProcessorResult._(); - factory ProcessorResult.success( + const factory ProcessorResult.success( T resultData, { - required TypeMatcher? resultHandlerType, + TypeMatcher? resultHandlerType, }) = ProcessorResultSuccess; - factory ProcessorResult.failed( + const factory ProcessorResult.failed( String message, { - required TypeMatcher? resultHandlerType, + TypeMatcher? resultHandlerType, }) = ProcessorResultFailed; bool get isSuccess => this is ProcessorResultSuccess; diff --git a/lib/model/processor_result.freezed.dart b/lib/model/processor_result.freezed.dart index 327c3355e..82d27fb6f 100644 --- a/lib/model/processor_result.freezed.dart +++ b/lib/model/processor_result.freezed.dart @@ -157,8 +157,7 @@ class __$$ProcessorResultSuccessImplCopyWithImpl /// @nodoc class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { - _$ProcessorResultSuccessImpl(this.resultData, - {required this.resultHandlerType}) + const _$ProcessorResultSuccessImpl(this.resultData, {this.resultHandlerType}) : super._(); @override @@ -271,10 +270,10 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { } abstract class ProcessorResultSuccess extends ProcessorResult { - factory ProcessorResultSuccess(final T resultData, - {required final TypeMatcher? resultHandlerType}) = + const factory ProcessorResultSuccess(final T resultData, + {final TypeMatcher? resultHandlerType}) = _$ProcessorResultSuccessImpl; - ProcessorResultSuccess._() : super._(); + const ProcessorResultSuccess._() : super._(); T get resultData; @override @@ -334,7 +333,7 @@ class __$$ProcessorResultFailedImplCopyWithImpl /// @nodoc class _$ProcessorResultFailedImpl extends ProcessorResultFailed { - _$ProcessorResultFailedImpl(this.message, {required this.resultHandlerType}) + const _$ProcessorResultFailedImpl(this.message, {this.resultHandlerType}) : super._(); @override @@ -445,10 +444,10 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { } abstract class ProcessorResultFailed extends ProcessorResult { - factory ProcessorResultFailed(final String message, - {required final TypeMatcher? resultHandlerType}) = + const factory ProcessorResultFailed(final String message, + {final TypeMatcher? resultHandlerType}) = _$ProcessorResultFailedImpl; - ProcessorResultFailed._() : super._(); + const ProcessorResultFailed._() : super._(); String get message; @override diff --git a/lib/model/riverpod_states/credentials_state.dart b/lib/model/riverpod_states/credentials_state.dart index 52e82a763..40e942f2e 100644 --- a/lib/model/riverpod_states/credentials_state.dart +++ b/lib/model/riverpod_states/credentials_state.dart @@ -40,5 +40,7 @@ class CredentialsState with _$CredentialsState { return CredentialsState(credentials: credentials); } + ContainerCredential? currentOf(ContainerCredential credential) => credentialsOf(credential.serial); + factory CredentialsState.fromJson(Map json) => _$CredentialsStateFromJson(json); } diff --git a/lib/model/tokens/container_credentials.dart b/lib/model/tokens/container_credentials.dart index a45c62209..66e6c692d 100644 --- a/lib/model/tokens/container_credentials.dart +++ b/lib/model/tokens/container_credentials.dart @@ -49,20 +49,20 @@ class ContainerCredential with _$ContainerCredential { validateMap(uriMap, { URI_ISSUER: const TypeMatcher(), URI_NONCE: const TypeMatcher(), - URI_TIMESTAMP: const TypeMatcher(), - URI_FINALIZATION_URL: const TypeMatcher(), + URI_TIMESTAMP: const TypeMatcher(), + URI_FINALIZATION_URL: const TypeMatcher(), URI_SERIAL: const TypeMatcher(), - URI_KEY_ALGORITHM: const TypeMatcher(), - URI_HASH_ALGORITHM: const TypeMatcher(), + URI_KEY_ALGORITHM: const TypeMatcher(), + URI_HASH_ALGORITHM: const TypeMatcher(), }); return ContainerCredential.unfinalized( issuer: uriMap[URI_ISSUER], nonce: uriMap[URI_NONCE], - timestamp: DateTime.parse(uriMap[URI_TIMESTAMP]), - finalizationUrl: Uri.parse(uriMap[URI_FINALIZATION_URL]), + timestamp: uriMap[URI_TIMESTAMP], + finalizationUrl: uriMap[URI_FINALIZATION_URL], serial: uriMap[URI_SERIAL], - ecKeyAlgorithm: EcKeyAlgorithm.values.byCurveName(uriMap[URI_KEY_ALGORITHM]), - hashAlgorithm: Algorithms.values.byName(uriMap[URI_HASH_ALGORITHM]), + ecKeyAlgorithm: uriMap[URI_KEY_ALGORITHM], + hashAlgorithm: uriMap[URI_HASH_ALGORITHM], ); } @@ -74,7 +74,7 @@ class ContainerCredential with _$ContainerCredential { required String serial, required EcKeyAlgorithm ecKeyAlgorithm, required Algorithms hashAlgorithm, - @Default(ContainerCredentialState.uninitialized) ContainerCredentialState state, + @Default(ContainerFinalizationState.uninitialized) ContainerFinalizationState finalizationState, String? passphrase, String? publicServerKey, String? publicClientKey, @@ -89,7 +89,7 @@ class ContainerCredential with _$ContainerCredential { required String serial, required EcKeyAlgorithm ecKeyAlgorithm, required Algorithms hashAlgorithm, - @Default(ContainerCredentialState.finalized) ContainerCredentialState state, + @Default(ContainerFinalizationState.finalized) ContainerFinalizationState finalizationState, String? passphrase, required String publicServerKey, required String publicClientKey, @@ -118,7 +118,7 @@ class ContainerCredential with _$ContainerCredential { ecKeyAlgorithm: ecKeyAlgorithm, hashAlgorithm: hashAlgorithm, passphrase: passphrase, - state: ContainerCredentialState.finalized, + finalizationState: ContainerFinalizationState.finalized, publicServerKey: this.publicServerKey ?? eccUtils.serializeECPublicKey(publicServerKey!), publicClientKey: publicClientKey ?? eccUtils.serializeECPublicKey(clientKeyPair!.publicKey), privateClientKey: privateClientKey ?? eccUtils.serializeECPrivateKey(clientKeyPair!.privateKey), @@ -129,9 +129,12 @@ class ContainerCredential with _$ContainerCredential { ContainerCredential withPublicServerKey(ECPublicKey publicServerKey) => copyWith(publicServerKey: eccUtils.serializeECPublicKey(publicServerKey)); ECPublicKey? get ecPublicClientKey => publicClientKey == null ? null : eccUtils.deserializeECPublicKey(publicClientKey!); ECPrivateKey? get ecPrivateClientKey => privateClientKey == null ? null : eccUtils.deserializeECPrivateKey(privateClientKey!); + + /// Add client key pair and set finalization state to generatingKeyPairCompleted ContainerCredential withClientKeyPair(AsymmetricKeyPair keyPair) => copyWith( publicClientKey: eccUtils.serializeECPublicKey(keyPair.publicKey), privateClientKey: eccUtils.serializeECPrivateKey(keyPair.privateKey), + finalizationState: ContainerFinalizationState.generatingKeyPairCompleted, ); factory ContainerCredential.fromJson(Map json) => _$ContainerCredentialFromJson(json); @@ -144,14 +147,17 @@ class ContainerCredential with _$ContainerCredential { // }; } -enum ContainerCredentialState { +enum ContainerFinalizationState { uninitialized, generatingKeyPair, generatingKeyPairFailed, + generatingKeyPairCompleted, sendingPublicKey, sendingPublicKeyFailed, + sendingPublicKeyCompleted, parsingResponse, parsingResponseFailed, + parsingResponseCompleted, finalized, } diff --git a/lib/model/tokens/container_credentials.freezed.dart b/lib/model/tokens/container_credentials.freezed.dart index 7f142b19d..7cb4fe051 100644 --- a/lib/model/tokens/container_credentials.freezed.dart +++ b/lib/model/tokens/container_credentials.freezed.dart @@ -36,7 +36,8 @@ mixin _$ContainerCredential { String get serial => throw _privateConstructorUsedError; EcKeyAlgorithm get ecKeyAlgorithm => throw _privateConstructorUsedError; Algorithms get hashAlgorithm => throw _privateConstructorUsedError; - ContainerCredentialState get state => throw _privateConstructorUsedError; + ContainerFinalizationState get finalizationState => + throw _privateConstructorUsedError; String? get passphrase => throw _privateConstructorUsedError; String? get publicServerKey => throw _privateConstructorUsedError; String? get publicClientKey => throw _privateConstructorUsedError; @@ -51,7 +52,7 @@ mixin _$ContainerCredential { String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String? publicServerKey, String? publicClientKey, @@ -65,7 +66,7 @@ mixin _$ContainerCredential { String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String publicServerKey, String publicClientKey, @@ -83,7 +84,7 @@ mixin _$ContainerCredential { String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String? publicServerKey, String? publicClientKey, @@ -97,7 +98,7 @@ mixin _$ContainerCredential { String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String publicServerKey, String publicClientKey, @@ -115,7 +116,7 @@ mixin _$ContainerCredential { String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String? publicServerKey, String? publicClientKey, @@ -129,7 +130,7 @@ mixin _$ContainerCredential { String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String publicServerKey, String publicClientKey, @@ -182,7 +183,7 @@ abstract class $ContainerCredentialCopyWith<$Res> { String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String publicServerKey, String publicClientKey, @@ -211,7 +212,7 @@ class _$ContainerCredentialCopyWithImpl<$Res, $Val extends ContainerCredential> Object? serial = null, Object? ecKeyAlgorithm = null, Object? hashAlgorithm = null, - Object? state = null, + Object? finalizationState = null, Object? passphrase = freezed, Object? publicServerKey = null, Object? publicClientKey = null, @@ -246,10 +247,10 @@ class _$ContainerCredentialCopyWithImpl<$Res, $Val extends ContainerCredential> ? _value.hashAlgorithm : hashAlgorithm // ignore: cast_nullable_to_non_nullable as Algorithms, - state: null == state - ? _value.state - : state // ignore: cast_nullable_to_non_nullable - as ContainerCredentialState, + finalizationState: null == finalizationState + ? _value.finalizationState + : finalizationState // ignore: cast_nullable_to_non_nullable + as ContainerFinalizationState, passphrase: freezed == passphrase ? _value.passphrase : passphrase // ignore: cast_nullable_to_non_nullable @@ -287,7 +288,7 @@ abstract class _$$ContainerCredentialUnfinalizedImplCopyWith<$Res> String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String? publicServerKey, String? publicClientKey, @@ -316,7 +317,7 @@ class __$$ContainerCredentialUnfinalizedImplCopyWithImpl<$Res> Object? serial = null, Object? ecKeyAlgorithm = null, Object? hashAlgorithm = null, - Object? state = null, + Object? finalizationState = null, Object? passphrase = freezed, Object? publicServerKey = freezed, Object? publicClientKey = freezed, @@ -351,10 +352,10 @@ class __$$ContainerCredentialUnfinalizedImplCopyWithImpl<$Res> ? _value.hashAlgorithm : hashAlgorithm // ignore: cast_nullable_to_non_nullable as Algorithms, - state: null == state - ? _value.state - : state // ignore: cast_nullable_to_non_nullable - as ContainerCredentialState, + finalizationState: null == finalizationState + ? _value.finalizationState + : finalizationState // ignore: cast_nullable_to_non_nullable + as ContainerFinalizationState, passphrase: freezed == passphrase ? _value.passphrase : passphrase // ignore: cast_nullable_to_non_nullable @@ -387,7 +388,7 @@ class _$ContainerCredentialUnfinalizedImpl required this.serial, required this.ecKeyAlgorithm, required this.hashAlgorithm, - this.state = ContainerCredentialState.uninitialized, + this.finalizationState = ContainerFinalizationState.uninitialized, this.passphrase, this.publicServerKey, this.publicClientKey, @@ -416,7 +417,7 @@ class _$ContainerCredentialUnfinalizedImpl final Algorithms hashAlgorithm; @override @JsonKey() - final ContainerCredentialState state; + final ContainerFinalizationState finalizationState; @override final String? passphrase; @override @@ -431,7 +432,7 @@ class _$ContainerCredentialUnfinalizedImpl @override String toString() { - return 'ContainerCredential.unfinalized(issuer: $issuer, nonce: $nonce, timestamp: $timestamp, finalizationUrl: $finalizationUrl, serial: $serial, ecKeyAlgorithm: $ecKeyAlgorithm, hashAlgorithm: $hashAlgorithm, state: $state, passphrase: $passphrase, publicServerKey: $publicServerKey, publicClientKey: $publicClientKey, privateClientKey: $privateClientKey)'; + return 'ContainerCredential.unfinalized(issuer: $issuer, nonce: $nonce, timestamp: $timestamp, finalizationUrl: $finalizationUrl, serial: $serial, ecKeyAlgorithm: $ecKeyAlgorithm, hashAlgorithm: $hashAlgorithm, finalizationState: $finalizationState, passphrase: $passphrase, publicServerKey: $publicServerKey, publicClientKey: $publicClientKey, privateClientKey: $privateClientKey)'; } @override @@ -450,7 +451,8 @@ class _$ContainerCredentialUnfinalizedImpl other.ecKeyAlgorithm == ecKeyAlgorithm) && (identical(other.hashAlgorithm, hashAlgorithm) || other.hashAlgorithm == hashAlgorithm) && - (identical(other.state, state) || other.state == state) && + (identical(other.finalizationState, finalizationState) || + other.finalizationState == finalizationState) && (identical(other.passphrase, passphrase) || other.passphrase == passphrase) && (identical(other.publicServerKey, publicServerKey) || @@ -472,7 +474,7 @@ class _$ContainerCredentialUnfinalizedImpl serial, ecKeyAlgorithm, hashAlgorithm, - state, + finalizationState, passphrase, publicServerKey, publicClientKey, @@ -499,7 +501,7 @@ class _$ContainerCredentialUnfinalizedImpl String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String? publicServerKey, String? publicClientKey, @@ -513,7 +515,7 @@ class _$ContainerCredentialUnfinalizedImpl String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String publicServerKey, String publicClientKey, @@ -528,7 +530,7 @@ class _$ContainerCredentialUnfinalizedImpl serial, ecKeyAlgorithm, hashAlgorithm, - state, + finalizationState, passphrase, publicServerKey, publicClientKey, @@ -546,7 +548,7 @@ class _$ContainerCredentialUnfinalizedImpl String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String? publicServerKey, String? publicClientKey, @@ -560,7 +562,7 @@ class _$ContainerCredentialUnfinalizedImpl String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String publicServerKey, String publicClientKey, @@ -575,7 +577,7 @@ class _$ContainerCredentialUnfinalizedImpl serial, ecKeyAlgorithm, hashAlgorithm, - state, + finalizationState, passphrase, publicServerKey, publicClientKey, @@ -593,7 +595,7 @@ class _$ContainerCredentialUnfinalizedImpl String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String? publicServerKey, String? publicClientKey, @@ -607,7 +609,7 @@ class _$ContainerCredentialUnfinalizedImpl String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String publicServerKey, String publicClientKey, @@ -624,7 +626,7 @@ class _$ContainerCredentialUnfinalizedImpl serial, ecKeyAlgorithm, hashAlgorithm, - state, + finalizationState, passphrase, publicServerKey, publicClientKey, @@ -681,7 +683,7 @@ abstract class ContainerCredentialUnfinalized extends ContainerCredential { required final String serial, required final EcKeyAlgorithm ecKeyAlgorithm, required final Algorithms hashAlgorithm, - final ContainerCredentialState state, + final ContainerFinalizationState finalizationState, final String? passphrase, final String? publicServerKey, final String? publicClientKey, @@ -706,7 +708,7 @@ abstract class ContainerCredentialUnfinalized extends ContainerCredential { @override Algorithms get hashAlgorithm; @override - ContainerCredentialState get state; + ContainerFinalizationState get finalizationState; @override String? get passphrase; @override @@ -742,7 +744,7 @@ abstract class _$$ContainerCredentialFinalizedImplCopyWith<$Res> String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String publicServerKey, String publicClientKey, @@ -771,7 +773,7 @@ class __$$ContainerCredentialFinalizedImplCopyWithImpl<$Res> Object? serial = null, Object? ecKeyAlgorithm = null, Object? hashAlgorithm = null, - Object? state = null, + Object? finalizationState = null, Object? passphrase = freezed, Object? publicServerKey = null, Object? publicClientKey = null, @@ -806,10 +808,10 @@ class __$$ContainerCredentialFinalizedImplCopyWithImpl<$Res> ? _value.hashAlgorithm : hashAlgorithm // ignore: cast_nullable_to_non_nullable as Algorithms, - state: null == state - ? _value.state - : state // ignore: cast_nullable_to_non_nullable - as ContainerCredentialState, + finalizationState: null == finalizationState + ? _value.finalizationState + : finalizationState // ignore: cast_nullable_to_non_nullable + as ContainerFinalizationState, passphrase: freezed == passphrase ? _value.passphrase : passphrase // ignore: cast_nullable_to_non_nullable @@ -841,7 +843,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { required this.serial, required this.ecKeyAlgorithm, required this.hashAlgorithm, - this.state = ContainerCredentialState.finalized, + this.finalizationState = ContainerFinalizationState.finalized, this.passphrase, required this.publicServerKey, required this.publicClientKey, @@ -870,7 +872,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { final Algorithms hashAlgorithm; @override @JsonKey() - final ContainerCredentialState state; + final ContainerFinalizationState finalizationState; @override final String? passphrase; @override @@ -885,7 +887,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { @override String toString() { - return 'ContainerCredential.finalized(issuer: $issuer, nonce: $nonce, timestamp: $timestamp, finalizationUrl: $finalizationUrl, serial: $serial, ecKeyAlgorithm: $ecKeyAlgorithm, hashAlgorithm: $hashAlgorithm, state: $state, passphrase: $passphrase, publicServerKey: $publicServerKey, publicClientKey: $publicClientKey, privateClientKey: $privateClientKey)'; + return 'ContainerCredential.finalized(issuer: $issuer, nonce: $nonce, timestamp: $timestamp, finalizationUrl: $finalizationUrl, serial: $serial, ecKeyAlgorithm: $ecKeyAlgorithm, hashAlgorithm: $hashAlgorithm, finalizationState: $finalizationState, passphrase: $passphrase, publicServerKey: $publicServerKey, publicClientKey: $publicClientKey, privateClientKey: $privateClientKey)'; } @override @@ -904,7 +906,8 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { other.ecKeyAlgorithm == ecKeyAlgorithm) && (identical(other.hashAlgorithm, hashAlgorithm) || other.hashAlgorithm == hashAlgorithm) && - (identical(other.state, state) || other.state == state) && + (identical(other.finalizationState, finalizationState) || + other.finalizationState == finalizationState) && (identical(other.passphrase, passphrase) || other.passphrase == passphrase) && (identical(other.publicServerKey, publicServerKey) || @@ -926,7 +929,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { serial, ecKeyAlgorithm, hashAlgorithm, - state, + finalizationState, passphrase, publicServerKey, publicClientKey, @@ -953,7 +956,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String? publicServerKey, String? publicClientKey, @@ -967,7 +970,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String publicServerKey, String publicClientKey, @@ -982,7 +985,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { serial, ecKeyAlgorithm, hashAlgorithm, - state, + finalizationState, passphrase, publicServerKey, publicClientKey, @@ -1000,7 +1003,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String? publicServerKey, String? publicClientKey, @@ -1014,7 +1017,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String publicServerKey, String publicClientKey, @@ -1029,7 +1032,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { serial, ecKeyAlgorithm, hashAlgorithm, - state, + finalizationState, passphrase, publicServerKey, publicClientKey, @@ -1047,7 +1050,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String? publicServerKey, String? publicClientKey, @@ -1061,7 +1064,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, - ContainerCredentialState state, + ContainerFinalizationState finalizationState, String? passphrase, String publicServerKey, String publicClientKey, @@ -1078,7 +1081,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { serial, ecKeyAlgorithm, hashAlgorithm, - state, + finalizationState, passphrase, publicServerKey, publicClientKey, @@ -1135,7 +1138,7 @@ abstract class ContainerCredentialFinalized extends ContainerCredential { required final String serial, required final EcKeyAlgorithm ecKeyAlgorithm, required final Algorithms hashAlgorithm, - final ContainerCredentialState state, + final ContainerFinalizationState finalizationState, final String? passphrase, required final String publicServerKey, required final String publicClientKey, @@ -1161,7 +1164,7 @@ abstract class ContainerCredentialFinalized extends ContainerCredential { @override Algorithms get hashAlgorithm; @override - ContainerCredentialState get state; + ContainerFinalizationState get finalizationState; @override String? get passphrase; @override diff --git a/lib/model/tokens/container_credentials.g.dart b/lib/model/tokens/container_credentials.g.dart index 1b5666295..7ce40697a 100644 --- a/lib/model/tokens/container_credentials.g.dart +++ b/lib/model/tokens/container_credentials.g.dart @@ -18,9 +18,10 @@ _$ContainerCredentialUnfinalizedImpl $enumDecode(_$EcKeyAlgorithmEnumMap, json['ecKeyAlgorithm']), hashAlgorithm: $enumDecode(_$AlgorithmsEnumMap, json['hashAlgorithm']), - state: $enumDecodeNullable( - _$ContainerCredentialStateEnumMap, json['state']) ?? - ContainerCredentialState.uninitialized, + finalizationState: $enumDecodeNullable( + _$ContainerFinalizationStateEnumMap, + json['finalizationState']) ?? + ContainerFinalizationState.uninitialized, passphrase: json['passphrase'] as String?, publicServerKey: json['publicServerKey'] as String?, publicClientKey: json['publicClientKey'] as String?, @@ -38,7 +39,8 @@ Map _$$ContainerCredentialUnfinalizedImplToJson( 'serial': instance.serial, 'ecKeyAlgorithm': _$EcKeyAlgorithmEnumMap[instance.ecKeyAlgorithm]!, 'hashAlgorithm': _$AlgorithmsEnumMap[instance.hashAlgorithm]!, - 'state': _$ContainerCredentialStateEnumMap[instance.state]!, + 'finalizationState': + _$ContainerFinalizationStateEnumMap[instance.finalizationState]!, 'passphrase': instance.passphrase, 'publicServerKey': instance.publicServerKey, 'publicClientKey': instance.publicClientKey, @@ -96,15 +98,21 @@ const _$AlgorithmsEnumMap = { Algorithms.SHA512: 'SHA512', }; -const _$ContainerCredentialStateEnumMap = { - ContainerCredentialState.uninitialized: 'uninitialized', - ContainerCredentialState.generatingKeyPair: 'generatingKeyPair', - ContainerCredentialState.generatingKeyPairFailed: 'generatingKeyPairFailed', - ContainerCredentialState.sendingPublicKey: 'sendingPublicKey', - ContainerCredentialState.sendingPublicKeyFailed: 'sendingPublicKeyFailed', - ContainerCredentialState.parsingResponse: 'parsingResponse', - ContainerCredentialState.parsingResponseFailed: 'parsingResponseFailed', - ContainerCredentialState.finalized: 'finalized', +const _$ContainerFinalizationStateEnumMap = { + ContainerFinalizationState.uninitialized: 'uninitialized', + ContainerFinalizationState.generatingKeyPair: 'generatingKeyPair', + ContainerFinalizationState.generatingKeyPairFailed: 'generatingKeyPairFailed', + ContainerFinalizationState.generatingKeyPairCompleted: + 'generatingKeyPairCompleted', + ContainerFinalizationState.sendingPublicKey: 'sendingPublicKey', + ContainerFinalizationState.sendingPublicKeyFailed: 'sendingPublicKeyFailed', + ContainerFinalizationState.sendingPublicKeyCompleted: + 'sendingPublicKeyCompleted', + ContainerFinalizationState.parsingResponse: 'parsingResponse', + ContainerFinalizationState.parsingResponseFailed: 'parsingResponseFailed', + ContainerFinalizationState.parsingResponseCompleted: + 'parsingResponseCompleted', + ContainerFinalizationState.finalized: 'finalized', }; _$ContainerCredentialFinalizedImpl _$$ContainerCredentialFinalizedImplFromJson( @@ -118,9 +126,9 @@ _$ContainerCredentialFinalizedImpl _$$ContainerCredentialFinalizedImplFromJson( ecKeyAlgorithm: $enumDecode(_$EcKeyAlgorithmEnumMap, json['ecKeyAlgorithm']), hashAlgorithm: $enumDecode(_$AlgorithmsEnumMap, json['hashAlgorithm']), - state: $enumDecodeNullable( - _$ContainerCredentialStateEnumMap, json['state']) ?? - ContainerCredentialState.finalized, + finalizationState: $enumDecodeNullable( + _$ContainerFinalizationStateEnumMap, json['finalizationState']) ?? + ContainerFinalizationState.finalized, passphrase: json['passphrase'] as String?, publicServerKey: json['publicServerKey'] as String, publicClientKey: json['publicClientKey'] as String, @@ -138,7 +146,8 @@ Map _$$ContainerCredentialFinalizedImplToJson( 'serial': instance.serial, 'ecKeyAlgorithm': _$EcKeyAlgorithmEnumMap[instance.ecKeyAlgorithm]!, 'hashAlgorithm': _$AlgorithmsEnumMap[instance.hashAlgorithm]!, - 'state': _$ContainerCredentialStateEnumMap[instance.state]!, + 'finalizationState': + _$ContainerFinalizationStateEnumMap[instance.finalizationState]!, 'passphrase': instance.passphrase, 'publicServerKey': instance.publicServerKey, 'publicClientKey': instance.publicClientKey, diff --git a/lib/processors/scheme_processors/container_credentials_processor.dart b/lib/processors/scheme_processors/container_credentials_processor.dart index 9a5c2d093..d9b6426ac 100644 --- a/lib/processors/scheme_processors/container_credentials_processor.dart +++ b/lib/processors/scheme_processors/container_credentials_processor.dart @@ -19,6 +19,7 @@ */ import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; +import '../../model/enums/algorithms.dart'; import '../../model/processor_result.dart'; import '../../utils/identifiers.dart'; import 'scheme_processor_interface.dart'; @@ -27,21 +28,72 @@ import '../../utils/logger.dart'; import '../../model/tokens/container_credentials.dart'; class ContainerCredentialsProcessor extends SchemeProcessor { - static const resultHandlerType = TypeMatcher(); + static const resultHandlerType = TypeMatcher(); static const scheme = 'pia'; - // static const hosts = {'container': _container}; + static const host = 'container'; @override Set get supportedSchemes => {scheme}; const ContainerCredentialsProcessor(); @override - Future>> processUri(Uri uri, {bool fromInit = false}) async { - if (!supportedSchemes.contains(uri.scheme)) { - Logger.error('Unsupported scheme', name: 'ContainerCredentialsProcessor'); - return []; + Future>?> processUri(Uri uri, {bool fromInit = false}) async { + if (!supportedSchemes.contains(uri.scheme)) return null; + if (uri.host != host) return null; + + // example: pia://container/SMPH00134123 + // ?issuer=privacyIDEA + // &nonce=887197025f5fa59b50f33c15196eb97ee651a5d1 + // &time=2024-08-21T07%3A43%3A07.086670%2B00%3A00 + // &url=http://127.0.0.1:5000/container/register/initialize + // &serial=SMPH00134123 + // &key_algorithm=secp384r1 + // &hash_algorithm=SHA256 + // &passphrase=Enter%20your%20passphrase + + final patameters = uri.queryParameters; + + final DateTime timeStamp; + final Uri finalizationUrl; + final EcKeyAlgorithm keyAlgorithm; + final Algorithms hashAlgorithm; + try { + validateMap(patameters, { + 'issuer': const TypeMatcher(), + 'nonce': const TypeMatcher(), + 'time': const TypeMatcher(), + 'url': const TypeMatcher(), + 'serial': const TypeMatcher(), + 'key_algorithm': const TypeMatcher(), + 'hash_algorithm': const TypeMatcher(), + 'passphrase': const TypeMatcher(), + }); + timeStamp = DateTime.parse(patameters['time']!); + finalizationUrl = Uri.parse(patameters['url']!); + keyAlgorithm = EcKeyAlgorithm.values.byCurveName(patameters['key_algorithm']!); + hashAlgorithm = Algorithms.values.byName(patameters['hash_algorithm']!); + } catch (e) { + Logger.warning('Error while processing URI ${uri.scheme}', error: e, name: 'ContainerCredentialsProcessor#processUri'); + return [ + ProcessorResult.failed( + '(Invalid URI) Missing or invalid parameters: $e', + resultHandlerType: resultHandlerType, + ) + ]; } - final credential = ContainerCredential.fromUriMap(uri.queryParameters); + + final uriMap = { + URI_ISSUER: patameters['issuer'], + URI_NONCE: patameters['nonce'], + URI_TIMESTAMP: timeStamp, + URI_FINALIZATION_URL: finalizationUrl, + URI_SERIAL: patameters['serial'], + URI_KEY_ALGORITHM: keyAlgorithm, + URI_HASH_ALGORITHM: hashAlgorithm, + URI_PASSPHRASE: patameters['passphrase'], + }; + + final credential = ContainerCredential.fromUriMap(uriMap); Logger.warning('Adding credential to container', name: 'ContainerCredentialsProcessor'); return [ ProcessorResult.success( diff --git a/lib/processors/scheme_processors/home_widget_processor.dart b/lib/processors/scheme_processors/home_widget_processor.dart index d84061fa9..7a6848220 100644 --- a/lib/processors/scheme_processors/home_widget_processor.dart +++ b/lib/processors/scheme_processors/home_widget_processor.dart @@ -62,7 +62,7 @@ class HomeWidgetProcessor implements SchemeProcessor { final widgetId = uri.queryParameters['widgetId']; if (widgetId == null) { return [ - ProcessorResult.failed( + const ProcessorResult.failed( 'Missing widgetId', resultHandlerType: null, ) @@ -85,7 +85,7 @@ class HomeWidgetProcessor implements SchemeProcessor { final widgetId = uri.queryParameters['widgetId']; if (widgetId == null) { return [ - ProcessorResult.failed( + const ProcessorResult.failed( 'Missing widgetId', resultHandlerType: null, ) @@ -108,7 +108,7 @@ class HomeWidgetProcessor implements SchemeProcessor { final widgetId = uri.queryParameters['widgetId']; if (widgetId == null) { return [ - ProcessorResult.failed( + const ProcessorResult.failed( 'Missing widgetId', resultHandlerType: null, ) diff --git a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart index a29ded304..01dbce942 100644 --- a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart +++ b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart @@ -47,7 +47,7 @@ class HomeWidgetNavigateProcessor implements NavigationSchemeProcessor { stackTrace: StackTrace.current, ); return [ - ProcessorResult.failed( + const ProcessorResult.failed( 'Cannot Navigate without context', resultHandlerType: resultHandlerType, ) @@ -128,7 +128,7 @@ class HomeWidgetNavigateProcessor implements NavigationSchemeProcessor { if (globalRef == null) { Logger.warning('Could not find globalRef', name: 'home_widget_processor.dart#_showLockedHomeWidgetProcessor'); return [ - ProcessorResult.failed( + const ProcessorResult.failed( 'Could not find globalRef', resultHandlerType: resultHandlerType, ) 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 aa8b10eb1..67ce5261d 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 @@ -87,10 +87,10 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { } if (_is2StepURI(uri)) { validateMap(uriMap, { - URI_SECRET: TypeMatcher(), - URI_ITERATIONS: TypeMatcher(), - URI_OUTPUT_LENGTH_IN_BYTES: TypeMatcher(), - URI_SALT_LENGTH: TypeMatcher(), + URI_SECRET: const TypeMatcher(), + URI_ITERATIONS: const TypeMatcher(), + URI_OUTPUT_LENGTH_IN_BYTES: const TypeMatcher(), + URI_SALT_LENGTH: const TypeMatcher(), }); final secret = uriMap[URI_SECRET] as Uint8List; // Calculate the whole secret. 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 685e0d670..6c68c5300 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 @@ -34,14 +34,9 @@ class PrivacyIDEAAuthenticatorQrProcessor extends TokenImportSchemeProcessor { @override Future>?> processUri(Uri uri, {bool fromInit = false}) async { - if (!supportedSchemes.contains(uri.scheme)) { - return null; - } - if (uri.host != host) { - Logger.warning('Unsupported scheme or host', name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); - return null; - } - Logger.info('Processing URI with scheme: ${uri.scheme}', name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); + if (!supportedSchemes.contains(uri.scheme)) return null; + if (uri.host != host) return null; + Logger.info('Processing URI with scheme: ${uri.scheme} and host: ${uri.host}', name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); try { final token = TokenEncryption.fromExportUri(uri); Logger.info('Processing URI ${uri.scheme} succeded', name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); diff --git a/lib/repo/secure_container_credentials_repository.dart b/lib/repo/secure_container_credentials_repository.dart index e912546ae..a4d311228 100644 --- a/lib/repo/secure_container_credentials_repository.dart +++ b/lib/repo/secure_container_credentials_repository.dart @@ -59,8 +59,8 @@ class SecureContainerCredentialsRepository extends ContainerCredentialsRepositor } @override - Future deleteCredential(String id) async { - await _delete(_keyOfSerial(id)); + Future deleteCredential(String serial) async { + await _delete(_keyOfSerial(serial)); return await loadCredentialsState(); } @@ -76,8 +76,8 @@ class SecureContainerCredentialsRepository extends ContainerCredentialsRepositor } @override - Future loadCredential(String id) async { - final credentialJsonString = await _read(_keyOfSerial(id)); + Future loadCredential(String serial) async { + final credentialJsonString = await _read(_keyOfSerial(serial)); if (credentialJsonString == null) return null; return ContainerCredential.fromJson(jsonDecode(credentialJsonString)); } diff --git a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart index 752900ee8..759b03d8d 100644 --- a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart +++ b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart @@ -49,12 +49,12 @@ class SecureTokenContainerRepository implements TokenContainerRepository { } Future _delete(String key) => _protect(() => _storage.delete(key: key)); - Future> _readAll() async { - Map? keys; - await _protect(() async => keys = await _storage.readAll()); - keys!.removeWhere((key, value) => !key.startsWith(prefix + containerId)); - return keys!; - } + // Future> _readAll() async { + // Map? keys; + // await _protect(() async => keys = await _storage.readAll()); + // keys!.removeWhere((key, value) => !key.startsWith(prefix + containerId)); + // return keys!; + // } @override Future saveContainerState(TokenContainer containerState) async { diff --git a/lib/utils/identifiers.dart b/lib/utils/identifiers.dart index 4dea2674f..3b5a36ec1 100644 --- a/lib/utils/identifiers.dart +++ b/lib/utils/identifiers.dart @@ -74,14 +74,19 @@ const String PUSH_REQUEST_ANSWERS = 'require_presence'; // 8. // Container registration: const String PUBLIC_SERVER_KEY = 'PUBLIC_SERVER_KEY'; +const String URI_PASSPHRASE = 'URI_PASSPHRASE'; const String GLOBAL_SECURE_REPO_PREFIX = 'app_v3_'; void validateMap(Map map, Map keys) { for (String key in keys.keys) { - final type = keys[key]!; - if (!type.isTypeOf(map[key])) { - throw ArgumentError('Map does not contain required key "$key" of type $type'); + final typeMatcher = keys[key]!; + final mapEntry = map[key]; + if (!typeMatcher.isTypeOf(map[key])) { + if (mapEntry == null) { + throw ArgumentError('Map does not contain required key "$key"'); + } + throw ArgumentError('Map does contain required key "$key" but ${mapEntry.runtimeType} is not a subtype of ${typeMatcher.type}'); } } } @@ -90,8 +95,10 @@ class TypeMatcher { const TypeMatcher(); bool isTypeOf(dynamic value) => value is T; + String get type => RegExp('(?<=<).+(?=>)').firstMatch(toString())!.group(0)!; + @override - String toString() => 'TypeMatcher<${T.runtimeType}>'; + String toString() => runtimeType.toString(); @override bool operator ==(Object other) => other is TypeMatcher; diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart index 51e97b73e..5461f8ecb 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart @@ -21,11 +21,13 @@ import 'dart:convert'; import 'package:basic_utils/basic_utils.dart'; import 'package:collection/collection.dart'; +import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; import 'package:privacyidea_authenticator/model/processor_result.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../../interfaces/repo/container_credentials_repository.dart'; @@ -37,19 +39,49 @@ import '../../../logger.dart'; part 'credential_notifier.g.dart'; +final containerCredentialsProvider = containerCredentialsNotifierProviderOf( + repo: SecureContainerCredentialsRepository(), + ioClient: const PrivacyideaIOClient(), +); + @Riverpod(keepAlive: true) -class CredentialsNotifier extends _$CredentialsNotifier with ResultHandler { +class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with ResultHandler { final _stateMutex = Mutex(); final _repoMutex = Mutex(); - late PrivacyideaIOClient _ioClient; + + ContainerCredentialsNotifier({ + ContainerCredentialsRepository? repoOverride, + PrivacyideaIOClient? ioClientOverride, + NotifierProviderRef? refOverride, + }) : _repoOverride = repoOverride, + _ioClientOverride = ioClientOverride; + + @override + ContainerCredentialsRepository get repo => _repo; late ContainerCredentialsRepository _repo; + final ContainerCredentialsRepository? _repoOverride; + + @override + PrivacyideaIOClient get ioClient => _ioClient; + late PrivacyideaIOClient _ioClient; + final PrivacyideaIOClient? _ioClientOverride; @override - Future build() async { - _repo = SecureContainerCredentialsRepository(); - _ioClient = const PrivacyideaIOClient(); + Future build({ + required ContainerCredentialsRepository repo, + required PrivacyideaIOClient ioClient, + }) async { + await _stateMutex.acquire(); + _repo = _repoOverride ?? repo; + _ioClient = _ioClientOverride ?? ioClient; + // _ref = _refOverride ?? ref; Logger.warning('Building credentialsProvider', name: 'CredentialsNotifier'); - return _repo.loadCredentialsState(); + final initState = await _repo.loadCredentialsState(); + for (var credential in initState.credentials.whereType()) { + finalize(credential); + } + _stateMutex.release(); + return initState; } @override @@ -61,6 +93,22 @@ class CredentialsNotifier extends _$CredentialsNotifier with ResultHandler { return super.update(cb, onError: onError); } + Future updateCredential(ContainerCredential credential, T Function(ContainerCredential) updater) async { + await _stateMutex.acquire(); + final oldState = await future; + final currentCredential = oldState.currentOf(credential); + if (currentCredential == null) { + Logger.info('Failed to update credential. It was probably removed in the meantime.', name: 'CredentialsNotifier#updateCredential'); + _stateMutex.release(); + return null; + } + final updated = updater(currentCredential); + final newState = await _saveCredentialToRepo(updated); + state = AsyncValue.data(newState); + _stateMutex.release(); + return updated; + } + Future addCredential(ContainerCredential credential) async { await _stateMutex.acquire(); final newState = await _saveCredentialToRepo(credential); @@ -98,27 +146,69 @@ class CredentialsNotifier extends _$CredentialsNotifier with ResultHandler { return await _repoMutex.protect(() async => await _repo.saveCredentialsState(credentialsState)); } - Future handleCredentialResults(List> credentialResults) async { - final containerCredentials = credentialResults.getData(); + @override + Future handleProcessorResult(ProcessorResult result, Map args) { + // TODO: implement handleResult + throw UnimplementedError(); + } + + @override + Future handleProcessorResults(List results, Map args) async { + Logger.info('Handling processor results', name: 'CredentialsNotifier#handleProcessorResults'); + final containerCredentials = results.getData().whereType().toList(); if (containerCredentials.isEmpty) { - return future; + return null; } final currentState = await future; final stateCredentials = currentState.credentials; final stateCredentialsSerials = stateCredentials.map((e) => e.serial); final newCredentials = containerCredentials.where((element) => !stateCredentialsSerials.contains(element.serial)).toList(); - return addCredentials(newCredentials); + await addCredentials(newCredentials); + for (var credential in containerCredentials) { + if (stateCredentialsSerials is! ContainerCredentialUnfinalized) continue; + await finalize(credential); + } + return null; } - Future finalize(ContainerCredential credential) async { - await _stateMutex.acquire(); + final Mutex _finalizationMutex = Mutex(); + Future finalize(ContainerCredential containerCredential) async { + ContainerCredential? credential = containerCredential; + await _finalizationMutex.acquire(); if (credential is! ContainerCredentialUnfinalized) { + _finalizationMutex.release(); throw ArgumentError('Credential must not be finalized'); } + Logger.info('Finalizing container credential ${credential.serial}', name: 'CredentialsNotifier#finalize'); + credential = await _generateKeyPair(credential); + final Response response; + (credential, response) = await _sendPublicKey(credential); + final ECPublicKey publicServerKey; + (credential, publicServerKey) = await _parseResponse(credential, response); + await updateCredential(credential, (c) => c.finalize(publicServerKey: publicServerKey)!); + _finalizationMutex.release(); + } + /// Finalization substep 1: Generate key pair + Future _generateKeyPair(ContainerCredential containerCredential) async { + // generatingKeyPair, + // generatingKeyPairFailed, + // generatingKeyPairCompleted, + ContainerCredential? credential = containerCredential; + credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.generatingKeyPair)); + if (credential == null) throw StateError('Credential was removed'); final keyPair = CryptoUtils.generateEcKeyPair(curve: credential.ecKeyAlgorithm.curveName); + credential = await updateCredential(credential, (c) => c.withClientKeyPair(keyPair) as ContainerCredentialUnfinalized); + if (credential == null) throw StateError('Credential was removed'); + return credential; + } - credential = credential.withClientKeyPair(keyPair) as ContainerCredentialUnfinalized; + /// Finalization substep 2: Send public key + Future<(ContainerCredential, Response)> _sendPublicKey(ContainerCredential containerCredential) async { + // sendingPublicKey, + // sendingPublicKeyFailed, + // sendingPublicKeyCompleted, + ContainerCredential? credential = containerCredential; final ecPrivateClientKey = credential.ecPrivateClientKey!; //POST /container/register/finalize // Request: { @@ -128,7 +218,6 @@ class CredentialsNotifier extends _$CredentialsNotifier with ResultHandler { // } final passphrase = credential.passphrase != null ? EnterPassphraseDialog.show(await globalContext) : null; - final message = '${credential.nonce}' '|${credential.timestamp}' '|${credential.finalizationUrl}' @@ -136,37 +225,49 @@ class CredentialsNotifier extends _$CredentialsNotifier with ResultHandler { '${passphrase != null ? '|$passphrase' : ''}'; final signature = const EccUtils().trySignWithPrivateKey(ecPrivateClientKey, message); - final body = { 'container_serial': credential.serial, 'public_client_key': credential.publicClientKey, 'signature': signature, }; - - final response = await _ioClient.doPost(url: credential.finalizationUrl, body: body); - final Map responseJson; - final ContainerCredentialFinalized finalizedCredential; + final Response response; + credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKey)); + if (credential == null) throw StateError('Credential was removed'); try { - responseJson = jsonDecode(response.body); - validateMap(responseJson, {PUBLIC_SERVER_KEY: TypeMatcher()}); - final publicServerKey = const EccUtils().deserializeECPublicKey(responseJson[PUBLIC_SERVER_KEY]); - finalizedCredential = credential.finalize(publicServerKey: publicServerKey)!; + response = await _ioClient.doPost(url: credential.finalizationUrl, body: body); } catch (e) { - Logger.error('Failed to decode response body', error: e, name: 'CredentialsNotifier#finalize'); - return null; + ref.read(statusMessageProvider.notifier).state = ('Failed to finalize container', e.toString()); + await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyFailed)); + rethrow; } - return await addCredential(finalizedCredential); + credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyCompleted)); + if (credential == null) throw StateError('Credential was removed'); + return (credential, response); } - @override - Future handleProcessorResult(ProcessorResult result, Map args) { - // TODO: implement handleResult - throw UnimplementedError(); - } + /// Finalization substep 3: Parse response + Future<(ContainerCredential, ECPublicKey)> _parseResponse(ContainerCredential containerCredential, Response response) async { + // parsingResponse, + // parsingResponseFailed, + // parsingResponseCompleted, - @override - Future handleProcessorResults(List results, Map args) { - // TODO: implement handleResults - throw UnimplementedError(); + ContainerCredential? credential = containerCredential; + ECPublicKey publicServerKey; + String responseBody = response.body; + Map responseJson; + credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponse)); + if (credential == null) throw StateError('Credential was removed'); + try { + responseJson = jsonDecode(responseBody); + validateMap(responseJson, {PUBLIC_SERVER_KEY: const TypeMatcher()}); + publicServerKey = const EccUtils().deserializeECPublicKey(responseJson[PUBLIC_SERVER_KEY]); + } catch (e) { + ref.read(statusMessageProvider.notifier).state = ('Failed to decode response body', e.toString()); + await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseFailed)); + rethrow; + } + credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseCompleted)); + if (credential == null) throw StateError('Credential was removed'); + return (credential, publicServerKey); } } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart index 97911d4b9..cdd06aefc 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart @@ -6,22 +6,198 @@ part of 'credential_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$credentialsNotifierHash() => - r'f2f6e55487b59136bb7ce6aa3e05df96d3d8fd82'; - -/// See also [CredentialsNotifier]. -@ProviderFor(CredentialsNotifier) -final credentialsNotifierProvider = - AsyncNotifierProvider.internal( - CredentialsNotifier.new, - name: r'credentialsNotifierProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$credentialsNotifierHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$CredentialsNotifier = AsyncNotifier; +String _$containerCredentialsNotifierHash() => + r'31ed5a5d583df5880f443784cbf454d97d0e6519'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$ContainerCredentialsNotifier + extends BuildlessAsyncNotifier { + late final ContainerCredentialsRepository repo; + late final PrivacyideaIOClient ioClient; + + FutureOr build({ + required ContainerCredentialsRepository repo, + required PrivacyideaIOClient ioClient, + }); +} + +/// See also [ContainerCredentialsNotifier]. +@ProviderFor(ContainerCredentialsNotifier) +const containerCredentialsNotifierProviderOf = + ContainerCredentialsNotifierFamily(); + +/// See also [ContainerCredentialsNotifier]. +class ContainerCredentialsNotifierFamily + extends Family> { + /// See also [ContainerCredentialsNotifier]. + const ContainerCredentialsNotifierFamily(); + + /// See also [ContainerCredentialsNotifier]. + ContainerCredentialsNotifierProvider call({ + required ContainerCredentialsRepository repo, + required PrivacyideaIOClient ioClient, + }) { + return ContainerCredentialsNotifierProvider( + repo: repo, + ioClient: ioClient, + ); + } + + @override + ContainerCredentialsNotifierProvider getProviderOverride( + covariant ContainerCredentialsNotifierProvider provider, + ) { + return call( + repo: provider.repo, + ioClient: provider.ioClient, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'containerCredentialsNotifierProviderOf'; +} + +/// See also [ContainerCredentialsNotifier]. +class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< + ContainerCredentialsNotifier, CredentialsState> { + /// See also [ContainerCredentialsNotifier]. + ContainerCredentialsNotifierProvider({ + required ContainerCredentialsRepository repo, + required PrivacyideaIOClient ioClient, + }) : this._internal( + () => ContainerCredentialsNotifier() + ..repo = repo + ..ioClient = ioClient, + from: containerCredentialsNotifierProviderOf, + name: r'containerCredentialsNotifierProviderOf', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$containerCredentialsNotifierHash, + dependencies: ContainerCredentialsNotifierFamily._dependencies, + allTransitiveDependencies: + ContainerCredentialsNotifierFamily._allTransitiveDependencies, + repo: repo, + ioClient: ioClient, + ); + + ContainerCredentialsNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.repo, + required this.ioClient, + }) : super.internal(); + + final ContainerCredentialsRepository repo; + final PrivacyideaIOClient ioClient; + + @override + FutureOr runNotifierBuild( + covariant ContainerCredentialsNotifier notifier, + ) { + return notifier.build( + repo: repo, + ioClient: ioClient, + ); + } + + @override + Override overrideWith(ContainerCredentialsNotifier Function() create) { + return ProviderOverride( + origin: this, + override: ContainerCredentialsNotifierProvider._internal( + () => create() + ..repo = repo + ..ioClient = ioClient, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + repo: repo, + ioClient: ioClient, + ), + ); + } + + @override + AsyncNotifierProviderElement + createElement() { + return _ContainerCredentialsNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ContainerCredentialsNotifierProvider && + other.repo == repo && + other.ioClient == ioClient; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, repo.hashCode); + hash = _SystemHash.combine(hash, ioClient.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin ContainerCredentialsNotifierRef + on AsyncNotifierProviderRef { + /// The parameter `repo` of this provider. + ContainerCredentialsRepository get repo; + + /// The parameter `ioClient` of this provider. + PrivacyideaIOClient get ioClient; +} + +class _ContainerCredentialsNotifierProviderElement + extends AsyncNotifierProviderElement with ContainerCredentialsNotifierRef { + _ContainerCredentialsNotifierProviderElement(super.provider); + + @override + ContainerCredentialsRepository get repo => + (origin as ContainerCredentialsNotifierProvider).repo; + @override + PrivacyideaIOClient get ioClient => + (origin as ContainerCredentialsNotifierProvider).ioClient; +} // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index f66ca582e..adbe275ad 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -40,7 +40,7 @@ class ContainerListensToTokenState extends TokenStateListener { static Future _onNewState(TokenState? previousState, TokenState nextState, WidgetRef ref) async { Logger.warning('New token state', name: 'TokenContainerTokenStateListener'); final maybePiTokenTemplates = nextState.lastlyUpdatedTokens.maybePiTokens.toTemplates(); - final credentials = (await ref.read(credentialsNotifierProvider.future)).credentials; + final credentials = (await ref.read(containerCredentialsProvider.future)).credentials; Logger.warning('Readed: $credentials', name: 'TokenContainerTokenStateListener'); // if (maybePiTokenTemplates.isEmpty) return; for (var credential in credentials) { diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 967230394..31c747bfa 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -208,6 +208,7 @@ BigInt bytesToBigInt(Uint8List bytes) => byteDataToBigInt(ByteData.sublistView(b Future scanQrCode(List resultHandlerList, Object? qrCode) async { Uri uri; try { + if (qrCode == null) return; uri = switch (qrCode.runtimeType) { const (String) => Uri.parse(qrCode as String), const (Uri) => qrCode as Uri, 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 da1eef86b..78478b939 100644 --- a/lib/views/main_view/main_view_widgets/connectivity_listener.dart +++ b/lib/views/main_view/main_view_widgets/connectivity_listener.dart @@ -38,6 +38,7 @@ class ConnectivityListener extends ConsumerWidget { ref.read(tokenProvider.notifier).initState.then((newState) { if (newState.hasPushTokens) { Logger.info("Connectivity changed: $connectivity"); + if (!context.mounted) return; ref.read(statusMessageProvider.notifier).state = (AppLocalizations.of(context)!.noNetworkConnection, null); } }); diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart index c863381e5..f4c79be0a 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart @@ -52,7 +52,7 @@ class QrScannerButton extends ConsumerWidget { Navigator.pushNamed(globalNavigatorKey.currentContext!, QRScannerView.routeName).then((qrCode) { final resultHandlers = [ ref.read(tokenProvider.notifier), - ref.read(credentialsNotifierProvider.notifier), + ref.read(containerCredentialsProvider.notifier), ]; scanQrCode(resultHandlers, qrCode); }); 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 9b1955378..f194659dd 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 @@ -21,6 +21,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; import '../../../model/mixins/sortable_mixin.dart'; import '../../../model/riverpod_states/settings_state.dart'; @@ -104,6 +105,11 @@ class _MainViewTokensListState extends ConsumerState { } if ((showSortables.isEmpty)) return const NoTokenScreen(); + + final credentials = ref.read(containerCredentialsProvider).whenOrNull( + data: (data) => data.credentials, + ); + return Stack( children: [ Column( @@ -147,11 +153,7 @@ class _MainViewTokensListState extends ConsumerState { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Column( - children: [ - ...MainViewTokensList.buildSortableWidgets(showSortables, draggingSortable), - ], - ), + ...MainViewTokensList.buildSortableWidgets(showSortables, draggingSortable), (draggingSortable != null) ? DragTargetDivider( dependingFolder: null, @@ -160,7 +162,13 @@ class _MainViewTokensListState extends ConsumerState { isLastDivider: true, bottomPaddingIfLast: 80, ) - : const SizedBox(height: 80) + : const SizedBox(height: 80), + ...(credentials!.map((credential) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text('${credential.serial} | ${credential.issuer} | ${credential.finalizationState.name}'), + ); + }).toList()) ], ), ), diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index 1259ca7e9..ae1982187 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -86,11 +86,11 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { @override Widget build(BuildContext context) { - final credentials = ref.watch(credentialsNotifierProvider).value?.credentials ?? []; + final credentials = ref.watch(containerCredentialsProvider).value?.credentials ?? []; Logger.debug('Credentials: $credentials', name: 'AppWrapper#build'); return SingleTouchRecognizer( child: StateObserver( - stateNotifierProviderListeners: [], + stateNotifierProviderListeners: const [], buildlessProviderListener: [ HomeWidgetTokenStateListener(provider: tokenProvider), ContainerListensToTokenState(provider: tokenProvider, ref: ref), diff --git a/lib/widgets/default_refresh_indicator.dart b/lib/widgets/default_refresh_indicator.dart index b2c59d09d..18ab16dce 100644 --- a/lib/widgets/default_refresh_indicator.dart +++ b/lib/widgets/default_refresh_indicator.dart @@ -32,7 +32,7 @@ class _DefaultRefreshIndicatorState extends ConsumerState _MyAppState(); + State createState() => _MyAppState(); } class _MyAppState extends State { - String _platformVersion = 'Unknown'; + String platformVersion = 'Unknown'; @override void initState() { @@ -31,7 +33,7 @@ class _MyAppState extends State { ), body: Center( child: Text( - 'Running on: $_platformVersion\n', + 'Running on: $platformVersion\n', overflow: TextOverflow.fade, softWrap: false, ), diff --git a/local_plugins/pi-authenticator-legacy/lib/identifiers.dart b/local_plugins/pi-authenticator-legacy/lib/identifiers.dart index d6823d0c0..bc4cb952c 100644 --- a/local_plugins/pi-authenticator-legacy/lib/identifiers.dart +++ b/local_plugins/pi-authenticator-legacy/lib/identifiers.dart @@ -1,3 +1,5 @@ +// ignore_for_file: constant_identifier_names + /* privacyIDEA Authenticator @@ -19,4 +21,4 @@ */ // Error codes for failed method calls -const String LEGACY_SIGNING_ERROR = "LEGACY_SIGNING_ERROR"; \ No newline at end of file +const String LEGACY_SIGNING_ERROR = "LEGACY_SIGNING_ERROR"; diff --git a/local_plugins/pi-authenticator-legacy/lib/pi_authenticator_legacy.dart b/local_plugins/pi-authenticator-legacy/lib/pi_authenticator_legacy.dart index f3e9f7779..fe415bcb1 100644 --- a/local_plugins/pi-authenticator-legacy/lib/pi_authenticator_legacy.dart +++ b/local_plugins/pi-authenticator-legacy/lib/pi_authenticator_legacy.dart @@ -1,3 +1,5 @@ +// ignore_for_file: constant_identifier_names + /* privacyIDEA Authenticator @@ -35,7 +37,7 @@ const String PARAMETER_SIGNATURE = "signature"; class LegacyUtils { const LegacyUtils(); - static const MethodChannel _channel = const MethodChannel(METHOD_CHANNEL_ID); + static const MethodChannel _channel = MethodChannel(METHOD_CHANNEL_ID); Future sign(String serial, String message) async => await (_channel.invokeMethod(METHOD_SIGN, { PARAMETER_SERIAL: serial, diff --git a/test/unit_test/model/processor_result_test.dart b/test/unit_test/model/processor_result_test.dart index b510c8f99..81aff422d 100644 --- a/test/unit_test/model/processor_result_test.dart +++ b/test/unit_test/model/processor_result_test.dart @@ -20,12 +20,12 @@ void _testProcessorResult() { }); group('factories', () { test('success', () { - final result = ProcessorResult.success('data'); + const result = ProcessorResult.success('data'); expect(result, isA()); expect((result as ProcessorResultSuccess).resultData, 'data'); }); test('error', () { - final result = ProcessorResult.failed('error'); + const result = ProcessorResult.failed('error'); expect(result, isA()); expect((result as ProcessorResultFailed).message, 'error'); }); diff --git a/test/unit_test/processors/scheme_processors/token_import_scheme_processors/privacyidea_authenticator_qr_processor_test.dart b/test/unit_test/processors/scheme_processors/token_import_scheme_processors/privacyidea_authenticator_qr_processor_test.dart index 1a11eafce..d112e1bc8 100644 --- a/test/unit_test/processors/scheme_processors/token_import_scheme_processors/privacyidea_authenticator_qr_processor_test.dart +++ b/test/unit_test/processors/scheme_processors/token_import_scheme_processors/privacyidea_authenticator_qr_processor_test.dart @@ -33,8 +33,8 @@ void _testPrivacyideaAuthenticatorQrProcessor() { final token = tokensList[i]; final uri = Uri.parse(uriStrings[i]); final result = await processor.processUri(uri); - expect(result.length, 1); - expect(result[0].isSuccess, true); + expect(result?.length, 1); + expect(result![0].isSuccess, true); expect(result[0].asSuccess, isNotNull); expect(result[0].asSuccess!.resultData, token); } From ce7a3792cce5894f1e71c9b11368ab95a546f010 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Wed, 28 Aug 2024 09:14:00 +0200 Subject: [PATCH 031/285] container finalization --- lib/mains/main_netknights.dart | 2 + lib/model/tokens/container_credentials.dart | 27 ++- .../tokens/container_credentials.freezed.dart | 10 - lib/repo/secure_token_repository.dart | 8 +- lib/utils/privacyidea_io_client.dart | 27 ++- lib/utils/push_provider.dart | 2 +- .../credential_notifier.dart | 196 +++++++++++++----- .../credential_notifier.g.dart | 29 ++- .../push_request_provider.dart | 2 +- .../push_request_provider.g.dart | 2 +- lib/views/container_view/container_view.dart | 154 ++++++++++++++ .../main_view_tokens_list.dart | 12 -- .../edit_day_password_token_action.dart | 4 +- .../default_delete_action.dart | 4 +- .../default_edit_action.dart | 4 +- .../default_lock_action.dart | 4 +- .../actions/edit_hotp_token_action.dart | 4 +- .../hotp_token_widgets/hotp_token_widget.dart | 14 +- .../actions/edit_push_token_action.dart | 4 +- ...oken_action.dart => slideable_action.dart} | 4 +- .../token_widgets/token_widget.dart | 1 + .../token_widgets/token_widget_base.dart | 23 +- .../actions/edit_totp_token_action.dart | 4 +- .../settings_group_container.dart | 48 +++++ lib/views/settings_view/settings_view.dart | 3 + lib/views/view_interface.dart | 1 + .../pi_slideable.dart} | 19 +- 27 files changed, 473 insertions(+), 139 deletions(-) create mode 100644 lib/views/container_view/container_view.dart rename lib/views/main_view/main_view_widgets/token_widgets/{token_action.dart => slideable_action.dart} (90%) create mode 100644 lib/views/settings_view/settings_groups/settings_group_container.dart rename lib/{views/main_view/main_view_widgets/token_widgets/token_widget_slideable.dart => widgets/pi_slideable.dart} (78%) diff --git a/lib/mains/main_netknights.dart b/lib/mains/main_netknights.dart index c6d7c69ac..95b60495b 100644 --- a/lib/mains/main_netknights.dart +++ b/lib/mains/main_netknights.dart @@ -35,6 +35,7 @@ import '../utils/logger.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart'; import '../views/add_token_manually_view/add_token_manually_view.dart'; +import '../views/container_view/container_view.dart'; import '../views/feedback_view/feedback_view.dart'; import '../views/import_tokens_view/import_tokens_view.dart'; import '../views/license_view/license_view.dart'; @@ -117,6 +118,7 @@ class PrivacyIDEAAuthenticator extends ConsumerWidget { SettingsView.routeName: (context) => const SettingsView(), SplashScreen.routeName: (context) => SplashScreen(customization: _customization, appConstraints: constraints), QRScannerView.routeName: (context) => const QRScannerView(), + ContainerView.routeName: (context) => const ContainerView(), }, ); }); diff --git a/lib/model/tokens/container_credentials.dart b/lib/model/tokens/container_credentials.dart index 66e6c692d..aecef6ead 100644 --- a/lib/model/tokens/container_credentials.dart +++ b/lib/model/tokens/container_credentials.dart @@ -41,7 +41,7 @@ part 'container_credentials.g.dart'; // &hash_algorithm=SHA256 // &passphrase=Enter%20your%20passphrase -@freezed +@Freezed(toStringOverride: false) class ContainerCredential with _$ContainerCredential { static const eccUtils = EccUtils(); const ContainerCredential._(); @@ -139,12 +139,19 @@ class ContainerCredential with _$ContainerCredential { factory ContainerCredential.fromJson(Map json) => _$ContainerCredentialFromJson(json); - // // Sign with private client key - // String signMessage(String message) { - // final signer = Signer(hashAlgorithm.name); - // signer.init(true, PrivateKey(ecPrivateClientKey!.d, ecPrivateClientKey!.parameters)); - // return signer.signMessage(message); - // }; + @override + String toString() => 'ContainerCredential(' + 'issuer: $issuer, ' + 'nonce: $nonce, ' + 'timestamp: $timestamp, ' + 'finalizationUrl: $finalizationUrl, ' + 'serial: $serial, ' + 'ecKeyAlgorithm: $ecKeyAlgorithm, ' + 'hashAlgorithm: $hashAlgorithm, ' + 'finalizationState: $finalizationState, ' + 'passphrase: $passphrase, ' + 'publicServerKey: $publicServerKey, ' + 'publicClientKey: $publicClientKey)'; } enum ContainerFinalizationState { @@ -299,10 +306,10 @@ extension EcKeyAlgorithmX on EcKeyAlgorithm { EcKeyAlgorithm.secp521r1 => 'secp521r1', }; } +//be99ff65b1c38ae8a7d6caf8799a0cce3749fe0e|2024-08-27 14:30:58.371312Z|http://192.168.0.230:5000/container/register/finalize|SMPH0000D49C +//be99ff65b1c38ae8a7d6caf8799a0cce3749fe0e|2024-08-27T14:30:58.371312+00:00|http://192.168.0.230:5000/container/register/finalize|SMPH0000D49C class EccUtils { - final String algorithmName = 'EC'; - const EccUtils(); String serializeECPublicKey(ECPublicKey publicKey) => CryptoUtils.encodeEcPublicKeyToPem(publicKey); @@ -311,7 +318,7 @@ class EccUtils { ECPrivateKey deserializeECPrivateKey(String ecPrivateKey) => CryptoUtils.ecPrivateKeyFromPem(ecPrivateKey); String trySignWithPrivateKey(ECPrivateKey privateKey, String message) { - final ecSignature = CryptoUtils.ecSign(privateKey, Uint8List.fromList(message.codeUnits)); + final ecSignature = CryptoUtils.ecSign(privateKey, Uint8List.fromList(message.codeUnits), algorithmName: 'SHA-256/ECDSA'); String signatureBase64 = CryptoUtils.ecSignatureToBase64(ecSignature); return signatureBase64; } diff --git a/lib/model/tokens/container_credentials.freezed.dart b/lib/model/tokens/container_credentials.freezed.dart index 7cb4fe051..467b42dc9 100644 --- a/lib/model/tokens/container_credentials.freezed.dart +++ b/lib/model/tokens/container_credentials.freezed.dart @@ -430,11 +430,6 @@ class _$ContainerCredentialUnfinalizedImpl @JsonKey(name: 'runtimeType') final String $type; - @override - String toString() { - return 'ContainerCredential.unfinalized(issuer: $issuer, nonce: $nonce, timestamp: $timestamp, finalizationUrl: $finalizationUrl, serial: $serial, ecKeyAlgorithm: $ecKeyAlgorithm, hashAlgorithm: $hashAlgorithm, finalizationState: $finalizationState, passphrase: $passphrase, publicServerKey: $publicServerKey, publicClientKey: $publicClientKey, privateClientKey: $privateClientKey)'; - } - @override bool operator ==(Object other) { return identical(this, other) || @@ -885,11 +880,6 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { @JsonKey(name: 'runtimeType') final String $type; - @override - String toString() { - return 'ContainerCredential.finalized(issuer: $issuer, nonce: $nonce, timestamp: $timestamp, finalizationUrl: $finalizationUrl, serial: $serial, ecKeyAlgorithm: $ecKeyAlgorithm, hashAlgorithm: $hashAlgorithm, finalizationState: $finalizationState, passphrase: $passphrase, publicServerKey: $publicServerKey, publicClientKey: $publicClientKey, privateClientKey: $privateClientKey)'; - } - @override bool operator ==(Object other) { return identical(this, other) || diff --git a/lib/repo/secure_token_repository.dart b/lib/repo/secure_token_repository.dart index 1ff0aae19..8f8be505e 100644 --- a/lib/repo/secure_token_repository.dart +++ b/lib/repo/secure_token_repository.dart @@ -135,7 +135,7 @@ class SecureTokenRepository implements TokenRepository { } } if (failedTokens.isNotEmpty) { - Logger.warning( + Logger.error( 'Could not save all tokens (${tokens.length - failedTokens.length}/${tokens.length}) to secure storage', name: 'secure_token_repository.dart#saveOrReplaceTokens', stackTrace: StackTrace.current, @@ -150,9 +150,9 @@ class SecureTokenRepository implements TokenRepository { Future saveOrReplaceToken(Token token) => _protect(() => _saveOrReplaceToken(token)); Future _saveOrReplaceToken(Token token) async { try { - await _storage.write(key: _TOKEN_PREFIX + token.id, value: jsonEncode(token)); - } catch (e, s) { - Logger.error('Could not save token to secure storage', name: 'secure_token_repository.dart#saveOrReplaceToken', error: e, stackTrace: s); + await _storage.write(key: _TOKEN_PREFIX + token.id, value: jsonEncode(token.toJson())); + } catch (e) { + Logger.warning('Could not save token to secure storage', error: e, name: 'secure_token_repository.dart#saveOrReplaceToken', verbose: true); return false; } return true; diff --git a/lib/utils/privacyidea_io_client.dart b/lib/utils/privacyidea_io_client.dart index 6a64699b2..24f3bb374 100644 --- a/lib/utils/privacyidea_io_client.dart +++ b/lib/utils/privacyidea_io_client.dart @@ -19,6 +19,7 @@ */ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -116,16 +117,16 @@ class PrivacyideaIOClient { } on HandshakeException catch (e, _) { Logger.warning('Handshake failed. sslVerify: $sslVerify', name: 'utils.dart#doPost'); showMessage(message: 'Handshake failed, please check the server certificate and try again.'); - response = Response('${e.runtimeType}', 525); + response = Response('Handshake failed', 525); } on TimeoutException catch (e, _) { Logger.info('Post request timed out', name: 'utils.dart#doPost'); - response = Response('${e.runtimeType}', 408); + response = Response('Request timed out', 408); } on SocketException catch (e, _) { Logger.info('Post request failed', name: 'utils.dart#doPost'); - response = Response('${e.runtimeType}', 404); + response = Response('Failed to send request', 404); } catch (e, _) { Logger.warning('Something unexpected happened', name: 'utils.dart#doPost'); - response = Response('${e.runtimeType}', 404); + response = Response('Failed to send request', 404); } if (response.statusCode != 200) { @@ -196,3 +197,21 @@ class PrivacyideaIOClient { return response; } } + +extension PiServerMessage on Response { + String? get piServerMessage { + try { + return jsonDecode(body)['result']['error']['message'].toString(); + } catch (e, _) { + return null; + } + } + + String? get piStatusCode { + try { + return jsonDecode(body)['result']['error']['code'].toString(); + } catch (e, _) { + return null; + } + } +} diff --git a/lib/utils/push_provider.dart b/lib/utils/push_provider.dart index b9c53467e..6942fd15d 100644 --- a/lib/utils/push_provider.dart +++ b/lib/utils/push_provider.dart @@ -353,7 +353,7 @@ class PushProvider { response = instance != null ? await instance!._ioClient.doGet(url: token.url!, parameters: parameters, sslVerify: token.sslVerify) : await const PrivacyideaIOClient().doGet(url: token.url!, parameters: parameters, sslVerify: token.sslVerify); - } catch (e) { + } catch (_) { if (isManually) { globalRef?.read(statusMessageProvider.notifier).state = ( AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorWhenPullingChallenges(token.serial), diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart index 5461f8ecb..b1a33f5db 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart @@ -17,6 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import 'dart:async'; import 'dart:convert'; import 'package:basic_utils/basic_utils.dart'; @@ -42,6 +43,7 @@ part 'credential_notifier.g.dart'; final containerCredentialsProvider = containerCredentialsNotifierProviderOf( repo: SecureContainerCredentialsRepository(), ioClient: const PrivacyideaIOClient(), + eccUtils: const EccUtils(), ); @Riverpod(keepAlive: true) @@ -52,9 +54,10 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R ContainerCredentialsNotifier({ ContainerCredentialsRepository? repoOverride, PrivacyideaIOClient? ioClientOverride, - NotifierProviderRef? refOverride, + EccUtils? eccUtilsOverride, }) : _repoOverride = repoOverride, - _ioClientOverride = ioClientOverride; + _ioClientOverride = ioClientOverride, + _eccUtilsOverride = eccUtilsOverride; @override ContainerCredentialsRepository get repo => _repo; @@ -66,15 +69,21 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R late PrivacyideaIOClient _ioClient; final PrivacyideaIOClient? _ioClientOverride; + @override + EccUtils get eccUtils => _eccUtils; + late EccUtils _eccUtils; + final EccUtils? _eccUtilsOverride; + @override Future build({ required ContainerCredentialsRepository repo, required PrivacyideaIOClient ioClient, + required EccUtils eccUtils, }) async { await _stateMutex.acquire(); _repo = _repoOverride ?? repo; _ioClient = _ioClientOverride ?? ioClient; - // _ref = _refOverride ?? ref; + _eccUtils = _eccUtilsOverride ?? eccUtils; Logger.warning('Building credentialsProvider', name: 'CredentialsNotifier'); final initState = await _repo.loadCredentialsState(); for (var credential in initState.credentials.whereType()) { @@ -84,6 +93,63 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R return initState; } +////////////////////////////////////////////////////////////////// +////////////////////////// REPO METHODS ////////////////////////// +////////////////////////////////////////////////////////////////// + + Future _saveCredentialToRepo(ContainerCredential credential) async { + return await _repoMutex.protect(() async => await _repo.saveCredential(credential)); + } + + Future _saveCredentialsStateToRepo(CredentialsState credentialsState) async { + return await _repoMutex.protect(() async => await _repo.saveCredentialsState(credentialsState)); + } + + Future _deleteCredentialFromRepo(ContainerCredential credential) async { + return await _repoMutex.protect(() async => await _repo.deleteCredential(credential.serial)); + } + + Future _deleteCredentialsStateToRepo() async { + return await _repoMutex.protect(() async => await _repo.deleteAllCredentials()); + } + +/*////////////////////////////////////////////////////////////////// +////////////////////////// PUBLIC METHODS ////////////////////////// +///////////////////////////////////////////////////////////////// */ + +// ADD CREDENTIALS + + Future addCredential(ContainerCredential credential) async { + await _stateMutex.acquire(); + final newState = await _saveCredentialToRepo(credential); + await update((_) => newState); + _stateMutex.release(); + return newState; + } + + Future addCredentials(List credentials) async { + await _stateMutex.acquire(); + final newCredentials = credentials.toList(); + final oldCredentials = (await future).credentials; + final combinedCredentials = []; + for (var oldCredential in oldCredentials) { + final newCredential = newCredentials.firstWhereOrNull((newCredential) => newCredential.serial == oldCredential.serial); + if (newCredential == null) { + combinedCredentials.add(oldCredential); + } else { + combinedCredentials.add(newCredential); + newCredentials.remove(newCredential); + } + } + combinedCredentials.addAll(newCredentials); + final newState = await _saveCredentialsStateToRepo(CredentialsState(credentials: combinedCredentials)); + await update((_) => newState); + _stateMutex.release(); + return newState; + } + + // UPDATE CREDENTIALS + @override Future update( FutureOr Function(CredentialsState state) cb, { @@ -104,20 +170,22 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R } final updated = updater(currentCredential); final newState = await _saveCredentialToRepo(updated); - state = AsyncValue.data(newState); + await update((_) => newState); _stateMutex.release(); return updated; } - Future addCredential(ContainerCredential credential) async { + // DELETE CREDENTIALS + + Future deleteCredential(ContainerCredential credential) async { await _stateMutex.acquire(); - final newState = await _saveCredentialToRepo(credential); - state = AsyncValue.data(newState); + final newState = await _deleteCredentialFromRepo(credential); + await update((_) => newState); _stateMutex.release(); return newState; } - Future addCredentials(List credentials) async { + Future deleteCredentials(List credentials) async { await _stateMutex.acquire(); final newCredentials = credentials.toList(); final oldCredentials = (await future).credentials; @@ -127,24 +195,16 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R if (newCredential == null) { combinedCredentials.add(oldCredential); } else { - combinedCredentials.add(newCredential); newCredentials.remove(newCredential); } } - combinedCredentials.addAll(newCredentials); final newState = await _saveCredentialsStateToRepo(CredentialsState(credentials: combinedCredentials)); - state = AsyncValue.data(newState); + await update((_) => newState); _stateMutex.release(); return newState; } - Future _saveCredentialToRepo(ContainerCredential credential) async { - return await _repoMutex.protect(() async => await _repo.saveCredential(credential)); - } - - Future _saveCredentialsStateToRepo(CredentialsState credentialsState) async { - return await _repoMutex.protect(() async => await _repo.saveCredentialsState(credentialsState)); - } + // HANDLE PROCESSOR RESULTS @override Future handleProcessorResult(ProcessorResult result, Map args) { @@ -163,32 +223,59 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R final stateCredentials = currentState.credentials; final stateCredentialsSerials = stateCredentials.map((e) => e.serial); final newCredentials = containerCredentials.where((element) => !stateCredentialsSerials.contains(element.serial)).toList(); + Logger.info('Handling processor results: adding Credential', name: 'CredentialsNotifier#handleProcessorResults'); await addCredentials(newCredentials); - for (var credential in containerCredentials) { - if (stateCredentialsSerials is! ContainerCredentialUnfinalized) continue; + Logger.info('Handling processor results: adding done (${newCredentials.length})', name: 'CredentialsNotifier#handleProcessorResults'); + for (var credential in newCredentials) { + Logger.info('Handling processor results: finalize check ()', name: 'CredentialsNotifier#handleProcessorResults'); + if (credential is! ContainerCredentialUnfinalized) continue; + Logger.info('Handling processor results: finalize', name: 'CredentialsNotifier#handleProcessorResults'); await finalize(credential); } return null; } final Mutex _finalizationMutex = Mutex(); - Future finalize(ContainerCredential containerCredential) async { - ContainerCredential? credential = containerCredential; + Future finalize(ContainerCredential credential) async { await _finalizationMutex.acquire(); if (credential is! ContainerCredentialUnfinalized) { _finalizationMutex.release(); - throw ArgumentError('Credential must not be finalized'); + throw ArgumentError('Container must not be finalized'); + } + Logger.info('Finalizing container ${credential.serial}', name: 'CredentialsNotifier#finalize'); + try { + credential = await _generateKeyPair(credential); + final Response response; + (credential, response) = await _sendPublicKey(credential); + if (response.statusCode != 200) { + if (response.piServerMessage != null) { + ref.read(statusMessageProvider.notifier).state = ( + 'Failed to finalize container: ${response.piServerMessage}', + response.piStatusCode != null ? 'PI Server code ${response.piStatusCode}' : 'Status code ${response.body}' + ); + } else { + ref.read(statusMessageProvider.notifier).state = ('Failed to finalize container: ${response.body}', 'Status code ${response.statusCode}'); + } + _finalizationMutex.release(); + return; + } + final ECPublicKey publicServerKey; + (credential, publicServerKey) = await _parseResponse(credential, response); + await updateCredential(credential, (c) => c.finalize(publicServerKey: publicServerKey)!); + } on StateError { + Logger.info('Container was removed while finalizing', name: 'CredentialsNotifier#finalize'); + } catch (e) { + Logger.error('Failed to finalize container ${credential.serial}', name: 'CredentialsNotifier#finalize', error: e); + _finalizationMutex.release(); + return; } - Logger.info('Finalizing container credential ${credential.serial}', name: 'CredentialsNotifier#finalize'); - credential = await _generateKeyPair(credential); - final Response response; - (credential, response) = await _sendPublicKey(credential); - final ECPublicKey publicServerKey; - (credential, publicServerKey) = await _parseResponse(credential, response); - await updateCredential(credential, (c) => c.finalize(publicServerKey: publicServerKey)!); _finalizationMutex.release(); } +//////////////////////////////////////////////////////////////////////////// +////////////////////////// PRIVATE HELPER METHODS ////////////////////////// +//////////////////////////////////////////////////////////////////////////// + /// Finalization substep 1: Generate key pair Future _generateKeyPair(ContainerCredential containerCredential) async { // generatingKeyPair, @@ -208,8 +295,8 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R // sendingPublicKey, // sendingPublicKeyFailed, // sendingPublicKeyCompleted, - ContainerCredential? credential = containerCredential; - final ecPrivateClientKey = credential.ecPrivateClientKey!; + ContainerCredential? container = containerCredential; + final ecPrivateClientKey = container.ecPrivateClientKey!; //POST /container/register/finalize // Request: { // 'container_serial': , @@ -217,32 +304,39 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R // 'signature': )>, // } - final passphrase = credential.passphrase != null ? EnterPassphraseDialog.show(await globalContext) : null; - final message = '${credential.nonce}' - '|${credential.timestamp}' - '|${credential.finalizationUrl}' - '|${credential.serial}' + final passphrase = container.passphrase != null ? EnterPassphraseDialog.show(await globalContext) : null; + final message = '${container.nonce}' + '|${container.timestamp.toIso8601String().replaceFirst('Z', '+00:00')}' + '|${container.finalizationUrl}' + '|${container.serial}' '${passphrase != null ? '|$passphrase' : ''}'; + print(message); + final signature = eccUtils.trySignWithPrivateKey(ecPrivateClientKey, message); - final signature = const EccUtils().trySignWithPrivateKey(ecPrivateClientKey, message); final body = { - 'container_serial': credential.serial, - 'public_client_key': credential.publicClientKey, + 'container_serial': container.serial, + 'public_client_key': container.publicClientKey, 'signature': signature, }; final Response response; - credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKey)); - if (credential == null) throw StateError('Credential was removed'); + container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKey)); + if (container == null) throw StateError('Credential was removed'); try { - response = await _ioClient.doPost(url: credential.finalizationUrl, body: body); + response = await _ioClient.doPost(url: container.finalizationUrl, body: body, sslVerify: false); //TODO: sslVerify } catch (e) { ref.read(statusMessageProvider.notifier).state = ('Failed to finalize container', e.toString()); - await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyFailed)); + await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyFailed)); rethrow; } - credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyCompleted)); - if (credential == null) throw StateError('Credential was removed'); - return (credential, response); + if (response.statusCode != 200) { + container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyFailed)); + if (container == null) throw StateError('Credential was removed'); + return (container, response); + } + + container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyCompleted)); + if (container == null) throw StateError('Credential was removed'); + return (container, response); } /// Finalization substep 3: Parse response @@ -259,8 +353,12 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R if (credential == null) throw StateError('Credential was removed'); try { responseJson = jsonDecode(responseBody); - validateMap(responseJson, {PUBLIC_SERVER_KEY: const TypeMatcher()}); - publicServerKey = const EccUtils().deserializeECPublicKey(responseJson[PUBLIC_SERVER_KEY]); + validateMap(responseJson, {'result': const TypeMatcher>()}); + final result = responseJson['result']; + validateMap(result, {'value': const TypeMatcher>()}); + final value = result['value']; + validateMap(value, {'public_server_key': const TypeMatcher()}); + publicServerKey = const EccUtils().deserializeECPublicKey(value['public_server_key']); } catch (e) { ref.read(statusMessageProvider.notifier).state = ('Failed to decode response body', e.toString()); await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseFailed)); diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart index cdd06aefc..400d6ac5c 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart @@ -7,7 +7,7 @@ part of 'credential_notifier.dart'; // ************************************************************************** String _$containerCredentialsNotifierHash() => - r'31ed5a5d583df5880f443784cbf454d97d0e6519'; + r'e89ea14f6a73f042c4cf8f0d80a10b2be7605082'; /// Copied from Dart SDK class _SystemHash { @@ -34,10 +34,12 @@ abstract class _$ContainerCredentialsNotifier extends BuildlessAsyncNotifier { late final ContainerCredentialsRepository repo; late final PrivacyideaIOClient ioClient; + late final EccUtils eccUtils; FutureOr build({ required ContainerCredentialsRepository repo, required PrivacyideaIOClient ioClient, + required EccUtils eccUtils, }); } @@ -56,10 +58,12 @@ class ContainerCredentialsNotifierFamily ContainerCredentialsNotifierProvider call({ required ContainerCredentialsRepository repo, required PrivacyideaIOClient ioClient, + required EccUtils eccUtils, }) { return ContainerCredentialsNotifierProvider( repo: repo, ioClient: ioClient, + eccUtils: eccUtils, ); } @@ -70,6 +74,7 @@ class ContainerCredentialsNotifierFamily return call( repo: provider.repo, ioClient: provider.ioClient, + eccUtils: provider.eccUtils, ); } @@ -95,10 +100,12 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< ContainerCredentialsNotifierProvider({ required ContainerCredentialsRepository repo, required PrivacyideaIOClient ioClient, + required EccUtils eccUtils, }) : this._internal( () => ContainerCredentialsNotifier() ..repo = repo - ..ioClient = ioClient, + ..ioClient = ioClient + ..eccUtils = eccUtils, from: containerCredentialsNotifierProviderOf, name: r'containerCredentialsNotifierProviderOf', debugGetCreateSourceHash: @@ -110,6 +117,7 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< ContainerCredentialsNotifierFamily._allTransitiveDependencies, repo: repo, ioClient: ioClient, + eccUtils: eccUtils, ); ContainerCredentialsNotifierProvider._internal( @@ -121,10 +129,12 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< required super.from, required this.repo, required this.ioClient, + required this.eccUtils, }) : super.internal(); final ContainerCredentialsRepository repo; final PrivacyideaIOClient ioClient; + final EccUtils eccUtils; @override FutureOr runNotifierBuild( @@ -133,6 +143,7 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< return notifier.build( repo: repo, ioClient: ioClient, + eccUtils: eccUtils, ); } @@ -143,7 +154,8 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< override: ContainerCredentialsNotifierProvider._internal( () => create() ..repo = repo - ..ioClient = ioClient, + ..ioClient = ioClient + ..eccUtils = eccUtils, from: from, name: null, dependencies: null, @@ -151,6 +163,7 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< debugGetCreateSourceHash: null, repo: repo, ioClient: ioClient, + eccUtils: eccUtils, ), ); } @@ -165,7 +178,8 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< bool operator ==(Object other) { return other is ContainerCredentialsNotifierProvider && other.repo == repo && - other.ioClient == ioClient; + other.ioClient == ioClient && + other.eccUtils == eccUtils; } @override @@ -173,6 +187,7 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< var hash = _SystemHash.combine(0, runtimeType.hashCode); hash = _SystemHash.combine(hash, repo.hashCode); hash = _SystemHash.combine(hash, ioClient.hashCode); + hash = _SystemHash.combine(hash, eccUtils.hashCode); return _SystemHash.finish(hash); } @@ -185,6 +200,9 @@ mixin ContainerCredentialsNotifierRef /// The parameter `ioClient` of this provider. PrivacyideaIOClient get ioClient; + + /// The parameter `eccUtils` of this provider. + EccUtils get eccUtils; } class _ContainerCredentialsNotifierProviderElement @@ -198,6 +216,9 @@ class _ContainerCredentialsNotifierProviderElement @override PrivacyideaIOClient get ioClient => (origin as ContainerCredentialsNotifierProvider).ioClient; + @override + EccUtils get eccUtils => + (origin as ContainerCredentialsNotifierProvider).eccUtils; } // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart index ad4921795..096a3599a 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart @@ -390,7 +390,7 @@ class PushRequestNotifier extends _$PushRequestNotifier { try { Logger.info('Sending push request response.', name: 'token_widgets.dart#_handleReaction'); response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body); - } catch (e) { + } catch (_) { Logger.warning('Sending push request response failed. Retrying.', name: 'token_widgets.dart#handleReaction'); try { response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body); diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart index 5473a3a39..d35b5124a 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart @@ -7,7 +7,7 @@ part of 'push_request_provider.dart'; // ************************************************************************** String _$pushRequestNotifierHash() => - r'34e1b940ac7ae3abb4d5887bae44d76f147b6c38'; + r'83bb88bfb1fbc8623da1d368a5bb8808734b1e88'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/views/container_view/container_view.dart b/lib/views/container_view/container_view.dart new file mode 100644 index 000000000..d4741603d --- /dev/null +++ b/lib/views/container_view/container_view.dart @@ -0,0 +1,154 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; +import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart'; +import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/token_widget_tile.dart'; + +import '../../l10n/app_localizations.dart'; +import '../../model/tokens/container_credentials.dart'; +import '../../utils/customization/theme_extentions/action_theme.dart'; +import '../main_view/main_view_widgets/token_widgets/slideable_action.dart'; +import '../../widgets/pi_slideable.dart'; +import '../view_interface.dart'; + +const String groupTag = 'container-actions'; + +class ContainerView extends ConsumerView { + static const String routeName = '/container'; + + @override + get routeSettings => const RouteSettings(name: routeName); + + const ContainerView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final credentials = ref.watch(containerCredentialsProvider).whenOrNull(data: (data) => data.credentials) ?? []; + return Scaffold( + appBar: AppBar(title: const Text('Container')), + floatingActionButton: const QrScannerButton(), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + for (var containerCredential in credentials) ContainerWidget(containerCredential: containerCredential), + ], + ), + ), + ); + } +} + +class ContainerWidget extends ConsumerWidget { + final ContainerCredential containerCredential; + + final List stack; + + const ContainerWidget({ + required this.containerCredential, + this.stack = const [], + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) => ClipRRect( + child: PiSlideable( + groupTag: groupTag, + identifier: containerCredential.serial, + actions: [ + DeleteContainerAction(container: containerCredential, key: Key('${containerCredential.serial}-DeleteContainerAction')), + EditContainerAction(container: containerCredential, key: Key('${containerCredential.serial}-EditContainerAction')), + ], + stack: stack, + tile: TokenWidgetTile( + title: Text(containerCredential.serial), + subtitles: [ + 'issuer: ${containerCredential.issuer}', + 'finalizationState: ${containerCredential.finalizationState.name}', + ], + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + // Open Slidable + }, + ), + ), + ), + ); +} + +class DeleteContainerAction extends PiSlideableAction { + final ContainerCredential container; + + const DeleteContainerAction({ + required this.container, + super.key, + }); + + @override + CustomSlidableAction build(BuildContext context, WidgetRef ref) => CustomSlidableAction( + onPressed: (BuildContext context) => ref.read(containerCredentialsProvider.notifier).deleteCredential(container), + backgroundColor: Theme.of(context).extension()!.deleteColor, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon(Icons.delete_forever), + Text( + AppLocalizations.of(context)!.delete, + overflow: TextOverflow.fade, + softWrap: false, + ), + ], + ), + ); +} + +class EditContainerAction extends PiSlideableAction { + final ContainerCredential container; + + const EditContainerAction({ + required this.container, + super.key, + }); + + @override + CustomSlidableAction build(BuildContext context, WidgetRef ref) => CustomSlidableAction( + onPressed: (BuildContext context) {}, + backgroundColor: Theme.of(context).extension()!.editColor, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon(Icons.edit), + Text( + AppLocalizations.of(context)!.edit, + overflow: TextOverflow.fade, + softWrap: false, + ), + ], + ), + ); +} 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 f194659dd..8ad4a640a 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 @@ -21,8 +21,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; - import '../../../model/mixins/sortable_mixin.dart'; import '../../../model/riverpod_states/settings_state.dart'; import '../../../model/token_folder.dart'; @@ -106,10 +104,6 @@ class _MainViewTokensListState extends ConsumerState { if ((showSortables.isEmpty)) return const NoTokenScreen(); - final credentials = ref.read(containerCredentialsProvider).whenOrNull( - data: (data) => data.credentials, - ); - return Stack( children: [ Column( @@ -163,12 +157,6 @@ class _MainViewTokensListState extends ConsumerState { bottomPaddingIfLast: 80, ) : const SizedBox(height: 80), - ...(credentials!.map((credential) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text('${credential.serial} | ${credential.issuer} | ${credential.finalizationState.name}'), - ); - }).toList()) ], ), ), 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 3ef5c57a2..753ee0fa6 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 @@ -30,9 +30,9 @@ import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../default_token_actions/default_edit_action_dialog.dart'; -import '../../token_action.dart'; +import '../../slideable_action.dart'; -class EditDayPassowrdTokenAction extends TokenAction { +class EditDayPassowrdTokenAction extends PiSlideableAction { final DayPasswordToken token; const EditDayPassowrdTokenAction({ 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 bc6943fed..d3a56f0e8 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 @@ -28,9 +28,9 @@ import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../loading_indicator.dart'; -import '../token_action.dart'; +import '../slideable_action.dart'; -class DefaultDeleteAction extends TokenAction { +class DefaultDeleteAction extends PiSlideableAction { final Token token; const DefaultDeleteAction({super.key, required this.token}); 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 85ba8ba2f..73e5b0f68 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 @@ -31,10 +31,10 @@ import '../../../../../utils/logger.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../widgets/focused_item_as_overlay.dart'; -import '../token_action.dart'; +import '../slideable_action.dart'; import 'default_edit_action_dialog.dart'; -class DefaultEditAction extends TokenAction { +class DefaultEditAction extends PiSlideableAction { final Token token; const DefaultEditAction({required this.token, super.key}); 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 0adfe03a5..b228d36e0 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 @@ -31,9 +31,9 @@ import '../../../../../utils/logger.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../widgets/focused_item_as_overlay.dart'; -import '../token_action.dart'; +import '../slideable_action.dart'; -class DefaultLockAction extends TokenAction { +class DefaultLockAction extends PiSlideableAction { final Token token; const DefaultLockAction({required this.token, super.key}); 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 6c0457e19..3a1ed32d6 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 @@ -30,9 +30,9 @@ import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../default_token_actions/default_edit_action_dialog.dart'; -import '../../token_action.dart'; +import '../../slideable_action.dart'; -class EditHOTPTokenAction extends TokenAction { +class EditHOTPTokenAction extends PiSlideableAction { final HOTPToken token; const EditHOTPTokenAction({ diff --git a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart index acc629314..48633c811 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart @@ -35,12 +35,10 @@ class HOTPTokenWidget extends TokenWidget { super.key, }); @override - TokenWidgetBase build(BuildContext context) { - return TokenWidgetBase( - token: token, - tile: HOTPTokenWidgetTile(token, key: ValueKey(token.id)), - dragIcon: Icons.replay, - editAction: EditHOTPTokenAction(token: token), - ); - } + TokenWidgetBase build(BuildContext context) => TokenWidgetBase( + token: token, + tile: HOTPTokenWidgetTile(token, key: ValueKey(token.id)), + dragIcon: Icons.replay, + editAction: EditHOTPTokenAction(token: token), + ); } 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 e2d2caa17..3d3adf8fa 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 @@ -32,9 +32,9 @@ import '../../../../../../utils/riverpod/riverpod_providers/generated_providers/ import '../../../../../../widgets/enable_text_edit_after_many_taps.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../default_token_actions/default_edit_action_dialog.dart'; -import '../../token_action.dart'; +import '../../slideable_action.dart'; -class EditPushTokenAction extends TokenAction { +class EditPushTokenAction extends PiSlideableAction { final PushToken token; const EditPushTokenAction({ diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/slideable_action.dart similarity index 90% rename from lib/views/main_view/main_view_widgets/token_widgets/token_action.dart rename to lib/views/main_view/main_view_widgets/token_widgets/slideable_action.dart index b6c43b71a..04ef0dbf3 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/slideable_action.dart @@ -21,8 +21,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -abstract class TokenAction extends ConsumerWidget { - const TokenAction({super.key}); +abstract class PiSlideableAction extends ConsumerWidget { + const PiSlideableAction({super.key}); @override CustomSlidableAction build(BuildContext context, WidgetRef ref); } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget.dart index 12120eb37..e2a0ae88b 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget.dart @@ -22,6 +22,7 @@ import 'package:flutter/material.dart'; import 'token_widget_base.dart'; abstract class TokenWidget extends StatelessWidget { + static const String groupTag = 'token-actions'; const TokenWidget({super.key}); @override diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart index 0c102968c..2448603f0 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart @@ -31,15 +31,16 @@ import '../../../../utils/utils.dart'; import 'default_token_actions/default_delete_action.dart'; import 'default_token_actions/default_edit_action.dart'; import 'default_token_actions/default_lock_action.dart'; -import 'token_action.dart'; -import 'token_widget_slideable.dart'; +import 'slideable_action.dart'; +import '../../../../widgets/pi_slideable.dart'; +import 'token_widget.dart'; class TokenWidgetBase extends ConsumerWidget { final Widget tile; final Token token; - final TokenAction? deleteAction; - final TokenAction? editAction; - final TokenAction? lockAction; + final PiSlideableAction? deleteAction; + final PiSlideableAction? editAction; + final PiSlideableAction? lockAction; final List stack; final IconData dragIcon; @@ -57,7 +58,7 @@ class TokenWidgetBase extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final SortableMixin? draggingSortable = ref.watch(draggingSortableProvider); - final List actions = [ + final List actions = [ deleteAction ?? DefaultDeleteAction(token: token, key: Key('${token.id}deleteAction')), editAction ?? DefaultEditAction(token: token, key: Key('${token.id}editAction')), ]; @@ -103,8 +104,9 @@ class TokenWidgetBase extends ConsumerWidget { ), data: token, child: ClipRRect( - child: TokenWidgetSlideable( - token: token, + child: PiSlideable( + groupTag: TokenWidget.groupTag, + identifier: token.id, actions: actions, stack: stack, tile: tile, @@ -114,8 +116,9 @@ class TokenWidgetBase extends ConsumerWidget { : draggingSortable == token ? const SizedBox() : ClipRRect( - child: TokenWidgetSlideable( - token: token, + child: PiSlideable( + groupTag: TokenWidget.groupTag, + identifier: token.id, actions: actions, stack: stack, tile: tile, 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 10a271af2..285c86e0b 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 @@ -30,9 +30,9 @@ import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../../../../widgets/focused_item_as_overlay.dart'; import '../../default_token_actions/default_edit_action_dialog.dart'; -import '../../token_action.dart'; +import '../../slideable_action.dart'; -class EditTOTPTokenAction extends TokenAction { +class EditTOTPTokenAction extends PiSlideableAction { final TOTPToken token; const EditTOTPTokenAction({ diff --git a/lib/views/settings_view/settings_groups/settings_group_container.dart b/lib/views/settings_view/settings_groups/settings_group_container.dart new file mode 100644 index 000000000..e761114eb --- /dev/null +++ b/lib/views/settings_view/settings_groups/settings_group_container.dart @@ -0,0 +1,48 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; +import 'package:privacyidea_authenticator/views/container_view/container_view.dart'; + +import '../settings_view_widgets/settings_groups.dart'; + +class SettingsGroupContainer extends StatelessWidget { + const SettingsGroupContainer({super.key}); + + @override + Widget build(BuildContext context) => SettingsGroup( + title: 'Container', + children: [ + TextButton( + child: ListTile( + title: Text( + 'Container', + style: Theme.of(context).textTheme.bodyMedium, + overflow: TextOverflow.fade, + softWrap: false, + ), + style: ListTileStyle.list, + trailing: Icon(Icons.arrow_forward_ios), + ), + onPressed: () => Navigator.of(context).pushNamed(ContainerView.routeName), + ), + ], + ); +} diff --git a/lib/views/settings_view/settings_view.dart b/lib/views/settings_view/settings_view.dart index bff4e741d..7d85af3b9 100644 --- a/lib/views/settings_view/settings_view.dart +++ b/lib/views/settings_view/settings_view.dart @@ -25,6 +25,7 @@ import '../../model/tokens/push_token.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../widgets/push_request_listener.dart'; import '../view_interface.dart'; +import 'settings_groups/settings_group_container.dart'; import 'settings_groups/settings_group_error_log.dart'; import 'settings_groups/settings_group_general.dart'; import 'settings_groups/settings_group_import_export_tokens.dart'; @@ -73,6 +74,8 @@ class SettingsView extends ConsumerView { ), const Divider(), const SettingsGroupErrorLog(), + const Divider(), + const SettingsGroupContainer(), ], ), ), diff --git a/lib/views/view_interface.dart b/lib/views/view_interface.dart index d0295832f..96d181a15 100644 --- a/lib/views/view_interface.dart +++ b/lib/views/view_interface.dart @@ -19,6 +19,7 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +export 'package:flutter_riverpod/flutter_riverpod.dart' show WidgetRef; abstract class ViewWidget extends Widget { RouteSettings get routeSettings; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_slideable.dart b/lib/widgets/pi_slideable.dart similarity index 78% rename from lib/views/main_view/main_view_widgets/token_widgets/token_widget_slideable.dart rename to lib/widgets/pi_slideable.dart index fd9e5afba..79e64a4d4 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_slideable.dart +++ b/lib/widgets/pi_slideable.dart @@ -20,17 +20,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -import '../../../../model/tokens/token.dart'; -import 'token_action.dart'; +import '../views/main_view/main_view_widgets/token_widgets/slideable_action.dart'; -class TokenWidgetSlideable extends StatelessWidget { - final Token token; - final List actions; +class PiSlideable extends StatelessWidget { + final String groupTag; + final String identifier; + final List actions; final List stack; final Widget tile; - const TokenWidgetSlideable({ - required this.token, + const PiSlideable({ + required this.groupTag, + required this.identifier, required this.actions, required this.stack, required this.tile, @@ -47,8 +48,8 @@ class TokenWidgetSlideable extends StatelessWidget { ); return actions.isNotEmpty ? Slidable( - key: ValueKey(token.id), - groupTag: 'myTag', // This is used to only let one be open at a time. + key: ValueKey('$groupTag-$identifier'), + groupTag: groupTag, endActionPane: ActionPane( motion: const DrawerMotion(), extentRatio: 1, From 87d6daf00b5847b2ffd5f1e489f369c9c8bd72e2 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Thu, 5 Sep 2024 13:44:46 +0200 Subject: [PATCH 032/285] refactoring --- lib/api/token_container_api_endpoint.dart | 182 +- lib/l10n/app_en.arb | 54 +- .../enums/container_finalization_state.dart | 32 + lib/model/enums/duration_unit.dart | 33 + lib/model/enums/ec_key_algorithm.dart | 66 + .../enums/duration_unit_extension.dart | 67 + .../enums/ec_key_algorithm_extension.dart | 113 ++ lib/model/processor_result.dart | 4 +- lib/model/processor_result.freezed.dart | 177 +- .../progress_state.freezed.dart | 64 +- lib/model/token_container.dart | 98 +- lib/model/token_container.freezed.dart | 1662 ++++------------- lib/model/token_container.g.dart | 130 +- lib/model/tokens/container_credentials.dart | 238 +-- .../tokens/container_credentials.freezed.dart | 108 +- lib/model/tokens/container_credentials.g.dart | 8 +- lib/model/tokens/day_password_token.dart | 160 +- lib/model/tokens/hotp_token.dart | 166 +- lib/model/tokens/otp_token.dart | 30 +- lib/model/tokens/push_token.dart | 268 ++- lib/model/tokens/push_token.g.dart | 2 - lib/model/tokens/steam_token.dart | 123 +- lib/model/tokens/token.dart | 57 +- lib/model/tokens/totp_token.dart | 184 +- .../mixins/token_import_processor.dart | 2 +- .../container_credentials_processor.dart | 92 +- .../home_widget_navigate_processor.dart | 23 +- .../otp_auth_processor.dart | 552 ++---- .../aegis_import_file_processor.dart | 211 ++- ...thenticator_pro_import_file_processor.dart | 61 +- .../free_otp_plus_import_file_processor.dart | 62 +- ...google_authenticator_qrfile_processor.dart | 13 +- .../two_fas_import_file_processor.dart | 58 +- ...cure_container_credentials_repository.dart | 19 - ...mote_token_container_state_repository.dart | 100 +- lib/utils/ecc_utils.dart | 38 + lib/utils/errors.dart | 8 +- lib/utils/identifiers.dart | 240 ++- .../credential_notifier.dart | 33 +- .../progress_state_provider.dart | 134 +- .../progress_state_provider.g.dart | 352 ++-- .../token_container_notifier.dart | 9 +- .../generated_providers/token_notifier.dart | 16 +- .../generated_providers/token_notifier.g.dart | 2 +- lib/utils/type_matchers.dart | 60 + lib/utils/utils.dart | 10 +- .../add_token_manually_view.dart | 276 +-- .../add_token_manually_row.dart | 55 + .../add_daypassword_manually.dart | 123 ++ .../add_hotp_manually.dart | 116 ++ .../add_steam_manually.dart | 118 ++ .../add_token_manually_interface.dart | 26 + .../add_totp_manually.dart | 123 ++ .../labeled_dropdown_button.dart | 104 +- .../rows/add_token_button.dart | 58 + .../rows/algorithms_dropdown_button.dart | 44 + .../rows/counter_input_field.dart | 60 + .../rows/digits_dropdown_button.dart | 42 + .../rows/duration_dropdown_button.dart | 48 + .../rows/encoding_dropdown_button.dart | 40 + .../rows/label_input_field.dart | 70 + .../rows/secret_input_field.dart | 83 + .../rows/token_type_dropdown_button.dart | 38 + .../import_tokens_view.dart | 9 +- .../pages/import_start_page.dart | 26 +- lib/widgets/deactivated.dart | 38 + lib/widgets/mutex_button.dart | 62 + .../model/token/day_password_test.dart | 12 +- .../model/token/hotp_token_test.dart | 10 +- .../model/token/push_token_test.dart | 4 +- .../model/token/steam_token_test.dart | 6 +- .../model/token/totp_token_test.dart | 12 +- .../hybrid_token_container_repo_test.dart | 2 +- 73 files changed, 3988 insertions(+), 3738 deletions(-) create mode 100644 lib/model/enums/container_finalization_state.dart create mode 100644 lib/model/enums/duration_unit.dart create mode 100644 lib/model/enums/ec_key_algorithm.dart create mode 100644 lib/model/extensions/enums/duration_unit_extension.dart create mode 100644 lib/model/extensions/enums/ec_key_algorithm_extension.dart create mode 100644 lib/utils/ecc_utils.dart create mode 100644 lib/utils/type_matchers.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/add_token_manually_row.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_daypassword_manually.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_hotp_manually.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_steam_manually.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_token_manually_interface.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_totp_manually.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/add_token_button.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/algorithms_dropdown_button.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/counter_input_field.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/digits_dropdown_button.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/duration_dropdown_button.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/encoding_dropdown_button.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/label_input_field.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/secret_input_field.dart create mode 100644 lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/token_type_dropdown_button.dart create mode 100644 lib/widgets/deactivated.dart create mode 100644 lib/widgets/mutex_button.dart diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index 9b66d516b..cec6353a2 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -1,99 +1,93 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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 '../model/extensions/enums/encodings_extension.dart'; -import '../model/token_container.dart'; -import '../model/tokens/container_credentials.dart'; -import '../utils/identifiers.dart'; -import '../utils/logger.dart'; +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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:http/io_client.dart'; -import '../interfaces/api_endpoint.dart'; -import '../model/enums/encodings.dart'; -import '../model/enums/token_types.dart'; -import '../model/riverpod_states/credentials_state.dart'; +// import '../model/extensions/enums/encodings_extension.dart'; +// import '../model/token_container.dart'; +// import '../model/tokens/container_credentials.dart'; +// import '../utils/identifiers.dart'; +// import '../utils/logger.dart'; -final Map> _data = { - '123': { - 'tokenID65381723659': TokenTemplate( - data: { - URI_LABEL: '123 container token 1', - URI_SECRET: Encodings.base32.decode('SECRET'), - URI_TYPE: TokenTypes.TOTP.name, - }, - ) - } -}; +// import '../interfaces/api_endpoint.dart'; +// import '../model/enums/encodings.dart'; +// import '../model/enums/token_types.dart'; +// import '../model/riverpod_states/credentials_state.dart'; -class TokenContainerApiEndpoint implements ApiEndpioint { - @override - final ContainerCredential credential; - TokenContainerApiEndpoint({required this.credential}); - @override - Future fetch() { - throw UnimplementedError(); - } +// class TokenContainerApiEndpoint implements ApiEndpioint { +// @override +// final ContainerCredential credential; +// final IOClient _client = IOClient(); +// TokenContainerApiEndpoint({required this.credential}); - @override - Future sync(TokenContainer containerState) async { - Logger.info('Syncing container with server', name: 'TokenContainerApiEndpoint#sync'); - final serverTemplates = _data[containerState.serial]; - if (serverTemplates == null) { - return containerState.copyTransformInto(args: {'message': 'Container not found'}); - } - for (var templateSerial in serverTemplates.keys) { - final template = serverTemplates[templateSerial]; - if (template?.serial == null) { - // Add serial(key of map) to template - serverTemplates[templateSerial] = template!.copyAddAll({URI_SERIAL: templateSerial}); - } - } - final localTemplates = containerState.localTokenTemplates; - Logger.debug('Local templates: ${localTemplates.length}', name: 'TokenContainerApiEndpoint#sync'); - for (var localTemplate in localTemplates) { - final oldLabel = localTemplate.data[URI_LABEL] as String; - Logger.debug('Old label: "$oldLabel" starts with "${containerState.serial}" ?', name: 'TokenContainerApiEndpoint#sync'); - if (oldLabel.startsWith(containerState.serial) == true) { - var merged = localTemplate.copyAddAll({ - URI_LABEL: '123 😀', - }); - Logger.debug('New label: "${merged.data[URI_LABEL]}"', name: 'TokenContainerApiEndpoint#sync'); - if (merged.serial == null) { - merged = merged.copyWith(data: merged.copyAddAll({URI_SERIAL: 'tokenID${DateTime.now().millisecondsSinceEpoch}'}).data); - } - Logger.debug('MergedData: ${merged.data}', name: 'TokenContainerApiEndpoint#sync'); - final localTemplateSerial = merged.serial!; - serverTemplates[localTemplateSerial] = merged; - } - } - Logger.debug('Server templates: $serverTemplates', name: 'TokenContainerApiEndpoint#sync'); - _data[containerState.serial] = serverTemplates; - Logger.debug('_data: $_data', name: 'TokenContainerApiEndpoint#sync'); - final serverTemplatesMerged = _data[containerState.serial]!.values.toList(); - final newContainerState = TokenContainerSynced( - lastSyncAt: DateTime.now(), - serial: containerState.serial, - description: 'Synced with server', - syncedTokenTemplates: serverTemplatesMerged, - localTokenTemplates: [], - ); - Logger.debug('Synced container: $newContainerState', name: 'TokenContainerApiEndpoint#sync'); - return newContainerState; - } -} +// @override +// Future fetch() { +// throw UnimplementedError(); +// } + +// String _buildContainerBo + +// @override +// Future sync(TokenContainer containerState) async { +// Logger.info('Syncing container with server', name: 'TokenContainerApiEndpoint#sync'); +// final serverTemplates = _data[containerState.serial]; +// if (serverTemplates == null) { +// return containerState.copyTransformInto(args: {'message': 'Container not found'}); +// } +// for (var templateSerial in serverTemplates.keys) { +// final template = serverTemplates[templateSerial]; +// if (template?.serial == null) { +// // Add serial(key of map) to template +// serverTemplates[templateSerial] = template!.copyAddAll({URI_SERIAL: templateSerial}); +// } +// } +// final localTemplates = containerState.localTokenTemplates; +// Logger.debug('Local templates: ${localTemplates.length}', name: 'TokenContainerApiEndpoint#sync'); +// for (var localTemplate in localTemplates) { +// final oldLabel = localTemplate.data[URI_LABEL] as String; +// Logger.debug('Old label: "$oldLabel" starts with "${containerState.serial}" ?', name: 'TokenContainerApiEndpoint#sync'); +// if (oldLabel.startsWith(containerState.serial) == true) { +// var merged = localTemplate.copyAddAll({ +// URI_LABEL: '123 😀', +// }); +// Logger.debug('New label: "${merged.data[URI_LABEL]}"', name: 'TokenContainerApiEndpoint#sync'); +// if (merged.serial == null) { +// merged = merged.copyWith(data: merged.copyAddAll({URI_SERIAL: 'tokenID${DateTime.now().millisecondsSinceEpoch}'}).data); +// } +// Logger.debug('MergedData: ${merged.data}', name: 'TokenContainerApiEndpoint#sync'); +// final localTemplateSerial = merged.serial!; +// serverTemplates[localTemplateSerial] = merged; +// } +// } +// Logger.debug('Server templates: $serverTemplates', name: 'TokenContainerApiEndpoint#sync'); +// _data[containerState.serial] = serverTemplates; +// Logger.debug('_data: $_data', name: 'TokenContainerApiEndpoint#sync'); +// final serverTemplatesMerged = _data[containerState.serial]!.values.toList(); +// final newContainerState = TokenContainerSynced( +// lastSyncAt: DateTime.now(), +// serial: containerState.serial, +// description: 'Synced with server', +// syncedTokenTemplates: serverTemplatesMerged, +// localTokenTemplates: [], +// ); +// Logger.debug('Synced container: $newContainerState', name: 'TokenContainerApiEndpoint#sync'); +// return newContainerState; +// } +// } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index dc6280247..8363226f9 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -31,6 +31,10 @@ "@secretKey": { "description": "Describes the field where the tokens secret should be entered." }, + "counter": "Counter", + "@counter": { + "description": "Describes the field where the tokens counter should be entered." + }, "encoding": "Encoding", "@encoding": { "description": "Title of the dropdown button where the encoding is selected." @@ -91,6 +95,14 @@ "@theSecretDoesNotFitTheCurrentEncoding": { "description": "Hint telling the user that the secret does not fit the selected encoding." }, + "notAnNumber": "The value is not a number.", + "@notAnNumber": { + "description": "Error message when the user entered a value that is not a number." + }, + "notAnInteger" : "The value is not an integer.", + "@notAnInteger": { + "description": "Error message when the user entered a value that is not an integer." + }, "renameToken": "Rename token", "@renameToken": { "description": "Title of the dialog where a new name for a token can be entered." @@ -524,7 +536,7 @@ "selectImportSource": "Select import source", "selectImportType": "How do you want to import the tokens?", "importTokens": "Import token", - "importNTokens": "{count, plural, zero{Import no tokens} one{Import one token} other{Import {count} tokens}}", + "importNTokens": "{count, plural, zero{Import no token} one{Import one token} other{Import {count} tokens}}", "selectFile": "Select file", "decrypt": "Decrypt", "tokensAreEncrypted": "The tokens are encrypted. Please enter the password to decrypt them.", @@ -643,18 +655,50 @@ "example": "counter" } } + }, "missingRequiredParameterIn": "Value for parameter [{parameter}] is required and is missing in \"{map}\"", + "@missingRequiredParameterIn": { + "placeholders": { + "parameter": { + "example": "counter" + }, + "map": { + "example": "query parameters" + } + } }, - "invalidValueForParameter": "\"{value}\" is not a valid value for the parameter \"{parameter}\".", - "@invalidValueForParameter": { + "invalidValue": "The {type} \"{value}\" is not valid for \"{parameter}\"", + "@invalidValue": { "placeholders": { + "type": { + "example": "int" + }, "value": { - "example": "abc" + "example": "5" }, "parameter": { - "example": "number" + "example": "counter" + } + } + }, + "invalidValueIn": "The {type} \"{value}\" is not valid for \"{parameter}\" in \"{map}\"", + "@invalidValueIn": { + "placeholders": { + "type": { + "example": "int" + }, + "value": { + "example": "5" + }, + "parameter": { + "example": "counter" + }, + "map": { + "example": "query parameters" } + } }, + "unsupported": "The {name} [{value}] is not supported by this version of the app.", "@unsupported": { "placeholders": { diff --git a/lib/model/enums/container_finalization_state.dart b/lib/model/enums/container_finalization_state.dart new file mode 100644 index 000000000..ae3ccfae6 --- /dev/null +++ b/lib/model/enums/container_finalization_state.dart @@ -0,0 +1,32 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ +enum ContainerFinalizationState { + uninitialized, + generatingKeyPair, + generatingKeyPairFailed, + generatingKeyPairCompleted, + sendingPublicKey, + sendingPublicKeyFailed, + sendingPublicKeyCompleted, + parsingResponse, + parsingResponseFailed, + parsingResponseCompleted, + finalized, +} diff --git a/lib/model/enums/duration_unit.dart b/lib/model/enums/duration_unit.dart new file mode 100644 index 000000000..f107769d4 --- /dev/null +++ b/lib/model/enums/duration_unit.dart @@ -0,0 +1,33 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ +enum DurationUnit { + microseconds, + milliseconds, + seconds, + minutes, + hours, + days, + weeks, + months, + years, + decades, + centuries, + millenniums, +} diff --git a/lib/model/enums/ec_key_algorithm.dart b/lib/model/enums/ec_key_algorithm.dart new file mode 100644 index 000000000..3cd046606 --- /dev/null +++ b/lib/model/enums/ec_key_algorithm.dart @@ -0,0 +1,66 @@ +// ignore_for_file: constant_identifier_names + +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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. + */ + +/// The following curves are supported: +enum EcKeyAlgorithm { + brainpoolp160r1, + brainpoolp160t1, + brainpoolp192r1, + brainpoolp192t1, + brainpoolp224r1, + brainpoolp224t1, + brainpoolp256r1, + brainpoolp256t1, + brainpoolp320r1, + brainpoolp320t1, + brainpoolp384r1, + brainpoolp384t1, + brainpoolp512r1, + brainpoolp512t1, + GostR3410_2001_CryptoPro_A, + GostR3410_2001_CryptoPro_B, + GostR3410_2001_CryptoPro_C, + GostR3410_2001_CryptoPro_XchA, + GostR3410_2001_CryptoPro_XchB, + prime192v1, + prime192v2, + prime192v3, + prime239v1, + prime239v2, + prime239v3, + prime256v1, + secp112r1, + secp112r2, + secp128r1, + secp128r2, + secp160k1, + secp160r1, + secp160r2, + secp192k1, + secp192r1, + secp224k1, + secp224r1, + secp256k1, + secp256r1, + secp384r1, + secp521r1, +} diff --git a/lib/model/extensions/enums/duration_unit_extension.dart b/lib/model/extensions/enums/duration_unit_extension.dart new file mode 100644 index 000000000..f689f69d2 --- /dev/null +++ b/lib/model/extensions/enums/duration_unit_extension.dart @@ -0,0 +1,67 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../enums/duration_unit.dart'; + +extension DurationUnitX on DurationUnit { + String get postfix => switch (this) { + DurationUnit.microseconds => 'µs', + DurationUnit.milliseconds => 'ms', + DurationUnit.seconds => 's', + DurationUnit.minutes => 'm', + DurationUnit.hours => 'h', + DurationUnit.days => 'd', + DurationUnit.weeks => 'w', + DurationUnit.months => 'M', + DurationUnit.years => 'y', + DurationUnit.decades => 'dec', + DurationUnit.centuries => 'c', + DurationUnit.millenniums => 'm', + }; + + int durationToUnitInt(Duration duration) => switch (this) { + DurationUnit.microseconds => duration.inMicroseconds, + DurationUnit.milliseconds => duration.inMilliseconds, + DurationUnit.seconds => duration.inSeconds, + DurationUnit.minutes => duration.inMinutes, + DurationUnit.hours => duration.inHours, + DurationUnit.days => duration.inDays, + DurationUnit.weeks => duration.inDays ~/ 7, + DurationUnit.months => duration.inDays ~/ 30, + DurationUnit.years => duration.inDays ~/ 365, + DurationUnit.decades => duration.inDays ~/ 3650, + DurationUnit.centuries => duration.inDays ~/ 36500, + DurationUnit.millenniums => duration.inDays ~/ 365000, + }; + + double durationToUnitDouble(Duration duration) => switch (this) { + DurationUnit.microseconds => duration.inMicroseconds.toDouble(), + DurationUnit.milliseconds => duration.inMicroseconds / 1000, + DurationUnit.seconds => duration.inMicroseconds / 1000000, + DurationUnit.minutes => duration.inMicroseconds / 60000000, + DurationUnit.hours => duration.inMicroseconds / 3600000000, + DurationUnit.days => duration.inMicroseconds / 86400000000, + DurationUnit.weeks => duration.inMicroseconds / 604800000000, + DurationUnit.months => duration.inMicroseconds / 2592000000000, + DurationUnit.years => duration.inMicroseconds / 31536000000000, + DurationUnit.decades => duration.inMicroseconds / 315360000000000, + DurationUnit.centuries => duration.inMicroseconds / 3153600000000000, + DurationUnit.millenniums => duration.inMicroseconds / 31536000000000000, + }; +} diff --git a/lib/model/extensions/enums/ec_key_algorithm_extension.dart b/lib/model/extensions/enums/ec_key_algorithm_extension.dart new file mode 100644 index 000000000..ae181c1a0 --- /dev/null +++ b/lib/model/extensions/enums/ec_key_algorithm_extension.dart @@ -0,0 +1,113 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../enums/ec_key_algorithm.dart'; + +extension EcKeyAlgorithmList on List { + EcKeyAlgorithm byCurveName(String domainName) => switch (domainName) { + 'brainpoolp160r1' => EcKeyAlgorithm.brainpoolp160r1, + 'brainpoolp160t1' => EcKeyAlgorithm.brainpoolp160t1, + 'brainpoolp192r1' => EcKeyAlgorithm.brainpoolp192r1, + 'brainpoolp192t1' => EcKeyAlgorithm.brainpoolp192t1, + 'brainpoolp224r1' => EcKeyAlgorithm.brainpoolp224r1, + 'brainpoolp224t1' => EcKeyAlgorithm.brainpoolp224t1, + 'brainpoolp256r1' => EcKeyAlgorithm.brainpoolp256r1, + 'brainpoolp256t1' => EcKeyAlgorithm.brainpoolp256t1, + 'brainpoolp320r1' => EcKeyAlgorithm.brainpoolp320r1, + 'brainpoolp320t1' => EcKeyAlgorithm.brainpoolp320t1, + 'brainpoolp384r1' => EcKeyAlgorithm.brainpoolp384r1, + 'brainpoolp384t1' => EcKeyAlgorithm.brainpoolp384t1, + 'brainpoolp512r1' => EcKeyAlgorithm.brainpoolp512r1, + 'brainpoolp512t1' => EcKeyAlgorithm.brainpoolp512t1, + 'GostR3410-2001-CryptoPro-A' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_A, + 'GostR3410-2001-CryptoPro-B' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_B, + 'GostR3410-2001-CryptoPro-C' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_C, + 'GostR3410-2001-CryptoPro-XchA' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchA, + 'GostR3410-2001-CryptoPro-XchB' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchB, + 'prime192v1' => EcKeyAlgorithm.prime192v1, + 'prime192v2' => EcKeyAlgorithm.prime192v2, + 'prime192v3' => EcKeyAlgorithm.prime192v3, + 'prime239v1' => EcKeyAlgorithm.prime239v1, + 'prime239v2' => EcKeyAlgorithm.prime239v2, + 'prime239v3' => EcKeyAlgorithm.prime239v3, + 'prime256v1' => EcKeyAlgorithm.prime256v1, + 'secp112r1' => EcKeyAlgorithm.secp112r1, + 'secp112r2' => EcKeyAlgorithm.secp112r2, + 'secp128r1' => EcKeyAlgorithm.secp128r1, + 'secp128r2' => EcKeyAlgorithm.secp128r2, + 'secp160k1' => EcKeyAlgorithm.secp160k1, + 'secp160r1' => EcKeyAlgorithm.secp160r1, + 'secp160r2' => EcKeyAlgorithm.secp160r2, + 'secp192k1' => EcKeyAlgorithm.secp192k1, + 'secp192r1' => EcKeyAlgorithm.secp192r1, + 'secp224k1' => EcKeyAlgorithm.secp224k1, + 'secp224r1' => EcKeyAlgorithm.secp224r1, + 'secp256k1' => EcKeyAlgorithm.secp256k1, + 'secp256r1' => EcKeyAlgorithm.secp256r1, + 'secp384r1' => EcKeyAlgorithm.secp384r1, + 'secp521r1' => EcKeyAlgorithm.secp521r1, + _ => throw ArgumentError('Unknown domain name: $domainName'), + }; +} + +extension EcKeyAlgorithmX on EcKeyAlgorithm { + String get curveName => switch (this) { + EcKeyAlgorithm.brainpoolp160r1 => 'brainpoolp160r1', + EcKeyAlgorithm.brainpoolp160t1 => 'brainpoolp160t1', + EcKeyAlgorithm.brainpoolp192r1 => 'brainpoolp192r1', + EcKeyAlgorithm.brainpoolp192t1 => 'brainpoolp192t1', + EcKeyAlgorithm.brainpoolp224r1 => 'brainpoolp224r1', + EcKeyAlgorithm.brainpoolp224t1 => 'brainpoolp224t1', + EcKeyAlgorithm.brainpoolp256r1 => 'brainpoolp256r1', + EcKeyAlgorithm.brainpoolp256t1 => 'brainpoolp256t1', + EcKeyAlgorithm.brainpoolp320r1 => 'brainpoolp320r1', + EcKeyAlgorithm.brainpoolp320t1 => 'brainpoolp320t1', + EcKeyAlgorithm.brainpoolp384r1 => 'brainpoolp384r1', + EcKeyAlgorithm.brainpoolp384t1 => 'brainpoolp384t1', + EcKeyAlgorithm.brainpoolp512r1 => 'brainpoolp512r1', + EcKeyAlgorithm.brainpoolp512t1 => 'brainpoolp512t1', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_A => 'GostR3410-2001-CryptoPro-A', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_B => 'GostR3410-2001-CryptoPro-B', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_C => 'GostR3410-2001-CryptoPro-C', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchA => 'GostR3410-2001-CryptoPro-XchA', + EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchB => 'GostR3410-2001-CryptoPro-XchB', + EcKeyAlgorithm.prime192v1 => 'prime192v1', + EcKeyAlgorithm.prime192v2 => 'prime192v2', + EcKeyAlgorithm.prime192v3 => 'prime192v3', + EcKeyAlgorithm.prime239v1 => 'prime239v1', + EcKeyAlgorithm.prime239v2 => 'prime239v2', + EcKeyAlgorithm.prime239v3 => 'prime239v3', + EcKeyAlgorithm.prime256v1 => 'prime256v1', + EcKeyAlgorithm.secp112r1 => 'secp112r1', + EcKeyAlgorithm.secp112r2 => 'secp112r2', + EcKeyAlgorithm.secp128r1 => 'secp128r1', + EcKeyAlgorithm.secp128r2 => 'secp128r2', + EcKeyAlgorithm.secp160k1 => 'secp160k1', + EcKeyAlgorithm.secp160r1 => 'secp160r1', + EcKeyAlgorithm.secp160r2 => 'secp160r2', + EcKeyAlgorithm.secp192k1 => 'secp192k1', + EcKeyAlgorithm.secp192r1 => 'secp192r1', + EcKeyAlgorithm.secp224k1 => 'secp224k1', + EcKeyAlgorithm.secp224r1 => 'secp224r1', + EcKeyAlgorithm.secp256k1 => 'secp256k1', + EcKeyAlgorithm.secp256r1 => 'secp256r1', + EcKeyAlgorithm.secp384r1 => 'secp384r1', + EcKeyAlgorithm.secp521r1 => 'secp521r1', + }; +} diff --git a/lib/model/processor_result.dart b/lib/model/processor_result.dart index 629eb6408..1bb887d34 100644 --- a/lib/model/processor_result.dart +++ b/lib/model/processor_result.dart @@ -34,11 +34,11 @@ abstract class ProcessorResult with _$ProcessorResult { const ProcessorResult._(); const factory ProcessorResult.success( T resultData, { - TypeMatcher? resultHandlerType, + TypeValidatorRequired? resultHandlerType, }) = ProcessorResultSuccess; const factory ProcessorResult.failed( String message, { - TypeMatcher? resultHandlerType, + TypeValidatorRequired? resultHandlerType, }) = ProcessorResultFailed; bool get isSuccess => this is ProcessorResultSuccess; diff --git a/lib/model/processor_result.freezed.dart b/lib/model/processor_result.freezed.dart index 82d27fb6f..e3647129a 100644 --- a/lib/model/processor_result.freezed.dart +++ b/lib/model/processor_result.freezed.dart @@ -16,36 +16,23 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$ProcessorResult { - TypeMatcher? get resultHandlerType => - throw _privateConstructorUsedError; + TypeValidatorRequired? get resultHandlerType => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function( - T resultData, TypeMatcher? resultHandlerType) - success, - required TResult Function( - String message, TypeMatcher? resultHandlerType) - failed, + required TResult Function(T resultData, TypeValidatorRequired? resultHandlerType) success, + required TResult Function(String message, TypeValidatorRequired? resultHandlerType) failed, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - T resultData, TypeMatcher? resultHandlerType)? - success, - TResult? Function( - String message, TypeMatcher? resultHandlerType)? - failed, + TResult? Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, + TResult? Function(String message, TypeValidatorRequired? resultHandlerType)? failed, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function( - T resultData, TypeMatcher? resultHandlerType)? - success, - TResult Function( - String message, TypeMatcher? resultHandlerType)? - failed, + TResult Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, + TResult Function(String message, TypeValidatorRequired? resultHandlerType)? failed, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -72,22 +59,19 @@ mixin _$ProcessorResult { /// Create a copy of ProcessorResult /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $ProcessorResultCopyWith> get copyWith => - throw _privateConstructorUsedError; + $ProcessorResultCopyWith> get copyWith => throw _privateConstructorUsedError; } /// @nodoc abstract class $ProcessorResultCopyWith { - factory $ProcessorResultCopyWith( - ProcessorResult value, $Res Function(ProcessorResult) then) = + factory $ProcessorResultCopyWith(ProcessorResult value, $Res Function(ProcessorResult) then) = _$ProcessorResultCopyWithImpl>; @useResult - $Res call({TypeMatcher? resultHandlerType}); + $Res call({TypeValidatorRequired? resultHandlerType}); } /// @nodoc -class _$ProcessorResultCopyWithImpl> - implements $ProcessorResultCopyWith { +class _$ProcessorResultCopyWithImpl> implements $ProcessorResultCopyWith { _$ProcessorResultCopyWithImpl(this._value, this._then); // ignore: unused_field @@ -106,31 +90,24 @@ class _$ProcessorResultCopyWithImpl> resultHandlerType: freezed == resultHandlerType ? _value.resultHandlerType : resultHandlerType // ignore: cast_nullable_to_non_nullable - as TypeMatcher?, + as TypeValidatorRequired?, ) as $Val); } } /// @nodoc -abstract class _$$ProcessorResultSuccessImplCopyWith - implements $ProcessorResultCopyWith { - factory _$$ProcessorResultSuccessImplCopyWith( - _$ProcessorResultSuccessImpl value, - $Res Function(_$ProcessorResultSuccessImpl) then) = +abstract class _$$ProcessorResultSuccessImplCopyWith implements $ProcessorResultCopyWith { + factory _$$ProcessorResultSuccessImplCopyWith(_$ProcessorResultSuccessImpl value, $Res Function(_$ProcessorResultSuccessImpl) then) = __$$ProcessorResultSuccessImplCopyWithImpl; @override @useResult - $Res call({T resultData, TypeMatcher? resultHandlerType}); + $Res call({T resultData, TypeValidatorRequired? resultHandlerType}); } /// @nodoc -class __$$ProcessorResultSuccessImplCopyWithImpl - extends _$ProcessorResultCopyWithImpl> +class __$$ProcessorResultSuccessImplCopyWithImpl extends _$ProcessorResultCopyWithImpl> implements _$$ProcessorResultSuccessImplCopyWith { - __$$ProcessorResultSuccessImplCopyWithImpl( - _$ProcessorResultSuccessImpl _value, - $Res Function(_$ProcessorResultSuccessImpl) _then) + __$$ProcessorResultSuccessImplCopyWithImpl(_$ProcessorResultSuccessImpl _value, $Res Function(_$ProcessorResultSuccessImpl) _then) : super(_value, _then); /// Create a copy of ProcessorResult @@ -149,7 +126,7 @@ class __$$ProcessorResultSuccessImplCopyWithImpl resultHandlerType: freezed == resultHandlerType ? _value.resultHandlerType : resultHandlerType // ignore: cast_nullable_to_non_nullable - as TypeMatcher?, + as TypeValidatorRequired?, )); } } @@ -157,13 +134,12 @@ class __$$ProcessorResultSuccessImplCopyWithImpl /// @nodoc class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { - const _$ProcessorResultSuccessImpl(this.resultData, {this.resultHandlerType}) - : super._(); + const _$ProcessorResultSuccessImpl(this.resultData, {this.resultHandlerType}) : super._(); @override final T resultData; @override - final TypeMatcher? resultHandlerType; + final TypeValidatorRequired? resultHandlerType; @override String toString() { @@ -175,34 +151,26 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ProcessorResultSuccessImpl && - const DeepCollectionEquality() - .equals(other.resultData, resultData) && - (identical(other.resultHandlerType, resultHandlerType) || - other.resultHandlerType == resultHandlerType)); + const DeepCollectionEquality().equals(other.resultData, resultData) && + (identical(other.resultHandlerType, resultHandlerType) || other.resultHandlerType == resultHandlerType)); } @override - int get hashCode => Object.hash(runtimeType, - const DeepCollectionEquality().hash(resultData), resultHandlerType); + int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(resultData), resultHandlerType); /// Create a copy of ProcessorResult /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$ProcessorResultSuccessImplCopyWith> - get copyWith => __$$ProcessorResultSuccessImplCopyWithImpl>(this, _$identity); + _$$ProcessorResultSuccessImplCopyWith> get copyWith => + __$$ProcessorResultSuccessImplCopyWithImpl>(this, _$identity); @override @optionalTypeArgs TResult when({ - required TResult Function( - T resultData, TypeMatcher? resultHandlerType) - success, - required TResult Function( - String message, TypeMatcher? resultHandlerType) - failed, + required TResult Function(T resultData, TypeValidatorRequired? resultHandlerType) success, + required TResult Function(String message, TypeValidatorRequired? resultHandlerType) failed, }) { return success(resultData, resultHandlerType); } @@ -210,12 +178,8 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - T resultData, TypeMatcher? resultHandlerType)? - success, - TResult? Function( - String message, TypeMatcher? resultHandlerType)? - failed, + TResult? Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, + TResult? Function(String message, TypeValidatorRequired? resultHandlerType)? failed, }) { return success?.call(resultData, resultHandlerType); } @@ -223,12 +187,8 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function( - T resultData, TypeMatcher? resultHandlerType)? - success, - TResult Function( - String message, TypeMatcher? resultHandlerType)? - failed, + TResult Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, + TResult Function(String message, TypeValidatorRequired? resultHandlerType)? failed, required TResult orElse(), }) { if (success != null) { @@ -270,44 +230,33 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { } abstract class ProcessorResultSuccess extends ProcessorResult { - const factory ProcessorResultSuccess(final T resultData, - {final TypeMatcher? resultHandlerType}) = - _$ProcessorResultSuccessImpl; + const factory ProcessorResultSuccess(final T resultData, {final TypeValidatorRequired? resultHandlerType}) = _$ProcessorResultSuccessImpl; const ProcessorResultSuccess._() : super._(); T get resultData; @override - TypeMatcher? get resultHandlerType; + TypeValidatorRequired? get resultHandlerType; /// Create a copy of ProcessorResult /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$ProcessorResultSuccessImplCopyWith> - get copyWith => throw _privateConstructorUsedError; + _$$ProcessorResultSuccessImplCopyWith> get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class _$$ProcessorResultFailedImplCopyWith - implements $ProcessorResultCopyWith { - factory _$$ProcessorResultFailedImplCopyWith( - _$ProcessorResultFailedImpl value, - $Res Function(_$ProcessorResultFailedImpl) then) = +abstract class _$$ProcessorResultFailedImplCopyWith implements $ProcessorResultCopyWith { + factory _$$ProcessorResultFailedImplCopyWith(_$ProcessorResultFailedImpl value, $Res Function(_$ProcessorResultFailedImpl) then) = __$$ProcessorResultFailedImplCopyWithImpl; @override @useResult - $Res call({String message, TypeMatcher? resultHandlerType}); + $Res call({String message, TypeValidatorRequired? resultHandlerType}); } /// @nodoc -class __$$ProcessorResultFailedImplCopyWithImpl - extends _$ProcessorResultCopyWithImpl> +class __$$ProcessorResultFailedImplCopyWithImpl extends _$ProcessorResultCopyWithImpl> implements _$$ProcessorResultFailedImplCopyWith { - __$$ProcessorResultFailedImplCopyWithImpl( - _$ProcessorResultFailedImpl _value, - $Res Function(_$ProcessorResultFailedImpl) _then) - : super(_value, _then); + __$$ProcessorResultFailedImplCopyWithImpl(_$ProcessorResultFailedImpl _value, $Res Function(_$ProcessorResultFailedImpl) _then) : super(_value, _then); /// Create a copy of ProcessorResult /// with the given fields replaced by the non-null parameter values. @@ -325,7 +274,7 @@ class __$$ProcessorResultFailedImplCopyWithImpl resultHandlerType: freezed == resultHandlerType ? _value.resultHandlerType : resultHandlerType // ignore: cast_nullable_to_non_nullable - as TypeMatcher?, + as TypeValidatorRequired?, )); } } @@ -333,13 +282,12 @@ class __$$ProcessorResultFailedImplCopyWithImpl /// @nodoc class _$ProcessorResultFailedImpl extends ProcessorResultFailed { - const _$ProcessorResultFailedImpl(this.message, {this.resultHandlerType}) - : super._(); + const _$ProcessorResultFailedImpl(this.message, {this.resultHandlerType}) : super._(); @override final String message; @override - final TypeMatcher? resultHandlerType; + final TypeValidatorRequired? resultHandlerType; @override String toString() { @@ -352,8 +300,7 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { (other.runtimeType == runtimeType && other is _$ProcessorResultFailedImpl && (identical(other.message, message) || other.message == message) && - (identical(other.resultHandlerType, resultHandlerType) || - other.resultHandlerType == resultHandlerType)); + (identical(other.resultHandlerType, resultHandlerType) || other.resultHandlerType == resultHandlerType)); } @override @@ -364,19 +311,14 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$ProcessorResultFailedImplCopyWith> - get copyWith => __$$ProcessorResultFailedImplCopyWithImpl>(this, _$identity); + _$$ProcessorResultFailedImplCopyWith> get copyWith => + __$$ProcessorResultFailedImplCopyWithImpl>(this, _$identity); @override @optionalTypeArgs TResult when({ - required TResult Function( - T resultData, TypeMatcher? resultHandlerType) - success, - required TResult Function( - String message, TypeMatcher? resultHandlerType) - failed, + required TResult Function(T resultData, TypeValidatorRequired? resultHandlerType) success, + required TResult Function(String message, TypeValidatorRequired? resultHandlerType) failed, }) { return failed(message, resultHandlerType); } @@ -384,12 +326,8 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - T resultData, TypeMatcher? resultHandlerType)? - success, - TResult? Function( - String message, TypeMatcher? resultHandlerType)? - failed, + TResult? Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, + TResult? Function(String message, TypeValidatorRequired? resultHandlerType)? failed, }) { return failed?.call(message, resultHandlerType); } @@ -397,12 +335,8 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function( - T resultData, TypeMatcher? resultHandlerType)? - success, - TResult Function( - String message, TypeMatcher? resultHandlerType)? - failed, + TResult Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, + TResult Function(String message, TypeValidatorRequired? resultHandlerType)? failed, required TResult orElse(), }) { if (failed != null) { @@ -444,19 +378,16 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { } abstract class ProcessorResultFailed extends ProcessorResult { - const factory ProcessorResultFailed(final String message, - {final TypeMatcher? resultHandlerType}) = - _$ProcessorResultFailedImpl; + const factory ProcessorResultFailed(final String message, {final TypeValidatorRequired? resultHandlerType}) = _$ProcessorResultFailedImpl; const ProcessorResultFailed._() : super._(); String get message; @override - TypeMatcher? get resultHandlerType; + TypeValidatorRequired? get resultHandlerType; /// Create a copy of ProcessorResult /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$ProcessorResultFailedImplCopyWith> - get copyWith => throw _privateConstructorUsedError; + _$$ProcessorResultFailedImplCopyWith> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/model/riverpod_states/progress_state.freezed.dart b/lib/model/riverpod_states/progress_state.freezed.dart index ae8d6890e..5b339d284 100644 --- a/lib/model/riverpod_states/progress_state.freezed.dart +++ b/lib/model/riverpod_states/progress_state.freezed.dart @@ -60,22 +60,18 @@ mixin _$ProgressState { /// Create a copy of ProgressState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $ProgressStateCopyWith get copyWith => - throw _privateConstructorUsedError; + $ProgressStateCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc abstract class $ProgressStateCopyWith<$Res> { - factory $ProgressStateCopyWith( - ProgressState value, $Res Function(ProgressState) then) = - _$ProgressStateCopyWithImpl<$Res, ProgressState>; + factory $ProgressStateCopyWith(ProgressState value, $Res Function(ProgressState) then) = _$ProgressStateCopyWithImpl<$Res, ProgressState>; @useResult $Res call({int max, int value}); } /// @nodoc -class _$ProgressStateCopyWithImpl<$Res, $Val extends ProgressState> - implements $ProgressStateCopyWith<$Res> { +class _$ProgressStateCopyWithImpl<$Res, $Val extends ProgressState> implements $ProgressStateCopyWith<$Res> { _$ProgressStateCopyWithImpl(this._value, this._then); // ignore: unused_field @@ -105,11 +101,8 @@ class _$ProgressStateCopyWithImpl<$Res, $Val extends ProgressState> } /// @nodoc -abstract class _$$ProgressStateUninitializedImplCopyWith<$Res> - implements $ProgressStateCopyWith<$Res> { - factory _$$ProgressStateUninitializedImplCopyWith( - _$ProgressStateUninitializedImpl value, - $Res Function(_$ProgressStateUninitializedImpl) then) = +abstract class _$$ProgressStateUninitializedImplCopyWith<$Res> implements $ProgressStateCopyWith<$Res> { + factory _$$ProgressStateUninitializedImplCopyWith(_$ProgressStateUninitializedImpl value, $Res Function(_$ProgressStateUninitializedImpl) then) = __$$ProgressStateUninitializedImplCopyWithImpl<$Res>; @override @useResult @@ -117,12 +110,9 @@ abstract class _$$ProgressStateUninitializedImplCopyWith<$Res> } /// @nodoc -class __$$ProgressStateUninitializedImplCopyWithImpl<$Res> - extends _$ProgressStateCopyWithImpl<$Res, _$ProgressStateUninitializedImpl> +class __$$ProgressStateUninitializedImplCopyWithImpl<$Res> extends _$ProgressStateCopyWithImpl<$Res, _$ProgressStateUninitializedImpl> implements _$$ProgressStateUninitializedImplCopyWith<$Res> { - __$$ProgressStateUninitializedImplCopyWithImpl( - _$ProgressStateUninitializedImpl _value, - $Res Function(_$ProgressStateUninitializedImpl) _then) + __$$ProgressStateUninitializedImplCopyWithImpl(_$ProgressStateUninitializedImpl _value, $Res Function(_$ProgressStateUninitializedImpl) _then) : super(_value, _then); /// Create a copy of ProgressState @@ -149,8 +139,7 @@ class __$$ProgressStateUninitializedImplCopyWithImpl<$Res> /// @nodoc class _$ProgressStateUninitializedImpl extends ProgressStateUninitialized { - const _$ProgressStateUninitializedImpl({this.max = 0, this.value = 0}) - : super._(); + const _$ProgressStateUninitializedImpl({this.max = 0, this.value = 0}) : super._(); @override @JsonKey() @@ -181,9 +170,8 @@ class _$ProgressStateUninitializedImpl extends ProgressStateUninitialized { @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$ProgressStateUninitializedImplCopyWith<_$ProgressStateUninitializedImpl> - get copyWith => __$$ProgressStateUninitializedImplCopyWithImpl< - _$ProgressStateUninitializedImpl>(this, _$identity); + _$$ProgressStateUninitializedImplCopyWith<_$ProgressStateUninitializedImpl> get copyWith => + __$$ProgressStateUninitializedImplCopyWithImpl<_$ProgressStateUninitializedImpl>(this, _$identity); @override @optionalTypeArgs @@ -249,8 +237,7 @@ class _$ProgressStateUninitializedImpl extends ProgressStateUninitialized { } abstract class ProgressStateUninitialized extends ProgressState { - const factory ProgressStateUninitialized({final int max, final int value}) = - _$ProgressStateUninitializedImpl; + const factory ProgressStateUninitialized({final int max, final int value}) = _$ProgressStateUninitializedImpl; const ProgressStateUninitialized._() : super._(); @override @@ -262,28 +249,20 @@ abstract class ProgressStateUninitialized extends ProgressState { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$ProgressStateUninitializedImplCopyWith<_$ProgressStateUninitializedImpl> - get copyWith => throw _privateConstructorUsedError; + _$$ProgressStateUninitializedImplCopyWith<_$ProgressStateUninitializedImpl> get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class _$$ProgressStateImplCopyWith<$Res> - implements $ProgressStateCopyWith<$Res> { - factory _$$ProgressStateImplCopyWith( - _$ProgressStateImpl value, $Res Function(_$ProgressStateImpl) then) = - __$$ProgressStateImplCopyWithImpl<$Res>; +abstract class _$$ProgressStateImplCopyWith<$Res> implements $ProgressStateCopyWith<$Res> { + factory _$$ProgressStateImplCopyWith(_$ProgressStateImpl value, $Res Function(_$ProgressStateImpl) then) = __$$ProgressStateImplCopyWithImpl<$Res>; @override @useResult $Res call({int max, int value}); } /// @nodoc -class __$$ProgressStateImplCopyWithImpl<$Res> - extends _$ProgressStateCopyWithImpl<$Res, _$ProgressStateImpl> - implements _$$ProgressStateImplCopyWith<$Res> { - __$$ProgressStateImplCopyWithImpl( - _$ProgressStateImpl _value, $Res Function(_$ProgressStateImpl) _then) - : super(_value, _then); +class __$$ProgressStateImplCopyWithImpl<$Res> extends _$ProgressStateCopyWithImpl<$Res, _$ProgressStateImpl> implements _$$ProgressStateImplCopyWith<$Res> { + __$$ProgressStateImplCopyWithImpl(_$ProgressStateImpl _value, $Res Function(_$ProgressStateImpl) _then) : super(_value, _then); /// Create a copy of ProgressState /// with the given fields replaced by the non-null parameter values. @@ -311,7 +290,7 @@ class __$$ProgressStateImplCopyWithImpl<$Res> class _$ProgressStateImpl extends _ProgressState { const _$ProgressStateImpl({required this.max, required this.value}) : assert(max >= 0, 'max must be greater than or equal to 0'), - assert(value >= max, 'value must be less than or equal to max'), + assert(value <= max, 'value must be less than or equal to max'), super._(); @override @@ -341,8 +320,7 @@ class _$ProgressStateImpl extends _ProgressState { @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$ProgressStateImplCopyWith<_$ProgressStateImpl> get copyWith => - __$$ProgressStateImplCopyWithImpl<_$ProgressStateImpl>(this, _$identity); + _$$ProgressStateImplCopyWith<_$ProgressStateImpl> get copyWith => __$$ProgressStateImplCopyWithImpl<_$ProgressStateImpl>(this, _$identity); @override @optionalTypeArgs @@ -408,8 +386,7 @@ class _$ProgressStateImpl extends _ProgressState { } abstract class _ProgressState extends ProgressState { - const factory _ProgressState( - {required final int max, required final int value}) = _$ProgressStateImpl; + const factory _ProgressState({required final int max, required final int value}) = _$ProgressStateImpl; const _ProgressState._() : super._(); @override @@ -421,6 +398,5 @@ abstract class _ProgressState extends ProgressState { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$ProgressStateImplCopyWith<_$ProgressStateImpl> get copyWith => - throw _privateConstructorUsedError; + _$$ProgressStateImplCopyWith<_$ProgressStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index 76181abdc..dabc5c59a 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -178,66 +178,42 @@ sealed class TokenContainer with _$TokenContainer { class TokenTemplate with _$TokenTemplate { TokenTemplate._(); factory TokenTemplate({ - required Map data, + required Map data, }) = _TokenTemplate; List get keys => data.keys.toList(); List get values => data.values.toList(); - String? get id { - final id = data[URI_ID]; - if (id is! String?) { - Logger.error('TokenTemplate id is not a string'); - } - return id; - } - - Uint8List? get secret { - final secret = data[URI_SECRET]; - if (secret is! Uint8List?) { - Logger.error('TokenTemplate secret is not a string'); - } - return secret; - } + String? get serial => validateOptional( + value: data[OTP_AUTH_SERIAL], + validator: const TypeValidatorOptional(), + name: OTP_AUTH_SERIAL, + ); - String? get serial { - final serial = data[URI_SERIAL]; - if (serial is! String?) { - Logger.error('TokenTemplate id is not a string'); - } - return serial; - } + String? get type => validateOptional( + value: data[OTP_AUTH_TYPE], + validator: const TypeValidatorOptional(), + name: OTP_AUTH_TYPE, + ); - String? get type { - final type = data[URI_TYPE]; - if (type is! String?) { - Logger.error('TokenTemplate type is not a string'); - } - return type; - } + List get otpValues => validate( + value: data[OTP_AUTH_OTP_VALUES], + validator: const TypeValidatorRequired>(), + name: OTP_AUTH_OTP_VALUES, + ); - String? get containerSerial { - final containerSerial = data[URI_CONTAINER_SERIAL]; - if (containerSerial is! String?) { - Logger.error('TokenTemplate containerSerial is not a string'); - } - return containerSerial; - } + String? get containerSerial => validateOptional( + value: data[CONTAINER_SERIAL], + validator: const TypeValidatorOptional(), + name: CONTAINER_SERIAL, + ); @override operator ==(Object other) { - if (other is! TokenTemplate) { - return false; - } - if (data.length != other.data.length) { - return false; - } + if (other is! TokenTemplate) return false; + if (data.length != other.data.length) return false; for (var key in data.keys) { - if (data[key] is Iterable) { - if (!const IterableEquality().equals(data[key], other.data[key])) { - return false; - } - } else if (data[key].toString() != other.data[key].toString()) { + if (data[key].toString() != other.data[key].toString()) { return false; } } @@ -246,16 +222,16 @@ class TokenTemplate with _$TokenTemplate { factory TokenTemplate.fromJson(Map json) => _$TokenTemplateFromJson(json); - String get label => data[URI_LABEL] ?? 'No label'; - - Token toToken(TokenContainer container) => Token.fromUriMap(data).copyWith( - containerSerial: () => container.serial, + Token toToken(TokenContainer container) => Token.fromOtpAuthMap( + data, origin: TokenOriginData( appName: '${container.serverName} ${container.serial}', data: data.toString(), source: TokenOriginSourceType.container, isPrivacyIdeaToken: true, ), + ).copyWith( + containerSerial: () => container.serial, ); /// Adds all key/value pairs of [other] to this map. @@ -270,29 +246,19 @@ class TokenTemplate with _$TokenTemplate { /// planets.addAll({5: 'Jupiter', 6: 'Saturn'}); /// print(planets); // {1: Mercury, 2: Earth, 5: Jupiter, 6: Saturn} /// ``` - TokenTemplate copyAddAll(Map addData) { - final newData = Map.from(data)..addAll(addData); + TokenTemplate copyAddAll(Map addData) { + final newData = Map.from(data)..addAll(addData); return TokenTemplate(data: newData); } @override int get hashCode => Object.hashAllUnordered(data.keys.map((key) => '$key:${data[key]}')); - bool isSameTokenAs(TokenTemplate other) => id == other.id || serial == other.serial || const IterableEquality().equals(secret, other.secret); + bool isSameTokenAs(TokenTemplate other) => serial == other.serial || (otpValues.isNotEmpty && const IterableEquality().equals(otpValues, other.otpValues)); bool hasSameValuesAs(TokenTemplate serverTokenTemplate) { Logger.debug('serverTokenTemplate.keys: ${serverTokenTemplate.keys}', name: 'TokenTemplate#hasSameValuesAs'); for (var key in serverTokenTemplate.keys) { - if (data[key] is Iterable && serverTokenTemplate.data[key] is Iterable) { - if (!const IterableEquality().equals(data[key], serverTokenTemplate.data[key])) { - Logger.debug( - 'TokenTemplate has different values for key "$key": ${data[key]} != ${serverTokenTemplate.data[key]}', - name: 'TokenTemplate#hasSameValuesAs', - ); - return false; - } - continue; - } if (data[key] != serverTokenTemplate.data[key]) { Logger.debug('TokenTemplate has different values for key "$key": ${data[key]} != ${serverTokenTemplate.data[key]}', name: 'TokenTemplate#hasSameValuesAs'); @@ -300,7 +266,7 @@ class TokenTemplate with _$TokenTemplate { } } Logger.debug( - 'AppTokenTemplate serial $serial/id $id has same values as serverTokenTemplate serial ${serverTokenTemplate.serial}/id ${serverTokenTemplate.id}', + 'AppTokenTemplate serial $serial/otp ($otpValues) has same values as serverTokenTemplate serial ${serverTokenTemplate.serial}/id ${serverTokenTemplate.otpValues}', name: 'TokenTemplate#hasSameValuesAs', ); return true; diff --git a/lib/model/token_container.freezed.dart b/lib/model/token_container.freezed.dart index b52533a13..4672c8780 100644 --- a/lib/model/token_container.freezed.dart +++ b/lib/model/token_container.freezed.dart @@ -30,8 +30,7 @@ TokenContainer _$TokenContainerFromJson(Map json) { return TokenContainerError.fromJson(json); default: - throw CheckedFromJsonException(json, 'runtimeType', 'TokenContainer', - 'Invalid union type "${json['runtimeType']}"!'); + throw CheckedFromJsonException(json, 'runtimeType', 'TokenContainer', 'Invalid union type "${json['runtimeType']}"!'); } } @@ -42,175 +41,71 @@ mixin _$TokenContainer { DateTime? get lastSyncAt => throw _privateConstructorUsedError; String get serial => throw _privateConstructorUsedError; String get description => throw _privateConstructorUsedError; - List get syncedTokenTemplates => - throw _privateConstructorUsedError; - List get localTokenTemplates => - throw _privateConstructorUsedError; + List get syncedTokenTemplates => throw _privateConstructorUsedError; + List get localTokenTemplates => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) uninitialized, - required TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) synced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt) modified, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message) unsynced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message) notFound, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error) error, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult? Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, required TResult orElse(), }) => @@ -253,15 +148,12 @@ mixin _$TokenContainer { /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $TokenContainerCopyWith get copyWith => - throw _privateConstructorUsedError; + $TokenContainerCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc abstract class $TokenContainerCopyWith<$Res> { - factory $TokenContainerCopyWith( - TokenContainer value, $Res Function(TokenContainer) then) = - _$TokenContainerCopyWithImpl<$Res, TokenContainer>; + factory $TokenContainerCopyWith(TokenContainer value, $Res Function(TokenContainer) then) = _$TokenContainerCopyWithImpl<$Res, TokenContainer>; @useResult $Res call( {String serverName, @@ -273,8 +165,7 @@ abstract class $TokenContainerCopyWith<$Res> { } /// @nodoc -class _$TokenContainerCopyWithImpl<$Res, $Val extends TokenContainer> - implements $TokenContainerCopyWith<$Res> { +class _$TokenContainerCopyWithImpl<$Res, $Val extends TokenContainer> implements $TokenContainerCopyWith<$Res> { _$TokenContainerCopyWithImpl(this._value, this._then); // ignore: unused_field @@ -324,11 +215,8 @@ class _$TokenContainerCopyWithImpl<$Res, $Val extends TokenContainer> } /// @nodoc -abstract class _$$TokenContainerUninitializedImplCopyWith<$Res> - implements $TokenContainerCopyWith<$Res> { - factory _$$TokenContainerUninitializedImplCopyWith( - _$TokenContainerUninitializedImpl value, - $Res Function(_$TokenContainerUninitializedImpl) then) = +abstract class _$$TokenContainerUninitializedImplCopyWith<$Res> implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerUninitializedImplCopyWith(_$TokenContainerUninitializedImpl value, $Res Function(_$TokenContainerUninitializedImpl) then) = __$$TokenContainerUninitializedImplCopyWithImpl<$Res>; @override @useResult @@ -342,13 +230,9 @@ abstract class _$$TokenContainerUninitializedImplCopyWith<$Res> } /// @nodoc -class __$$TokenContainerUninitializedImplCopyWithImpl<$Res> - extends _$TokenContainerCopyWithImpl<$Res, - _$TokenContainerUninitializedImpl> +class __$$TokenContainerUninitializedImplCopyWithImpl<$Res> extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerUninitializedImpl> implements _$$TokenContainerUninitializedImplCopyWith<$Res> { - __$$TokenContainerUninitializedImplCopyWithImpl( - _$TokenContainerUninitializedImpl _value, - $Res Function(_$TokenContainerUninitializedImpl) _then) + __$$TokenContainerUninitializedImplCopyWithImpl(_$TokenContainerUninitializedImpl _value, $Res Function(_$TokenContainerUninitializedImpl) _then) : super(_value, _then); /// Create a copy of TokenContainer @@ -394,8 +278,7 @@ class __$$TokenContainerUninitializedImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized - with DiagnosticableTreeMixin { +class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized with DiagnosticableTreeMixin { const _$TokenContainerUninitializedImpl( {this.serverName = 'PrivacyIDEA', this.lastSyncAt, @@ -409,9 +292,7 @@ class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized $type = $type ?? 'uninitialized', super._(); - factory _$TokenContainerUninitializedImpl.fromJson( - Map json) => - _$$TokenContainerUninitializedImplFromJson(json); + factory _$TokenContainerUninitializedImpl.fromJson(Map json) => _$$TokenContainerUninitializedImplFromJson(json); // Base fields @override @@ -429,8 +310,7 @@ class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized @override @JsonKey() List get syncedTokenTemplates { - if (_syncedTokenTemplates is EqualUnmodifiableListView) - return _syncedTokenTemplates; + if (_syncedTokenTemplates is EqualUnmodifiableListView) return _syncedTokenTemplates; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_syncedTokenTemplates); } @@ -439,8 +319,7 @@ class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized @override @JsonKey() List get localTokenTemplates { - if (_localTokenTemplates is EqualUnmodifiableListView) - return _localTokenTemplates; + if (_localTokenTemplates is EqualUnmodifiableListView) return _localTokenTemplates; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_localTokenTemplates); } @@ -471,28 +350,17 @@ class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized return identical(this, other) || (other.runtimeType == runtimeType && other is _$TokenContainerUninitializedImpl && - (identical(other.serverName, serverName) || - other.serverName == serverName) && - (identical(other.lastSyncAt, lastSyncAt) || - other.lastSyncAt == lastSyncAt) && + (identical(other.serverName, serverName) || other.serverName == serverName) && + (identical(other.lastSyncAt, lastSyncAt) || other.lastSyncAt == lastSyncAt) && (identical(other.serial, serial) || other.serial == serial) && - (identical(other.description, description) || - other.description == description) && - const DeepCollectionEquality() - .equals(other._syncedTokenTemplates, _syncedTokenTemplates) && - const DeepCollectionEquality() - .equals(other._localTokenTemplates, _localTokenTemplates)); + (identical(other.description, description) || other.description == description) && + const DeepCollectionEquality().equals(other._syncedTokenTemplates, _syncedTokenTemplates) && + const DeepCollectionEquality().equals(other._localTokenTemplates, _localTokenTemplates)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, - serverName, - lastSyncAt, - serial, - description, - const DeepCollectionEquality().hash(_syncedTokenTemplates), + int get hashCode => Object.hash(runtimeType, serverName, lastSyncAt, serial, description, const DeepCollectionEquality().hash(_syncedTokenTemplates), const DeepCollectionEquality().hash(_localTokenTemplates)); /// Create a copy of TokenContainer @@ -500,190 +368,84 @@ class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$TokenContainerUninitializedImplCopyWith<_$TokenContainerUninitializedImpl> - get copyWith => __$$TokenContainerUninitializedImplCopyWithImpl< - _$TokenContainerUninitializedImpl>(this, _$identity); + _$$TokenContainerUninitializedImplCopyWith<_$TokenContainerUninitializedImpl> get copyWith => + __$$TokenContainerUninitializedImplCopyWithImpl<_$TokenContainerUninitializedImpl>(this, _$identity); @override @optionalTypeArgs TResult when({ - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) uninitialized, - required TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) synced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt) modified, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message) unsynced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message) notFound, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error) error, }) { - return uninitialized(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates); + return uninitialized(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult? Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, }) { - return uninitialized?.call(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates); + return uninitialized?.call(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, required TResult orElse(), }) { if (uninitialized != null) { - return uninitialized(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates); + return uninitialized(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates); } return orElse(); } @@ -741,17 +503,15 @@ class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized abstract class TokenContainerUninitialized extends TokenContainer { const factory TokenContainerUninitialized( - {final String serverName, - final DateTime? lastSyncAt, - final String serial, - final String description, - final List syncedTokenTemplates, - final List localTokenTemplates}) = - _$TokenContainerUninitializedImpl; + {final String serverName, + final DateTime? lastSyncAt, + final String serial, + final String description, + final List syncedTokenTemplates, + final List localTokenTemplates}) = _$TokenContainerUninitializedImpl; const TokenContainerUninitialized._() : super._(); - factory TokenContainerUninitialized.fromJson(Map json) = - _$TokenContainerUninitializedImpl.fromJson; + factory TokenContainerUninitialized.fromJson(Map json) = _$TokenContainerUninitializedImpl.fromJson; // Base fields @override @@ -771,15 +531,12 @@ abstract class TokenContainerUninitialized extends TokenContainer { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenContainerUninitializedImplCopyWith<_$TokenContainerUninitializedImpl> - get copyWith => throw _privateConstructorUsedError; + _$$TokenContainerUninitializedImplCopyWith<_$TokenContainerUninitializedImpl> get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class _$$TokenContainerSyncedImplCopyWith<$Res> - implements $TokenContainerCopyWith<$Res> { - factory _$$TokenContainerSyncedImplCopyWith(_$TokenContainerSyncedImpl value, - $Res Function(_$TokenContainerSyncedImpl) then) = +abstract class _$$TokenContainerSyncedImplCopyWith<$Res> implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerSyncedImplCopyWith(_$TokenContainerSyncedImpl value, $Res Function(_$TokenContainerSyncedImpl) then) = __$$TokenContainerSyncedImplCopyWithImpl<$Res>; @override @useResult @@ -793,12 +550,9 @@ abstract class _$$TokenContainerSyncedImplCopyWith<$Res> } /// @nodoc -class __$$TokenContainerSyncedImplCopyWithImpl<$Res> - extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerSyncedImpl> +class __$$TokenContainerSyncedImplCopyWithImpl<$Res> extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerSyncedImpl> implements _$$TokenContainerSyncedImplCopyWith<$Res> { - __$$TokenContainerSyncedImplCopyWithImpl(_$TokenContainerSyncedImpl _value, - $Res Function(_$TokenContainerSyncedImpl) _then) - : super(_value, _then); + __$$TokenContainerSyncedImplCopyWithImpl(_$TokenContainerSyncedImpl _value, $Res Function(_$TokenContainerSyncedImpl) _then) : super(_value, _then); /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @@ -843,8 +597,7 @@ class __$$TokenContainerSyncedImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$TokenContainerSyncedImpl extends TokenContainerSynced - with DiagnosticableTreeMixin { +class _$TokenContainerSyncedImpl extends TokenContainerSynced with DiagnosticableTreeMixin { const _$TokenContainerSyncedImpl( {this.serverName = 'PrivacyIDEA', required this.lastSyncAt, @@ -858,8 +611,7 @@ class _$TokenContainerSyncedImpl extends TokenContainerSynced $type = $type ?? 'synced', super._(); - factory _$TokenContainerSyncedImpl.fromJson(Map json) => - _$$TokenContainerSyncedImplFromJson(json); + factory _$TokenContainerSyncedImpl.fromJson(Map json) => _$$TokenContainerSyncedImplFromJson(json); // Base fields @override @@ -874,8 +626,7 @@ class _$TokenContainerSyncedImpl extends TokenContainerSynced final List _syncedTokenTemplates; @override List get syncedTokenTemplates { - if (_syncedTokenTemplates is EqualUnmodifiableListView) - return _syncedTokenTemplates; + if (_syncedTokenTemplates is EqualUnmodifiableListView) return _syncedTokenTemplates; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_syncedTokenTemplates); } @@ -883,8 +634,7 @@ class _$TokenContainerSyncedImpl extends TokenContainerSynced final List _localTokenTemplates; @override List get localTokenTemplates { - if (_localTokenTemplates is EqualUnmodifiableListView) - return _localTokenTemplates; + if (_localTokenTemplates is EqualUnmodifiableListView) return _localTokenTemplates; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_localTokenTemplates); } @@ -915,28 +665,17 @@ class _$TokenContainerSyncedImpl extends TokenContainerSynced return identical(this, other) || (other.runtimeType == runtimeType && other is _$TokenContainerSyncedImpl && - (identical(other.serverName, serverName) || - other.serverName == serverName) && - (identical(other.lastSyncAt, lastSyncAt) || - other.lastSyncAt == lastSyncAt) && + (identical(other.serverName, serverName) || other.serverName == serverName) && + (identical(other.lastSyncAt, lastSyncAt) || other.lastSyncAt == lastSyncAt) && (identical(other.serial, serial) || other.serial == serial) && - (identical(other.description, description) || - other.description == description) && - const DeepCollectionEquality() - .equals(other._syncedTokenTemplates, _syncedTokenTemplates) && - const DeepCollectionEquality() - .equals(other._localTokenTemplates, _localTokenTemplates)); + (identical(other.description, description) || other.description == description) && + const DeepCollectionEquality().equals(other._syncedTokenTemplates, _syncedTokenTemplates) && + const DeepCollectionEquality().equals(other._localTokenTemplates, _localTokenTemplates)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, - serverName, - lastSyncAt, - serial, - description, - const DeepCollectionEquality().hash(_syncedTokenTemplates), + int get hashCode => Object.hash(runtimeType, serverName, lastSyncAt, serial, description, const DeepCollectionEquality().hash(_syncedTokenTemplates), const DeepCollectionEquality().hash(_localTokenTemplates)); /// Create a copy of TokenContainer @@ -944,191 +683,84 @@ class _$TokenContainerSyncedImpl extends TokenContainerSynced @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$TokenContainerSyncedImplCopyWith<_$TokenContainerSyncedImpl> - get copyWith => - __$$TokenContainerSyncedImplCopyWithImpl<_$TokenContainerSyncedImpl>( - this, _$identity); + _$$TokenContainerSyncedImplCopyWith<_$TokenContainerSyncedImpl> get copyWith => + __$$TokenContainerSyncedImplCopyWithImpl<_$TokenContainerSyncedImpl>(this, _$identity); @override @optionalTypeArgs TResult when({ - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) uninitialized, - required TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) synced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt) modified, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message) unsynced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message) notFound, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error) error, }) { - return synced(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates); + return synced(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult? Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, }) { - return synced?.call(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates); + return synced?.call(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, required TResult orElse(), }) { if (synced != null) { - return synced(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates); + return synced(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates); } return orElse(); } @@ -1186,17 +818,15 @@ class _$TokenContainerSyncedImpl extends TokenContainerSynced abstract class TokenContainerSynced extends TokenContainer { const factory TokenContainerSynced( - {final String serverName, - required final DateTime lastSyncAt, - required final String serial, - required final String description, - required final List syncedTokenTemplates, - required final List localTokenTemplates}) = - _$TokenContainerSyncedImpl; + {final String serverName, + required final DateTime lastSyncAt, + required final String serial, + required final String description, + required final List syncedTokenTemplates, + required final List localTokenTemplates}) = _$TokenContainerSyncedImpl; const TokenContainerSynced._() : super._(); - factory TokenContainerSynced.fromJson(Map json) = - _$TokenContainerSyncedImpl.fromJson; + factory TokenContainerSynced.fromJson(Map json) = _$TokenContainerSyncedImpl.fromJson; // Base fields @override @@ -1216,16 +846,12 @@ abstract class TokenContainerSynced extends TokenContainer { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenContainerSyncedImplCopyWith<_$TokenContainerSyncedImpl> - get copyWith => throw _privateConstructorUsedError; + _$$TokenContainerSyncedImplCopyWith<_$TokenContainerSyncedImpl> get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class _$$TokenContainerModifiedImplCopyWith<$Res> - implements $TokenContainerCopyWith<$Res> { - factory _$$TokenContainerModifiedImplCopyWith( - _$TokenContainerModifiedImpl value, - $Res Function(_$TokenContainerModifiedImpl) then) = +abstract class _$$TokenContainerModifiedImplCopyWith<$Res> implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerModifiedImplCopyWith(_$TokenContainerModifiedImpl value, $Res Function(_$TokenContainerModifiedImpl) then) = __$$TokenContainerModifiedImplCopyWithImpl<$Res>; @override @useResult @@ -1240,13 +866,9 @@ abstract class _$$TokenContainerModifiedImplCopyWith<$Res> } /// @nodoc -class __$$TokenContainerModifiedImplCopyWithImpl<$Res> - extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerModifiedImpl> +class __$$TokenContainerModifiedImplCopyWithImpl<$Res> extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerModifiedImpl> implements _$$TokenContainerModifiedImplCopyWith<$Res> { - __$$TokenContainerModifiedImplCopyWithImpl( - _$TokenContainerModifiedImpl _value, - $Res Function(_$TokenContainerModifiedImpl) _then) - : super(_value, _then); + __$$TokenContainerModifiedImplCopyWithImpl(_$TokenContainerModifiedImpl _value, $Res Function(_$TokenContainerModifiedImpl) _then) : super(_value, _then); /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @@ -1296,8 +918,7 @@ class __$$TokenContainerModifiedImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$TokenContainerModifiedImpl extends TokenContainerModified - with DiagnosticableTreeMixin { +class _$TokenContainerModifiedImpl extends TokenContainerModified with DiagnosticableTreeMixin { const _$TokenContainerModifiedImpl( {this.serverName = 'PrivacyIDEA', this.lastSyncAt, @@ -1312,8 +933,7 @@ class _$TokenContainerModifiedImpl extends TokenContainerModified $type = $type ?? 'modified', super._(); - factory _$TokenContainerModifiedImpl.fromJson(Map json) => - _$$TokenContainerModifiedImplFromJson(json); + factory _$TokenContainerModifiedImpl.fromJson(Map json) => _$$TokenContainerModifiedImplFromJson(json); // Base fields @override @@ -1328,8 +948,7 @@ class _$TokenContainerModifiedImpl extends TokenContainerModified final List _syncedTokenTemplates; @override List get syncedTokenTemplates { - if (_syncedTokenTemplates is EqualUnmodifiableListView) - return _syncedTokenTemplates; + if (_syncedTokenTemplates is EqualUnmodifiableListView) return _syncedTokenTemplates; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_syncedTokenTemplates); } @@ -1337,8 +956,7 @@ class _$TokenContainerModifiedImpl extends TokenContainerModified final List _localTokenTemplates; @override List get localTokenTemplates { - if (_localTokenTemplates is EqualUnmodifiableListView) - return _localTokenTemplates; + if (_localTokenTemplates is EqualUnmodifiableListView) return _localTokenTemplates; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_localTokenTemplates); } @@ -1374,222 +992,103 @@ class _$TokenContainerModifiedImpl extends TokenContainerModified return identical(this, other) || (other.runtimeType == runtimeType && other is _$TokenContainerModifiedImpl && - (identical(other.serverName, serverName) || - other.serverName == serverName) && - (identical(other.lastSyncAt, lastSyncAt) || - other.lastSyncAt == lastSyncAt) && + (identical(other.serverName, serverName) || other.serverName == serverName) && + (identical(other.lastSyncAt, lastSyncAt) || other.lastSyncAt == lastSyncAt) && (identical(other.serial, serial) || other.serial == serial) && - (identical(other.description, description) || - other.description == description) && - const DeepCollectionEquality() - .equals(other._syncedTokenTemplates, _syncedTokenTemplates) && - const DeepCollectionEquality() - .equals(other._localTokenTemplates, _localTokenTemplates) && - (identical(other.lastModifiedAt, lastModifiedAt) || - other.lastModifiedAt == lastModifiedAt)); + (identical(other.description, description) || other.description == description) && + const DeepCollectionEquality().equals(other._syncedTokenTemplates, _syncedTokenTemplates) && + const DeepCollectionEquality().equals(other._localTokenTemplates, _localTokenTemplates) && + (identical(other.lastModifiedAt, lastModifiedAt) || other.lastModifiedAt == lastModifiedAt)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, - serverName, - lastSyncAt, - serial, - description, - const DeepCollectionEquality().hash(_syncedTokenTemplates), - const DeepCollectionEquality().hash(_localTokenTemplates), - lastModifiedAt); + int get hashCode => Object.hash(runtimeType, serverName, lastSyncAt, serial, description, const DeepCollectionEquality().hash(_syncedTokenTemplates), + const DeepCollectionEquality().hash(_localTokenTemplates), lastModifiedAt); /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$TokenContainerModifiedImplCopyWith<_$TokenContainerModifiedImpl> - get copyWith => __$$TokenContainerModifiedImplCopyWithImpl< - _$TokenContainerModifiedImpl>(this, _$identity); + _$$TokenContainerModifiedImplCopyWith<_$TokenContainerModifiedImpl> get copyWith => + __$$TokenContainerModifiedImplCopyWithImpl<_$TokenContainerModifiedImpl>(this, _$identity); @override @optionalTypeArgs TResult when({ - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) uninitialized, - required TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) synced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt) modified, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message) unsynced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message) notFound, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error) error, }) { - return modified(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates, lastModifiedAt); + return modified(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, lastModifiedAt); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult? Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, }) { - return modified?.call(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates, lastModifiedAt); + return modified?.call(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, lastModifiedAt); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, required TResult orElse(), }) { if (modified != null) { - return modified(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates, lastModifiedAt); + return modified(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, lastModifiedAt); } return orElse(); } @@ -1656,8 +1155,7 @@ abstract class TokenContainerModified extends TokenContainer { required final DateTime lastModifiedAt}) = _$TokenContainerModifiedImpl; const TokenContainerModified._() : super._(); - factory TokenContainerModified.fromJson(Map json) = - _$TokenContainerModifiedImpl.fromJson; + factory TokenContainerModified.fromJson(Map json) = _$TokenContainerModifiedImpl.fromJson; // Base fields @override @@ -1678,16 +1176,12 @@ abstract class TokenContainerModified extends TokenContainer { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenContainerModifiedImplCopyWith<_$TokenContainerModifiedImpl> - get copyWith => throw _privateConstructorUsedError; + _$$TokenContainerModifiedImplCopyWith<_$TokenContainerModifiedImpl> get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class _$$TokenContainerUnsyncedImplCopyWith<$Res> - implements $TokenContainerCopyWith<$Res> { - factory _$$TokenContainerUnsyncedImplCopyWith( - _$TokenContainerUnsyncedImpl value, - $Res Function(_$TokenContainerUnsyncedImpl) then) = +abstract class _$$TokenContainerUnsyncedImplCopyWith<$Res> implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerUnsyncedImplCopyWith(_$TokenContainerUnsyncedImpl value, $Res Function(_$TokenContainerUnsyncedImpl) then) = __$$TokenContainerUnsyncedImplCopyWithImpl<$Res>; @override @useResult @@ -1702,13 +1196,9 @@ abstract class _$$TokenContainerUnsyncedImplCopyWith<$Res> } /// @nodoc -class __$$TokenContainerUnsyncedImplCopyWithImpl<$Res> - extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerUnsyncedImpl> +class __$$TokenContainerUnsyncedImplCopyWithImpl<$Res> extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerUnsyncedImpl> implements _$$TokenContainerUnsyncedImplCopyWith<$Res> { - __$$TokenContainerUnsyncedImplCopyWithImpl( - _$TokenContainerUnsyncedImpl _value, - $Res Function(_$TokenContainerUnsyncedImpl) _then) - : super(_value, _then); + __$$TokenContainerUnsyncedImplCopyWithImpl(_$TokenContainerUnsyncedImpl _value, $Res Function(_$TokenContainerUnsyncedImpl) _then) : super(_value, _then); /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @@ -1758,8 +1248,7 @@ class __$$TokenContainerUnsyncedImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$TokenContainerUnsyncedImpl extends TokenContainerUnsynced - with DiagnosticableTreeMixin { +class _$TokenContainerUnsyncedImpl extends TokenContainerUnsynced with DiagnosticableTreeMixin { const _$TokenContainerUnsyncedImpl( {this.serverName = 'PrivacyIDEA', this.lastSyncAt, @@ -1774,8 +1263,7 @@ class _$TokenContainerUnsyncedImpl extends TokenContainerUnsynced $type = $type ?? 'unsynced', super._(); - factory _$TokenContainerUnsyncedImpl.fromJson(Map json) => - _$$TokenContainerUnsyncedImplFromJson(json); + factory _$TokenContainerUnsyncedImpl.fromJson(Map json) => _$$TokenContainerUnsyncedImplFromJson(json); // Base fields @override @@ -1790,8 +1278,7 @@ class _$TokenContainerUnsyncedImpl extends TokenContainerUnsynced final List _syncedTokenTemplates; @override List get syncedTokenTemplates { - if (_syncedTokenTemplates is EqualUnmodifiableListView) - return _syncedTokenTemplates; + if (_syncedTokenTemplates is EqualUnmodifiableListView) return _syncedTokenTemplates; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_syncedTokenTemplates); } @@ -1799,8 +1286,7 @@ class _$TokenContainerUnsyncedImpl extends TokenContainerUnsynced final List _localTokenTemplates; @override List get localTokenTemplates { - if (_localTokenTemplates is EqualUnmodifiableListView) - return _localTokenTemplates; + if (_localTokenTemplates is EqualUnmodifiableListView) return _localTokenTemplates; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_localTokenTemplates); } @@ -1836,221 +1322,103 @@ class _$TokenContainerUnsyncedImpl extends TokenContainerUnsynced return identical(this, other) || (other.runtimeType == runtimeType && other is _$TokenContainerUnsyncedImpl && - (identical(other.serverName, serverName) || - other.serverName == serverName) && - (identical(other.lastSyncAt, lastSyncAt) || - other.lastSyncAt == lastSyncAt) && + (identical(other.serverName, serverName) || other.serverName == serverName) && + (identical(other.lastSyncAt, lastSyncAt) || other.lastSyncAt == lastSyncAt) && (identical(other.serial, serial) || other.serial == serial) && - (identical(other.description, description) || - other.description == description) && - const DeepCollectionEquality() - .equals(other._syncedTokenTemplates, _syncedTokenTemplates) && - const DeepCollectionEquality() - .equals(other._localTokenTemplates, _localTokenTemplates) && + (identical(other.description, description) || other.description == description) && + const DeepCollectionEquality().equals(other._syncedTokenTemplates, _syncedTokenTemplates) && + const DeepCollectionEquality().equals(other._localTokenTemplates, _localTokenTemplates) && (identical(other.message, message) || other.message == message)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, - serverName, - lastSyncAt, - serial, - description, - const DeepCollectionEquality().hash(_syncedTokenTemplates), - const DeepCollectionEquality().hash(_localTokenTemplates), - message); + int get hashCode => Object.hash(runtimeType, serverName, lastSyncAt, serial, description, const DeepCollectionEquality().hash(_syncedTokenTemplates), + const DeepCollectionEquality().hash(_localTokenTemplates), message); /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$TokenContainerUnsyncedImplCopyWith<_$TokenContainerUnsyncedImpl> - get copyWith => __$$TokenContainerUnsyncedImplCopyWithImpl< - _$TokenContainerUnsyncedImpl>(this, _$identity); + _$$TokenContainerUnsyncedImplCopyWith<_$TokenContainerUnsyncedImpl> get copyWith => + __$$TokenContainerUnsyncedImplCopyWithImpl<_$TokenContainerUnsyncedImpl>(this, _$identity); @override @optionalTypeArgs TResult when({ - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) uninitialized, - required TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) synced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt) modified, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message) unsynced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message) notFound, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error) error, }) { - return unsynced(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates, message); + return unsynced(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, message); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult? Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, }) { - return unsynced?.call(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates, message); + return unsynced?.call(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, message); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, required TResult orElse(), }) { if (unsynced != null) { - return unsynced(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates, message); + return unsynced(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, message); } return orElse(); } @@ -2117,8 +1485,7 @@ abstract class TokenContainerUnsynced extends TokenContainer { final String? message}) = _$TokenContainerUnsyncedImpl; const TokenContainerUnsynced._() : super._(); - factory TokenContainerUnsynced.fromJson(Map json) = - _$TokenContainerUnsyncedImpl.fromJson; + factory TokenContainerUnsynced.fromJson(Map json) = _$TokenContainerUnsyncedImpl.fromJson; // Base fields @override @@ -2139,16 +1506,12 @@ abstract class TokenContainerUnsynced extends TokenContainer { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenContainerUnsyncedImplCopyWith<_$TokenContainerUnsyncedImpl> - get copyWith => throw _privateConstructorUsedError; + _$$TokenContainerUnsyncedImplCopyWith<_$TokenContainerUnsyncedImpl> get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class _$$TokenContainerNotFoundImplCopyWith<$Res> - implements $TokenContainerCopyWith<$Res> { - factory _$$TokenContainerNotFoundImplCopyWith( - _$TokenContainerNotFoundImpl value, - $Res Function(_$TokenContainerNotFoundImpl) then) = +abstract class _$$TokenContainerNotFoundImplCopyWith<$Res> implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerNotFoundImplCopyWith(_$TokenContainerNotFoundImpl value, $Res Function(_$TokenContainerNotFoundImpl) then) = __$$TokenContainerNotFoundImplCopyWithImpl<$Res>; @override @useResult @@ -2163,13 +1526,9 @@ abstract class _$$TokenContainerNotFoundImplCopyWith<$Res> } /// @nodoc -class __$$TokenContainerNotFoundImplCopyWithImpl<$Res> - extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerNotFoundImpl> +class __$$TokenContainerNotFoundImplCopyWithImpl<$Res> extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerNotFoundImpl> implements _$$TokenContainerNotFoundImplCopyWith<$Res> { - __$$TokenContainerNotFoundImplCopyWithImpl( - _$TokenContainerNotFoundImpl _value, - $Res Function(_$TokenContainerNotFoundImpl) _then) - : super(_value, _then); + __$$TokenContainerNotFoundImplCopyWithImpl(_$TokenContainerNotFoundImpl _value, $Res Function(_$TokenContainerNotFoundImpl) _then) : super(_value, _then); /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @@ -2219,8 +1578,7 @@ class __$$TokenContainerNotFoundImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$TokenContainerNotFoundImpl extends TokenContainerNotFound - with DiagnosticableTreeMixin { +class _$TokenContainerNotFoundImpl extends TokenContainerNotFound with DiagnosticableTreeMixin { const _$TokenContainerNotFoundImpl( {this.serverName = 'PrivacyIDEA', this.lastSyncAt, @@ -2235,8 +1593,7 @@ class _$TokenContainerNotFoundImpl extends TokenContainerNotFound $type = $type ?? 'notFound', super._(); - factory _$TokenContainerNotFoundImpl.fromJson(Map json) => - _$$TokenContainerNotFoundImplFromJson(json); + factory _$TokenContainerNotFoundImpl.fromJson(Map json) => _$$TokenContainerNotFoundImplFromJson(json); // Base fields @override @@ -2251,8 +1608,7 @@ class _$TokenContainerNotFoundImpl extends TokenContainerNotFound final List _syncedTokenTemplates; @override List get syncedTokenTemplates { - if (_syncedTokenTemplates is EqualUnmodifiableListView) - return _syncedTokenTemplates; + if (_syncedTokenTemplates is EqualUnmodifiableListView) return _syncedTokenTemplates; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_syncedTokenTemplates); } @@ -2260,8 +1616,7 @@ class _$TokenContainerNotFoundImpl extends TokenContainerNotFound final List _localTokenTemplates; @override List get localTokenTemplates { - if (_localTokenTemplates is EqualUnmodifiableListView) - return _localTokenTemplates; + if (_localTokenTemplates is EqualUnmodifiableListView) return _localTokenTemplates; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_localTokenTemplates); } @@ -2297,221 +1652,103 @@ class _$TokenContainerNotFoundImpl extends TokenContainerNotFound return identical(this, other) || (other.runtimeType == runtimeType && other is _$TokenContainerNotFoundImpl && - (identical(other.serverName, serverName) || - other.serverName == serverName) && - (identical(other.lastSyncAt, lastSyncAt) || - other.lastSyncAt == lastSyncAt) && + (identical(other.serverName, serverName) || other.serverName == serverName) && + (identical(other.lastSyncAt, lastSyncAt) || other.lastSyncAt == lastSyncAt) && (identical(other.serial, serial) || other.serial == serial) && - (identical(other.description, description) || - other.description == description) && - const DeepCollectionEquality() - .equals(other._syncedTokenTemplates, _syncedTokenTemplates) && - const DeepCollectionEquality() - .equals(other._localTokenTemplates, _localTokenTemplates) && + (identical(other.description, description) || other.description == description) && + const DeepCollectionEquality().equals(other._syncedTokenTemplates, _syncedTokenTemplates) && + const DeepCollectionEquality().equals(other._localTokenTemplates, _localTokenTemplates) && (identical(other.message, message) || other.message == message)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, - serverName, - lastSyncAt, - serial, - description, - const DeepCollectionEquality().hash(_syncedTokenTemplates), - const DeepCollectionEquality().hash(_localTokenTemplates), - message); + int get hashCode => Object.hash(runtimeType, serverName, lastSyncAt, serial, description, const DeepCollectionEquality().hash(_syncedTokenTemplates), + const DeepCollectionEquality().hash(_localTokenTemplates), message); /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$TokenContainerNotFoundImplCopyWith<_$TokenContainerNotFoundImpl> - get copyWith => __$$TokenContainerNotFoundImplCopyWithImpl< - _$TokenContainerNotFoundImpl>(this, _$identity); + _$$TokenContainerNotFoundImplCopyWith<_$TokenContainerNotFoundImpl> get copyWith => + __$$TokenContainerNotFoundImplCopyWithImpl<_$TokenContainerNotFoundImpl>(this, _$identity); @override @optionalTypeArgs TResult when({ - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) uninitialized, - required TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) synced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt) modified, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message) unsynced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message) notFound, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error) error, }) { - return notFound(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates, message); + return notFound(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, message); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult? Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, }) { - return notFound?.call(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates, message); + return notFound?.call(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, message); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, required TResult orElse(), }) { if (notFound != null) { - return notFound(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates, message); + return notFound(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, message); } return orElse(); } @@ -2578,8 +1815,7 @@ abstract class TokenContainerNotFound extends TokenContainer { required final String message}) = _$TokenContainerNotFoundImpl; const TokenContainerNotFound._() : super._(); - factory TokenContainerNotFound.fromJson(Map json) = - _$TokenContainerNotFoundImpl.fromJson; + factory TokenContainerNotFound.fromJson(Map json) = _$TokenContainerNotFoundImpl.fromJson; // Base fields @override @@ -2600,15 +1836,12 @@ abstract class TokenContainerNotFound extends TokenContainer { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenContainerNotFoundImplCopyWith<_$TokenContainerNotFoundImpl> - get copyWith => throw _privateConstructorUsedError; + _$$TokenContainerNotFoundImplCopyWith<_$TokenContainerNotFoundImpl> get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class _$$TokenContainerErrorImplCopyWith<$Res> - implements $TokenContainerCopyWith<$Res> { - factory _$$TokenContainerErrorImplCopyWith(_$TokenContainerErrorImpl value, - $Res Function(_$TokenContainerErrorImpl) then) = +abstract class _$$TokenContainerErrorImplCopyWith<$Res> implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerErrorImplCopyWith(_$TokenContainerErrorImpl value, $Res Function(_$TokenContainerErrorImpl) then) = __$$TokenContainerErrorImplCopyWithImpl<$Res>; @override @useResult @@ -2623,12 +1856,9 @@ abstract class _$$TokenContainerErrorImplCopyWith<$Res> } /// @nodoc -class __$$TokenContainerErrorImplCopyWithImpl<$Res> - extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerErrorImpl> +class __$$TokenContainerErrorImplCopyWithImpl<$Res> extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerErrorImpl> implements _$$TokenContainerErrorImplCopyWith<$Res> { - __$$TokenContainerErrorImplCopyWithImpl(_$TokenContainerErrorImpl _value, - $Res Function(_$TokenContainerErrorImpl) _then) - : super(_value, _then); + __$$TokenContainerErrorImplCopyWithImpl(_$TokenContainerErrorImpl _value, $Res Function(_$TokenContainerErrorImpl) _then) : super(_value, _then); /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @@ -2678,8 +1908,7 @@ class __$$TokenContainerErrorImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$TokenContainerErrorImpl extends TokenContainerError - with DiagnosticableTreeMixin { +class _$TokenContainerErrorImpl extends TokenContainerError with DiagnosticableTreeMixin { const _$TokenContainerErrorImpl( {this.serverName = 'PrivacyIDEA', this.lastSyncAt, @@ -2694,8 +1923,7 @@ class _$TokenContainerErrorImpl extends TokenContainerError $type = $type ?? 'error', super._(); - factory _$TokenContainerErrorImpl.fromJson(Map json) => - _$$TokenContainerErrorImplFromJson(json); + factory _$TokenContainerErrorImpl.fromJson(Map json) => _$$TokenContainerErrorImplFromJson(json); // Base fields @override @@ -2713,8 +1941,7 @@ class _$TokenContainerErrorImpl extends TokenContainerError @override @JsonKey() List get syncedTokenTemplates { - if (_syncedTokenTemplates is EqualUnmodifiableListView) - return _syncedTokenTemplates; + if (_syncedTokenTemplates is EqualUnmodifiableListView) return _syncedTokenTemplates; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_syncedTokenTemplates); } @@ -2723,8 +1950,7 @@ class _$TokenContainerErrorImpl extends TokenContainerError @override @JsonKey() List get localTokenTemplates { - if (_localTokenTemplates is EqualUnmodifiableListView) - return _localTokenTemplates; + if (_localTokenTemplates is EqualUnmodifiableListView) return _localTokenTemplates; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_localTokenTemplates); } @@ -2760,31 +1986,19 @@ class _$TokenContainerErrorImpl extends TokenContainerError return identical(this, other) || (other.runtimeType == runtimeType && other is _$TokenContainerErrorImpl && - (identical(other.serverName, serverName) || - other.serverName == serverName) && - (identical(other.lastSyncAt, lastSyncAt) || - other.lastSyncAt == lastSyncAt) && + (identical(other.serverName, serverName) || other.serverName == serverName) && + (identical(other.lastSyncAt, lastSyncAt) || other.lastSyncAt == lastSyncAt) && (identical(other.serial, serial) || other.serial == serial) && - (identical(other.description, description) || - other.description == description) && - const DeepCollectionEquality() - .equals(other._syncedTokenTemplates, _syncedTokenTemplates) && - const DeepCollectionEquality() - .equals(other._localTokenTemplates, _localTokenTemplates) && + (identical(other.description, description) || other.description == description) && + const DeepCollectionEquality().equals(other._syncedTokenTemplates, _syncedTokenTemplates) && + const DeepCollectionEquality().equals(other._localTokenTemplates, _localTokenTemplates) && const DeepCollectionEquality().equals(other.error, error)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, - serverName, - lastSyncAt, - serial, - description, - const DeepCollectionEquality().hash(_syncedTokenTemplates), - const DeepCollectionEquality().hash(_localTokenTemplates), - const DeepCollectionEquality().hash(error)); + int get hashCode => Object.hash(runtimeType, serverName, lastSyncAt, serial, description, const DeepCollectionEquality().hash(_syncedTokenTemplates), + const DeepCollectionEquality().hash(_localTokenTemplates), const DeepCollectionEquality().hash(error)); /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @@ -2792,189 +2006,83 @@ class _$TokenContainerErrorImpl extends TokenContainerError @override @pragma('vm:prefer-inline') _$$TokenContainerErrorImplCopyWith<_$TokenContainerErrorImpl> get copyWith => - __$$TokenContainerErrorImplCopyWithImpl<_$TokenContainerErrorImpl>( - this, _$identity); + __$$TokenContainerErrorImplCopyWithImpl<_$TokenContainerErrorImpl>(this, _$identity); @override @optionalTypeArgs TResult when({ - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) uninitialized, - required TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates) synced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt) modified, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message) unsynced, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message) notFound, - required TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error) + required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error) error, }) { - return error(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates, this.error); + return error(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, this.error); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult? Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult? Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, }) { - return error?.call(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates, this.error); + return error?.call(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, this.error); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? uninitialized, - TResult Function( - String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, + TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, List localTokenTemplates)? synced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, DateTime lastModifiedAt)? modified, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String? message)? unsynced, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, String message)? notFound, - TResult Function( - String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error)? + TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, + List localTokenTemplates, dynamic error)? error, required TResult orElse(), }) { if (error != null) { - return error(serverName, lastSyncAt, serial, description, - syncedTokenTemplates, localTokenTemplates, this.error); + return error(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, this.error); } return orElse(); } @@ -3041,8 +2149,7 @@ abstract class TokenContainerError extends TokenContainer { required final dynamic error}) = _$TokenContainerErrorImpl; const TokenContainerError._() : super._(); - factory TokenContainerError.fromJson(Map json) = - _$TokenContainerErrorImpl.fromJson; + factory TokenContainerError.fromJson(Map json) = _$TokenContainerErrorImpl.fromJson; // Base fields @override @@ -3063,8 +2170,7 @@ abstract class TokenContainerError extends TokenContainer { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenContainerErrorImplCopyWith<_$TokenContainerErrorImpl> get copyWith => - throw _privateConstructorUsedError; + _$$TokenContainerErrorImplCopyWith<_$TokenContainerErrorImpl> get copyWith => throw _privateConstructorUsedError; } TokenTemplate _$TokenTemplateFromJson(Map json) { @@ -3073,7 +2179,7 @@ TokenTemplate _$TokenTemplateFromJson(Map json) { /// @nodoc mixin _$TokenTemplate { - Map get data => throw _privateConstructorUsedError; + Map get data => throw _privateConstructorUsedError; /// Serializes this TokenTemplate to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -3081,22 +2187,18 @@ mixin _$TokenTemplate { /// Create a copy of TokenTemplate /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $TokenTemplateCopyWith get copyWith => - throw _privateConstructorUsedError; + $TokenTemplateCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc abstract class $TokenTemplateCopyWith<$Res> { - factory $TokenTemplateCopyWith( - TokenTemplate value, $Res Function(TokenTemplate) then) = - _$TokenTemplateCopyWithImpl<$Res, TokenTemplate>; + factory $TokenTemplateCopyWith(TokenTemplate value, $Res Function(TokenTemplate) then) = _$TokenTemplateCopyWithImpl<$Res, TokenTemplate>; @useResult $Res call({Map data}); } /// @nodoc -class _$TokenTemplateCopyWithImpl<$Res, $Val extends TokenTemplate> - implements $TokenTemplateCopyWith<$Res> { +class _$TokenTemplateCopyWithImpl<$Res, $Val extends TokenTemplate> implements $TokenTemplateCopyWith<$Res> { _$TokenTemplateCopyWithImpl(this._value, this._then); // ignore: unused_field @@ -3121,23 +2223,16 @@ class _$TokenTemplateCopyWithImpl<$Res, $Val extends TokenTemplate> } /// @nodoc -abstract class _$$TokenTemplateImplCopyWith<$Res> - implements $TokenTemplateCopyWith<$Res> { - factory _$$TokenTemplateImplCopyWith( - _$TokenTemplateImpl value, $Res Function(_$TokenTemplateImpl) then) = - __$$TokenTemplateImplCopyWithImpl<$Res>; +abstract class _$$TokenTemplateImplCopyWith<$Res> implements $TokenTemplateCopyWith<$Res> { + factory _$$TokenTemplateImplCopyWith(_$TokenTemplateImpl value, $Res Function(_$TokenTemplateImpl) then) = __$$TokenTemplateImplCopyWithImpl<$Res>; @override @useResult $Res call({Map data}); } /// @nodoc -class __$$TokenTemplateImplCopyWithImpl<$Res> - extends _$TokenTemplateCopyWithImpl<$Res, _$TokenTemplateImpl> - implements _$$TokenTemplateImplCopyWith<$Res> { - __$$TokenTemplateImplCopyWithImpl( - _$TokenTemplateImpl _value, $Res Function(_$TokenTemplateImpl) _then) - : super(_value, _then); +class __$$TokenTemplateImplCopyWithImpl<$Res> extends _$TokenTemplateCopyWithImpl<$Res, _$TokenTemplateImpl> implements _$$TokenTemplateImplCopyWith<$Res> { + __$$TokenTemplateImplCopyWithImpl(_$TokenTemplateImpl _value, $Res Function(_$TokenTemplateImpl) _then) : super(_value, _then); /// Create a copy of TokenTemplate /// with the given fields replaced by the non-null parameter values. @@ -3150,7 +2245,7 @@ class __$$TokenTemplateImplCopyWithImpl<$Res> data: null == data ? _value._data : data // ignore: cast_nullable_to_non_nullable - as Map, + as Map, )); } } @@ -3158,16 +2253,15 @@ class __$$TokenTemplateImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() class _$TokenTemplateImpl extends _TokenTemplate with DiagnosticableTreeMixin { - _$TokenTemplateImpl({required final Map data}) + _$TokenTemplateImpl({required final Map data}) : _data = data, super._(); - factory _$TokenTemplateImpl.fromJson(Map json) => - _$$TokenTemplateImplFromJson(json); + factory _$TokenTemplateImpl.fromJson(Map json) => _$$TokenTemplateImplFromJson(json); - final Map _data; + final Map _data; @override - Map get data { + Map get data { if (_data is EqualUnmodifiableMapView) return _data; // ignore: implicit_dynamic_type return EqualUnmodifiableMapView(_data); @@ -3191,8 +2285,7 @@ class _$TokenTemplateImpl extends _TokenTemplate with DiagnosticableTreeMixin { @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$TokenTemplateImplCopyWith<_$TokenTemplateImpl> get copyWith => - __$$TokenTemplateImplCopyWithImpl<_$TokenTemplateImpl>(this, _$identity); + _$$TokenTemplateImplCopyWith<_$TokenTemplateImpl> get copyWith => __$$TokenTemplateImplCopyWithImpl<_$TokenTemplateImpl>(this, _$identity); @override Map toJson() { @@ -3203,20 +2296,17 @@ class _$TokenTemplateImpl extends _TokenTemplate with DiagnosticableTreeMixin { } abstract class _TokenTemplate extends TokenTemplate { - factory _TokenTemplate({required final Map data}) = - _$TokenTemplateImpl; + factory _TokenTemplate({required final Map data}) = _$TokenTemplateImpl; _TokenTemplate._() : super._(); - factory _TokenTemplate.fromJson(Map json) = - _$TokenTemplateImpl.fromJson; + factory _TokenTemplate.fromJson(Map json) = _$TokenTemplateImpl.fromJson; @override - Map get data; + Map get data; /// Create a copy of TokenTemplate /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenTemplateImplCopyWith<_$TokenTemplateImpl> get copyWith => - throw _privateConstructorUsedError; + _$$TokenTemplateImplCopyWith<_$TokenTemplateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/model/token_container.g.dart b/lib/model/token_container.g.dart index 659d300a1..f90406830 100644 --- a/lib/model/token_container.g.dart +++ b/lib/model/token_container.g.dart @@ -6,29 +6,18 @@ part of 'token_container.dart'; // JsonSerializableGenerator // ************************************************************************** -_$TokenContainerUninitializedImpl _$$TokenContainerUninitializedImplFromJson( - Map json) => - _$TokenContainerUninitializedImpl( +_$TokenContainerUninitializedImpl _$$TokenContainerUninitializedImplFromJson(Map json) => _$TokenContainerUninitializedImpl( serverName: json['serverName'] as String? ?? 'PrivacyIDEA', - lastSyncAt: json['lastSyncAt'] == null - ? null - : DateTime.parse(json['lastSyncAt'] as String), + lastSyncAt: json['lastSyncAt'] == null ? null : DateTime.parse(json['lastSyncAt'] as String), serial: json['serial'] as String? ?? 'none', description: json['description'] as String? ?? 'Uninitialized', - syncedTokenTemplates: (json['syncedTokenTemplates'] as List?) - ?.map((e) => TokenTemplate.fromJson(e as Map)) - .toList() ?? - const [], - localTokenTemplates: (json['localTokenTemplates'] as List?) - ?.map((e) => TokenTemplate.fromJson(e as Map)) - .toList() ?? - const [], + syncedTokenTemplates: + (json['syncedTokenTemplates'] as List?)?.map((e) => TokenTemplate.fromJson(e as Map)).toList() ?? const [], + localTokenTemplates: (json['localTokenTemplates'] as List?)?.map((e) => TokenTemplate.fromJson(e as Map)).toList() ?? const [], $type: json['runtimeType'] as String?, ); -Map _$$TokenContainerUninitializedImplToJson( - _$TokenContainerUninitializedImpl instance) => - { +Map _$$TokenContainerUninitializedImplToJson(_$TokenContainerUninitializedImpl instance) => { 'serverName': instance.serverName, 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), 'serial': instance.serial, @@ -38,25 +27,17 @@ Map _$$TokenContainerUninitializedImplToJson( 'runtimeType': instance.$type, }; -_$TokenContainerSyncedImpl _$$TokenContainerSyncedImplFromJson( - Map json) => - _$TokenContainerSyncedImpl( +_$TokenContainerSyncedImpl _$$TokenContainerSyncedImplFromJson(Map json) => _$TokenContainerSyncedImpl( serverName: json['serverName'] as String? ?? 'PrivacyIDEA', lastSyncAt: DateTime.parse(json['lastSyncAt'] as String), serial: json['serial'] as String, description: json['description'] as String, - syncedTokenTemplates: (json['syncedTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - localTokenTemplates: (json['localTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), + syncedTokenTemplates: (json['syncedTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), + localTokenTemplates: (json['localTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), $type: json['runtimeType'] as String?, ); -Map _$$TokenContainerSyncedImplToJson( - _$TokenContainerSyncedImpl instance) => - { +Map _$$TokenContainerSyncedImplToJson(_$TokenContainerSyncedImpl instance) => { 'serverName': instance.serverName, 'lastSyncAt': instance.lastSyncAt.toIso8601String(), 'serial': instance.serial, @@ -66,28 +47,18 @@ Map _$$TokenContainerSyncedImplToJson( 'runtimeType': instance.$type, }; -_$TokenContainerModifiedImpl _$$TokenContainerModifiedImplFromJson( - Map json) => - _$TokenContainerModifiedImpl( +_$TokenContainerModifiedImpl _$$TokenContainerModifiedImplFromJson(Map json) => _$TokenContainerModifiedImpl( serverName: json['serverName'] as String? ?? 'PrivacyIDEA', - lastSyncAt: json['lastSyncAt'] == null - ? null - : DateTime.parse(json['lastSyncAt'] as String), + lastSyncAt: json['lastSyncAt'] == null ? null : DateTime.parse(json['lastSyncAt'] as String), serial: json['serial'] as String, description: json['description'] as String, - syncedTokenTemplates: (json['syncedTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - localTokenTemplates: (json['localTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), + syncedTokenTemplates: (json['syncedTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), + localTokenTemplates: (json['localTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), lastModifiedAt: DateTime.parse(json['lastModifiedAt'] as String), $type: json['runtimeType'] as String?, ); -Map _$$TokenContainerModifiedImplToJson( - _$TokenContainerModifiedImpl instance) => - { +Map _$$TokenContainerModifiedImplToJson(_$TokenContainerModifiedImpl instance) => { 'serverName': instance.serverName, 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), 'serial': instance.serial, @@ -98,28 +69,18 @@ Map _$$TokenContainerModifiedImplToJson( 'runtimeType': instance.$type, }; -_$TokenContainerUnsyncedImpl _$$TokenContainerUnsyncedImplFromJson( - Map json) => - _$TokenContainerUnsyncedImpl( +_$TokenContainerUnsyncedImpl _$$TokenContainerUnsyncedImplFromJson(Map json) => _$TokenContainerUnsyncedImpl( serverName: json['serverName'] as String? ?? 'PrivacyIDEA', - lastSyncAt: json['lastSyncAt'] == null - ? null - : DateTime.parse(json['lastSyncAt'] as String), + lastSyncAt: json['lastSyncAt'] == null ? null : DateTime.parse(json['lastSyncAt'] as String), serial: json['serial'] as String, description: json['description'] as String, - syncedTokenTemplates: (json['syncedTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - localTokenTemplates: (json['localTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), + syncedTokenTemplates: (json['syncedTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), + localTokenTemplates: (json['localTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), message: json['message'] as String?, $type: json['runtimeType'] as String?, ); -Map _$$TokenContainerUnsyncedImplToJson( - _$TokenContainerUnsyncedImpl instance) => - { +Map _$$TokenContainerUnsyncedImplToJson(_$TokenContainerUnsyncedImpl instance) => { 'serverName': instance.serverName, 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), 'serial': instance.serial, @@ -130,28 +91,18 @@ Map _$$TokenContainerUnsyncedImplToJson( 'runtimeType': instance.$type, }; -_$TokenContainerNotFoundImpl _$$TokenContainerNotFoundImplFromJson( - Map json) => - _$TokenContainerNotFoundImpl( +_$TokenContainerNotFoundImpl _$$TokenContainerNotFoundImplFromJson(Map json) => _$TokenContainerNotFoundImpl( serverName: json['serverName'] as String? ?? 'PrivacyIDEA', - lastSyncAt: json['lastSyncAt'] == null - ? null - : DateTime.parse(json['lastSyncAt'] as String), + lastSyncAt: json['lastSyncAt'] == null ? null : DateTime.parse(json['lastSyncAt'] as String), serial: json['serial'] as String, description: json['description'] as String, - syncedTokenTemplates: (json['syncedTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), - localTokenTemplates: (json['localTokenTemplates'] as List) - .map((e) => TokenTemplate.fromJson(e as Map)) - .toList(), + syncedTokenTemplates: (json['syncedTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), + localTokenTemplates: (json['localTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), message: json['message'] as String, $type: json['runtimeType'] as String?, ); -Map _$$TokenContainerNotFoundImplToJson( - _$TokenContainerNotFoundImpl instance) => - { +Map _$$TokenContainerNotFoundImplToJson(_$TokenContainerNotFoundImpl instance) => { 'serverName': instance.serverName, 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), 'serial': instance.serial, @@ -162,30 +113,19 @@ Map _$$TokenContainerNotFoundImplToJson( 'runtimeType': instance.$type, }; -_$TokenContainerErrorImpl _$$TokenContainerErrorImplFromJson( - Map json) => - _$TokenContainerErrorImpl( +_$TokenContainerErrorImpl _$$TokenContainerErrorImplFromJson(Map json) => _$TokenContainerErrorImpl( serverName: json['serverName'] as String? ?? 'PrivacyIDEA', - lastSyncAt: json['lastSyncAt'] == null - ? null - : DateTime.parse(json['lastSyncAt'] as String), + lastSyncAt: json['lastSyncAt'] == null ? null : DateTime.parse(json['lastSyncAt'] as String), serial: json['serial'] as String? ?? 'none', description: json['description'] as String? ?? 'Error', - syncedTokenTemplates: (json['syncedTokenTemplates'] as List?) - ?.map((e) => TokenTemplate.fromJson(e as Map)) - .toList() ?? - const [], - localTokenTemplates: (json['localTokenTemplates'] as List?) - ?.map((e) => TokenTemplate.fromJson(e as Map)) - .toList() ?? - const [], + syncedTokenTemplates: + (json['syncedTokenTemplates'] as List?)?.map((e) => TokenTemplate.fromJson(e as Map)).toList() ?? const [], + localTokenTemplates: (json['localTokenTemplates'] as List?)?.map((e) => TokenTemplate.fromJson(e as Map)).toList() ?? const [], error: json['error'], $type: json['runtimeType'] as String?, ); -Map _$$TokenContainerErrorImplToJson( - _$TokenContainerErrorImpl instance) => - { +Map _$$TokenContainerErrorImplToJson(_$TokenContainerErrorImpl instance) => { 'serverName': instance.serverName, 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), 'serial': instance.serial, @@ -196,12 +136,10 @@ Map _$$TokenContainerErrorImplToJson( 'runtimeType': instance.$type, }; -_$TokenTemplateImpl _$$TokenTemplateImplFromJson(Map json) => - _$TokenTemplateImpl( - data: json['data'] as Map, +_$TokenTemplateImpl _$$TokenTemplateImplFromJson(Map json) => _$TokenTemplateImpl( + data: json['data'] as Map, ); -Map _$$TokenTemplateImplToJson(_$TokenTemplateImpl instance) => - { +Map _$$TokenTemplateImplToJson(_$TokenTemplateImpl instance) => { 'data': instance.data, }; diff --git a/lib/model/tokens/container_credentials.dart b/lib/model/tokens/container_credentials.dart index aecef6ead..ddd28ee6d 100644 --- a/lib/model/tokens/container_credentials.dart +++ b/lib/model/tokens/container_credentials.dart @@ -20,49 +20,59 @@ * limitations under the License. */ -import 'dart:typed_data'; - import 'package:basic_utils/basic_utils.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/ec_key_algorithm_extension.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; +import 'package:privacyidea_authenticator/utils/type_matchers.dart'; +import '../../utils/ecc_utils.dart'; import '../../utils/logger.dart'; +import '../enums/container_finalization_state.dart'; +import '../enums/ec_key_algorithm.dart'; part 'container_credentials.freezed.dart'; part 'container_credentials.g.dart'; -// issuer=privacyIDEA -// &serial=SMPH00134123 -// &nonce=887197025f5fa59b50f33c15196eb97ee651a5d1 -// &time=2024-08-21T07%3A43%3A07.086670%2B00%3A00 -// &url="http://127.0.0.1:5000/container/register/finalize" -// &key_algorithm=secp384r1 -// &hash_algorithm=SHA256 -// &passphrase=Enter%20your%20passphrase - @Freezed(toStringOverride: false) class ContainerCredential with _$ContainerCredential { static const eccUtils = EccUtils(); const ContainerCredential._(); + + // example: pia://container/SMPH00134123 + // ?issuer=privacyIDEA + // &nonce=887197025f5fa59b50f33c15196eb97ee651a5d1 + // &time=2024-08-21T07%3A43%3A07.086670%2B00%3A00 + // &url=http://127.0.0.1:5000/container/register/initialize + // &serial=SMPH00134123 + // &key_algorithm=secp384r1 + // &hash_algorithm=SHA256 + // &passphrase=Enter%20your%20passphrase factory ContainerCredential.fromUriMap(Map uriMap) { - validateMap(uriMap, { - URI_ISSUER: const TypeMatcher(), - URI_NONCE: const TypeMatcher(), - URI_TIMESTAMP: const TypeMatcher(), - URI_FINALIZATION_URL: const TypeMatcher(), - URI_SERIAL: const TypeMatcher(), - URI_KEY_ALGORITHM: const TypeMatcher(), - URI_HASH_ALGORITHM: const TypeMatcher(), - }); + uriMap = validateMap( + map: uriMap, + validators: { + CONTAINER_ISSUER: const TypeValidatorRequired(), + CONTAINER_NONCE: const TypeValidatorRequired(), + CONTAINER_TIMESTAMP: TypeValidatorRequired(transformer: (v) => DateTime.parse(v)), + CONTAINER_FINALIZATION_URL: stringToUrivalidator, + CONTAINER_SERIAL: const TypeValidatorRequired(), + CONTAINER_EC_KEY_ALGORITHM: TypeValidatorRequired(transformer: (v) => EcKeyAlgorithm.values.byCurveName(v)), + CONTAINER_HASH_ALGORITHM: stringToAlgorithmsValidator, + CONTAINER_PASSPHRASE_QUESTION: const TypeValidatorOptional(), + }, + name: 'Container', + ); return ContainerCredential.unfinalized( - issuer: uriMap[URI_ISSUER], - nonce: uriMap[URI_NONCE], - timestamp: uriMap[URI_TIMESTAMP], - finalizationUrl: uriMap[URI_FINALIZATION_URL], - serial: uriMap[URI_SERIAL], - ecKeyAlgorithm: uriMap[URI_KEY_ALGORITHM], - hashAlgorithm: uriMap[URI_HASH_ALGORITHM], + issuer: uriMap[CONTAINER_ISSUER], + nonce: uriMap[CONTAINER_NONCE], + timestamp: uriMap[CONTAINER_TIMESTAMP], + finalizationUrl: uriMap[CONTAINER_FINALIZATION_URL], + serial: uriMap[CONTAINER_SERIAL], + ecKeyAlgorithm: uriMap[CONTAINER_EC_KEY_ALGORITHM], + hashAlgorithm: uriMap[CONTAINER_HASH_ALGORITHM], + passphraseQuestion: uriMap[CONTAINER_PASSPHRASE_QUESTION], ); } @@ -75,7 +85,7 @@ class ContainerCredential with _$ContainerCredential { required EcKeyAlgorithm ecKeyAlgorithm, required Algorithms hashAlgorithm, @Default(ContainerFinalizationState.uninitialized) ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String? publicServerKey, String? publicClientKey, String? privateClientKey, @@ -90,7 +100,7 @@ class ContainerCredential with _$ContainerCredential { required EcKeyAlgorithm ecKeyAlgorithm, required Algorithms hashAlgorithm, @Default(ContainerFinalizationState.finalized) ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, required String publicServerKey, required String publicClientKey, required String privateClientKey, @@ -117,7 +127,7 @@ class ContainerCredential with _$ContainerCredential { serial: serial, ecKeyAlgorithm: ecKeyAlgorithm, hashAlgorithm: hashAlgorithm, - passphrase: passphrase, + passphraseQuestion: passphraseQuestion, finalizationState: ContainerFinalizationState.finalized, publicServerKey: this.publicServerKey ?? eccUtils.serializeECPublicKey(publicServerKey!), publicClientKey: publicClientKey ?? eccUtils.serializeECPublicKey(clientKeyPair!.publicKey), @@ -149,177 +159,11 @@ class ContainerCredential with _$ContainerCredential { 'ecKeyAlgorithm: $ecKeyAlgorithm, ' 'hashAlgorithm: $hashAlgorithm, ' 'finalizationState: $finalizationState, ' - 'passphrase: $passphrase, ' + 'passphraseQuestion: $passphraseQuestion, ' 'publicServerKey: $publicServerKey, ' 'publicClientKey: $publicClientKey)'; } -enum ContainerFinalizationState { - uninitialized, - generatingKeyPair, - generatingKeyPairFailed, - generatingKeyPairCompleted, - sendingPublicKey, - sendingPublicKeyFailed, - sendingPublicKeyCompleted, - parsingResponse, - parsingResponseFailed, - parsingResponseCompleted, - finalized, -} - -/// The following curves are supported: - -enum EcKeyAlgorithm { - brainpoolp160r1, - brainpoolp160t1, - brainpoolp192r1, - brainpoolp192t1, - brainpoolp224r1, - brainpoolp224t1, - brainpoolp256r1, - brainpoolp256t1, - brainpoolp320r1, - brainpoolp320t1, - brainpoolp384r1, - brainpoolp384t1, - brainpoolp512r1, - brainpoolp512t1, - GostR3410_2001_CryptoPro_A, - GostR3410_2001_CryptoPro_B, - GostR3410_2001_CryptoPro_C, - GostR3410_2001_CryptoPro_XchA, - GostR3410_2001_CryptoPro_XchB, - prime192v1, - prime192v2, - prime192v3, - prime239v1, - prime239v2, - prime239v3, - prime256v1, - secp112r1, - secp112r2, - secp128r1, - secp128r2, - secp160k1, - secp160r1, - secp160r2, - secp192k1, - secp192r1, - secp224k1, - secp224r1, - secp256k1, - secp256r1, - secp384r1, - secp521r1, -} - -extension EcKeyAlgorithmList on List { - EcKeyAlgorithm byCurveName(String domainName) => switch (domainName) { - 'brainpoolp160r1' => EcKeyAlgorithm.brainpoolp160r1, - 'brainpoolp160t1' => EcKeyAlgorithm.brainpoolp160t1, - 'brainpoolp192r1' => EcKeyAlgorithm.brainpoolp192r1, - 'brainpoolp192t1' => EcKeyAlgorithm.brainpoolp192t1, - 'brainpoolp224r1' => EcKeyAlgorithm.brainpoolp224r1, - 'brainpoolp224t1' => EcKeyAlgorithm.brainpoolp224t1, - 'brainpoolp256r1' => EcKeyAlgorithm.brainpoolp256r1, - 'brainpoolp256t1' => EcKeyAlgorithm.brainpoolp256t1, - 'brainpoolp320r1' => EcKeyAlgorithm.brainpoolp320r1, - 'brainpoolp320t1' => EcKeyAlgorithm.brainpoolp320t1, - 'brainpoolp384r1' => EcKeyAlgorithm.brainpoolp384r1, - 'brainpoolp384t1' => EcKeyAlgorithm.brainpoolp384t1, - 'brainpoolp512r1' => EcKeyAlgorithm.brainpoolp512r1, - 'brainpoolp512t1' => EcKeyAlgorithm.brainpoolp512t1, - 'GostR3410-2001-CryptoPro-A' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_A, - 'GostR3410-2001-CryptoPro-B' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_B, - 'GostR3410-2001-CryptoPro-C' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_C, - 'GostR3410-2001-CryptoPro-XchA' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchA, - 'GostR3410-2001-CryptoPro-XchB' => EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchB, - 'prime192v1' => EcKeyAlgorithm.prime192v1, - 'prime192v2' => EcKeyAlgorithm.prime192v2, - 'prime192v3' => EcKeyAlgorithm.prime192v3, - 'prime239v1' => EcKeyAlgorithm.prime239v1, - 'prime239v2' => EcKeyAlgorithm.prime239v2, - 'prime239v3' => EcKeyAlgorithm.prime239v3, - 'prime256v1' => EcKeyAlgorithm.prime256v1, - 'secp112r1' => EcKeyAlgorithm.secp112r1, - 'secp112r2' => EcKeyAlgorithm.secp112r2, - 'secp128r1' => EcKeyAlgorithm.secp128r1, - 'secp128r2' => EcKeyAlgorithm.secp128r2, - 'secp160k1' => EcKeyAlgorithm.secp160k1, - 'secp160r1' => EcKeyAlgorithm.secp160r1, - 'secp160r2' => EcKeyAlgorithm.secp160r2, - 'secp192k1' => EcKeyAlgorithm.secp192k1, - 'secp192r1' => EcKeyAlgorithm.secp192r1, - 'secp224k1' => EcKeyAlgorithm.secp224k1, - 'secp224r1' => EcKeyAlgorithm.secp224r1, - 'secp256k1' => EcKeyAlgorithm.secp256k1, - 'secp256r1' => EcKeyAlgorithm.secp256r1, - 'secp384r1' => EcKeyAlgorithm.secp384r1, - 'secp521r1' => EcKeyAlgorithm.secp521r1, - _ => throw ArgumentError('Unknown domain name: $domainName'), - }; -} - -extension EcKeyAlgorithmX on EcKeyAlgorithm { - String get curveName => switch (this) { - EcKeyAlgorithm.brainpoolp160r1 => 'brainpoolp160r1', - EcKeyAlgorithm.brainpoolp160t1 => 'brainpoolp160t1', - EcKeyAlgorithm.brainpoolp192r1 => 'brainpoolp192r1', - EcKeyAlgorithm.brainpoolp192t1 => 'brainpoolp192t1', - EcKeyAlgorithm.brainpoolp224r1 => 'brainpoolp224r1', - EcKeyAlgorithm.brainpoolp224t1 => 'brainpoolp224t1', - EcKeyAlgorithm.brainpoolp256r1 => 'brainpoolp256r1', - EcKeyAlgorithm.brainpoolp256t1 => 'brainpoolp256t1', - EcKeyAlgorithm.brainpoolp320r1 => 'brainpoolp320r1', - EcKeyAlgorithm.brainpoolp320t1 => 'brainpoolp320t1', - EcKeyAlgorithm.brainpoolp384r1 => 'brainpoolp384r1', - EcKeyAlgorithm.brainpoolp384t1 => 'brainpoolp384t1', - EcKeyAlgorithm.brainpoolp512r1 => 'brainpoolp512r1', - EcKeyAlgorithm.brainpoolp512t1 => 'brainpoolp512t1', - EcKeyAlgorithm.GostR3410_2001_CryptoPro_A => 'GostR3410-2001-CryptoPro-A', - EcKeyAlgorithm.GostR3410_2001_CryptoPro_B => 'GostR3410-2001-CryptoPro-B', - EcKeyAlgorithm.GostR3410_2001_CryptoPro_C => 'GostR3410-2001-CryptoPro-C', - EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchA => 'GostR3410-2001-CryptoPro-XchA', - EcKeyAlgorithm.GostR3410_2001_CryptoPro_XchB => 'GostR3410-2001-CryptoPro-XchB', - EcKeyAlgorithm.prime192v1 => 'prime192v1', - EcKeyAlgorithm.prime192v2 => 'prime192v2', - EcKeyAlgorithm.prime192v3 => 'prime192v3', - EcKeyAlgorithm.prime239v1 => 'prime239v1', - EcKeyAlgorithm.prime239v2 => 'prime239v2', - EcKeyAlgorithm.prime239v3 => 'prime239v3', - EcKeyAlgorithm.prime256v1 => 'prime256v1', - EcKeyAlgorithm.secp112r1 => 'secp112r1', - EcKeyAlgorithm.secp112r2 => 'secp112r2', - EcKeyAlgorithm.secp128r1 => 'secp128r1', - EcKeyAlgorithm.secp128r2 => 'secp128r2', - EcKeyAlgorithm.secp160k1 => 'secp160k1', - EcKeyAlgorithm.secp160r1 => 'secp160r1', - EcKeyAlgorithm.secp160r2 => 'secp160r2', - EcKeyAlgorithm.secp192k1 => 'secp192k1', - EcKeyAlgorithm.secp192r1 => 'secp192r1', - EcKeyAlgorithm.secp224k1 => 'secp224k1', - EcKeyAlgorithm.secp224r1 => 'secp224r1', - EcKeyAlgorithm.secp256k1 => 'secp256k1', - EcKeyAlgorithm.secp256r1 => 'secp256r1', - EcKeyAlgorithm.secp384r1 => 'secp384r1', - EcKeyAlgorithm.secp521r1 => 'secp521r1', - }; -} //be99ff65b1c38ae8a7d6caf8799a0cce3749fe0e|2024-08-27 14:30:58.371312Z|http://192.168.0.230:5000/container/register/finalize|SMPH0000D49C //be99ff65b1c38ae8a7d6caf8799a0cce3749fe0e|2024-08-27T14:30:58.371312+00:00|http://192.168.0.230:5000/container/register/finalize|SMPH0000D49C -class EccUtils { - const EccUtils(); - - String serializeECPublicKey(ECPublicKey publicKey) => CryptoUtils.encodeEcPublicKeyToPem(publicKey); - ECPublicKey deserializeECPublicKey(String ecPublicKey) => CryptoUtils.ecPublicKeyFromPem(ecPublicKey); - String serializeECPrivateKey(ECPrivateKey ecPrivateKey) => CryptoUtils.encodeEcPrivateKeyToPem(ecPrivateKey); - ECPrivateKey deserializeECPrivateKey(String ecPrivateKey) => CryptoUtils.ecPrivateKeyFromPem(ecPrivateKey); - - String trySignWithPrivateKey(ECPrivateKey privateKey, String message) { - final ecSignature = CryptoUtils.ecSign(privateKey, Uint8List.fromList(message.codeUnits), algorithmName: 'SHA-256/ECDSA'); - String signatureBase64 = CryptoUtils.ecSignatureToBase64(ecSignature); - return signatureBase64; - } -} diff --git a/lib/model/tokens/container_credentials.freezed.dart b/lib/model/tokens/container_credentials.freezed.dart index 467b42dc9..b89b8afc8 100644 --- a/lib/model/tokens/container_credentials.freezed.dart +++ b/lib/model/tokens/container_credentials.freezed.dart @@ -38,7 +38,7 @@ mixin _$ContainerCredential { Algorithms get hashAlgorithm => throw _privateConstructorUsedError; ContainerFinalizationState get finalizationState => throw _privateConstructorUsedError; - String? get passphrase => throw _privateConstructorUsedError; + String? get passphraseQuestion => throw _privateConstructorUsedError; String? get publicServerKey => throw _privateConstructorUsedError; String? get publicClientKey => throw _privateConstructorUsedError; String? get privateClientKey => throw _privateConstructorUsedError; @@ -53,7 +53,7 @@ mixin _$ContainerCredential { EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String? publicServerKey, String? publicClientKey, String? privateClientKey) @@ -67,7 +67,7 @@ mixin _$ContainerCredential { EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String publicServerKey, String publicClientKey, String privateClientKey) @@ -85,7 +85,7 @@ mixin _$ContainerCredential { EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String? publicServerKey, String? publicClientKey, String? privateClientKey)? @@ -99,7 +99,7 @@ mixin _$ContainerCredential { EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String publicServerKey, String publicClientKey, String privateClientKey)? @@ -117,7 +117,7 @@ mixin _$ContainerCredential { EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String? publicServerKey, String? publicClientKey, String? privateClientKey)? @@ -131,7 +131,7 @@ mixin _$ContainerCredential { EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String publicServerKey, String publicClientKey, String privateClientKey)? @@ -184,7 +184,7 @@ abstract class $ContainerCredentialCopyWith<$Res> { EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String publicServerKey, String publicClientKey, String privateClientKey}); @@ -213,7 +213,7 @@ class _$ContainerCredentialCopyWithImpl<$Res, $Val extends ContainerCredential> Object? ecKeyAlgorithm = null, Object? hashAlgorithm = null, Object? finalizationState = null, - Object? passphrase = freezed, + Object? passphraseQuestion = freezed, Object? publicServerKey = null, Object? publicClientKey = null, Object? privateClientKey = null, @@ -251,9 +251,9 @@ class _$ContainerCredentialCopyWithImpl<$Res, $Val extends ContainerCredential> ? _value.finalizationState : finalizationState // ignore: cast_nullable_to_non_nullable as ContainerFinalizationState, - passphrase: freezed == passphrase - ? _value.passphrase - : passphrase // ignore: cast_nullable_to_non_nullable + passphraseQuestion: freezed == passphraseQuestion + ? _value.passphraseQuestion + : passphraseQuestion // ignore: cast_nullable_to_non_nullable as String?, publicServerKey: null == publicServerKey ? _value.publicServerKey! @@ -289,7 +289,7 @@ abstract class _$$ContainerCredentialUnfinalizedImplCopyWith<$Res> EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String? publicServerKey, String? publicClientKey, String? privateClientKey}); @@ -318,7 +318,7 @@ class __$$ContainerCredentialUnfinalizedImplCopyWithImpl<$Res> Object? ecKeyAlgorithm = null, Object? hashAlgorithm = null, Object? finalizationState = null, - Object? passphrase = freezed, + Object? passphraseQuestion = freezed, Object? publicServerKey = freezed, Object? publicClientKey = freezed, Object? privateClientKey = freezed, @@ -356,9 +356,9 @@ class __$$ContainerCredentialUnfinalizedImplCopyWithImpl<$Res> ? _value.finalizationState : finalizationState // ignore: cast_nullable_to_non_nullable as ContainerFinalizationState, - passphrase: freezed == passphrase - ? _value.passphrase - : passphrase // ignore: cast_nullable_to_non_nullable + passphraseQuestion: freezed == passphraseQuestion + ? _value.passphraseQuestion + : passphraseQuestion // ignore: cast_nullable_to_non_nullable as String?, publicServerKey: freezed == publicServerKey ? _value.publicServerKey @@ -389,7 +389,7 @@ class _$ContainerCredentialUnfinalizedImpl required this.ecKeyAlgorithm, required this.hashAlgorithm, this.finalizationState = ContainerFinalizationState.uninitialized, - this.passphrase, + this.passphraseQuestion, this.publicServerKey, this.publicClientKey, this.privateClientKey, @@ -419,7 +419,7 @@ class _$ContainerCredentialUnfinalizedImpl @JsonKey() final ContainerFinalizationState finalizationState; @override - final String? passphrase; + final String? passphraseQuestion; @override final String? publicServerKey; @override @@ -448,8 +448,8 @@ class _$ContainerCredentialUnfinalizedImpl other.hashAlgorithm == hashAlgorithm) && (identical(other.finalizationState, finalizationState) || other.finalizationState == finalizationState) && - (identical(other.passphrase, passphrase) || - other.passphrase == passphrase) && + (identical(other.passphraseQuestion, passphraseQuestion) || + other.passphraseQuestion == passphraseQuestion) && (identical(other.publicServerKey, publicServerKey) || other.publicServerKey == publicServerKey) && (identical(other.publicClientKey, publicClientKey) || @@ -470,7 +470,7 @@ class _$ContainerCredentialUnfinalizedImpl ecKeyAlgorithm, hashAlgorithm, finalizationState, - passphrase, + passphraseQuestion, publicServerKey, publicClientKey, privateClientKey); @@ -497,7 +497,7 @@ class _$ContainerCredentialUnfinalizedImpl EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String? publicServerKey, String? publicClientKey, String? privateClientKey) @@ -511,7 +511,7 @@ class _$ContainerCredentialUnfinalizedImpl EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String publicServerKey, String publicClientKey, String privateClientKey) @@ -526,7 +526,7 @@ class _$ContainerCredentialUnfinalizedImpl ecKeyAlgorithm, hashAlgorithm, finalizationState, - passphrase, + passphraseQuestion, publicServerKey, publicClientKey, privateClientKey); @@ -544,7 +544,7 @@ class _$ContainerCredentialUnfinalizedImpl EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String? publicServerKey, String? publicClientKey, String? privateClientKey)? @@ -558,7 +558,7 @@ class _$ContainerCredentialUnfinalizedImpl EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String publicServerKey, String publicClientKey, String privateClientKey)? @@ -573,7 +573,7 @@ class _$ContainerCredentialUnfinalizedImpl ecKeyAlgorithm, hashAlgorithm, finalizationState, - passphrase, + passphraseQuestion, publicServerKey, publicClientKey, privateClientKey); @@ -591,7 +591,7 @@ class _$ContainerCredentialUnfinalizedImpl EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String? publicServerKey, String? publicClientKey, String? privateClientKey)? @@ -605,7 +605,7 @@ class _$ContainerCredentialUnfinalizedImpl EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String publicServerKey, String publicClientKey, String privateClientKey)? @@ -622,7 +622,7 @@ class _$ContainerCredentialUnfinalizedImpl ecKeyAlgorithm, hashAlgorithm, finalizationState, - passphrase, + passphraseQuestion, publicServerKey, publicClientKey, privateClientKey); @@ -679,7 +679,7 @@ abstract class ContainerCredentialUnfinalized extends ContainerCredential { required final EcKeyAlgorithm ecKeyAlgorithm, required final Algorithms hashAlgorithm, final ContainerFinalizationState finalizationState, - final String? passphrase, + final String? passphraseQuestion, final String? publicServerKey, final String? publicClientKey, final String? privateClientKey}) = _$ContainerCredentialUnfinalizedImpl; @@ -705,7 +705,7 @@ abstract class ContainerCredentialUnfinalized extends ContainerCredential { @override ContainerFinalizationState get finalizationState; @override - String? get passphrase; + String? get passphraseQuestion; @override String? get publicServerKey; @override @@ -740,7 +740,7 @@ abstract class _$$ContainerCredentialFinalizedImplCopyWith<$Res> EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String publicServerKey, String publicClientKey, String privateClientKey}); @@ -769,7 +769,7 @@ class __$$ContainerCredentialFinalizedImplCopyWithImpl<$Res> Object? ecKeyAlgorithm = null, Object? hashAlgorithm = null, Object? finalizationState = null, - Object? passphrase = freezed, + Object? passphraseQuestion = freezed, Object? publicServerKey = null, Object? publicClientKey = null, Object? privateClientKey = null, @@ -807,9 +807,9 @@ class __$$ContainerCredentialFinalizedImplCopyWithImpl<$Res> ? _value.finalizationState : finalizationState // ignore: cast_nullable_to_non_nullable as ContainerFinalizationState, - passphrase: freezed == passphrase - ? _value.passphrase - : passphrase // ignore: cast_nullable_to_non_nullable + passphraseQuestion: freezed == passphraseQuestion + ? _value.passphraseQuestion + : passphraseQuestion // ignore: cast_nullable_to_non_nullable as String?, publicServerKey: null == publicServerKey ? _value.publicServerKey @@ -839,7 +839,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { required this.ecKeyAlgorithm, required this.hashAlgorithm, this.finalizationState = ContainerFinalizationState.finalized, - this.passphrase, + this.passphraseQuestion, required this.publicServerKey, required this.publicClientKey, required this.privateClientKey, @@ -869,7 +869,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { @JsonKey() final ContainerFinalizationState finalizationState; @override - final String? passphrase; + final String? passphraseQuestion; @override final String publicServerKey; @override @@ -898,8 +898,8 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { other.hashAlgorithm == hashAlgorithm) && (identical(other.finalizationState, finalizationState) || other.finalizationState == finalizationState) && - (identical(other.passphrase, passphrase) || - other.passphrase == passphrase) && + (identical(other.passphraseQuestion, passphraseQuestion) || + other.passphraseQuestion == passphraseQuestion) && (identical(other.publicServerKey, publicServerKey) || other.publicServerKey == publicServerKey) && (identical(other.publicClientKey, publicClientKey) || @@ -920,7 +920,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { ecKeyAlgorithm, hashAlgorithm, finalizationState, - passphrase, + passphraseQuestion, publicServerKey, publicClientKey, privateClientKey); @@ -947,7 +947,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String? publicServerKey, String? publicClientKey, String? privateClientKey) @@ -961,7 +961,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String publicServerKey, String publicClientKey, String privateClientKey) @@ -976,7 +976,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { ecKeyAlgorithm, hashAlgorithm, finalizationState, - passphrase, + passphraseQuestion, publicServerKey, publicClientKey, privateClientKey); @@ -994,7 +994,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String? publicServerKey, String? publicClientKey, String? privateClientKey)? @@ -1008,7 +1008,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String publicServerKey, String publicClientKey, String privateClientKey)? @@ -1023,7 +1023,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { ecKeyAlgorithm, hashAlgorithm, finalizationState, - passphrase, + passphraseQuestion, publicServerKey, publicClientKey, privateClientKey); @@ -1041,7 +1041,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String? publicServerKey, String? publicClientKey, String? privateClientKey)? @@ -1055,7 +1055,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, ContainerFinalizationState finalizationState, - String? passphrase, + String? passphraseQuestion, String publicServerKey, String publicClientKey, String privateClientKey)? @@ -1072,7 +1072,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { ecKeyAlgorithm, hashAlgorithm, finalizationState, - passphrase, + passphraseQuestion, publicServerKey, publicClientKey, privateClientKey); @@ -1129,7 +1129,7 @@ abstract class ContainerCredentialFinalized extends ContainerCredential { required final EcKeyAlgorithm ecKeyAlgorithm, required final Algorithms hashAlgorithm, final ContainerFinalizationState finalizationState, - final String? passphrase, + final String? passphraseQuestion, required final String publicServerKey, required final String publicClientKey, required final String privateClientKey}) = @@ -1156,7 +1156,7 @@ abstract class ContainerCredentialFinalized extends ContainerCredential { @override ContainerFinalizationState get finalizationState; @override - String? get passphrase; + String? get passphraseQuestion; @override String get publicServerKey; @override diff --git a/lib/model/tokens/container_credentials.g.dart b/lib/model/tokens/container_credentials.g.dart index 7ce40697a..c58531d2e 100644 --- a/lib/model/tokens/container_credentials.g.dart +++ b/lib/model/tokens/container_credentials.g.dart @@ -22,7 +22,7 @@ _$ContainerCredentialUnfinalizedImpl _$ContainerFinalizationStateEnumMap, json['finalizationState']) ?? ContainerFinalizationState.uninitialized, - passphrase: json['passphrase'] as String?, + passphraseQuestion: json['passphraseQuestion'] as String?, publicServerKey: json['publicServerKey'] as String?, publicClientKey: json['publicClientKey'] as String?, privateClientKey: json['privateClientKey'] as String?, @@ -41,7 +41,7 @@ Map _$$ContainerCredentialUnfinalizedImplToJson( 'hashAlgorithm': _$AlgorithmsEnumMap[instance.hashAlgorithm]!, 'finalizationState': _$ContainerFinalizationStateEnumMap[instance.finalizationState]!, - 'passphrase': instance.passphrase, + 'passphraseQuestion': instance.passphraseQuestion, 'publicServerKey': instance.publicServerKey, 'publicClientKey': instance.publicClientKey, 'privateClientKey': instance.privateClientKey, @@ -129,7 +129,7 @@ _$ContainerCredentialFinalizedImpl _$$ContainerCredentialFinalizedImplFromJson( finalizationState: $enumDecodeNullable( _$ContainerFinalizationStateEnumMap, json['finalizationState']) ?? ContainerFinalizationState.finalized, - passphrase: json['passphrase'] as String?, + passphraseQuestion: json['passphraseQuestion'] as String?, publicServerKey: json['publicServerKey'] as String, publicClientKey: json['publicClientKey'] as String, privateClientKey: json['privateClientKey'] as String, @@ -148,7 +148,7 @@ Map _$$ContainerCredentialFinalizedImplToJson( 'hashAlgorithm': _$AlgorithmsEnumMap[instance.hashAlgorithm]!, 'finalizationState': _$ContainerFinalizationStateEnumMap[instance.finalizationState]!, - 'passphrase': instance.passphrase, + 'passphraseQuestion': instance.passphraseQuestion, 'publicServerKey': instance.publicServerKey, 'publicClientKey': instance.publicClientKey, 'privateClientKey': instance.privateClientKey, diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index c8de998cf..85dedcdbd 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -17,21 +17,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; +import '../../utils/identifiers.dart'; +import '../../utils/type_matchers.dart'; import '../token_container.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'; @@ -51,8 +48,9 @@ class DayPasswordToken extends OTPToken { required super.algorithm, required super.digits, required super.secret, - super.containerSerial, super.serial, + super.containerSerial, + super.checkedContainers, this.viewMode = DayPasswordTokenViewMode.VALIDFOR, String? type, // just for @JsonSerializable(): type of DayPasswordToken is always TokenTypes.DAYPASSWORD super.tokenImage, @@ -95,6 +93,7 @@ class DayPasswordToken extends OTPToken { String? label, String? issuer, String? Function()? containerSerial, + List? checkedContainers, String? id, Algorithms? algorithm, int? digits, @@ -114,6 +113,7 @@ class DayPasswordToken extends OTPToken { label: label ?? this.label, issuer: issuer ?? this.issuer, containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, + checkedContainers: checkedContainers ?? this.checkedContainers, id: id ?? this.id, type: TokenTypes.DAYPASSWORD.name, algorithm: algorithm ?? this.algorithm, @@ -128,15 +128,19 @@ class DayPasswordToken extends OTPToken { origin: origin ?? this.origin, ); - @override - String get otpValue => algorithm.generateTOTPCodeString( + String otpFromTime(DateTime time) => algorithm.generateTOTPCodeString( secret: secret, - time: DateTime.now(), + time: time, length: digits, interval: period, isGoogle: true, ); + @override + String get otpValue => otpFromTime(DateTime.now()); + @override + String get nextValue => otpFromTime(DateTime.now().add(period)); + Duration get durationSinceLastOTP { final msPassedThisPeriod = (DateTime.now().millisecondsSinceEpoch) % period.inMilliseconds; return Duration(milliseconds: msPassedThisPeriod); @@ -150,84 +154,94 @@ class DayPasswordToken extends OTPToken { } @override - DayPasswordToken copyWithFromTemplate(TokenTemplate template) { - final uriMap = template.data; + DayPasswordToken copyUpdateByTemplate(TokenTemplate template) { + final uriMap = validateMap( + map: template.data, + validators: { + OTP_AUTH_LABEL: const TypeValidatorOptional(), + OTP_AUTH_ISSUER: const TypeValidatorOptional(), + OTP_AUTH_SERIAL: const TypeValidatorOptional(), + OTP_AUTH_ALGORITHM: stringToAlgorithmsValidatorOptional, + OTP_AUTH_DIGITS: stringToIntValidatorOptional, + OTP_AUTH_SECRET_BASE32: base32SecretValidatorOptional, + OTP_AUTH_PERIOD_SECONDS: stringSecondsToDurationvalidatorOptional, + OTP_AUTH_IMAGE: const TypeValidatorOptional(), + OTP_AUTH_PIN: stringToBoolValidatorOptional, + }, + name: 'DayPasswordToken', + ); return copyWith( - label: uriMap[URI_LABEL], - issuer: uriMap[URI_ISSUER], - id: uriMap[URI_ID] == String ? uriMap[URI_ID] : const Uuid().v4(), - serial: uriMap[URI_SERIAL], - algorithm: uriMap[URI_ALGORITHM] != null ? Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()) : null, - digits: uriMap[URI_DIGITS], - secret: uriMap[URI_SECRET] != null ? Encodings.base32.encode(uriMap[URI_SECRET]) : null, - period: uriMap[URI_PERIOD] != null ? Duration(seconds: uriMap[URI_PERIOD]) : null, - tokenImage: uriMap[URI_IMAGE], - pin: uriMap[URI_PIN], - isLocked: uriMap[URI_PIN], - origin: uriMap[URI_ORIGIN], + label: uriMap[OTP_AUTH_LABEL] as String?, + issuer: uriMap[OTP_AUTH_ISSUER] as String?, + serial: uriMap[OTP_AUTH_SERIAL] as String?, + algorithm: uriMap[OTP_AUTH_ALGORITHM] as Algorithms?, + digits: uriMap[OTP_AUTH_DIGITS] as int?, + secret: uriMap[OTP_AUTH_SECRET_BASE32] as String?, + period: uriMap[OTP_AUTH_PERIOD_SECONDS] as Duration?, + tokenImage: uriMap[OTP_AUTH_IMAGE] as String?, + pin: uriMap[OTP_AUTH_PIN] as bool?, + isLocked: uriMap[OTP_AUTH_PIN] as bool?, ); } - factory DayPasswordToken.fromUriMap(Map uriMap) { - validateUriMap(uriMap); + factory DayPasswordToken.fromOtpAuthMap(Map uriMap, {required TokenOriginData origin}) { + uriMap = validateMap( + map: uriMap, + validators: { + OTP_AUTH_LABEL: const TypeValidatorRequired(defaultValue: ''), + OTP_AUTH_ISSUER: const TypeValidatorRequired(defaultValue: ''), + OTP_AUTH_SERIAL: const TypeValidatorOptional(), + OTP_AUTH_ALGORITHM: stringToAlgorithmsValidator.withDefault(Algorithms.SHA1), + OTP_AUTH_DIGITS: stringToIntvalidator.withDefault(6), + OTP_AUTH_SECRET_BASE32: base32Secretvalidator, + OTP_AUTH_PERIOD_SECONDS: stringSecondsToDurationvalidator.withDefault(const Duration(hours: 24)), + OTP_AUTH_IMAGE: const TypeValidatorOptional(), + OTP_AUTH_PIN: stringToBoolValidatorOptional, + }, + name: 'DayPasswordToken', + ); return DayPasswordToken( - label: uriMap[URI_LABEL] ?? '', - issuer: uriMap[URI_ISSUER] ?? '', + label: uriMap[OTP_AUTH_LABEL], + issuer: uriMap[OTP_AUTH_ISSUER], id: const Uuid().v4(), - algorithm: Algorithms.values.byName((uriMap[URI_ALGORITHM] as String? ?? 'SHA1').toUpperCase()), - digits: uriMap[URI_DIGITS] ?? 6, - secret: Encodings.base32.encode(uriMap[URI_SECRET]), - period: Duration(seconds: uriMap[URI_PERIOD] ?? 86400), // default 24 hours - tokenImage: uriMap[URI_IMAGE], - pin: uriMap[URI_PIN], - isLocked: uriMap[URI_PIN], - origin: uriMap[URI_ORIGIN], + serial: uriMap[OTP_AUTH_SERIAL], + algorithm: uriMap[OTP_AUTH_ALGORITHM], + digits: uriMap[OTP_AUTH_DIGITS], + secret: uriMap[OTP_AUTH_SECRET_BASE32], + period: uriMap[OTP_AUTH_PERIOD_SECONDS], + tokenImage: uriMap[OTP_AUTH_IMAGE], + pin: uriMap[OTP_AUTH_PIN], + isLocked: uriMap[OTP_AUTH_PIN], + origin: origin, ); } + /// This is used to create a map that typically was created from a uri. + /// ```dart + /// ------------------------- [Token] ------------------------- + /// | OTP_AUTH_SERIAL: serial, (optional) | + /// | OTP_AUTH_TYPE: type, | + /// | OTP_AUTH_LABEL: label, | + /// | OTP_AUTH_ISSUER: issuer, | + /// | OTP_AUTH_PIN: pin, | + /// | OTP_AUTH_IMAGE: tokenImage, (optional) | + /// ----------------------------------------------------------- + /// ----------------------- [OTPToken] ------------------------ + /// | OTP_AUTH_ALGORITHM: algorithm, | + /// | OTP_AUTH_DIGITS: digits, | + /// ----------------------------------------------------------- + /// ------------------- [DayPasswordToken] -------------------- + /// | OTP_AUTH_PERIOD: period, | + /// ----------------------------------------------------------- + /// ``` @override - Map toUriMap() { - return super.toUriMap() + Map toOtpAuthMap({String? containerSerial}) { + return super.toOtpAuthMap() ..addAll({ - URI_PERIOD: period.inSeconds, + OTP_AUTH_PERIOD_SECONDS: period.inSeconds.toString(), }); } - /// Validates the uriMap for the required fields throws [LocalizedArgumentError] if a field is missing or invalid. - static void validateUriMap(Map uriMap) { - if (uriMap[URI_SECRET] is! Uint8List) { - throw LocalizedArgumentError( - localizedMessage: ((localizations, value, name) => localizations.secretIsRequired), - unlocalizedMessage: 'Secret is required and must be a Uint8List', - invalidValue: uriMap[URI_SECRET], - name: URI_SECRET, - ); - } - 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', - invalidValue: uriMap[URI_PERIOD], - name: URI_PERIOD, - ); - } - 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', - invalidValue: uriMap[URI_DIGITS], - name: URI_DIGITS, - ); - } - if (uriMap[URI_ALGORITHM] != null) { - try { - Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()); - } catch (e) { - throw ArgumentError('Algorithm ${uriMap[URI_ALGORITHM]} is not supported'); - } - } - } - @override Map toJson() => _$DayPasswordTokenToJson(this); factory DayPasswordToken.fromJson(Map json) => _$DayPasswordTokenFromJson(json); diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index 657839e2b..23df2749a 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -17,19 +17,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'dart:typed_data'; import 'package:json_annotation/json_annotation.dart'; +import 'package:privacyidea_authenticator/utils/type_matchers.dart'; import '../token_container.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/algorithms_extension.dart'; -import '../extensions/enums/encodings_extension.dart'; import '../token_import/token_origin_data.dart'; import 'otp_token.dart'; import 'token.dart'; @@ -47,6 +44,7 @@ class HOTPToken extends OTPToken { HOTPToken({ this.counter = 0, super.containerSerial, + super.checkedContainers, required super.id, required super.algorithm, required super.digits, @@ -79,6 +77,14 @@ class HOTPToken extends OTPToken { isGoogle: true, ); + @override + String get nextValue => algorithm.generateHOTPCodeString( + secret: secret, + counter: counter + 1, + length: digits, + isGoogle: true, + ); + HOTPToken withNextCounter() => copyWith(counter: counter + 1); @override @@ -88,6 +94,7 @@ class HOTPToken extends OTPToken { String? label, String? issuer, String? Function()? containerSerial, + List? checkedContainers, String? id, Algorithms? algorithm, int? digits, @@ -106,6 +113,7 @@ class HOTPToken extends OTPToken { label: label ?? this.label, issuer: issuer ?? this.issuer, containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, + checkedContainers: checkedContainers ?? this.checkedContainers, id: id ?? this.id, algorithm: algorithm ?? this.algorithm, digits: digits ?? this.digits, @@ -125,98 +133,94 @@ class HOTPToken extends OTPToken { } @override - HOTPToken copyWithFromTemplate(TokenTemplate template) { - final uriMap = template.data; + HOTPToken copyUpdateByTemplate(TokenTemplate template) { + final uriMap = validateMap( + map: template.data, + validators: { + OTP_AUTH_LABEL: const TypeValidatorOptional(), + OTP_AUTH_ISSUER: const TypeValidatorOptional(), + OTP_AUTH_SERIAL: const TypeValidatorOptional(), + OTP_AUTH_ALGORITHM: stringToAlgorithmsValidatorOptional, + OTP_AUTH_DIGITS: stringToIntValidatorOptional, + OTP_AUTH_SECRET_BASE32: base32SecretValidatorOptional, + OTP_AUTH_COUNTER: stringToIntValidatorOptional, + OTP_AUTH_IMAGE: const TypeValidatorOptional(), + OTP_AUTH_PIN: stringToBoolValidatorOptional, + }, + name: 'HOTPToken', + ); return copyWith( - label: uriMap[URI_LABEL], - issuer: uriMap[URI_ISSUER], - id: uriMap[URI_ID], - serial: uriMap[URI_SERIAL], - algorithm: uriMap[URI_ALGORITHM] != null ? Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()) : null, - digits: uriMap[URI_DIGITS], - secret: uriMap[URI_SECRET] != null ? Encodings.base32.encode(uriMap[URI_SECRET]) : null, - counter: uriMap[URI_COUNTER], - tokenImage: uriMap[URI_IMAGE], - pin: uriMap[URI_PIN], - isLocked: uriMap[URI_PIN], - origin: uriMap[URI_ORIGIN], + label: uriMap[OTP_AUTH_LABEL] as String?, + issuer: uriMap[OTP_AUTH_ISSUER] as String?, + serial: uriMap[OTP_AUTH_SERIAL] as String?, + algorithm: uriMap[OTP_AUTH_ALGORITHM] as Algorithms?, + digits: uriMap[OTP_AUTH_DIGITS] as int?, + secret: uriMap[OTP_AUTH_SECRET_BASE32] as String?, + counter: uriMap[OTP_AUTH_COUNTER] as int?, + tokenImage: uriMap[OTP_AUTH_IMAGE] as String?, + pin: uriMap[OTP_AUTH_PIN] as bool?, + isLocked: uriMap[OTP_AUTH_PIN] as bool?, ); } - factory HOTPToken.fromUriMap(Map uriMap) { - validateUriMap(uriMap); + factory HOTPToken.fromOtpAuthMap(Map otpAuthMap, {required TokenOriginData origin}) { + final validatedMap = validateMap( + map: otpAuthMap, + validators: { + OTP_AUTH_LABEL: const TypeValidatorRequired(defaultValue: ''), + OTP_AUTH_ISSUER: const TypeValidatorRequired(defaultValue: ''), + OTP_AUTH_SERIAL: const TypeValidatorOptional(), + OTP_AUTH_ALGORITHM: stringToAlgorithmsValidator.withDefault(Algorithms.SHA1), + OTP_AUTH_DIGITS: stringToIntvalidator.withDefault(6), + OTP_AUTH_SECRET_BASE32: base32Secretvalidator, + OTP_AUTH_COUNTER: stringToIntvalidator.withDefault(0), + OTP_AUTH_IMAGE: const TypeValidatorOptional(), + OTP_AUTH_PIN: stringToBoolValidatorOptional, + }, + name: 'HOTPToken', + ); return HOTPToken( - label: uriMap[URI_LABEL] ?? '', - issuer: uriMap[URI_ISSUER] ?? '', - id: uriMap[URI_ID] == String ? uriMap[URI_ID] : const Uuid().v4(), - serial: uriMap[URI_SERIAL], - algorithm: Algorithms.values.byName((uriMap[URI_ALGORITHM] as String? ?? 'SHA1').toUpperCase()), - digits: uriMap[URI_DIGITS] ?? 6, - secret: Encodings.base32.encode(uriMap[URI_SECRET]), - counter: uriMap[URI_COUNTER] ?? 0, - tokenImage: uriMap[URI_IMAGE], - pin: uriMap[URI_PIN], - isLocked: uriMap[URI_PIN], - origin: uriMap[URI_ORIGIN], + label: validatedMap[OTP_AUTH_LABEL] as String, + issuer: validatedMap[OTP_AUTH_ISSUER] as String, + id: const Uuid().v4(), + serial: validatedMap[OTP_AUTH_SERIAL] as String?, + algorithm: validatedMap[OTP_AUTH_ALGORITHM] as Algorithms, + digits: validatedMap[OTP_AUTH_DIGITS] as int, + secret: validatedMap[OTP_AUTH_SECRET_BASE32] as String, + counter: validatedMap[OTP_AUTH_COUNTER] as int, + tokenImage: validatedMap[OTP_AUTH_IMAGE] as String?, + pin: validatedMap[OTP_AUTH_PIN] as bool?, + isLocked: validatedMap[OTP_AUTH_PIN] as bool?, + origin: origin, ); } + /// This is used to create a map that typically was created from a uri. /// ```dart - /// URI_TYPE: tokenType, - /// URI_COUNTER: counter, - /// ``` - /// ------ OTPTOKEN ------ - /// ```dart - /// URI_SECRET: Encodings.base32.decode(secret), - /// URI_ALGORITHM: algorithm.name, - /// URI_DIGITS: digits, - /// ``` - /// ------- TOKEN --------- - /// ```dart - /// URI_LABEL: label, - /// URI_ISSUER: issuer, - /// URI_PIN: pin, - /// URI_IMAGE: tokenImage, - /// URI_ORIGIN: jsonEncode(origin!.toJson()), + /// ------------------------- [Token] ------------------------- + /// | OTP_AUTH_SERIAL: serial, (optional) | + /// | OTP_AUTH_TYPE: type, | + /// | OTP_AUTH_LABEL: label, | + /// | OTP_AUTH_ISSUER: issuer, | + /// | OTP_AUTH_PIN: pin, | + /// | OTP_AUTH_IMAGE: tokenImage, (optional) | + /// ----------------------------------------------------------- + /// ----------------------- [OTPToken] ------------------------ + /// | OTP_AUTH_ALGORITHM: algorithm, | + /// | OTP_AUTH_DIGITS: digits, | + /// ----------------------------------------------------------- + /// ----------------------- [HOTPToken] ----------------------- + /// | OTP_AUTH_COUNTER: counter, | + /// ----------------------------------------------------------- /// ``` @override - Map toUriMap() { - return super.toUriMap() + Map toOtpAuthMap({String? containerSerial}) { + return super.toOtpAuthMap() ..addAll({ - URI_COUNTER: counter, + OTP_AUTH_COUNTER: counter.toString(), }); } - /// Validates the uriMap for the required fields throws [LocalizedArgumentError] if a field is missing or invalid. - static void validateUriMap(Map uriMap) { - if (uriMap[URI_SECRET] is! Uint8List) { - throw LocalizedArgumentError( - localizedMessage: ((localizations, value, name) => localizations.secretIsRequired), - unlocalizedMessage: 'Secret is required and must be a Uint8List', - invalidValue: uriMap[URI_SECRET], - name: URI_SECRET, - ); - } - if (uriMap[URI_DIGITS] != null && uriMap[URI_DIGITS] < 1) { - throw LocalizedArgumentError( - invalidValue: uriMap[URI_DIGITS], - name: URI_DIGITS, - unlocalizedMessage: 'Digits must be greater than 0', - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter)); - } - if (uriMap[URI_ALGORITHM] != null) { - try { - Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()); - } catch (e) { - throw LocalizedArgumentError( - invalidValue: uriMap[URI_ALGORITHM], - name: URI_ALGORITHM, - unlocalizedMessage: 'Algorithm ${uriMap[URI_ALGORITHM]} is not supported', - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter)); - } - } - } - @override Map toJson() => _$HOTPTokenToJson(this); factory HOTPToken.fromJson(Map json) => _$HOTPTokenFromJson(json); diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index 305af46e6..99b5f4794 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -20,8 +20,6 @@ import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; import '../enums/algorithms.dart'; -import '../enums/encodings.dart'; -import '../extensions/enums/encodings_extension.dart'; import '../token_import/token_origin_data.dart'; import 'token.dart'; @@ -30,6 +28,7 @@ abstract class OTPToken extends Token { final int digits; // the number of digits the otp value will have final String secret; // the secret based on which the otp value is calculated in base32 String get otpValue; // the current otp value + String get nextValue; // the next otp value Duration get showDuration { const Duration duration = Duration(seconds: 30); Logger.info('$runtimeType showDuration: ${duration.inSeconds} seconds'); @@ -43,6 +42,7 @@ abstract class OTPToken extends Token { required super.id, required super.type, super.containerSerial, + super.checkedContainers, super.serial, super.pin, super.tokenImage, @@ -70,6 +70,7 @@ abstract class OTPToken extends Token { String? label, String? issuer, String? Function()? containerSerial, + List? checkedContainers, String? id, Algorithms? algorithm, int? digits, @@ -88,13 +89,28 @@ abstract class OTPToken extends Token { return 'OTP${super.toString()}algorithm: $algorithm, digits: $digits, pin: $pin, '; } + /// This is used to create a map that typically was created from a uri. + /// ```dart + /// ------------------------- [Token] ------------------------- + /// | OTP_AUTH_SERIAL: serial, (optional) | + /// | OTP_AUTH_TYPE: type, | + /// | OTP_AUTH_LABEL: label, | + /// | OTP_AUTH_ISSUER: issuer, | + /// | OTP_AUTH_PIN: pin, | + /// | OTP_AUTH_IMAGE: tokenImage, (optional) | + /// ----------------------------------------------------------- + /// ------------------------ [OTPToken] ----------------------- + /// | OTP_AUTH_ALGORITHM: algorithm, | + /// | OTP_AUTH_DIGITS: digits, | + /// ----------------------------------------------------------- + /// ``` @override - Map toUriMap() { - return super.toUriMap() + Map toOtpAuthMap({String? containerSerial}) { + return super.toOtpAuthMap() ..addAll({ - URI_SECRET: Encodings.base32.decode(secret), - URI_ALGORITHM: algorithm.name, - URI_DIGITS: digits, + OTP_AUTH_ALGORITHM: algorithm.name, + OTP_AUTH_DIGITS: digits.toString(), + if (serial == null && !checkedContainers.contains(containerSerial)) OTP_AUTH_OTP_VALUES: '[$otpValue, $nextValue]', }); } } diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index b445e622b..de2e9c4e5 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -19,16 +19,16 @@ */ import 'package:json_annotation/json_annotation.dart'; import 'package:pointycastle/asymmetric/api.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:uuid/uuid.dart'; import '../../utils/custom_int_buffer.dart'; import '../../utils/errors.dart'; import '../../utils/identifiers.dart'; -import '../../utils/logger.dart'; import '../../utils/rsa_utils.dart'; +import '../../utils/type_matchers.dart'; import '../enums/push_token_rollout_state.dart'; import '../enums/token_types.dart'; -import '../token_container.dart'; import '../token_import/token_origin_data.dart'; import 'token.dart'; @@ -70,6 +70,7 @@ class PushToken extends Token { super.label, super.issuer, super.containerSerial, + super.checkedContainers, required super.id, this.fbToken, this.url, @@ -81,7 +82,6 @@ class PushToken extends Token { bool? isRolledOut, bool? sslVerify, PushTokenRollOutState? rolloutState, - String? type, // just for @JsonSerializable(): type of PushToken is always TokenTypes.PIPUSH super.tokenImage, super.sortIndex, super.folderId, @@ -122,6 +122,7 @@ class PushToken extends Token { String? serial, String? issuer, String? Function()? containerSerial, + List? checkedContainers, String? id, String? tokenImage, String? fbToken, @@ -149,6 +150,7 @@ class PushToken extends Token { tokenImage: tokenImage ?? this.tokenImage, fbToken: fbToken ?? this.fbToken, containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, + checkedContainers: checkedContainers ?? this.checkedContainers, id: id ?? this.id, pin: pin ?? this.pin, isLocked: isLocked ?? this.isLocked, @@ -189,172 +191,116 @@ class PushToken extends Token { 'publicTokenKey: $publicTokenKey}'; } - @override - PushToken copyWithFromTemplate(TokenTemplate template) { - final uriMap = template.data; - return copyWith( - label: uriMap[URI_LABEL], - issuer: uriMap[URI_ISSUER], - id: uriMap[URI_ID], - serial: uriMap[URI_SERIAL], - sslVerify: uriMap[URI_SSL_VERIFY], - enrollmentCredentials: uriMap[URI_ENROLLMENT_CREDENTIAL], - url: uriMap[URI_ROLLOUT_URL], - tokenImage: uriMap[URI_IMAGE], - pin: uriMap[URI_PIN], - isLocked: uriMap[URI_PIN], - origin: uriMap[URI_ORIGIN], - // do not update expirationDate + factory PushToken.fromOtpAuthMap(Map otpAuthMap, {TokenOriginData? origin}) { +// Validate map for Push token + final validatedMap = validateMap( + map: otpAuthMap, + validators: { + OTP_AUTH_LABEL: const TypeValidatorOptional(defaultValue: ''), + OTP_AUTH_ISSUER: const TypeValidatorOptional(defaultValue: ''), + OTP_AUTH_SERIAL: const TypeValidatorRequired(), + OTP_AUTH_PUSH_SSL_VERIFY: stringToBoolValidator.withDefault(true), + OTP_AUTH_PUSH_TTL_MINUTES: TypeValidatorRequired( + transformer: (v) => Duration(minutes: int.parse(v)), + defaultValue: const Duration(minutes: 10), + ), + OTP_AUTH_PUSH_ENROLLMENT_CREDENTIAL: const TypeValidatorOptional(), + OTP_AUTH_PUSH_ROLLOUT_URL: stringToUrivalidator, + OTP_AUTH_IMAGE: stringToUriValidatorOptional, + OTP_AUTH_PIN: const TypeValidatorOptional(), + OTP_AUTH_VERSION: const TypeValidatorRequired(), + }, + name: 'PushToken', ); + return switch (validatedMap[OTP_AUTH_VERSION]) { + '1' => PushToken( + id: const Uuid().v4(), + label: validatedMap[OTP_AUTH_LABEL] as String, + issuer: validatedMap[OTP_AUTH_ISSUER] as String, + serial: validatedMap[OTP_AUTH_SERIAL] as String, + sslVerify: validatedMap[OTP_AUTH_PUSH_SSL_VERIFY] as bool, + expirationDate: DateTime.now().add(validatedMap[OTP_AUTH_PUSH_TTL_MINUTES] as Duration), + enrollmentCredentials: validatedMap[OTP_AUTH_PUSH_ENROLLMENT_CREDENTIAL] as String?, + url: validatedMap[OTP_AUTH_PUSH_ROLLOUT_URL] as Uri, + tokenImage: validatedMap[OTP_AUTH_IMAGE] as String?, + pin: validatedMap[OTP_AUTH_PIN] as bool?, + isLocked: validatedMap[OTP_AUTH_PIN] as bool?, + origin: origin, + ), + _ => throw LocalizedArgumentError( + localizedMessage: (localizations, value, name) => localizations.unsupported(value, name), + unlocalizedMessage: 'The piauth version [${validatedMap[OTP_AUTH_VERSION]}] is not supported by this version of the app.', + invalidValue: validatedMap[OTP_AUTH_VERSION].toString(), + name: 'piauth version', + ), + }; } - factory PushToken.fromUriMap(Map uriMap) { - validateUriMap(uriMap); - return PushToken( - label: uriMap[URI_LABEL] ?? '', - issuer: uriMap[URI_ISSUER] ?? '', - id: uriMap[URI_ID] == String ? uriMap[URI_ID] : const Uuid().v4(), - serial: uriMap[URI_SERIAL], - sslVerify: uriMap[URI_SSL_VERIFY], - expirationDate: uriMap[URI_TTL] != null ? DateTime.now().add(Duration(minutes: uriMap[URI_TTL])) : null, - enrollmentCredentials: uriMap[URI_ENROLLMENT_CREDENTIAL], - url: uriMap[URI_ROLLOUT_URL], - tokenImage: uriMap[URI_IMAGE], - pin: uriMap[URI_PIN], - isLocked: uriMap[URI_PIN], - origin: uriMap[URI_ORIGIN], + @override + Token copyUpdateByTemplate(TokenTemplate template) { + final uriMap = validateMap( + map: template.data, + validators: { + OTP_AUTH_LABEL: const TypeValidatorOptional(), + OTP_AUTH_ISSUER: const TypeValidatorOptional(), + OTP_AUTH_SERIAL: const TypeValidatorOptional(), + OTP_AUTH_PUSH_SSL_VERIFY: stringToBoolValidatorOptional, + OTP_AUTH_PUSH_TTL_MINUTES: TypeValidatorOptional( + transformer: (v) => Duration(minutes: int.parse(v)), + ), + OTP_AUTH_PUSH_ENROLLMENT_CREDENTIAL: const TypeValidatorOptional(), + OTP_AUTH_PUSH_ROLLOUT_URL: stringToUriValidatorOptional, + OTP_AUTH_IMAGE: stringToUriValidatorOptional, + OTP_AUTH_PIN: stringToBoolValidator, + OTP_AUTH_VERSION: stringToIntValidatorOptional, + }, + name: 'PushToken', + ); + return copyWith( + label: uriMap[OTP_AUTH_LABEL] as String?, + issuer: uriMap[OTP_AUTH_ISSUER] as String?, + serial: uriMap[OTP_AUTH_SERIAL] as String?, + sslVerify: uriMap[OTP_AUTH_PUSH_SSL_VERIFY] as bool?, + expirationDate: uriMap[OTP_AUTH_PUSH_TTL_MINUTES] != null ? DateTime.now().add(uriMap[OTP_AUTH_PUSH_TTL_MINUTES] as Duration) : expirationDate, + enrollmentCredentials: uriMap[OTP_AUTH_PUSH_ENROLLMENT_CREDENTIAL] as String?, + url: uriMap[OTP_AUTH_PUSH_ROLLOUT_URL] as Uri?, + tokenImage: uriMap[OTP_AUTH_IMAGE] as String?, + pin: uriMap[OTP_AUTH_PIN] as bool?, + isLocked: uriMap[OTP_AUTH_PIN] as bool?, ); } - static void validateUriMap(Map uriMap) { - uriMap = Map.from(uriMap); - if (uriMap[URI_TYPE]?.toUpperCase() != TokenTypes.PIPUSH.name.toUpperCase()) { - throw ArgumentError('Invalid type: ${uriMap[URI_TYPE]}'); - } - uriMap.remove(URI_TYPE); - if (uriMap[URI_LABEL] is! String?) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: 'Invalid value ${uriMap[URI_LABEL]} for parameter $URI_LABEL', - invalidValue: uriMap[URI_LABEL], - name: URI_LABEL, - ); - } - uriMap.remove(URI_LABEL); - if (uriMap[URI_ISSUER] is! String?) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: 'Invalid value ${uriMap[URI_ISSUER]} for parameter $URI_ISSUER', - invalidValue: uriMap[URI_ISSUER], - name: URI_ISSUER, - ); - } - uriMap.remove(URI_ISSUER); - if (uriMap[URI_ID] is! String?) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: 'Invalid value ${uriMap[URI_ID]} for parameter $URI_ID', - invalidValue: uriMap[URI_ID], - name: URI_ID, - ); - } - uriMap.remove(URI_ID); - if (uriMap[URI_SERIAL] is! String) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: 'Invalid value ${uriMap[URI_SERIAL]} for parameter $URI_SERIAL', - invalidValue: uriMap[URI_SERIAL], - name: URI_SERIAL, - ); - } - uriMap.remove(URI_SERIAL); - if (uriMap[URI_SSL_VERIFY] is! bool) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: 'Invalid value ${uriMap[URI_SSL_VERIFY]} for parameter $URI_SSL_VERIFY', - invalidValue: uriMap[URI_SSL_VERIFY], - name: URI_SSL_VERIFY, - ); - } - uriMap.remove(URI_SSL_VERIFY); - /** - - expirationDate: uriMap[URI_TTL] != null ? DateTime.now().add(Duration(minutes: uriMap[URI_TTL])) : null, - enrollmentCredentials: uriMap[URI_ENROLLMENT_CREDENTIAL], - url: uriMap[URI_ROLLOUT_URL], - tokenImage: uriMap[URI_IMAGE], - pin: uriMap[URI_PIN], - isLocked: uriMap[URI_PIN], - origin: uriMap[URI_ORIGIN], - */ - if (uriMap[URI_TTL] is! int?) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: 'Invalid value ${uriMap[URI_TTL]} for parameter $URI_TTL', - invalidValue: uriMap[URI_TTL], - name: URI_TTL, - ); - } - uriMap.remove(URI_TTL); - if (uriMap[URI_ENROLLMENT_CREDENTIAL] is! String?) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: 'Invalid value ${uriMap[URI_ENROLLMENT_CREDENTIAL]} for parameter $URI_ENROLLMENT_CREDENTIAL', - invalidValue: uriMap[URI_ENROLLMENT_CREDENTIAL], - name: URI_ENROLLMENT_CREDENTIAL, - ); - } - uriMap.remove(URI_ENROLLMENT_CREDENTIAL); - if (uriMap[URI_ROLLOUT_URL] is! Uri?) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: 'Invalid value ${uriMap[URI_ROLLOUT_URL]} for parameter $URI_ROLLOUT_URL', - invalidValue: uriMap[URI_ROLLOUT_URL], - name: URI_ROLLOUT_URL, - ); - } - uriMap.remove(URI_ROLLOUT_URL); - if (uriMap[URI_IMAGE] is! String?) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: 'Invalid value ${uriMap[URI_IMAGE]} for parameter $URI_IMAGE', - invalidValue: uriMap[URI_IMAGE], - name: URI_IMAGE, - ); - } - uriMap.remove(URI_IMAGE); - if (uriMap[URI_PIN] is! bool?) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: 'Invalid value ${uriMap[URI_PIN]} for parameter $URI_PIN', - invalidValue: uriMap[URI_PIN], - name: URI_PIN, - ); - } - uriMap.remove(URI_PIN); - if (uriMap[URI_ORIGIN] is! TokenOriginData?) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: 'Invalid value ${uriMap[URI_ORIGIN]} for parameter $URI_ORIGIN', - invalidValue: uriMap[URI_ORIGIN], - name: URI_ORIGIN, - ); - } - uriMap.remove(URI_ORIGIN); - if (uriMap.isNotEmpty) { - Logger.warning('Unknown parameters in uriMap: $uriMap'); - } - } - + /// This is used to create a map that typically was created from a uri. + /// ```dart + /// ---------------------------- [Token] ---------------------------- + /// | OTP_AUTH_SERIAL: serial, (optional) | + /// | OTP_AUTH_TYPE: type, | + /// | OTP_AUTH_LABEL: label, | + /// | OTP_AUTH_ISSUER: issuer, | + /// | OTP_AUTH_PIN: pin, | + /// | OTP_AUTH_IMAGE: tokenImage, (optional) | + /// ------------------------------------------------------------------ + /// -------------------------- [PushToken] --------------------------- + /// | OTP_AUTH_SSL_VERIFY: sslVerify, | + /// | OTP_AUTH_ROLLOUT_TTL_MINUTES: expirationDate, (optional) | + /// | OTP_AUTH_ENROLLMENT_CREDENTIAL: enrollmentCredentials, (optional)| + /// | OTP_AUTH_ROLLOUT_URL: url, (optional) | + /// | OTP_AUTH_IMAGE: tokenImage, (optional) | + /// | OTP_AUTH_PIN: pin, | + /// | OTP_AUTH_VERSION: 1, | + /// ------------------------------------------------------------------ + /// ``` @override - Map toUriMap() { - return super.toUriMap() + Map toOtpAuthMap({String? containerSerial}) { + return super.toOtpAuthMap() ..addAll({ - URI_SERIAL: serial, - URI_SSL_VERIFY: sslVerify, - if (expirationDate != null) URI_TTL: expirationDate!.difference(DateTime.now()).inMinutes, - if (enrollmentCredentials != null) URI_ENROLLMENT_CREDENTIAL: enrollmentCredentials, - if (url != null) URI_ROLLOUT_URL: url.toString(), + OTP_AUTH_PUSH_SSL_VERIFY: sslVerify ? OTP_AUTH_PUSH_SSL_VERIFY_TRUE : OTP_AUTH_PUSH_SSL_VERIFY_FALSE, + if (expirationDate != null) OTP_AUTH_PUSH_TTL_MINUTES: expirationDate!.difference(DateTime.now()).inMinutes.toString(), + if (enrollmentCredentials != null) OTP_AUTH_PUSH_ENROLLMENT_CREDENTIAL: enrollmentCredentials!, + if (url != null) OTP_AUTH_PUSH_ROLLOUT_URL: url.toString(), + if (tokenImage != null) OTP_AUTH_IMAGE: tokenImage!, + OTP_AUTH_PIN: pin ? OTP_AUTH_PIN_TRUE : OTP_AUTH_PIN_FALSE, + OTP_AUTH_VERSION: '1', }); } diff --git a/lib/model/tokens/push_token.g.dart b/lib/model/tokens/push_token.g.dart index 922481066..52f8d0b8e 100644 --- a/lib/model/tokens/push_token.g.dart +++ b/lib/model/tokens/push_token.g.dart @@ -25,7 +25,6 @@ PushToken _$PushTokenFromJson(Map json) => PushToken( sslVerify: json['sslVerify'] as bool?, rolloutState: $enumDecodeNullable( _$PushTokenRollOutStateEnumMap, json['rolloutState']), - type: json['type'] as String?, tokenImage: json['tokenImage'] as String?, sortIndex: (json['sortIndex'] as num?)?.toInt(), folderId: (json['folderId'] as num?)?.toInt(), @@ -49,7 +48,6 @@ Map _$PushTokenToJson(PushToken instance) => { 'folderId': instance.folderId, 'sortIndex': instance.sortIndex, 'origin': instance.origin, - 'type': instance.type, 'expirationDate': instance.expirationDate?.toIso8601String(), 'serial': instance.serial, 'fbToken': instance.fbToken, diff --git a/lib/model/tokens/steam_token.dart b/lib/model/tokens/steam_token.dart index 0d1ac4c0b..600d69215 100644 --- a/lib/model/tokens/steam_token.dart +++ b/lib/model/tokens/steam_token.dart @@ -20,14 +20,13 @@ import 'package:base32/base32.dart'; import 'package:crypto/crypto.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:uuid/uuid.dart'; -import '../../utils/errors.dart'; import '../../utils/identifiers.dart'; +import '../../utils/type_matchers.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'; @@ -45,8 +44,9 @@ class SteamToken extends TOTPToken { SteamToken({ required super.id, required super.secret, - super.containerSerial, super.serial, + super.containerSerial, + super.checkedContainers, String? type, super.tokenImage, super.pin, @@ -70,6 +70,7 @@ class SteamToken extends TOTPToken { String? label, String? issuer, String? Function()? containerSerial, + List? checkedContainers, String? id, bool? isLocked, bool? isHidden, @@ -78,16 +79,17 @@ class SteamToken extends TOTPToken { int? sortIndex, int? Function()? folderId, TokenOriginData? origin, + String? secret, int? period, // unused steam tokens always have 30 seconds period int? digits, // unused steam tokens always have 5 digits Algorithms? algorithm, // unused steam tokens always have SHA1 algorithm - String? secret, }) { return SteamToken( serial: serial ?? this.serial, label: label ?? this.label, issuer: issuer ?? this.issuer, containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, + checkedContainers: checkedContainers ?? this.checkedContainers, id: id ?? this.id, secret: secret ?? this.secret, tokenImage: tokenImage ?? this.tokenImage, @@ -107,7 +109,8 @@ class SteamToken extends TOTPToken { @override bool isSameTokenAs(Token other) => super.isSameTokenAs(other) && other is SteamToken; - String otpOfTime(DateTime time) { + @override + String otpFromTime(DateTime time) { // Flooring time/counter is TOTP default, but yes, steam uses the rounded time/counter. final counterBytes = (time.millisecondsSinceEpoch / 1000 / period).round().bytes; final secretList = base32.decode(secret.toUpperCase()); @@ -126,49 +129,87 @@ class SteamToken extends TOTPToken { } @override - String get otpValue => otpOfTime(DateTime.now()); - - static SteamToken fromUriMap(Map uriMap) { - if (uriMap[URI_SECRET] == null) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, name) => localizations.secretIsRequired, - unlocalizedMessage: 'Secret is required', - invalidValue: uriMap[URI_SECRET], - name: 'SteamToken#fromUriMap', - ); - } + String get otpValue => otpFromTime(DateTime.now()); + + @override + String toString() { + return 'STEAM-${super.toString()}'; + } + + @override + SteamToken copyUpdateByTemplate(TokenTemplate template) { + final uriMap = validateMap( + map: template.data, + validators: { + OTP_AUTH_LABEL: const TypeValidatorOptional(), + OTP_AUTH_ISSUER: const TypeValidatorOptional(), + OTP_AUTH_SERIAL: const TypeValidatorOptional(), + OTP_AUTH_SECRET_BASE32: base32SecretValidatorOptional, + OTP_AUTH_IMAGE: const TypeValidatorOptional(), + OTP_AUTH_PIN: stringToBoolValidatorOptional, + }, + name: 'SteamToken', + ); + return copyWith( + label: uriMap[OTP_AUTH_LABEL] as String?, + issuer: uriMap[OTP_AUTH_ISSUER] as String?, + serial: uriMap[OTP_AUTH_SERIAL] as String?, + secret: uriMap[OTP_AUTH_SECRET_BASE32] as String?, + tokenImage: uriMap[OTP_AUTH_IMAGE] as String?, + pin: uriMap[OTP_AUTH_PIN] as bool?, + isLocked: uriMap[OTP_AUTH_PIN] as bool?, + ); + } + + static SteamToken fromOtpAuthMap(Map uriMap, {required TokenOriginData origin}) { + uriMap = validateMap( + map: uriMap, + validators: { + OTP_AUTH_LABEL: const TypeValidatorRequired(defaultValue: ''), + OTP_AUTH_ISSUER: const TypeValidatorRequired(defaultValue: ''), + OTP_AUTH_SERIAL: const TypeValidatorOptional(), + OTP_AUTH_SECRET_BASE32: base32Secretvalidator, + OTP_AUTH_IMAGE: const TypeValidatorOptional(), + OTP_AUTH_PIN: stringToBoolValidatorOptional, + }, + name: 'SteamToken', + ); return SteamToken( - label: (uriMap[URI_LABEL] as String?) ?? '', - issuer: (uriMap[URI_ISSUER] as String?) ?? '', + label: uriMap[OTP_AUTH_LABEL], + issuer: uriMap[OTP_AUTH_ISSUER], id: const Uuid().v4(), - secret: Encodings.base32.encode(uriMap[URI_SECRET]), - tokenImage: uriMap[URI_IMAGE] as String?, - pin: uriMap[URI_PIN] as bool?, - origin: uriMap[URI_ORIGIN] as TokenOriginData?, + serial: uriMap[OTP_AUTH_SERIAL], + secret: uriMap[OTP_AUTH_SECRET_BASE32], + tokenImage: uriMap[OTP_AUTH_IMAGE], + pin: uriMap[OTP_AUTH_PIN], + isLocked: uriMap[OTP_AUTH_PIN], + origin: origin, ); } - /// ----- TOTP TOKEN ----- - /// ```dart - /// URI_TYPE: tokenType, - /// URI_PERIOD: period, - /// ``` - /// ------ OTP TOKEN ------ - /// ```dart - /// URI_SECRET: Encodings.base32.decode(secret), - /// URI_ALGORITHM: algorithm.name, - /// URI_DIGITS: digits, - /// ``` - /// ------- TOKEN --------- + /// This is used to create a map that typically was created from a uri. /// ```dart - /// URI_LABEL: label, - /// URI_ISSUER: issuer, - /// URI_PIN: pin, - /// URI_IMAGE: tokenImage, - /// URI_ORIGIN: jsonEncode(origin!.toJson()), + /// ------------------------- [Token] ------------------------- + /// | OTP_AUTH_SERIAL: serial, (optional) | + /// | OTP_AUTH_TYPE: type, | + /// | OTP_AUTH_LABEL: label, | + /// | OTP_AUTH_ISSUER: issuer, | + /// | OTP_AUTH_PIN: pin, | + /// | OTP_AUTH_IMAGE: tokenImage, (optional) | + /// ----------------------------------------------------------- + /// ----------------------- [OTPToken] ------------------------ + /// | OTP_AUTH_ALGORITHM: algorithm, | + /// | OTP_AUTH_DIGITS: digits, | + /// ----------------------------------------------------------- + /// ----------------------- [HOTPToken] ----------------------- + /// | OTP_AUTH_COUNTER: period, | + /// ----------------------------------------------------------- + /// ----------------------- [SteamToken] ---------------------- + /// | /*No additional fields*/ | + /// ----------------------------------------------------------- /// ``` @override - Map toUriMap() => super.toUriMap(); + Map toOtpAuthMap({String? containerSerial}) => super.toOtpAuthMap(); static SteamToken fromJson(Map json) => _$SteamTokenFromJson(json); @override diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index d8f1c9e3c..0a1006eba 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -36,6 +36,7 @@ abstract class Token with SortableMixin { bool? get isPrivacyIdeaToken => origin?.isPrivacyIdeaToken; final String tokenVersion = 'v1.0.0'; // The version of this token, this is used for serialization. final String? containerSerial; // The serial of the container this token belongs to. + final List checkedContainers; // The serials of the containers this token should not be in. final String label; // the name of the token, it cannot be uses as an identifier final String issuer; // The issuer of this token, currently unused. final String id; // this is the identifier of the token @@ -65,14 +66,15 @@ abstract class Token with SortableMixin { } /// Creates a token from a uri map. - factory Token.fromUriMap(Map uriMap) { - String type = uriMap[URI_TYPE]; - if (TokenTypes.HOTP.isName(type, caseSensitive: false)) return HOTPToken.fromUriMap(uriMap); - if (TokenTypes.TOTP.isName(type, caseSensitive: false)) return TOTPToken.fromUriMap(uriMap); - if (TokenTypes.PIPUSH.isName(type, caseSensitive: false)) return PushToken.fromUriMap(uriMap); - if (TokenTypes.DAYPASSWORD.isName(type, caseSensitive: false)) return DayPasswordToken.fromUriMap(uriMap); - if (TokenTypes.STEAM.isName(type, caseSensitive: false)) return SteamToken.fromUriMap(uriMap); - throw ArgumentError.value(uriMap, 'Token#fromUriMap', 'Token type [$type] is not a supported'); + factory Token.fromOtpAuthMap(Map otpAuthMap, {required TokenOriginData origin}) { + String? type = otpAuthMap[OTP_AUTH_TYPE]; + if (type == null) throw ArgumentError.value(otpAuthMap, 'Token#fromUriMap', 'Token type is not defined in the uri map'); + if (TokenTypes.HOTP.isName(type, caseSensitive: false)) return HOTPToken.fromOtpAuthMap(otpAuthMap, origin: origin); + if (TokenTypes.TOTP.isName(type, caseSensitive: false)) return TOTPToken.fromOtpAuthMap(otpAuthMap, origin: origin); + if (TokenTypes.PIPUSH.isName(type, caseSensitive: false)) return PushToken.fromOtpAuthMap(otpAuthMap, origin: origin); + if (TokenTypes.DAYPASSWORD.isName(type, caseSensitive: false)) return DayPasswordToken.fromOtpAuthMap(otpAuthMap, origin: origin); + if (TokenTypes.STEAM.isName(type, caseSensitive: false)) return SteamToken.fromOtpAuthMap(otpAuthMap, origin: origin); + throw ArgumentError.value(otpAuthMap, 'Token#fromUriMap', 'Token type [$type] is not a supported'); } const Token({ @@ -80,6 +82,7 @@ abstract class Token with SortableMixin { this.label = '', this.issuer = '', this.containerSerial, + this.checkedContainers = const [], required this.id, required this.type, this.tokenImage, @@ -109,6 +112,7 @@ abstract class Token with SortableMixin { String? label, String? issuer, String? Function()? containerSerial, + List? checkedContainers, String? id, bool? isLocked, bool? isHidden, @@ -147,29 +151,28 @@ abstract class Token with SortableMixin { /// This is used to create a map that typically was created from a uri. /// ```dart - /// ------------------------------- [Token] --------------------------------- - /// URI_LABEL: name of the token (String), - /// URI_ISSUER: name of the issuer (String), - /// URI_PIN: is the user forced to have a pin (bool), - /// URI_IMAGE: url to an image e.g. "https://example.com/image.png" (String), - /// URI_ORIGIN: json string of the origin class (String), - /// ------------------------------------------------------------------------- + /// ------------------------- [Token] ------------------------- + /// | OTP_AUTH_SERIAL: serial, (optional) | + /// | OTP_AUTH_TYPE: type, | + /// | OTP_AUTH_LABEL: label, | + /// | OTP_AUTH_ISSUER: issuer, | + /// | OTP_AUTH_PIN: pin, | + /// | OTP_AUTH_IMAGE: tokenImage, (optional) | + /// ----------------------------------------------------------- /// ``` - Map toUriMap() { + Map toOtpAuthMap({String? containerSerial}) { return { - URI_ID: id, - URI_SERIAL: serial, - URI_CONTAINER_SERIAL: containerSerial, - URI_TYPE: type, - URI_LABEL: label, - URI_ISSUER: issuer, - URI_PIN: pin, - if (tokenImage != null) URI_IMAGE: tokenImage, - if (origin != null) URI_ORIGIN: origin!, + if (containerSerial != null) CONTAINER_SERIAL: containerSerial, + if (serial != null) OTP_AUTH_SERIAL: serial!, + OTP_AUTH_TYPE: type, + OTP_AUTH_LABEL: label, + OTP_AUTH_ISSUER: issuer, + OTP_AUTH_PIN: pin ? OTP_AUTH_PIN_TRUE : OTP_AUTH_PIN_FALSE, + if (tokenImage != null) OTP_AUTH_IMAGE: tokenImage!, }; } - Token copyWithFromTemplate(TokenTemplate template); + Token copyUpdateByTemplate(TokenTemplate template); - TokenTemplate toTemplate() => TokenTemplate(data: toUriMap()); + TokenTemplate toTemplate() => TokenTemplate(data: toOtpAuthMap()); } diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index 230ef15b4..b116530a9 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -17,24 +17,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'dart:typed_data'; import 'package:json_annotation/json_annotation.dart'; import 'package:uuid/uuid.dart'; - -import '../../utils/errors.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; +import '../../utils/type_matchers.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_container.dart'; import '../token_import/token_origin_data.dart'; import 'otp_token.dart'; import 'token.dart'; - part 'totp_token.g.dart'; @JsonSerializable() @@ -62,6 +57,8 @@ class TOTPToken extends OTPToken { @override String get otpValue => otpFromTime(DateTime.now()); + @override + String get nextValue => otpFromTime(DateTime.now().add(Duration(seconds: period))); TOTPToken({ required int period, @@ -69,8 +66,9 @@ class TOTPToken extends OTPToken { required super.algorithm, required super.digits, required super.secret, - super.containerSerial, super.serial, + super.containerSerial, + super.checkedContainers, String? type, super.tokenImage, super.pin, @@ -97,6 +95,7 @@ class TOTPToken extends OTPToken { String? label, String? issuer, String? Function()? containerSerial, + List? checkedContainers, String? id, Algorithms? algorithm, int? digits, @@ -115,6 +114,7 @@ class TOTPToken extends OTPToken { label: label ?? this.label, issuer: issuer ?? this.issuer, containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, + checkedContainers: checkedContainers ?? this.checkedContainers, id: id ?? this.id, algorithm: algorithm ?? this.algorithm, digits: digits ?? this.digits, @@ -131,26 +131,34 @@ class TOTPToken extends OTPToken { } @override - TOTPToken copyWithFromTemplate(TokenTemplate template) { - final uriMap = template.data; - - final newToken = copyWith( - id: uriMap[URI_ID], - label: uriMap[URI_LABEL], - issuer: uriMap[URI_ISSUER], - serial: uriMap[URI_SERIAL], - algorithm: uriMap[URI_ALGORITHM] != null ? Algorithms.values.byName((uriMap[URI_ALGORITHM] as String).toUpperCase()) : null, - digits: uriMap[URI_DIGITS], - tokenImage: uriMap[URI_IMAGE], - secret: uriMap[URI_SECRET] != null ? Encodings.base32.encode(uriMap[URI_SECRET]) : null, - period: uriMap[URI_PERIOD], - pin: uriMap[URI_PIN], - isLocked: uriMap[URI_PIN], - origin: uriMap[URI_ORIGIN], + TOTPToken copyUpdateByTemplate(TokenTemplate template) { + final uriMap = validateMap( + map: template.data, + validators: { + OTP_AUTH_LABEL: const TypeValidatorOptional(), + OTP_AUTH_ISSUER: const TypeValidatorOptional(), + OTP_AUTH_SERIAL: const TypeValidatorOptional(), + OTP_AUTH_ALGORITHM: stringToAlgorithmsValidatorOptional, + OTP_AUTH_DIGITS: stringToIntValidatorOptional, + OTP_AUTH_SECRET_BASE32: base32SecretValidatorOptional, + OTP_AUTH_PERIOD_SECONDS: stringToIntValidatorOptional, + OTP_AUTH_IMAGE: const TypeValidatorOptional(), + OTP_AUTH_PIN: stringToBoolValidatorOptional, + }, + name: 'TOTPToken', + ); + return copyWith( + label: uriMap[OTP_AUTH_LABEL] as String?, + issuer: uriMap[OTP_AUTH_ISSUER] as String?, + serial: uriMap[OTP_AUTH_SERIAL] as String?, + algorithm: uriMap[OTP_AUTH_ALGORITHM] as Algorithms?, + digits: uriMap[OTP_AUTH_DIGITS] as int?, + secret: uriMap[OTP_AUTH_SECRET_BASE32] as String?, + period: uriMap[OTP_AUTH_PERIOD_SECONDS] as int?, + tokenImage: uriMap[OTP_AUTH_IMAGE] as String?, + pin: uriMap[OTP_AUTH_PIN] as bool?, + isLocked: uriMap[OTP_AUTH_PIN] as bool?, ); - Logger.debug('TOTPToken.copyWithFromTemplate old token: $this'); - Logger.debug('TOTPToken.copyWithFromTemplate new token: $newToken'); - return newToken; } @override @@ -158,96 +166,64 @@ class TOTPToken extends OTPToken { return 'T${super.toString()}period: $period}'; } - factory TOTPToken.fromUriMap(Map uriMap) { - validateUriMap(uriMap); + factory TOTPToken.fromOtpAuthMap(Map otpAuthMap, {required TokenOriginData origin}) { + final validatedMap = validateMap( + map: otpAuthMap, + validators: { + OTP_AUTH_LABEL: const TypeValidatorRequired(defaultValue: ''), + OTP_AUTH_ISSUER: const TypeValidatorRequired(defaultValue: ''), + OTP_AUTH_SERIAL: const TypeValidatorOptional(), + OTP_AUTH_ALGORITHM: stringToAlgorithmsValidator.withDefault(Algorithms.SHA1), + OTP_AUTH_DIGITS: stringToIntvalidator.withDefault(6), + OTP_AUTH_SECRET_BASE32: base32Secretvalidator, + OTP_AUTH_PERIOD_SECONDS: stringToIntvalidator.withDefault(30), + OTP_AUTH_IMAGE: const TypeValidatorOptional(), + OTP_AUTH_PIN: stringToBoolValidatorOptional, + }, + name: 'TOTPToken', + ); return TOTPToken( - label: uriMap[URI_LABEL] ?? '', - issuer: uriMap[URI_ISSUER] ?? '', - id: uriMap[URI_ID] == String ? uriMap[URI_ID] : const Uuid().v4(), - serial: uriMap[URI_SERIAL], - algorithm: Algorithms.values.byName((uriMap[URI_ALGORITHM] as String? ?? 'SHA1').toUpperCase()), - digits: uriMap[URI_DIGITS] ?? 6, - tokenImage: uriMap[URI_IMAGE], - secret: Encodings.base32.encode(uriMap[URI_SECRET]), - period: uriMap[URI_PERIOD] ?? 30, - pin: uriMap[URI_PIN], - isLocked: uriMap[URI_PIN], - origin: uriMap[URI_ORIGIN], + label: validatedMap[OTP_AUTH_LABEL] as String, + issuer: validatedMap[OTP_AUTH_ISSUER] as String, + id: const Uuid().v4(), + serial: validatedMap[OTP_AUTH_SERIAL] as String?, + algorithm: validatedMap[OTP_AUTH_ALGORITHM] as Algorithms, + digits: validatedMap[OTP_AUTH_DIGITS] as int, + secret: validatedMap[OTP_AUTH_SECRET_BASE32] as String, + period: validatedMap[OTP_AUTH_PERIOD_SECONDS] as int, + tokenImage: validatedMap[OTP_AUTH_IMAGE] as String?, + pin: validatedMap[OTP_AUTH_PIN] as bool?, + isLocked: validatedMap[OTP_AUTH_PIN] as bool?, + origin: origin, ); } /// This is used to create a map that typically was created from a uri. /// ```dart - /// ----------------------------- [TOTPToken] ------------------------------ - /// URI_PERIOD: period of otp generation in seconds (int), - /// ------------------------------ [OTPToken] ------------------------------ - /// URI_SECRET: base32 encoded string (String), - /// URI_ALGORITHM: algorithm name e.g. SHA1 (String), - /// URI_DIGITS: number of digits (int), - /// ------------------------------- [Token] --------------------------------- - /// URI_LABEL: name of the token (String), - /// URI_ISSUER: name of the issuer (String), - /// URI_PIN: is the user forced to have a pin (bool), - /// URI_IMAGE: url to an image e.g. "https://example.com/image.png" (String), - /// URI_ORIGIN: json string of the origin class (String), - /// ------------------------------------------------------------------------- + /// ------------------------- [Token] ------------------------- + /// | OTP_AUTH_SERIAL: serial, (optional) | + /// | OTP_AUTH_TYPE: type, | + /// | OTP_AUTH_LABEL: label, | + /// | OTP_AUTH_ISSUER: issuer, | + /// | OTP_AUTH_PIN: pin, | + /// | OTP_AUTH_IMAGE: tokenImage, (optional) | + /// ----------------------------------------------------------- + /// ----------------------- [OTPToken] ------------------------ + /// | OTP_AUTH_ALGORITHM: algorithm, | + /// | OTP_AUTH_DIGITS: digits, | + /// ----------------------------------------------------------- + /// ----------------------- [HOTPToken] ----------------------- + /// | OTP_AUTH_COUNTER: period, | + /// ----------------------------------------------------------- /// ``` @override - Map toUriMap() { - return super.toUriMap() + Map toOtpAuthMap({String? containerSerial}) { + return super.toOtpAuthMap() ..addAll({ - URI_PERIOD: period, + OTP_AUTH_COUNTER: period.toString(), }); } - /// Validates the uriMap for the required fields throws [LocalizedArgumentError] if a field is missing or invalid. - static void validateUriMap(Map uriMap) { - if (uriMap[URI_SECRET] is! Uint8List) { - throw LocalizedArgumentError( - localizedMessage: ((localizations, value, name) => localizations.secretIsRequired), - unlocalizedMessage: 'Secret is required and must be a Uint8List', - invalidValue: uriMap[URI_SECRET], - name: URI_SECRET, - ); - } - if (uriMap[URI_SERIAL] is! String?) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: 'Serial must be a string', - invalidValue: uriMap[URI_SERIAL], - name: URI_SERIAL, - ); - } - 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', - invalidValue: uriMap[URI_DIGITS], - name: URI_DIGITS, - ); - } - 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', - invalidValue: uriMap[URI_PERIOD], - name: URI_PERIOD, - ); - } - if (uriMap[URI_ALGORITHM] != null) { - try { - Algorithms.values.byName(uriMap[URI_ALGORITHM].toUpperCase()); - } catch (e) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: 'Algorithm ${uriMap[URI_ALGORITHM]} is not supported', - invalidValue: uriMap[URI_ALGORITHM], - name: URI_ALGORITHM, - ); - } - } - } - double get currentProgress { final secondsSinceEpoch = DateTime.now().toUtc().millisecondsSinceEpoch ~/ 1000; return (secondsSinceEpoch % (period)) * (1 / period); diff --git a/lib/processors/mixins/token_import_processor.dart b/lib/processors/mixins/token_import_processor.dart index 22307d3be..18d73ca42 100644 --- a/lib/processors/mixins/token_import_processor.dart +++ b/lib/processors/mixins/token_import_processor.dart @@ -25,7 +25,7 @@ import '../scheme_processors/token_import_scheme_processors/google_authenticator import '../token_import_file_processor/token_import_file_processor_interface.dart'; mixin TokenImportProcessor { - static const resultHandlerType = TypeMatcher(); + static const resultHandlerType = TypeValidatorRequired(); static Set implementations = { const GoogleAuthenticatorQrProcessor(), ...TokenImportFileProcessor.implementations, diff --git a/lib/processors/scheme_processors/container_credentials_processor.dart b/lib/processors/scheme_processors/container_credentials_processor.dart index d9b6426ac..8345f933f 100644 --- a/lib/processors/scheme_processors/container_credentials_processor.dart +++ b/lib/processors/scheme_processors/container_credentials_processor.dart @@ -17,9 +17,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; +import 'package:privacyidea_authenticator/utils/errors.dart'; +import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; - -import '../../model/enums/algorithms.dart'; import '../../model/processor_result.dart'; import '../../utils/identifiers.dart'; import 'scheme_processor_interface.dart'; @@ -28,7 +29,7 @@ import '../../utils/logger.dart'; import '../../model/tokens/container_credentials.dart'; class ContainerCredentialsProcessor extends SchemeProcessor { - static const resultHandlerType = TypeMatcher(); + static const resultHandlerType = TypeValidatorRequired(); static const scheme = 'pia'; static const host = 'container'; @@ -41,86 +42,23 @@ class ContainerCredentialsProcessor extends SchemeProcessor { if (!supportedSchemes.contains(uri.scheme)) return null; if (uri.host != host) return null; - // example: pia://container/SMPH00134123 - // ?issuer=privacyIDEA - // &nonce=887197025f5fa59b50f33c15196eb97ee651a5d1 - // &time=2024-08-21T07%3A43%3A07.086670%2B00%3A00 - // &url=http://127.0.0.1:5000/container/register/initialize - // &serial=SMPH00134123 - // &key_algorithm=secp384r1 - // &hash_algorithm=SHA256 - // &passphrase=Enter%20your%20passphrase - - final patameters = uri.queryParameters; - - final DateTime timeStamp; - final Uri finalizationUrl; - final EcKeyAlgorithm keyAlgorithm; - final Algorithms hashAlgorithm; try { - validateMap(patameters, { - 'issuer': const TypeMatcher(), - 'nonce': const TypeMatcher(), - 'time': const TypeMatcher(), - 'url': const TypeMatcher(), - 'serial': const TypeMatcher(), - 'key_algorithm': const TypeMatcher(), - 'hash_algorithm': const TypeMatcher(), - 'passphrase': const TypeMatcher(), - }); - timeStamp = DateTime.parse(patameters['time']!); - finalizationUrl = Uri.parse(patameters['url']!); - keyAlgorithm = EcKeyAlgorithm.values.byCurveName(patameters['key_algorithm']!); - hashAlgorithm = Algorithms.values.byName(patameters['hash_algorithm']!); - } catch (e) { - Logger.warning('Error while processing URI ${uri.scheme}', error: e, name: 'ContainerCredentialsProcessor#processUri'); + final credential = ContainerCredential.fromUriMap(uri.queryParameters); + Logger.info('Successfully parsed container credential', name: 'ContainerCredentialsProcessor#processUri'); + return [ + ProcessorResult.success( + credential, + resultHandlerType: resultHandlerType, + ) + ]; + } on LocalizedArgumentError catch (e) { + Logger.warning('Error while processing URI ${uri.scheme}', error: e.message, name: 'ContainerCredentialsProcessor#processUri'); return [ ProcessorResult.failed( - '(Invalid URI) Missing or invalid parameters: $e', + e.localizedMessage(AppLocalizations.of(await globalContext)!), resultHandlerType: resultHandlerType, ) ]; } - - final uriMap = { - URI_ISSUER: patameters['issuer'], - URI_NONCE: patameters['nonce'], - URI_TIMESTAMP: timeStamp, - URI_FINALIZATION_URL: finalizationUrl, - URI_SERIAL: patameters['serial'], - URI_KEY_ALGORITHM: keyAlgorithm, - URI_HASH_ALGORITHM: hashAlgorithm, - URI_PASSPHRASE: patameters['passphrase'], - }; - - final credential = ContainerCredential.fromUriMap(uriMap); - Logger.warning('Adding credential to container', name: 'ContainerCredentialsProcessor'); - return [ - ProcessorResult.success( - credential, - resultHandlerType: resultHandlerType, - ) - ]; } - - // static Future>> _container(Uri uri) async { - // try { - // final credential = ContainerCredential.fromUriMap(uri.queryParameters); - // Logger.info('Processing URI ${uri.scheme} succeded', name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); - // return [ - // ProcessorResult.success( - // credential, - // resultHandlerType: resultHandlerType, - // ) - // ]; - // } catch (e) { - // Logger.error('Error while processing URI ${uri.scheme}', error: e, name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); - // return [ - // ProcessorResult.failed( - // 'Invalid URI', - // resultHandlerType: resultHandlerType, - // ) - // ]; - // } - // } } diff --git a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart index 01dbce942..53b90334a 100644 --- a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart +++ b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart @@ -30,7 +30,7 @@ import '../../../views/link_home_widget_view/link_home_widget_view.dart'; import 'navigation_scheme_processor_interface.dart'; class HomeWidgetNavigateProcessor implements NavigationSchemeProcessor { - static const resultHandlerType = TypeMatcher(); + static const resultHandlerType = TypeValidatorRequired(); HomeWidgetNavigateProcessor(); static final Map>?> Function(Uri, BuildContext, {bool fromInit})> _processors = { @@ -151,7 +151,11 @@ class NavigationHandler with ResultHandler { Future handleProcessorResult(ProcessorResult result, Map args) async { if (result is! ProcessorResult) return null; if (result.isFailed) return null; - validateMap(args, {'context': const TypeMatcher()}); + validate( + value: args['context'], + validator: const TypeValidatorRequired(), + name: 'context', + ); final navigation = result.asSuccess!.resultData; final BuildContext context = args['context']; return await navigation(context); @@ -161,7 +165,20 @@ class NavigationHandler with ResultHandler { Future?> handleProcessorResults(List results, Map args) async { final successResults = results.whereType>().toList().successResults; if (successResults.isEmpty) return null; - validateMap(args, {'context': const TypeMatcher()}); + try { + validate( + value: args['context'], + validator: const TypeValidatorRequired(), + name: 'context', + ); + } catch (e, s) { + Logger.error( + 'Error while processing navigation results', + error: e, + stackTrace: s, + ); + return null; + } List navigations = successResults.getData(); final context = args['context']; final retunValues = []; 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 67ce5261d..511e9d559 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 @@ -19,24 +19,18 @@ */ import 'dart:typed_data'; -import 'package:collection/collection.dart'; - -import '../../../l10n/app_localizations.dart'; -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/enum_extension.dart'; import '../../../model/extensions/enums/encodings_extension.dart'; import '../../../model/extensions/enums/token_origin_source_type.dart'; import '../../../model/processor_result.dart'; import '../../../model/token_import/token_origin_data.dart'; import '../../../model/tokens/token.dart'; -import '../../../utils/errors.dart'; -import '../../../utils/globals.dart'; import '../../../utils/identifiers.dart'; import '../../../utils/logger.dart'; -import '../../../utils/utils.dart' show getCurrentAppName; +import '../../../utils/type_matchers.dart'; +import '../../../utils/utils.dart'; import '../../../utils/view_utils.dart'; import '../../../widgets/dialog_widgets/two_step_dialog.dart'; import 'token_import_scheme_processor_interface.dart'; @@ -47,6 +41,8 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { @override Set get supportedSchemes => {'otpauth'}; + /// This method parses otpauth uris according + /// to https://github.com/google/google-authenticator/wiki/Key-Uri-Format. @override Future>> processUri(Uri uri, {bool fromInit = false}) async { if (!supportedSchemes.contains(uri.scheme)) { @@ -58,53 +54,26 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { ]; } Logger.info('Try to handle otpAuth:', name: 'token_notifier.dart#addTokenFromOtpAuth'); - Map uriMap; - try { - uriMap = _parseOtpToken(uri); - } 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 = globalContextSync != null ? e.localizedMessage(AppLocalizations.of(globalContextSync!)!) : e.unlocalizedMessage; - return [ - ProcessorResult.failed( - message, - resultHandlerType: resultHandlerType, - ) - ]; - } - String? message; - if (e is ArgumentError) { - Logger.warning('Error while parsing otpAuth.', name: 'token_notifier.dart#addTokenFromOtpAuth', error: e.message, stackTrace: s); - message = '${e.message} - ${e.name}: ${e.invalidValue}'; - } - message ??= 'An error occurred while parsing the QR code.'; - return [ - ProcessorResult.failed( - globalContextSync != null ? AppLocalizations.of(globalContextSync!)?.tokenDataParseError ?? message : message, - resultHandlerType: resultHandlerType, - ) - ]; - } - if (_is2StepURI(uri)) { - validateMap(uriMap, { - URI_SECRET: const TypeMatcher(), - URI_ITERATIONS: const TypeMatcher(), - URI_OUTPUT_LENGTH_IN_BYTES: const TypeMatcher(), - URI_SALT_LENGTH: const TypeMatcher(), - }); - final secret = uriMap[URI_SECRET] as Uint8List; - // Calculate the whole secret. - - final twoStepSecret = (await showAsyncDialog( - barrierDismissible: false, - builder: (context) => GenerateTwoStepDialog( - iterations: uriMap[URI_ITERATIONS], - keyLength: uriMap[URI_OUTPUT_LENGTH_IN_BYTES], - saltLength: uriMap[URI_SALT_LENGTH], - password: secret, - ), - )); - if (twoStepSecret == null) { + // The values from queryParameters are always strings. + Map queryParameters = uri.queryParameters; + + queryParameters = validateMap( + map: queryParameters, + validators: {OTP_AUTH_SECRET_BASE32: const TypeValidatorRequired()}, + name: 'queryParameters', + ); + + final (label, issuer) = _parseLabelAndIssuer(uri); + queryParameters[OTP_AUTH_LABEL] = label; + queryParameters[OTP_AUTH_ISSUER] = issuer; + queryParameters[OTP_AUTH_TYPE] = _parseTokenType(uri); + queryParameters[OTP_AUTH_SECRET_BASE32] = _secretPadding(queryParameters[OTP_AUTH_SECRET_BASE32]!); + + _logInfo(uri); + final twoStepSecretFuture = _parse2StepSecret(uri); + if (twoStepSecretFuture != null) { + final twoStepSecretString = await twoStepSecretFuture; + if (twoStepSecretString == null) { return [ ProcessorResultFailed( 'The two step secret could not be generated, or was canceled.', @@ -112,36 +81,10 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { ) ]; } - uriMap[URI_SECRET] = twoStepSecret; - } - Token newToken; - try { - newToken = Token.fromUriMap(uriMap).copyWith( - origin: TokenOriginData( - appName: getCurrentAppName(), - 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, - resultHandlerType: resultHandlerType, - ) - ]; - } 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)); - return [ - ProcessorResultFailed( - 'An error occurred while parsing the QR code.', - resultHandlerType: resultHandlerType, - ) - ]; + // Update the secret with the two step secret. + queryParameters[OTP_AUTH_SECRET_BASE32] = twoStepSecretString; } + final newToken = Token.fromOtpAuthMap(queryParameters, origin: _parseCreatorToOrigin(uri)); return [ ProcessorResultSuccess( newToken, @@ -151,180 +94,168 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { } } -/// This method parses otpauth uris according -/// to https://github.com/google/google-authenticator/wiki/Key-Uri-Format. -Map _parseOtpToken(Uri uri) { - final type = uri.host; - if (TokenTypes.PIPUSH.isName(type, caseSensitive: false)) { - // otpauth://pipush/LABEL?PARAMETERS - return _parsePiPushToken(uri); +// Map _parseOtpToken(Uri uri) { +// final type = uri.host; +// if (TokenTypes.PIPUSH.isName(type, caseSensitive: false)) { +// // otpauth://pipush/LABEL?PARAMETERS +// return _parsePiPushToken(uri); +// } +// if (TokenTypes.values.firstWhereOrNull((element) => element.isName(type, caseSensitive: false)) != null) { +// return _parseOtpAuth(uri); +// } +// throw ArgumentError.value( +// 'Invalid type: $type', +// 'QrParser#_parseOtpToken', +// 'The type [$type] is not supported.', +// ); +// } + +TokenOriginData _parseCreatorToOrigin(Uri uri) { + final origin = TokenOriginSourceType.unknown.toTokenOrigin( + data: uri.toString(), + originName: getCurrentAppName(), + // If creator is present, it is a privacyIDEA token. If not it could be from an old version of the server too. + isPrivacyIdeaToken: uri.queryParameters[OTP_AUTH_CREATOR] != null ? true : null, + creator: uri.queryParameters[OTP_AUTH_CREATOR], + createdAt: DateTime.now(), + ); + return origin; +} + +/// Parse the label and the issuer (if it exists) from the url. +(String, String) _parseLabelAndIssuer(Uri uri) { + String label = ''; + String issuer = ''; + String param = uri.path.substring(1); + param = Uri.decodeFull(param); + + try { + if (param.contains(':')) { + List split = param.split(':'); + issuer = split[0]; + label = split[1]; + } else { + issuer = _parseIssuer(uri); + label = param; + } + } on Error { + label = param; } - if (TokenTypes.values.firstWhereOrNull((element) => element.isName(type, caseSensitive: false)) != null) { - return _parseOtpAuth(uri); + + return (label, issuer); +} + +String _parseIssuer(Uri uri) { + final param = validate( + value: uri.queryParameters[OTP_AUTH_ISSUER], + validator: const TypeValidatorRequired(defaultValue: ''), + name: OTP_AUTH_ISSUER, + ); + try { + return Uri.decodeFull(param); + } catch (_) { + return param; } - throw ArgumentError.value( - 'Invalid type: $type', - 'QrParser#_parseOtpToken', - 'The type [$type] is not supported.', +} + +bool _is2StepURI(Uri uri) { + final queryParameters = uri.queryParameters; + return queryParameters[OTP_AUTH_2STEP_SALT_LENTH] != null || + queryParameters[OTP_AUTH_2STEP_OUTPUT_LENTH] != null || + queryParameters[OTP_AUTH_2STEP_ITERATIONS] != null; +} + +/// This method parses the 2 step secret from the uri. +/// If its not a 2 step uri, it returns no Future (only null). +/// If the two step secret could not be generated, or was canceled, it returns a Future with null. +/// If the two step secret was generated, it returns a Future with the secret. +Future? _parse2StepSecret(Uri uri) { + final queryParameters = uri.queryParameters; + if (_is2StepURI(uri) == false) return null; + + validateMap( + map: queryParameters, + validators: { + OTP_AUTH_SECRET_BASE32: TypeValidatorRequired(transformer: (v) => Encodings.base32.decode(v)), + OTP_AUTH_2STEP_SALT_LENTH: stringToIntvalidator, + OTP_AUTH_2STEP_OUTPUT_LENTH: stringToIntvalidator, + OTP_AUTH_2STEP_ITERATIONS: stringToIntvalidator, + }, + name: '2StepSecret', ); + final secret = Encodings.base32.decode(queryParameters[OTP_AUTH_SECRET_BASE32]!); + // Calculate the whole secret. + + final twoStepSecret = showAsyncDialog( + barrierDismissible: false, + builder: (context) => GenerateTwoStepDialog( + iterations: int.parse(queryParameters[OTP_AUTH_2STEP_ITERATIONS]!), + keyLength: int.parse(queryParameters[OTP_AUTH_2STEP_OUTPUT_LENTH]!), + saltLength: int.parse(queryParameters[OTP_AUTH_2STEP_SALT_LENTH]!), + password: secret, + ), + ); + final twoStepSecretString = twoStepSecret.then((value) { + if (value == null) return null; + try { + return Encodings.base32.encode(value); + } catch (_) { + return null; + } + }); + return twoStepSecretString; } -const String _steamTokenIssuer = "Steam"; -Map _parseOtpAuth(Uri uri) { - // otpauth://TYPE/LABEL?PARAMETERS - final Map uriMap = {}; - // parse.host -> Type totp or hotp - uriMap[URI_TYPE] = uri.host; +void _logInfo(Uri uri) { // parse.path.substring(1) -> Label - var infoLog = '\nKey: [..] | Value: [..]'; + var infoLog = '\nKey: [..] | Value: [..]' '\n-----------------------'; final queryParameters = uri.queryParameters; queryParameters.forEach((key, value) { - if (key == URI_SECRET || key.toLowerCase().contains('secret')) { - value = '********'; - } infoLog += '\n${key.padLeft(9)} | $value'; }); Logger.info(infoLog, name: 'parsing_utils.dart#_parseOtpAuth'); +} - final (label, issuer) = _parseLabelAndIssuer(uri); - uriMap[URI_LABEL] = label; - uriMap[URI_ISSUER] = issuer; - if (issuer == _steamTokenIssuer) { - uriMap[URI_TYPE] = TokenTypes.STEAM.name; - } - - // parse pin from response 'True' - if (queryParameters['pin'] == 'True') { - uriMap[URI_PIN] = true; - } - - if (queryParameters['image'] != null) { - uriMap[URI_IMAGE] = queryParameters['image']; - } - - String algorithm = (queryParameters['algorithm'] ?? Algorithms.SHA1.name).toUpperCase(); // Optional parameter - algorithm = Algorithms.values.byName(algorithm).name; // Validate algorithm, throw error if not supported. - - uriMap[URI_ALGORITHM] = algorithm; +// This is a fix for omitted padding in base32 encoded secrets. +// +// According to https://github.com/google/google-authenticator/wiki/Key-Uri-Format, +// the padding can be omitted, but the libraries for base32 do not allow this. +String _secretPadding(String secret) => '$secret${secret.length % 2 == 1 ? '=' : ''}'; + +String _parseTokenType(Uri uri) { + if (_parseIssuer(uri) == "Steam") return TokenTypes.STEAM.name; + Logger.debug('Token type host: ${uri.host}', name: 'otp_auth_processor.dart#_parseTokenType'); + Logger.debug('Token type queryParameters: ${uri.queryParameters[OTP_AUTH_TYPE]}', name: 'otp_auth_processor.dart#_parseTokenType'); + final value = uri.queryParameters[OTP_AUTH_TYPE] ?? uri.host; + Logger.debug('Token type value: $value', name: 'otp_auth_processor.dart#_parseTokenType'); + return validate( + value: uri.queryParameters[OTP_AUTH_TYPE] ?? uri.host, + validator: TypeValidatorRequired(defaultValue: uri.host), + name: OTP_AUTH_TYPE, + ); +} - // Parse digits. - String digitsAsString = queryParameters['digits'] ?? '6'; // Optional parameter - if (digitsAsString != '6' && digitsAsString != '8') { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, name) => localizations.invalidValueForParameter(value, name), - unlocalizedMessage: '[$digitsAsString] is not a valid number of digits.', - invalidValue: digitsAsString, - name: 'digits', - ); - } - - int digits = int.parse(digitsAsString); +/* - uriMap[URI_DIGITS] = digits; - // Parse secret. - String? secretAsString = queryParameters['secret']; - ArgumentError.checkNotNull(secretAsString, 'secret'); - // This is a fix for omitted padding in base32 encoded secrets. - // - // According to https://github.com/google/google-authenticator/wiki/Key-Uri-Format, - // the padding can be omitted, but the libraries for base32 do not allow this. - if (secretAsString!.length % 2 == 1) { - secretAsString += '='; - } - secretAsString = secretAsString.toUpperCase(); - final secret = Encodings.base32.tryDecode(secretAsString); - if (secret == null) { - throw ArgumentError.value( - uri, - 'uri', - '[${Encodings.base32.name}] is not a valid encoding for [$secretAsString].', - ); - } + validateMap(queryParameters, { + OTP_AUTH_SECRET: validator(transformer: (v) => Encodings.base32.decode(v)), + OTP_AUTH_PIN: const validator(isOptional: true), + OTP_AUTH_IMAGE: const validator(isOptional: true), + OTP_AUTH_ALGORITHM: validator(transformer: (v) => Algorithms.values.byName(v), isOptional: true), + OTP_AUTH_DIGITS: validator(transformer: (v) => int.parse(v), isOptional: true), + OTP_AUTH_COUNTER: validator(transformer: (v) => int.parse(v), isOptional: true), + OTP_AUTH_PERIOD: validator(transformer: (v) => int.parse(v), isOptional: true), + }); - uriMap[URI_SECRET] = secret; - - // Parse counter. - String? counterString = 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: '[$counterString] is not a valid value for uri parameter [counter].', - invalidValue: counterString, - name: 'counter', - ); - } - } - // Parse period. - String? periodString = 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: 'Value [$periodString] for parameter [period] is invalid.', - invalidValue: periodString, - name: 'period', - ); - } - } - if (_is2StepURI(uri)) { - uriMap.addAll(_parse2StepURI(uri)); - } - // Parse creator. - uriMap[URI_ORIGIN] = _parseCreatorToOrigin(uri); - return uriMap; -} -Map _parse2StepURI(Uri uri) { - final Map uriMap2Step = {}; - final queryParameters = uri.queryParameters; - // Parse for 2 step roll out. - final String saltLengthAsString = queryParameters['2step_salt'] ?? '10'; - final String outputLengthInByteAsString = queryParameters['2step_output'] ?? '20'; - final String iterationsAsString = 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? @@ -336,145 +267,34 @@ Map _parsePiPushToken(Uri uri) { // &serial=PIPU0006EF87 // &sslverify=1 - final Map uriMap = {}; - final queryParameters = uri.queryParameters; - - uriMap[URI_TYPE] = uri.host; - - // If we do not support the version of this piauth url, we can stop here. - String? pushVersionAsString = queryParameters['v']; - if (pushVersionAsString == null) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, name) => localizations.missingRequiredParameter(name), - unlocalizedMessage: 'Parameter [v] is not an optional parameter and is missing.', - invalidValue: pushVersionAsString, - name: 'v', - ); - } - - try { - int pushVersion = int.parse(pushVersionAsString); +// Validate map for Push token + validateMap(queryParameters, { + OTP_AUTH_SECRET: const validator(), + OTP_AUTH_ALGORITHM: const validator(), + OTP_AUTH_SERIEL: const validator(), + OTP_AUTH_LABEL: const validator(isOptional: true), + OTP_AUTH_ISSUER: const validator(isOptional: true), + OTP_AUTH_PIN: const validator(isOptional: true), + OTP_AUTH_VERSION: stringToIntvalidator, + OTP_AUTH_TTL: stringToIntvalidatorOptional, + OTP_AUTH_IMAGE: validator(transformer: (v) => Uri.parse(v), isOptional: true), + OTP_AUTH_URL: validator(transformer: (v) => Uri.parse(v), isOptional: false), + }); - Logger.info('Parsing push token with version: $pushVersion', name: 'parsing_utils.dart#parsePiAuth'); + final pushVersion = queryParameters[OTP_AUTH_VERSION]! as int; - if (pushVersion > maxPushTokenVersion) { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, name) => localizations.unsupported(value, name), - unlocalizedMessage: 'The piauth version [$pushVersionAsString] is not supported by this version of the app.', - invalidValue: pushVersionAsString, - name: 'piauth version', - ); - } - } on FormatException { + if (pushVersion > maxPushTokenVersion) { throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: '[$pushVersionAsString] is not a valid value for parameter [v].', + localizedMessage: (localizations, value, name) => localizations.unsupported(value, name), + unlocalizedMessage: 'The piauth version [$pushVersionAsString] is not supported by this version of the app.', invalidValue: pushVersionAsString, - name: 'v', + name: 'piauth version', ); } + // String ttlAsString = queryParameters[OTP_AUTH_TTL] ?? '10'; + // uriMap[URI_SSL_VERIFY] = (queryParameters[OTP_AUTH_SSL_VERIFY] ?? '1') == '1'; - if (queryParameters['image'] != null) { - uriMap[URI_IMAGE] = queryParameters['image']; - } - - final (label, issuer) = _parseLabelAndIssuer(uri); - uriMap[URI_LABEL] = label; - uriMap[URI_ISSUER] = issuer; - uriMap[URI_SERIAL] = queryParameters['serial']; - ArgumentError.checkNotNull(uriMap[URI_SERIAL], 'serial'); - - final String? url = queryParameters['url']; - ArgumentError.checkNotNull(url, 'url'); - try { - uriMap[URI_ROLLOUT_URL] = Uri.parse(url!); - } on FormatException { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, name) => localizations.invalidValueForParameter(value, name), - unlocalizedMessage: '[$url] is not a valid Uri.', - invalidValue: url!, - name: 'url', - ); - } - - String ttlAsString = queryParameters['ttl'] ?? '10'; - try { - uriMap[URI_TTL] = int.parse(ttlAsString); - } on FormatException { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, parameter) => localizations.invalidValueForParameter(value, parameter), - unlocalizedMessage: '[$ttlAsString] is not a valid value for parameter [ttl].', - invalidValue: ttlAsString, - name: 'ttl', - ); - } - - uriMap[URI_ENROLLMENT_CREDENTIAL] = queryParameters['enrollment_credential']; - ArgumentError.checkNotNull(uriMap[URI_ENROLLMENT_CREDENTIAL], 'enrollment_credential'); - - uriMap[URI_SSL_VERIFY] = (queryParameters['sslverify'] ?? '1') == '1'; - - // parse pin from response 'True' - if (queryParameters['pin'] == 'True') { - uriMap[URI_PIN] = true; - } - - // Parse creator. - uriMap[URI_ORIGIN] = _parseCreatorToOrigin(uri); - - return uriMap; -} - -TokenOriginData _parseCreatorToOrigin(Uri uri) { - final origin = TokenOriginSourceType.unknown.toTokenOrigin( - data: uri.toString(), - originName: getCurrentAppName(), - // If creator is present, it is a privacyIDEA token. If not it could be from an old version of the server too. - isPrivacyIdeaToken: uri.queryParameters['creator'] != null ? true : null, - creator: uri.queryParameters['creator'], - createdAt: DateTime.now(), - ); - return origin; -} -/// Parse the label and the issuer (if it exists) from the url. -(String, String) _parseLabelAndIssuer(Uri uri) { - String label = ''; - String issuer = ''; - String param = uri.path.substring(1); - param = Uri.decodeFull(param); - try { - if (param.contains(':')) { - List split = param.split(':'); - issuer = split[0]; - label = split[1]; - } else { - issuer = _parseIssuer(uri); - label = param; - } - } on Error { - label = param; - } - - return (label, issuer); -} - -String _parseIssuer(Uri uri) { - String? issuer; - String? param = uri.queryParameters['issuer']; - - try { - issuer = Uri.decodeFull(param!); - } on Error { - issuer = param; - } - - return issuer ?? ''; -} - -bool _is2StepURI(Uri uri) { - final queryParameters = uri.queryParameters; - return queryParameters['2step_salt'] != null || queryParameters['2step_output'] != null || queryParameters['2step_difficulty'] != null; -} + */ \ No newline at end of file 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 ed6e4280b..ea191c228 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 @@ -27,11 +27,11 @@ import 'package:cryptography/cryptography.dart' as crypto; import 'package:encrypt/encrypt.dart'; import 'package:file_selector/file_selector.dart'; import 'package:pointycastle/export.dart'; +import 'package:privacyidea_authenticator/utils/type_matchers.dart'; import '../../l10n/app_localizations.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/processor_result.dart'; @@ -41,10 +41,11 @@ import '../../utils/globals.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; import '../../utils/token_import_origins.dart'; -import '../../utils/utils.dart'; import 'token_import_file_processor_interface.dart'; import 'two_fas_import_file_processor.dart'; +//TODO:Test Again + /// Args: [SendPort] sendPort, [ScryptParameters] scryptParameters, [String] password void _isolatedKdf(List args) { final SendPort sendPort = args[0] as SendPort; @@ -64,21 +65,44 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { static get resultHandlerType => TokenImportFileProcessor.resultHandlerType; const AegisImportFileProcessor(); + static const String AEGIS_JSON_HEADER = 'header'; + static const String AEGIS_HEADER_SLOTS = 'slots'; + static const String AEGIS_SLOT_KEYPARAMS = 'key_params'; + static const String AEGIS_KEYPARAMS_NONCE = 'nonce'; + static const String AEGIS_KEYPARAMS_TAG = 'tag'; + static const String AEGIS_SLOT_N = 'n'; + static const String AEGIS_SLOT_R = 'r'; + static const String AEGIS_SLOT_P = 'p'; + static const String AEGIS_SLOT_SALT = 'salt'; + static const String AEGIS_SLOT_KEY = 'key'; + static const String AEGIS_HEADER_HEADERPARAMS = 'params'; + static const String AEGIS_HEADERPARAMS_NONCE = 'nonce'; + static const String AEGIS_HEADERPARAMS_TAG = 'tag'; + static const String AEGIS_JSON_DB = 'db'; + static const String AEGIS_DB_VERSION = 'version'; + static const String AEGIS_DB_ENTRIES = 'entries'; + static const String AEGIS_ENTRY_INFO = 'info'; - static const String AEGIS_TYPE = 'type'; - static const String AEGIS_LABEL = 'name'; - static const String AEGIS_ISSUER = 'issuer'; - static const String AEGIS_SECRET = 'secret'; - static const String AEGIS_ALGORITHM = 'algo'; - static const String AEGIS_DIGITS = 'digits'; - static const String AEGIS_PERIOD = 'period'; - static const String AEGIS_COUNTER = 'counter'; - static const String AEGIS_PIN = 'pin'; - static const String AEGIS_ID = 'uuid'; + static const String AEGIS_ENTRY_TYPE = 'type'; + static const String AEGIS_ENTRY_ID = 'uuid'; + static const String AEGIS_ENTRY_LABEL = 'name'; + static const String AEGIS_ENTRY_ISSUER = 'issuer'; + static const String AEGIS_ENTRY_NOTE = 'note'; // Not used + static const String AEGIS_ENTRY_ICON = 'icon'; // Not used + static const String AEGIS_INFO_SECRET = 'secret'; + static const String AEGIS_INFO_ALGORITHM = 'algo'; + static const String AEGIS_INFO_DIGITS = 'digits'; + static const String AEGIS_INFO_PERIOD = 'period'; + static const String AEGIS_INFO_COUNTER = 'counter'; + static const String AEGIS_INFO_PIN = 'pin'; + static const String AEGIS_ENTRY_GROUPS = 'groups'; // Not used bool _isValidPlain(Map json) { try { - return json['db'] != null && json['db'] is Map && json['db']['entries'] != null && json['db']['entries'].length > 0; + return json[AEGIS_JSON_DB] != null && + json[AEGIS_JSON_DB] is Map && + json[AEGIS_JSON_DB][AEGIS_DB_ENTRIES] != null && + json[AEGIS_JSON_DB][AEGIS_DB_ENTRIES].length > 0; } catch (e) { return false; } @@ -86,11 +110,11 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { bool _isValidEncrypted(Map json) { try { - return json['header'] != null && - json['header']['slots'] != null && - (json['header']['slots'] as List).isNotEmpty && - json['db'] != null && - (json['db'] is String); + return json[AEGIS_JSON_HEADER] != null && + json[AEGIS_JSON_HEADER][AEGIS_HEADER_SLOTS] != null && + (json[AEGIS_JSON_HEADER][AEGIS_HEADER_SLOTS] as List).isNotEmpty && + json[AEGIS_JSON_DB] != null && + (json[AEGIS_JSON_DB] is String); } catch (e) { return false; } @@ -138,7 +162,7 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { } } - Future>> _processPlain(Map json) async => switch (json['db']['version'] as int) { + Future>> _processPlain(Map json) async => switch (json[AEGIS_JSON_DB][AEGIS_DB_VERSION] as int) { 2 => _processPlainV2(json), 3 => _processPlainV3(json), _ => _processPlainTryLatest(json), @@ -150,8 +174,8 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { } catch (_) { throw LocalizedArgumentError( localizedMessage: (localizations, value, name) => localizations.unsupported(name, value), - unlocalizedMessage: 'Unsupported backup version: ${json['db']['version']}.', - invalidValue: json['db']['version'], + unlocalizedMessage: 'Unsupported backup version: ${json[AEGIS_JSON_DB][AEGIS_DB_VERSION]}.', + invalidValue: json[AEGIS_JSON_DB][AEGIS_DB_VERSION], name: 'aegis backup version', ); } @@ -160,37 +184,45 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { Future>> _processPlainV2(Map json) { final results = >[]; final localization = globalContextSync != null ? AppLocalizations.of(globalContextSync!)! : null; - for (Map entry in json['db']['entries']) { + for (Map entry in json[AEGIS_JSON_DB][AEGIS_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']) ?? 'Unsupported token type: ${entry['type']}', - resultHandlerType: resultHandlerType, - )); - continue; - } - Map info = entry['info']; - final entryUriMap = { - URI_TYPE: entry[AEGIS_TYPE], - URI_LABEL: entry[AEGIS_LABEL], - URI_ISSUER: entry[AEGIS_ISSUER], - URI_SECRET: Encodings.none.decode(info[AEGIS_SECRET]), - URI_ALGORITHM: info[AEGIS_ALGORITHM], - URI_DIGITS: info[AEGIS_DIGITS], - URI_PERIOD: info[AEGIS_PERIOD], - URI_COUNTER: info[AEGIS_COUNTER], - URI_PIN: info[AEGIS_PIN], - URI_ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( + Map info = entry[AEGIS_ENTRY_INFO]; + final otpAuthMap = validateMap( + map: { + OTP_AUTH_TYPE: entry[AEGIS_ENTRY_TYPE], + OTP_AUTH_LABEL: entry[AEGIS_ENTRY_LABEL], + OTP_AUTH_ISSUER: entry[AEGIS_ENTRY_ISSUER], + OTP_AUTH_SECRET_BASE32: entry[AEGIS_INFO_SECRET], + OTP_AUTH_ALGORITHM: info[AEGIS_INFO_ALGORITHM], + OTP_AUTH_DIGITS: info[AEGIS_INFO_DIGITS], + OTP_AUTH_PERIOD_SECONDS: info[AEGIS_INFO_PERIOD], + OTP_AUTH_COUNTER: info[AEGIS_INFO_COUNTER], + OTP_AUTH_PIN: info[AEGIS_INFO_PIN], + }, + validators: { + OTP_AUTH_TYPE: const TypeValidatorRequired(), + OTP_AUTH_LABEL: const TypeValidatorRequired(defaultValue: ''), + OTP_AUTH_ISSUER: const TypeValidatorRequired(defaultValue: ''), + OTP_AUTH_SECRET_BASE32: TypeValidatorRequired(transformer: (v) => Encodings.none.encodeStringTo(Encodings.base32, info[AEGIS_INFO_SECRET])), + OTP_AUTH_ALGORITHM: const TypeValidatorOptional(), + OTP_AUTH_DIGITS: TypeValidatorOptional(transformer: (v) => (v as int).toString()), + OTP_AUTH_PERIOD_SECONDS: TypeValidatorOptional(transformer: (v) => (v as int).toString()), + OTP_AUTH_COUNTER: TypeValidatorOptional(transformer: (v) => (v as int).toString()), + OTP_AUTH_PIN: const TypeValidatorOptional(), + }, + name: 'aegisV2Entry', + ); + + final token = Token.fromOtpAuthMap( + otpAuthMap, + origin: TokenOriginSourceType.backupFile.toTokenOrigin( originName: TokenImportOrigins.aegisAuthenticator.appName, isPrivacyIdeaToken: false, data: jsonEncode(entry), ), - }; - final token = Token.fromUriMap(entryUriMap); + ); results.add(ProcessorResult.success( - token.copyWith(id: entry[AEGIS_ID]), + token.copyWith(id: entry[AEGIS_ENTRY_ID]), resultHandlerType: resultHandlerType, )); } on LocalizedException catch (e) { @@ -212,37 +244,44 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { Future>> _processPlainV3(Map json) { final results = >[]; final localization = globalContextSync != null ? AppLocalizations.of(globalContextSync!)! : null; - final entries = json['db']['entries'] as List; + final entries = json[AEGIS_JSON_DB][AEGIS_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']) ?? 'Unsupported token type: ${entry['type']}', - resultHandlerType: resultHandlerType, - )); - continue; - } - Map info = entry['info']; - final entryUriMap = { - URI_TYPE: entry[AEGIS_TYPE], - URI_LABEL: entry[AEGIS_LABEL], - URI_ISSUER: entry[AEGIS_ISSUER], - URI_SECRET: Encodings.base32.decode(info[AEGIS_SECRET]), - URI_ALGORITHM: info[AEGIS_ALGORITHM], - URI_DIGITS: info[AEGIS_DIGITS], - URI_PERIOD: info[AEGIS_PERIOD], - URI_COUNTER: info[AEGIS_COUNTER], - URI_PIN: info[AEGIS_PIN], - URI_ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( - originName: TokenImportOrigins.aegisAuthenticator.appName, - isPrivacyIdeaToken: false, - data: jsonEncode(entry), - ), - }; + Map info = entry[AEGIS_ENTRY_INFO]; + final otpAuthMap = validateMap( + map: { + OTP_AUTH_TYPE: entry[AEGIS_ENTRY_TYPE], + OTP_AUTH_LABEL: entry[AEGIS_ENTRY_LABEL], + OTP_AUTH_ISSUER: entry[AEGIS_ENTRY_ISSUER], + OTP_AUTH_SECRET_BASE32: info[AEGIS_INFO_SECRET], + OTP_AUTH_ALGORITHM: info[AEGIS_INFO_ALGORITHM], + OTP_AUTH_DIGITS: info[AEGIS_INFO_DIGITS], + OTP_AUTH_PERIOD_SECONDS: info[AEGIS_INFO_PERIOD], + OTP_AUTH_COUNTER: info[AEGIS_INFO_COUNTER], + OTP_AUTH_PIN: info[AEGIS_INFO_PIN], + }, + validators: { + OTP_AUTH_TYPE: const TypeValidatorRequired(), + OTP_AUTH_LABEL: const TypeValidatorRequired(defaultValue: ''), + OTP_AUTH_ISSUER: const TypeValidatorRequired(defaultValue: ''), + OTP_AUTH_SECRET_BASE32: TypeValidatorRequired(transformer: (v) => Encodings.none.encodeStringTo(Encodings.base32, v)), + OTP_AUTH_ALGORITHM: const TypeValidatorOptional(), + OTP_AUTH_DIGITS: intToStringValidatorOptional, + OTP_AUTH_PERIOD_SECONDS: intToStringValidatorOptional, + OTP_AUTH_COUNTER: intToStringValidatorOptional, + OTP_AUTH_PIN: const TypeValidatorOptional(), + }, + name: 'aegisV3Entry', + ); results.add(ProcessorResult.success( - Token.fromUriMap(entryUriMap), + Token.fromOtpAuthMap( + otpAuthMap, + origin: TokenOriginSourceType.backupFile.toTokenOrigin( + originName: TokenImportOrigins.aegisAuthenticator.appName, + isPrivacyIdeaToken: false, + data: jsonEncode(entry), + ), + ), resultHandlerType: resultHandlerType, )); } on LocalizedException catch (e) { @@ -274,26 +313,26 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { } Future>> _processEncrypted(Map json, String? password) async { - final String dbEncrypted = json['db']; - final Map header = json['header']; - final Map dbParams = header['params']; - final Map key = header['slots'].first; - final Map keyParams = key['key_params']; + final String dbEncrypted = json[AEGIS_JSON_DB]; + final Map header = json[AEGIS_JSON_HEADER]; + final Map headerParams = header[AEGIS_HEADER_HEADERPARAMS]; + final Map slot = header[AEGIS_HEADER_SLOTS].first; + final Map keyParams = slot[AEGIS_SLOT_KEYPARAMS]; final passwordKeyBytes = await runIsolatedKdf( - ScryptParameters(key['n'], key['r'], key['p'], 32, decodeHexString(key['salt'])), + ScryptParameters(slot[AEGIS_SLOT_N], slot[AEGIS_SLOT_R], slot[AEGIS_SLOT_P], 32, decodeHexString(slot[AEGIS_SLOT_SALT])), password!, ); - final slotNonceBytes = decodeHexString(keyParams['nonce']); + final slotNonceBytes = decodeHexString(keyParams[AEGIS_KEYPARAMS_NONCE]); final cipher = crypto.AesGcm.with256bits(nonceLength: slotNonceBytes.length); final List masterKeyBytes; try { masterKeyBytes = await cipher.decrypt( crypto.SecretBox( - decodeHexString(key['key']), + decodeHexString(slot[AEGIS_SLOT_KEY]), nonce: slotNonceBytes, - mac: crypto.Mac(decodeHexString(keyParams['tag'])), + mac: crypto.Mac(decodeHexString(keyParams[AEGIS_KEYPARAMS_TAG])), ), secretKey: crypto.SecretKey(passwordKeyBytes), ); @@ -303,13 +342,13 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { final dbDecryptedBytes = await cipher.decrypt( crypto.SecretBox( base64Decode(dbEncrypted), - nonce: decodeHexString(dbParams['nonce']), - mac: crypto.Mac(decodeHexString(dbParams['tag'])), + nonce: decodeHexString(headerParams[AEGIS_HEADERPARAMS_NONCE]), + mac: crypto.Mac(decodeHexString(headerParams[AEGIS_HEADERPARAMS_TAG])), ), secretKey: crypto.SecretKey(masterKeyBytes), ); final dbDecrypted = utf8.decode(dbDecryptedBytes); final dbJson = jsonDecode(dbDecrypted) as Map; - return _processPlain({'db': dbJson}); + return _processPlain({AEGIS_JSON_DB: dbJson}); } } 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 b9b55290e..7aab30285 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 @@ -23,14 +23,13 @@ import 'dart:convert'; import 'package:cryptography/cryptography.dart'; import 'package:file_selector/file_selector.dart'; +import 'package:privacyidea_authenticator/utils/type_matchers.dart'; import '../../l10n/app_localizations.dart'; import '../../model/encryption/uint_8_buffer.dart'; 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/processor_result.dart'; import '../../model/tokens/token.dart'; @@ -156,12 +155,13 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { return results.map((t) { if (t is! ProcessorResultSuccess) return t; - return ProcessorResultSuccess(TokenOriginSourceType.backupFile.addOriginToToken( - appName: TokenImportOrigins.authenticatorPro.appName, - token: t.resultData, - isPrivacyIdeaToken: false, - data: t.resultData.origin!.data, - ), + return ProcessorResultSuccess( + TokenOriginSourceType.backupFile.addOriginToToken( + appName: TokenImportOrigins.authenticatorPro.appName, + token: t.resultData, + isPrivacyIdeaToken: false, + data: t.resultData.origin!.data, + ), resultHandlerType: resultHandlerType, ); }).toList(); @@ -320,23 +320,39 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { Logger.warning('Unsupported token type: $typeInt'); continue; } - final uriMap = { - URI_TYPE: tokenType, - URI_ISSUER: tokenMap[_AUTHENTICATOR_PRO_ISSUER] as String, - URI_LABEL: tokenMap[_AUTHENTICATOR_PRO_LABEL] 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], - URI_COUNTER: tokenMap[_AUTHENTICATOR_PRO_COUNTER] as int, - URI_ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( + + final otpAuthMap = validateMap( + map: { + OTP_AUTH_TYPE: tokenType, + OTP_AUTH_ISSUER: tokenMap[_AUTHENTICATOR_PRO_ISSUER], + OTP_AUTH_LABEL: tokenMap[_AUTHENTICATOR_PRO_LABEL], + OTP_AUTH_SECRET_BASE32: tokenMap[_AUTHENTICATOR_PRO_SECRET], + OTP_AUTH_DIGITS: tokenMap[_AUTHENTICATOR_PRO_DIGITS], + OTP_AUTH_PERIOD_SECONDS: tokenMap[_AUTHENTICATOR_PRO_PERIOD], + OTP_AUTH_ALGORITHM: tokenMap[_AUTHENTICATOR_PRO_ALGORITHM], + OTP_AUTH_COUNTER: tokenMap[_AUTHENTICATOR_PRO_COUNTER], + }, + validators: { + OTP_AUTH_TYPE: const TypeValidatorRequired(), + OTP_AUTH_ISSUER: const TypeValidatorRequired(), + OTP_AUTH_LABEL: const TypeValidatorRequired(), + OTP_AUTH_SECRET_BASE32: const TypeValidatorRequired(), + OTP_AUTH_DIGITS: intToStringValidator, + OTP_AUTH_PERIOD_SECONDS: intToStringValidator, + OTP_AUTH_ALGORITHM: TypeValidatorRequired(transformer: (value) => algorithmMap[value]!), + OTP_AUTH_COUNTER: intToStringValidator, + }, + name: 'AuthenticatorProToken', + ); + + final token = Token.fromOtpAuthMap( + otpAuthMap, + origin: TokenOriginSourceType.backupFile.toTokenOrigin( originName: TokenImportOrigins.authenticatorPro.appName, isPrivacyIdeaToken: false, data: jsonEncode(tokenMap), ), - }; - - final token = Token.fromUriMap(uriMap); + ); result.add(ProcessorResultSuccess( token, resultHandlerType: resultHandlerType, @@ -348,7 +364,8 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { )); } catch (e) { Logger.error('Failed to parse token.', name: 'authenticator_pro_import_file_processor#_processAuthPro', error: e, stackTrace: StackTrace.current); - result.add(ProcessorResultFailed(e.toString(), + result.add(ProcessorResultFailed( + e.toString(), resultHandlerType: resultHandlerType, )); } diff --git a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart index 404dbfddc..d0eb76829 100644 --- a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart @@ -23,6 +23,9 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:file_selector/file_selector.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; +import 'package:privacyidea_authenticator/model/enums/encodings.dart'; +import 'package:privacyidea_authenticator/utils/type_matchers.dart'; import '../../l10n/app_localizations.dart'; import '../../model/enums/token_origin_source_type.dart'; @@ -89,7 +92,8 @@ class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { results.addAll(await const FreeOtpPlusQrProcessor().processUri(uri)); } catch (e) { Logger.error('Failed to process line: $line', name: 'FreeOtpPlusFileProcessor#processFile', error: e, stackTrace: StackTrace.current); - results.add(ProcessorResultFailed(e.toString(), + results.add(ProcessorResultFailed( + e.toString(), resultHandlerType: resultHandlerType, )); } @@ -123,7 +127,14 @@ class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { Future> _processJsonToken(Map tokenJson) async { try { return ProcessorResultSuccess( - Token.fromUriMap(_jsonToUriMap(tokenJson)), + Token.fromOtpAuthMap( + _jsonToOtpAuth(tokenJson), + origin: TokenOriginSourceType.backupFile.toTokenOrigin( + originName: TokenImportOrigins.freeOtpPlus.appName, + isPrivacyIdeaToken: false, + data: jsonEncode(tokenJson), + ), + ), resultHandlerType: resultHandlerType, ); } on LocalizedException catch (e) { @@ -131,8 +142,8 @@ class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { e.localizedMessage(AppLocalizations.of(await globalContext)!), resultHandlerType: resultHandlerType, ); - } catch (e) { - Logger.error('Failed to parse token.', name: 'FreeOtpPlusFileProcessor#_processJsonToken', error: e, stackTrace: StackTrace.current); + } catch (e, s) { + Logger.warning('Failed to parse token.', name: 'FreeOtpPlusFileProcessor#_processJsonToken', error: e, stackTrace: s); return ProcessorResultFailed( e.toString(), resultHandlerType: resultHandlerType, @@ -140,22 +151,29 @@ class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { } } - Map _jsonToUriMap(Map tokenJson) { - return { - /// Steam is a special case, its hardcoded in the original app. - URI_TYPE: tokenJson[_FREE_OTP_PLUS_ISSUER] == _steamTokenIssuer ? _steamTokenType : tokenJson[_FREE_OTP_PLUS_TYPE].toLowerCase(), - URI_LABEL: tokenJson[_FREE_OTP_PLUS_LABEL], - URI_SECRET: Uint8List.fromList((tokenJson[_FREE_OTP_PLUS_SECRET] as List).cast()), - 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] + 1, // FreeOTP+ saves only in JSON as 0-based counter - URI_PERIOD: tokenJson[_FREE_OTP_PLUS_PERIOD], - URI_ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( - originName: TokenImportOrigins.freeOtpPlus.appName, - isPrivacyIdeaToken: false, - data: jsonEncode(tokenJson), - ), - }; - } + Map _jsonToOtpAuth(Map tokenJson) => validateMap( + name: 'FreeOtpPlusToken', + map: { + /// Steam is a special case, its hardcoded in the original app. + OTP_AUTH_TYPE: tokenJson[_FREE_OTP_PLUS_ISSUER] == _steamTokenIssuer ? _steamTokenType : tokenJson[_FREE_OTP_PLUS_TYPE], + OTP_AUTH_LABEL: tokenJson[_FREE_OTP_PLUS_LABEL], + OTP_AUTH_SECRET_BASE32: tokenJson[_FREE_OTP_PLUS_SECRET], + OTP_AUTH_ISSUER: tokenJson[_FREE_OTP_PLUS_ISSUER], + OTP_AUTH_ALGORITHM: tokenJson[_FREE_OTP_PLUS_ALGORITHM], + OTP_AUTH_DIGITS: tokenJson[_FREE_OTP_PLUS_DIGITS], + OTP_AUTH_COUNTER: tokenJson[_FREE_OTP_PLUS_COUNTER], + OTP_AUTH_PERIOD_SECONDS: tokenJson[_FREE_OTP_PLUS_PERIOD], + }, + validators: { + OTP_AUTH_TYPE: const TypeValidatorRequired(), + OTP_AUTH_LABEL: const TypeValidatorRequired(), + OTP_AUTH_SECRET_BASE32: + TypeValidatorRequired(transformer: (value) => Encodings.base32.encode(Uint8List.fromList((value as List).cast()))), + OTP_AUTH_ISSUER: const TypeValidatorRequired(), + OTP_AUTH_ALGORITHM: const TypeValidatorRequired(), + OTP_AUTH_DIGITS: intToStringValidator, + OTP_AUTH_COUNTER: intToStringValidatorOptional, + OTP_AUTH_PERIOD_SECONDS: intToStringValidatorOptional, + }, + ); } diff --git a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart index f3658c46d..cf27dc7ce 100644 --- a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart +++ b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart @@ -73,10 +73,11 @@ class GoogleAuthenticatorQrfileProcessor extends TokenImportFileProcessor { Logger.info("Cropped image to square: ${qrImage.width}x${qrImage.height}"); } - var progress = globalRef?.read(progressStateProvider.notifier).initProgress(maxZoomLevel * 360, 0).progress; - for (var zoomLevel = 0; zoomLevel <= maxZoomLevel && qrResult == null && progress != null; zoomLevel++) { - for (var rotation = 0; rotation < 360 && progress != null; rotation += 90) { - globalRef?.read(progressStateProvider.notifier).setProgressValue(zoomLevel * 360 + rotation); + // var progress = globalRef?.read(progressStateProvider.notifier).initProgress(maxZoomLevel * 360, 0).progress; + for (var zoomLevel = 0; zoomLevel <= maxZoomLevel && qrResult == null; zoomLevel++) { + for (var rotation = 0; rotation < 360; rotation += 90) { + // await Future.delayed(const Duration(milliseconds: 1)); + // globalRef?.read(progressStateProvider.notifier).setProgressValue(zoomLevel * 360 + rotation); try { qrResult = await compute(_decodeQrImageIsolate, [qrImage, rotation, zoomLevel]); @@ -87,10 +88,10 @@ class GoogleAuthenticatorQrfileProcessor extends TokenImportFileProcessor { } on NotFoundException catch (_) { Logger.info("Qr-Code not detected. Zoom level: $zoomLevel|rotation: $rotation"); } - progress = globalRef?.read(progressStateProvider).progress; + // progress = globalRef?.read(progressStateProvider).progress; } } - if (qrResult == null || progress == null) { + if (qrResult == null) { Logger.warning("Error decoding QR file..", name: "_pickQrFile#ImportStartPage"); throw NotFoundException(); } 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 1d2a06e16..096afed34 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 @@ -23,11 +23,10 @@ import 'dart:convert'; import 'package:cryptography/cryptography.dart'; import 'package:file_selector/file_selector.dart'; +import 'package:privacyidea_authenticator/utils/type_matchers.dart'; import '../../l10n/app_localizations.dart'; -import '../../model/enums/encodings.dart'; import '../../model/enums/token_origin_source_type.dart'; -import '../../model/extensions/enums/encodings_extension.dart'; import '../../model/extensions/enums/token_origin_source_type.dart'; import '../../model/processor_result.dart'; import '../../model/tokens/token.dart'; @@ -42,6 +41,7 @@ import 'token_import_file_processor_interface.dart'; class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { static get resultHandlerType => TokenImportFileProcessor.resultHandlerType; const TwoFasAuthenticatorImportFileProcessor(); + static const String TWOFAS_OTP = 'otp'; static const String TWOFAS_TYPE = 'tokenType'; static const String TWOFAS_ISSUER = 'name'; static const String TWOFAS_SECRET = 'secret'; @@ -147,7 +147,14 @@ class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { for (Map twoFasToken in tokensJsonList) { try { results.add(ProcessorResult.success( - Token.fromUriMap(_twoFasToUriMap(twoFasToken)), + Token.fromOtpAuthMap( + _twoFasToOtpAuth(twoFasToken), + origin: TokenOriginSourceType.backupFile.toTokenOrigin( + originName: TokenImportOrigins.twoFasAuthenticator.appName, + isPrivacyIdeaToken: false, + data: jsonEncode(twoFasToken), + ), + ), resultHandlerType: resultHandlerType, )); } on LocalizedException catch (e) { @@ -157,7 +164,8 @@ class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { )); } catch (e) { Logger.error('Failed to parse token.', name: 'two_fas_import_file_processor.dart#_processPlainTokens', error: e, stackTrace: StackTrace.current); - results.add(ProcessorResultFailed(e.toString(), + results.add(ProcessorResultFailed( + e.toString(), resultHandlerType: resultHandlerType, )); } @@ -166,23 +174,31 @@ class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { return results; } - Map _twoFasToUriMap(Map twoFasToken) { - final twoFasOTP = twoFasToken['otp']; - return { - URI_TYPE: twoFasOTP[TWOFAS_TYPE], - URI_ISSUER: twoFasToken[TWOFAS_ISSUER], - 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( - originName: TokenImportOrigins.twoFasAuthenticator.appName, - isPrivacyIdeaToken: false, - data: jsonEncode(twoFasToken), - ), - }; + Map _twoFasToOtpAuth(Map twoFasToken) { + Map twoFasOTP = twoFasToken[TWOFAS_OTP]; + return validateMap( + map: { + OTP_AUTH_TYPE: twoFasOTP[TWOFAS_TYPE], + OTP_AUTH_ISSUER: twoFasOTP[TWOFAS_ISSUER], + OTP_AUTH_LABEL: twoFasOTP[TWOFAS_LABEL], + OTP_AUTH_SECRET_BASE32: twoFasToken[TWOFAS_SECRET], + OTP_AUTH_ALGORITHM: twoFasOTP[TWOFAS_ALGORITHM], + OTP_AUTH_DIGITS: twoFasOTP[TWOFAS_DIGITS], + OTP_AUTH_PERIOD_SECONDS: twoFasOTP[TWOFAS_PERIOD], + OTP_AUTH_COUNTER: twoFasOTP[TWOFAS_COUNTER], + }, + validators: { + OTP_AUTH_TYPE: const TypeValidatorRequired(), + OTP_AUTH_ISSUER: const TypeValidatorOptional(), + OTP_AUTH_LABEL: const TypeValidatorOptional(), + OTP_AUTH_SECRET_BASE32: const TypeValidatorRequired(), + OTP_AUTH_ALGORITHM: const TypeValidatorOptional(), + OTP_AUTH_DIGITS: intToStringValidatorOptional, + OTP_AUTH_PERIOD_SECONDS: intToStringValidatorOptional, + OTP_AUTH_COUNTER: intToStringValidatorOptional, + }, + name: '2FAS token', + ); } } diff --git a/lib/repo/secure_container_credentials_repository.dart b/lib/repo/secure_container_credentials_repository.dart index a4d311228..6203718cb 100644 --- a/lib/repo/secure_container_credentials_repository.dart +++ b/lib/repo/secure_container_credentials_repository.dart @@ -4,7 +4,6 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:mutex/mutex.dart'; import '../interfaces/repo/container_credentials_repository.dart'; -import '../model/enums/algorithms.dart'; import '../model/riverpod_states/credentials_state.dart'; import '../model/tokens/container_credentials.dart'; import '../utils/logger.dart'; @@ -26,24 +25,6 @@ class SecureContainerCredentialsRepository extends ContainerCredentialsRepositor Future loadCredentialsState() async { final credentialsJsonString = await _readAll(); Logger.warning('Loaded credentials: $credentialsJsonString', name: 'SecureContainerCredentialsRepository'); - if (credentialsJsonString.isEmpty) { - final credentialState = CredentialsState(credentials: [ - ContainerCredential.finalized( - serial: '123', - ecKeyAlgorithm: EcKeyAlgorithm.secp256k1, - hashAlgorithm: Algorithms.SHA256, - issuer: '', - nonce: '', - timestamp: DateTime.now(), - finalizationUrl: Uri(), - publicServerKey: '', - publicClientKey: '', - privateClientKey: '', - ), - ]); - Logger.warning('Returning default credentials: $credentialState', name: 'SecureContainerCredentialsRepository'); - return credentialState; - } return CredentialsState.fromJsonStringList(credentialsJsonString.values.toList()); } diff --git a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart index 72c91bac0..adfa078c7 100644 --- a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart @@ -1,50 +1,50 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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:mutex/mutex.dart'; -import '../../utils/logger.dart'; - -import '../../api/token_container_api_endpoint.dart'; -import '../../interfaces/repo/container_repository.dart'; -import '../../model/token_container.dart'; - -class RemoteTokenContainerRepository implements TokenContainerRepository { - final TokenContainerApiEndpoint apiEndpoint; - final Mutex _m = Mutex(); - - Future _protect(Future Function() f) async => _m.protect(f); - - RemoteTokenContainerRepository({required this.apiEndpoint}); - - @override - Future saveContainerState(TokenContainer containerState) async => await _saveContainerState(containerState); - - Future _saveContainerState(TokenContainer containerState) async { - Logger.info('Saving container state', name: 'RemoteTokenContainerRepository'); - return await _protect(() async => (await apiEndpoint.sync(containerState)).copyTransformInto()); - } - - @override - Future loadContainerState() { - Logger.info('Loading container state', name: 'RemoteTokenContainerRepository'); - return _fetchContainerState(); - } - - Future _fetchContainerState() async => await _protect(() async => await apiEndpoint.fetch()); -} +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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:mutex/mutex.dart'; +// import '../../utils/logger.dart'; + +// import '../../api/token_container_api_endpoint.dart'; +// import '../../interfaces/repo/container_repository.dart'; +// import '../../model/token_container.dart'; + +// class RemoteTokenContainerRepository implements TokenContainerRepository { +// final TokenContainerApiEndpoint apiEndpoint; +// final Mutex _m = Mutex(); + +// Future _protect(Future Function() f) async => _m.protect(f); + +// RemoteTokenContainerRepository({required this.apiEndpoint}); + +// @override +// Future saveContainerState(TokenContainer containerState) async => await _saveContainerState(containerState); + +// Future _saveContainerState(TokenContainer containerState) async { +// Logger.info('Saving container state', name: 'RemoteTokenContainerRepository'); +// return await _protect(() async => (await apiEndpoint.sync(containerState)).copyTransformInto()); +// } + +// @override +// Future loadContainerState() { +// Logger.info('Loading container state', name: 'RemoteTokenContainerRepository'); +// return _fetchContainerState(); +// } + +// Future _fetchContainerState() async => await _protect(() async => await apiEndpoint.fetch()); +// } diff --git a/lib/utils/ecc_utils.dart b/lib/utils/ecc_utils.dart new file mode 100644 index 000000000..7c2ca00e1 --- /dev/null +++ b/lib/utils/ecc_utils.dart @@ -0,0 +1,38 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:typed_data'; + +import 'package:basic_utils/basic_utils.dart'; + +class EccUtils { + const EccUtils(); + + String serializeECPublicKey(ECPublicKey publicKey) => CryptoUtils.encodeEcPublicKeyToPem(publicKey); + ECPublicKey deserializeECPublicKey(String ecPublicKey) => CryptoUtils.ecPublicKeyFromPem(ecPublicKey); + String serializeECPrivateKey(ECPrivateKey ecPrivateKey) => CryptoUtils.encodeEcPrivateKeyToPem(ecPrivateKey); + ECPrivateKey deserializeECPrivateKey(String ecPrivateKey) => CryptoUtils.ecPrivateKeyFromPem(ecPrivateKey); + + String trySignWithPrivateKey(ECPrivateKey privateKey, String message) { + final ecSignature = CryptoUtils.ecSign(privateKey, Uint8List.fromList(message.codeUnits), algorithmName: 'SHA-256/ECDSA'); + String signatureBase64 = CryptoUtils.ecSignatureToBase64(ecSignature); + return signatureBase64; + } +} diff --git a/lib/utils/errors.dart b/lib/utils/errors.dart index bdd55fc36..807a2a030 100644 --- a/lib/utils/errors.dart +++ b/lib/utils/errors.dart @@ -19,15 +19,15 @@ */ import '../l10n/app_localizations.dart'; -class LocalizedArgumentError extends LocalizedException implements ArgumentError { - final T _invalidValue; +class LocalizedArgumentError extends LocalizedException implements ArgumentError { + final String _invalidValue; final String? _name; final StackTrace? _stackTrace; factory LocalizedArgumentError({ - required String Function(AppLocalizations localizations, T value, String name) localizedMessage, + required String Function(AppLocalizations localizations, String valueString, String name) localizedMessage, required String unlocalizedMessage, - required T invalidValue, + required String invalidValue, required String name, StackTrace? stackTrace, }) => diff --git a/lib/utils/identifiers.dart b/lib/utils/identifiers.dart index 3b5a36ec1..b2a7f1984 100644 --- a/lib/utils/identifiers.dart +++ b/lib/utils/identifiers.dart @@ -22,39 +22,72 @@ // default email address for crash reports +import 'package:privacyidea_authenticator/utils/errors.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; + const defaultCrashReportRecipient = 'app-crash@netknights.it'; -// qr codes: -const String URI_ID = 'URI_ID'; -const String URI_SERIAL = 'URI_SERIAL'; -const String URI_NONCE = 'URI_NONCE'; -const String URI_TIMESTAMP = 'URI_TIMESTAMP'; -const String URI_FINALIZATION_URL = 'URI_FINALIZATION_URL'; -const String URI_KEY_ALGORITHM = 'URI_KEY_ALGORITHM'; -const String URI_HASH_ALGORITHM = 'URI_HASH_ALGORITHM'; -const String URI_CONTAINER_SERIAL = 'URI_CONTAINER_SERIAL'; -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 Uint8List -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_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'; +// otp auth +const OTP_AUTH_VERSION = 'v'; +const OTP_AUTH_CREATOR = 'creator'; +const OTP_AUTH_TYPE = 'type'; + +/// [String] (optional) default = null +const OTP_AUTH_SERIAL = 'serial'; + +/// [String] (required) +const OTP_AUTH_SECRET_BASE32 = 'secret'; + +/// [String] (optional) default =' 0' +const OTP_AUTH_COUNTER = 'counter'; + +/// [String] (optional) default = '30' +const OTP_AUTH_PERIOD_SECONDS = 'period'; // int optional default 30 +/// [String] (optional) default = 'SHA1' +const OTP_AUTH_ALGORITHM = 'algorithm'; // String optional default 'SHA1' +/// [String] (optional) default = '6' +const OTP_AUTH_DIGITS = 'digits'; // int optional default 6 +/// [String] (optional) default = '' +const OTP_AUTH_LABEL = 'label'; // String optional default '' +/// [String] (optional) default = '' +const OTP_AUTH_ISSUER = 'issuer'; // String optional default '' +/// [String] (optional) default = '' +const OTP_AUTH_PIN = 'pin'; // String optional default "False" +/// [String] (optional) default = 'False' +const OTP_AUTH_PIN_TRUE = 'True'; +const OTP_AUTH_PIN_FALSE = 'False'; + +/// [String] (optional) default = '' +const OTP_AUTH_IMAGE = 'image'; // String optional default '' + +// OTP auth push + +/// [String] (required for PUSH) +const OTP_AUTH_PUSH_ROLLOUT_URL = 'url'; +const OTP_AUTH_PUSH_TTL_MINUTES = 'ttl'; + +/// [String] (optional) default = null +const OTP_AUTH_PUSH_ENROLLMENT_CREDENTIAL = 'enrollment_credential'; +const OTP_AUTH_PUSH_SSL_VERIFY = 'sslverify'; // String optional default '1' +const OTP_AUTH_PUSH_SSL_VERIFY_TRUE = '1'; +const OTP_AUTH_PUSH_SSL_VERIFY_FALSE = '0'; + +// otp auth 2step + +/// [String] (required for 2step) +const OTP_AUTH_2STEP_SALT_LENTH = '2step_salt'; + +/// [String] (required for 2step) +const OTP_AUTH_2STEP_OUTPUT_LENTH = '2step_output'; + +/// [String] (required for 2step) +const OTP_AUTH_2STEP_ITERATIONS = '2step_difficulty'; + +// Container otp sync + +const OTP_AUTH_OTP_VALUES = 'otp_values'; + +const OTP_AUTH_STEAM_ISSUER = 'Steam'; // Crypto stuff: const String SIGNING_ALGORITHM = 'SHA-256/RSA'; @@ -73,27 +106,121 @@ const String PUSH_REQUEST_SIGNATURE = 'signature'; // 7. const String PUSH_REQUEST_ANSWERS = 'require_presence'; // 8. // Container registration: -const String PUBLIC_SERVER_KEY = 'PUBLIC_SERVER_KEY'; -const String URI_PASSPHRASE = 'URI_PASSPHRASE'; +const String CONTAINER_ISSUER = 'issuer'; +const String CONTAINER_NONCE = 'nonce'; +const String CONTAINER_TIMESTAMP = 'time'; +const String CONTAINER_FINALIZATION_URL = 'url'; +const String CONTAINER_SERIAL = 'serial'; +const String CONTAINER_EC_KEY_ALGORITHM = 'key_algorithm'; +const String CONTAINER_HASH_ALGORITHM = 'hash_algorithm'; +const String CONTAINER_PASSPHRASE_QUESTION = 'passphrase'; const String GLOBAL_SECURE_REPO_PREFIX = 'app_v3_'; -void validateMap(Map map, Map keys) { - for (String key in keys.keys) { - final typeMatcher = keys[key]!; +T? validateOptional({required dynamic value, required TypeValidatorOptional validator, required String name}) { + if (validator.isTypeOf(value)) { + return validator.transform(value); + } else { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, name) => localizations.invalidValue(value.runtimeType.toString(), value, name), + unlocalizedMessage: 'The ${value.runtimeType} "$value" is not valid for "$name"', + invalidValue: value, + name: name, + ); + } +} + +T validate({required dynamic value, required TypeValidatorRequired validator, required String name}) { + if (validator.isTypeOf(value)) { + return validator.transform(value); + } else { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, name) => localizations.invalidValue(value.runtimeType.toString(), value, name), + unlocalizedMessage: 'The ${value.runtimeType} "$value" is not valid for "$name"', + invalidValue: value, + name: name, + ); + } +} + +/// Validates a map by checking if it contains all required keys and if the values are of the correct type. +/// +/// Throws a [LocalizedArgumentError] if the map is invalid. +///
If the validator provides a transformer function, the value will be transformed before checking the type. +///
The returned map will contain the transformed values. +Map validateMap({required Map map, required Map> validators, required String? name}) { + Map validatedMap = {}; + for (String key in validators.keys) { + final validator = validators[key]!; final mapEntry = map[key]; - if (!typeMatcher.isTypeOf(map[key])) { + if (validator.isTypeOf(mapEntry)) { + final newValue = validator.transform(mapEntry); + if (newValue != null) validatedMap[key] = newValue; + } else { if (mapEntry == null) { - throw ArgumentError('Map does not contain required key "$key"'); + throw LocalizedArgumentError( + localizedMessage: name != null + ? (localizations, value, key) => localizations.missingRequiredParameterIn(key, name) + : (localizations, value, key) => localizations.missingRequiredParameter(key), + unlocalizedMessage: 'Map does not contain required key "$key"', + invalidValue: mapEntry.toString(), + name: key, + ); } - throw ArgumentError('Map does contain required key "$key" but ${mapEntry.runtimeType} is not a subtype of ${typeMatcher.type}'); + throw LocalizedArgumentError( + localizedMessage: name != null + ? (localizations, value, key) => localizations.invalidValueIn(value.runtimeType.toString(), value.toString(), key, name) + : (localizations, value, key) => localizations.invalidValue(value.runtimeType.toString(), value.toString(), key), + unlocalizedMessage: 'The ${mapEntry.runtimeType} "$mapEntry" is not valid for "$key"${name != null ? ' in $name' : ''}', + invalidValue: mapEntry.toString(), + name: key, + ); } } + return validatedMap; } -class TypeMatcher { - const TypeMatcher(); - bool isTypeOf(dynamic value) => value is T; +class TypeValidatorOptional { + bool get isOptional => runtimeType.toString().contains('Optional'); + + final T Function(dynamic value)? transformer; + final T? defaultValue; + + const TypeValidatorOptional({ + this.transformer, + this.defaultValue, + }); + + /// Checks if the value is of the correct type, or sub-type. + /// If the transformer is provided, the value will be transformed before checking the type. + bool isTypeOf(dynamic value) { + Logger.debug('Checking type of $value and default value $defaultValue with transformer $transformer (isOptional $isOptional)'); + if (value == null) return (defaultValue != null) ? true : isOptional; + + if (transformer == null) return value is T?; + try { + transformer!(value); + return true; + } catch (e) { + return false; + } + } + + /// Transforms the value if a transformer is provided, otherwise returns the value as is. + /// May throw an error if the value is not of the correct type. + /// To prevent an error, use isTypeOf before calling transform. + T? transform(dynamic value) { + if (value == null) return defaultValue; + if (transformer == null) return value as T; + try { + return transformer!(value); + } catch (e) { + return defaultValue; + } + } + + TypeValidatorOptional optional() => TypeValidatorOptional(transformer: transformer, defaultValue: defaultValue); + TypeValidatorOptional withDefault(T? defaultValue) => TypeValidatorOptional(transformer: transformer, defaultValue: defaultValue); String get type => RegExp('(?<=<).+(?=>)').firstMatch(toString())!.group(0)!; @@ -101,8 +228,35 @@ class TypeMatcher { String toString() => runtimeType.toString(); @override - bool operator ==(Object other) => other is TypeMatcher; + bool operator ==(Object other) => other is TypeValidatorOptional; @override int get hashCode => toString().hashCode; } + +class TypeValidatorRequired extends TypeValidatorOptional { + const TypeValidatorRequired({ + super.transformer, + super.defaultValue, + }); + + /// Transforms the value if a transformer is provided, otherwise returns the value as is. + /// May throw an error if the value is not of the correct type. + /// To prevent an error, use isTypeOf before calling transform. + @override + T transform(dynamic value) { + try { + if (value == null) return defaultValue!; + if (transformer == null) return value is T ? value : defaultValue!; + return transformer!(value); + } catch (e) { + if (defaultValue != null) return defaultValue!; + throw LocalizedArgumentError( + localizedMessage: (localizations, value, name) => localizations.invalidValue(value.runtimeType.toString(), value, name), + unlocalizedMessage: 'The type ${value.runtimeType} for value "$value" is not valid.', + invalidValue: value, + name: type, + ); + } + } +} diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart index b1a33f5db..f80fdc563 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart @@ -24,6 +24,7 @@ import 'package:basic_utils/basic_utils.dart'; import 'package:collection/collection.dart'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/ec_key_algorithm_extension.dart'; import 'package:privacyidea_authenticator/model/processor_result.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; @@ -32,10 +33,14 @@ import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/stat import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../../../interfaces/repo/container_credentials_repository.dart'; +import '../../../../l10n/app_localizations.dart'; +import '../../../../model/enums/container_finalization_state.dart'; import '../../../../model/riverpod_states/credentials_state.dart'; import '../../../../model/tokens/container_credentials.dart'; import '../../../../repo/secure_container_credentials_repository.dart'; import '../../../../widgets/dialog_widgets/enter_passphrase_dialog.dart'; +import '../../../ecc_utils.dart'; +import '../../../errors.dart'; import '../../../logger.dart'; part 'credential_notifier.g.dart'; @@ -264,6 +269,9 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R await updateCredential(credential, (c) => c.finalize(publicServerKey: publicServerKey)!); } on StateError { Logger.info('Container was removed while finalizing', name: 'CredentialsNotifier#finalize'); + } on LocalizedArgumentError catch (e) { + ref.read(statusMessageProvider.notifier).state = ('Failed to decode response body', e.localizedMessage(AppLocalizations.of(await globalContext)!)); + await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseFailed)); } catch (e) { Logger.error('Failed to finalize container ${credential.serial}', name: 'CredentialsNotifier#finalize', error: e); _finalizationMutex.release(); @@ -304,13 +312,13 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R // 'signature': )>, // } - final passphrase = container.passphrase != null ? EnterPassphraseDialog.show(await globalContext) : null; + final passphrase = container.passphraseQuestion != null ? EnterPassphraseDialog.show(await globalContext) : null; final message = '${container.nonce}' '|${container.timestamp.toIso8601String().replaceFirst('Z', '+00:00')}' '|${container.finalizationUrl}' '|${container.serial}' '${passphrase != null ? '|$passphrase' : ''}'; - print(message); + final signature = eccUtils.trySignWithPrivateKey(ecPrivateClientKey, message); final body = { @@ -351,19 +359,14 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R Map responseJson; credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponse)); if (credential == null) throw StateError('Credential was removed'); - try { - responseJson = jsonDecode(responseBody); - validateMap(responseJson, {'result': const TypeMatcher>()}); - final result = responseJson['result']; - validateMap(result, {'value': const TypeMatcher>()}); - final value = result['value']; - validateMap(value, {'public_server_key': const TypeMatcher()}); - publicServerKey = const EccUtils().deserializeECPublicKey(value['public_server_key']); - } catch (e) { - ref.read(statusMessageProvider.notifier).state = ('Failed to decode response body', e.toString()); - await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseFailed)); - rethrow; - } + responseJson = jsonDecode(responseBody); + final result = validate(value: responseJson['result'], validator: const TypeValidatorRequired>(), name: 'result'); + final value = validate(value: result['value'], validator: const TypeValidatorRequired>(), name: 'value'); + publicServerKey = validate( + value: value['public_server_key'], + validator: TypeValidatorRequired(transformer: (v) => const EccUtils().deserializeECPublicKey(v)), + name: 'public_server_key', + ); credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseCompleted)); if (credential == null) throw StateError('Credential was removed'); return (credential, publicServerKey); diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart index 0d79cacf0..247a41d9a 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart @@ -1,78 +1,78 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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. - */ +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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:riverpod_annotation/riverpod_annotation.dart'; +// import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../../../../model/riverpod_states/progress_state.dart'; -import '../../../logger.dart'; +// import '../../../../model/riverpod_states/progress_state.dart'; +// import '../../../logger.dart'; -part 'progress_state_provider.g.dart'; +// part 'progress_state_provider.g.dart'; -final progressStateProvider = progressStateNotifierProviderOf(Null); +// final progressStateProvider = progressStateNotifierProviderOf(Null); -@riverpod -class ProgressStateNotifier extends _$ProgressStateNotifier { - @override - ProgressState build(Type type) { - Logger.info('Initializing progress state', name: 'ProgressStateNotifier#initProgress'); - return const ProgressState.uninitialized(); - } +// @riverpod +// class ProgressStateNotifier extends _$ProgressStateNotifier { +// @override +// ProgressState build(Type type) { +// Logger.info('Initializing progress state', name: 'ProgressStateNotifier#initProgress'); +// return const ProgressState.uninitialized(); +// } - double? get progress => state.progress; +// double? get progress => state.progress; - ProgressState initProgress(int max, int value) { - Logger.info('Initializing progress state', name: 'ProgressStateNotifier#initProgress'); - final newState = ProgressState(max: max, value: value); - state = newState; - return newState; - } +// ProgressState initProgress(int max, int value) { +// Logger.info('Initializing progress state', name: 'ProgressStateNotifier#initProgress'); +// final newState = ProgressState(max: max, value: value); +// state = newState; +// return newState; +// } - void deleteProgress() { - Logger.info('Deleting progress state', name: 'ProgressStateNotifier#deleteProgress'); - state = const ProgressState.uninitialized(); - } +// void deleteProgress() { +// Logger.info('Deleting progress state', name: 'ProgressStateNotifier#deleteProgress'); +// state = const ProgressState.uninitialized(); +// } - ProgressState? resetProgress() { - if (state is ProgressStateUninitialized) return state; - Logger.info('Resetting progress state', name: 'ProgressStateNotifier#resetProgress'); - final newState = state.copyWith(value: 0); - state = newState; - return newState; - } +// ProgressState? resetProgress() { +// if (state is ProgressStateUninitialized) return state; +// Logger.info('Resetting progress state', name: 'ProgressStateNotifier#resetProgress'); +// final newState = state.copyWith(value: 0); +// state = newState; +// return newState; +// } - ProgressState? setProgressMax(int max) { - assert(state is! ProgressStateUninitialized, 'Progress state is uninitialized'); - assert(max > 0, 'Max value must be greater than 0'); - Logger.info('Setting progress max to $max', name: 'ProgressStateNotifier#setProgressMax'); - final newState = state.copyWith(max: max); - state = newState; - return newState; - } +// ProgressState? setProgressMax(int max) { +// assert(state is! ProgressStateUninitialized, 'Progress state is uninitialized'); +// assert(max > 0, 'Max value must be greater than 0'); +// Logger.info('Setting progress max to $max', name: 'ProgressStateNotifier#setProgressMax'); +// final newState = state.copyWith(max: max); +// state = newState; +// return newState; +// } - ProgressState? setProgressValue(int value) { - assert(state is! ProgressStateUninitialized, 'Progress state is uninitialized'); - assert(value >= 0, 'Value must be greater than or equal to 0'); - assert(value <= state.max, 'Value must be less than or equal to max value'); - Logger.info('Setting progress value to $value/${state.max}', name: 'ProgressStateNotifier#setProgressValue'); - final newState = state.copyWith(value: value); - state = newState; - return newState; - } -} +// ProgressState? setProgressValue(int value) { +// assert(state is! ProgressStateUninitialized, 'Progress state is uninitialized'); +// assert(value >= 0, 'Value must be greater than or equal to 0'); +// if (value > state.max) value = state.max; +// Logger.info('Setting progress value to $value/${state.max}', name: 'ProgressStateNotifier#setProgressValue'); +// final newState = state.copyWith(value: value); +// state = newState; +// return newState; +// } +// } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.g.dart index e5af71f1f..b092f5267 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.g.dart @@ -1,176 +1,176 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'progress_state_provider.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$progressStateNotifierHash() => - r'f2da79434b36425d91169f5eb0f82cbd48ef8949'; - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} - -abstract class _$ProgressStateNotifier - extends BuildlessAutoDisposeNotifier { - late final Type type; - - ProgressState build( - Type type, - ); -} - -/// See also [ProgressStateNotifier]. -@ProviderFor(ProgressStateNotifier) -const progressStateNotifierProviderOf = ProgressStateNotifierFamily(); - -/// See also [ProgressStateNotifier]. -class ProgressStateNotifierFamily extends Family { - /// See also [ProgressStateNotifier]. - const ProgressStateNotifierFamily(); - - /// See also [ProgressStateNotifier]. - ProgressStateNotifierProvider call( - Type type, - ) { - return ProgressStateNotifierProvider( - type, - ); - } - - @override - ProgressStateNotifierProvider getProviderOverride( - covariant ProgressStateNotifierProvider provider, - ) { - return call( - provider.type, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'progressStateNotifierProviderOf'; -} - -/// See also [ProgressStateNotifier]. -class ProgressStateNotifierProvider extends AutoDisposeNotifierProviderImpl< - ProgressStateNotifier, ProgressState> { - /// See also [ProgressStateNotifier]. - ProgressStateNotifierProvider( - Type type, - ) : this._internal( - () => ProgressStateNotifier()..type = type, - from: progressStateNotifierProviderOf, - name: r'progressStateNotifierProviderOf', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$progressStateNotifierHash, - dependencies: ProgressStateNotifierFamily._dependencies, - allTransitiveDependencies: - ProgressStateNotifierFamily._allTransitiveDependencies, - type: type, - ); - - ProgressStateNotifierProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.type, - }) : super.internal(); - - final Type type; - - @override - ProgressState runNotifierBuild( - covariant ProgressStateNotifier notifier, - ) { - return notifier.build( - type, - ); - } - - @override - Override overrideWith(ProgressStateNotifier Function() create) { - return ProviderOverride( - origin: this, - override: ProgressStateNotifierProvider._internal( - () => create()..type = type, - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - type: type, - ), - ); - } - - @override - AutoDisposeNotifierProviderElement - createElement() { - return _ProgressStateNotifierProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is ProgressStateNotifierProvider && other.type == type; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, type.hashCode); - - return _SystemHash.finish(hash); - } -} - -mixin ProgressStateNotifierRef - on AutoDisposeNotifierProviderRef { - /// The parameter `type` of this provider. - Type get type; -} - -class _ProgressStateNotifierProviderElement - extends AutoDisposeNotifierProviderElement with ProgressStateNotifierRef { - _ProgressStateNotifierProviderElement(super.provider); - - @override - Type get type => (origin as ProgressStateNotifierProvider).type; -} -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member +// // GENERATED CODE - DO NOT MODIFY BY HAND + +// part of 'progress_state_provider.dart'; + +// // ************************************************************************** +// // RiverpodGenerator +// // ************************************************************************** + +// String _$progressStateNotifierHash() => +// r'f2da79434b36425d91169f5eb0f82cbd48ef8949'; + +// /// Copied from Dart SDK +// class _SystemHash { +// _SystemHash._(); + +// static int combine(int hash, int value) { +// // ignore: parameter_assignments +// hash = 0x1fffffff & (hash + value); +// // ignore: parameter_assignments +// hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); +// return hash ^ (hash >> 6); +// } + +// static int finish(int hash) { +// // ignore: parameter_assignments +// hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); +// // ignore: parameter_assignments +// hash = hash ^ (hash >> 11); +// return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); +// } +// } + +// abstract class _$ProgressStateNotifier +// extends BuildlessAutoDisposeNotifier { +// late final Type type; + +// ProgressState build( +// Type type, +// ); +// } + +// /// See also [ProgressStateNotifier]. +// @ProviderFor(ProgressStateNotifier) +// const progressStateNotifierProviderOf = ProgressStateNotifierFamily(); + +// /// See also [ProgressStateNotifier]. +// class ProgressStateNotifierFamily extends Family { +// /// See also [ProgressStateNotifier]. +// const ProgressStateNotifierFamily(); + +// /// See also [ProgressStateNotifier]. +// ProgressStateNotifierProvider call( +// Type type, +// ) { +// return ProgressStateNotifierProvider( +// type, +// ); +// } + +// @override +// ProgressStateNotifierProvider getProviderOverride( +// covariant ProgressStateNotifierProvider provider, +// ) { +// return call( +// provider.type, +// ); +// } + +// static const Iterable? _dependencies = null; + +// @override +// Iterable? get dependencies => _dependencies; + +// static const Iterable? _allTransitiveDependencies = null; + +// @override +// Iterable? get allTransitiveDependencies => +// _allTransitiveDependencies; + +// @override +// String? get name => r'progressStateNotifierProviderOf'; +// } + +// /// See also [ProgressStateNotifier]. +// class ProgressStateNotifierProvider extends AutoDisposeNotifierProviderImpl< +// ProgressStateNotifier, ProgressState> { +// /// See also [ProgressStateNotifier]. +// ProgressStateNotifierProvider( +// Type type, +// ) : this._internal( +// () => ProgressStateNotifier()..type = type, +// from: progressStateNotifierProviderOf, +// name: r'progressStateNotifierProviderOf', +// debugGetCreateSourceHash: +// const bool.fromEnvironment('dart.vm.product') +// ? null +// : _$progressStateNotifierHash, +// dependencies: ProgressStateNotifierFamily._dependencies, +// allTransitiveDependencies: +// ProgressStateNotifierFamily._allTransitiveDependencies, +// type: type, +// ); + +// ProgressStateNotifierProvider._internal( +// super._createNotifier, { +// required super.name, +// required super.dependencies, +// required super.allTransitiveDependencies, +// required super.debugGetCreateSourceHash, +// required super.from, +// required this.type, +// }) : super.internal(); + +// final Type type; + +// @override +// ProgressState runNotifierBuild( +// covariant ProgressStateNotifier notifier, +// ) { +// return notifier.build( +// type, +// ); +// } + +// @override +// Override overrideWith(ProgressStateNotifier Function() create) { +// return ProviderOverride( +// origin: this, +// override: ProgressStateNotifierProvider._internal( +// () => create()..type = type, +// from: from, +// name: null, +// dependencies: null, +// allTransitiveDependencies: null, +// debugGetCreateSourceHash: null, +// type: type, +// ), +// ); +// } + +// @override +// AutoDisposeNotifierProviderElement +// createElement() { +// return _ProgressStateNotifierProviderElement(this); +// } + +// @override +// bool operator ==(Object other) { +// return other is ProgressStateNotifierProvider && other.type == type; +// } + +// @override +// int get hashCode { +// var hash = _SystemHash.combine(0, runtimeType.hashCode); +// hash = _SystemHash.combine(hash, type.hashCode); + +// return _SystemHash.finish(hash); +// } +// } + +// mixin ProgressStateNotifierRef +// on AutoDisposeNotifierProviderRef { +// /// The parameter `type` of this provider. +// Type get type; +// } + +// class _ProgressStateNotifierProviderElement +// extends AutoDisposeNotifierProviderElement with ProgressStateNotifierRef { +// _ProgressStateNotifierProviderElement(super.provider); + +// @override +// Type get type => (origin as ProgressStateNotifierProvider).type; +// } +// // ignore_for_file: type=lint +// // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart index 1ac5ca1bb..fa04f9f5f 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -45,10 +45,11 @@ class TokenContainerNotifier extends _$TokenContainerNotifier { }) async { Logger.info('New tokenContainerStateProvider created', name: 'TokenContainerNotifier#build'); await _stateMutex.acquire(); - _repository = HybridTokenContainerRepository( - localRepository: SecureTokenContainerRepository(containerId: credential.serial), - remoteRepository: RemoteTokenContainerRepository(apiEndpoint: TokenContainerApiEndpoint(credential: credential)), - ); + _repository = SecureTokenContainerRepository(containerId: credential.serial); + // HybridTokenContainerRepository( + // localRepository: SecureTokenContainerRepository(containerId: credential.serial), + // remoteRepository: RemoteTokenContainerRepository(apiEndpoint: TokenContainerApiEndpoint(credential: credential)), + // ); final initialState = await _repository.loadContainerState(); Logger.debug('Initial state: $initialState', name: 'TokenContainerNotifier#build'); _stateMutex.release(); diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart index 3b6749e30..59e57489c 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart @@ -423,26 +423,26 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { ); if (appTemplate == null) { Logger.debug( - 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} not found in app. Adding them.', + 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} not found in app. Adding them.', name: 'token_notifier.dart#updateContainerTokens', ); templatesToAdd.add(serverTokenTemplate); } else { Logger.debug( - 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app. Checking for update.', + 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} found in app. Checking for update.', name: 'token_notifier.dart#updateContainerTokens', ); appTokenTemplates.remove(appTemplate); if (!appTemplate.hasSameValuesAs(serverTokenTemplate)) { Logger.debug( - 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app the Template but is different. Check update.', + 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} found in app the Template but is different. Check update.', name: 'token_notifier.dart#updateContainerTokens', ); // Only update the token if the template is different templatesToUpdate.add(serverTokenTemplate); } else { Logger.debug( - 'Token with serial ${serverTokenTemplate.serial} or id ${serverTokenTemplate.id} found in app. And is the same. Skipping.', + 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} found in app. And is the same. Skipping.', name: 'token_notifier.dart#updateContainerTokens', ); } @@ -460,7 +460,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { final tokensToAdd = templatesToAdd.map((e) => e.toToken(container)).toList(); final tokensToUpdate = []; for (var template in templatesToUpdate) { - final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id || token.serial == template.serial); + final token = knownContainerTokens.firstWhereOrNull((token) => token.serial == template.serial); if (token == null) continue; final needsUpdate = template.tokenWouldBeUpdated(token); if (needsUpdate) { @@ -469,7 +469,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { } final tokensToRemove = []; for (var template in templatesToRemove) { - final token = knownContainerTokens.firstWhereOrNull((token) => token.id == template.id || token.serial == template.serial); + final token = knownContainerTokens.firstWhereOrNull((token) => token.serial == template.serial); if (token == null) continue; tokensToRemove.add(token); } @@ -484,7 +484,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { } if (tokensToUpdate.isNotEmpty) { await updateTokens(tokensToUpdate, (token) { - final template = templatesToUpdate.firstWhereOrNull((template) => template.id == token.id || template.serial == token.serial); + final template = templatesToUpdate.firstWhereOrNull((template) => template.serial == token.serial); if (template == null) { Logger.debug( 'Not able to update token with id ${token.id} or serial ${token.serial}. No token serial/id matches the templates serial/id.', @@ -496,7 +496,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { 'Updating token with id:"${token.id}"/serial:"${token.serial}".', name: 'token_notifier.dart#updateContainerTokens', ); - return token.copyWithFromTemplate(template); + return token.copyUpdateByTemplate(template); }); } if (tokensToRemove.isNotEmpty) { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart index 83c5c0f93..caad2f89a 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart @@ -6,7 +6,7 @@ part of 'token_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$tokenNotifierHash() => r'f367f6acf72448089aa9f5b45b2f1e1dba5d0097'; +String _$tokenNotifierHash() => r'bfa2c87ccc80b4446c19926c565c3c8ed962d2a2'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/type_matchers.dart b/lib/utils/type_matchers.dart new file mode 100644 index 000000000..29b6c4657 --- /dev/null +++ b/lib/utils/type_matchers.dart @@ -0,0 +1,60 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; + +import '../model/enums/algorithms.dart'; +import '../model/enums/encodings.dart'; +import 'identifiers.dart'; + +final stringToIntValidatorOptional = stringToIntvalidator.optional(); +final stringToIntvalidator = TypeValidatorRequired(transformer: (v) => int.parse(v)); + +final intToStringValidator = TypeValidatorRequired(transformer: (v) => (v as int).toString()); +final intToStringValidatorOptional = intToStringValidator.optional(); + +final stringSecondsToDurationvalidatorOptional = stringSecondsToDurationvalidator.optional(); +final stringSecondsToDurationvalidator = TypeValidatorRequired(transformer: (v) => Duration(seconds: int.parse(v))); + +final stringToUriValidatorOptional = stringToUrivalidator.optional(); +final stringToUrivalidator = TypeValidatorRequired(transformer: (v) => Uri.parse(v)); + +final stringToBoolValidatorOptional = stringToBoolValidator.optional(); +final stringToBoolValidator = TypeValidatorRequired( + transformer: (v) => switch ((v as String).toLowerCase()) { + 'true' => true, + '1' => true, + 'false' => false, + '0' => false, + _ => throw ArgumentError('Invalid boolean value: $v'), + }); + +final stringToAlgorithmsValidator = TypeValidatorRequired( + transformer: (v) => Algorithms.values.byName((v as String).toUpperCase()), +); +final stringToAlgorithmsValidatorOptional = stringToAlgorithmsValidator.optional(); + +/// When value is given, it checks if the value is a base32 encoded string. +final base32SecretValidatorOptional = base32Secretvalidator.optional(); + +/// Checks if the value is a base32 encoded string. +final base32Secretvalidator = TypeValidatorRequired(transformer: (v) { + if (v is String) v = Encodings.base32.decode(v); + return Encodings.base32.encode(v); +}); diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 31c747bfa..4fe6184ae 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -221,15 +221,15 @@ Future scanQrCode(List resultHandlerList, Object? qrCode) a } final processorResults = await SchemeProcessor.processUriByAny(uri); if (processorResults == null) return; - final resultHandlerTypeMap = , List>{}; + final resultHandlerTypeMap = , List>{}; for (var result in processorResults) { - final typeMatcher = result.resultHandlerType; - if (typeMatcher == null) continue; + final validator = result.resultHandlerType; + if (validator == null) continue; if (resultHandlerTypeMap.containsKey(result.resultHandlerType)) { - resultHandlerTypeMap[typeMatcher]!.add(result); + resultHandlerTypeMap[validator]!.add(result); } else { - resultHandlerTypeMap[typeMatcher] = [result]; + resultHandlerTypeMap[validator] = [result]; } } 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 6d837409e..59a911374 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 @@ -17,30 +17,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../l10n/app_localizations.dart'; -import '../../mains/main_netknights.dart'; 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'; -import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; -import 'add_token_manually_view_widgets/labeled_dropdown_button.dart'; +import 'add_token_manually_view_widgets/add_tokens_manually/add_daypassword_manually.dart'; +import 'add_token_manually_view_widgets/add_tokens_manually/add_hotp_manually.dart'; +import 'add_token_manually_view_widgets/add_tokens_manually/add_steam_manually.dart'; +import 'add_token_manually_view_widgets/add_tokens_manually/add_token_manually_interface.dart'; +import 'add_token_manually_view_widgets/add_tokens_manually/add_totp_manually.dart'; class AddTokenManuallyView extends ConsumerStatefulWidget { static const routeName = '/add_token_manually'; - static final List allowedDigits = [6, 8]; - static final List allowedPeriodsTOTP = [30, 60]; - static final List allowedPeriodsDayPassword = List.generate(24, (i) => 24 - i, growable: false); const AddTokenManuallyView({super.key}); @@ -49,36 +41,100 @@ class AddTokenManuallyView extends ConsumerStatefulWidget { } class _AddTokenManuallyViewState extends ConsumerState { - final ValueNotifier _algorithmNotifier = ValueNotifier(Algorithms.SHA512); - final ValueNotifier _encodingNotifier = ValueNotifier(Encodings.none); - final ValueNotifier _typeNotifier = ValueNotifier(TokenTypes.HOTP); - final ValueNotifier _digitsNotifier = ValueNotifier(AddTokenManuallyView.allowedDigits[0]); - final ValueNotifier _periodNotifier = ValueNotifier(AddTokenManuallyView.allowedPeriodsTOTP[0]); - final ValueNotifier _periodDayPasswordNotifier = ValueNotifier(AddTokenManuallyView.allowedPeriodsDayPassword[0]); - final TextEditingController _labelController = TextEditingController(); - final TextEditingController _secretController = TextEditingController(); + late ValueNotifier selectedTypeNotifier; - // fields needed to manage the widget - final _labelInputKey = GlobalKey(); - final _secretInputKey = GlobalKey(); + late TextEditingController labelController; + late TextEditingController secretController; - // used to handle focusing certain input fields - final FocusNode _labelFieldFocus = FocusNode(); - final FocusNode _secretFieldFocus = FocusNode(); - - AutovalidateMode _autoValidateLabel = AutovalidateMode.disabled; - AutovalidateMode _autoValidateSecret = AutovalidateMode.disabled; + late ValueNotifier autoValidateLabel; + late ValueNotifier autoValidateSecret; + late ValueNotifier encodingNofitier; + late ValueNotifier algorithmsNotifier; + late ValueNotifier digitsNotifier; + late ValueNotifier counterNotifier; + late ValueNotifier periodNotifierTOTP; + late ValueNotifier periodNotifierDayPassword; @override void initState() { super.initState(); - _typeNotifier.addListener(() { - setState(() {}); - }); + selectedTypeNotifier = ValueNotifier(TokenTypes.HOTP); + selectedTypeNotifier.addListener(() => setState(() {})); + labelController = TextEditingController(); + secretController = TextEditingController(); + autoValidateLabel = ValueNotifier(false); + autoValidateSecret = ValueNotifier(false); + encodingNofitier = ValueNotifier(Encodings.base32); + algorithmsNotifier = ValueNotifier(Algorithms.SHA1); + digitsNotifier = ValueNotifier(null); + counterNotifier = ValueNotifier(null); + periodNotifierTOTP = ValueNotifier(null); + periodNotifierDayPassword = ValueNotifier(null); + } + + @override + void dispose() { + selectedTypeNotifier.dispose(); + labelController.dispose(); + secretController.dispose(); + autoValidateLabel.dispose(); + autoValidateSecret.dispose(); + encodingNofitier.dispose(); + algorithmsNotifier.dispose(); + digitsNotifier.dispose(); + counterNotifier.dispose(); + periodNotifierTOTP.dispose(); + periodNotifierDayPassword.dispose(); + + super.dispose(); } @override Widget build(BuildContext context) { + final AddTokenManually column = switch (selectedTypeNotifier.value) { + const (TokenTypes.HOTP) => AddHotpManually( + labelController: labelController, + secretController: secretController, + autoValidateLabel: autoValidateLabel, + autoValidateSecret: autoValidateSecret, + encodingNofitier: encodingNofitier, + algorithmsNotifier: algorithmsNotifier, + digitsNotifier: digitsNotifier, + counterNotifier: counterNotifier, + typeNotifier: selectedTypeNotifier, + ), + TokenTypes.TOTP => AddTotpManually( + labelController: labelController, + secretController: secretController, + autoValidateLabel: autoValidateLabel, + autoValidateSecret: autoValidateSecret, + encodingNofitier: encodingNofitier, + algorithmsNotifier: algorithmsNotifier, + digitsNotifier: digitsNotifier, + periodNotifier: periodNotifierTOTP, + typeNotifier: selectedTypeNotifier, + ), + TokenTypes.DAYPASSWORD => AddDayPasswordManually( + labelController: labelController, + secretController: secretController, + autoValidateLabel: autoValidateLabel, + autoValidateSecret: autoValidateSecret, + encodingNofitier: encodingNofitier, + algorithmsNotifier: algorithmsNotifier, + digitsNotifier: digitsNotifier, + periodNotifier: periodNotifierDayPassword, + typeNotifier: selectedTypeNotifier, + ), + TokenTypes.STEAM => AddSteamManually( + labelController: labelController, + secretController: secretController, + autoValidateLabel: autoValidateLabel, + autoValidateSecret: autoValidateSecret, + typeNotifier: selectedTypeNotifier, + ), + TokenTypes.PIPUSH => throw UnimplementedError(), + }; + return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( @@ -90,160 +146,8 @@ class _AddTokenManuallyViewState extends ConsumerState { ), body: SingleChildScrollView( padding: const EdgeInsets.all(20), - child: Form( - child: Column( - children: [ - TextFormField( - key: _labelInputKey, - controller: _labelController, - autovalidateMode: _autoValidateLabel, - focusNode: _labelFieldFocus, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.name), - validator: (value) { - if (value!.isEmpty) { - return AppLocalizations.of(context)!.pleaseEnterANameForThisToken; - } - return null; - }, - ), - TextFormField( - key: _secretInputKey, - controller: _secretController, - autovalidateMode: _autoValidateSecret, - focusNode: _secretFieldFocus, - decoration: InputDecoration( - labelText: AppLocalizations.of(context)!.secretKey, - ), - validator: (value) { - if (value!.isEmpty) { - return AppLocalizations.of(context)!.pleaseEnterASecretForThisToken; - } else if ((_typeNotifier.value == TokenTypes.STEAM && Encodings.base32.isInvalidEncoding(value)) || - (_typeNotifier.value != TokenTypes.STEAM && _encodingNotifier.value.isInvalidEncoding(value))) { - return AppLocalizations.of(context)!.theSecretDoesNotFitTheCurrentEncoding; - } - return null; - }, - ), - Visibility( - visible: _typeNotifier.value != TokenTypes.STEAM, - child: LabeledDropdownButton( - label: AppLocalizations.of(context)!.encoding, - values: Encodings.values, - valueNotifier: _encodingNotifier, - ), - ), - Visibility( - visible: _typeNotifier.value != TokenTypes.STEAM, - child: LabeledDropdownButton( - label: AppLocalizations.of(context)!.algorithm, - values: Algorithms.values.reversed.toList(), - valueNotifier: _algorithmNotifier, - ), - ), - Visibility( - visible: _typeNotifier.value != TokenTypes.STEAM, - child: LabeledDropdownButton( - label: AppLocalizations.of(context)!.digits, - values: AddTokenManuallyView.allowedDigits, - valueNotifier: _digitsNotifier, - ), - ), - LabeledDropdownButton( - label: AppLocalizations.of(context)!.type, - values: List.from(TokenTypes.values)..remove(TokenTypes.PIPUSH), - valueNotifier: _typeNotifier, - ), - Visibility( - // the period is only used by TOTP tokens - visible: _typeNotifier.value == TokenTypes.TOTP, - child: LabeledDropdownButton( - label: AppLocalizations.of(context)!.period, - values: AddTokenManuallyView.allowedPeriodsTOTP, - valueNotifier: _periodNotifier, - postFix: 's' /*seconds*/, - ), - ), - Visibility( - // the period is only used by DayPassword tokens - visible: _typeNotifier.value == TokenTypes.DAYPASSWORD, - child: LabeledDropdownButton( - label: AppLocalizations.of(context)!.period, - values: AddTokenManuallyView.allowedPeriodsDayPassword, - valueNotifier: _periodDayPasswordNotifier, - postFix: 'h' /*hours*/, - ), - ), - SizedBox( - width: double.infinity, - child: ElevatedButton( - child: Text( - AppLocalizations.of(context)!.addToken, - style: Theme.of(context).textTheme.titleLarge?.copyWith(color: Theme.of(context).colorScheme.onPrimary), - overflow: TextOverflow.fade, - softWrap: false, - ), - onPressed: () { - final token = _buildTokenIfValid(context: context); - if (token != null) { - ref.read(tokenProvider.notifier).addOrReplaceToken(token); - Navigator.pop(context); - } - }, - ), - ), - ], - ), - ), + child: column, ), ); } - - Token? _buildTokenIfValid({required BuildContext context}) { - if (_inputIsValid(context) == false) return null; - Logger.info('Input is valid, building token'); - - final uriMap = { - URI_TYPE: _typeNotifier.value.name, - URI_LABEL: _labelController.text, - URI_ISSUER: '', - URI_ALGORITHM: _algorithmNotifier.value.name, - URI_DIGITS: _digitsNotifier.value, - URI_SECRET: _encodingNotifier.value.decode(_secretController.text), - URI_COUNTER: 0, - URI_PERIOD: _typeNotifier.value == TokenTypes.DAYPASSWORD ? _periodDayPasswordNotifier.value * 60 * 60 : _periodNotifier.value, - }; - uriMap.addAll({ - URI_ORIGIN: TokenOriginSourceType.manually.toTokenOrigin( - data: jsonEncode(uriMap), - originName: PrivacyIDEAAuthenticator.currentCustomization?.appName, - isPrivacyIdeaToken: false, - ), - }); - return Token.fromUriMap(uriMap); - } - - /// Validates the inputs of the label and secret. - bool _inputIsValid(BuildContext context) { - if (_labelInputKey.currentState!.validate()) { - _labelInputKey.currentState!.save(); - } else { - setState(() { - _autoValidateLabel = AutovalidateMode.always; - }); - FocusScope.of(context).requestFocus(_labelFieldFocus); - return false; - } - - if (_secretInputKey.currentState!.validate()) { - _secretInputKey.currentState!.save(); - } else { - setState(() { - _autoValidateSecret = AutovalidateMode.always; - }); - FocusScope.of(context).requestFocus(_secretFieldFocus); - return false; - } - - return true; - } } diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_token_manually_row.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_token_manually_row.dart new file mode 100644 index 000000000..1fb05b47e --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_token_manually_row.dart @@ -0,0 +1,55 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; + +class AddTokenManuallyRow extends StatelessWidget { + final String label; + final Widget child; + + const AddTokenManuallyRow({ + required this.label, + required this.child, + super.key, + }); + + @override + Widget build(BuildContext context) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + flex: 5, + child: Text( + label, + style: Theme.of(context).textTheme.bodyMedium, + overflow: TextOverflow.fade, + softWrap: false, + ), + ), + Flexible( + flex: 3, + child: ConstrainedBox( + constraints: const BoxConstraints(minWidth: 100), + child: child, + ), + ), + ], + ); +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_daypassword_manually.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_daypassword_manually.dart new file mode 100644 index 000000000..0abfa21ce --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_daypassword_manually.dart @@ -0,0 +1,123 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.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:uuid/uuid.dart'; + +import '../../../../model/enums/algorithms.dart'; +import '../../../../model/enums/duration_unit.dart'; +import '../../../../model/enums/encodings.dart'; +import '../../../../model/enums/token_origin_source_type.dart'; +import '../../../../model/enums/token_types.dart'; +import '../../../../model/tokens/day_password_token.dart'; +import '../../../../utils/logger.dart'; +import '../rows/add_token_button.dart'; +import '../rows/algorithms_dropdown_button.dart'; +import '../rows/digits_dropdown_button.dart'; +import '../rows/duration_dropdown_button.dart'; +import '../rows/encoding_dropdown_button.dart'; +import '../rows/label_input_field.dart'; +import '../rows/secret_input_field.dart'; +import '../rows/token_type_dropdown_button.dart'; +import 'add_token_manually_interface.dart'; + +class AddDayPasswordManually extends AddTokenManually { + static final allowedPeriodsDayPassword = List.generate(24, (i) => Duration(hours: 24 - i), growable: false); + + final TextEditingController labelController; + + final TextEditingController secretController; + + final ValueNotifier autoValidateLabel; + final ValueNotifier autoValidateSecret; + final ValueNotifier encodingNofitier; + final ValueNotifier algorithmsNotifier; + final ValueNotifier digitsNotifier; + final ValueNotifier periodNotifier; + final ValueNotifier typeNotifier; + + DayPasswordToken? _tryBuildToken() { + if (LabelInputField.validator(labelController.text) != null) { + autoValidateLabel.value = true; + return null; + } + if (SecretInputField.validator(secretController.text, encodingNofitier.value) != null) { + autoValidateSecret.value = true; + return null; + } + if (digitsNotifier.value == null || periodNotifier.value == null) { + return null; + } + Logger.info('Input is valid, building DayPasswordToken'); + return DayPasswordToken( + id: const Uuid().v4(), + algorithm: algorithmsNotifier.value, + digits: digitsNotifier.value!, + secret: encodingNofitier.value.encodeStringTo(Encodings.base32, secretController.text), + type: TokenTypes.DAYPASSWORD.name, + origin: TokenOriginSourceType.manually.toTokenOrigin(), + period: periodNotifier.value!, + ); + } + + const AddDayPasswordManually({ + required this.labelController, + required this.secretController, + required this.autoValidateLabel, + required this.autoValidateSecret, + required this.encodingNofitier, + required this.algorithmsNotifier, + required this.digitsNotifier, + required this.periodNotifier, + required this.typeNotifier, + super.key, + }); + + @override + Column build(BuildContext context) => Column( + children: [ + LabelInputField( + controller: labelController, + autoValidate: autoValidateLabel, + ), + SecretInputField( + controller: secretController, + autoValidate: autoValidateSecret, + encodingNotifier: encodingNofitier, + ), + TokenTypeDropdownButton(typeNotifier: typeNotifier), + EncodingsDropdownButton(encodingNotifier: encodingNofitier), + AlgorithmsDropdownButton(algorithmsNotifier: algorithmsNotifier), + DigitsDropdownButton(digitsNotifier: digitsNotifier), + DurationDropdownButton( + periodNotifier: periodNotifier, + values: allowedPeriodsDayPassword, + unit: DurationUnit.hours, + ), + AddTokenButton( + autoValidateLabel: autoValidateLabel, + autoValidateSecret: autoValidateSecret, + tokenBuilder: _tryBuildToken, + ), + ], + ); +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_hotp_manually.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_hotp_manually.dart new file mode 100644 index 000000000..91f7e7803 --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_hotp_manually.dart @@ -0,0 +1,116 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.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:uuid/uuid.dart'; + +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/tokens/hotp_token.dart'; +import '../../../../utils/logger.dart'; +import '../rows/add_token_button.dart'; +import '../rows/algorithms_dropdown_button.dart'; +import '../rows/counter_input_field.dart'; +import '../rows/digits_dropdown_button.dart'; +import '../rows/encoding_dropdown_button.dart'; +import '../rows/label_input_field.dart'; +import '../rows/secret_input_field.dart'; +import '../rows/token_type_dropdown_button.dart'; +import 'add_token_manually_interface.dart'; + +class AddHotpManually extends AddTokenManually { + final TextEditingController labelController; + final TextEditingController secretController; + + final ValueNotifier autoValidateLabel; + final ValueNotifier autoValidateSecret; + final ValueNotifier encodingNofitier; + final ValueNotifier algorithmsNotifier; + final ValueNotifier digitsNotifier; + final ValueNotifier counterNotifier; + final ValueNotifier typeNotifier; + + const AddHotpManually({ + super.key, + required this.labelController, + required this.secretController, + required this.autoValidateLabel, + required this.autoValidateSecret, + required this.encodingNofitier, + required this.algorithmsNotifier, + required this.digitsNotifier, + required this.counterNotifier, + required this.typeNotifier, + }); + + HOTPToken? _tryBuildToken() { + if (LabelInputField.validator(labelController.text) != null) { + autoValidateLabel.value = true; + return null; + } + if (SecretInputField.validator(secretController.text, encodingNofitier.value) != null) { + autoValidateSecret.value = true; + return null; + } + if (CounterInputField.validator(counterNotifier.value?.toString()) != null) { + return null; + } + Logger.info('Input is valid, building token'); + return HOTPToken( + id: const Uuid().v4(), + type: TokenTypes.HOTP.name, + label: labelController.text, + algorithm: algorithmsNotifier.value, + secret: encodingNofitier.value.encodeStringTo(Encodings.base32, secretController.text), + digits: digitsNotifier.value!, + counter: counterNotifier.value!, + origin: TokenOriginSourceType.manually.toTokenOrigin(), + ); + } + + @override + Column build(BuildContext context) => Column( + children: [ + LabelInputField( + controller: labelController, + autoValidate: autoValidateLabel, + ), + SecretInputField( + controller: secretController, + autoValidate: autoValidateSecret, + encodingNotifier: encodingNofitier, + ), + TokenTypeDropdownButton(typeNotifier: typeNotifier), + EncodingsDropdownButton(encodingNotifier: encodingNofitier), + AlgorithmsDropdownButton(algorithmsNotifier: algorithmsNotifier), + DigitsDropdownButton(digitsNotifier: digitsNotifier), + CounterInputField(counterNotifier: counterNotifier), + AddTokenButton( + autoValidateLabel: autoValidateLabel, + autoValidateSecret: autoValidateSecret, + tokenBuilder: _tryBuildToken, + ), + ], + ); +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_steam_manually.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_steam_manually.dart new file mode 100644 index 000000000..3ec9f6ba2 --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_steam_manually.dart @@ -0,0 +1,118 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/token_origin_source_type.dart'; +import 'package:uuid/uuid.dart'; + +import '../../../../model/enums/algorithms.dart'; +import '../../../../model/enums/duration_unit.dart'; +import '../../../../model/enums/encodings.dart'; +import '../../../../model/enums/token_origin_source_type.dart'; +import '../../../../model/enums/token_types.dart'; +import '../../../../model/tokens/steam_token.dart'; +import '../../../../utils/logger.dart'; +import '../rows/add_token_button.dart'; +import '../rows/algorithms_dropdown_button.dart'; +import '../rows/digits_dropdown_button.dart'; +import '../rows/duration_dropdown_button.dart'; +import '../rows/encoding_dropdown_button.dart'; +import '../rows/label_input_field.dart'; +import '../rows/secret_input_field.dart'; +import '../rows/token_type_dropdown_button.dart'; +import 'add_token_manually_interface.dart'; + +class AddSteamManually extends AddTokenManually { + final TextEditingController labelController; + + final TextEditingController secretController; + + final ValueNotifier autoValidateLabel; + final ValueNotifier autoValidateSecret; + final ValueNotifier typeNotifier; + + const AddSteamManually( + {required this.labelController, + required this.secretController, + required this.autoValidateLabel, + required this.autoValidateSecret, + required this.typeNotifier, + super.key}); + + SteamToken? _tokenBuilder() { + if (LabelInputField.validator(labelController.text) != null) { + autoValidateLabel.value = true; + return null; + } + if (SecretInputField.validator(secretController.text, Encodings.none) != null) { + autoValidateSecret.value = true; + return null; + } + Logger.info('Input is valid, building Steam token'); + return SteamToken( + id: const Uuid().v4(), + type: TokenTypes.STEAM.name, + origin: TokenOriginSourceType.manually.toTokenOrigin(), + label: labelController.text, + secret: secretController.text, + ); + } + + // int? period, // unused steam tokens always have 30 seconds period + // int? digits, // unused steam tokens always have 5 digits + // Algorithms? algorithm, // unused steam tokens always have SHA1 algorithm + @override + Column build(BuildContext context) => Column( + children: [ + LabelInputField( + controller: labelController, + autoValidate: autoValidateLabel, + ), + SecretInputField( + controller: secretController, + autoValidate: autoValidateSecret, + encodingNotifier: ValueNotifier(Encodings.none), + ), + TokenTypeDropdownButton(typeNotifier: typeNotifier), + const EncodingsDropdownButton( + enabled: false, + values: [Encodings.none], + ), + const AlgorithmsDropdownButton( + enabled: false, + allowedAlgorithms: [Algorithms.SHA1], + ), + const DigitsDropdownButton( + enabled: false, + allowedDigits: [5], + ), + const DurationDropdownButton( + enabled: false, + unit: DurationUnit.seconds, + values: [Duration(seconds: 30)], + ), + AddTokenButton( + autoValidateLabel: autoValidateLabel, + autoValidateSecret: autoValidateSecret, + tokenBuilder: _tokenBuilder, + ), + ], + ); +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_token_manually_interface.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_token_manually_interface.dart new file mode 100644 index 000000000..5ad00d250 --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_token_manually_interface.dart @@ -0,0 +1,26 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; + +abstract class AddTokenManually extends StatelessWidget { + const AddTokenManually({super.key}); + @override + Column build(BuildContext context); +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_totp_manually.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_totp_manually.dart new file mode 100644 index 000000000..39dfbcee4 --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/add_tokens_manually/add_totp_manually.dart @@ -0,0 +1,123 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.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:uuid/uuid.dart'; + +import '../../../../model/enums/algorithms.dart'; +import '../../../../model/enums/duration_unit.dart'; +import '../../../../model/enums/encodings.dart'; +import '../../../../model/enums/token_origin_source_type.dart'; +import '../../../../model/enums/token_types.dart'; +import '../../../../model/tokens/totp_token.dart'; +import '../../../../utils/logger.dart'; +import '../rows/add_token_button.dart'; +import '../rows/algorithms_dropdown_button.dart'; +import '../rows/digits_dropdown_button.dart'; +import '../rows/duration_dropdown_button.dart'; +import '../rows/encoding_dropdown_button.dart'; +import '../rows/label_input_field.dart'; +import '../rows/secret_input_field.dart'; +import '../rows/token_type_dropdown_button.dart'; +import 'add_token_manually_interface.dart'; + +class AddTotpManually extends AddTokenManually { + static final allowedPeriodsTOTP = [const Duration(seconds: 30), const Duration(seconds: 60)]; + + final TextEditingController labelController; + + final TextEditingController secretController; + + final ValueNotifier autoValidateLabel; + final ValueNotifier autoValidateSecret; + final ValueNotifier encodingNofitier; + final ValueNotifier algorithmsNotifier; + final ValueNotifier digitsNotifier; + final ValueNotifier periodNotifier; + final ValueNotifier typeNotifier; + + const AddTotpManually({ + required this.labelController, + required this.secretController, + required this.autoValidateLabel, + required this.autoValidateSecret, + required this.encodingNofitier, + required this.algorithmsNotifier, + required this.digitsNotifier, + required this.periodNotifier, + required this.typeNotifier, + super.key, + }); + + TOTPToken? _tryBuildToken() { + if (LabelInputField.validator(labelController.text) != null) { + autoValidateLabel.value = true; + return null; + } + if (SecretInputField.validator(secretController.text, encodingNofitier.value) != null) { + autoValidateSecret.value = true; + return null; + } + if (digitsNotifier.value == null || periodNotifier.value == null) { + return null; + } + Logger.info('Input is valid, building token'); + return TOTPToken( + id: const Uuid().v4(), + algorithm: algorithmsNotifier.value, + digits: digitsNotifier.value!, + secret: encodingNofitier.value.encodeStringTo(Encodings.base32, secretController.text), + type: TokenTypes.TOTP.name, + origin: TokenOriginSourceType.manually.toTokenOrigin(), + label: labelController.text, + period: periodNotifier.value!.inSeconds, + ); + } + + @override + Column build(BuildContext context) => Column( + children: [ + LabelInputField( + controller: labelController, + autoValidate: autoValidateLabel, + ), + SecretInputField( + controller: secretController, + autoValidate: autoValidateSecret, + encodingNotifier: encodingNofitier, + ), + TokenTypeDropdownButton(typeNotifier: typeNotifier), + EncodingsDropdownButton(encodingNotifier: encodingNofitier), + AlgorithmsDropdownButton(algorithmsNotifier: algorithmsNotifier), + DigitsDropdownButton(digitsNotifier: digitsNotifier), + DurationDropdownButton( + periodNotifier: periodNotifier, + values: allowedPeriodsTOTP, + unit: DurationUnit.seconds, + ), + AddTokenButton( + autoValidateLabel: autoValidateLabel, + autoValidateSecret: autoValidateSecret, + tokenBuilder: _tryBuildToken, + ), + ], + ); +} 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 9cac8fa0d..c79e56843 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 @@ -18,23 +18,26 @@ * limitations under the License. */ import 'package:flutter/material.dart'; - import '../../../utils/logger.dart'; +import 'add_token_manually_row.dart'; class LabeledDropdownButton extends StatefulWidget { final String label; final List values; - final ValueNotifier valueNotifier; + final bool enabled; + // If valueLabel is not avaible, the value toString are used as label + final List? valueLabels; + final ValueNotifier? valueNotifier; final String postFix; - final Function(T)? onChanged; const LabeledDropdownButton({ required this.label, required this.values, - required this.valueNotifier, + this.enabled = true, + this.valueLabels, this.postFix = '', - this.onChanged, super.key, + required this.valueNotifier, }); @override @@ -42,51 +45,58 @@ class LabeledDropdownButton extends StatefulWidget { } class _LabeledDropdownButtonState extends State> { + void _setState() => setState(() {}); + + @override + void initState() { + super.initState(); + widget.valueNotifier?.value ??= widget.values.firstOrNull; + widget.valueNotifier?.addListener(_setState); + } + + @override + void dispose() { + widget.valueNotifier?.removeListener(_setState); + super.dispose(); + } + @override Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - flex: 5, - child: Text( - widget.label, - style: Theme.of(context).textTheme.bodyMedium, - overflow: TextOverflow.fade, - softWrap: false, - ), - ), - Flexible( - flex: 3, - child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 100), - child: DropdownButton( - value: widget.valueNotifier.value, - isExpanded: true, - items: widget.values.map>((T value) { - return DropdownMenuItem( - value: value, - child: Text( - '${value is Enum ? value.name : value}' - '${widget.postFix}', - style: Theme.of(context).textTheme.bodyMedium, - overflow: TextOverflow.fade, - softWrap: false, - ), - ); - }).toList(), - onChanged: (T? newValue) { - if (newValue == null) return; - setState(() { - Logger.info('DropdownButton onChanged: $newValue'); - widget.onChanged?.call(newValue); - widget.valueNotifier.value = newValue; - }); - }, + final child = AddTokenManuallyRow( + label: widget.label, + child: DropdownButton( + value: widget.valueNotifier?.value != null && widget.values.contains(widget.valueNotifier!.value) + ? widget.valueNotifier!.value + : widget.values.firstOrNull, + isExpanded: true, + items: [ + for (var i = 0; i < widget.values.length; i++) + DropdownMenuItem( + value: widget.values[i], + child: Text( + '${widget.valueLabels != null && i < widget.valueLabels!.length ? widget.valueLabels![i] : widget.values[i].toString()}' + ' ${widget.postFix}', + style: Theme.of(context).textTheme.bodyMedium, + overflow: TextOverflow.fade, + softWrap: false, + ), ), - ), - ), - ], + ], + onChanged: (T? newValue) { + if (newValue == null) return; + Logger.info('DropdownButton onChanged: $newValue'); + widget.valueNotifier?.value = newValue; + }, + ), ); + + return widget.enabled + ? child + : ShaderMask( + shaderCallback: (Rect bounds) => LinearGradient( + colors: [Colors.grey.shade600, Colors.grey.shade600], + ).createShader(bounds), + child: IgnorePointer(ignoring: true, child: child), + ); } } diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/add_token_button.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/add_token_button.dart new file mode 100644 index 000000000..0b4cfaf90 --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/add_token_button.dart @@ -0,0 +1,58 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../l10n/app_localizations.dart'; +import '../../../../model/tokens/token.dart'; +import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; +import '../../../../widgets/mutex_button.dart'; + +class AddTokenButton extends ConsumerWidget { + final ValueNotifier autoValidateLabel; + final ValueNotifier autoValidateSecret; + final Token? Function() tokenBuilder; + const AddTokenButton({ + required this.autoValidateLabel, + required this.autoValidateSecret, + required this.tokenBuilder, + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) => SizedBox( + width: double.infinity, + child: MutexButton( + onPressed: () async { + if (!context.mounted) return; + final token = tokenBuilder(); + if (token == null) return; + Navigator.pop(context); + await ref.read(tokenProvider.notifier).addOrReplaceToken(token); + }, + child: Text( + AppLocalizations.of(context)!.addToken, + style: Theme.of(context).textTheme.titleLarge?.copyWith(color: Theme.of(context).colorScheme.onPrimary), + overflow: TextOverflow.fade, + softWrap: false, + ), + ), + ); +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/algorithms_dropdown_button.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/algorithms_dropdown_button.dart new file mode 100644 index 000000000..553b41681 --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/algorithms_dropdown_button.dart @@ -0,0 +1,44 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; + +import '../../../../l10n/app_localizations.dart'; +import '../../../../model/enums/algorithms.dart'; +import '../labeled_dropdown_button.dart'; + +class AlgorithmsDropdownButton extends StatelessWidget { + final ValueNotifier? algorithmsNotifier; + final List allowedAlgorithms; + final bool enabled; + const AlgorithmsDropdownButton({ + super.key, + this.algorithmsNotifier, + this.allowedAlgorithms = Algorithms.values, + this.enabled = true, + }); + @override + Widget build(BuildContext context) => LabeledDropdownButton( + label: AppLocalizations.of(context)!.algorithm, + enabled: enabled, + valueNotifier: algorithmsNotifier, + values: allowedAlgorithms, + valueLabels: [for (final value in allowedAlgorithms) value.name], + ); +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/counter_input_field.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/counter_input_field.dart new file mode 100644 index 000000000..534f089f6 --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/counter_input_field.dart @@ -0,0 +1,60 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; + +import '../../../../l10n/app_localizations.dart'; +import '../add_token_manually_row.dart'; + +class CounterInputField extends StatelessWidget { + static final FocusNode counterFieldFocus = FocusNode(debugLabel: 'CounterInputField'); + static String? validator(String? value, {AppLocalizations? locale}) { + if (value == null || value.isEmpty) { + counterFieldFocus.requestFocus(); + return locale?.mustNotBeEmpty(locale.counter) ?? 'Must not be empty'; + } + return int.tryParse(value) == null ? locale?.notAnInteger ?? 'Must be a number' : null; + } + + final ValueNotifier counterNotifier; + const CounterInputField({ + super.key, + required this.counterNotifier, + }); + @override + Widget build(BuildContext context) { + counterNotifier.value ??= 0; + return AddTokenManuallyRow( + label: AppLocalizations.of(context)!.counter, + child: TextFormField( + decoration: const InputDecoration(errorMaxLines: 2), + keyboardType: TextInputType.number, + initialValue: counterNotifier.value.toString(), + onChanged: (value) { + final number = int.tryParse(value); + if (number != null) counterNotifier.value = number; + counterNotifier.value = int.tryParse(value); + }, + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (value) => validator(value, locale: AppLocalizations.of(context)), + focusNode: CounterInputField.counterFieldFocus, + ), + ); + } +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/digits_dropdown_button.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/digits_dropdown_button.dart new file mode 100644 index 000000000..8313ca34b --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/digits_dropdown_button.dart @@ -0,0 +1,42 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; + +import '../../../../l10n/app_localizations.dart'; +import '../labeled_dropdown_button.dart'; + +class DigitsDropdownButton extends StatelessWidget { + final bool enabled; + final List allowedDigits; + final ValueNotifier? digitsNotifier; + const DigitsDropdownButton({ + super.key, + this.digitsNotifier, + this.allowedDigits = const [6, 8], + this.enabled = true, + }); + @override + Widget build(BuildContext context) => LabeledDropdownButton( + label: AppLocalizations.of(context)!.digits, + enabled: enabled, + valueNotifier: digitsNotifier, + values: allowedDigits, + ); +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/duration_dropdown_button.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/duration_dropdown_button.dart new file mode 100644 index 000000000..63160f565 --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/duration_dropdown_button.dart @@ -0,0 +1,48 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/duration_unit_extension.dart'; + +import '../../../../l10n/app_localizations.dart'; +import '../../../../model/enums/duration_unit.dart'; +import '../labeled_dropdown_button.dart'; + +class DurationDropdownButton extends StatelessWidget { + final bool enabled; + final ValueNotifier? periodNotifier; + final List values; + final DurationUnit unit; + const DurationDropdownButton({ + super.key, + this.periodNotifier, + required this.values, + required this.unit, + this.enabled = true, + }); + @override + Widget build(BuildContext context) => LabeledDropdownButton( + label: AppLocalizations.of(context)!.period, + enabled: enabled, + valueNotifier: periodNotifier, + values: values, + valueLabels: [for (final value in values) unit.durationToUnitInt(value).toString()], + postFix: unit.postfix, + ); +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/encoding_dropdown_button.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/encoding_dropdown_button.dart new file mode 100644 index 000000000..098318d27 --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/encoding_dropdown_button.dart @@ -0,0 +1,40 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; + +import '../../../../l10n/app_localizations.dart'; +import '../../../../model/enums/encodings.dart'; +import '../labeled_dropdown_button.dart'; + +class EncodingsDropdownButton extends StatelessWidget { + final ValueNotifier? encodingNotifier; + final List values; + final bool enabled; + + const EncodingsDropdownButton({super.key, this.encodingNotifier, this.values = Encodings.values, this.enabled = true}); + @override + Widget build(BuildContext context) => LabeledDropdownButton( + label: AppLocalizations.of(context)!.encoding, + enabled: enabled, + valueNotifier: encodingNotifier, + values: values, + valueLabels: [for (final value in values) value.name], + ); +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/label_input_field.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/label_input_field.dart new file mode 100644 index 000000000..ad8870f59 --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/label_input_field.dart @@ -0,0 +1,70 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; + +import '../../../../l10n/app_localizations.dart'; + +class LabelInputField extends StatefulWidget { + static final FocusNode labelFieldFocus = FocusNode(debugLabel: 'LabelInputField'); + static String? validator(String? value, {AppLocalizations? locale}) { + if (value == null || value.isEmpty) { + labelFieldFocus.requestFocus(); + return locale?.pleaseEnterANameForThisToken ?? 'Not Valid'; + } + return null; + } + + final TextEditingController controller; + final ValueNotifier autoValidate; + + const LabelInputField({ + super.key, + required this.controller, + required this.autoValidate, + }); + + @override + State createState() => _LabelInputFieldState(); +} + +class _LabelInputFieldState extends State { + void _setState() => setState(() {}); + + @override + void initState() { + super.initState(); + widget.autoValidate.addListener(_setState); + } + + @override + void dispose() { + widget.autoValidate.removeListener(_setState); + super.dispose(); + } + + @override + Widget build(BuildContext context) => TextFormField( + controller: widget.controller, + autovalidateMode: widget.autoValidate.value ? AutovalidateMode.always : AutovalidateMode.disabled, + decoration: InputDecoration(labelText: AppLocalizations.of(context)!.name), + validator: (value) => LabelInputField.validator(value, locale: AppLocalizations.of(context)), + focusNode: LabelInputField.labelFieldFocus, + ); +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/secret_input_field.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/secret_input_field.dart new file mode 100644 index 000000000..55fe8b4bf --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/secret_input_field.dart @@ -0,0 +1,83 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; + +import '../../../../l10n/app_localizations.dart'; +import '../../../../model/enums/encodings.dart'; + +class SecretInputField extends StatefulWidget { + static final FocusNode secretFieldFocus = FocusNode(debugLabel: 'SecretInputField'); + static String? validator(String? value, Encodings encoding, {AppLocalizations? locale}) { + if (value == null || value.isEmpty) { + secretFieldFocus.requestFocus(); + return locale?.pleaseEnterASecretForThisToken ?? 'Not Valid'; + } + if (encoding.isInvalidEncoding(value)) { + secretFieldFocus.requestFocus(); + return locale?.theSecretDoesNotFitTheCurrentEncoding ?? 'Not Valid'; + } + return null; + } + + final TextEditingController controller; + + final ValueNotifier autoValidate; + final ValueNotifier encodingNotifier; + + const SecretInputField({ + super.key, + required this.controller, + required this.autoValidate, + required this.encodingNotifier, + }); + + @override + State createState() => _SecretInputFieldState(); +} + +class _SecretInputFieldState extends State { + void _setState() => setState(() {}); + + @override + void initState() { + super.initState(); + widget.autoValidate.addListener(_setState); + widget.encodingNotifier.addListener(_setState); + } + + @override + void dispose() { + widget.autoValidate.removeListener(_setState); + widget.encodingNotifier.removeListener(_setState); + super.dispose(); + } + + @override + Widget build(BuildContext context) => TextFormField( + controller: widget.controller, + autovalidateMode: widget.autoValidate.value ? AutovalidateMode.always : AutovalidateMode.disabled, + decoration: InputDecoration( + labelText: AppLocalizations.of(context)!.secretKey, + ), + validator: (value) => SecretInputField.validator(value, widget.encodingNotifier.value, locale: AppLocalizations.of(context)), + focusNode: SecretInputField.secretFieldFocus, + ); +} diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/token_type_dropdown_button.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/token_type_dropdown_button.dart new file mode 100644 index 000000000..a3736fd27 --- /dev/null +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/token_type_dropdown_button.dart @@ -0,0 +1,38 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; + +import '../../../../l10n/app_localizations.dart'; +import '../../../../model/enums/token_types.dart'; +import '../labeled_dropdown_button.dart'; + +class TokenTypeDropdownButton extends StatelessWidget { + static List values = TokenTypes.values.toList()..remove(TokenTypes.PIPUSH); + + final ValueNotifier typeNotifier; + const TokenTypeDropdownButton({super.key, required this.typeNotifier}); + @override + Widget build(BuildContext context) => LabeledDropdownButton( + label: AppLocalizations.of(context)!.type, + valueNotifier: typeNotifier, + values: values, + valueLabels: [for (final value in values) value.name], + ); +} diff --git a/lib/views/import_tokens_view/import_tokens_view.dart b/lib/views/import_tokens_view/import_tokens_view.dart index 83959eccc..fbd606e78 100644 --- a/lib/views/import_tokens_view/import_tokens_view.dart +++ b/lib/views/import_tokens_view/import_tokens_view.dart @@ -62,7 +62,6 @@ class _ImportTokensViewState extends ConsumerState { ), ), ); - return; } else { tokensToImport = await Navigator.of(context).push( MaterialPageRoute( @@ -70,11 +69,9 @@ class _ImportTokensViewState extends ConsumerState { ), ); } - if (tokensToImport != null) { - ref.read(tokenProvider.notifier).addOrReplaceTokens(tokensToImport); - if (!mounted) return; - Navigator.of(context).pop(true); - } + if (tokensToImport == null) return; + if (tokensToImport.isNotEmpty) ref.read(tokenProvider.notifier).addOrReplaceTokens(tokensToImport); + if (mounted) return Navigator.of(context).pop(tokensToImport.isNotEmpty); } @override 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 42d72cc95..92c5d9570 100644 --- a/lib/views/import_tokens_view/pages/import_start_page.dart +++ b/lib/views/import_tokens_view/pages/import_start_page.dart @@ -76,16 +76,16 @@ class _ImportStartPageState extends ConsumerState { _linkController.dispose(); _deleteCopyOfXFile(); future?.ignore(); - WidgetsBinding.instance.addPostFrameCallback((_) { - globalRef?.read(progressStateProvider.notifier).deleteProgress(); - }); + // WidgetsBinding.instance.addPostFrameCallback((_) { + // globalRef?.read(progressStateProvider.notifier).deleteProgress(); + // }); super.dispose(); } @override Widget build(BuildContext context) { final localizations = AppLocalizations.of(context)!; - final currentLoadingProgress = ref.watch(progressStateProvider).progress; + // final currentLoadingProgress = ref.watch(progressStateProvider).progress; return Scaffold( appBar: AppBar( title: Text(widget.appName), @@ -151,11 +151,11 @@ class _ImportStartPageState extends ConsumerState { children: [ Stack( children: [ - LinearProgressIndicator( - minHeight: _progessLabel != null ? 16 : null, - value: currentLoadingProgress, - semanticsLabel: _progessLabel, - ), + // LinearProgressIndicator( + // minHeight: _progessLabel != null ? 16 : null, + // value: currentLoadingProgress, + // semanticsLabel: _progessLabel, + // ), if (_progessLabel != null) Positioned.fill( child: Center( @@ -192,7 +192,7 @@ class _ImportStartPageState extends ConsumerState { final fileProcessor = processor as TokenImportFileProcessor; final localizations = AppLocalizations.of(context)!; final XTypeGroup typeGroup = XTypeGroup(label: localizations.selectFile); - final XFile? file = await openFile(acceptedTypeGroups: [typeGroup]); + final XFile? file = await openFile(acceptedTypeGroups: [typeGroup]); if (file == null) { Logger.warning("No file selected", name: "_pickAFile#ImportSelectFilePage"); return; @@ -230,7 +230,7 @@ class _ImportStartPageState extends ConsumerState { isPrivacyIdeaToken: false, data: t.resultData.origin?.data ?? fileString, ), - resultHandlerType: const TypeMatcher(), + resultHandlerType: const TypeValidatorRequired(), ); }).toList(); @@ -267,7 +267,7 @@ class _ImportStartPageState extends ConsumerState { token: t.resultData, data: t.resultData.origin?.data ?? uri.toString(), ), - resultHandlerType: const TypeMatcher(), + resultHandlerType: const TypeValidatorRequired(), ); }).toList(); Logger.info("QR code scanned successfully", name: "_scanQrCode#ImportStartPage"); @@ -363,7 +363,7 @@ class _ImportStartPageState extends ConsumerState { isPrivacyIdeaToken: false, data: _linkController.text, ), - resultHandlerType: const TypeMatcher(), + resultHandlerType: const TypeValidatorRequired(), ); }).toList(); if (!mounted) return; diff --git a/lib/widgets/deactivated.dart b/lib/widgets/deactivated.dart new file mode 100644 index 000000000..b012090ea --- /dev/null +++ b/lib/widgets/deactivated.dart @@ -0,0 +1,38 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; + +class Deactivateable extends StatelessWidget { + final bool deactivated; + final Widget child; + + const Deactivateable({super.key, required this.deactivated, required this.child}); + + @override + Widget build(BuildContext context) => deactivated + ? ColorFiltered( + colorFilter: const ColorFilter.mode( + Colors.grey, + BlendMode.saturation, + ), + child: AbsorbPointer(child: child), + ) + : child; +} diff --git a/lib/widgets/mutex_button.dart b/lib/widgets/mutex_button.dart new file mode 100644 index 000000000..623ecb703 --- /dev/null +++ b/lib/widgets/mutex_button.dart @@ -0,0 +1,62 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; +import 'package:mutex/mutex.dart'; + +class MutexButton extends StatefulWidget { + final Future Function()? onPressed; + final Widget child; + final ButtonStyle? style; + const MutexButton({super.key, required this.onPressed, required this.child, this.style}); + + @override + State createState() => _MutexButtonState(); +} + +class _MutexButtonState extends State { + final m = Mutex(); + late bool isPressable = !m.isLocked && widget.onPressed != null; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) => Padding( + padding: const EdgeInsets.all(3), + child: ElevatedButton( + onPressed: isPressable + ? () => m.protect(() async { + setState(() => isPressable = false); + await widget.onPressed!(); + setState(() => isPressable = true); + }) + : null, + style: widget.style?.merge(Theme.of(context).elevatedButtonTheme.style) ?? Theme.of(context).elevatedButtonTheme.style, + child: widget.child, + ), + ); +} diff --git a/test/unit_test/model/token/day_password_test.dart b/test/unit_test/model/token/day_password_test.dart index 097eb7007..fd4fed7b2 100644 --- a/test/unit_test/model/token/day_password_test.dart +++ b/test/unit_test/model/token/day_password_test.dart @@ -87,7 +87,7 @@ void _testDayPasswordToken() { 'URI_PIN': false, 'URI_IMAGE': 'example.png', }; - final totpFromUriMap = DayPasswordToken.fromUriMap(uriMap); + final totpFromUriMap = DayPasswordToken.fromOtpAuthMap(uriMap); expect(totpFromUriMap.period, const Duration(seconds: 30)); expect(totpFromUriMap.label, 'label'); expect(totpFromUriMap.issuer, 'issuer'); @@ -109,7 +109,7 @@ void _testDayPasswordToken() { 'URI_PIN': false, 'URI_IMAGE': 'example.png', }; - expect(() => DayPasswordToken.fromUriMap(uriMap), throwsA(isA())); + expect(() => DayPasswordToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); test('with zero period', () { final uriMap = { @@ -123,7 +123,7 @@ void _testDayPasswordToken() { 'URI_PIN': false, 'URI_IMAGE': 'example.png', }; - expect(() => DayPasswordToken.fromUriMap(uriMap), throwsA(isA())); + expect(() => DayPasswordToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); test('with zero digits', () { final uriMap = { @@ -137,7 +137,7 @@ void _testDayPasswordToken() { 'URI_PIN': false, 'URI_IMAGE': 'example.png', }; - expect(() => DayPasswordToken.fromUriMap(uriMap), throwsA(isA())); + expect(() => DayPasswordToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); test('with lowercase algorithm', () { final uriMap = { @@ -151,12 +151,12 @@ void _testDayPasswordToken() { 'URI_PIN': false, 'URI_IMAGE': 'example.png', }; - final totpFromUriMap = DayPasswordToken.fromUriMap(uriMap); + final totpFromUriMap = DayPasswordToken.fromOtpAuthMap(uriMap); expect(totpFromUriMap.algorithm, Algorithms.SHA1); }); test('with empty map', () { final uriMap = {}; - expect(() => DayPasswordToken.fromUriMap(uriMap), throwsA(isA())); + expect(() => DayPasswordToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); }); test('fromJson', () { diff --git a/test/unit_test/model/token/hotp_token_test.dart b/test/unit_test/model/token/hotp_token_test.dart index eb97a6fc6..2e637b16c 100644 --- a/test/unit_test/model/token/hotp_token_test.dart +++ b/test/unit_test/model/token/hotp_token_test.dart @@ -89,7 +89,7 @@ void _testHotpToken() { 'URI_PIN': true, 'URI_IMAGE': 'example.png', }; - final hotpFromUriMap = HOTPToken.fromUriMap(uriMap); + final hotpFromUriMap = HOTPToken.fromOtpAuthMap(uriMap); expect(hotpFromUriMap.counter, 10); expect(hotpFromUriMap.label, 'label'); expect(hotpFromUriMap.issuer, 'issuer'); @@ -111,7 +111,7 @@ void _testHotpToken() { 'URI_PIN': true, 'URI_IMAGE': 'example.png', }; - expect(() => HOTPToken.fromUriMap(uriMap), throwsArgumentError); + expect(() => HOTPToken.fromOtpAuthMap(uriMap), throwsArgumentError); }); test('digits is zero', () { final uriMap = { @@ -125,7 +125,7 @@ void _testHotpToken() { 'URI_PIN': true, 'URI_IMAGE': 'example.png', }; - expect(() => HOTPToken.fromUriMap(uriMap), throwsArgumentError); + expect(() => HOTPToken.fromOtpAuthMap(uriMap), throwsArgumentError); }); test('with lowercase algorithm', () { final uriMap = { @@ -139,7 +139,7 @@ void _testHotpToken() { 'URI_PIN': true, 'URI_IMAGE': 'example.png', }; - final hotpFromUriMap = HOTPToken.fromUriMap(uriMap); + final hotpFromUriMap = HOTPToken.fromOtpAuthMap(uriMap); expect(hotpFromUriMap.counter, 10); expect(hotpFromUriMap.label, 'label'); expect(hotpFromUriMap.issuer, 'issuer'); @@ -153,7 +153,7 @@ void _testHotpToken() { test('with empty map', () { final uriMap = {}; - expect(() => HOTPToken.fromUriMap(uriMap), throwsArgumentError); + expect(() => HOTPToken.fromOtpAuthMap(uriMap), throwsArgumentError); }); }); }); diff --git a/test/unit_test/model/token/push_token_test.dart b/test/unit_test/model/token/push_token_test.dart index ca2b83321..209a6f122 100644 --- a/test/unit_test/model/token/push_token_test.dart +++ b/test/unit_test/model/token/push_token_test.dart @@ -177,7 +177,7 @@ void _testPushToken() { 'URI_ROLLOUT_URL': Uri.parse('http://www.example.com'), 'URI_TTL': 10, }; - final token = PushToken.fromUriMap(uriMap); + final token = PushToken.fromOtpAuthMap(uriMap); expect(token.type, 'PIPUSH'); expect(token.label, 'label'); expect(token.issuer, 'issuer'); @@ -188,7 +188,7 @@ void _testPushToken() { }); test('with empty map', () { final uriMap = {}; - expect(() => PushToken.fromUriMap(uriMap), throwsA(isA())); + expect(() => PushToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); }); }); diff --git a/test/unit_test/model/token/steam_token_test.dart b/test/unit_test/model/token/steam_token_test.dart index 6f96dc2fe..ac6be89ca 100644 --- a/test/unit_test/model/token/steam_token_test.dart +++ b/test/unit_test/model/token/steam_token_test.dart @@ -78,7 +78,7 @@ void _testSteamToken() { 'URI_PIN': false, 'URI_IMAGE': 'example.png', }; - final totpFromUriMap = SteamToken.fromUriMap(uriMap); + final totpFromUriMap = SteamToken.fromOtpAuthMap(uriMap); expect(totpFromUriMap.period, 30); expect(totpFromUriMap.label, 'label'); expect(totpFromUriMap.issuer, 'issuer'); @@ -97,11 +97,11 @@ void _testSteamToken() { 'URI_PIN': false, 'URI_IMAGE': 'example.png', }; - expect(() => SteamToken.fromUriMap(uriMap), throwsA(isA())); + expect(() => SteamToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); test('with empty map', () { final uriMap = {}; - expect(() => TOTPToken.fromUriMap(uriMap), throwsA(isA())); + expect(() => TOTPToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); }); test('fromJson', () { diff --git a/test/unit_test/model/token/totp_token_test.dart b/test/unit_test/model/token/totp_token_test.dart index d6e7c5f1a..6aaa4fb46 100644 --- a/test/unit_test/model/token/totp_token_test.dart +++ b/test/unit_test/model/token/totp_token_test.dart @@ -85,7 +85,7 @@ void _testTotpToken() { 'URI_PIN': false, 'URI_IMAGE': 'example.png', }; - final totpFromUriMap = TOTPToken.fromUriMap(uriMap); + final totpFromUriMap = TOTPToken.fromOtpAuthMap(uriMap); expect(totpFromUriMap.period, 30); expect(totpFromUriMap.label, 'label'); expect(totpFromUriMap.issuer, 'issuer'); @@ -107,7 +107,7 @@ void _testTotpToken() { 'URI_PIN': false, 'URI_IMAGE': 'example.png', }; - expect(() => TOTPToken.fromUriMap(uriMap), throwsA(isA())); + expect(() => TOTPToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); test('with zero period', () { final uriMap = { @@ -121,7 +121,7 @@ void _testTotpToken() { 'URI_PIN': false, 'URI_IMAGE': 'example.png', }; - expect(() => TOTPToken.fromUriMap(uriMap), throwsA(isA())); + expect(() => TOTPToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); test('with zero digits', () { final uriMap = { @@ -135,7 +135,7 @@ void _testTotpToken() { 'URI_PIN': false, 'URI_IMAGE': 'example.png', }; - expect(() => TOTPToken.fromUriMap(uriMap), throwsA(isA())); + expect(() => TOTPToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); test('with lowercase algorithm', () { final uriMap = { @@ -149,12 +149,12 @@ void _testTotpToken() { 'URI_PIN': false, 'URI_IMAGE': 'example.png', }; - final totpFromUriMap = TOTPToken.fromUriMap(uriMap); + final totpFromUriMap = TOTPToken.fromOtpAuthMap(uriMap); expect(totpFromUriMap.algorithm, Algorithms.SHA1); }); test('with empty map', () { final uriMap = {}; - expect(() => TOTPToken.fromUriMap(uriMap), throwsA(isA())); + expect(() => TOTPToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); }); test('fromJson', () { diff --git a/test/unit_test/repo/hybrid_token_container_repo_test.dart b/test/unit_test/repo/hybrid_token_container_repo_test.dart index cd9fd2179..4a28d1319 100644 --- a/test/unit_test/repo/hybrid_token_container_repo_test.dart +++ b/test/unit_test/repo/hybrid_token_container_repo_test.dart @@ -98,7 +98,7 @@ void _testHybridTokenContainerRepository() { expect(state.lastSyncAt.isBefore(dateTimeAfter), isTrue); expect(state.syncedTokenTemplates.length, 1); final template = state.syncedTokenTemplates.first; - expect(template.data, token.toUriMap(), reason: 'Should be the remote state if both are changed since last sync'); + expect(template.data, token.toOtpAuthMap(), reason: 'Should be the remote state if both are changed since last sync'); }); }); } From f24773bae7dec3c2998b6929bf911b285bb9dfb6 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Thu, 5 Sep 2024 13:44:56 +0200 Subject: [PATCH 033/285] refactoring --- .../labeled_dropdown_button.dart | 61 +++++++++---------- .../{deactivated.dart => deactivateable.dart} | 0 2 files changed, 28 insertions(+), 33 deletions(-) rename lib/widgets/{deactivated.dart => deactivateable.dart} (100%) 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 c79e56843..bd662e8e1 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 @@ -19,6 +19,7 @@ */ import 'package:flutter/material.dart'; import '../../../utils/logger.dart'; +import '../../../widgets/deactivateable.dart'; import 'add_token_manually_row.dart'; class LabeledDropdownButton extends StatefulWidget { @@ -62,41 +63,35 @@ class _LabeledDropdownButtonState extends State> { @override Widget build(BuildContext context) { - final child = AddTokenManuallyRow( - label: widget.label, - child: DropdownButton( - value: widget.valueNotifier?.value != null && widget.values.contains(widget.valueNotifier!.value) - ? widget.valueNotifier!.value - : widget.values.firstOrNull, - isExpanded: true, - items: [ - for (var i = 0; i < widget.values.length; i++) - DropdownMenuItem( - value: widget.values[i], - child: Text( - '${widget.valueLabels != null && i < widget.valueLabels!.length ? widget.valueLabels![i] : widget.values[i].toString()}' - ' ${widget.postFix}', - style: Theme.of(context).textTheme.bodyMedium, - overflow: TextOverflow.fade, - softWrap: false, + return Deactivateable( + deactivated: !widget.enabled, + child: AddTokenManuallyRow( + label: widget.label, + child: DropdownButton( + value: widget.valueNotifier?.value != null && widget.values.contains(widget.valueNotifier!.value) + ? widget.valueNotifier!.value + : widget.values.firstOrNull, + isExpanded: true, + items: [ + for (var i = 0; i < widget.values.length; i++) + DropdownMenuItem( + value: widget.values[i], + child: Text( + '${widget.valueLabels != null && i < widget.valueLabels!.length ? widget.valueLabels![i] : widget.values[i].toString()}' + ' ${widget.postFix}', + style: Theme.of(context).textTheme.bodyMedium, + overflow: TextOverflow.fade, + softWrap: false, + ), ), - ), - ], - onChanged: (T? newValue) { - if (newValue == null) return; - Logger.info('DropdownButton onChanged: $newValue'); - widget.valueNotifier?.value = newValue; - }, + ], + onChanged: (T? newValue) { + if (newValue == null) return; + Logger.info('DropdownButton onChanged: $newValue'); + widget.valueNotifier?.value = newValue; + }, + ), ), ); - - return widget.enabled - ? child - : ShaderMask( - shaderCallback: (Rect bounds) => LinearGradient( - colors: [Colors.grey.shade600, Colors.grey.shade600], - ).createShader(bounds), - child: IgnorePointer(ignoring: true, child: child), - ); } } diff --git a/lib/widgets/deactivated.dart b/lib/widgets/deactivateable.dart similarity index 100% rename from lib/widgets/deactivated.dart rename to lib/widgets/deactivateable.dart From e56e3d5b33331f7881f8923b7ecd7860a33facb8 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Fri, 13 Sep 2024 10:38:36 +0200 Subject: [PATCH 034/285] container sync works now --- lib/api/token_container_api_endpoint.dart | 611 ++++- .../token_container_api_endpoint.freezed.dart | 717 +++++ lib/interfaces/api_endpoint.dart | 9 +- lib/interfaces/repo/container_repository.dart | 68 +- lib/l10n/app_cs.arb | 4 +- lib/l10n/app_de.arb | 4 +- lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/l10n/app_fr.arb | 4 +- lib/l10n/app_nl.arb | 4 +- lib/l10n/app_pl.arb | 4 +- lib/model/enums/ec_key_algorithm.dart | 41 + .../push_token_rollout_state_extension.dart | 9 +- lib/model/mixins/sortable_mixin.dart | 4 + lib/model/processor_result.freezed.dart | 164 +- .../riverpod_states/credentials_state.dart | 6 +- .../progress_state.freezed.dart | 64 +- lib/model/riverpod_states/token_state.dart | 41 +- lib/model/token_container.dart | 281 -- lib/model/token_container.freezed.dart | 2312 ----------------- lib/model/token_container.g.dart | 145 -- lib/model/token_folder.dart | 6 + lib/model/token_import/token_origin_data.dart | 16 + lib/model/token_template.dart | 168 ++ lib/model/token_template.freezed.dart | 713 +++++ lib/model/token_template.g.dart | 59 + lib/model/tokens/container_credentials.dart | 35 +- .../tokens/container_credentials.freezed.dart | 149 +- lib/model/tokens/container_credentials.g.dart | 12 +- lib/model/tokens/day_password_token.dart | 57 +- lib/model/tokens/day_password_token.g.dart | 9 +- lib/model/tokens/hotp_token.dart | 59 +- lib/model/tokens/hotp_token.g.dart | 7 +- lib/model/tokens/otp_token.dart | 54 +- lib/model/tokens/push_token.dart | 39 +- lib/model/tokens/push_token.g.dart | 9 +- lib/model/tokens/steam_token.dart | 20 +- lib/model/tokens/steam_token.g.dart | 9 +- lib/model/tokens/token.dart | 68 +- lib/model/tokens/totp_token.dart | 20 +- lib/model/tokens/totp_token.g.dart | 9 +- .../otp_auth_processor.dart | 18 +- .../aegis_import_file_processor.dart | 19 +- ...thenticator_pro_import_file_processor.dart | 12 +- .../free_otp_plus_import_file_processor.dart | 12 +- ...google_authenticator_qrfile_processor.dart | 2 - .../two_fas_import_file_processor.dart | 12 +- lib/repo/secure_token_repository.dart | 9 +- ...brid_token_container_state_repository.dart | 380 +-- ...token_container_state_repository.dart.dart | 180 +- lib/utils/ecc_utils.dart | 9 +- lib/utils/identifiers.dart | 39 +- .../credential_notifier.dart | 91 +- .../credential_notifier.g.dart | 40 +- .../progress_state_provider.g.dart | 176 -- .../token_container_notifier.dart | 367 +-- .../token_container_notifier.g.dart | 179 -- .../generated_providers/token_notifier.dart | 272 +- .../generated_providers/token_notifier.g.dart | 2 +- .../token_container_token_state_listener.dart | 122 +- lib/utils/utils.dart | 3 +- lib/views/container_view/container_view.dart | 21 +- .../push_token_widgets/push_token_widget.dart | 10 +- .../rollout_failed_widget.dart | 29 +- lib/widgets/app_wrapper.dart | 64 +- lib/widgets/default_refresh_indicator.dart | 2 +- pubspec.lock | 372 +-- pubspec.yaml | 5 +- .../model/token/push_token_test.dart | 1 - .../hybrid_token_container_repo_test.dart | 184 +- .../state_notifiers/token_notifier_test.dart | 1 - 71 files changed, 4131 insertions(+), 4520 deletions(-) create mode 100644 lib/api/token_container_api_endpoint.freezed.dart delete mode 100644 lib/model/token_container.dart delete mode 100644 lib/model/token_container.freezed.dart delete mode 100644 lib/model/token_container.g.dart create mode 100644 lib/model/token_template.dart create mode 100644 lib/model/token_template.freezed.dart create mode 100644 lib/model/token_template.g.dart delete mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.g.dart delete mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index cec6353a2..6ef736ca4 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -1,93 +1,518 @@ -// /* -// * privacyIDEA Authenticator -// * -// * Author: Frank Merkel -// * -// * Copyright (c) 2024 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:http/io_client.dart'; - -// import '../model/extensions/enums/encodings_extension.dart'; -// import '../model/token_container.dart'; -// import '../model/tokens/container_credentials.dart'; -// import '../utils/identifiers.dart'; -// import '../utils/logger.dart'; - -// import '../interfaces/api_endpoint.dart'; -// import '../model/enums/encodings.dart'; -// import '../model/enums/token_types.dart'; -// import '../model/riverpod_states/credentials_state.dart'; - - -// class TokenContainerApiEndpoint implements ApiEndpioint { -// @override -// final ContainerCredential credential; -// final IOClient _client = IOClient(); -// TokenContainerApiEndpoint({required this.credential}); - -// @override -// Future fetch() { -// throw UnimplementedError(); -// } - -// String _buildContainerBo - -// @override -// Future sync(TokenContainer containerState) async { -// Logger.info('Syncing container with server', name: 'TokenContainerApiEndpoint#sync'); -// final serverTemplates = _data[containerState.serial]; -// if (serverTemplates == null) { -// return containerState.copyTransformInto(args: {'message': 'Container not found'}); -// } -// for (var templateSerial in serverTemplates.keys) { -// final template = serverTemplates[templateSerial]; -// if (template?.serial == null) { -// // Add serial(key of map) to template -// serverTemplates[templateSerial] = template!.copyAddAll({URI_SERIAL: templateSerial}); -// } -// } -// final localTemplates = containerState.localTokenTemplates; -// Logger.debug('Local templates: ${localTemplates.length}', name: 'TokenContainerApiEndpoint#sync'); -// for (var localTemplate in localTemplates) { -// final oldLabel = localTemplate.data[URI_LABEL] as String; -// Logger.debug('Old label: "$oldLabel" starts with "${containerState.serial}" ?', name: 'TokenContainerApiEndpoint#sync'); -// if (oldLabel.startsWith(containerState.serial) == true) { -// var merged = localTemplate.copyAddAll({ -// URI_LABEL: '123 😀', -// }); -// Logger.debug('New label: "${merged.data[URI_LABEL]}"', name: 'TokenContainerApiEndpoint#sync'); -// if (merged.serial == null) { -// merged = merged.copyWith(data: merged.copyAddAll({URI_SERIAL: 'tokenID${DateTime.now().millisecondsSinceEpoch}'}).data); -// } -// Logger.debug('MergedData: ${merged.data}', name: 'TokenContainerApiEndpoint#sync'); -// final localTemplateSerial = merged.serial!; -// serverTemplates[localTemplateSerial] = merged; -// } -// } -// Logger.debug('Server templates: $serverTemplates', name: 'TokenContainerApiEndpoint#sync'); -// _data[containerState.serial] = serverTemplates; -// Logger.debug('_data: $_data', name: 'TokenContainerApiEndpoint#sync'); -// final serverTemplatesMerged = _data[containerState.serial]!.values.toList(); -// final newContainerState = TokenContainerSynced( -// lastSyncAt: DateTime.now(), -// serial: containerState.serial, -// description: 'Synced with server', -// syncedTokenTemplates: serverTemplatesMerged, -// localTokenTemplates: [], -// ); -// Logger.debug('Synced container: $newContainerState', name: 'TokenContainerApiEndpoint#sync'); -// return newContainerState; -// } -// } +// ignore_for_file: constant_identifier_names + +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:collection/collection.dart'; +import 'package:cryptography/cryptography.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:http/http.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/ec_key_algorithm_extension.dart'; +import 'package:privacyidea_authenticator/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart'; +import 'package:privacyidea_authenticator/utils/ecc_utils.dart'; +import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; + +import '../model/enums/ec_key_algorithm.dart'; +import '../model/riverpod_states/token_state.dart'; +import '../model/token_import/token_origin_data.dart'; +import '../model/token_template.dart'; +import '../model/tokens/container_credentials.dart'; +import '../model/tokens/token.dart'; +import '../utils/globals.dart'; +import '../utils/identifiers.dart'; +import '../utils/logger.dart'; + +import '../widgets/dialog_widgets/enter_passphrase_dialog.dart'; + +part 'token_container_api_endpoint.freezed.dart'; + +class PrivacyideaContainerApi { + final PrivacyideaIOClient _ioClient; + const PrivacyideaContainerApi({required PrivacyideaIOClient ioClient}) : _ioClient = ioClient; + + Future _getChallenge(ContainerCredentialFinalized container) async { + final initResponse = await _ioClient.doGet(url: container.syncUrl, parameters: {CONTAINER_SERIAL: container.serial}); + try { + Logger.debug('Received container sync challenge: ${initResponse.body}', name: 'TokenContainerApiEndpoint#sync'); + final piResponse = PiServerResponse.fromResponse(initResponse); + if (piResponse.isError) { + Logger.error('Error while syncing container: ${piResponse.asError.resultError}', name: 'TokenContainerApiEndpoint#sync'); + return null; + } + return piResponse.asSuccess.resultValue; + } catch (e, s) { + Logger.error('Error while syncing container: $e', name: 'TokenContainerApiEndpoint#sync', stackTrace: s); + return null; + } + } + + // Returns a tuple of updated/new tokens and serials of deleted tokens + Future<(List, List)?> sync(ContainerCredentialFinalized container, TokenState tokenState) async { + final containerTokenTemplates = tokenState.containerTokens(container.serial).toTemplates(); + final maybePiTokensTemplates = tokenState.maybePiTokens.toTemplates(); + + final ContainerChallenge? challenge = await _getChallenge(container); + if (challenge == null) return null; + + final decryptedContainerDictJson = await _getContainerDict( + container: container, + challenge: challenge, + otpAuthMaps: [ + for (var template in [...containerTokenTemplates, ...maybePiTokensTemplates]) template.otpAuthMap + ], + ); + if (decryptedContainerDictJson == null) return null; + + final tokens = decryptedContainerDictJson[CONTAINER_DICT_TOKENS] as Map; + final newOtpAuthTokens = (tokens[CONTAINER_DICT_TOKENS_ADD] as List).cast().map(Uri.parse).toList(); + final newTokens = await _parseNewTokens(otpAuthUris: newOtpAuthTokens, container: container); + + // All server tokens should have a serial but if client token has no serial the server token also has otps + final serverTokensUpdate = (tokens[CONTAINER_DICT_TOKENS_UPDATE] as List).cast>(); + final serverTokensWithOtps = serverTokensUpdate.where((element) => element[OTP_AUTH_OTP_VALUES] != null).toList(); + serverTokensWithOtps.forEach(serverTokensUpdate.remove); + + // Now we have to find all tokens that are in the server lists but not in the local list + // These tokens should be deleted + + // MaybePiTokens Should not be deleted + final mergedTemplatesWithOtps = _handleMaybePiTokens( + maybePiTokensTemplates: maybePiTokensTemplates, + serverTokensWithOtps: serverTokensWithOtps, + container: container, + ); + + final serverTokensWithSerial = serverTokensUpdate.where((element) => element[OTP_AUTH_SERIAL] != null).toList(); + serverTokensWithSerial.forEach(serverTokensUpdate.remove); + assert(serverTokensUpdate.isEmpty, 'Server token otps map should be empty after removing all tokens with serial and otps'); + // Container tokens can be deleted if they are not in the server list + final List mergedTemplatesWithSerial; + final List deleteSerials; + (mergedTemplatesWithSerial, deleteSerials) = _handlePiTokens( + containerTokenTemplates: containerTokenTemplates, + serverTokensWithSerial: serverTokensWithSerial, + ); + + final updatedTokens = []; + // Add updated Tokens + for (var mergedTemplate in [...mergedTemplatesWithOtps, ...mergedTemplatesWithSerial]) { + final token = container.addOriginToToken(token: mergedTemplate.toToken()); + updatedTokens.add(token); + } + + return ([...updatedTokens, ...newTokens], deleteSerials); + } + + Future finalizeContainer(ContainerCredentialUnfinalized container, EccUtils eccUtils) async { + final ecPrivateClientKey = container.ecPrivateClientKey; + if (ecPrivateClientKey == null) return null; + + final passphrase = container.passphraseQuestion?.isNotEmpty == true ? await EnterPassphraseDialog.show(await globalContext) : null; + final message = '${container.nonce}' + '|${container.timestamp.toIso8601String().replaceFirst('Z', '+00:00')}' + '|${container.finalizationUrl}' + '|${container.serial}' + '${passphrase != null ? '|$passphrase' : ''}'; + + final signature = eccUtils.signWithPrivateKey(ecPrivateClientKey, message); + + final body = { + 'container_serial': container.serial, + 'public_client_key': container.publicClientKey, + 'signature': signature, + }; + + return _ioClient.doPost(url: container.finalizationUrl, body: body, sslVerify: false); //TODO: sslVerify + } + + /* //////////////////////////// + ////// PRIVATE FUNCTIONS ////// + ////////////////////////////// */ + + Future?> _getContainerDict({ + required ContainerCredentialFinalized container, + required ContainerChallenge challenge, + required List otpAuthMaps, + }) async { + final encKeyPair = await X25519().newKeyPair(); + final publicKey = await encKeyPair.extractPublicKey(); + + final algorithm = X25519(); + + final publicKeyBase64 = base64.encode(publicKey.bytes); + + final containerDict = { + CONTAINER_DICT_SERIAL: container.serial, + CONTAINER_DICT_TYPE: 'smartphone', + 'tokens': otpAuthMaps, + }; + final signMessage = + '${challenge.nonce}|${challenge.timeStamp}|${container.serial}|${challenge.finalizeSyncUrl}|$publicKeyBase64|${jsonEncode(containerDict)}'; + Logger.debug(signMessage); + final signature = container.signMessage(signMessage); + Logger.debug('Sended container: ${jsonEncode(containerDict)}'); + final body = { + CONTAINER_SYNC_SIGNATURE: signature, + CONTAINER_SYNC_PUBLIC_CLIENT_KEY: publicKeyBase64, + CONTAINER_SYNC_DICT_CLIENT: jsonEncode(containerDict), + }; + + final response = await _ioClient.doPost(url: Uri.parse(challenge.finalizeSyncUrl), body: body); + final containerSyncResponse = PiServerResponse.fromResponse(response); + if (containerSyncResponse.isError) { + Logger.error('Error while syncing container: ${containerSyncResponse.asError.resultError}', name: 'TokenContainerApiEndpoint#sync'); + return null; + } + + final syncResult = containerSyncResponse.asSuccess.resultValue; + + final remotePublicKey = SimplePublicKey(syncResult.publicServerKeyBytes, type: KeyPairType.x25519); + final sharedKey = await algorithm.sharedSecretKey(keyPair: encKeyPair, remotePublicKey: remotePublicKey); + Logger.debug('Shared key: ${await sharedKey.extractBytes()}'); + + final params = syncResult.encryptionParams; + + final secretMsgBytes = base64Url.decoder.convert(syncResult.containerDictEncrypted); + final ivBytes = base64Url.decoder.convert(params.initVector); + final tagBytes = base64Url.decoder.convert(params.tag); + + final decryptedContainerDict = await AesGcm.with256bits(nonceLength: 16).decrypt( + SecretBox( + secretMsgBytes, + nonce: ivBytes, + mac: Mac(tagBytes), + ), + secretKey: sharedKey, + ); + + Logger.debug('Decrypted container dict: ${utf8.decode(decryptedContainerDict)}'); + return jsonDecode(utf8.decode(decryptedContainerDict)) as Map; + } + + List _handleMaybePiTokens({ + required List maybePiTokensTemplates, + required List> serverTokensWithOtps, + required ContainerCredentialFinalized container, + }) { + final merged = []; + for (var serverTokenWithOtp in serverTokensWithOtps) { + final otps = (serverTokenWithOtp[OTP_AUTH_OTP_VALUES] as List).cast(); + var mergedTemplate = maybePiTokensTemplates.firstWhere( + (maybePiToken) => const IterableEquality().equals(otps, maybePiToken.otpValues), + orElse: () => TokenTemplate.withOtps( + otps: serverTokenWithOtp[OTP_AUTH_OTP_VALUES]!, otpAuthMap: serverTokenWithOtp, container: container, checkedContainers: [container.serial]), + ); + mergedTemplate = mergedTemplate.withOtpAuthData(serverTokenWithOtp); + mergedTemplate = mergedTemplate.copyWith(container: container); + merged.add(mergedTemplate); + } + return merged; + } + + Future> _parseNewTokens({required ContainerCredentialFinalized container, required List otpAuthUris}) async { + final newTokens = []; + for (var otpAuthUri in otpAuthUris) { + Logger.debug('Processing token: $otpAuthUri'); + var newToken = (await const OtpAuthProcessor().processUri(otpAuthUri)).firstOrNull?.asSuccess?.resultData; + if (newToken != null) { + newToken = container.addOriginToToken(token: newToken, tokenData: otpAuthUri.toString()); + newTokens.add(newToken); + } + } + return newTokens; + } + + (List, List) _handlePiTokens({ + required List containerTokenTemplates, + required List> serverTokensWithSerial, + }) { + final deleteSerials = []; + final mergedTemplatesWithSerial = []; + for (var containerToken in containerTokenTemplates) { + final serverToken = serverTokensWithSerial.firstWhereOrNull((element) => element[OTP_AUTH_SERIAL] == containerToken.serial); + serverTokensWithSerial.remove(serverToken); + if (serverToken == null) { + deleteSerials.add(containerToken.serial!); + } else { + var mergedTemplate = containerToken.withOtpAuthData(serverToken); + mergedTemplatesWithSerial.add(mergedTemplate); + } + } + return (mergedTemplatesWithSerial, deleteSerials); + } +} + +abstract class PiServerResult { + bool get status; + PiServerResultError? get asError => this is PiServerResultError ? this as PiServerResultError : null; + PiServerResultValue? get asValue => this is PiServerResultValue ? this as PiServerResultValue : null; + const PiServerResult(); +} + +@freezed +class PiServerResponse with _$PiServerResponse { + static const RESULT = 'result'; + static const DETAIL = 'detail'; + static const ID = 'id'; + static const JSONRPC = 'jsonrpc'; + static const TIME = 'time'; + static const VERSION = 'version'; + static const SIGNATURE = 'signature'; + + static const RESULT_STATUS = 'status'; + static const RESULT_VALUE = 'value'; + static const RESULT_ERROR = 'error'; + + const PiServerResponse._(); + factory PiServerResponse.success({ + required dynamic detail, + required int id, + required String jsonrpc, + required T resultValue, + required double time, + required String version, + required String signature, + }) = PiServerResponseSuccess; + bool get isSuccess => this is PiServerResponseSuccess; + PiServerResponseSuccess get asSuccess => this as PiServerResponseSuccess; + + factory PiServerResponse.error({ + required dynamic detail, + required int id, + required String jsonrpc, + required PiServerResultError resultError, + required double time, + required String version, + required String signature, + }) = PiServerResponseError; + bool get isError => this is PiServerResponseError; + PiServerResponseError get asError => this as PiServerResponseError; + + factory PiServerResponse.fromJson(Map json) { + Logger.debug('Received container sync response: $json', name: 'PiServerResponse#fromJson'); + final map = validateMap( + map: json, + validators: { + RESULT: const TypeValidatorRequired>(), + ID: const TypeValidatorRequired(), + JSONRPC: const TypeValidatorRequired(), + DETAIL: const TypeValidatorOptional(), + TIME: const TypeValidatorRequired(), + VERSION: const TypeValidatorRequired(), + SIGNATURE: const TypeValidatorRequired(), + }, + name: 'PiServerResponse#fromJson', + ); + final result = validateMap( + map: map[RESULT], + validators: { + RESULT_STATUS: const TypeValidatorRequired(), + RESULT_VALUE: const TypeValidatorOptional>(), + RESULT_ERROR: const TypeValidatorOptional>(), + }, + name: 'PiServerResponse#fromJson#result', + ); + if (result[RESULT_STATUS] == true && result.containsKey(RESULT_VALUE)) { + return PiServerResponse.success( + detail: map[DETAIL], + id: map[ID], + jsonrpc: map[JSONRPC], + resultValue: PiServerResultValue.fromJsonOfType(result[RESULT_VALUE]), + time: map[TIME], + version: map[VERSION], + signature: map[SIGNATURE], + ); + } + if (result[RESULT_STATUS] == false && result.containsKey(RESULT_ERROR)) { + return PiServerResponse.error( + detail: map[DETAIL], + id: json[ID], + jsonrpc: map[JSONRPC], + resultError: PiServerResultError.fromJson(result[RESULT_ERROR]), + time: map[TIME], + version: map[VERSION], + signature: map[SIGNATURE], + ); + } + Logger.info( + 'Status: ${result[RESULT_STATUS]}' + '\nContains error: ${result.containsKey(RESULT_ERROR)}' + '\nContains value: ${result.containsKey(RESULT_VALUE)}', + name: 'PiServerResponse#fromJson'); + + throw UnimplementedError('Unknown PiServerResponse type'); + } + + factory PiServerResponse.fromResponse(Response response) { + return PiServerResponse.fromJson(jsonDecode(response.body)); + } +} + +class EncryptionParams { + final String algorithm; + final String initVector; + final String mode; + final String tag; + + const EncryptionParams({ + required this.algorithm, + required this.mode, + required this.initVector, + required this.tag, + }); + + static EncryptionParams fromJson(Map json) { + final map = validateMap( + map: json, + validators: { + 'algorithm': const TypeValidatorRequired(), + 'init_vector': const TypeValidatorRequired(), + 'mode': const TypeValidatorRequired(), + 'tag': const TypeValidatorRequired(), + }, + name: 'EncryptionParams#fromJson', + ); + return EncryptionParams( + algorithm: map['algorithm'] as String, + initVector: map['init_vector'] as String, + mode: map['mode'] as String, + tag: map['tag'] as String, + ); + } +} +/* //////////////////////////// +////// PI SERVER RESULTS ////// +//////////////////////////// */ + +class PiServerResultError extends PiServerResult { + @override + bool get status => false; + final int code; + final String message; + + const PiServerResultError({ + required this.code, + required this.message, + }); + + factory PiServerResultError.fromJson(Map json) { + final map = validateMap( + map: json, + validators: { + PI_SERVER_ERROR_CODE: const TypeValidatorRequired(), + PI_SERVER_ERROR_MESSAGE: const TypeValidatorRequired(), + }, + name: 'PiServerResultError#fromJson', + ); + return PiServerResultError( + code: map[PI_SERVER_ERROR_CODE] as int, + message: map[PI_SERVER_ERROR_MESSAGE] as String, + ); + } + @override + String toString() => 'PiError(code: $code, message: $message)'; +} + +sealed class PiServerResultValue extends PiServerResult { + @override + bool get status => true; + + static T fromJsonOfType(Map json) { + return switch (T) { + const (ContainerChallenge) => ContainerChallenge.fromJson(json) as T, + const (ContainerSyncResult) => ContainerSyncResult.fromJson(json) as T, + _ => throw UnimplementedError('Unknown PiServerResultValue type'), + }; + } + + const PiServerResultValue(); +} + +class ContainerChallenge extends PiServerResultValue { + final String finalizeSyncUrl; + final String keyAlgorithm; + final String nonce; + final String timeStamp; + + get timeAsDatetime => DateTime.parse(timeStamp); + + const ContainerChallenge({ + required this.finalizeSyncUrl, + required this.keyAlgorithm, + required this.nonce, + required this.timeStamp, + }); + + factory ContainerChallenge.fromJson(Map json) { + final map = validateMap( + map: json, + validators: { + CONTAINER_SYNC_URL: const TypeValidatorRequired(), + CONTAINER_SYNC_KEY_ALGORITHM: const TypeValidatorRequired(), + CONTAINER_SYNC_NONCE: const TypeValidatorRequired(), + CONTAINER_SYNC_TIMESTAMP: const TypeValidatorRequired(), + }, + name: 'ContainerChallenge#fromJson', + ); + return ContainerChallenge( + finalizeSyncUrl: map[CONTAINER_SYNC_URL] as String, + keyAlgorithm: map[CONTAINER_SYNC_KEY_ALGORITHM] as String, + nonce: map[CONTAINER_SYNC_NONCE] as String, + timeStamp: map[CONTAINER_SYNC_TIMESTAMP] as String, + ); + } +} + +class ContainerSyncResult extends PiServerResultValue { + final String publicServerKey; + Uint8List get publicServerKeyBytes => base64Decode(publicServerKey); + final String encryptionAlgorithm; + final EncryptionParams encryptionParams; + final String containerDictEncrypted; + + const ContainerSyncResult({ + required this.publicServerKey, + required this.encryptionAlgorithm, + required this.encryptionParams, + required this.containerDictEncrypted, + }); + + static ContainerSyncResult fromJson(Map json) { + final map = validateMap( + map: json, + validators: { + CONTAINER_SYNC_PUBLIC_SERVER_KEY: const TypeValidatorRequired(), + CONTAINER_SYNC_ENC_ALGORITHM: const TypeValidatorRequired(), + CONTAINER_SYNC_ENC_PARAMS: TypeValidatorRequired(transformer: (v) => EncryptionParams.fromJson(v)), + CONTAINER_SYNC_DICT_SERVER: const TypeValidatorRequired(), + }, + name: 'ContainerSyncResult#fromJson', + ); + return ContainerSyncResult( + publicServerKey: map[CONTAINER_SYNC_PUBLIC_SERVER_KEY] as String, + encryptionAlgorithm: map[CONTAINER_SYNC_ENC_ALGORITHM] as String, + encryptionParams: map[CONTAINER_SYNC_ENC_PARAMS] as EncryptionParams, + containerDictEncrypted: map[CONTAINER_SYNC_DICT_SERVER] as String, + ); + } +} diff --git a/lib/api/token_container_api_endpoint.freezed.dart b/lib/api/token_container_api_endpoint.freezed.dart new file mode 100644 index 000000000..27181ca41 --- /dev/null +++ b/lib/api/token_container_api_endpoint.freezed.dart @@ -0,0 +1,717 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'token_container_api_endpoint.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$PiServerResponse { + dynamic get detail => throw _privateConstructorUsedError; + int get id => throw _privateConstructorUsedError; + String get jsonrpc => throw _privateConstructorUsedError; + double get time => throw _privateConstructorUsedError; + String get version => throw _privateConstructorUsedError; + String get signature => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(dynamic detail, int id, String jsonrpc, + T resultValue, double time, String version, String signature) + success, + required TResult Function( + dynamic detail, + int id, + String jsonrpc, + PiServerResultError resultError, + double time, + String version, + String signature) + error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(dynamic detail, int id, String jsonrpc, T resultValue, + double time, String version, String signature)? + success, + TResult? Function( + dynamic detail, + int id, + String jsonrpc, + PiServerResultError resultError, + double time, + String version, + String signature)? + error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(dynamic detail, int id, String jsonrpc, T resultValue, + double time, String version, String signature)? + success, + TResult Function( + dynamic detail, + int id, + String jsonrpc, + PiServerResultError resultError, + double time, + String version, + String signature)? + error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(PiServerResponseSuccess value) success, + required TResult Function(PiServerResponseError value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(PiServerResponseSuccess value)? success, + TResult? Function(PiServerResponseError value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(PiServerResponseSuccess value)? success, + TResult Function(PiServerResponseError value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Create a copy of PiServerResponse + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $PiServerResponseCopyWith> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PiServerResponseCopyWith { + factory $PiServerResponseCopyWith( + PiServerResponse value, $Res Function(PiServerResponse) then) = + _$PiServerResponseCopyWithImpl>; + @useResult + $Res call( + {dynamic detail, + int id, + String jsonrpc, + double time, + String version, + String signature}); +} + +/// @nodoc +class _$PiServerResponseCopyWithImpl> + implements $PiServerResponseCopyWith { + _$PiServerResponseCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PiServerResponse + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? detail = freezed, + Object? id = null, + Object? jsonrpc = null, + Object? time = null, + Object? version = null, + Object? signature = null, + }) { + return _then(_value.copyWith( + detail: freezed == detail + ? _value.detail + : detail // ignore: cast_nullable_to_non_nullable + as dynamic, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + jsonrpc: null == jsonrpc + ? _value.jsonrpc + : jsonrpc // ignore: cast_nullable_to_non_nullable + as String, + time: null == time + ? _value.time + : time // ignore: cast_nullable_to_non_nullable + as double, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + signature: null == signature + ? _value.signature + : signature // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$PiServerResponseSuccessImplCopyWith< + T extends PiServerResultValue, + $Res> implements $PiServerResponseCopyWith { + factory _$$PiServerResponseSuccessImplCopyWith( + _$PiServerResponseSuccessImpl value, + $Res Function(_$PiServerResponseSuccessImpl) then) = + __$$PiServerResponseSuccessImplCopyWithImpl; + @override + @useResult + $Res call( + {dynamic detail, + int id, + String jsonrpc, + T resultValue, + double time, + String version, + String signature}); +} + +/// @nodoc +class __$$PiServerResponseSuccessImplCopyWithImpl + extends _$PiServerResponseCopyWithImpl> + implements _$$PiServerResponseSuccessImplCopyWith { + __$$PiServerResponseSuccessImplCopyWithImpl( + _$PiServerResponseSuccessImpl _value, + $Res Function(_$PiServerResponseSuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of PiServerResponse + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? detail = freezed, + Object? id = null, + Object? jsonrpc = null, + Object? resultValue = null, + Object? time = null, + Object? version = null, + Object? signature = null, + }) { + return _then(_$PiServerResponseSuccessImpl( + detail: freezed == detail + ? _value.detail + : detail // ignore: cast_nullable_to_non_nullable + as dynamic, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + jsonrpc: null == jsonrpc + ? _value.jsonrpc + : jsonrpc // ignore: cast_nullable_to_non_nullable + as String, + resultValue: null == resultValue + ? _value.resultValue + : resultValue // ignore: cast_nullable_to_non_nullable + as T, + time: null == time + ? _value.time + : time // ignore: cast_nullable_to_non_nullable + as double, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + signature: null == signature + ? _value.signature + : signature // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$PiServerResponseSuccessImpl + extends PiServerResponseSuccess { + _$PiServerResponseSuccessImpl( + {required this.detail, + required this.id, + required this.jsonrpc, + required this.resultValue, + required this.time, + required this.version, + required this.signature}) + : super._(); + + @override + final dynamic detail; + @override + final int id; + @override + final String jsonrpc; + @override + final T resultValue; + @override + final double time; + @override + final String version; + @override + final String signature; + + @override + String toString() { + return 'PiServerResponse<$T>.success(detail: $detail, id: $id, jsonrpc: $jsonrpc, resultValue: $resultValue, time: $time, version: $version, signature: $signature)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PiServerResponseSuccessImpl && + const DeepCollectionEquality().equals(other.detail, detail) && + (identical(other.id, id) || other.id == id) && + (identical(other.jsonrpc, jsonrpc) || other.jsonrpc == jsonrpc) && + const DeepCollectionEquality() + .equals(other.resultValue, resultValue) && + (identical(other.time, time) || other.time == time) && + (identical(other.version, version) || other.version == version) && + (identical(other.signature, signature) || + other.signature == signature)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(detail), + id, + jsonrpc, + const DeepCollectionEquality().hash(resultValue), + time, + version, + signature); + + /// Create a copy of PiServerResponse + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PiServerResponseSuccessImplCopyWith> + get copyWith => __$$PiServerResponseSuccessImplCopyWithImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(dynamic detail, int id, String jsonrpc, + T resultValue, double time, String version, String signature) + success, + required TResult Function( + dynamic detail, + int id, + String jsonrpc, + PiServerResultError resultError, + double time, + String version, + String signature) + error, + }) { + return success(detail, id, jsonrpc, resultValue, time, version, signature); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(dynamic detail, int id, String jsonrpc, T resultValue, + double time, String version, String signature)? + success, + TResult? Function( + dynamic detail, + int id, + String jsonrpc, + PiServerResultError resultError, + double time, + String version, + String signature)? + error, + }) { + return success?.call( + detail, id, jsonrpc, resultValue, time, version, signature); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(dynamic detail, int id, String jsonrpc, T resultValue, + double time, String version, String signature)? + success, + TResult Function( + dynamic detail, + int id, + String jsonrpc, + PiServerResultError resultError, + double time, + String version, + String signature)? + error, + required TResult orElse(), + }) { + if (success != null) { + return success( + detail, id, jsonrpc, resultValue, time, version, signature); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(PiServerResponseSuccess value) success, + required TResult Function(PiServerResponseError value) error, + }) { + return success(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(PiServerResponseSuccess value)? success, + TResult? Function(PiServerResponseError value)? error, + }) { + return success?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(PiServerResponseSuccess value)? success, + TResult Function(PiServerResponseError value)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(this); + } + return orElse(); + } +} + +abstract class PiServerResponseSuccess + extends PiServerResponse { + factory PiServerResponseSuccess( + {required final dynamic detail, + required final int id, + required final String jsonrpc, + required final T resultValue, + required final double time, + required final String version, + required final String signature}) = _$PiServerResponseSuccessImpl; + PiServerResponseSuccess._() : super._(); + + @override + dynamic get detail; + @override + int get id; + @override + String get jsonrpc; + T get resultValue; + @override + double get time; + @override + String get version; + @override + String get signature; + + /// Create a copy of PiServerResponse + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PiServerResponseSuccessImplCopyWith> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$PiServerResponseErrorImplCopyWith< + T extends PiServerResultValue, + $Res> implements $PiServerResponseCopyWith { + factory _$$PiServerResponseErrorImplCopyWith( + _$PiServerResponseErrorImpl value, + $Res Function(_$PiServerResponseErrorImpl) then) = + __$$PiServerResponseErrorImplCopyWithImpl; + @override + @useResult + $Res call( + {dynamic detail, + int id, + String jsonrpc, + PiServerResultError resultError, + double time, + String version, + String signature}); +} + +/// @nodoc +class __$$PiServerResponseErrorImplCopyWithImpl + extends _$PiServerResponseCopyWithImpl> + implements _$$PiServerResponseErrorImplCopyWith { + __$$PiServerResponseErrorImplCopyWithImpl( + _$PiServerResponseErrorImpl _value, + $Res Function(_$PiServerResponseErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of PiServerResponse + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? detail = freezed, + Object? id = null, + Object? jsonrpc = null, + Object? resultError = null, + Object? time = null, + Object? version = null, + Object? signature = null, + }) { + return _then(_$PiServerResponseErrorImpl( + detail: freezed == detail + ? _value.detail + : detail // ignore: cast_nullable_to_non_nullable + as dynamic, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + jsonrpc: null == jsonrpc + ? _value.jsonrpc + : jsonrpc // ignore: cast_nullable_to_non_nullable + as String, + resultError: null == resultError + ? _value.resultError + : resultError // ignore: cast_nullable_to_non_nullable + as PiServerResultError, + time: null == time + ? _value.time + : time // ignore: cast_nullable_to_non_nullable + as double, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + signature: null == signature + ? _value.signature + : signature // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$PiServerResponseErrorImpl + extends PiServerResponseError { + _$PiServerResponseErrorImpl( + {required this.detail, + required this.id, + required this.jsonrpc, + required this.resultError, + required this.time, + required this.version, + required this.signature}) + : super._(); + + @override + final dynamic detail; + @override + final int id; + @override + final String jsonrpc; + @override + final PiServerResultError resultError; + @override + final double time; + @override + final String version; + @override + final String signature; + + @override + String toString() { + return 'PiServerResponse<$T>.error(detail: $detail, id: $id, jsonrpc: $jsonrpc, resultError: $resultError, time: $time, version: $version, signature: $signature)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PiServerResponseErrorImpl && + const DeepCollectionEquality().equals(other.detail, detail) && + (identical(other.id, id) || other.id == id) && + (identical(other.jsonrpc, jsonrpc) || other.jsonrpc == jsonrpc) && + (identical(other.resultError, resultError) || + other.resultError == resultError) && + (identical(other.time, time) || other.time == time) && + (identical(other.version, version) || other.version == version) && + (identical(other.signature, signature) || + other.signature == signature)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(detail), + id, + jsonrpc, + resultError, + time, + version, + signature); + + /// Create a copy of PiServerResponse + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PiServerResponseErrorImplCopyWith> + get copyWith => __$$PiServerResponseErrorImplCopyWithImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(dynamic detail, int id, String jsonrpc, + T resultValue, double time, String version, String signature) + success, + required TResult Function( + dynamic detail, + int id, + String jsonrpc, + PiServerResultError resultError, + double time, + String version, + String signature) + error, + }) { + return error(detail, id, jsonrpc, resultError, time, version, signature); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(dynamic detail, int id, String jsonrpc, T resultValue, + double time, String version, String signature)? + success, + TResult? Function( + dynamic detail, + int id, + String jsonrpc, + PiServerResultError resultError, + double time, + String version, + String signature)? + error, + }) { + return error?.call( + detail, id, jsonrpc, resultError, time, version, signature); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(dynamic detail, int id, String jsonrpc, T resultValue, + double time, String version, String signature)? + success, + TResult Function( + dynamic detail, + int id, + String jsonrpc, + PiServerResultError resultError, + double time, + String version, + String signature)? + error, + required TResult orElse(), + }) { + if (error != null) { + return error(detail, id, jsonrpc, resultError, time, version, signature); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(PiServerResponseSuccess value) success, + required TResult Function(PiServerResponseError value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(PiServerResponseSuccess value)? success, + TResult? Function(PiServerResponseError value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(PiServerResponseSuccess value)? success, + TResult Function(PiServerResponseError value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class PiServerResponseError + extends PiServerResponse { + factory PiServerResponseError( + {required final dynamic detail, + required final int id, + required final String jsonrpc, + required final PiServerResultError resultError, + required final double time, + required final String version, + required final String signature}) = _$PiServerResponseErrorImpl; + PiServerResponseError._() : super._(); + + @override + dynamic get detail; + @override + int get id; + @override + String get jsonrpc; + PiServerResultError get resultError; + @override + double get time; + @override + String get version; + @override + String get signature; + + /// Create a copy of PiServerResponse + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PiServerResponseErrorImplCopyWith> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/interfaces/api_endpoint.dart b/lib/interfaces/api_endpoint.dart index c7f7231f6..ada68bc90 100644 --- a/lib/interfaces/api_endpoint.dart +++ b/lib/interfaces/api_endpoint.dart @@ -17,10 +17,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import '../model/tokens/container_credentials.dart'; -abstract class ApiEndpioint { - ContainerCredential get credential; +import '../utils/privacyidea_io_client.dart'; + +abstract class ApiEndpioint { + ApiEndpioint(PrivacyideaIOClient ioClient); Future fetch(); - Future sync(Data data); + Future sync(Credential credential, Data data); } diff --git a/lib/interfaces/repo/container_repository.dart b/lib/interfaces/repo/container_repository.dart index c6f484cb0..bc07c9127 100644 --- a/lib/interfaces/repo/container_repository.dart +++ b/lib/interfaces/repo/container_repository.dart @@ -1,39 +1,39 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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. - */ +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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 '../../model/token_container.dart'; +// import '../../model/token_container.dart'; -abstract class TokenContainerRepository { - /// Save the container state to the repository - /// Returns the state that was actually written - Future saveContainerState(TokenContainer containerState); +// abstract class TokenContainerRepository { +// /// Save the container state to the repository +// /// Returns the state that was actually written +// Future saveContainerState(TokenContainer containerState); - /// Load the container state from the repository - /// Returns the loaded state - Future loadContainerState(); +// /// Load the container state from the repository +// /// Returns the loaded state +// Future loadContainerState(); - // /// Save a token template to the repository - // /// Returns the template that was actually written - // Future saveTokenTemplate(TokenTemplate tokenTemplate); +// // /// Save a token template to the repository +// // /// Returns the template that was actually written +// // Future saveTokenTemplate(TokenTemplate tokenTemplate); - // /// Load a token template from the repository - // /// Returns the loaded template - // Future loadTokenTemplate(String tokenTemplateId); -} +// // /// Load a token template from the repository +// // /// Returns the loaded template +// // Future loadTokenTemplate(String tokenTemplateId); +// } diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index e58b27753..89a49f00c 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -242,8 +242,8 @@ } } }, - "rollingOut": "Registrace", - "@rollingOut": { + "startRollout": "Začít zavádění", + "@startRollout": { "description": "Label that tells the user that the token is being rolled out." }, "pollingChallenges": "Čekám na nové požadavky", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 17708503a..6869e7f7b 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -238,8 +238,8 @@ } } }, - "rollingOut": "Ausrollen", - "@rollingOut": { + "startRollout": "Starte Rollout", + "@startRollout": { "description": "Label that tells the user that the token is being rolled out." }, "pollingChallenges": "Frage ausstehende Authentifizierungsanfragen ab", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8363226f9..cfaaae8c3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -246,8 +246,8 @@ } } }, - "rollingOut": "Rolling out", - "@rollingOut": { + "startRollout": "Start rollout", + "@startRollout": { "description": "Label that tells the user that the token is being rolled out." }, "pollingChallenges": "Polling for new challenges", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 32550022e..e2388d386 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -238,8 +238,8 @@ } } }, - "rollingOut": "Despliegue", - "@rollingOut": { + "startRollout": "Iniciar despliegue", + "@startRollout": { "description": "Label that tells the user that the token is being rolled out." }, "pollingChallenges": "Sondeo para nuevos challenges", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index aba34e8ec..e32dfec24 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -242,8 +242,8 @@ } } }, - "rollingOut": "Déploiement en cours", - "@rollingOut": { + "startRollout": "Démarrer le déploiement", + "@startRollout": { "description": "Label that tells the user that the token is being rolled out." }, "pollingChallenges": "Vérification de nouveaux challenges", diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 78def0203..26c7d5059 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -235,8 +235,8 @@ } } }, - "rollingOut": "Uitrollen", - "@rollingOut": { + "startRollout": "Start uitrollen", + "@startRollout": { "description": "Label that tells the user that the token is being rolled out." }, "pollingChallenges": "Zoeken naar nieuwe aanvragen", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 6cde5dbe6..7dbc7eb61 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -238,8 +238,8 @@ } } }, - "rollingOut": "Wdrażanie", - "@rollingOut": { + "startRollout": "Rozpocznij wdrożenie", + "@startRollout": { "description": "Label that tells the user that the token is being rolled out." }, "pollingChallenges": "Sprawdzanie nowych wyzwań", diff --git a/lib/model/enums/ec_key_algorithm.dart b/lib/model/enums/ec_key_algorithm.dart index 3cd046606..92314af81 100644 --- a/lib/model/enums/ec_key_algorithm.dart +++ b/lib/model/enums/ec_key_algorithm.dart @@ -21,6 +21,47 @@ */ /// The following curves are supported: +/// - brainpoolp160r1 +/// - brainpoolp160t1 +/// - brainpoolp192r1 +/// - brainpoolp192t1 +/// - brainpoolp224r1 +/// - brainpoolp224t1 +/// - brainpoolp256r1 +/// - brainpoolp256t1 +/// - brainpoolp320r1 +/// - brainpoolp320t1 +/// - brainpoolp384r1 +/// - brainpoolp384t1 +/// - brainpoolp512r1 +/// - brainpoolp512t1 +/// - GostR3410_2001_CryptoPro_A +/// - GostR3410_2001_CryptoPro_B +/// - GostR3410_2001_CryptoPro_C +/// - GostR3410_2001_CryptoPro_XchA +/// - GostR3410_2001_CryptoPro_XchB +/// - prime192v1 +/// - prime192v2 +/// - prime192v3 +/// - prime239v1 +/// - prime239v2 +/// - prime239v3 +/// - prime256v1 +/// - secp112r1 +/// - secp112r2 +/// - secp128r1 +/// - secp128r2 +/// - secp160k1 +/// - secp160r1 +/// - secp160r2 +/// - secp192k1 +/// - secp192r1 +/// - secp224k1 +/// - secp224r1 +/// - secp256k1 +/// - secp256r1 +/// - secp384r1 +/// - secp521r1 enum EcKeyAlgorithm { brainpoolp160r1, brainpoolp160t1, diff --git a/lib/model/extensions/enums/push_token_rollout_state_extension.dart b/lib/model/extensions/enums/push_token_rollout_state_extension.dart index 7e8d57351..0a7579899 100644 --- a/lib/model/extensions/enums/push_token_rollout_state_extension.dart +++ b/lib/model/extensions/enums/push_token_rollout_state_extension.dart @@ -32,6 +32,13 @@ extension PushTokenRollOutStateX on PushTokenRollOutState { PushTokenRollOutState.rolloutComplete => false, }; + bool get rolloutFailed => switch (this) { + PushTokenRollOutState.generatingRSAKeyPairFailed => true, + PushTokenRollOutState.sendRSAPublicKeyFailed => true, + PushTokenRollOutState.parsingResponseFailed => true, + _ => false, + }; + PushTokenRollOutState getFailed() => switch (this) { PushTokenRollOutState.rolloutNotStarted => PushTokenRollOutState.rolloutNotStarted, PushTokenRollOutState.generatingRSAKeyPair => PushTokenRollOutState.generatingRSAKeyPairFailed, @@ -44,7 +51,7 @@ extension PushTokenRollOutStateX on PushTokenRollOutState { }; String rolloutMsg(AppLocalizations localizations) => switch (this) { - PushTokenRollOutState.rolloutNotStarted => localizations.rollingOut, + PushTokenRollOutState.rolloutNotStarted => localizations.startRollout, PushTokenRollOutState.generatingRSAKeyPair => localizations.generatingRSAKeyPair, PushTokenRollOutState.generatingRSAKeyPairFailed => localizations.generatingRSAKeyPairFailed, PushTokenRollOutState.sendRSAPublicKey => localizations.sendingRSAPublicKey, diff --git a/lib/model/mixins/sortable_mixin.dart b/lib/model/mixins/sortable_mixin.dart index a9d0b6e35..e5df7e8be 100644 --- a/lib/model/mixins/sortable_mixin.dart +++ b/lib/model/mixins/sortable_mixin.dart @@ -1,3 +1,5 @@ +// ignore_for_file: constant_identifier_names + /* * privacyIDEA Authenticator * @@ -18,6 +20,8 @@ * limitations under the License. */ mixin SortableMixin { + static const String SORT_INDEX = 'sortIndex'; + int? get sortIndex; SortableMixin copyWith({int? sortIndex}); diff --git a/lib/model/processor_result.freezed.dart b/lib/model/processor_result.freezed.dart index e3647129a..b34be9d50 100644 --- a/lib/model/processor_result.freezed.dart +++ b/lib/model/processor_result.freezed.dart @@ -16,23 +16,36 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$ProcessorResult { - TypeValidatorRequired? get resultHandlerType => throw _privateConstructorUsedError; + TypeValidatorRequired? get resultHandlerType => + throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function(T resultData, TypeValidatorRequired? resultHandlerType) success, - required TResult Function(String message, TypeValidatorRequired? resultHandlerType) failed, + required TResult Function(T resultData, + TypeValidatorRequired? resultHandlerType) + success, + required TResult Function(String message, + TypeValidatorRequired? resultHandlerType) + failed, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, - TResult? Function(String message, TypeValidatorRequired? resultHandlerType)? failed, + TResult? Function(T resultData, + TypeValidatorRequired? resultHandlerType)? + success, + TResult? Function(String message, + TypeValidatorRequired? resultHandlerType)? + failed, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, - TResult Function(String message, TypeValidatorRequired? resultHandlerType)? failed, + TResult Function(T resultData, + TypeValidatorRequired? resultHandlerType)? + success, + TResult Function(String message, + TypeValidatorRequired? resultHandlerType)? + failed, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -59,19 +72,22 @@ mixin _$ProcessorResult { /// Create a copy of ProcessorResult /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $ProcessorResultCopyWith> get copyWith => throw _privateConstructorUsedError; + $ProcessorResultCopyWith> get copyWith => + throw _privateConstructorUsedError; } /// @nodoc abstract class $ProcessorResultCopyWith { - factory $ProcessorResultCopyWith(ProcessorResult value, $Res Function(ProcessorResult) then) = + factory $ProcessorResultCopyWith( + ProcessorResult value, $Res Function(ProcessorResult) then) = _$ProcessorResultCopyWithImpl>; @useResult $Res call({TypeValidatorRequired? resultHandlerType}); } /// @nodoc -class _$ProcessorResultCopyWithImpl> implements $ProcessorResultCopyWith { +class _$ProcessorResultCopyWithImpl> + implements $ProcessorResultCopyWith { _$ProcessorResultCopyWithImpl(this._value, this._then); // ignore: unused_field @@ -96,18 +112,26 @@ class _$ProcessorResultCopyWithImpl> im } /// @nodoc -abstract class _$$ProcessorResultSuccessImplCopyWith implements $ProcessorResultCopyWith { - factory _$$ProcessorResultSuccessImplCopyWith(_$ProcessorResultSuccessImpl value, $Res Function(_$ProcessorResultSuccessImpl) then) = +abstract class _$$ProcessorResultSuccessImplCopyWith + implements $ProcessorResultCopyWith { + factory _$$ProcessorResultSuccessImplCopyWith( + _$ProcessorResultSuccessImpl value, + $Res Function(_$ProcessorResultSuccessImpl) then) = __$$ProcessorResultSuccessImplCopyWithImpl; @override @useResult - $Res call({T resultData, TypeValidatorRequired? resultHandlerType}); + $Res call( + {T resultData, TypeValidatorRequired? resultHandlerType}); } /// @nodoc -class __$$ProcessorResultSuccessImplCopyWithImpl extends _$ProcessorResultCopyWithImpl> +class __$$ProcessorResultSuccessImplCopyWithImpl + extends _$ProcessorResultCopyWithImpl> implements _$$ProcessorResultSuccessImplCopyWith { - __$$ProcessorResultSuccessImplCopyWithImpl(_$ProcessorResultSuccessImpl _value, $Res Function(_$ProcessorResultSuccessImpl) _then) + __$$ProcessorResultSuccessImplCopyWithImpl( + _$ProcessorResultSuccessImpl _value, + $Res Function(_$ProcessorResultSuccessImpl) _then) : super(_value, _then); /// Create a copy of ProcessorResult @@ -134,7 +158,8 @@ class __$$ProcessorResultSuccessImplCopyWithImpl extends _$ProcessorRes /// @nodoc class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { - const _$ProcessorResultSuccessImpl(this.resultData, {this.resultHandlerType}) : super._(); + const _$ProcessorResultSuccessImpl(this.resultData, {this.resultHandlerType}) + : super._(); @override final T resultData; @@ -151,26 +176,34 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ProcessorResultSuccessImpl && - const DeepCollectionEquality().equals(other.resultData, resultData) && - (identical(other.resultHandlerType, resultHandlerType) || other.resultHandlerType == resultHandlerType)); + const DeepCollectionEquality() + .equals(other.resultData, resultData) && + (identical(other.resultHandlerType, resultHandlerType) || + other.resultHandlerType == resultHandlerType)); } @override - int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(resultData), resultHandlerType); + int get hashCode => Object.hash(runtimeType, + const DeepCollectionEquality().hash(resultData), resultHandlerType); /// Create a copy of ProcessorResult /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$ProcessorResultSuccessImplCopyWith> get copyWith => - __$$ProcessorResultSuccessImplCopyWithImpl>(this, _$identity); + _$$ProcessorResultSuccessImplCopyWith> + get copyWith => __$$ProcessorResultSuccessImplCopyWithImpl>(this, _$identity); @override @optionalTypeArgs TResult when({ - required TResult Function(T resultData, TypeValidatorRequired? resultHandlerType) success, - required TResult Function(String message, TypeValidatorRequired? resultHandlerType) failed, + required TResult Function(T resultData, + TypeValidatorRequired? resultHandlerType) + success, + required TResult Function(String message, + TypeValidatorRequired? resultHandlerType) + failed, }) { return success(resultData, resultHandlerType); } @@ -178,8 +211,12 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, - TResult? Function(String message, TypeValidatorRequired? resultHandlerType)? failed, + TResult? Function(T resultData, + TypeValidatorRequired? resultHandlerType)? + success, + TResult? Function(String message, + TypeValidatorRequired? resultHandlerType)? + failed, }) { return success?.call(resultData, resultHandlerType); } @@ -187,8 +224,12 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, - TResult Function(String message, TypeValidatorRequired? resultHandlerType)? failed, + TResult Function(T resultData, + TypeValidatorRequired? resultHandlerType)? + success, + TResult Function(String message, + TypeValidatorRequired? resultHandlerType)? + failed, required TResult orElse(), }) { if (success != null) { @@ -230,7 +271,9 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { } abstract class ProcessorResultSuccess extends ProcessorResult { - const factory ProcessorResultSuccess(final T resultData, {final TypeValidatorRequired? resultHandlerType}) = _$ProcessorResultSuccessImpl; + const factory ProcessorResultSuccess(final T resultData, + {final TypeValidatorRequired? resultHandlerType}) = + _$ProcessorResultSuccessImpl; const ProcessorResultSuccess._() : super._(); T get resultData; @@ -241,22 +284,33 @@ abstract class ProcessorResultSuccess extends ProcessorResult { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$ProcessorResultSuccessImplCopyWith> get copyWith => throw _privateConstructorUsedError; + _$$ProcessorResultSuccessImplCopyWith> + get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class _$$ProcessorResultFailedImplCopyWith implements $ProcessorResultCopyWith { - factory _$$ProcessorResultFailedImplCopyWith(_$ProcessorResultFailedImpl value, $Res Function(_$ProcessorResultFailedImpl) then) = +abstract class _$$ProcessorResultFailedImplCopyWith + implements $ProcessorResultCopyWith { + factory _$$ProcessorResultFailedImplCopyWith( + _$ProcessorResultFailedImpl value, + $Res Function(_$ProcessorResultFailedImpl) then) = __$$ProcessorResultFailedImplCopyWithImpl; @override @useResult - $Res call({String message, TypeValidatorRequired? resultHandlerType}); + $Res call( + {String message, + TypeValidatorRequired? resultHandlerType}); } /// @nodoc -class __$$ProcessorResultFailedImplCopyWithImpl extends _$ProcessorResultCopyWithImpl> +class __$$ProcessorResultFailedImplCopyWithImpl + extends _$ProcessorResultCopyWithImpl> implements _$$ProcessorResultFailedImplCopyWith { - __$$ProcessorResultFailedImplCopyWithImpl(_$ProcessorResultFailedImpl _value, $Res Function(_$ProcessorResultFailedImpl) _then) : super(_value, _then); + __$$ProcessorResultFailedImplCopyWithImpl( + _$ProcessorResultFailedImpl _value, + $Res Function(_$ProcessorResultFailedImpl) _then) + : super(_value, _then); /// Create a copy of ProcessorResult /// with the given fields replaced by the non-null parameter values. @@ -282,7 +336,8 @@ class __$$ProcessorResultFailedImplCopyWithImpl extends _$ProcessorResu /// @nodoc class _$ProcessorResultFailedImpl extends ProcessorResultFailed { - const _$ProcessorResultFailedImpl(this.message, {this.resultHandlerType}) : super._(); + const _$ProcessorResultFailedImpl(this.message, {this.resultHandlerType}) + : super._(); @override final String message; @@ -300,7 +355,8 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { (other.runtimeType == runtimeType && other is _$ProcessorResultFailedImpl && (identical(other.message, message) || other.message == message) && - (identical(other.resultHandlerType, resultHandlerType) || other.resultHandlerType == resultHandlerType)); + (identical(other.resultHandlerType, resultHandlerType) || + other.resultHandlerType == resultHandlerType)); } @override @@ -311,14 +367,19 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$ProcessorResultFailedImplCopyWith> get copyWith => - __$$ProcessorResultFailedImplCopyWithImpl>(this, _$identity); + _$$ProcessorResultFailedImplCopyWith> + get copyWith => __$$ProcessorResultFailedImplCopyWithImpl>(this, _$identity); @override @optionalTypeArgs TResult when({ - required TResult Function(T resultData, TypeValidatorRequired? resultHandlerType) success, - required TResult Function(String message, TypeValidatorRequired? resultHandlerType) failed, + required TResult Function(T resultData, + TypeValidatorRequired? resultHandlerType) + success, + required TResult Function(String message, + TypeValidatorRequired? resultHandlerType) + failed, }) { return failed(message, resultHandlerType); } @@ -326,8 +387,12 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, - TResult? Function(String message, TypeValidatorRequired? resultHandlerType)? failed, + TResult? Function(T resultData, + TypeValidatorRequired? resultHandlerType)? + success, + TResult? Function(String message, + TypeValidatorRequired? resultHandlerType)? + failed, }) { return failed?.call(message, resultHandlerType); } @@ -335,8 +400,12 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, - TResult Function(String message, TypeValidatorRequired? resultHandlerType)? failed, + TResult Function(T resultData, + TypeValidatorRequired? resultHandlerType)? + success, + TResult Function(String message, + TypeValidatorRequired? resultHandlerType)? + failed, required TResult orElse(), }) { if (failed != null) { @@ -378,7 +447,9 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { } abstract class ProcessorResultFailed extends ProcessorResult { - const factory ProcessorResultFailed(final String message, {final TypeValidatorRequired? resultHandlerType}) = _$ProcessorResultFailedImpl; + const factory ProcessorResultFailed(final String message, + {final TypeValidatorRequired? resultHandlerType}) = + _$ProcessorResultFailedImpl; const ProcessorResultFailed._() : super._(); String get message; @@ -389,5 +460,6 @@ abstract class ProcessorResultFailed extends ProcessorResult { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$ProcessorResultFailedImplCopyWith> get copyWith => throw _privateConstructorUsedError; + _$$ProcessorResultFailedImplCopyWith> + get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/model/riverpod_states/credentials_state.dart b/lib/model/riverpod_states/credentials_state.dart index 40e942f2e..f17fa5962 100644 --- a/lib/model/riverpod_states/credentials_state.dart +++ b/lib/model/riverpod_states/credentials_state.dart @@ -40,7 +40,11 @@ class CredentialsState with _$CredentialsState { return CredentialsState(credentials: credentials); } - ContainerCredential? currentOf(ContainerCredential credential) => credentialsOf(credential.serial); + T? currentOf(T credential) { + final current = credentialsOf(credential.serial); + if (current is T) return current; + return null; + } factory CredentialsState.fromJson(Map json) => _$CredentialsStateFromJson(json); } diff --git a/lib/model/riverpod_states/progress_state.freezed.dart b/lib/model/riverpod_states/progress_state.freezed.dart index 5b339d284..ae8d6890e 100644 --- a/lib/model/riverpod_states/progress_state.freezed.dart +++ b/lib/model/riverpod_states/progress_state.freezed.dart @@ -60,18 +60,22 @@ mixin _$ProgressState { /// Create a copy of ProgressState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $ProgressStateCopyWith get copyWith => throw _privateConstructorUsedError; + $ProgressStateCopyWith get copyWith => + throw _privateConstructorUsedError; } /// @nodoc abstract class $ProgressStateCopyWith<$Res> { - factory $ProgressStateCopyWith(ProgressState value, $Res Function(ProgressState) then) = _$ProgressStateCopyWithImpl<$Res, ProgressState>; + factory $ProgressStateCopyWith( + ProgressState value, $Res Function(ProgressState) then) = + _$ProgressStateCopyWithImpl<$Res, ProgressState>; @useResult $Res call({int max, int value}); } /// @nodoc -class _$ProgressStateCopyWithImpl<$Res, $Val extends ProgressState> implements $ProgressStateCopyWith<$Res> { +class _$ProgressStateCopyWithImpl<$Res, $Val extends ProgressState> + implements $ProgressStateCopyWith<$Res> { _$ProgressStateCopyWithImpl(this._value, this._then); // ignore: unused_field @@ -101,8 +105,11 @@ class _$ProgressStateCopyWithImpl<$Res, $Val extends ProgressState> implements $ } /// @nodoc -abstract class _$$ProgressStateUninitializedImplCopyWith<$Res> implements $ProgressStateCopyWith<$Res> { - factory _$$ProgressStateUninitializedImplCopyWith(_$ProgressStateUninitializedImpl value, $Res Function(_$ProgressStateUninitializedImpl) then) = +abstract class _$$ProgressStateUninitializedImplCopyWith<$Res> + implements $ProgressStateCopyWith<$Res> { + factory _$$ProgressStateUninitializedImplCopyWith( + _$ProgressStateUninitializedImpl value, + $Res Function(_$ProgressStateUninitializedImpl) then) = __$$ProgressStateUninitializedImplCopyWithImpl<$Res>; @override @useResult @@ -110,9 +117,12 @@ abstract class _$$ProgressStateUninitializedImplCopyWith<$Res> implements $Progr } /// @nodoc -class __$$ProgressStateUninitializedImplCopyWithImpl<$Res> extends _$ProgressStateCopyWithImpl<$Res, _$ProgressStateUninitializedImpl> +class __$$ProgressStateUninitializedImplCopyWithImpl<$Res> + extends _$ProgressStateCopyWithImpl<$Res, _$ProgressStateUninitializedImpl> implements _$$ProgressStateUninitializedImplCopyWith<$Res> { - __$$ProgressStateUninitializedImplCopyWithImpl(_$ProgressStateUninitializedImpl _value, $Res Function(_$ProgressStateUninitializedImpl) _then) + __$$ProgressStateUninitializedImplCopyWithImpl( + _$ProgressStateUninitializedImpl _value, + $Res Function(_$ProgressStateUninitializedImpl) _then) : super(_value, _then); /// Create a copy of ProgressState @@ -139,7 +149,8 @@ class __$$ProgressStateUninitializedImplCopyWithImpl<$Res> extends _$ProgressSta /// @nodoc class _$ProgressStateUninitializedImpl extends ProgressStateUninitialized { - const _$ProgressStateUninitializedImpl({this.max = 0, this.value = 0}) : super._(); + const _$ProgressStateUninitializedImpl({this.max = 0, this.value = 0}) + : super._(); @override @JsonKey() @@ -170,8 +181,9 @@ class _$ProgressStateUninitializedImpl extends ProgressStateUninitialized { @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$ProgressStateUninitializedImplCopyWith<_$ProgressStateUninitializedImpl> get copyWith => - __$$ProgressStateUninitializedImplCopyWithImpl<_$ProgressStateUninitializedImpl>(this, _$identity); + _$$ProgressStateUninitializedImplCopyWith<_$ProgressStateUninitializedImpl> + get copyWith => __$$ProgressStateUninitializedImplCopyWithImpl< + _$ProgressStateUninitializedImpl>(this, _$identity); @override @optionalTypeArgs @@ -237,7 +249,8 @@ class _$ProgressStateUninitializedImpl extends ProgressStateUninitialized { } abstract class ProgressStateUninitialized extends ProgressState { - const factory ProgressStateUninitialized({final int max, final int value}) = _$ProgressStateUninitializedImpl; + const factory ProgressStateUninitialized({final int max, final int value}) = + _$ProgressStateUninitializedImpl; const ProgressStateUninitialized._() : super._(); @override @@ -249,20 +262,28 @@ abstract class ProgressStateUninitialized extends ProgressState { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$ProgressStateUninitializedImplCopyWith<_$ProgressStateUninitializedImpl> get copyWith => throw _privateConstructorUsedError; + _$$ProgressStateUninitializedImplCopyWith<_$ProgressStateUninitializedImpl> + get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class _$$ProgressStateImplCopyWith<$Res> implements $ProgressStateCopyWith<$Res> { - factory _$$ProgressStateImplCopyWith(_$ProgressStateImpl value, $Res Function(_$ProgressStateImpl) then) = __$$ProgressStateImplCopyWithImpl<$Res>; +abstract class _$$ProgressStateImplCopyWith<$Res> + implements $ProgressStateCopyWith<$Res> { + factory _$$ProgressStateImplCopyWith( + _$ProgressStateImpl value, $Res Function(_$ProgressStateImpl) then) = + __$$ProgressStateImplCopyWithImpl<$Res>; @override @useResult $Res call({int max, int value}); } /// @nodoc -class __$$ProgressStateImplCopyWithImpl<$Res> extends _$ProgressStateCopyWithImpl<$Res, _$ProgressStateImpl> implements _$$ProgressStateImplCopyWith<$Res> { - __$$ProgressStateImplCopyWithImpl(_$ProgressStateImpl _value, $Res Function(_$ProgressStateImpl) _then) : super(_value, _then); +class __$$ProgressStateImplCopyWithImpl<$Res> + extends _$ProgressStateCopyWithImpl<$Res, _$ProgressStateImpl> + implements _$$ProgressStateImplCopyWith<$Res> { + __$$ProgressStateImplCopyWithImpl( + _$ProgressStateImpl _value, $Res Function(_$ProgressStateImpl) _then) + : super(_value, _then); /// Create a copy of ProgressState /// with the given fields replaced by the non-null parameter values. @@ -290,7 +311,7 @@ class __$$ProgressStateImplCopyWithImpl<$Res> extends _$ProgressStateCopyWithImp class _$ProgressStateImpl extends _ProgressState { const _$ProgressStateImpl({required this.max, required this.value}) : assert(max >= 0, 'max must be greater than or equal to 0'), - assert(value <= max, 'value must be less than or equal to max'), + assert(value >= max, 'value must be less than or equal to max'), super._(); @override @@ -320,7 +341,8 @@ class _$ProgressStateImpl extends _ProgressState { @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$ProgressStateImplCopyWith<_$ProgressStateImpl> get copyWith => __$$ProgressStateImplCopyWithImpl<_$ProgressStateImpl>(this, _$identity); + _$$ProgressStateImplCopyWith<_$ProgressStateImpl> get copyWith => + __$$ProgressStateImplCopyWithImpl<_$ProgressStateImpl>(this, _$identity); @override @optionalTypeArgs @@ -386,7 +408,8 @@ class _$ProgressStateImpl extends _ProgressState { } abstract class _ProgressState extends ProgressState { - const factory _ProgressState({required final int max, required final int value}) = _$ProgressStateImpl; + const factory _ProgressState( + {required final int max, required final int value}) = _$ProgressStateImpl; const _ProgressState._() : super._(); @override @@ -398,5 +421,6 @@ abstract class _ProgressState extends ProgressState { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$ProgressStateImplCopyWith<_$ProgressStateImpl> get copyWith => throw _privateConstructorUsedError; + _$$ProgressStateImplCopyWith<_$ProgressStateImpl> get copyWith => + throw _privateConstructorUsedError; } diff --git a/lib/model/riverpod_states/token_state.dart b/lib/model/riverpod_states/token_state.dart index f03a9dd41..7c5eeb7c3 100644 --- a/lib/model/riverpod_states/token_state.dart +++ b/lib/model/riverpod_states/token_state.dart @@ -23,7 +23,7 @@ import 'package:flutter/material.dart'; import '../../utils/logger.dart'; import '../enums/push_token_rollout_state.dart'; import '../enums/token_origin_source_type.dart'; -import '../token_container.dart'; +import '../token_template.dart'; import '../token_folder.dart'; import '../tokens/otp_token.dart'; import '../tokens/push_token.dart'; @@ -76,7 +76,7 @@ class TokenState { return sameTokensMap; } - T? currentOf(T token) => tokens.firstWhereOrNull((element) => element.id == token.id) as T?; + T? currentOf(T token) => tokens.firstWhereOrNull((element) => element.isSameTokenAs(token)) as T?; T? currentOfId(String id) => tokens.firstWhereOrNull((element) => element.id == id) as T?; TokenState withToken(Token token) { @@ -177,18 +177,34 @@ class TokenState { List tokensWithoutFolder({List only = const [], List exclude = const []}) => tokens.withoutFolder(only: only, exclude: exclude); - List containerTokens(String containerId) => tokens.piTokens.fromContainer(containerId); + List containerTokens(String containerSerial) { + final piTokens = tokens.piTokens; + Logger.debug('PiTokens: ${piTokens}', name: 'token_state.dart#containerTokens'); + final containerTokens = piTokens.ofContainer(containerSerial); + Logger.debug('${containerTokens.length}/${piTokens.length} tokens with containerSerial: $containerSerial', name: 'token_state.dart#containerTokens'); + return containerTokens; + } } extension TokenListExtension on List { - List get nonPiTokens => where((token) => token.isPrivacyIdeaToken == false).toList(); + List get piTokens { + final piTokens = where((token) => token.isPrivacyIdeaToken == true).toList(); + Logger.debug('${piTokens.length}/$length tokens with "isPrivacyIdeaToken == true"', name: 'token_state.dart#piTokens'); + return piTokens; + } + + List get nonPiTokens { + final nonPiTokens = where((token) => token.isPrivacyIdeaToken == false).toList(); + Logger.debug('${nonPiTokens.length}/$length tokens with "isPrivacyIdeaToken == false"', name: 'token_state.dart#nonPiTokens'); + return nonPiTokens; + } + List get maybePiTokens { final maybePiTokens = where((token) => token.isPrivacyIdeaToken == null).toList(); - Logger.debug('${maybePiTokens.length}/$length tokens with "isPrivacyIdeaToken" == null', name: 'token_state.dart#maybePiTokens'); + Logger.debug('${maybePiTokens.length}/$length tokens with "isPrivacyIdeaToken == null"', name: 'token_state.dart#maybePiTokens'); return maybePiTokens; } - List get piTokens => where((token) => token.isPrivacyIdeaToken == true).toList(); List inFolder(TokenFolder folder, {List only = const [], List exclude = const []}) => where((token) { if (token.folderId != folder.folderId) return false; if (exclude.contains(token.runtimeType)) return false; @@ -203,16 +219,19 @@ extension TokenListExtension on List { return true; }).toList(); - List fromContainer(String containerSerial) { - final filtered = where((token) { - return token.origin?.source == TokenOriginSourceType.container && token.containerSerial == containerSerial; - }).toList(); + List ofContainer(String containerSerial) { + final filtered = where((token) => token.origin?.source == TokenOriginSourceType.container && token.containerSerial == containerSerial).toList(); Logger.debug('${filtered.length}/$length tokens with containerSerial: $containerSerial', name: 'token_state.dart#fromContainer'); return filtered; } List toTemplates() { if (isEmpty) return []; - return map((token) => token.toTemplate()).toList(); + final templates = []; + for (var token in this) { + final template = token.toTemplate(); + if (template != null) templates.add(template); + } + return templates; } } diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart deleted file mode 100644 index dabc5c59a..000000000 --- a/lib/model/token_container.dart +++ /dev/null @@ -1,281 +0,0 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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. - */ - -// We need some unnecessary_overrides to force to add the fields in factory constructors -// ignore_for_file: unnecessary_overrides - -import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; -import 'package:privacyidea_authenticator/utils/identifiers.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; - -import 'token_import/token_origin_data.dart'; -import 'tokens/token.dart'; - -part 'token_container.freezed.dart'; -part 'token_container.g.dart'; - -@freezed -sealed class TokenContainer with _$TokenContainer { - const TokenContainer._(); - // Overriding fields resutls in a error when someone - // forgot to add the field in a new factory constructor - // On error "The getter '...' isn't defined in a superclass of 'TokenContainer'." - // add the '...' field to the every factory constructor to get rid of this error - @override - String get serverName => super.serverName; - @override - DateTime? get lastSyncAt => super.lastSyncAt; - @override - String get serial => super.serial; - @override - String get description => super.description; - @override - List get syncedTokenTemplates => super.syncedTokenTemplates; - @override - List get localTokenTemplates => super.localTokenTemplates; - - const factory TokenContainer.uninitialized({ - // Base fields - @Default('PrivacyIDEA') String serverName, - DateTime? lastSyncAt, - @Default('none') String serial, - @Default('Uninitialized') String description, - @Default([]) List syncedTokenTemplates, - @Default([]) List localTokenTemplates, - // Base fields end - }) = TokenContainerUninitialized; - const factory TokenContainer.synced({ - // Base fields - @Default('PrivacyIDEA') String serverName, - required DateTime lastSyncAt, - required String serial, - required String description, - required List syncedTokenTemplates, - required List localTokenTemplates, - // Base fields end - }) = TokenContainerSynced; - const factory TokenContainer.modified({ - // Base fields - @Default('PrivacyIDEA') String serverName, - DateTime? lastSyncAt, - required String serial, - required String description, - required List syncedTokenTemplates, - required List localTokenTemplates, - // Base fields end - required DateTime lastModifiedAt, - }) = TokenContainerModified; - const factory TokenContainer.unsynced({ - // Base fields - @Default('PrivacyIDEA') String serverName, - DateTime? lastSyncAt, - required String serial, - required String description, - required List syncedTokenTemplates, - required List localTokenTemplates, - // Base fields end - String? message, - }) = TokenContainerUnsynced; - const factory TokenContainer.notFound({ - // Base fields - @Default('PrivacyIDEA') String serverName, - DateTime? lastSyncAt, - required String serial, - required String description, - required List syncedTokenTemplates, - required List localTokenTemplates, - // Base fields end - required String message, - }) = TokenContainerNotFound; - const factory TokenContainer.error({ - // Base fields - @Default('PrivacyIDEA') String serverName, - DateTime? lastSyncAt, - @Default('none') String serial, - @Default('Error') String description, - @Default([]) List syncedTokenTemplates, - @Default([]) List localTokenTemplates, - // Base fields end - required dynamic error, - }) = TokenContainerError; - - T copyTransformInto({ - // Base fields - String? serverName, - DateTime? lastSyncAt, - String? serial, - String? description, - List? syncedTokenTemplates, - List? localTokenTemplates, - // Base fields end - Map? args, - }) => - switch (T) { - const (TokenContainerSynced) => TokenContainerSynced( - serverName: serverName ?? this.serverName, - lastSyncAt: lastSyncAt ?? this.lastSyncAt!, - serial: serial ?? this.serial, - description: description ?? this.description, - syncedTokenTemplates: [ - ...(syncedTokenTemplates ?? this.syncedTokenTemplates), - ...(localTokenTemplates ?? this.localTokenTemplates), - ], - localTokenTemplates: [], - ) as T, - const (TokenContainerUnsynced) => TokenContainerUnsynced( - serverName: serverName ?? this.serverName, - lastSyncAt: lastSyncAt ?? this.lastSyncAt, - serial: serial ?? this.serial, - description: description ?? this.description, - syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, - localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, - ) as T, - const (TokenContainerNotFound) => TokenContainerNotFound( - serverName: serverName ?? this.serverName, - lastSyncAt: lastSyncAt ?? this.lastSyncAt, - serial: serial ?? this.serial, - description: description ?? this.description, - syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, - localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, - message: args != null && args['message'] != null ? args['message'] : 'Unknown message', - ) as T, - const (TokenContainerError) => TokenContainerError( - serverName: serverName ?? this.serverName, - lastSyncAt: lastSyncAt ?? this.lastSyncAt, - serial: serial ?? this.serial, - description: description ?? this.description, - syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, - localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, - error: args != null && args['error'] != null ? args['error'] : 'Unknown error', - ) as T, - _ => throw UnimplementedError('Unknown TokenContainer type: $T'), - }; - - factory TokenContainer.fromJson(Map json) => _$TokenContainerFromJson(json); -} - -@freezed -class TokenTemplate with _$TokenTemplate { - TokenTemplate._(); - factory TokenTemplate({ - required Map data, - }) = _TokenTemplate; - - List get keys => data.keys.toList(); - List get values => data.values.toList(); - - String? get serial => validateOptional( - value: data[OTP_AUTH_SERIAL], - validator: const TypeValidatorOptional(), - name: OTP_AUTH_SERIAL, - ); - - String? get type => validateOptional( - value: data[OTP_AUTH_TYPE], - validator: const TypeValidatorOptional(), - name: OTP_AUTH_TYPE, - ); - - List get otpValues => validate( - value: data[OTP_AUTH_OTP_VALUES], - validator: const TypeValidatorRequired>(), - name: OTP_AUTH_OTP_VALUES, - ); - - String? get containerSerial => validateOptional( - value: data[CONTAINER_SERIAL], - validator: const TypeValidatorOptional(), - name: CONTAINER_SERIAL, - ); - - @override - operator ==(Object other) { - if (other is! TokenTemplate) return false; - if (data.length != other.data.length) return false; - for (var key in data.keys) { - if (data[key].toString() != other.data[key].toString()) { - return false; - } - } - return true; - } - - factory TokenTemplate.fromJson(Map json) => _$TokenTemplateFromJson(json); - - Token toToken(TokenContainer container) => Token.fromOtpAuthMap( - data, - origin: TokenOriginData( - appName: '${container.serverName} ${container.serial}', - data: data.toString(), - source: TokenOriginSourceType.container, - isPrivacyIdeaToken: true, - ), - ).copyWith( - containerSerial: () => container.serial, - ); - - /// Adds all key/value pairs of [other] to this map. - /// - /// If a key of [other] is already in this map, its value is overwritten. - /// - /// The operation is equivalent to doing `this[key] = value` for each key - /// and associated value in other. It iterates over [other], which must - /// therefore not change during the iteration. - /// ```dart - /// final planets = {1: 'Mercury', 2: 'Earth'}; - /// planets.addAll({5: 'Jupiter', 6: 'Saturn'}); - /// print(planets); // {1: Mercury, 2: Earth, 5: Jupiter, 6: Saturn} - /// ``` - TokenTemplate copyAddAll(Map addData) { - final newData = Map.from(data)..addAll(addData); - return TokenTemplate(data: newData); - } - - @override - int get hashCode => Object.hashAllUnordered(data.keys.map((key) => '$key:${data[key]}')); - - bool isSameTokenAs(TokenTemplate other) => serial == other.serial || (otpValues.isNotEmpty && const IterableEquality().equals(otpValues, other.otpValues)); - - bool hasSameValuesAs(TokenTemplate serverTokenTemplate) { - Logger.debug('serverTokenTemplate.keys: ${serverTokenTemplate.keys}', name: 'TokenTemplate#hasSameValuesAs'); - for (var key in serverTokenTemplate.keys) { - if (data[key] != serverTokenTemplate.data[key]) { - Logger.debug('TokenTemplate has different values for key "$key": ${data[key]} != ${serverTokenTemplate.data[key]}', - name: 'TokenTemplate#hasSameValuesAs'); - return false; - } - } - Logger.debug( - 'AppTokenTemplate serial $serial/otp ($otpValues) has same values as serverTokenTemplate serial ${serverTokenTemplate.serial}/id ${serverTokenTemplate.otpValues}', - name: 'TokenTemplate#hasSameValuesAs', - ); - return true; - } - - bool tokenWouldBeUpdated(Token token) { - Logger.debug('Checking if token would be updated', name: 'TokenTemplate#tokenWouldBeUpdated'); - final tokenTemplate = token.toTemplate(); - Logger.debug('TokenTemplate: \n$tokenTemplate\n has same values as \n$this\n ?', name: 'TokenTemplate#tokenWouldBeUpdated'); - return !tokenTemplate.hasSameValuesAs(this); - } -} diff --git a/lib/model/token_container.freezed.dart b/lib/model/token_container.freezed.dart deleted file mode 100644 index 4672c8780..000000000 --- a/lib/model/token_container.freezed.dart +++ /dev/null @@ -1,2312 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'token_container.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -TokenContainer _$TokenContainerFromJson(Map json) { - switch (json['runtimeType']) { - case 'uninitialized': - return TokenContainerUninitialized.fromJson(json); - case 'synced': - return TokenContainerSynced.fromJson(json); - case 'modified': - return TokenContainerModified.fromJson(json); - case 'unsynced': - return TokenContainerUnsynced.fromJson(json); - case 'notFound': - return TokenContainerNotFound.fromJson(json); - case 'error': - return TokenContainerError.fromJson(json); - - default: - throw CheckedFromJsonException(json, 'runtimeType', 'TokenContainer', 'Invalid union type "${json['runtimeType']}"!'); - } -} - -/// @nodoc -mixin _$TokenContainer { -// Base fields - String get serverName => throw _privateConstructorUsedError; - DateTime? get lastSyncAt => throw _privateConstructorUsedError; - String get serial => throw _privateConstructorUsedError; - String get description => throw _privateConstructorUsedError; - List get syncedTokenTemplates => throw _privateConstructorUsedError; - List get localTokenTemplates => throw _privateConstructorUsedError; - @optionalTypeArgs - TResult when({ - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - uninitialized, - required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - synced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt) - modified, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message) - unsynced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message) - notFound, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error) - error, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult map({ - required TResult Function(TokenContainerUninitialized value) uninitialized, - required TResult Function(TokenContainerSynced value) synced, - required TResult Function(TokenContainerModified value) modified, - required TResult Function(TokenContainerUnsynced value) unsynced, - required TResult Function(TokenContainerNotFound value) notFound, - required TResult Function(TokenContainerError value) error, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(TokenContainerUninitialized value)? uninitialized, - TResult? Function(TokenContainerSynced value)? synced, - TResult? Function(TokenContainerModified value)? modified, - TResult? Function(TokenContainerUnsynced value)? unsynced, - TResult? Function(TokenContainerNotFound value)? notFound, - TResult? Function(TokenContainerError value)? error, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeMap({ - TResult Function(TokenContainerUninitialized value)? uninitialized, - TResult Function(TokenContainerSynced value)? synced, - TResult Function(TokenContainerModified value)? modified, - TResult Function(TokenContainerUnsynced value)? unsynced, - TResult Function(TokenContainerNotFound value)? notFound, - TResult Function(TokenContainerError value)? error, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - - /// Serializes this TokenContainer to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $TokenContainerCopyWith get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $TokenContainerCopyWith<$Res> { - factory $TokenContainerCopyWith(TokenContainer value, $Res Function(TokenContainer) then) = _$TokenContainerCopyWithImpl<$Res, TokenContainer>; - @useResult - $Res call( - {String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates}); -} - -/// @nodoc -class _$TokenContainerCopyWithImpl<$Res, $Val extends TokenContainer> implements $TokenContainerCopyWith<$Res> { - _$TokenContainerCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? serverName = null, - Object? lastSyncAt = null, - Object? serial = null, - Object? description = null, - Object? syncedTokenTemplates = null, - Object? localTokenTemplates = null, - }) { - return _then(_value.copyWith( - serverName: null == serverName - ? _value.serverName - : serverName // ignore: cast_nullable_to_non_nullable - as String, - lastSyncAt: null == lastSyncAt - ? _value.lastSyncAt! - : lastSyncAt // ignore: cast_nullable_to_non_nullable - as DateTime, - serial: null == serial - ? _value.serial - : serial // ignore: cast_nullable_to_non_nullable - as String, - description: null == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String, - syncedTokenTemplates: null == syncedTokenTemplates - ? _value.syncedTokenTemplates - : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - localTokenTemplates: null == localTokenTemplates - ? _value.localTokenTemplates - : localTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$TokenContainerUninitializedImplCopyWith<$Res> implements $TokenContainerCopyWith<$Res> { - factory _$$TokenContainerUninitializedImplCopyWith(_$TokenContainerUninitializedImpl value, $Res Function(_$TokenContainerUninitializedImpl) then) = - __$$TokenContainerUninitializedImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates}); -} - -/// @nodoc -class __$$TokenContainerUninitializedImplCopyWithImpl<$Res> extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerUninitializedImpl> - implements _$$TokenContainerUninitializedImplCopyWith<$Res> { - __$$TokenContainerUninitializedImplCopyWithImpl(_$TokenContainerUninitializedImpl _value, $Res Function(_$TokenContainerUninitializedImpl) _then) - : super(_value, _then); - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? serverName = null, - Object? lastSyncAt = freezed, - Object? serial = null, - Object? description = null, - Object? syncedTokenTemplates = null, - Object? localTokenTemplates = null, - }) { - return _then(_$TokenContainerUninitializedImpl( - serverName: null == serverName - ? _value.serverName - : serverName // ignore: cast_nullable_to_non_nullable - as String, - lastSyncAt: freezed == lastSyncAt - ? _value.lastSyncAt - : lastSyncAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - serial: null == serial - ? _value.serial - : serial // ignore: cast_nullable_to_non_nullable - as String, - description: null == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String, - syncedTokenTemplates: null == syncedTokenTemplates - ? _value._syncedTokenTemplates - : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - localTokenTemplates: null == localTokenTemplates - ? _value._localTokenTemplates - : localTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TokenContainerUninitializedImpl extends TokenContainerUninitialized with DiagnosticableTreeMixin { - const _$TokenContainerUninitializedImpl( - {this.serverName = 'PrivacyIDEA', - this.lastSyncAt, - this.serial = 'none', - this.description = 'Uninitialized', - final List syncedTokenTemplates = const [], - final List localTokenTemplates = const [], - final String? $type}) - : _syncedTokenTemplates = syncedTokenTemplates, - _localTokenTemplates = localTokenTemplates, - $type = $type ?? 'uninitialized', - super._(); - - factory _$TokenContainerUninitializedImpl.fromJson(Map json) => _$$TokenContainerUninitializedImplFromJson(json); - -// Base fields - @override - @JsonKey() - final String serverName; - @override - final DateTime? lastSyncAt; - @override - @JsonKey() - final String serial; - @override - @JsonKey() - final String description; - final List _syncedTokenTemplates; - @override - @JsonKey() - List get syncedTokenTemplates { - if (_syncedTokenTemplates is EqualUnmodifiableListView) return _syncedTokenTemplates; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_syncedTokenTemplates); - } - - final List _localTokenTemplates; - @override - @JsonKey() - List get localTokenTemplates { - if (_localTokenTemplates is EqualUnmodifiableListView) return _localTokenTemplates; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_localTokenTemplates); - } - - @JsonKey(name: 'runtimeType') - final String $type; - - @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'TokenContainer.uninitialized(serverName: $serverName, lastSyncAt: $lastSyncAt, serial: $serial, description: $description, syncedTokenTemplates: $syncedTokenTemplates, localTokenTemplates: $localTokenTemplates)'; - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('type', 'TokenContainer.uninitialized')) - ..add(DiagnosticsProperty('serverName', serverName)) - ..add(DiagnosticsProperty('lastSyncAt', lastSyncAt)) - ..add(DiagnosticsProperty('serial', serial)) - ..add(DiagnosticsProperty('description', description)) - ..add(DiagnosticsProperty('syncedTokenTemplates', syncedTokenTemplates)) - ..add(DiagnosticsProperty('localTokenTemplates', localTokenTemplates)); - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TokenContainerUninitializedImpl && - (identical(other.serverName, serverName) || other.serverName == serverName) && - (identical(other.lastSyncAt, lastSyncAt) || other.lastSyncAt == lastSyncAt) && - (identical(other.serial, serial) || other.serial == serial) && - (identical(other.description, description) || other.description == description) && - const DeepCollectionEquality().equals(other._syncedTokenTemplates, _syncedTokenTemplates) && - const DeepCollectionEquality().equals(other._localTokenTemplates, _localTokenTemplates)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, serverName, lastSyncAt, serial, description, const DeepCollectionEquality().hash(_syncedTokenTemplates), - const DeepCollectionEquality().hash(_localTokenTemplates)); - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$TokenContainerUninitializedImplCopyWith<_$TokenContainerUninitializedImpl> get copyWith => - __$$TokenContainerUninitializedImplCopyWithImpl<_$TokenContainerUninitializedImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - uninitialized, - required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - synced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt) - modified, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message) - unsynced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message) - notFound, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error) - error, - }) { - return uninitialized(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - }) { - return uninitialized?.call(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - required TResult orElse(), - }) { - if (uninitialized != null) { - return uninitialized(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(TokenContainerUninitialized value) uninitialized, - required TResult Function(TokenContainerSynced value) synced, - required TResult Function(TokenContainerModified value) modified, - required TResult Function(TokenContainerUnsynced value) unsynced, - required TResult Function(TokenContainerNotFound value) notFound, - required TResult Function(TokenContainerError value) error, - }) { - return uninitialized(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(TokenContainerUninitialized value)? uninitialized, - TResult? Function(TokenContainerSynced value)? synced, - TResult? Function(TokenContainerModified value)? modified, - TResult? Function(TokenContainerUnsynced value)? unsynced, - TResult? Function(TokenContainerNotFound value)? notFound, - TResult? Function(TokenContainerError value)? error, - }) { - return uninitialized?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(TokenContainerUninitialized value)? uninitialized, - TResult Function(TokenContainerSynced value)? synced, - TResult Function(TokenContainerModified value)? modified, - TResult Function(TokenContainerUnsynced value)? unsynced, - TResult Function(TokenContainerNotFound value)? notFound, - TResult Function(TokenContainerError value)? error, - required TResult orElse(), - }) { - if (uninitialized != null) { - return uninitialized(this); - } - return orElse(); - } - - @override - Map toJson() { - return _$$TokenContainerUninitializedImplToJson( - this, - ); - } -} - -abstract class TokenContainerUninitialized extends TokenContainer { - const factory TokenContainerUninitialized( - {final String serverName, - final DateTime? lastSyncAt, - final String serial, - final String description, - final List syncedTokenTemplates, - final List localTokenTemplates}) = _$TokenContainerUninitializedImpl; - const TokenContainerUninitialized._() : super._(); - - factory TokenContainerUninitialized.fromJson(Map json) = _$TokenContainerUninitializedImpl.fromJson; - -// Base fields - @override - String get serverName; - @override - DateTime? get lastSyncAt; - @override - String get serial; - @override - String get description; - @override - List get syncedTokenTemplates; - @override - List get localTokenTemplates; - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenContainerUninitializedImplCopyWith<_$TokenContainerUninitializedImpl> get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$TokenContainerSyncedImplCopyWith<$Res> implements $TokenContainerCopyWith<$Res> { - factory _$$TokenContainerSyncedImplCopyWith(_$TokenContainerSyncedImpl value, $Res Function(_$TokenContainerSyncedImpl) then) = - __$$TokenContainerSyncedImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String serverName, - DateTime lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates}); -} - -/// @nodoc -class __$$TokenContainerSyncedImplCopyWithImpl<$Res> extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerSyncedImpl> - implements _$$TokenContainerSyncedImplCopyWith<$Res> { - __$$TokenContainerSyncedImplCopyWithImpl(_$TokenContainerSyncedImpl _value, $Res Function(_$TokenContainerSyncedImpl) _then) : super(_value, _then); - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? serverName = null, - Object? lastSyncAt = null, - Object? serial = null, - Object? description = null, - Object? syncedTokenTemplates = null, - Object? localTokenTemplates = null, - }) { - return _then(_$TokenContainerSyncedImpl( - serverName: null == serverName - ? _value.serverName - : serverName // ignore: cast_nullable_to_non_nullable - as String, - lastSyncAt: null == lastSyncAt - ? _value.lastSyncAt - : lastSyncAt // ignore: cast_nullable_to_non_nullable - as DateTime, - serial: null == serial - ? _value.serial - : serial // ignore: cast_nullable_to_non_nullable - as String, - description: null == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String, - syncedTokenTemplates: null == syncedTokenTemplates - ? _value._syncedTokenTemplates - : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - localTokenTemplates: null == localTokenTemplates - ? _value._localTokenTemplates - : localTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TokenContainerSyncedImpl extends TokenContainerSynced with DiagnosticableTreeMixin { - const _$TokenContainerSyncedImpl( - {this.serverName = 'PrivacyIDEA', - required this.lastSyncAt, - required this.serial, - required this.description, - required final List syncedTokenTemplates, - required final List localTokenTemplates, - final String? $type}) - : _syncedTokenTemplates = syncedTokenTemplates, - _localTokenTemplates = localTokenTemplates, - $type = $type ?? 'synced', - super._(); - - factory _$TokenContainerSyncedImpl.fromJson(Map json) => _$$TokenContainerSyncedImplFromJson(json); - -// Base fields - @override - @JsonKey() - final String serverName; - @override - final DateTime lastSyncAt; - @override - final String serial; - @override - final String description; - final List _syncedTokenTemplates; - @override - List get syncedTokenTemplates { - if (_syncedTokenTemplates is EqualUnmodifiableListView) return _syncedTokenTemplates; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_syncedTokenTemplates); - } - - final List _localTokenTemplates; - @override - List get localTokenTemplates { - if (_localTokenTemplates is EqualUnmodifiableListView) return _localTokenTemplates; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_localTokenTemplates); - } - - @JsonKey(name: 'runtimeType') - final String $type; - - @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'TokenContainer.synced(serverName: $serverName, lastSyncAt: $lastSyncAt, serial: $serial, description: $description, syncedTokenTemplates: $syncedTokenTemplates, localTokenTemplates: $localTokenTemplates)'; - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('type', 'TokenContainer.synced')) - ..add(DiagnosticsProperty('serverName', serverName)) - ..add(DiagnosticsProperty('lastSyncAt', lastSyncAt)) - ..add(DiagnosticsProperty('serial', serial)) - ..add(DiagnosticsProperty('description', description)) - ..add(DiagnosticsProperty('syncedTokenTemplates', syncedTokenTemplates)) - ..add(DiagnosticsProperty('localTokenTemplates', localTokenTemplates)); - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TokenContainerSyncedImpl && - (identical(other.serverName, serverName) || other.serverName == serverName) && - (identical(other.lastSyncAt, lastSyncAt) || other.lastSyncAt == lastSyncAt) && - (identical(other.serial, serial) || other.serial == serial) && - (identical(other.description, description) || other.description == description) && - const DeepCollectionEquality().equals(other._syncedTokenTemplates, _syncedTokenTemplates) && - const DeepCollectionEquality().equals(other._localTokenTemplates, _localTokenTemplates)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, serverName, lastSyncAt, serial, description, const DeepCollectionEquality().hash(_syncedTokenTemplates), - const DeepCollectionEquality().hash(_localTokenTemplates)); - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$TokenContainerSyncedImplCopyWith<_$TokenContainerSyncedImpl> get copyWith => - __$$TokenContainerSyncedImplCopyWithImpl<_$TokenContainerSyncedImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - uninitialized, - required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - synced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt) - modified, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message) - unsynced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message) - notFound, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error) - error, - }) { - return synced(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - }) { - return synced?.call(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - required TResult orElse(), - }) { - if (synced != null) { - return synced(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(TokenContainerUninitialized value) uninitialized, - required TResult Function(TokenContainerSynced value) synced, - required TResult Function(TokenContainerModified value) modified, - required TResult Function(TokenContainerUnsynced value) unsynced, - required TResult Function(TokenContainerNotFound value) notFound, - required TResult Function(TokenContainerError value) error, - }) { - return synced(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(TokenContainerUninitialized value)? uninitialized, - TResult? Function(TokenContainerSynced value)? synced, - TResult? Function(TokenContainerModified value)? modified, - TResult? Function(TokenContainerUnsynced value)? unsynced, - TResult? Function(TokenContainerNotFound value)? notFound, - TResult? Function(TokenContainerError value)? error, - }) { - return synced?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(TokenContainerUninitialized value)? uninitialized, - TResult Function(TokenContainerSynced value)? synced, - TResult Function(TokenContainerModified value)? modified, - TResult Function(TokenContainerUnsynced value)? unsynced, - TResult Function(TokenContainerNotFound value)? notFound, - TResult Function(TokenContainerError value)? error, - required TResult orElse(), - }) { - if (synced != null) { - return synced(this); - } - return orElse(); - } - - @override - Map toJson() { - return _$$TokenContainerSyncedImplToJson( - this, - ); - } -} - -abstract class TokenContainerSynced extends TokenContainer { - const factory TokenContainerSynced( - {final String serverName, - required final DateTime lastSyncAt, - required final String serial, - required final String description, - required final List syncedTokenTemplates, - required final List localTokenTemplates}) = _$TokenContainerSyncedImpl; - const TokenContainerSynced._() : super._(); - - factory TokenContainerSynced.fromJson(Map json) = _$TokenContainerSyncedImpl.fromJson; - -// Base fields - @override - String get serverName; - @override - DateTime get lastSyncAt; - @override - String get serial; - @override - String get description; - @override - List get syncedTokenTemplates; - @override - List get localTokenTemplates; - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenContainerSyncedImplCopyWith<_$TokenContainerSyncedImpl> get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$TokenContainerModifiedImplCopyWith<$Res> implements $TokenContainerCopyWith<$Res> { - factory _$$TokenContainerModifiedImplCopyWith(_$TokenContainerModifiedImpl value, $Res Function(_$TokenContainerModifiedImpl) then) = - __$$TokenContainerModifiedImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - DateTime lastModifiedAt}); -} - -/// @nodoc -class __$$TokenContainerModifiedImplCopyWithImpl<$Res> extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerModifiedImpl> - implements _$$TokenContainerModifiedImplCopyWith<$Res> { - __$$TokenContainerModifiedImplCopyWithImpl(_$TokenContainerModifiedImpl _value, $Res Function(_$TokenContainerModifiedImpl) _then) : super(_value, _then); - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? serverName = null, - Object? lastSyncAt = freezed, - Object? serial = null, - Object? description = null, - Object? syncedTokenTemplates = null, - Object? localTokenTemplates = null, - Object? lastModifiedAt = null, - }) { - return _then(_$TokenContainerModifiedImpl( - serverName: null == serverName - ? _value.serverName - : serverName // ignore: cast_nullable_to_non_nullable - as String, - lastSyncAt: freezed == lastSyncAt - ? _value.lastSyncAt - : lastSyncAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - serial: null == serial - ? _value.serial - : serial // ignore: cast_nullable_to_non_nullable - as String, - description: null == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String, - syncedTokenTemplates: null == syncedTokenTemplates - ? _value._syncedTokenTemplates - : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - localTokenTemplates: null == localTokenTemplates - ? _value._localTokenTemplates - : localTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - lastModifiedAt: null == lastModifiedAt - ? _value.lastModifiedAt - : lastModifiedAt // ignore: cast_nullable_to_non_nullable - as DateTime, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TokenContainerModifiedImpl extends TokenContainerModified with DiagnosticableTreeMixin { - const _$TokenContainerModifiedImpl( - {this.serverName = 'PrivacyIDEA', - this.lastSyncAt, - required this.serial, - required this.description, - required final List syncedTokenTemplates, - required final List localTokenTemplates, - required this.lastModifiedAt, - final String? $type}) - : _syncedTokenTemplates = syncedTokenTemplates, - _localTokenTemplates = localTokenTemplates, - $type = $type ?? 'modified', - super._(); - - factory _$TokenContainerModifiedImpl.fromJson(Map json) => _$$TokenContainerModifiedImplFromJson(json); - -// Base fields - @override - @JsonKey() - final String serverName; - @override - final DateTime? lastSyncAt; - @override - final String serial; - @override - final String description; - final List _syncedTokenTemplates; - @override - List get syncedTokenTemplates { - if (_syncedTokenTemplates is EqualUnmodifiableListView) return _syncedTokenTemplates; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_syncedTokenTemplates); - } - - final List _localTokenTemplates; - @override - List get localTokenTemplates { - if (_localTokenTemplates is EqualUnmodifiableListView) return _localTokenTemplates; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_localTokenTemplates); - } - -// Base fields end - @override - final DateTime lastModifiedAt; - - @JsonKey(name: 'runtimeType') - final String $type; - - @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'TokenContainer.modified(serverName: $serverName, lastSyncAt: $lastSyncAt, serial: $serial, description: $description, syncedTokenTemplates: $syncedTokenTemplates, localTokenTemplates: $localTokenTemplates, lastModifiedAt: $lastModifiedAt)'; - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('type', 'TokenContainer.modified')) - ..add(DiagnosticsProperty('serverName', serverName)) - ..add(DiagnosticsProperty('lastSyncAt', lastSyncAt)) - ..add(DiagnosticsProperty('serial', serial)) - ..add(DiagnosticsProperty('description', description)) - ..add(DiagnosticsProperty('syncedTokenTemplates', syncedTokenTemplates)) - ..add(DiagnosticsProperty('localTokenTemplates', localTokenTemplates)) - ..add(DiagnosticsProperty('lastModifiedAt', lastModifiedAt)); - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TokenContainerModifiedImpl && - (identical(other.serverName, serverName) || other.serverName == serverName) && - (identical(other.lastSyncAt, lastSyncAt) || other.lastSyncAt == lastSyncAt) && - (identical(other.serial, serial) || other.serial == serial) && - (identical(other.description, description) || other.description == description) && - const DeepCollectionEquality().equals(other._syncedTokenTemplates, _syncedTokenTemplates) && - const DeepCollectionEquality().equals(other._localTokenTemplates, _localTokenTemplates) && - (identical(other.lastModifiedAt, lastModifiedAt) || other.lastModifiedAt == lastModifiedAt)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, serverName, lastSyncAt, serial, description, const DeepCollectionEquality().hash(_syncedTokenTemplates), - const DeepCollectionEquality().hash(_localTokenTemplates), lastModifiedAt); - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$TokenContainerModifiedImplCopyWith<_$TokenContainerModifiedImpl> get copyWith => - __$$TokenContainerModifiedImplCopyWithImpl<_$TokenContainerModifiedImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - uninitialized, - required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - synced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt) - modified, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message) - unsynced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message) - notFound, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error) - error, - }) { - return modified(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, lastModifiedAt); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - }) { - return modified?.call(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, lastModifiedAt); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - required TResult orElse(), - }) { - if (modified != null) { - return modified(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, lastModifiedAt); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(TokenContainerUninitialized value) uninitialized, - required TResult Function(TokenContainerSynced value) synced, - required TResult Function(TokenContainerModified value) modified, - required TResult Function(TokenContainerUnsynced value) unsynced, - required TResult Function(TokenContainerNotFound value) notFound, - required TResult Function(TokenContainerError value) error, - }) { - return modified(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(TokenContainerUninitialized value)? uninitialized, - TResult? Function(TokenContainerSynced value)? synced, - TResult? Function(TokenContainerModified value)? modified, - TResult? Function(TokenContainerUnsynced value)? unsynced, - TResult? Function(TokenContainerNotFound value)? notFound, - TResult? Function(TokenContainerError value)? error, - }) { - return modified?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(TokenContainerUninitialized value)? uninitialized, - TResult Function(TokenContainerSynced value)? synced, - TResult Function(TokenContainerModified value)? modified, - TResult Function(TokenContainerUnsynced value)? unsynced, - TResult Function(TokenContainerNotFound value)? notFound, - TResult Function(TokenContainerError value)? error, - required TResult orElse(), - }) { - if (modified != null) { - return modified(this); - } - return orElse(); - } - - @override - Map toJson() { - return _$$TokenContainerModifiedImplToJson( - this, - ); - } -} - -abstract class TokenContainerModified extends TokenContainer { - const factory TokenContainerModified( - {final String serverName, - final DateTime? lastSyncAt, - required final String serial, - required final String description, - required final List syncedTokenTemplates, - required final List localTokenTemplates, - required final DateTime lastModifiedAt}) = _$TokenContainerModifiedImpl; - const TokenContainerModified._() : super._(); - - factory TokenContainerModified.fromJson(Map json) = _$TokenContainerModifiedImpl.fromJson; - -// Base fields - @override - String get serverName; - @override - DateTime? get lastSyncAt; - @override - String get serial; - @override - String get description; - @override - List get syncedTokenTemplates; - @override - List get localTokenTemplates; // Base fields end - DateTime get lastModifiedAt; - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenContainerModifiedImplCopyWith<_$TokenContainerModifiedImpl> get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$TokenContainerUnsyncedImplCopyWith<$Res> implements $TokenContainerCopyWith<$Res> { - factory _$$TokenContainerUnsyncedImplCopyWith(_$TokenContainerUnsyncedImpl value, $Res Function(_$TokenContainerUnsyncedImpl) then) = - __$$TokenContainerUnsyncedImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String? message}); -} - -/// @nodoc -class __$$TokenContainerUnsyncedImplCopyWithImpl<$Res> extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerUnsyncedImpl> - implements _$$TokenContainerUnsyncedImplCopyWith<$Res> { - __$$TokenContainerUnsyncedImplCopyWithImpl(_$TokenContainerUnsyncedImpl _value, $Res Function(_$TokenContainerUnsyncedImpl) _then) : super(_value, _then); - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? serverName = null, - Object? lastSyncAt = freezed, - Object? serial = null, - Object? description = null, - Object? syncedTokenTemplates = null, - Object? localTokenTemplates = null, - Object? message = freezed, - }) { - return _then(_$TokenContainerUnsyncedImpl( - serverName: null == serverName - ? _value.serverName - : serverName // ignore: cast_nullable_to_non_nullable - as String, - lastSyncAt: freezed == lastSyncAt - ? _value.lastSyncAt - : lastSyncAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - serial: null == serial - ? _value.serial - : serial // ignore: cast_nullable_to_non_nullable - as String, - description: null == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String, - syncedTokenTemplates: null == syncedTokenTemplates - ? _value._syncedTokenTemplates - : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - localTokenTemplates: null == localTokenTemplates - ? _value._localTokenTemplates - : localTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - message: freezed == message - ? _value.message - : message // ignore: cast_nullable_to_non_nullable - as String?, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TokenContainerUnsyncedImpl extends TokenContainerUnsynced with DiagnosticableTreeMixin { - const _$TokenContainerUnsyncedImpl( - {this.serverName = 'PrivacyIDEA', - this.lastSyncAt, - required this.serial, - required this.description, - required final List syncedTokenTemplates, - required final List localTokenTemplates, - this.message, - final String? $type}) - : _syncedTokenTemplates = syncedTokenTemplates, - _localTokenTemplates = localTokenTemplates, - $type = $type ?? 'unsynced', - super._(); - - factory _$TokenContainerUnsyncedImpl.fromJson(Map json) => _$$TokenContainerUnsyncedImplFromJson(json); - -// Base fields - @override - @JsonKey() - final String serverName; - @override - final DateTime? lastSyncAt; - @override - final String serial; - @override - final String description; - final List _syncedTokenTemplates; - @override - List get syncedTokenTemplates { - if (_syncedTokenTemplates is EqualUnmodifiableListView) return _syncedTokenTemplates; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_syncedTokenTemplates); - } - - final List _localTokenTemplates; - @override - List get localTokenTemplates { - if (_localTokenTemplates is EqualUnmodifiableListView) return _localTokenTemplates; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_localTokenTemplates); - } - -// Base fields end - @override - final String? message; - - @JsonKey(name: 'runtimeType') - final String $type; - - @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'TokenContainer.unsynced(serverName: $serverName, lastSyncAt: $lastSyncAt, serial: $serial, description: $description, syncedTokenTemplates: $syncedTokenTemplates, localTokenTemplates: $localTokenTemplates, message: $message)'; - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('type', 'TokenContainer.unsynced')) - ..add(DiagnosticsProperty('serverName', serverName)) - ..add(DiagnosticsProperty('lastSyncAt', lastSyncAt)) - ..add(DiagnosticsProperty('serial', serial)) - ..add(DiagnosticsProperty('description', description)) - ..add(DiagnosticsProperty('syncedTokenTemplates', syncedTokenTemplates)) - ..add(DiagnosticsProperty('localTokenTemplates', localTokenTemplates)) - ..add(DiagnosticsProperty('message', message)); - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TokenContainerUnsyncedImpl && - (identical(other.serverName, serverName) || other.serverName == serverName) && - (identical(other.lastSyncAt, lastSyncAt) || other.lastSyncAt == lastSyncAt) && - (identical(other.serial, serial) || other.serial == serial) && - (identical(other.description, description) || other.description == description) && - const DeepCollectionEquality().equals(other._syncedTokenTemplates, _syncedTokenTemplates) && - const DeepCollectionEquality().equals(other._localTokenTemplates, _localTokenTemplates) && - (identical(other.message, message) || other.message == message)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, serverName, lastSyncAt, serial, description, const DeepCollectionEquality().hash(_syncedTokenTemplates), - const DeepCollectionEquality().hash(_localTokenTemplates), message); - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$TokenContainerUnsyncedImplCopyWith<_$TokenContainerUnsyncedImpl> get copyWith => - __$$TokenContainerUnsyncedImplCopyWithImpl<_$TokenContainerUnsyncedImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - uninitialized, - required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - synced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt) - modified, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message) - unsynced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message) - notFound, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error) - error, - }) { - return unsynced(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, message); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - }) { - return unsynced?.call(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, message); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - required TResult orElse(), - }) { - if (unsynced != null) { - return unsynced(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, message); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(TokenContainerUninitialized value) uninitialized, - required TResult Function(TokenContainerSynced value) synced, - required TResult Function(TokenContainerModified value) modified, - required TResult Function(TokenContainerUnsynced value) unsynced, - required TResult Function(TokenContainerNotFound value) notFound, - required TResult Function(TokenContainerError value) error, - }) { - return unsynced(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(TokenContainerUninitialized value)? uninitialized, - TResult? Function(TokenContainerSynced value)? synced, - TResult? Function(TokenContainerModified value)? modified, - TResult? Function(TokenContainerUnsynced value)? unsynced, - TResult? Function(TokenContainerNotFound value)? notFound, - TResult? Function(TokenContainerError value)? error, - }) { - return unsynced?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(TokenContainerUninitialized value)? uninitialized, - TResult Function(TokenContainerSynced value)? synced, - TResult Function(TokenContainerModified value)? modified, - TResult Function(TokenContainerUnsynced value)? unsynced, - TResult Function(TokenContainerNotFound value)? notFound, - TResult Function(TokenContainerError value)? error, - required TResult orElse(), - }) { - if (unsynced != null) { - return unsynced(this); - } - return orElse(); - } - - @override - Map toJson() { - return _$$TokenContainerUnsyncedImplToJson( - this, - ); - } -} - -abstract class TokenContainerUnsynced extends TokenContainer { - const factory TokenContainerUnsynced( - {final String serverName, - final DateTime? lastSyncAt, - required final String serial, - required final String description, - required final List syncedTokenTemplates, - required final List localTokenTemplates, - final String? message}) = _$TokenContainerUnsyncedImpl; - const TokenContainerUnsynced._() : super._(); - - factory TokenContainerUnsynced.fromJson(Map json) = _$TokenContainerUnsyncedImpl.fromJson; - -// Base fields - @override - String get serverName; - @override - DateTime? get lastSyncAt; - @override - String get serial; - @override - String get description; - @override - List get syncedTokenTemplates; - @override - List get localTokenTemplates; // Base fields end - String? get message; - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenContainerUnsyncedImplCopyWith<_$TokenContainerUnsyncedImpl> get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$TokenContainerNotFoundImplCopyWith<$Res> implements $TokenContainerCopyWith<$Res> { - factory _$$TokenContainerNotFoundImplCopyWith(_$TokenContainerNotFoundImpl value, $Res Function(_$TokenContainerNotFoundImpl) then) = - __$$TokenContainerNotFoundImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - String message}); -} - -/// @nodoc -class __$$TokenContainerNotFoundImplCopyWithImpl<$Res> extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerNotFoundImpl> - implements _$$TokenContainerNotFoundImplCopyWith<$Res> { - __$$TokenContainerNotFoundImplCopyWithImpl(_$TokenContainerNotFoundImpl _value, $Res Function(_$TokenContainerNotFoundImpl) _then) : super(_value, _then); - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? serverName = null, - Object? lastSyncAt = freezed, - Object? serial = null, - Object? description = null, - Object? syncedTokenTemplates = null, - Object? localTokenTemplates = null, - Object? message = null, - }) { - return _then(_$TokenContainerNotFoundImpl( - serverName: null == serverName - ? _value.serverName - : serverName // ignore: cast_nullable_to_non_nullable - as String, - lastSyncAt: freezed == lastSyncAt - ? _value.lastSyncAt - : lastSyncAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - serial: null == serial - ? _value.serial - : serial // ignore: cast_nullable_to_non_nullable - as String, - description: null == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String, - syncedTokenTemplates: null == syncedTokenTemplates - ? _value._syncedTokenTemplates - : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - localTokenTemplates: null == localTokenTemplates - ? _value._localTokenTemplates - : localTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - message: null == message - ? _value.message - : message // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TokenContainerNotFoundImpl extends TokenContainerNotFound with DiagnosticableTreeMixin { - const _$TokenContainerNotFoundImpl( - {this.serverName = 'PrivacyIDEA', - this.lastSyncAt, - required this.serial, - required this.description, - required final List syncedTokenTemplates, - required final List localTokenTemplates, - required this.message, - final String? $type}) - : _syncedTokenTemplates = syncedTokenTemplates, - _localTokenTemplates = localTokenTemplates, - $type = $type ?? 'notFound', - super._(); - - factory _$TokenContainerNotFoundImpl.fromJson(Map json) => _$$TokenContainerNotFoundImplFromJson(json); - -// Base fields - @override - @JsonKey() - final String serverName; - @override - final DateTime? lastSyncAt; - @override - final String serial; - @override - final String description; - final List _syncedTokenTemplates; - @override - List get syncedTokenTemplates { - if (_syncedTokenTemplates is EqualUnmodifiableListView) return _syncedTokenTemplates; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_syncedTokenTemplates); - } - - final List _localTokenTemplates; - @override - List get localTokenTemplates { - if (_localTokenTemplates is EqualUnmodifiableListView) return _localTokenTemplates; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_localTokenTemplates); - } - -// Base fields end - @override - final String message; - - @JsonKey(name: 'runtimeType') - final String $type; - - @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'TokenContainer.notFound(serverName: $serverName, lastSyncAt: $lastSyncAt, serial: $serial, description: $description, syncedTokenTemplates: $syncedTokenTemplates, localTokenTemplates: $localTokenTemplates, message: $message)'; - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('type', 'TokenContainer.notFound')) - ..add(DiagnosticsProperty('serverName', serverName)) - ..add(DiagnosticsProperty('lastSyncAt', lastSyncAt)) - ..add(DiagnosticsProperty('serial', serial)) - ..add(DiagnosticsProperty('description', description)) - ..add(DiagnosticsProperty('syncedTokenTemplates', syncedTokenTemplates)) - ..add(DiagnosticsProperty('localTokenTemplates', localTokenTemplates)) - ..add(DiagnosticsProperty('message', message)); - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TokenContainerNotFoundImpl && - (identical(other.serverName, serverName) || other.serverName == serverName) && - (identical(other.lastSyncAt, lastSyncAt) || other.lastSyncAt == lastSyncAt) && - (identical(other.serial, serial) || other.serial == serial) && - (identical(other.description, description) || other.description == description) && - const DeepCollectionEquality().equals(other._syncedTokenTemplates, _syncedTokenTemplates) && - const DeepCollectionEquality().equals(other._localTokenTemplates, _localTokenTemplates) && - (identical(other.message, message) || other.message == message)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, serverName, lastSyncAt, serial, description, const DeepCollectionEquality().hash(_syncedTokenTemplates), - const DeepCollectionEquality().hash(_localTokenTemplates), message); - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$TokenContainerNotFoundImplCopyWith<_$TokenContainerNotFoundImpl> get copyWith => - __$$TokenContainerNotFoundImplCopyWithImpl<_$TokenContainerNotFoundImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - uninitialized, - required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - synced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt) - modified, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message) - unsynced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message) - notFound, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error) - error, - }) { - return notFound(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, message); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - }) { - return notFound?.call(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, message); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - required TResult orElse(), - }) { - if (notFound != null) { - return notFound(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, message); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(TokenContainerUninitialized value) uninitialized, - required TResult Function(TokenContainerSynced value) synced, - required TResult Function(TokenContainerModified value) modified, - required TResult Function(TokenContainerUnsynced value) unsynced, - required TResult Function(TokenContainerNotFound value) notFound, - required TResult Function(TokenContainerError value) error, - }) { - return notFound(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(TokenContainerUninitialized value)? uninitialized, - TResult? Function(TokenContainerSynced value)? synced, - TResult? Function(TokenContainerModified value)? modified, - TResult? Function(TokenContainerUnsynced value)? unsynced, - TResult? Function(TokenContainerNotFound value)? notFound, - TResult? Function(TokenContainerError value)? error, - }) { - return notFound?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(TokenContainerUninitialized value)? uninitialized, - TResult Function(TokenContainerSynced value)? synced, - TResult Function(TokenContainerModified value)? modified, - TResult Function(TokenContainerUnsynced value)? unsynced, - TResult Function(TokenContainerNotFound value)? notFound, - TResult Function(TokenContainerError value)? error, - required TResult orElse(), - }) { - if (notFound != null) { - return notFound(this); - } - return orElse(); - } - - @override - Map toJson() { - return _$$TokenContainerNotFoundImplToJson( - this, - ); - } -} - -abstract class TokenContainerNotFound extends TokenContainer { - const factory TokenContainerNotFound( - {final String serverName, - final DateTime? lastSyncAt, - required final String serial, - required final String description, - required final List syncedTokenTemplates, - required final List localTokenTemplates, - required final String message}) = _$TokenContainerNotFoundImpl; - const TokenContainerNotFound._() : super._(); - - factory TokenContainerNotFound.fromJson(Map json) = _$TokenContainerNotFoundImpl.fromJson; - -// Base fields - @override - String get serverName; - @override - DateTime? get lastSyncAt; - @override - String get serial; - @override - String get description; - @override - List get syncedTokenTemplates; - @override - List get localTokenTemplates; // Base fields end - String get message; - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenContainerNotFoundImplCopyWith<_$TokenContainerNotFoundImpl> get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$TokenContainerErrorImplCopyWith<$Res> implements $TokenContainerCopyWith<$Res> { - factory _$$TokenContainerErrorImplCopyWith(_$TokenContainerErrorImpl value, $Res Function(_$TokenContainerErrorImpl) then) = - __$$TokenContainerErrorImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String serverName, - DateTime? lastSyncAt, - String serial, - String description, - List syncedTokenTemplates, - List localTokenTemplates, - dynamic error}); -} - -/// @nodoc -class __$$TokenContainerErrorImplCopyWithImpl<$Res> extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerErrorImpl> - implements _$$TokenContainerErrorImplCopyWith<$Res> { - __$$TokenContainerErrorImplCopyWithImpl(_$TokenContainerErrorImpl _value, $Res Function(_$TokenContainerErrorImpl) _then) : super(_value, _then); - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? serverName = null, - Object? lastSyncAt = freezed, - Object? serial = null, - Object? description = null, - Object? syncedTokenTemplates = null, - Object? localTokenTemplates = null, - Object? error = freezed, - }) { - return _then(_$TokenContainerErrorImpl( - serverName: null == serverName - ? _value.serverName - : serverName // ignore: cast_nullable_to_non_nullable - as String, - lastSyncAt: freezed == lastSyncAt - ? _value.lastSyncAt - : lastSyncAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - serial: null == serial - ? _value.serial - : serial // ignore: cast_nullable_to_non_nullable - as String, - description: null == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String, - syncedTokenTemplates: null == syncedTokenTemplates - ? _value._syncedTokenTemplates - : syncedTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - localTokenTemplates: null == localTokenTemplates - ? _value._localTokenTemplates - : localTokenTemplates // ignore: cast_nullable_to_non_nullable - as List, - error: freezed == error - ? _value.error - : error // ignore: cast_nullable_to_non_nullable - as dynamic, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TokenContainerErrorImpl extends TokenContainerError with DiagnosticableTreeMixin { - const _$TokenContainerErrorImpl( - {this.serverName = 'PrivacyIDEA', - this.lastSyncAt, - this.serial = 'none', - this.description = 'Error', - final List syncedTokenTemplates = const [], - final List localTokenTemplates = const [], - required this.error, - final String? $type}) - : _syncedTokenTemplates = syncedTokenTemplates, - _localTokenTemplates = localTokenTemplates, - $type = $type ?? 'error', - super._(); - - factory _$TokenContainerErrorImpl.fromJson(Map json) => _$$TokenContainerErrorImplFromJson(json); - -// Base fields - @override - @JsonKey() - final String serverName; - @override - final DateTime? lastSyncAt; - @override - @JsonKey() - final String serial; - @override - @JsonKey() - final String description; - final List _syncedTokenTemplates; - @override - @JsonKey() - List get syncedTokenTemplates { - if (_syncedTokenTemplates is EqualUnmodifiableListView) return _syncedTokenTemplates; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_syncedTokenTemplates); - } - - final List _localTokenTemplates; - @override - @JsonKey() - List get localTokenTemplates { - if (_localTokenTemplates is EqualUnmodifiableListView) return _localTokenTemplates; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_localTokenTemplates); - } - -// Base fields end - @override - final dynamic error; - - @JsonKey(name: 'runtimeType') - final String $type; - - @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'TokenContainer.error(serverName: $serverName, lastSyncAt: $lastSyncAt, serial: $serial, description: $description, syncedTokenTemplates: $syncedTokenTemplates, localTokenTemplates: $localTokenTemplates, error: $error)'; - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('type', 'TokenContainer.error')) - ..add(DiagnosticsProperty('serverName', serverName)) - ..add(DiagnosticsProperty('lastSyncAt', lastSyncAt)) - ..add(DiagnosticsProperty('serial', serial)) - ..add(DiagnosticsProperty('description', description)) - ..add(DiagnosticsProperty('syncedTokenTemplates', syncedTokenTemplates)) - ..add(DiagnosticsProperty('localTokenTemplates', localTokenTemplates)) - ..add(DiagnosticsProperty('error', error)); - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TokenContainerErrorImpl && - (identical(other.serverName, serverName) || other.serverName == serverName) && - (identical(other.lastSyncAt, lastSyncAt) || other.lastSyncAt == lastSyncAt) && - (identical(other.serial, serial) || other.serial == serial) && - (identical(other.description, description) || other.description == description) && - const DeepCollectionEquality().equals(other._syncedTokenTemplates, _syncedTokenTemplates) && - const DeepCollectionEquality().equals(other._localTokenTemplates, _localTokenTemplates) && - const DeepCollectionEquality().equals(other.error, error)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, serverName, lastSyncAt, serial, description, const DeepCollectionEquality().hash(_syncedTokenTemplates), - const DeepCollectionEquality().hash(_localTokenTemplates), const DeepCollectionEquality().hash(error)); - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$TokenContainerErrorImplCopyWith<_$TokenContainerErrorImpl> get copyWith => - __$$TokenContainerErrorImplCopyWithImpl<_$TokenContainerErrorImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - uninitialized, - required TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates) - synced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt) - modified, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message) - unsynced, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message) - notFound, - required TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error) - error, - }) { - return error(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, this.error); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult? Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult? Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - }) { - return error?.call(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, this.error); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - uninitialized, - TResult Function(String serverName, DateTime lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates)? - synced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, DateTime lastModifiedAt)? - modified, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String? message)? - unsynced, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, String message)? - notFound, - TResult Function(String serverName, DateTime? lastSyncAt, String serial, String description, List syncedTokenTemplates, - List localTokenTemplates, dynamic error)? - error, - required TResult orElse(), - }) { - if (error != null) { - return error(serverName, lastSyncAt, serial, description, syncedTokenTemplates, localTokenTemplates, this.error); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(TokenContainerUninitialized value) uninitialized, - required TResult Function(TokenContainerSynced value) synced, - required TResult Function(TokenContainerModified value) modified, - required TResult Function(TokenContainerUnsynced value) unsynced, - required TResult Function(TokenContainerNotFound value) notFound, - required TResult Function(TokenContainerError value) error, - }) { - return error(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(TokenContainerUninitialized value)? uninitialized, - TResult? Function(TokenContainerSynced value)? synced, - TResult? Function(TokenContainerModified value)? modified, - TResult? Function(TokenContainerUnsynced value)? unsynced, - TResult? Function(TokenContainerNotFound value)? notFound, - TResult? Function(TokenContainerError value)? error, - }) { - return error?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(TokenContainerUninitialized value)? uninitialized, - TResult Function(TokenContainerSynced value)? synced, - TResult Function(TokenContainerModified value)? modified, - TResult Function(TokenContainerUnsynced value)? unsynced, - TResult Function(TokenContainerNotFound value)? notFound, - TResult Function(TokenContainerError value)? error, - required TResult orElse(), - }) { - if (error != null) { - return error(this); - } - return orElse(); - } - - @override - Map toJson() { - return _$$TokenContainerErrorImplToJson( - this, - ); - } -} - -abstract class TokenContainerError extends TokenContainer { - const factory TokenContainerError( - {final String serverName, - final DateTime? lastSyncAt, - final String serial, - final String description, - final List syncedTokenTemplates, - final List localTokenTemplates, - required final dynamic error}) = _$TokenContainerErrorImpl; - const TokenContainerError._() : super._(); - - factory TokenContainerError.fromJson(Map json) = _$TokenContainerErrorImpl.fromJson; - -// Base fields - @override - String get serverName; - @override - DateTime? get lastSyncAt; - @override - String get serial; - @override - String get description; - @override - List get syncedTokenTemplates; - @override - List get localTokenTemplates; // Base fields end - dynamic get error; - - /// Create a copy of TokenContainer - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenContainerErrorImplCopyWith<_$TokenContainerErrorImpl> get copyWith => throw _privateConstructorUsedError; -} - -TokenTemplate _$TokenTemplateFromJson(Map json) { - return _TokenTemplate.fromJson(json); -} - -/// @nodoc -mixin _$TokenTemplate { - Map get data => throw _privateConstructorUsedError; - - /// Serializes this TokenTemplate to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of TokenTemplate - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $TokenTemplateCopyWith get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $TokenTemplateCopyWith<$Res> { - factory $TokenTemplateCopyWith(TokenTemplate value, $Res Function(TokenTemplate) then) = _$TokenTemplateCopyWithImpl<$Res, TokenTemplate>; - @useResult - $Res call({Map data}); -} - -/// @nodoc -class _$TokenTemplateCopyWithImpl<$Res, $Val extends TokenTemplate> implements $TokenTemplateCopyWith<$Res> { - _$TokenTemplateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of TokenTemplate - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? data = null, - }) { - return _then(_value.copyWith( - data: null == data - ? _value.data - : data // ignore: cast_nullable_to_non_nullable - as Map, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$TokenTemplateImplCopyWith<$Res> implements $TokenTemplateCopyWith<$Res> { - factory _$$TokenTemplateImplCopyWith(_$TokenTemplateImpl value, $Res Function(_$TokenTemplateImpl) then) = __$$TokenTemplateImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({Map data}); -} - -/// @nodoc -class __$$TokenTemplateImplCopyWithImpl<$Res> extends _$TokenTemplateCopyWithImpl<$Res, _$TokenTemplateImpl> implements _$$TokenTemplateImplCopyWith<$Res> { - __$$TokenTemplateImplCopyWithImpl(_$TokenTemplateImpl _value, $Res Function(_$TokenTemplateImpl) _then) : super(_value, _then); - - /// Create a copy of TokenTemplate - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? data = null, - }) { - return _then(_$TokenTemplateImpl( - data: null == data - ? _value._data - : data // ignore: cast_nullable_to_non_nullable - as Map, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TokenTemplateImpl extends _TokenTemplate with DiagnosticableTreeMixin { - _$TokenTemplateImpl({required final Map data}) - : _data = data, - super._(); - - factory _$TokenTemplateImpl.fromJson(Map json) => _$$TokenTemplateImplFromJson(json); - - final Map _data; - @override - Map get data { - if (_data is EqualUnmodifiableMapView) return _data; - // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(_data); - } - - @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'TokenTemplate(data: $data)'; - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('type', 'TokenTemplate')) - ..add(DiagnosticsProperty('data', data)); - } - - /// Create a copy of TokenTemplate - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$TokenTemplateImplCopyWith<_$TokenTemplateImpl> get copyWith => __$$TokenTemplateImplCopyWithImpl<_$TokenTemplateImpl>(this, _$identity); - - @override - Map toJson() { - return _$$TokenTemplateImplToJson( - this, - ); - } -} - -abstract class _TokenTemplate extends TokenTemplate { - factory _TokenTemplate({required final Map data}) = _$TokenTemplateImpl; - _TokenTemplate._() : super._(); - - factory _TokenTemplate.fromJson(Map json) = _$TokenTemplateImpl.fromJson; - - @override - Map get data; - - /// Create a copy of TokenTemplate - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$TokenTemplateImplCopyWith<_$TokenTemplateImpl> get copyWith => throw _privateConstructorUsedError; -} diff --git a/lib/model/token_container.g.dart b/lib/model/token_container.g.dart deleted file mode 100644 index f90406830..000000000 --- a/lib/model/token_container.g.dart +++ /dev/null @@ -1,145 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'token_container.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$TokenContainerUninitializedImpl _$$TokenContainerUninitializedImplFromJson(Map json) => _$TokenContainerUninitializedImpl( - serverName: json['serverName'] as String? ?? 'PrivacyIDEA', - lastSyncAt: json['lastSyncAt'] == null ? null : DateTime.parse(json['lastSyncAt'] as String), - serial: json['serial'] as String? ?? 'none', - description: json['description'] as String? ?? 'Uninitialized', - syncedTokenTemplates: - (json['syncedTokenTemplates'] as List?)?.map((e) => TokenTemplate.fromJson(e as Map)).toList() ?? const [], - localTokenTemplates: (json['localTokenTemplates'] as List?)?.map((e) => TokenTemplate.fromJson(e as Map)).toList() ?? const [], - $type: json['runtimeType'] as String?, - ); - -Map _$$TokenContainerUninitializedImplToJson(_$TokenContainerUninitializedImpl instance) => { - 'serverName': instance.serverName, - 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), - 'serial': instance.serial, - 'description': instance.description, - 'syncedTokenTemplates': instance.syncedTokenTemplates, - 'localTokenTemplates': instance.localTokenTemplates, - 'runtimeType': instance.$type, - }; - -_$TokenContainerSyncedImpl _$$TokenContainerSyncedImplFromJson(Map json) => _$TokenContainerSyncedImpl( - serverName: json['serverName'] as String? ?? 'PrivacyIDEA', - lastSyncAt: DateTime.parse(json['lastSyncAt'] as String), - serial: json['serial'] as String, - description: json['description'] as String, - syncedTokenTemplates: (json['syncedTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), - localTokenTemplates: (json['localTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), - $type: json['runtimeType'] as String?, - ); - -Map _$$TokenContainerSyncedImplToJson(_$TokenContainerSyncedImpl instance) => { - 'serverName': instance.serverName, - 'lastSyncAt': instance.lastSyncAt.toIso8601String(), - 'serial': instance.serial, - 'description': instance.description, - 'syncedTokenTemplates': instance.syncedTokenTemplates, - 'localTokenTemplates': instance.localTokenTemplates, - 'runtimeType': instance.$type, - }; - -_$TokenContainerModifiedImpl _$$TokenContainerModifiedImplFromJson(Map json) => _$TokenContainerModifiedImpl( - serverName: json['serverName'] as String? ?? 'PrivacyIDEA', - lastSyncAt: json['lastSyncAt'] == null ? null : DateTime.parse(json['lastSyncAt'] as String), - serial: json['serial'] as String, - description: json['description'] as String, - syncedTokenTemplates: (json['syncedTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), - localTokenTemplates: (json['localTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), - lastModifiedAt: DateTime.parse(json['lastModifiedAt'] as String), - $type: json['runtimeType'] as String?, - ); - -Map _$$TokenContainerModifiedImplToJson(_$TokenContainerModifiedImpl instance) => { - 'serverName': instance.serverName, - 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), - 'serial': instance.serial, - 'description': instance.description, - 'syncedTokenTemplates': instance.syncedTokenTemplates, - 'localTokenTemplates': instance.localTokenTemplates, - 'lastModifiedAt': instance.lastModifiedAt.toIso8601String(), - 'runtimeType': instance.$type, - }; - -_$TokenContainerUnsyncedImpl _$$TokenContainerUnsyncedImplFromJson(Map json) => _$TokenContainerUnsyncedImpl( - serverName: json['serverName'] as String? ?? 'PrivacyIDEA', - lastSyncAt: json['lastSyncAt'] == null ? null : DateTime.parse(json['lastSyncAt'] as String), - serial: json['serial'] as String, - description: json['description'] as String, - syncedTokenTemplates: (json['syncedTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), - localTokenTemplates: (json['localTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), - message: json['message'] as String?, - $type: json['runtimeType'] as String?, - ); - -Map _$$TokenContainerUnsyncedImplToJson(_$TokenContainerUnsyncedImpl instance) => { - 'serverName': instance.serverName, - 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), - 'serial': instance.serial, - 'description': instance.description, - 'syncedTokenTemplates': instance.syncedTokenTemplates, - 'localTokenTemplates': instance.localTokenTemplates, - 'message': instance.message, - 'runtimeType': instance.$type, - }; - -_$TokenContainerNotFoundImpl _$$TokenContainerNotFoundImplFromJson(Map json) => _$TokenContainerNotFoundImpl( - serverName: json['serverName'] as String? ?? 'PrivacyIDEA', - lastSyncAt: json['lastSyncAt'] == null ? null : DateTime.parse(json['lastSyncAt'] as String), - serial: json['serial'] as String, - description: json['description'] as String, - syncedTokenTemplates: (json['syncedTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), - localTokenTemplates: (json['localTokenTemplates'] as List).map((e) => TokenTemplate.fromJson(e as Map)).toList(), - message: json['message'] as String, - $type: json['runtimeType'] as String?, - ); - -Map _$$TokenContainerNotFoundImplToJson(_$TokenContainerNotFoundImpl instance) => { - 'serverName': instance.serverName, - 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), - 'serial': instance.serial, - 'description': instance.description, - 'syncedTokenTemplates': instance.syncedTokenTemplates, - 'localTokenTemplates': instance.localTokenTemplates, - 'message': instance.message, - 'runtimeType': instance.$type, - }; - -_$TokenContainerErrorImpl _$$TokenContainerErrorImplFromJson(Map json) => _$TokenContainerErrorImpl( - serverName: json['serverName'] as String? ?? 'PrivacyIDEA', - lastSyncAt: json['lastSyncAt'] == null ? null : DateTime.parse(json['lastSyncAt'] as String), - serial: json['serial'] as String? ?? 'none', - description: json['description'] as String? ?? 'Error', - syncedTokenTemplates: - (json['syncedTokenTemplates'] as List?)?.map((e) => TokenTemplate.fromJson(e as Map)).toList() ?? const [], - localTokenTemplates: (json['localTokenTemplates'] as List?)?.map((e) => TokenTemplate.fromJson(e as Map)).toList() ?? const [], - error: json['error'], - $type: json['runtimeType'] as String?, - ); - -Map _$$TokenContainerErrorImplToJson(_$TokenContainerErrorImpl instance) => { - 'serverName': instance.serverName, - 'lastSyncAt': instance.lastSyncAt?.toIso8601String(), - 'serial': instance.serial, - 'description': instance.description, - 'syncedTokenTemplates': instance.syncedTokenTemplates, - 'localTokenTemplates': instance.localTokenTemplates, - 'error': instance.error, - 'runtimeType': instance.$type, - }; - -_$TokenTemplateImpl _$$TokenTemplateImplFromJson(Map json) => _$TokenTemplateImpl( - data: json['data'] as Map, - ); - -Map _$$TokenTemplateImplToJson(_$TokenTemplateImpl instance) => { - 'data': instance.data, - }; diff --git a/lib/model/token_folder.dart b/lib/model/token_folder.dart index fe7840d7d..356843941 100644 --- a/lib/model/token_folder.dart +++ b/lib/model/token_folder.dart @@ -27,6 +27,12 @@ part 'token_folder.g.dart'; @immutable @JsonSerializable() class TokenFolder with SortableMixin { + static const LABEL = 'label'; + static const FOLDER_ID = 'folderId'; + static const IS_EXPANDED = 'isExpanded'; + static const IS_LOCKED = 'isLocked'; + static const SORT_INDEX = SortableMixin.SORT_INDEX; + final String label; final int folderId; final bool isExpanded; diff --git a/lib/model/token_import/token_origin_data.dart b/lib/model/token_import/token_origin_data.dart index e279b541e..e5e9dd02c 100644 --- a/lib/model/token_import/token_origin_data.dart +++ b/lib/model/token_import/token_origin_data.dart @@ -18,6 +18,7 @@ * limitations under the License. */ import 'package:json_annotation/json_annotation.dart'; +import 'package:privacyidea_authenticator/model/tokens/container_credentials.dart'; import '../enums/token_origin_source_type.dart'; import '../version.dart'; @@ -106,4 +107,19 @@ class TokenOriginData { factory TokenOriginData.fromJson(Map json) => _$TokenOriginDataFromJson(json); Map toJson() => _$TokenOriginDataToJson(this); + factory TokenOriginData.fromContainer({required ContainerCredential container, required String tokenData}) => TokenOriginData( + source: TokenOriginSourceType.container, + appName: container.issuer, + data: tokenData, + createdAt: DateTime.now(), + isPrivacyIdeaToken: true, + ); + + factory TokenOriginData.unknown([dynamic data]) => TokenOriginData( + source: TokenOriginSourceType.unknown, + appName: 'Unknown', + data: data.toString(), + createdAt: DateTime.now(), + isPrivacyIdeaToken: false, + ); } diff --git a/lib/model/token_template.dart b/lib/model/token_template.dart new file mode 100644 index 000000000..cbb6768c8 --- /dev/null +++ b/lib/model/token_template.dart @@ -0,0 +1,168 @@ +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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. +// */ + +// We need some unnecessary_overrides to force to add the fields in factory constructors +// ignore_for_file: unnecessary_overrides + +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; +import 'package:privacyidea_authenticator/model/tokens/container_credentials.dart'; +import 'package:privacyidea_authenticator/model/tokens/otp_token.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; + +import 'token_import/token_origin_data.dart'; +import 'tokens/token.dart'; + +part 'token_template.freezed.dart'; +part 'token_template.g.dart'; + +@freezed +class TokenTemplate with _$TokenTemplate { + TokenTemplate._(); + + factory TokenTemplate.withSerial({ + required Map otpAuthMap, + required String serial, + @Default({}) Map additionalData, + ContainerCredential? container, + }) = _TokenTemplateWithSerial; + + factory TokenTemplate.withOtps({ + required Map otpAuthMap, + required List otps, + required List checkedContainers, + @Default({}) Map additionalData, + ContainerCredential? container, + }) = _TokenTemplateWithOtps; + + List get keys => otpAuthMap.keys.toList(); + List get values => otpAuthMap.values.toList(); + + String? get serial => validateOptional( + value: otpAuthMap[OTP_AUTH_SERIAL], + validator: const TypeValidatorOptional(), + name: OTP_AUTH_SERIAL, + ); + + String? get type => validateOptional( + value: otpAuthMap[OTP_AUTH_TYPE], + validator: const TypeValidatorOptional(), + name: OTP_AUTH_TYPE, + ); + + List? get otpValues => this is _TokenTemplateWithOtps ? (this as _TokenTemplateWithOtps).otps : null; + + String? get containerSerial => validateOptional( + value: otpAuthMap[CONTAINER_SERIAL], + validator: const TypeValidatorOptional(), + name: CONTAINER_SERIAL, + ); + + @override + operator ==(Object other) { + if (other is! TokenTemplate) return false; + if (otpAuthMap.length != other.otpAuthMap.length) return false; + for (var key in otpAuthMap.keys) { + if (otpAuthMap[key].toString() != other.otpAuthMap[key].toString()) { + return false; + } + } + return true; + } + + factory TokenTemplate.fromJson(Map json) => _$TokenTemplateFromJson(json); + + Token toToken() { + if (container != null) { + additionalData[Token.CONTAINER_SERIAL] = container!.serial; + } + if (additionalData[Token.ORIGIN] != null) { + additionalData[Token.ORIGIN] = container != null + ? TokenOriginData( + appName: '${container!.serverName} ${container!.serial}', + data: otpAuthMap.toString(), + source: TokenOriginSourceType.container, + isPrivacyIdeaToken: true, + ) + : TokenOriginData.unknown(otpAuthMap); + } + return Token.fromOtpAuthMap(otpAuthMap, additionalData: additionalData); + } + + /// Adds all key/value pairs of [other] to this map. + /// + /// If a key of [other] is already in this map, its value is overwritten. + /// + /// The operation is equivalent to doing `this[key] = value` for each key + /// and associated value in other. It iterates over [other], which must + /// therefore not change during the iteration. + /// ```dart + /// final planets = {1: 'Mercury', 2: 'Earth'}; + /// planets.addAll({5: 'Jupiter', 6: 'Saturn'}); + /// print(planets); // {1: Mercury, 2: Earth, 5: Jupiter, 6: Saturn} + /// ``` + TokenTemplate withOtpAuthData(Map otpAuthMap) { + final newOtpAuthMap = Map.from(this.otpAuthMap)..addAll(otpAuthMap); + return copyWith(otpAuthMap: newOtpAuthMap); + } + + TokenTemplate withAditionalData(Map additionalData) { + final newAdditionalData = Map.from(this.additionalData)..addAll(additionalData); + return copyWith(additionalData: newAdditionalData); + } + + @override + int get hashCode => Object.hashAllUnordered([ + ...otpAuthMap.keys.map((key) => '$key:${otpAuthMap[key]}'), + ...additionalData.keys.map((key) => '$key:${additionalData[key]}'), + ]); + + bool isSameTokenAs(TokenTemplate? other) { + if (other == null) return false; + if (serial != null && serial == other.serial) return true; + if (otpValues != null && otpValues!.isNotEmpty && other is OTPToken && otpValues == other.otpValues) return true; + return false; + } + + // bool hasSameValuesAs(TokenTemplate serverTokenTemplate) { + // Logger.debug('serverTokenTemplate.keys: ${serverTokenTemplate.keys}', name: 'TokenTemplate#hasSameValuesAs'); + // for (var key in serverTokenTemplate.keys) { + // if (otpAuthMap[key] != serverTokenTemplate.otpAuthMap[key]) { + // Logger.debug('TokenTemplate has different values for key "$key": ${otpAuthMap[key]} != ${serverTokenTemplate.otpAuthMap[key]}', + // name: 'TokenTemplate#hasSameValuesAs'); + // return false; + // } + // } + // Logger.debug( + // 'AppTokenTemplate serial $serial/otp ($otpValues) has same values as serverTokenTemplate serial ${serverTokenTemplate.serial}/id ${serverTokenTemplate.otpValues}', + // name: 'TokenTemplate#hasSameValuesAs', + // ); + // return true; + // } + + // bool tokenWouldBeUpdated(Token token) { + // Logger.debug('Checking if token would be updated', name: 'TokenTemplate#tokenWouldBeUpdated'); + // final tokenTemplate = token.toTemplate(this); + // Logger.debug('TokenTemplate: \n$tokenTemplate\n has same values as \n$this\n ?', name: 'TokenTemplate#tokenWouldBeUpdated'); + // return tokenTemplate?.hasSameValuesAs(this) == false; + // } +} diff --git a/lib/model/token_template.freezed.dart b/lib/model/token_template.freezed.dart new file mode 100644 index 000000000..b83bcda33 --- /dev/null +++ b/lib/model/token_template.freezed.dart @@ -0,0 +1,713 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'token_template.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +TokenTemplate _$TokenTemplateFromJson(Map json) { + switch (json['runtimeType']) { + case 'withSerial': + return _TokenTemplateWithSerial.fromJson(json); + case 'withOtps': + return _TokenTemplateWithOtps.fromJson(json); + + default: + throw CheckedFromJsonException(json, 'runtimeType', 'TokenTemplate', + 'Invalid union type "${json['runtimeType']}"!'); + } +} + +/// @nodoc +mixin _$TokenTemplate { + Map get otpAuthMap => throw _privateConstructorUsedError; + Map get additionalData => throw _privateConstructorUsedError; + ContainerCredential? get container => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(Map otpAuthMap, String serial, + Map additionalData, ContainerCredential? container) + withSerial, + required TResult Function( + Map otpAuthMap, + List otps, + List checkedContainers, + Map additionalData, + ContainerCredential? container) + withOtps, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + Map otpAuthMap, + String serial, + Map additionalData, + ContainerCredential? container)? + withSerial, + TResult? Function( + Map otpAuthMap, + List otps, + List checkedContainers, + Map additionalData, + ContainerCredential? container)? + withOtps, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + Map otpAuthMap, + String serial, + Map additionalData, + ContainerCredential? container)? + withSerial, + TResult Function( + Map otpAuthMap, + List otps, + List checkedContainers, + Map additionalData, + ContainerCredential? container)? + withOtps, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_TokenTemplateWithSerial value) withSerial, + required TResult Function(_TokenTemplateWithOtps value) withOtps, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_TokenTemplateWithSerial value)? withSerial, + TResult? Function(_TokenTemplateWithOtps value)? withOtps, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_TokenTemplateWithSerial value)? withSerial, + TResult Function(_TokenTemplateWithOtps value)? withOtps, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Serializes this TokenTemplate to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $TokenTemplateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TokenTemplateCopyWith<$Res> { + factory $TokenTemplateCopyWith( + TokenTemplate value, $Res Function(TokenTemplate) then) = + _$TokenTemplateCopyWithImpl<$Res, TokenTemplate>; + @useResult + $Res call( + {Map otpAuthMap, + Map additionalData, + ContainerCredential? container}); + + $ContainerCredentialCopyWith<$Res>? get container; +} + +/// @nodoc +class _$TokenTemplateCopyWithImpl<$Res, $Val extends TokenTemplate> + implements $TokenTemplateCopyWith<$Res> { + _$TokenTemplateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? otpAuthMap = null, + Object? additionalData = null, + Object? container = freezed, + }) { + return _then(_value.copyWith( + otpAuthMap: null == otpAuthMap + ? _value.otpAuthMap + : otpAuthMap // ignore: cast_nullable_to_non_nullable + as Map, + additionalData: null == additionalData + ? _value.additionalData + : additionalData // ignore: cast_nullable_to_non_nullable + as Map, + container: freezed == container + ? _value.container + : container // ignore: cast_nullable_to_non_nullable + as ContainerCredential?, + ) as $Val); + } + + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ContainerCredentialCopyWith<$Res>? get container { + if (_value.container == null) { + return null; + } + + return $ContainerCredentialCopyWith<$Res>(_value.container!, (value) { + return _then(_value.copyWith(container: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$TokenTemplateWithSerialImplCopyWith<$Res> + implements $TokenTemplateCopyWith<$Res> { + factory _$$TokenTemplateWithSerialImplCopyWith( + _$TokenTemplateWithSerialImpl value, + $Res Function(_$TokenTemplateWithSerialImpl) then) = + __$$TokenTemplateWithSerialImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Map otpAuthMap, + String serial, + Map additionalData, + ContainerCredential? container}); + + @override + $ContainerCredentialCopyWith<$Res>? get container; +} + +/// @nodoc +class __$$TokenTemplateWithSerialImplCopyWithImpl<$Res> + extends _$TokenTemplateCopyWithImpl<$Res, _$TokenTemplateWithSerialImpl> + implements _$$TokenTemplateWithSerialImplCopyWith<$Res> { + __$$TokenTemplateWithSerialImplCopyWithImpl( + _$TokenTemplateWithSerialImpl _value, + $Res Function(_$TokenTemplateWithSerialImpl) _then) + : super(_value, _then); + + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? otpAuthMap = null, + Object? serial = null, + Object? additionalData = null, + Object? container = freezed, + }) { + return _then(_$TokenTemplateWithSerialImpl( + otpAuthMap: null == otpAuthMap + ? _value._otpAuthMap + : otpAuthMap // ignore: cast_nullable_to_non_nullable + as Map, + serial: null == serial + ? _value.serial + : serial // ignore: cast_nullable_to_non_nullable + as String, + additionalData: null == additionalData + ? _value._additionalData + : additionalData // ignore: cast_nullable_to_non_nullable + as Map, + container: freezed == container + ? _value.container + : container // ignore: cast_nullable_to_non_nullable + as ContainerCredential?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TokenTemplateWithSerialImpl extends _TokenTemplateWithSerial + with DiagnosticableTreeMixin { + _$TokenTemplateWithSerialImpl( + {required final Map otpAuthMap, + required this.serial, + final Map additionalData = const {}, + this.container, + final String? $type}) + : _otpAuthMap = otpAuthMap, + _additionalData = additionalData, + $type = $type ?? 'withSerial', + super._(); + + factory _$TokenTemplateWithSerialImpl.fromJson(Map json) => + _$$TokenTemplateWithSerialImplFromJson(json); + + final Map _otpAuthMap; + @override + Map get otpAuthMap { + if (_otpAuthMap is EqualUnmodifiableMapView) return _otpAuthMap; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_otpAuthMap); + } + + @override + final String serial; + final Map _additionalData; + @override + @JsonKey() + Map get additionalData { + if (_additionalData is EqualUnmodifiableMapView) return _additionalData; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_additionalData); + } + + @override + final ContainerCredential? container; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'TokenTemplate.withSerial(otpAuthMap: $otpAuthMap, serial: $serial, additionalData: $additionalData, container: $container)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'TokenTemplate.withSerial')) + ..add(DiagnosticsProperty('otpAuthMap', otpAuthMap)) + ..add(DiagnosticsProperty('serial', serial)) + ..add(DiagnosticsProperty('additionalData', additionalData)) + ..add(DiagnosticsProperty('container', container)); + } + + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$TokenTemplateWithSerialImplCopyWith<_$TokenTemplateWithSerialImpl> + get copyWith => __$$TokenTemplateWithSerialImplCopyWithImpl< + _$TokenTemplateWithSerialImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Map otpAuthMap, String serial, + Map additionalData, ContainerCredential? container) + withSerial, + required TResult Function( + Map otpAuthMap, + List otps, + List checkedContainers, + Map additionalData, + ContainerCredential? container) + withOtps, + }) { + return withSerial(otpAuthMap, serial, additionalData, container); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + Map otpAuthMap, + String serial, + Map additionalData, + ContainerCredential? container)? + withSerial, + TResult? Function( + Map otpAuthMap, + List otps, + List checkedContainers, + Map additionalData, + ContainerCredential? container)? + withOtps, + }) { + return withSerial?.call(otpAuthMap, serial, additionalData, container); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + Map otpAuthMap, + String serial, + Map additionalData, + ContainerCredential? container)? + withSerial, + TResult Function( + Map otpAuthMap, + List otps, + List checkedContainers, + Map additionalData, + ContainerCredential? container)? + withOtps, + required TResult orElse(), + }) { + if (withSerial != null) { + return withSerial(otpAuthMap, serial, additionalData, container); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_TokenTemplateWithSerial value) withSerial, + required TResult Function(_TokenTemplateWithOtps value) withOtps, + }) { + return withSerial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_TokenTemplateWithSerial value)? withSerial, + TResult? Function(_TokenTemplateWithOtps value)? withOtps, + }) { + return withSerial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_TokenTemplateWithSerial value)? withSerial, + TResult Function(_TokenTemplateWithOtps value)? withOtps, + required TResult orElse(), + }) { + if (withSerial != null) { + return withSerial(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$TokenTemplateWithSerialImplToJson( + this, + ); + } +} + +abstract class _TokenTemplateWithSerial extends TokenTemplate { + factory _TokenTemplateWithSerial( + {required final Map otpAuthMap, + required final String serial, + final Map additionalData, + final ContainerCredential? container}) = _$TokenTemplateWithSerialImpl; + _TokenTemplateWithSerial._() : super._(); + + factory _TokenTemplateWithSerial.fromJson(Map json) = + _$TokenTemplateWithSerialImpl.fromJson; + + @override + Map get otpAuthMap; + String get serial; + @override + Map get additionalData; + @override + ContainerCredential? get container; + + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$TokenTemplateWithSerialImplCopyWith<_$TokenTemplateWithSerialImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$TokenTemplateWithOtpsImplCopyWith<$Res> + implements $TokenTemplateCopyWith<$Res> { + factory _$$TokenTemplateWithOtpsImplCopyWith( + _$TokenTemplateWithOtpsImpl value, + $Res Function(_$TokenTemplateWithOtpsImpl) then) = + __$$TokenTemplateWithOtpsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Map otpAuthMap, + List otps, + List checkedContainers, + Map additionalData, + ContainerCredential? container}); + + @override + $ContainerCredentialCopyWith<$Res>? get container; +} + +/// @nodoc +class __$$TokenTemplateWithOtpsImplCopyWithImpl<$Res> + extends _$TokenTemplateCopyWithImpl<$Res, _$TokenTemplateWithOtpsImpl> + implements _$$TokenTemplateWithOtpsImplCopyWith<$Res> { + __$$TokenTemplateWithOtpsImplCopyWithImpl(_$TokenTemplateWithOtpsImpl _value, + $Res Function(_$TokenTemplateWithOtpsImpl) _then) + : super(_value, _then); + + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? otpAuthMap = null, + Object? otps = null, + Object? checkedContainers = null, + Object? additionalData = null, + Object? container = freezed, + }) { + return _then(_$TokenTemplateWithOtpsImpl( + otpAuthMap: null == otpAuthMap + ? _value._otpAuthMap + : otpAuthMap // ignore: cast_nullable_to_non_nullable + as Map, + otps: null == otps + ? _value._otps + : otps // ignore: cast_nullable_to_non_nullable + as List, + checkedContainers: null == checkedContainers + ? _value._checkedContainers + : checkedContainers // ignore: cast_nullable_to_non_nullable + as List, + additionalData: null == additionalData + ? _value._additionalData + : additionalData // ignore: cast_nullable_to_non_nullable + as Map, + container: freezed == container + ? _value.container + : container // ignore: cast_nullable_to_non_nullable + as ContainerCredential?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TokenTemplateWithOtpsImpl extends _TokenTemplateWithOtps + with DiagnosticableTreeMixin { + _$TokenTemplateWithOtpsImpl( + {required final Map otpAuthMap, + required final List otps, + required final List checkedContainers, + final Map additionalData = const {}, + this.container, + final String? $type}) + : _otpAuthMap = otpAuthMap, + _otps = otps, + _checkedContainers = checkedContainers, + _additionalData = additionalData, + $type = $type ?? 'withOtps', + super._(); + + factory _$TokenTemplateWithOtpsImpl.fromJson(Map json) => + _$$TokenTemplateWithOtpsImplFromJson(json); + + final Map _otpAuthMap; + @override + Map get otpAuthMap { + if (_otpAuthMap is EqualUnmodifiableMapView) return _otpAuthMap; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_otpAuthMap); + } + + final List _otps; + @override + List get otps { + if (_otps is EqualUnmodifiableListView) return _otps; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_otps); + } + + final List _checkedContainers; + @override + List get checkedContainers { + if (_checkedContainers is EqualUnmodifiableListView) + return _checkedContainers; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_checkedContainers); + } + + final Map _additionalData; + @override + @JsonKey() + Map get additionalData { + if (_additionalData is EqualUnmodifiableMapView) return _additionalData; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_additionalData); + } + + @override + final ContainerCredential? container; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'TokenTemplate.withOtps(otpAuthMap: $otpAuthMap, otps: $otps, checkedContainers: $checkedContainers, additionalData: $additionalData, container: $container)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'TokenTemplate.withOtps')) + ..add(DiagnosticsProperty('otpAuthMap', otpAuthMap)) + ..add(DiagnosticsProperty('otps', otps)) + ..add(DiagnosticsProperty('checkedContainers', checkedContainers)) + ..add(DiagnosticsProperty('additionalData', additionalData)) + ..add(DiagnosticsProperty('container', container)); + } + + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$TokenTemplateWithOtpsImplCopyWith<_$TokenTemplateWithOtpsImpl> + get copyWith => __$$TokenTemplateWithOtpsImplCopyWithImpl< + _$TokenTemplateWithOtpsImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Map otpAuthMap, String serial, + Map additionalData, ContainerCredential? container) + withSerial, + required TResult Function( + Map otpAuthMap, + List otps, + List checkedContainers, + Map additionalData, + ContainerCredential? container) + withOtps, + }) { + return withOtps( + otpAuthMap, otps, checkedContainers, additionalData, container); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + Map otpAuthMap, + String serial, + Map additionalData, + ContainerCredential? container)? + withSerial, + TResult? Function( + Map otpAuthMap, + List otps, + List checkedContainers, + Map additionalData, + ContainerCredential? container)? + withOtps, + }) { + return withOtps?.call( + otpAuthMap, otps, checkedContainers, additionalData, container); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + Map otpAuthMap, + String serial, + Map additionalData, + ContainerCredential? container)? + withSerial, + TResult Function( + Map otpAuthMap, + List otps, + List checkedContainers, + Map additionalData, + ContainerCredential? container)? + withOtps, + required TResult orElse(), + }) { + if (withOtps != null) { + return withOtps( + otpAuthMap, otps, checkedContainers, additionalData, container); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_TokenTemplateWithSerial value) withSerial, + required TResult Function(_TokenTemplateWithOtps value) withOtps, + }) { + return withOtps(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_TokenTemplateWithSerial value)? withSerial, + TResult? Function(_TokenTemplateWithOtps value)? withOtps, + }) { + return withOtps?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_TokenTemplateWithSerial value)? withSerial, + TResult Function(_TokenTemplateWithOtps value)? withOtps, + required TResult orElse(), + }) { + if (withOtps != null) { + return withOtps(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$TokenTemplateWithOtpsImplToJson( + this, + ); + } +} + +abstract class _TokenTemplateWithOtps extends TokenTemplate { + factory _TokenTemplateWithOtps( + {required final Map otpAuthMap, + required final List otps, + required final List checkedContainers, + final Map additionalData, + final ContainerCredential? container}) = _$TokenTemplateWithOtpsImpl; + _TokenTemplateWithOtps._() : super._(); + + factory _TokenTemplateWithOtps.fromJson(Map json) = + _$TokenTemplateWithOtpsImpl.fromJson; + + @override + Map get otpAuthMap; + List get otps; + List get checkedContainers; + @override + Map get additionalData; + @override + ContainerCredential? get container; + + /// Create a copy of TokenTemplate + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$TokenTemplateWithOtpsImplCopyWith<_$TokenTemplateWithOtpsImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/model/token_template.g.dart b/lib/model/token_template.g.dart new file mode 100644 index 000000000..7d9adad38 --- /dev/null +++ b/lib/model/token_template.g.dart @@ -0,0 +1,59 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'token_template.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$TokenTemplateWithSerialImpl _$$TokenTemplateWithSerialImplFromJson( + Map json) => + _$TokenTemplateWithSerialImpl( + otpAuthMap: json['otpAuthMap'] as Map, + serial: json['serial'] as String, + additionalData: + json['additionalData'] as Map? ?? const {}, + container: json['container'] == null + ? null + : ContainerCredential.fromJson( + json['container'] as Map), + $type: json['runtimeType'] as String?, + ); + +Map _$$TokenTemplateWithSerialImplToJson( + _$TokenTemplateWithSerialImpl instance) => + { + 'otpAuthMap': instance.otpAuthMap, + 'serial': instance.serial, + 'additionalData': instance.additionalData, + 'container': instance.container, + 'runtimeType': instance.$type, + }; + +_$TokenTemplateWithOtpsImpl _$$TokenTemplateWithOtpsImplFromJson( + Map json) => + _$TokenTemplateWithOtpsImpl( + otpAuthMap: json['otpAuthMap'] as Map, + otps: (json['otps'] as List).map((e) => e as String).toList(), + checkedContainers: (json['checkedContainers'] as List) + .map((e) => e as String) + .toList(), + additionalData: + json['additionalData'] as Map? ?? const {}, + container: json['container'] == null + ? null + : ContainerCredential.fromJson( + json['container'] as Map), + $type: json['runtimeType'] as String?, + ); + +Map _$$TokenTemplateWithOtpsImplToJson( + _$TokenTemplateWithOtpsImpl instance) => + { + 'otpAuthMap': instance.otpAuthMap, + 'otps': instance.otps, + 'checkedContainers': instance.checkedContainers, + 'additionalData': instance.additionalData, + 'container': instance.container, + 'runtimeType': instance.$type, + }; diff --git a/lib/model/tokens/container_credentials.dart b/lib/model/tokens/container_credentials.dart index ddd28ee6d..50faee744 100644 --- a/lib/model/tokens/container_credentials.dart +++ b/lib/model/tokens/container_credentials.dart @@ -24,6 +24,7 @@ import 'package:basic_utils/basic_utils.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; import 'package:privacyidea_authenticator/model/extensions/enums/ec_key_algorithm_extension.dart'; +import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; import 'package:privacyidea_authenticator/utils/type_matchers.dart'; @@ -31,12 +32,15 @@ import '../../utils/ecc_utils.dart'; import '../../utils/logger.dart'; import '../enums/container_finalization_state.dart'; import '../enums/ec_key_algorithm.dart'; +import '../enums/token_origin_source_type.dart'; +import '../token_import/token_origin_data.dart'; part 'container_credentials.freezed.dart'; part 'container_credentials.g.dart'; @Freezed(toStringOverride: false) class ContainerCredential with _$ContainerCredential { + static const SERIAL = 'serial'; static const eccUtils = EccUtils(); const ContainerCredential._(); @@ -81,9 +85,11 @@ class ContainerCredential with _$ContainerCredential { required String nonce, required DateTime timestamp, required Uri finalizationUrl, + Uri? syncUrl, required String serial, required EcKeyAlgorithm ecKeyAlgorithm, required Algorithms hashAlgorithm, + @Default('privacyIDEA') String serverName, @Default(ContainerFinalizationState.uninitialized) ContainerFinalizationState finalizationState, String? passphraseQuestion, String? publicServerKey, @@ -95,35 +101,41 @@ class ContainerCredential with _$ContainerCredential { required String issuer, required String nonce, required DateTime timestamp, - required Uri finalizationUrl, + required Uri syncUrl, required String serial, required EcKeyAlgorithm ecKeyAlgorithm, required Algorithms hashAlgorithm, + @Default('privacyIDEA') String serverName, @Default(ContainerFinalizationState.finalized) ContainerFinalizationState finalizationState, String? passphraseQuestion, required String publicServerKey, required String publicClientKey, required String privateClientKey, }) = ContainerCredentialFinalized; + ContainerCredentialFinalized? finalize({ ECPublicKey? publicServerKey, AsymmetricKeyPair? clientKeyPair, + Uri? syncUrl, }) { if (this is ContainerCredentialFinalized) return this as ContainerCredentialFinalized; if (publicServerKey == null && this.publicServerKey == null) { Logger.warning('Unable to finalize without public server key'); return null; } - assert(publicServerKey != null || this.publicServerKey != null, 'Unable to finalize without public server key'); if (clientKeyPair == null && (publicClientKey == null || privateClientKey == null)) { Logger.warning('Unable to finalize without client key pair'); return null; } + if (syncUrl == null && this.syncUrl == null) { + Logger.warning('Unable to finalize without sync url'); + return null; + } return ContainerCredentialFinalized( issuer: issuer, nonce: nonce, timestamp: timestamp, - finalizationUrl: finalizationUrl, + syncUrl: syncUrl ?? this.syncUrl!, serial: serial, ecKeyAlgorithm: ecKeyAlgorithm, hashAlgorithm: hashAlgorithm, @@ -154,7 +166,8 @@ class ContainerCredential with _$ContainerCredential { 'issuer: $issuer, ' 'nonce: $nonce, ' 'timestamp: $timestamp, ' - 'finalizationUrl: $finalizationUrl, ' + '${(this is ContainerCredentialUnfinalized) ? ' finalizationUrl: ${(this as ContainerCredentialUnfinalized).finalizationUrl}, ' : ''}' + 'syncUrl: $syncUrl, ' 'serial: $serial, ' 'ecKeyAlgorithm: $ecKeyAlgorithm, ' 'hashAlgorithm: $hashAlgorithm, ' @@ -162,8 +175,20 @@ class ContainerCredential with _$ContainerCredential { 'passphraseQuestion: $passphraseQuestion, ' 'publicServerKey: $publicServerKey, ' 'publicClientKey: $publicClientKey)'; -} + String signMessage(String msg) { + assert(ecPrivateClientKey != null, 'Unable to sign without private client key'); + return eccUtils.signWithPrivateKey(ecPrivateClientKey!, msg); + } + + String? trySignMessage(String msg) => ecPrivateClientKey == null ? null : eccUtils.signWithPrivateKey(ecPrivateClientKey!, msg); + + Token addOriginToToken({required Token token, String? tokenData}) => token.copyWith( + containerSerial: () => serial, + origin: token.origin == null + ? TokenOriginData.fromContainer(container: this, tokenData: tokenData ?? '') + : token.origin!.copyWith(source: TokenOriginSourceType.container)); +} //be99ff65b1c38ae8a7d6caf8799a0cce3749fe0e|2024-08-27 14:30:58.371312Z|http://192.168.0.230:5000/container/register/finalize|SMPH0000D49C //be99ff65b1c38ae8a7d6caf8799a0cce3749fe0e|2024-08-27T14:30:58.371312+00:00|http://192.168.0.230:5000/container/register/finalize|SMPH0000D49C diff --git a/lib/model/tokens/container_credentials.freezed.dart b/lib/model/tokens/container_credentials.freezed.dart index b89b8afc8..3f371cf5e 100644 --- a/lib/model/tokens/container_credentials.freezed.dart +++ b/lib/model/tokens/container_credentials.freezed.dart @@ -32,10 +32,11 @@ mixin _$ContainerCredential { String get issuer => throw _privateConstructorUsedError; String get nonce => throw _privateConstructorUsedError; DateTime get timestamp => throw _privateConstructorUsedError; - Uri get finalizationUrl => throw _privateConstructorUsedError; + Uri? get syncUrl => throw _privateConstructorUsedError; String get serial => throw _privateConstructorUsedError; EcKeyAlgorithm get ecKeyAlgorithm => throw _privateConstructorUsedError; Algorithms get hashAlgorithm => throw _privateConstructorUsedError; + String get serverName => throw _privateConstructorUsedError; ContainerFinalizationState get finalizationState => throw _privateConstructorUsedError; String? get passphraseQuestion => throw _privateConstructorUsedError; @@ -49,9 +50,11 @@ mixin _$ContainerCredential { String nonce, DateTime timestamp, Uri finalizationUrl, + Uri? syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String? publicServerKey, @@ -62,10 +65,11 @@ mixin _$ContainerCredential { String issuer, String nonce, DateTime timestamp, - Uri finalizationUrl, + Uri syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String publicServerKey, @@ -81,9 +85,11 @@ mixin _$ContainerCredential { String nonce, DateTime timestamp, Uri finalizationUrl, + Uri? syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String? publicServerKey, @@ -94,10 +100,11 @@ mixin _$ContainerCredential { String issuer, String nonce, DateTime timestamp, - Uri finalizationUrl, + Uri syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String publicServerKey, @@ -113,9 +120,11 @@ mixin _$ContainerCredential { String nonce, DateTime timestamp, Uri finalizationUrl, + Uri? syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String? publicServerKey, @@ -126,10 +135,11 @@ mixin _$ContainerCredential { String issuer, String nonce, DateTime timestamp, - Uri finalizationUrl, + Uri syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String publicServerKey, @@ -179,10 +189,11 @@ abstract class $ContainerCredentialCopyWith<$Res> { {String issuer, String nonce, DateTime timestamp, - Uri finalizationUrl, + Uri syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String publicServerKey, @@ -208,10 +219,11 @@ class _$ContainerCredentialCopyWithImpl<$Res, $Val extends ContainerCredential> Object? issuer = null, Object? nonce = null, Object? timestamp = null, - Object? finalizationUrl = null, + Object? syncUrl = null, Object? serial = null, Object? ecKeyAlgorithm = null, Object? hashAlgorithm = null, + Object? serverName = null, Object? finalizationState = null, Object? passphraseQuestion = freezed, Object? publicServerKey = null, @@ -231,9 +243,9 @@ class _$ContainerCredentialCopyWithImpl<$Res, $Val extends ContainerCredential> ? _value.timestamp : timestamp // ignore: cast_nullable_to_non_nullable as DateTime, - finalizationUrl: null == finalizationUrl - ? _value.finalizationUrl - : finalizationUrl // ignore: cast_nullable_to_non_nullable + syncUrl: null == syncUrl + ? _value.syncUrl! + : syncUrl // ignore: cast_nullable_to_non_nullable as Uri, serial: null == serial ? _value.serial @@ -247,6 +259,10 @@ class _$ContainerCredentialCopyWithImpl<$Res, $Val extends ContainerCredential> ? _value.hashAlgorithm : hashAlgorithm // ignore: cast_nullable_to_non_nullable as Algorithms, + serverName: null == serverName + ? _value.serverName + : serverName // ignore: cast_nullable_to_non_nullable + as String, finalizationState: null == finalizationState ? _value.finalizationState : finalizationState // ignore: cast_nullable_to_non_nullable @@ -285,9 +301,11 @@ abstract class _$$ContainerCredentialUnfinalizedImplCopyWith<$Res> String nonce, DateTime timestamp, Uri finalizationUrl, + Uri? syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String? publicServerKey, @@ -314,9 +332,11 @@ class __$$ContainerCredentialUnfinalizedImplCopyWithImpl<$Res> Object? nonce = null, Object? timestamp = null, Object? finalizationUrl = null, + Object? syncUrl = freezed, Object? serial = null, Object? ecKeyAlgorithm = null, Object? hashAlgorithm = null, + Object? serverName = null, Object? finalizationState = null, Object? passphraseQuestion = freezed, Object? publicServerKey = freezed, @@ -340,6 +360,10 @@ class __$$ContainerCredentialUnfinalizedImplCopyWithImpl<$Res> ? _value.finalizationUrl : finalizationUrl // ignore: cast_nullable_to_non_nullable as Uri, + syncUrl: freezed == syncUrl + ? _value.syncUrl + : syncUrl // ignore: cast_nullable_to_non_nullable + as Uri?, serial: null == serial ? _value.serial : serial // ignore: cast_nullable_to_non_nullable @@ -352,6 +376,10 @@ class __$$ContainerCredentialUnfinalizedImplCopyWithImpl<$Res> ? _value.hashAlgorithm : hashAlgorithm // ignore: cast_nullable_to_non_nullable as Algorithms, + serverName: null == serverName + ? _value.serverName + : serverName // ignore: cast_nullable_to_non_nullable + as String, finalizationState: null == finalizationState ? _value.finalizationState : finalizationState // ignore: cast_nullable_to_non_nullable @@ -385,9 +413,11 @@ class _$ContainerCredentialUnfinalizedImpl required this.nonce, required this.timestamp, required this.finalizationUrl, + this.syncUrl, required this.serial, required this.ecKeyAlgorithm, required this.hashAlgorithm, + this.serverName = 'privacyIDEA', this.finalizationState = ContainerFinalizationState.uninitialized, this.passphraseQuestion, this.publicServerKey, @@ -410,6 +440,8 @@ class _$ContainerCredentialUnfinalizedImpl @override final Uri finalizationUrl; @override + final Uri? syncUrl; + @override final String serial; @override final EcKeyAlgorithm ecKeyAlgorithm; @@ -417,6 +449,9 @@ class _$ContainerCredentialUnfinalizedImpl final Algorithms hashAlgorithm; @override @JsonKey() + final String serverName; + @override + @JsonKey() final ContainerFinalizationState finalizationState; @override final String? passphraseQuestion; @@ -441,11 +476,14 @@ class _$ContainerCredentialUnfinalizedImpl other.timestamp == timestamp) && (identical(other.finalizationUrl, finalizationUrl) || other.finalizationUrl == finalizationUrl) && + (identical(other.syncUrl, syncUrl) || other.syncUrl == syncUrl) && (identical(other.serial, serial) || other.serial == serial) && (identical(other.ecKeyAlgorithm, ecKeyAlgorithm) || other.ecKeyAlgorithm == ecKeyAlgorithm) && (identical(other.hashAlgorithm, hashAlgorithm) || other.hashAlgorithm == hashAlgorithm) && + (identical(other.serverName, serverName) || + other.serverName == serverName) && (identical(other.finalizationState, finalizationState) || other.finalizationState == finalizationState) && (identical(other.passphraseQuestion, passphraseQuestion) || @@ -466,9 +504,11 @@ class _$ContainerCredentialUnfinalizedImpl nonce, timestamp, finalizationUrl, + syncUrl, serial, ecKeyAlgorithm, hashAlgorithm, + serverName, finalizationState, passphraseQuestion, publicServerKey, @@ -493,9 +533,11 @@ class _$ContainerCredentialUnfinalizedImpl String nonce, DateTime timestamp, Uri finalizationUrl, + Uri? syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String? publicServerKey, @@ -506,10 +548,11 @@ class _$ContainerCredentialUnfinalizedImpl String issuer, String nonce, DateTime timestamp, - Uri finalizationUrl, + Uri syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String publicServerKey, @@ -522,9 +565,11 @@ class _$ContainerCredentialUnfinalizedImpl nonce, timestamp, finalizationUrl, + syncUrl, serial, ecKeyAlgorithm, hashAlgorithm, + serverName, finalizationState, passphraseQuestion, publicServerKey, @@ -540,9 +585,11 @@ class _$ContainerCredentialUnfinalizedImpl String nonce, DateTime timestamp, Uri finalizationUrl, + Uri? syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String? publicServerKey, @@ -553,10 +600,11 @@ class _$ContainerCredentialUnfinalizedImpl String issuer, String nonce, DateTime timestamp, - Uri finalizationUrl, + Uri syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String publicServerKey, @@ -569,9 +617,11 @@ class _$ContainerCredentialUnfinalizedImpl nonce, timestamp, finalizationUrl, + syncUrl, serial, ecKeyAlgorithm, hashAlgorithm, + serverName, finalizationState, passphraseQuestion, publicServerKey, @@ -587,9 +637,11 @@ class _$ContainerCredentialUnfinalizedImpl String nonce, DateTime timestamp, Uri finalizationUrl, + Uri? syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String? publicServerKey, @@ -600,10 +652,11 @@ class _$ContainerCredentialUnfinalizedImpl String issuer, String nonce, DateTime timestamp, - Uri finalizationUrl, + Uri syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String publicServerKey, @@ -618,9 +671,11 @@ class _$ContainerCredentialUnfinalizedImpl nonce, timestamp, finalizationUrl, + syncUrl, serial, ecKeyAlgorithm, hashAlgorithm, + serverName, finalizationState, passphraseQuestion, publicServerKey, @@ -675,9 +730,11 @@ abstract class ContainerCredentialUnfinalized extends ContainerCredential { required final String nonce, required final DateTime timestamp, required final Uri finalizationUrl, + final Uri? syncUrl, required final String serial, required final EcKeyAlgorithm ecKeyAlgorithm, required final Algorithms hashAlgorithm, + final String serverName, final ContainerFinalizationState finalizationState, final String? passphraseQuestion, final String? publicServerKey, @@ -694,15 +751,18 @@ abstract class ContainerCredentialUnfinalized extends ContainerCredential { String get nonce; @override DateTime get timestamp; - @override Uri get finalizationUrl; @override + Uri? get syncUrl; + @override String get serial; @override EcKeyAlgorithm get ecKeyAlgorithm; @override Algorithms get hashAlgorithm; @override + String get serverName; + @override ContainerFinalizationState get finalizationState; @override String? get passphraseQuestion; @@ -735,10 +795,11 @@ abstract class _$$ContainerCredentialFinalizedImplCopyWith<$Res> {String issuer, String nonce, DateTime timestamp, - Uri finalizationUrl, + Uri syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String publicServerKey, @@ -764,10 +825,11 @@ class __$$ContainerCredentialFinalizedImplCopyWithImpl<$Res> Object? issuer = null, Object? nonce = null, Object? timestamp = null, - Object? finalizationUrl = null, + Object? syncUrl = null, Object? serial = null, Object? ecKeyAlgorithm = null, Object? hashAlgorithm = null, + Object? serverName = null, Object? finalizationState = null, Object? passphraseQuestion = freezed, Object? publicServerKey = null, @@ -787,9 +849,9 @@ class __$$ContainerCredentialFinalizedImplCopyWithImpl<$Res> ? _value.timestamp : timestamp // ignore: cast_nullable_to_non_nullable as DateTime, - finalizationUrl: null == finalizationUrl - ? _value.finalizationUrl - : finalizationUrl // ignore: cast_nullable_to_non_nullable + syncUrl: null == syncUrl + ? _value.syncUrl + : syncUrl // ignore: cast_nullable_to_non_nullable as Uri, serial: null == serial ? _value.serial @@ -803,6 +865,10 @@ class __$$ContainerCredentialFinalizedImplCopyWithImpl<$Res> ? _value.hashAlgorithm : hashAlgorithm // ignore: cast_nullable_to_non_nullable as Algorithms, + serverName: null == serverName + ? _value.serverName + : serverName // ignore: cast_nullable_to_non_nullable + as String, finalizationState: null == finalizationState ? _value.finalizationState : finalizationState // ignore: cast_nullable_to_non_nullable @@ -834,10 +900,11 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { {required this.issuer, required this.nonce, required this.timestamp, - required this.finalizationUrl, + required this.syncUrl, required this.serial, required this.ecKeyAlgorithm, required this.hashAlgorithm, + this.serverName = 'privacyIDEA', this.finalizationState = ContainerFinalizationState.finalized, this.passphraseQuestion, required this.publicServerKey, @@ -858,7 +925,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { @override final DateTime timestamp; @override - final Uri finalizationUrl; + final Uri syncUrl; @override final String serial; @override @@ -867,6 +934,9 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { final Algorithms hashAlgorithm; @override @JsonKey() + final String serverName; + @override + @JsonKey() final ContainerFinalizationState finalizationState; @override final String? passphraseQuestion; @@ -889,13 +959,14 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { (identical(other.nonce, nonce) || other.nonce == nonce) && (identical(other.timestamp, timestamp) || other.timestamp == timestamp) && - (identical(other.finalizationUrl, finalizationUrl) || - other.finalizationUrl == finalizationUrl) && + (identical(other.syncUrl, syncUrl) || other.syncUrl == syncUrl) && (identical(other.serial, serial) || other.serial == serial) && (identical(other.ecKeyAlgorithm, ecKeyAlgorithm) || other.ecKeyAlgorithm == ecKeyAlgorithm) && (identical(other.hashAlgorithm, hashAlgorithm) || other.hashAlgorithm == hashAlgorithm) && + (identical(other.serverName, serverName) || + other.serverName == serverName) && (identical(other.finalizationState, finalizationState) || other.finalizationState == finalizationState) && (identical(other.passphraseQuestion, passphraseQuestion) || @@ -915,10 +986,11 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { issuer, nonce, timestamp, - finalizationUrl, + syncUrl, serial, ecKeyAlgorithm, hashAlgorithm, + serverName, finalizationState, passphraseQuestion, publicServerKey, @@ -943,9 +1015,11 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { String nonce, DateTime timestamp, Uri finalizationUrl, + Uri? syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String? publicServerKey, @@ -956,10 +1030,11 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { String issuer, String nonce, DateTime timestamp, - Uri finalizationUrl, + Uri syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String publicServerKey, @@ -971,10 +1046,11 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { issuer, nonce, timestamp, - finalizationUrl, + syncUrl, serial, ecKeyAlgorithm, hashAlgorithm, + serverName, finalizationState, passphraseQuestion, publicServerKey, @@ -990,9 +1066,11 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { String nonce, DateTime timestamp, Uri finalizationUrl, + Uri? syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String? publicServerKey, @@ -1003,10 +1081,11 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { String issuer, String nonce, DateTime timestamp, - Uri finalizationUrl, + Uri syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String publicServerKey, @@ -1018,10 +1097,11 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { issuer, nonce, timestamp, - finalizationUrl, + syncUrl, serial, ecKeyAlgorithm, hashAlgorithm, + serverName, finalizationState, passphraseQuestion, publicServerKey, @@ -1037,9 +1117,11 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { String nonce, DateTime timestamp, Uri finalizationUrl, + Uri? syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String? publicServerKey, @@ -1050,10 +1132,11 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { String issuer, String nonce, DateTime timestamp, - Uri finalizationUrl, + Uri syncUrl, String serial, EcKeyAlgorithm ecKeyAlgorithm, Algorithms hashAlgorithm, + String serverName, ContainerFinalizationState finalizationState, String? passphraseQuestion, String publicServerKey, @@ -1067,10 +1150,11 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { issuer, nonce, timestamp, - finalizationUrl, + syncUrl, serial, ecKeyAlgorithm, hashAlgorithm, + serverName, finalizationState, passphraseQuestion, publicServerKey, @@ -1124,10 +1208,11 @@ abstract class ContainerCredentialFinalized extends ContainerCredential { {required final String issuer, required final String nonce, required final DateTime timestamp, - required final Uri finalizationUrl, + required final Uri syncUrl, required final String serial, required final EcKeyAlgorithm ecKeyAlgorithm, required final Algorithms hashAlgorithm, + final String serverName, final ContainerFinalizationState finalizationState, final String? passphraseQuestion, required final String publicServerKey, @@ -1146,7 +1231,7 @@ abstract class ContainerCredentialFinalized extends ContainerCredential { @override DateTime get timestamp; @override - Uri get finalizationUrl; + Uri get syncUrl; @override String get serial; @override @@ -1154,6 +1239,8 @@ abstract class ContainerCredentialFinalized extends ContainerCredential { @override Algorithms get hashAlgorithm; @override + String get serverName; + @override ContainerFinalizationState get finalizationState; @override String? get passphraseQuestion; diff --git a/lib/model/tokens/container_credentials.g.dart b/lib/model/tokens/container_credentials.g.dart index c58531d2e..c7a66e427 100644 --- a/lib/model/tokens/container_credentials.g.dart +++ b/lib/model/tokens/container_credentials.g.dart @@ -13,11 +13,15 @@ _$ContainerCredentialUnfinalizedImpl nonce: json['nonce'] as String, timestamp: DateTime.parse(json['timestamp'] as String), finalizationUrl: Uri.parse(json['finalizationUrl'] as String), + syncUrl: json['syncUrl'] == null + ? null + : Uri.parse(json['syncUrl'] as String), serial: json['serial'] as String, ecKeyAlgorithm: $enumDecode(_$EcKeyAlgorithmEnumMap, json['ecKeyAlgorithm']), hashAlgorithm: $enumDecode(_$AlgorithmsEnumMap, json['hashAlgorithm']), + serverName: json['serverName'] as String? ?? 'privacyIDEA', finalizationState: $enumDecodeNullable( _$ContainerFinalizationStateEnumMap, json['finalizationState']) ?? @@ -36,9 +40,11 @@ Map _$$ContainerCredentialUnfinalizedImplToJson( 'nonce': instance.nonce, 'timestamp': instance.timestamp.toIso8601String(), 'finalizationUrl': instance.finalizationUrl.toString(), + 'syncUrl': instance.syncUrl?.toString(), 'serial': instance.serial, 'ecKeyAlgorithm': _$EcKeyAlgorithmEnumMap[instance.ecKeyAlgorithm]!, 'hashAlgorithm': _$AlgorithmsEnumMap[instance.hashAlgorithm]!, + 'serverName': instance.serverName, 'finalizationState': _$ContainerFinalizationStateEnumMap[instance.finalizationState]!, 'passphraseQuestion': instance.passphraseQuestion, @@ -121,11 +127,12 @@ _$ContainerCredentialFinalizedImpl _$$ContainerCredentialFinalizedImplFromJson( issuer: json['issuer'] as String, nonce: json['nonce'] as String, timestamp: DateTime.parse(json['timestamp'] as String), - finalizationUrl: Uri.parse(json['finalizationUrl'] as String), + syncUrl: Uri.parse(json['syncUrl'] as String), serial: json['serial'] as String, ecKeyAlgorithm: $enumDecode(_$EcKeyAlgorithmEnumMap, json['ecKeyAlgorithm']), hashAlgorithm: $enumDecode(_$AlgorithmsEnumMap, json['hashAlgorithm']), + serverName: json['serverName'] as String? ?? 'privacyIDEA', finalizationState: $enumDecodeNullable( _$ContainerFinalizationStateEnumMap, json['finalizationState']) ?? ContainerFinalizationState.finalized, @@ -142,10 +149,11 @@ Map _$$ContainerCredentialFinalizedImplToJson( 'issuer': instance.issuer, 'nonce': instance.nonce, 'timestamp': instance.timestamp.toIso8601String(), - 'finalizationUrl': instance.finalizationUrl.toString(), + 'syncUrl': instance.syncUrl.toString(), 'serial': instance.serial, 'ecKeyAlgorithm': _$EcKeyAlgorithmEnumMap[instance.ecKeyAlgorithm]!, 'hashAlgorithm': _$AlgorithmsEnumMap[instance.hashAlgorithm]!, + 'serverName': instance.serverName, 'finalizationState': _$ContainerFinalizationStateEnumMap[instance.finalizationState]!, 'passphraseQuestion': instance.passphraseQuestion, diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index 85dedcdbd..abb55a977 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -22,7 +22,7 @@ import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; import '../../utils/identifiers.dart'; import '../../utils/type_matchers.dart'; -import '../token_container.dart'; +import '../token_template.dart'; import 'package:uuid/uuid.dart'; import '../enums/algorithms.dart'; @@ -156,7 +156,7 @@ class DayPasswordToken extends OTPToken { @override DayPasswordToken copyUpdateByTemplate(TokenTemplate template) { final uriMap = validateMap( - map: template.data, + map: template.otpAuthMap, validators: { OTP_AUTH_LABEL: const TypeValidatorOptional(), OTP_AUTH_ISSUER: const TypeValidatorOptional(), @@ -184,7 +184,7 @@ class DayPasswordToken extends OTPToken { ); } - factory DayPasswordToken.fromOtpAuthMap(Map uriMap, {required TokenOriginData origin}) { + factory DayPasswordToken.fromOtpAuthMap(Map uriMap, {required Map additionalData}) { uriMap = validateMap( map: uriMap, validators: { @@ -200,10 +200,10 @@ class DayPasswordToken extends OTPToken { }, name: 'DayPasswordToken', ); + final validatedAdditionalData = Token.validateAdditionalData(additionalData); return DayPasswordToken( label: uriMap[OTP_AUTH_LABEL], issuer: uriMap[OTP_AUTH_ISSUER], - id: const Uuid().v4(), serial: uriMap[OTP_AUTH_SERIAL], algorithm: uriMap[OTP_AUTH_ALGORITHM], digits: uriMap[OTP_AUTH_DIGITS], @@ -212,30 +212,45 @@ class DayPasswordToken extends OTPToken { tokenImage: uriMap[OTP_AUTH_IMAGE], pin: uriMap[OTP_AUTH_PIN], isLocked: uriMap[OTP_AUTH_PIN], - origin: origin, + id: validatedAdditionalData[Token.ID] ?? const Uuid().v4(), + containerSerial: validatedAdditionalData[Token.CONTAINER_SERIAL], + checkedContainers: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], + sortIndex: validatedAdditionalData[Token.SORT_INDEX], + folderId: validatedAdditionalData[Token.FOLDER_ID], + origin: validatedAdditionalData[Token.ORIGIN], + isHidden: validatedAdditionalData[Token.HIDDEN], ); } /// This is used to create a map that typically was created from a uri. /// ```dart - /// ------------------------- [Token] ------------------------- - /// | OTP_AUTH_SERIAL: serial, (optional) | - /// | OTP_AUTH_TYPE: type, | - /// | OTP_AUTH_LABEL: label, | - /// | OTP_AUTH_ISSUER: issuer, | - /// | OTP_AUTH_PIN: pin, | - /// | OTP_AUTH_IMAGE: tokenImage, (optional) | - /// ----------------------------------------------------------- - /// ----------------------- [OTPToken] ------------------------ - /// | OTP_AUTH_ALGORITHM: algorithm, | - /// | OTP_AUTH_DIGITS: digits, | - /// ----------------------------------------------------------- - /// ------------------- [DayPasswordToken] -------------------- - /// | OTP_AUTH_PERIOD: period, | - /// ----------------------------------------------------------- + /// -------------------------- [Token] -------------------------------- + /// | OTP_AUTH_SERIAL: serial, (optional) | + /// | OTP_AUTH_LABEL: label, | + /// | OTP_AUTH_ISSUER: issuer, | + /// | CONTAINER_SERIAL: containerSerial, (optional) | + /// | CHECKED_CONTAINERS: checkedContainers, | + /// | TOKEN_ID: id, | + /// | OTP_AUTH_TYPE: type, | + /// | OTP_AUTH_IMAGE: tokenImage, (optional) | + /// | SORTABLE_INDEX: sortIndex, (optional) | + /// | FOLDER_ID: folderId, (optional) | + /// | TOKEN_ORIGIN: origin, (optional) | + /// | OTP_AUTH_PIN: pin, | + /// | TOKEN_HIDDEN: isHidden, | + /// ------------------------------------------------------------------- + /// ------------------------- [OTPToken] ------------------------------ + /// | OTP_AUTH_ALGORITHM: algorithm, | + /// | OTP_AUTH_DIGITS: digits, | + /// | OTP_AUTH_SECRET_BASE32: secret, | + /// | OTP_AUTH_OTP_VALUES: [otpValue, nextValue], (if serial is null) | + /// ------------------------------------------------------------------- + /// -------------------- [DayPasswordToken] --------------------------- + /// | OTP_AUTH_PERIOD: period, | + /// ------------------------------------------------------------------- /// ``` @override - Map toOtpAuthMap({String? containerSerial}) { + Map toOtpAuthMap() { return super.toOtpAuthMap() ..addAll({ OTP_AUTH_PERIOD_SECONDS: period.inSeconds.toString(), diff --git a/lib/model/tokens/day_password_token.g.dart b/lib/model/tokens/day_password_token.g.dart index 072f69ff3..5b261a626 100644 --- a/lib/model/tokens/day_password_token.g.dart +++ b/lib/model/tokens/day_password_token.g.dart @@ -13,8 +13,12 @@ DayPasswordToken _$DayPasswordTokenFromJson(Map json) => algorithm: $enumDecode(_$AlgorithmsEnumMap, json['algorithm']), digits: (json['digits'] as num).toInt(), secret: json['secret'] as String, - containerSerial: json['containerSerial'] as String?, serial: json['serial'] as String?, + containerSerial: json['containerSerial'] as String?, + checkedContainers: (json['checkedContainers'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], viewMode: $enumDecodeNullable( _$DayPasswordTokenViewModeEnumMap, json['viewMode']) ?? DayPasswordTokenViewMode.VALIDFOR, @@ -34,9 +38,10 @@ DayPasswordToken _$DayPasswordTokenFromJson(Map json) => Map _$DayPasswordTokenToJson(DayPasswordToken instance) => { - 'containerSerial': instance.containerSerial, + 'checkedContainers': instance.checkedContainers, 'label': instance.label, 'issuer': instance.issuer, + 'containerSerial': instance.containerSerial, 'id': instance.id, 'serial': instance.serial, 'pin': instance.pin, diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index 23df2749a..b9d201ebd 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -20,7 +20,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:privacyidea_authenticator/utils/type_matchers.dart'; -import '../token_container.dart'; +import '../token_template.dart'; import 'package:uuid/uuid.dart'; import '../../utils/identifiers.dart'; @@ -135,7 +135,7 @@ class HOTPToken extends OTPToken { @override HOTPToken copyUpdateByTemplate(TokenTemplate template) { final uriMap = validateMap( - map: template.data, + map: template.otpAuthMap, validators: { OTP_AUTH_LABEL: const TypeValidatorOptional(), OTP_AUTH_ISSUER: const TypeValidatorOptional(), @@ -163,7 +163,7 @@ class HOTPToken extends OTPToken { ); } - factory HOTPToken.fromOtpAuthMap(Map otpAuthMap, {required TokenOriginData origin}) { + factory HOTPToken.fromOtpAuthMap(Map otpAuthMap, {required Map additionalData}) { final validatedMap = validateMap( map: otpAuthMap, validators: { @@ -177,12 +177,12 @@ class HOTPToken extends OTPToken { OTP_AUTH_IMAGE: const TypeValidatorOptional(), OTP_AUTH_PIN: stringToBoolValidatorOptional, }, - name: 'HOTPToken', + name: 'HOTPToken#otpAuthMap', ); + final validatedAdditionalData = Token.validateAdditionalData(additionalData); return HOTPToken( label: validatedMap[OTP_AUTH_LABEL] as String, issuer: validatedMap[OTP_AUTH_ISSUER] as String, - id: const Uuid().v4(), serial: validatedMap[OTP_AUTH_SERIAL] as String?, algorithm: validatedMap[OTP_AUTH_ALGORITHM] as Algorithms, digits: validatedMap[OTP_AUTH_DIGITS] as int, @@ -191,30 +191,45 @@ class HOTPToken extends OTPToken { tokenImage: validatedMap[OTP_AUTH_IMAGE] as String?, pin: validatedMap[OTP_AUTH_PIN] as bool?, isLocked: validatedMap[OTP_AUTH_PIN] as bool?, - origin: origin, + containerSerial: validatedAdditionalData[Token.CONTAINER_SERIAL], + id: validatedAdditionalData[Token.ID] ?? const Uuid().v4(), + origin: validatedAdditionalData[Token.ORIGIN], + isHidden: validatedAdditionalData[Token.HIDDEN], + checkedContainers: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], + folderId: validatedAdditionalData[Token.FOLDER_ID], + sortIndex: validatedAdditionalData[Token.SORT_INDEX], ); } /// This is used to create a map that typically was created from a uri. /// ```dart - /// ------------------------- [Token] ------------------------- - /// | OTP_AUTH_SERIAL: serial, (optional) | - /// | OTP_AUTH_TYPE: type, | - /// | OTP_AUTH_LABEL: label, | - /// | OTP_AUTH_ISSUER: issuer, | - /// | OTP_AUTH_PIN: pin, | - /// | OTP_AUTH_IMAGE: tokenImage, (optional) | - /// ----------------------------------------------------------- - /// ----------------------- [OTPToken] ------------------------ - /// | OTP_AUTH_ALGORITHM: algorithm, | - /// | OTP_AUTH_DIGITS: digits, | - /// ----------------------------------------------------------- - /// ----------------------- [HOTPToken] ----------------------- - /// | OTP_AUTH_COUNTER: counter, | - /// ----------------------------------------------------------- + /// -------------------------- [Token] -------------------------------- + /// | OTP_AUTH_SERIAL: serial, (optional) | + /// | OTP_AUTH_LABEL: label, | + /// | OTP_AUTH_ISSUER: issuer, | + /// | CONTAINER_SERIAL: containerSerial, (optional) | + /// | CHECKED_CONTAINERS: checkedContainers, | + /// | TOKEN_ID: id, | + /// | OTP_AUTH_TYPE: type, | + /// | OTP_AUTH_IMAGE: tokenImage, (optional) | + /// | SORTABLE_INDEX: sortIndex, (optional) | + /// | FOLDER_ID: folderId, (optional) | + /// | TOKEN_ORIGIN: origin, (optional) | + /// | OTP_AUTH_PIN: pin, | + /// | TOKEN_HIDDEN: isHidden, | + /// ------------------------------------------------------------------- + /// ------------------------- [OTPToken] ------------------------------ + /// | OTP_AUTH_ALGORITHM: algorithm, | + /// | OTP_AUTH_DIGITS: digits, | + /// | OTP_AUTH_SECRET_BASE32: secret, | + /// | OTP_AUTH_OTP_VALUES: [otpValue, nextValue], (if serial is null) | + /// ------------------------------------------------------------------- + /// ------------------------ [HOTPToken] ------------------------------ + /// | OTP_AUTH_COUNTER: counter, | + /// ------------------------------------------------------------------- /// ``` @override - Map toOtpAuthMap({String? containerSerial}) { + Map toOtpAuthMap() { return super.toOtpAuthMap() ..addAll({ OTP_AUTH_COUNTER: counter.toString(), diff --git a/lib/model/tokens/hotp_token.g.dart b/lib/model/tokens/hotp_token.g.dart index bc83a0cda..b8499cabe 100644 --- a/lib/model/tokens/hotp_token.g.dart +++ b/lib/model/tokens/hotp_token.g.dart @@ -9,6 +9,10 @@ part of 'hotp_token.dart'; HOTPToken _$HOTPTokenFromJson(Map json) => HOTPToken( counter: (json['counter'] as num?)?.toInt() ?? 0, containerSerial: json['containerSerial'] as String?, + checkedContainers: (json['checkedContainers'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], id: json['id'] as String, algorithm: $enumDecode(_$AlgorithmsEnumMap, json['algorithm']), digits: (json['digits'] as num).toInt(), @@ -29,9 +33,10 @@ HOTPToken _$HOTPTokenFromJson(Map json) => HOTPToken( ); Map _$HOTPTokenToJson(HOTPToken instance) => { - 'containerSerial': instance.containerSerial, + 'checkedContainers': instance.checkedContainers, 'label': instance.label, 'issuer': instance.issuer, + 'containerSerial': instance.containerSerial, 'id': instance.id, 'serial': instance.serial, 'pin': instance.pin, diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index 99b5f4794..981f60e0f 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -17,10 +17,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import 'dart:convert'; + import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; import '../enums/algorithms.dart'; +import '../token_template.dart'; import '../token_import/token_origin_data.dart'; +import 'container_credentials.dart'; import 'token.dart'; abstract class OTPToken extends Token { @@ -91,26 +95,48 @@ abstract class OTPToken extends Token { /// This is used to create a map that typically was created from a uri. /// ```dart - /// ------------------------- [Token] ------------------------- - /// | OTP_AUTH_SERIAL: serial, (optional) | - /// | OTP_AUTH_TYPE: type, | - /// | OTP_AUTH_LABEL: label, | - /// | OTP_AUTH_ISSUER: issuer, | - /// | OTP_AUTH_PIN: pin, | - /// | OTP_AUTH_IMAGE: tokenImage, (optional) | - /// ----------------------------------------------------------- - /// ------------------------ [OTPToken] ----------------------- - /// | OTP_AUTH_ALGORITHM: algorithm, | - /// | OTP_AUTH_DIGITS: digits, | - /// ----------------------------------------------------------- + /// -------------------------- [Token] ------------------------------- + /// | OTP_AUTH_SERIAL: serial, (optional) | + /// | OTP_AUTH_LABEL: label, | + /// | OTP_AUTH_ISSUER: issuer, | + /// | CONTAINER_SERIAL: containerSerial, (optional) | + /// | CHECKED_CONTAINERS: checkedContainers, | + /// | TOKEN_ID: id, | + /// | OTP_AUTH_TYPE: type, | + /// | OTP_AUTH_IMAGE: tokenImage, (optional) | + /// | SORTABLE_INDEX: sortIndex, (optional) | + /// | FOLDER_ID: folderId, (optional) | + /// | TOKEN_ORIGIN: origin, (optional) | + /// | OTP_AUTH_PIN: pin, | + /// | TOKEN_HIDDEN: isHidden, | + /// ------------------------------------------------------------------- + /// ------------------------- [OTPToken] ------------------------------ + /// | OTP_AUTH_ALGORITHM: algorithm, | + /// | OTP_AUTH_DIGITS: digits, | + /// | OTP_AUTH_SECRET_BASE32: secret, | + /// | OTP_AUTH_OTP_VALUES: [otpValue, nextValue], (if serial is null) | + /// ------------------------------------------------------------------- /// ``` @override - Map toOtpAuthMap({String? containerSerial}) { + Map toOtpAuthMap() { + Logger.debug('$OTP_AUTH_OTP_VALUES ${jsonEncode([otpValue, nextValue])}'); return super.toOtpAuthMap() ..addAll({ OTP_AUTH_ALGORITHM: algorithm.name, OTP_AUTH_DIGITS: digits.toString(), - if (serial == null && !checkedContainers.contains(containerSerial)) OTP_AUTH_OTP_VALUES: '[$otpValue, $nextValue]', + OTP_AUTH_SECRET_BASE32: secret, + if (serial == null) OTP_AUTH_OTP_VALUES: [otpValue, nextValue], }); } + + @override + TokenTemplate toTemplate({ContainerCredential? container}) => + super.toTemplate(container: container) ?? + TokenTemplate.withOtps( + otpAuthMap: toOtpAuthMap(), + otps: [otpValue, nextValue], + container: container, + checkedContainers: checkedContainers, + additionalData: additionalData, + ); } diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index de2e9c4e5..0399a68b7 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -19,7 +19,7 @@ */ import 'package:json_annotation/json_annotation.dart'; import 'package:pointycastle/asymmetric/api.dart'; -import 'package:privacyidea_authenticator/model/token_container.dart'; +import 'package:privacyidea_authenticator/model/token_template.dart'; import 'package:uuid/uuid.dart'; import '../../utils/custom_int_buffer.dart'; @@ -30,6 +30,7 @@ import '../../utils/type_matchers.dart'; import '../enums/push_token_rollout_state.dart'; import '../enums/token_types.dart'; import '../token_import/token_origin_data.dart'; +import 'container_credentials.dart'; import 'token.dart'; part 'push_token.g.dart'; @@ -37,6 +38,9 @@ part 'push_token.g.dart'; @JsonSerializable() class PushToken extends Token { static RsaUtils rsaParser = const RsaUtils(); + // ignore: constant_identifier_names + static const String EXPIRATION_DATE = 'expirationDate'; + final DateTime? expirationDate; @override String get serial => super.serial!; @@ -82,6 +86,7 @@ class PushToken extends Token { bool? isRolledOut, bool? sslVerify, PushTokenRollOutState? rolloutState, + String? type, super.tokenImage, super.sortIndex, super.folderId, @@ -92,7 +97,7 @@ class PushToken extends Token { }) : isRolledOut = isRolledOut ?? false, sslVerify = sslVerify ?? false, rolloutState = rolloutState ?? PushTokenRollOutState.rolloutNotStarted, - super(type: TokenTypes.PIPUSH.name, serial: serial); + super(type: type ?? TokenTypes.PIPUSH.name, serial: serial); @override bool sameValuesAs(Token other) { @@ -191,7 +196,7 @@ class PushToken extends Token { 'publicTokenKey: $publicTokenKey}'; } - factory PushToken.fromOtpAuthMap(Map otpAuthMap, {TokenOriginData? origin}) { + factory PushToken.fromOtpAuthMap(Map otpAuthMap, {required Map additionalData}) { // Validate map for Push token final validatedMap = validateMap( map: otpAuthMap, @@ -212,20 +217,30 @@ class PushToken extends Token { }, name: 'PushToken', ); + final validatedAdditionalData = Token.validateAdditionalData(additionalData); + final expirationDate = validateOptional( + value: additionalData[EXPIRATION_DATE], + validator: const TypeValidatorOptional(), + name: 'PushToken#expirationDate', + ); return switch (validatedMap[OTP_AUTH_VERSION]) { '1' => PushToken( - id: const Uuid().v4(), label: validatedMap[OTP_AUTH_LABEL] as String, issuer: validatedMap[OTP_AUTH_ISSUER] as String, serial: validatedMap[OTP_AUTH_SERIAL] as String, sslVerify: validatedMap[OTP_AUTH_PUSH_SSL_VERIFY] as bool, - expirationDate: DateTime.now().add(validatedMap[OTP_AUTH_PUSH_TTL_MINUTES] as Duration), + expirationDate: expirationDate ?? DateTime.now().add(validatedMap[OTP_AUTH_PUSH_TTL_MINUTES] as Duration), enrollmentCredentials: validatedMap[OTP_AUTH_PUSH_ENROLLMENT_CREDENTIAL] as String?, url: validatedMap[OTP_AUTH_PUSH_ROLLOUT_URL] as Uri, tokenImage: validatedMap[OTP_AUTH_IMAGE] as String?, pin: validatedMap[OTP_AUTH_PIN] as bool?, isLocked: validatedMap[OTP_AUTH_PIN] as bool?, - origin: origin, + id: validatedAdditionalData[Token.ID] ?? const Uuid().v4(), + origin: validatedAdditionalData[Token.ORIGIN], + isHidden: validatedAdditionalData[Token.HIDDEN], + checkedContainers: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], + folderId: validatedAdditionalData[Token.FOLDER_ID], + sortIndex: validatedAdditionalData[Token.SORT_INDEX], ), _ => throw LocalizedArgumentError( localizedMessage: (localizations, value, name) => localizations.unsupported(value, name), @@ -239,7 +254,7 @@ class PushToken extends Token { @override Token copyUpdateByTemplate(TokenTemplate template) { final uriMap = validateMap( - map: template.data, + map: template.otpAuthMap, validators: { OTP_AUTH_LABEL: const TypeValidatorOptional(), OTP_AUTH_ISSUER: const TypeValidatorOptional(), @@ -256,6 +271,7 @@ class PushToken extends Token { }, name: 'PushToken', ); + return copyWith( label: uriMap[OTP_AUTH_LABEL] as String?, issuer: uriMap[OTP_AUTH_ISSUER] as String?, @@ -291,7 +307,7 @@ class PushToken extends Token { /// ------------------------------------------------------------------ /// ``` @override - Map toOtpAuthMap({String? containerSerial}) { + Map toOtpAuthMap() { return super.toOtpAuthMap() ..addAll({ OTP_AUTH_PUSH_SSL_VERIFY: sslVerify ? OTP_AUTH_PUSH_SSL_VERIFY_TRUE : OTP_AUTH_PUSH_SSL_VERIFY_FALSE, @@ -304,6 +320,13 @@ class PushToken extends Token { }); } + @override + TokenTemplate? toTemplate({ContainerCredential? container}) => expirationDate != null + ? super.toTemplate(container: container) + : super.toTemplate(container: container)?.withAditionalData({ + EXPIRATION_DATE: expirationDate!, + }); + factory PushToken.fromJson(Map json) { final newToken = _$PushTokenFromJson(json); final currentRolloutState = switch (newToken.rolloutState) { diff --git a/lib/model/tokens/push_token.g.dart b/lib/model/tokens/push_token.g.dart index 52f8d0b8e..3453c02d5 100644 --- a/lib/model/tokens/push_token.g.dart +++ b/lib/model/tokens/push_token.g.dart @@ -11,6 +11,10 @@ PushToken _$PushTokenFromJson(Map json) => PushToken( label: json['label'] as String? ?? '', issuer: json['issuer'] as String? ?? '', containerSerial: json['containerSerial'] as String?, + checkedContainers: (json['checkedContainers'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], id: json['id'] as String, fbToken: json['fbToken'] as String?, url: json['url'] == null ? null : Uri.parse(json['url'] as String), @@ -25,6 +29,7 @@ PushToken _$PushTokenFromJson(Map json) => PushToken( sslVerify: json['sslVerify'] as bool?, rolloutState: $enumDecodeNullable( _$PushTokenRollOutStateEnumMap, json['rolloutState']), + type: json['type'] as String?, tokenImage: json['tokenImage'] as String?, sortIndex: (json['sortIndex'] as num?)?.toInt(), folderId: (json['folderId'] as num?)?.toInt(), @@ -37,9 +42,10 @@ PushToken _$PushTokenFromJson(Map json) => PushToken( ); Map _$PushTokenToJson(PushToken instance) => { - 'containerSerial': instance.containerSerial, + 'checkedContainers': instance.checkedContainers, 'label': instance.label, 'issuer': instance.issuer, + 'containerSerial': instance.containerSerial, 'id': instance.id, 'pin': instance.pin, 'isLocked': instance.isLocked, @@ -48,6 +54,7 @@ Map _$PushTokenToJson(PushToken instance) => { 'folderId': instance.folderId, 'sortIndex': instance.sortIndex, 'origin': instance.origin, + 'type': instance.type, 'expirationDate': instance.expirationDate?.toIso8601String(), 'serial': instance.serial, 'fbToken': instance.fbToken, diff --git a/lib/model/tokens/steam_token.dart b/lib/model/tokens/steam_token.dart index 600d69215..c6505e9c0 100644 --- a/lib/model/tokens/steam_token.dart +++ b/lib/model/tokens/steam_token.dart @@ -20,7 +20,7 @@ import 'package:base32/base32.dart'; import 'package:crypto/crypto.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:privacyidea_authenticator/model/token_container.dart'; +import 'package:privacyidea_authenticator/model/token_template.dart'; import 'package:uuid/uuid.dart'; import '../../utils/identifiers.dart'; @@ -139,7 +139,7 @@ class SteamToken extends TOTPToken { @override SteamToken copyUpdateByTemplate(TokenTemplate template) { final uriMap = validateMap( - map: template.data, + map: template.otpAuthMap, validators: { OTP_AUTH_LABEL: const TypeValidatorOptional(), OTP_AUTH_ISSUER: const TypeValidatorOptional(), @@ -161,7 +161,7 @@ class SteamToken extends TOTPToken { ); } - static SteamToken fromOtpAuthMap(Map uriMap, {required TokenOriginData origin}) { + static SteamToken fromOtpAuthMap(Map uriMap, {required Map additionalData}) { uriMap = validateMap( map: uriMap, validators: { @@ -172,18 +172,24 @@ class SteamToken extends TOTPToken { OTP_AUTH_IMAGE: const TypeValidatorOptional(), OTP_AUTH_PIN: stringToBoolValidatorOptional, }, - name: 'SteamToken', + name: 'SteamToken#otpAuthMap', ); + final validatedAdditionalData = Token.validateAdditionalData(additionalData); return SteamToken( label: uriMap[OTP_AUTH_LABEL], issuer: uriMap[OTP_AUTH_ISSUER], - id: const Uuid().v4(), serial: uriMap[OTP_AUTH_SERIAL], secret: uriMap[OTP_AUTH_SECRET_BASE32], tokenImage: uriMap[OTP_AUTH_IMAGE], pin: uriMap[OTP_AUTH_PIN], isLocked: uriMap[OTP_AUTH_PIN], - origin: origin, + id: validatedAdditionalData[Token.ID] ?? const Uuid().v4(), + containerSerial: validatedAdditionalData[Token.CONTAINER_SERIAL], + checkedContainers: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], + sortIndex: validatedAdditionalData[Token.SORT_INDEX], + folderId: validatedAdditionalData[Token.FOLDER_ID], + origin: validatedAdditionalData[Token.ORIGIN], + isHidden: validatedAdditionalData[Token.HIDDEN], ); } @@ -209,7 +215,7 @@ class SteamToken extends TOTPToken { /// ----------------------------------------------------------- /// ``` @override - Map toOtpAuthMap({String? containerSerial}) => super.toOtpAuthMap(); + Map toOtpAuthMap() => super.toOtpAuthMap(); static SteamToken fromJson(Map json) => _$SteamTokenFromJson(json); @override diff --git a/lib/model/tokens/steam_token.g.dart b/lib/model/tokens/steam_token.g.dart index 76487e26d..7398f992a 100644 --- a/lib/model/tokens/steam_token.g.dart +++ b/lib/model/tokens/steam_token.g.dart @@ -9,8 +9,12 @@ part of 'steam_token.dart'; SteamToken _$SteamTokenFromJson(Map json) => SteamToken( id: json['id'] as String, secret: json['secret'] as String, - containerSerial: json['containerSerial'] as String?, serial: json['serial'] as String?, + containerSerial: json['containerSerial'] as String?, + checkedContainers: (json['checkedContainers'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], type: json['type'] as String?, tokenImage: json['tokenImage'] as String?, pin: json['pin'] as bool?, @@ -27,9 +31,10 @@ SteamToken _$SteamTokenFromJson(Map json) => SteamToken( Map _$SteamTokenToJson(SteamToken instance) => { - 'containerSerial': instance.containerSerial, + 'checkedContainers': instance.checkedContainers, 'label': instance.label, 'issuer': instance.issuer, + 'containerSerial': instance.containerSerial, 'id': instance.id, 'serial': instance.serial, 'pin': instance.pin, diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index 0a1006eba..dd33fb928 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -1,3 +1,5 @@ +// ignore_for_file: constant_identifier_names + /* * privacyIDEA Authenticator * @@ -18,7 +20,8 @@ * limitations under the License. */ import 'package:flutter/material.dart'; -import '../token_container.dart'; +import 'package:privacyidea_authenticator/model/tokens/container_credentials.dart'; +import '../token_template.dart'; import '../../utils/identifiers.dart'; import '../enums/token_types.dart'; @@ -33,12 +36,20 @@ import 'totp_token.dart'; @immutable abstract class Token with SortableMixin { + static const CONTAINER_SERIAL = 'containerSerial'; + static const ID = 'id'; + static const ORIGIN = 'origin'; + static const HIDDEN = 'hidden'; + static const CHECKED_CONTAINERS = 'checkedContainers'; + static const FOLDER_ID = 'folderId'; + static const SORT_INDEX = SortableMixin.SORT_INDEX; + bool? get isPrivacyIdeaToken => origin?.isPrivacyIdeaToken; final String tokenVersion = 'v1.0.0'; // The version of this token, this is used for serialization. - final String? containerSerial; // The serial of the container this token belongs to. final List checkedContainers; // The serials of the containers this token should not be in. final String label; // the name of the token, it cannot be uses as an identifier final String issuer; // The issuer of this token, currently unused. + final String? containerSerial; // The serial of the container this token belongs to. final String id; // this is the identifier of the token final String? serial; // The serial of the token, this is used to identify the token in the privacyIDEA server. final bool pin; @@ -66,17 +77,33 @@ abstract class Token with SortableMixin { } /// Creates a token from a uri map. - factory Token.fromOtpAuthMap(Map otpAuthMap, {required TokenOriginData origin}) { + factory Token.fromOtpAuthMap(Map otpAuthMap, {required Map additionalData}) { String? type = otpAuthMap[OTP_AUTH_TYPE]; if (type == null) throw ArgumentError.value(otpAuthMap, 'Token#fromUriMap', 'Token type is not defined in the uri map'); - if (TokenTypes.HOTP.isName(type, caseSensitive: false)) return HOTPToken.fromOtpAuthMap(otpAuthMap, origin: origin); - if (TokenTypes.TOTP.isName(type, caseSensitive: false)) return TOTPToken.fromOtpAuthMap(otpAuthMap, origin: origin); - if (TokenTypes.PIPUSH.isName(type, caseSensitive: false)) return PushToken.fromOtpAuthMap(otpAuthMap, origin: origin); - if (TokenTypes.DAYPASSWORD.isName(type, caseSensitive: false)) return DayPasswordToken.fromOtpAuthMap(otpAuthMap, origin: origin); - if (TokenTypes.STEAM.isName(type, caseSensitive: false)) return SteamToken.fromOtpAuthMap(otpAuthMap, origin: origin); + if (TokenTypes.HOTP.isName(type, caseSensitive: false)) return HOTPToken.fromOtpAuthMap(otpAuthMap, additionalData: additionalData); + if (TokenTypes.TOTP.isName(type, caseSensitive: false)) return TOTPToken.fromOtpAuthMap(otpAuthMap, additionalData: additionalData); + if (TokenTypes.PIPUSH.isName(type, caseSensitive: false)) return PushToken.fromOtpAuthMap(otpAuthMap, additionalData: additionalData); + if (TokenTypes.DAYPASSWORD.isName(type, caseSensitive: false)) { + return DayPasswordToken.fromOtpAuthMap(otpAuthMap, additionalData: additionalData); + } + if (TokenTypes.STEAM.isName(type, caseSensitive: false)) return SteamToken.fromOtpAuthMap(otpAuthMap, additionalData: additionalData); throw ArgumentError.value(otpAuthMap, 'Token#fromUriMap', 'Token type [$type] is not a supported'); } + static Map validateAdditionalData(Map additionalData) => validateMap( + map: additionalData, + validators: { + Token.CONTAINER_SERIAL: const TypeValidatorOptional(), + Token.ID: const TypeValidatorOptional(), + Token.ORIGIN: const TypeValidatorOptional(), + Token.HIDDEN: const TypeValidatorOptional(), + Token.CHECKED_CONTAINERS: const TypeValidatorOptional>(), + Token.FOLDER_ID: const TypeValidatorOptional(), + Token.SORT_INDEX: const TypeValidatorOptional(), + }, + name: 'Token#validateAdditionalData', + ); + const Token({ this.serial, this.label = '', @@ -143,7 +170,8 @@ abstract class Token with SortableMixin { 'type: $type, ' 'sortIndex: $sortIndex, ' 'folderId: $folderId, ' - 'origin: $origin, '; + 'origin: $origin, ' + 'containerSerial: $containerSerial, '; } /// This is used to create a map that can be used to serialize the token. @@ -159,10 +187,10 @@ abstract class Token with SortableMixin { /// | OTP_AUTH_PIN: pin, | /// | OTP_AUTH_IMAGE: tokenImage, (optional) | /// ----------------------------------------------------------- + /// /// ``` - Map toOtpAuthMap({String? containerSerial}) { + Map toOtpAuthMap() { return { - if (containerSerial != null) CONTAINER_SERIAL: containerSerial, if (serial != null) OTP_AUTH_SERIAL: serial!, OTP_AUTH_TYPE: type, OTP_AUTH_LABEL: label, @@ -174,5 +202,21 @@ abstract class Token with SortableMixin { Token copyUpdateByTemplate(TokenTemplate template); - TokenTemplate toTemplate() => TokenTemplate(data: toOtpAuthMap()); + Map get additionalData => { + ID: id, + ORIGIN: origin?.data, + SORT_INDEX: sortIndex, + FOLDER_ID: folderId, + HIDDEN: isHidden, + CHECKED_CONTAINERS: checkedContainers, + }; + + TokenTemplate? toTemplate({ContainerCredential? container}) => serial != null + ? TokenTemplate.withSerial( + otpAuthMap: toOtpAuthMap(), + additionalData: additionalData, + serial: serial!, + container: container, + ) + : null; } diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index b116530a9..c1e197d22 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -26,7 +26,7 @@ import '../../utils/type_matchers.dart'; import '../enums/algorithms.dart'; import '../enums/token_types.dart'; import '../extensions/enums/algorithms_extension.dart'; -import '../token_container.dart'; +import '../token_template.dart'; import '../token_import/token_origin_data.dart'; import 'otp_token.dart'; import 'token.dart'; @@ -133,7 +133,7 @@ class TOTPToken extends OTPToken { @override TOTPToken copyUpdateByTemplate(TokenTemplate template) { final uriMap = validateMap( - map: template.data, + map: template.otpAuthMap, validators: { OTP_AUTH_LABEL: const TypeValidatorOptional(), OTP_AUTH_ISSUER: const TypeValidatorOptional(), @@ -166,7 +166,7 @@ class TOTPToken extends OTPToken { return 'T${super.toString()}period: $period}'; } - factory TOTPToken.fromOtpAuthMap(Map otpAuthMap, {required TokenOriginData origin}) { + factory TOTPToken.fromOtpAuthMap(Map otpAuthMap, {required Map additionalData}) { final validatedMap = validateMap( map: otpAuthMap, validators: { @@ -180,12 +180,12 @@ class TOTPToken extends OTPToken { OTP_AUTH_IMAGE: const TypeValidatorOptional(), OTP_AUTH_PIN: stringToBoolValidatorOptional, }, - name: 'TOTPToken', + name: 'TOTPToken#otpAuthMap', ); + final validatedAdditionalData = Token.validateAdditionalData(additionalData); return TOTPToken( label: validatedMap[OTP_AUTH_LABEL] as String, issuer: validatedMap[OTP_AUTH_ISSUER] as String, - id: const Uuid().v4(), serial: validatedMap[OTP_AUTH_SERIAL] as String?, algorithm: validatedMap[OTP_AUTH_ALGORITHM] as Algorithms, digits: validatedMap[OTP_AUTH_DIGITS] as int, @@ -194,7 +194,13 @@ class TOTPToken extends OTPToken { tokenImage: validatedMap[OTP_AUTH_IMAGE] as String?, pin: validatedMap[OTP_AUTH_PIN] as bool?, isLocked: validatedMap[OTP_AUTH_PIN] as bool?, - origin: origin, + id: validatedAdditionalData[Token.ID] ?? const Uuid().v4(), + containerSerial: validatedAdditionalData[Token.CONTAINER_SERIAL], + checkedContainers: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], + sortIndex: validatedAdditionalData[Token.SORT_INDEX], + folderId: validatedAdditionalData[Token.FOLDER_ID], + origin: validatedAdditionalData[Token.ORIGIN], + isHidden: validatedAdditionalData[Token.HIDDEN], ); } @@ -217,7 +223,7 @@ class TOTPToken extends OTPToken { /// ----------------------------------------------------------- /// ``` @override - Map toOtpAuthMap({String? containerSerial}) { + Map toOtpAuthMap() { return super.toOtpAuthMap() ..addAll({ OTP_AUTH_COUNTER: period.toString(), diff --git a/lib/model/tokens/totp_token.g.dart b/lib/model/tokens/totp_token.g.dart index 9af80575f..49c2f6289 100644 --- a/lib/model/tokens/totp_token.g.dart +++ b/lib/model/tokens/totp_token.g.dart @@ -12,8 +12,12 @@ TOTPToken _$TOTPTokenFromJson(Map json) => TOTPToken( algorithm: $enumDecode(_$AlgorithmsEnumMap, json['algorithm']), digits: (json['digits'] as num).toInt(), secret: json['secret'] as String, - containerSerial: json['containerSerial'] as String?, serial: json['serial'] as String?, + containerSerial: json['containerSerial'] as String?, + checkedContainers: (json['checkedContainers'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], type: json['type'] as String?, tokenImage: json['tokenImage'] as String?, pin: json['pin'] as bool?, @@ -29,9 +33,10 @@ TOTPToken _$TOTPTokenFromJson(Map json) => TOTPToken( ); Map _$TOTPTokenToJson(TOTPToken instance) => { - 'containerSerial': instance.containerSerial, + 'checkedContainers': instance.checkedContainers, 'label': instance.label, 'issuer': instance.issuer, + 'containerSerial': instance.containerSerial, 'id': instance.id, 'serial': instance.serial, 'pin': instance.pin, 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 511e9d559..c032d2d02 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 @@ -55,19 +55,13 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { } Logger.info('Try to handle otpAuth:', name: 'token_notifier.dart#addTokenFromOtpAuth'); // The values from queryParameters are always strings. - Map queryParameters = uri.queryParameters; - - queryParameters = validateMap( - map: queryParameters, - validators: {OTP_AUTH_SECRET_BASE32: const TypeValidatorRequired()}, - name: 'queryParameters', - ); + Map queryParameters = {...uri.queryParameters}; final (label, issuer) = _parseLabelAndIssuer(uri); queryParameters[OTP_AUTH_LABEL] = label; queryParameters[OTP_AUTH_ISSUER] = issuer; queryParameters[OTP_AUTH_TYPE] = _parseTokenType(uri); - queryParameters[OTP_AUTH_SECRET_BASE32] = _secretPadding(queryParameters[OTP_AUTH_SECRET_BASE32]!); + queryParameters = _secretAddPadding(queryParameters); _logInfo(uri); final twoStepSecretFuture = _parse2StepSecret(uri); @@ -84,7 +78,7 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { // Update the secret with the two step secret. queryParameters[OTP_AUTH_SECRET_BASE32] = twoStepSecretString; } - final newToken = Token.fromOtpAuthMap(queryParameters, origin: _parseCreatorToOrigin(uri)); + final newToken = Token.fromOtpAuthMap(queryParameters, additionalData: {Token.ORIGIN: _parseCreatorToOrigin(uri)}); return [ ProcessorResultSuccess( newToken, @@ -220,7 +214,11 @@ void _logInfo(Uri uri) { // // According to https://github.com/google/google-authenticator/wiki/Key-Uri-Format, // the padding can be omitted, but the libraries for base32 do not allow this. -String _secretPadding(String secret) => '$secret${secret.length % 2 == 1 ? '=' : ''}'; +Map _secretAddPadding(Map queryParameters) { + if (queryParameters[OTP_AUTH_SECRET_BASE32] == null) return queryParameters; + final secret = queryParameters[OTP_AUTH_SECRET_BASE32]!; + return {...queryParameters}..addAll({OTP_AUTH_SECRET_BASE32: '$secret${secret.length % 2 == 1 ? '=' : ''}'}); +} String _parseTokenType(Uri uri) { if (_parseIssuer(uri) == "Steam") return TokenTypes.STEAM.name; 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 ea191c228..8d26bcd78 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 @@ -213,14 +213,13 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { name: 'aegisV2Entry', ); - final token = Token.fromOtpAuthMap( - otpAuthMap, - origin: TokenOriginSourceType.backupFile.toTokenOrigin( + final token = Token.fromOtpAuthMap(otpAuthMap, additionalData: { + Token.ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( originName: TokenImportOrigins.aegisAuthenticator.appName, isPrivacyIdeaToken: false, data: jsonEncode(entry), ), - ); + }); results.add(ProcessorResult.success( token.copyWith(id: entry[AEGIS_ENTRY_ID]), resultHandlerType: resultHandlerType, @@ -276,11 +275,13 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { results.add(ProcessorResult.success( Token.fromOtpAuthMap( otpAuthMap, - origin: TokenOriginSourceType.backupFile.toTokenOrigin( - originName: TokenImportOrigins.aegisAuthenticator.appName, - isPrivacyIdeaToken: false, - data: jsonEncode(entry), - ), + additionalData: { + Token.ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( + originName: TokenImportOrigins.aegisAuthenticator.appName, + isPrivacyIdeaToken: false, + data: jsonEncode(entry), + ), + }, ), resultHandlerType: resultHandlerType, )); 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 7aab30285..4c896b5a0 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 @@ -347,11 +347,13 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { final token = Token.fromOtpAuthMap( otpAuthMap, - origin: TokenOriginSourceType.backupFile.toTokenOrigin( - originName: TokenImportOrigins.authenticatorPro.appName, - isPrivacyIdeaToken: false, - data: jsonEncode(tokenMap), - ), + additionalData: { + Token.ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( + originName: TokenImportOrigins.authenticatorPro.appName, + isPrivacyIdeaToken: false, + data: jsonEncode(tokenMap), + ), + }, ); result.add(ProcessorResultSuccess( token, diff --git a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart index d0eb76829..261572c04 100644 --- a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart @@ -129,11 +129,13 @@ class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { return ProcessorResultSuccess( Token.fromOtpAuthMap( _jsonToOtpAuth(tokenJson), - origin: TokenOriginSourceType.backupFile.toTokenOrigin( - originName: TokenImportOrigins.freeOtpPlus.appName, - isPrivacyIdeaToken: false, - data: jsonEncode(tokenJson), - ), + additionalData: { + Token.ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( + originName: TokenImportOrigins.freeOtpPlus.appName, + isPrivacyIdeaToken: false, + data: jsonEncode(tokenJson), + ), + }, ), resultHandlerType: resultHandlerType, ); diff --git a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart index cf27dc7ce..499d1ae4e 100644 --- a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart +++ b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart @@ -30,9 +30,7 @@ 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/globals.dart'; import '../../utils/logger.dart'; -import '../../utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart'; import '../../utils/token_import_origins.dart'; import '../scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart'; import 'token_import_file_processor_interface.dart'; 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 096afed34..71afd82c7 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 @@ -149,11 +149,13 @@ class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { results.add(ProcessorResult.success( Token.fromOtpAuthMap( _twoFasToOtpAuth(twoFasToken), - origin: TokenOriginSourceType.backupFile.toTokenOrigin( - originName: TokenImportOrigins.twoFasAuthenticator.appName, - isPrivacyIdeaToken: false, - data: jsonEncode(twoFasToken), - ), + additionalData: { + Token.ORIGIN: TokenOriginSourceType.backupFile.toTokenOrigin( + originName: TokenImportOrigins.twoFasAuthenticator.appName, + isPrivacyIdeaToken: false, + data: jsonEncode(twoFasToken), + ), + }, ), resultHandlerType: resultHandlerType, )); diff --git a/lib/repo/secure_token_repository.dart b/lib/repo/secure_token_repository.dart index 8f8be505e..18074fbeb 100644 --- a/lib/repo/secure_token_repository.dart +++ b/lib/repo/secure_token_repository.dart @@ -100,11 +100,18 @@ class SecureTokenRepository implements TokenRepository { valueJson = jsonDecode(value); } on FormatException catch (_) { // Value should be a json. Skip everything that is not a json. + Logger.debug('Value is not a json', name: 'secure_token_repository.dart#loadTokens'); continue; } - if (valueJson == null || !valueJson.containsKey('type')) { + if (valueJson == null) { // If valueJson is null or does not contain a type, it can't be a token. Skip it. + Logger.debug('Value Json is null', name: 'secure_token_repository.dart#loadTokens'); + continue; + } + if (!valueJson.containsKey('type')) { + // If valueJson is null or does not contain a type, it can't be a token. Skip it. + Logger.debug('Value Json does not contain a type', name: 'secure_token_repository.dart#loadTokens'); continue; } diff --git a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart index 4b1908178..8d2acb345 100644 --- a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart +++ b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart @@ -1,190 +1,190 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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 '../../interfaces/repo/container_repository.dart'; -import '../../model/token_container.dart'; -import '../../utils/errors.dart'; -import '../../utils/logger.dart'; - -class HybridTokenContainerRepository - implements TokenContainerRepository { - final LocalRepo _localRepository; - final RemoteRepo _remoteRepository; - - HybridTokenContainerRepository({ - required LocalRepo localRepository, - required RemoteRepo remoteRepository, - }) : _localRepository = localRepository, - _remoteRepository = remoteRepository; - - @override - Future loadContainerState({bool isInitial = false}) async { - Logger.warning('Loading container state', name: 'HybridTokenContainerRepository'); - TokenContainer localState; - TokenContainer newState; - - try { - localState = await _localRepository.loadContainerState(); - } catch (e) { - Logger.warning('Failed to load local container state'); - return TokenContainer.error( - error: LocalizedException( - unlocalizedMessage: 'Failed to load local container state', - localizedMessage: (localization) => localization.failedToLoad('local container state'), - ), - ); - } - try { - newState = await _remoteRepository.saveContainerState(localState); - } catch (e) { - newState = localState.copyTransformInto(); - } - - try { - await _localRepository.saveContainerState(newState); - } catch (e) { - Logger.error('Failed to save synced state to local repository', name: 'HybridTokenContainerRepository', error: e); - } - - return newState; - } - - @override - Future saveContainerState(TokenContainer currentState) async { - if (currentState is TokenContainerError) { - Logger.warning('Cannot save error state to repository'); - return currentState; - } - TokenContainer newState; - - try { - newState = await _remoteRepository.saveContainerState(currentState); - } catch (e, s) { - Logger.warning( - 'Failed to save state to remote repository: Changed to unsynced state', - name: 'HybridTokenContainerRepository#saveContainerState', - error: e, - stackTrace: s, - ); - newState = currentState.copyTransformInto(); - return _localRepository.saveContainerState(newState); - } - - try { - newState = await _localRepository.saveContainerState(newState); - } catch (e) { - Logger.error('Failed to save state to local repository'); - return newState; - } - return newState; - } - - // Future _merge({ - // required TokenContainer localState, - // required TokenContainer remoteState, - // }) async { - // List localTemplates; - // List remoteTemplates; - // if (localState is TokenContainerUninitialized) { - // // Uninitialized state is always overwritten by other states - // localTemplates = []; - // } else { - // localTemplates = localState.tokenTemplates; - // } - // if (remoteState is TokenContainerUninitialized) { - // // Uninitialized state is always overwritten by other states - // remoteTemplates = []; - // } else { - // remoteTemplates = remoteState.tokenTemplates ?? []; - // } - - // final mergedTemplates = await _mergeTemplateLists( - // localTemplates: localTemplates, - // remoteTemplates: remoteTemplates, - // ); - - // final newSyncedState = TokenContainerSynced( - // lastSyncedAt: DateTime.now(), - // containerId: localState.containerId, - // description: localState.description, - // type: '', // TODO: Implement type - // tokenTemplates: mergedTemplates, - // ); - // return newSyncedState; - // } - - // Future> _mergeTemplateLists({ - // required List localTemplates, - // required List remoteTemplates, - // }) async { - // final mergedTemplates = []; - - // // Add all remaining local templates - // for (var localTemplate in localTemplates) { - // final remoteTemplate = remoteTemplates.firstWhereOrNull((template) => template.id == localTemplate.id); - // if (remoteTemplate == null) { - // mergedTemplates.add(localTemplate); - // continue; - // } - // mergedTemplates.add(await _mergeTemplates(localTemplate, remoteTemplate)); - // } - - // // Add all remaining remote templates - // mergedTemplates.addAll(remoteTemplates); - - // return mergedTemplates; - // } - - /// Merges local and remote token templates with the last synced state - /// If both local and remote templates have changed, the remote changes are prioritized - // Future _mergeTemplates(TokenTemplate local, TokenTemplate remote) async { - // assert(local.id == remote.id, 'Both templates must have the same id'); - // final mergedData = {}; - - // print('------------------------------------------------------------------'); - // print('Local: ${local.data}'); - // mergedData.addAll(local.data); - // print('MergedData: $mergedData'); - // print('------------------------------------------------------------------'); - // print('Remote: ${remote.data}'); - // mergedData.addAll(remote.data); - // print('MergedData: $mergedData'); - // print('------------------------------------------------------------------'); - - // return TokenTemplate(data: mergedData); - // } - - // @override - // Future loadTokenTemplate(String tokenTemplateId) async { - // final state = await loadContainerState(); - // final template = state.tokenTemplates.firstWhereOrNull((template) => template.id == tokenTemplateId); - // return template; - // } - - // @override - // Future saveTokenTemplate(TokenTemplate tokenTemplate) async { - // final state = await loadContainerState(); - // final templates = state.tokenTemplates; - // templates.removeWhere((template) => template.id == tokenTemplate.id); - // templates.add(tokenTemplate); - // final newState = state.copyWith(tokenTemplates: templates); - // final savedState = await saveContainerState(newState); - // return savedState.tokenTemplates.firstWhere((template) => template.id == tokenTemplate.id); - // } -} +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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 '../../interfaces/repo/container_repository.dart'; +// import '../../model/token_container.dart'; +// import '../../utils/errors.dart'; +// import '../../utils/logger.dart'; + +// class HybridTokenContainerRepository +// implements TokenContainerRepository { +// final LocalRepo _localRepository; +// final RemoteRepo _remoteRepository; + +// HybridTokenContainerRepository({ +// required LocalRepo localRepository, +// required RemoteRepo remoteRepository, +// }) : _localRepository = localRepository, +// _remoteRepository = remoteRepository; + +// @override +// Future loadContainerState({bool isInitial = false}) async { +// Logger.warning('Loading container state', name: 'HybridTokenContainerRepository'); +// TokenContainer localState; +// TokenContainer newState; + +// try { +// localState = await _localRepository.loadContainerState(); +// } catch (e) { +// Logger.warning('Failed to load local container state'); +// return TokenContainer.error( +// error: LocalizedException( +// unlocalizedMessage: 'Failed to load local container state', +// localizedMessage: (localization) => localization.failedToLoad('local container state'), +// ), +// ); +// } +// try { +// newState = await _remoteRepository.saveContainerState(localState); +// } catch (e) { +// newState = localState.copyTransformInto(); +// } + +// try { +// await _localRepository.saveContainerState(newState); +// } catch (e) { +// Logger.error('Failed to save synced state to local repository', name: 'HybridTokenContainerRepository', error: e); +// } + +// return newState; +// } + +// @override +// Future saveContainerState(TokenContainer currentState) async { +// if (currentState is TokenContainerError) { +// Logger.warning('Cannot save error state to repository'); +// return currentState; +// } +// TokenContainer newState; + +// try { +// newState = await _remoteRepository.saveContainerState(currentState); +// } catch (e, s) { +// Logger.warning( +// 'Failed to save state to remote repository: Changed to unsynced state', +// name: 'HybridTokenContainerRepository#saveContainerState', +// error: e, +// stackTrace: s, +// ); +// newState = currentState.copyTransformInto(); +// return _localRepository.saveContainerState(newState); +// } + +// try { +// newState = await _localRepository.saveContainerState(newState); +// } catch (e) { +// Logger.error('Failed to save state to local repository'); +// return newState; +// } +// return newState; +// } + +// // Future _merge({ +// // required TokenContainer localState, +// // required TokenContainer remoteState, +// // }) async { +// // List localTemplates; +// // List remoteTemplates; +// // if (localState is TokenContainerUninitialized) { +// // // Uninitialized state is always overwritten by other states +// // localTemplates = []; +// // } else { +// // localTemplates = localState.tokenTemplates; +// // } +// // if (remoteState is TokenContainerUninitialized) { +// // // Uninitialized state is always overwritten by other states +// // remoteTemplates = []; +// // } else { +// // remoteTemplates = remoteState.tokenTemplates ?? []; +// // } + +// // final mergedTemplates = await _mergeTemplateLists( +// // localTemplates: localTemplates, +// // remoteTemplates: remoteTemplates, +// // ); + +// // final newSyncedState = TokenContainerSynced( +// // lastSyncedAt: DateTime.now(), +// // containerId: localState.containerId, +// // description: localState.description, +// // type: '', // TODO: Implement type +// // tokenTemplates: mergedTemplates, +// // ); +// // return newSyncedState; +// // } + +// // Future> _mergeTemplateLists({ +// // required List localTemplates, +// // required List remoteTemplates, +// // }) async { +// // final mergedTemplates = []; + +// // // Add all remaining local templates +// // for (var localTemplate in localTemplates) { +// // final remoteTemplate = remoteTemplates.firstWhereOrNull((template) => template.id == localTemplate.id); +// // if (remoteTemplate == null) { +// // mergedTemplates.add(localTemplate); +// // continue; +// // } +// // mergedTemplates.add(await _mergeTemplates(localTemplate, remoteTemplate)); +// // } + +// // // Add all remaining remote templates +// // mergedTemplates.addAll(remoteTemplates); + +// // return mergedTemplates; +// // } + +// /// Merges local and remote token templates with the last synced state +// /// If both local and remote templates have changed, the remote changes are prioritized +// // Future _mergeTemplates(TokenTemplate local, TokenTemplate remote) async { +// // assert(local.id == remote.id, 'Both templates must have the same id'); +// // final mergedData = {}; + +// // print('------------------------------------------------------------------'); +// // print('Local: ${local.data}'); +// // mergedData.addAll(local.data); +// // print('MergedData: $mergedData'); +// // print('------------------------------------------------------------------'); +// // print('Remote: ${remote.data}'); +// // mergedData.addAll(remote.data); +// // print('MergedData: $mergedData'); +// // print('------------------------------------------------------------------'); + +// // return TokenTemplate(data: mergedData); +// // } + +// // @override +// // Future loadTokenTemplate(String tokenTemplateId) async { +// // final state = await loadContainerState(); +// // final template = state.tokenTemplates.firstWhereOrNull((template) => template.id == tokenTemplateId); +// // return template; +// // } + +// // @override +// // Future saveTokenTemplate(TokenTemplate tokenTemplate) async { +// // final state = await loadContainerState(); +// // final templates = state.tokenTemplates; +// // templates.removeWhere((template) => template.id == tokenTemplate.id); +// // templates.add(tokenTemplate); +// // final newState = state.copyWith(tokenTemplates: templates); +// // final savedState = await saveContainerState(newState); +// // return savedState.tokenTemplates.firstWhere((template) => template.id == tokenTemplate.id); +// // } +// } diff --git a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart index 759b03d8d..3ea0b7ccd 100644 --- a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart +++ b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart @@ -1,99 +1,99 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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:convert'; +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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:convert'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:mutex/mutex.dart'; -import '../../utils/logger.dart'; -import '../../interfaces/repo/container_repository.dart'; -import '../../model/token_container.dart'; +// import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +// import 'package:mutex/mutex.dart'; +// import '../../utils/logger.dart'; +// import '../../interfaces/repo/container_repository.dart'; +// import '../../model/token_container.dart'; -class SecureTokenContainerRepository implements TokenContainerRepository { - static String prefix = 'token_container_state_'; - String get _containerStateKey => '$prefix${containerId}_container_state'; - final Mutex _m = Mutex(); - Future _protect(Future Function() f) => _m.protect(f); - final FlutterSecureStorage _storage = const FlutterSecureStorage(); +// class SecureTokenContainerRepository implements TokenContainerRepository { +// static String prefix = 'token_container_state_'; +// String get _containerStateKey => '$prefix${containerId}_container_state'; +// final Mutex _m = Mutex(); +// Future _protect(Future Function() f) => _m.protect(f); +// final FlutterSecureStorage _storage = const FlutterSecureStorage(); - final String containerId; +// final String containerId; - SecureTokenContainerRepository({ - required this.containerId, - }); +// SecureTokenContainerRepository({ +// required this.containerId, +// }); - Future _write(String key, String value) => _protect(() { - Logger.debug('Writing key: $key, value: $value', name: 'secure_token_container_state_repository.dart.dart#_write'); - return _storage.write(key: key, value: value); - }); - Future _read(String key) async { - final value = await _protect(() async => await _storage.read(key: key)); - Logger.debug('Reading key: $key, value: $value', name: 'secure_token_container_state_repository.dart.dart#_read'); - return value; - } +// Future _write(String key, String value) => _protect(() { +// Logger.debug('Writing key: $key, value: $value', name: 'secure_token_container_state_repository.dart.dart#_write'); +// return _storage.write(key: key, value: value); +// }); +// Future _read(String key) async { +// final value = await _protect(() async => await _storage.read(key: key)); +// Logger.debug('Reading key: $key, value: $value', name: 'secure_token_container_state_repository.dart.dart#_read'); +// return value; +// } - Future _delete(String key) => _protect(() => _storage.delete(key: key)); - // Future> _readAll() async { - // Map? keys; - // await _protect(() async => keys = await _storage.readAll()); - // keys!.removeWhere((key, value) => !key.startsWith(prefix + containerId)); - // return keys!; - // } +// Future _delete(String key) => _protect(() => _storage.delete(key: key)); +// // Future> _readAll() async { +// // Map? keys; +// // await _protect(() async => keys = await _storage.readAll()); +// // keys!.removeWhere((key, value) => !key.startsWith(prefix + containerId)); +// // return keys!; +// // } - @override - Future saveContainerState(TokenContainer containerState) async { - Logger.info('Saving container state', name: 'secure_token_container_state_repository.dart.dart#saveContainerState'); - if (TokenContainer is TokenContainerError) { - Logger.error('Cannot save error state to repository', name: 'secure_token_container_state_repository.dart.dart#saveContainerState'); - return containerState; - } - final json = containerState.toJson(); - final jsonString = jsonEncode(json); - await _write(_containerStateKey, jsonString); - Logger.debug('Saved container state: $jsonString', name: 'secure_token_container_state_repository.dart.dart#saveContainerState'); - return containerState; - } +// @override +// Future saveContainerState(TokenContainer containerState) async { +// Logger.info('Saving container state', name: 'secure_token_container_state_repository.dart.dart#saveContainerState'); +// if (TokenContainer is TokenContainerError) { +// Logger.error('Cannot save error state to repository', name: 'secure_token_container_state_repository.dart.dart#saveContainerState'); +// return containerState; +// } +// final json = containerState.toJson(); +// final jsonString = jsonEncode(json); +// await _write(_containerStateKey, jsonString); +// Logger.debug('Saved container state: $jsonString', name: 'secure_token_container_state_repository.dart.dart#saveContainerState'); +// return containerState; +// } - @override +// @override - /// Load the container state from the shared preferences - Future loadContainerState() async { - Logger.info('Loading container state', name: 'secure_token_container_state_repository.dart.dart#loadContainerState'); - String? containerStateJsonString = await _read(_containerStateKey); - Logger.debug('Loaded jsonString: $containerStateJsonString', name: 'secure_token_container_state_repository.dart.dart#loadContainerState'); - if (containerStateJsonString == null) { - return const TokenContainer.uninitialized(serial: '123'); - } - final json = jsonDecode(containerStateJsonString); - try { - final state = TokenContainer.fromJson(json); - Logger.debug('Loaded container state: $containerStateJsonString', name: 'secure_token_container_state_repository.dart.dart#loadContainerState'); - return state; - } catch (e) { - Logger.error( - 'Failed to decode container state', - name: 'secure_token_container_state_repository.dart.dart#loadContainerState', - error: e, - stackTrace: StackTrace.current, - ); - await _delete(_containerStateKey); - return const TokenContainer.uninitialized(serial: '123'); - } - } -} +// /// Load the container state from the shared preferences +// Future loadContainerState() async { +// Logger.info('Loading container state', name: 'secure_token_container_state_repository.dart.dart#loadContainerState'); +// String? containerStateJsonString = await _read(_containerStateKey); +// Logger.debug('Loaded jsonString: $containerStateJsonString', name: 'secure_token_container_state_repository.dart.dart#loadContainerState'); +// if (containerStateJsonString == null) { +// return const TokenContainer.uninitialized(serial: '123'); +// } +// final json = jsonDecode(containerStateJsonString); +// try { +// final state = TokenContainer.fromJson(json); +// Logger.debug('Loaded container state: $containerStateJsonString', name: 'secure_token_container_state_repository.dart.dart#loadContainerState'); +// return state; +// } catch (e) { +// Logger.error( +// 'Failed to decode container state', +// name: 'secure_token_container_state_repository.dart.dart#loadContainerState', +// error: e, +// stackTrace: StackTrace.current, +// ); +// await _delete(_containerStateKey); +// return const TokenContainer.uninitialized(serial: '123'); +// } +// } +// } diff --git a/lib/utils/ecc_utils.dart b/lib/utils/ecc_utils.dart index 7c2ca00e1..46ce7de6e 100644 --- a/lib/utils/ecc_utils.dart +++ b/lib/utils/ecc_utils.dart @@ -21,6 +21,8 @@ import 'dart:typed_data'; import 'package:basic_utils/basic_utils.dart'; +import 'package:privacyidea_authenticator/model/enums/ec_key_algorithm.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/ec_key_algorithm_extension.dart'; class EccUtils { const EccUtils(); @@ -30,9 +32,10 @@ class EccUtils { String serializeECPrivateKey(ECPrivateKey ecPrivateKey) => CryptoUtils.encodeEcPrivateKeyToPem(ecPrivateKey); ECPrivateKey deserializeECPrivateKey(String ecPrivateKey) => CryptoUtils.ecPrivateKeyFromPem(ecPrivateKey); - String trySignWithPrivateKey(ECPrivateKey privateKey, String message) { + String signWithPrivateKey(ECPrivateKey privateKey, String message) { final ecSignature = CryptoUtils.ecSign(privateKey, Uint8List.fromList(message.codeUnits), algorithmName: 'SHA-256/ECDSA'); - String signatureBase64 = CryptoUtils.ecSignatureToBase64(ecSignature); - return signatureBase64; + return CryptoUtils.ecSignatureToBase64(ecSignature); } + + AsymmetricKeyPair generateKeyPair(EcKeyAlgorithm keyAlgorithm) => CryptoUtils.generateEcKeyPair(curve: keyAlgorithm.curveName); } diff --git a/lib/utils/identifiers.dart b/lib/utils/identifiers.dart index b2a7f1984..0b548e573 100644 --- a/lib/utils/identifiers.dart +++ b/lib/utils/identifiers.dart @@ -85,7 +85,7 @@ const OTP_AUTH_2STEP_ITERATIONS = '2step_difficulty'; // Container otp sync -const OTP_AUTH_OTP_VALUES = 'otp_values'; +const OTP_AUTH_OTP_VALUES = 'otp'; const OTP_AUTH_STEAM_ISSUER = 'Steam'; @@ -95,6 +95,10 @@ const String SIGNING_ALGORITHM = 'SHA-256/RSA'; // Custom error identifiers const String FIREBASE_TOKEN_ERROR_CODE = 'FIREBASE_TOKEN_ERROR_CODE'; +// Pi Server Error +const String PI_SERVER_ERROR_CODE = 'code'; +const String PI_SERVER_ERROR_MESSAGE = 'message'; + // Push request: const String PUSH_REQUEST_NONCE = 'nonce'; // 1. const String PUSH_REQUEST_URL = 'url'; // 2. @@ -115,6 +119,31 @@ const String CONTAINER_EC_KEY_ALGORITHM = 'key_algorithm'; const String CONTAINER_HASH_ALGORITHM = 'hash_algorithm'; const String CONTAINER_PASSPHRASE_QUESTION = 'passphrase'; +// Container sync: +const String CONTAINER_SYNC_NONCE = 'nonce'; +const String CONTAINER_SYNC_TIMESTAMP = 'time_stamp'; +const String CONTAINER_SYNC_KEY_ALGORITHM = 'key_algorithm'; +const String CONTAINER_SYNC_URL = 'container_sync_url'; +const String CONTAINER_SYNC_SIGNATURE = 'signature'; +const String CONTAINER_SYNC_PUBLIC_CLIENT_KEY = 'public_enc_key_client'; +const String CONTAINER_SYNC_DICT_SERVER = 'container_dict_server'; +const String CONTAINER_SYNC_DICT_CLIENT = 'container_dict_client'; + +const String CONTAINER_DICT_SERIAL = 'serial'; +const String CONTAINER_DICT_TYPE = 'type'; +const String CONTAINER_DICT_TOKENS = 'tokens'; +const String CONTAINER_DICT_TOKENS_ADD = 'add'; +const String CONTAINER_DICT_TOKENS_UPDATE = 'update'; + +const String CONTAINER_SYNC_PUBLIC_SERVER_KEY = 'public_server_key'; + +const String CONTAINER_SYNC_ENC_ALGORITHM = 'encryption_algorithm'; +const String CONTAINER_SYNC_ENC_PARAMS = 'encryption_params'; +const String CONTAINER_SYNC_ENC_PARAMS_MODE = 'mode'; +const String CONTAINER_SYNC_ENC_PARAMS_IV = 'init_vector'; +const String CONTAINER_SYNC_ENC_PARAMS_TAG = 'tag'; +const String CONTAINER_SYNC_DICT_ENCRYPTED = 'container_dict_encrypted'; + const String GLOBAL_SECURE_REPO_PREFIX = 'app_v3_'; T? validateOptional({required dynamic value, required TypeValidatorOptional validator, required String name}) { @@ -180,7 +209,7 @@ Map validateMap({required Map map, required Map return validatedMap; } -class TypeValidatorOptional { +class TypeValidatorOptional { bool get isOptional => runtimeType.toString().contains('Optional'); final T Function(dynamic value)? transformer; @@ -197,7 +226,7 @@ class TypeValidatorOptional { Logger.debug('Checking type of $value and default value $defaultValue with transformer $transformer (isOptional $isOptional)'); if (value == null) return (defaultValue != null) ? true : isOptional; - if (transformer == null) return value is T?; + if (transformer == null) return value is T; try { transformer!(value); return true; @@ -213,7 +242,7 @@ class TypeValidatorOptional { if (value == null) return defaultValue; if (transformer == null) return value as T; try { - return transformer!(value); + return transformer!.call(value); } catch (e) { return defaultValue; } @@ -248,7 +277,7 @@ class TypeValidatorRequired extends TypeValidatorOptional { try { if (value == null) return defaultValue!; if (transformer == null) return value is T ? value : defaultValue!; - return transformer!(value); + return transformer!.call(value); } catch (e) { if (defaultValue != null) return defaultValue!; throw LocalizedArgumentError( diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart index f80fdc563..78a60cc76 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart @@ -26,19 +26,22 @@ import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; import 'package:privacyidea_authenticator/model/extensions/enums/ec_key_algorithm_extension.dart'; import 'package:privacyidea_authenticator/model/processor_result.dart'; +import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../../../../api/token_container_api_endpoint.dart'; import '../../../../interfaces/repo/container_credentials_repository.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../model/enums/container_finalization_state.dart'; import '../../../../model/riverpod_states/credentials_state.dart'; +import '../../../../model/riverpod_states/token_state.dart'; import '../../../../model/tokens/container_credentials.dart'; import '../../../../repo/secure_container_credentials_repository.dart'; -import '../../../../widgets/dialog_widgets/enter_passphrase_dialog.dart'; import '../../../ecc_utils.dart'; import '../../../errors.dart'; import '../../../logger.dart'; @@ -47,7 +50,7 @@ part 'credential_notifier.g.dart'; final containerCredentialsProvider = containerCredentialsNotifierProviderOf( repo: SecureContainerCredentialsRepository(), - ioClient: const PrivacyideaIOClient(), + containerApi: const PrivacyideaContainerApi(ioClient: PrivacyideaIOClient()), eccUtils: const EccUtils(), ); @@ -58,10 +61,10 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R ContainerCredentialsNotifier({ ContainerCredentialsRepository? repoOverride, - PrivacyideaIOClient? ioClientOverride, + PrivacyideaContainerApi? containerApiOverride, EccUtils? eccUtilsOverride, }) : _repoOverride = repoOverride, - _ioClientOverride = ioClientOverride, + _containerApiOverride = containerApiOverride, _eccUtilsOverride = eccUtilsOverride; @override @@ -70,9 +73,9 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R final ContainerCredentialsRepository? _repoOverride; @override - PrivacyideaIOClient get ioClient => _ioClient; - late PrivacyideaIOClient _ioClient; - final PrivacyideaIOClient? _ioClientOverride; + PrivacyideaContainerApi get containerApi => _containerApi; + late PrivacyideaContainerApi _containerApi; + final PrivacyideaContainerApi? _containerApiOverride; @override EccUtils get eccUtils => _eccUtils; @@ -82,14 +85,15 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R @override Future build({ required ContainerCredentialsRepository repo, - required PrivacyideaIOClient ioClient, + required PrivacyideaContainerApi containerApi, required EccUtils eccUtils, }) async { await _stateMutex.acquire(); _repo = _repoOverride ?? repo; - _ioClient = _ioClientOverride ?? ioClient; + _containerApi = _containerApiOverride ?? containerApi; _eccUtils = _eccUtilsOverride ?? eccUtils; Logger.warning('Building credentialsProvider', name: 'CredentialsNotifier'); + final initState = await _repo.loadCredentialsState(); for (var credential in initState.credentials.whereType()) { finalize(credential); @@ -136,6 +140,7 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R await _stateMutex.acquire(); final newCredentials = credentials.toList(); final oldCredentials = (await future).credentials; + Logger.debug('Loaded credentials: $oldCredentials', name: 'CredentialsNotifier#addCredentials'); final combinedCredentials = []; for (var oldCredential in oldCredentials) { final newCredential = newCredentials.firstWhereOrNull((newCredential) => newCredential.serial == oldCredential.serial); @@ -147,8 +152,11 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R } } combinedCredentials.addAll(newCredentials); + Logger.debug('Combined credentials: $combinedCredentials', name: 'CredentialsNotifier#addCredentials'); final newState = await _saveCredentialsStateToRepo(CredentialsState(credentials: combinedCredentials)); + Logger.debug('Saved credentials: $newState', name: 'CredentialsNotifier#addCredentials'); await update((_) => newState); + Logger.debug('Updated credentials: $newState', name: 'CredentialsNotifier#addCredentials'); _stateMutex.release(); return newState; } @@ -164,10 +172,10 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R return super.update(cb, onError: onError); } - Future updateCredential(ContainerCredential credential, T Function(ContainerCredential) updater) async { + Future updateCredential(T credential, T Function(T) updater) async { await _stateMutex.acquire(); final oldState = await future; - final currentCredential = oldState.currentOf(credential); + final currentCredential = oldState.currentOf(credential); if (currentCredential == null) { Logger.info('Failed to update credential. It was probably removed in the meantime.', name: 'CredentialsNotifier#updateCredential'); _stateMutex.release(); @@ -280,16 +288,16 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R _finalizationMutex.release(); } -//////////////////////////////////////////////////////////////////////////// +/* ///////////////////////////////////////////////////////////////////////// ////////////////////////// PRIVATE HELPER METHODS ////////////////////////// -//////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// */ /// Finalization substep 1: Generate key pair - Future _generateKeyPair(ContainerCredential containerCredential) async { + Future _generateKeyPair(ContainerCredentialUnfinalized containerCredential) async { // generatingKeyPair, // generatingKeyPairFailed, // generatingKeyPairCompleted, - ContainerCredential? credential = containerCredential; + ContainerCredentialUnfinalized? credential = containerCredential; credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.generatingKeyPair)); if (credential == null) throw StateError('Credential was removed'); final keyPair = CryptoUtils.generateEcKeyPair(curve: credential.ecKeyAlgorithm.curveName); @@ -299,12 +307,11 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R } /// Finalization substep 2: Send public key - Future<(ContainerCredential, Response)> _sendPublicKey(ContainerCredential containerCredential) async { + Future<(ContainerCredential, Response)> _sendPublicKey(ContainerCredentialUnfinalized containerc) async { // sendingPublicKey, // sendingPublicKeyFailed, // sendingPublicKeyCompleted, - ContainerCredential? container = containerCredential; - final ecPrivateClientKey = container.ecPrivateClientKey!; + //POST /container/register/finalize // Request: { // 'container_serial': , @@ -312,25 +319,14 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R // 'signature': )>, // } - final passphrase = container.passphraseQuestion != null ? EnterPassphraseDialog.show(await globalContext) : null; - final message = '${container.nonce}' - '|${container.timestamp.toIso8601String().replaceFirst('Z', '+00:00')}' - '|${container.finalizationUrl}' - '|${container.serial}' - '${passphrase != null ? '|$passphrase' : ''}'; + ContainerCredentialUnfinalized? container = containerc; - final signature = eccUtils.trySignWithPrivateKey(ecPrivateClientKey, message); - - final body = { - 'container_serial': container.serial, - 'public_client_key': container.publicClientKey, - 'signature': signature, - }; final Response response; - container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKey)); + container = + await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKey)); if (container == null) throw StateError('Credential was removed'); try { - response = await _ioClient.doPost(url: container.finalizationUrl, body: body, sslVerify: false); //TODO: sslVerify + response = (await _containerApi.finalizeContainer(container, eccUtils))!; } catch (e) { ref.read(statusMessageProvider.notifier).state = ('Failed to finalize container', e.toString()); await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyFailed)); @@ -360,6 +356,7 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponse)); if (credential == null) throw StateError('Credential was removed'); responseJson = jsonDecode(responseBody); + Logger.debug('Response JSON: $responseJson', name: 'CredentialsNotifier#_parseResponse'); final result = validate(value: responseJson['result'], validator: const TypeValidatorRequired>(), name: 'result'); final value = validate(value: result['value'], validator: const TypeValidatorRequired>(), name: 'value'); publicServerKey = validate( @@ -367,8 +364,34 @@ class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with R validator: TypeValidatorRequired(transformer: (v) => const EccUtils().deserializeECPublicKey(v)), name: 'public_server_key', ); - credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseCompleted)); + final syncUrlUri = validate( + value: value['container_sync_url'], + validator: TypeValidatorRequired(transformer: (v) => Uri.parse(v)), + name: 'container_sync_url', + ); + credential = + await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseCompleted, syncUrl: syncUrlUri)); if (credential == null) throw StateError('Credential was removed'); return (credential, publicServerKey); } + + Future syncTokens(TokenState tokenState) async { + final containerCredentials = (await future).credentials; + final containerCredential = containerCredentials.whereType().first; + + List syncedTokens; + List deletedTokens; + final tuple = await _containerApi.sync( + containerCredential, + tokenState, + ); + if (tuple == null) { + return; + } + syncedTokens = tuple.$1; + deletedTokens = tuple.$2; + + await ref.read(tokenProvider.notifier).addOrReplaceTokens(syncedTokens); + await ref.read(tokenProvider.notifier).removeTokensBySerials(deletedTokens); + } } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart index 400d6ac5c..f7cc82d6e 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart @@ -7,7 +7,7 @@ part of 'credential_notifier.dart'; // ************************************************************************** String _$containerCredentialsNotifierHash() => - r'e89ea14f6a73f042c4cf8f0d80a10b2be7605082'; + r'83daeabb57839b956f33171c95c8f45393049d29'; /// Copied from Dart SDK class _SystemHash { @@ -33,12 +33,12 @@ class _SystemHash { abstract class _$ContainerCredentialsNotifier extends BuildlessAsyncNotifier { late final ContainerCredentialsRepository repo; - late final PrivacyideaIOClient ioClient; + late final PrivacyideaContainerApi containerApi; late final EccUtils eccUtils; FutureOr build({ required ContainerCredentialsRepository repo, - required PrivacyideaIOClient ioClient, + required PrivacyideaContainerApi containerApi, required EccUtils eccUtils, }); } @@ -57,12 +57,12 @@ class ContainerCredentialsNotifierFamily /// See also [ContainerCredentialsNotifier]. ContainerCredentialsNotifierProvider call({ required ContainerCredentialsRepository repo, - required PrivacyideaIOClient ioClient, + required PrivacyideaContainerApi containerApi, required EccUtils eccUtils, }) { return ContainerCredentialsNotifierProvider( repo: repo, - ioClient: ioClient, + containerApi: containerApi, eccUtils: eccUtils, ); } @@ -73,7 +73,7 @@ class ContainerCredentialsNotifierFamily ) { return call( repo: provider.repo, - ioClient: provider.ioClient, + containerApi: provider.containerApi, eccUtils: provider.eccUtils, ); } @@ -99,12 +99,12 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< /// See also [ContainerCredentialsNotifier]. ContainerCredentialsNotifierProvider({ required ContainerCredentialsRepository repo, - required PrivacyideaIOClient ioClient, + required PrivacyideaContainerApi containerApi, required EccUtils eccUtils, }) : this._internal( () => ContainerCredentialsNotifier() ..repo = repo - ..ioClient = ioClient + ..containerApi = containerApi ..eccUtils = eccUtils, from: containerCredentialsNotifierProviderOf, name: r'containerCredentialsNotifierProviderOf', @@ -116,7 +116,7 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< allTransitiveDependencies: ContainerCredentialsNotifierFamily._allTransitiveDependencies, repo: repo, - ioClient: ioClient, + containerApi: containerApi, eccUtils: eccUtils, ); @@ -128,12 +128,12 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< required super.debugGetCreateSourceHash, required super.from, required this.repo, - required this.ioClient, + required this.containerApi, required this.eccUtils, }) : super.internal(); final ContainerCredentialsRepository repo; - final PrivacyideaIOClient ioClient; + final PrivacyideaContainerApi containerApi; final EccUtils eccUtils; @override @@ -142,7 +142,7 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< ) { return notifier.build( repo: repo, - ioClient: ioClient, + containerApi: containerApi, eccUtils: eccUtils, ); } @@ -154,7 +154,7 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< override: ContainerCredentialsNotifierProvider._internal( () => create() ..repo = repo - ..ioClient = ioClient + ..containerApi = containerApi ..eccUtils = eccUtils, from: from, name: null, @@ -162,7 +162,7 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< allTransitiveDependencies: null, debugGetCreateSourceHash: null, repo: repo, - ioClient: ioClient, + containerApi: containerApi, eccUtils: eccUtils, ), ); @@ -178,7 +178,7 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< bool operator ==(Object other) { return other is ContainerCredentialsNotifierProvider && other.repo == repo && - other.ioClient == ioClient && + other.containerApi == containerApi && other.eccUtils == eccUtils; } @@ -186,7 +186,7 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< int get hashCode { var hash = _SystemHash.combine(0, runtimeType.hashCode); hash = _SystemHash.combine(hash, repo.hashCode); - hash = _SystemHash.combine(hash, ioClient.hashCode); + hash = _SystemHash.combine(hash, containerApi.hashCode); hash = _SystemHash.combine(hash, eccUtils.hashCode); return _SystemHash.finish(hash); @@ -198,8 +198,8 @@ mixin ContainerCredentialsNotifierRef /// The parameter `repo` of this provider. ContainerCredentialsRepository get repo; - /// The parameter `ioClient` of this provider. - PrivacyideaIOClient get ioClient; + /// The parameter `containerApi` of this provider. + PrivacyideaContainerApi get containerApi; /// The parameter `eccUtils` of this provider. EccUtils get eccUtils; @@ -214,8 +214,8 @@ class _ContainerCredentialsNotifierProviderElement ContainerCredentialsRepository get repo => (origin as ContainerCredentialsNotifierProvider).repo; @override - PrivacyideaIOClient get ioClient => - (origin as ContainerCredentialsNotifierProvider).ioClient; + PrivacyideaContainerApi get containerApi => + (origin as ContainerCredentialsNotifierProvider).containerApi; @override EccUtils get eccUtils => (origin as ContainerCredentialsNotifierProvider).eccUtils; diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.g.dart deleted file mode 100644 index b092f5267..000000000 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.g.dart +++ /dev/null @@ -1,176 +0,0 @@ -// // GENERATED CODE - DO NOT MODIFY BY HAND - -// part of 'progress_state_provider.dart'; - -// // ************************************************************************** -// // RiverpodGenerator -// // ************************************************************************** - -// String _$progressStateNotifierHash() => -// r'f2da79434b36425d91169f5eb0f82cbd48ef8949'; - -// /// Copied from Dart SDK -// class _SystemHash { -// _SystemHash._(); - -// static int combine(int hash, int value) { -// // ignore: parameter_assignments -// hash = 0x1fffffff & (hash + value); -// // ignore: parameter_assignments -// hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); -// return hash ^ (hash >> 6); -// } - -// static int finish(int hash) { -// // ignore: parameter_assignments -// hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); -// // ignore: parameter_assignments -// hash = hash ^ (hash >> 11); -// return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); -// } -// } - -// abstract class _$ProgressStateNotifier -// extends BuildlessAutoDisposeNotifier { -// late final Type type; - -// ProgressState build( -// Type type, -// ); -// } - -// /// See also [ProgressStateNotifier]. -// @ProviderFor(ProgressStateNotifier) -// const progressStateNotifierProviderOf = ProgressStateNotifierFamily(); - -// /// See also [ProgressStateNotifier]. -// class ProgressStateNotifierFamily extends Family { -// /// See also [ProgressStateNotifier]. -// const ProgressStateNotifierFamily(); - -// /// See also [ProgressStateNotifier]. -// ProgressStateNotifierProvider call( -// Type type, -// ) { -// return ProgressStateNotifierProvider( -// type, -// ); -// } - -// @override -// ProgressStateNotifierProvider getProviderOverride( -// covariant ProgressStateNotifierProvider provider, -// ) { -// return call( -// provider.type, -// ); -// } - -// static const Iterable? _dependencies = null; - -// @override -// Iterable? get dependencies => _dependencies; - -// static const Iterable? _allTransitiveDependencies = null; - -// @override -// Iterable? get allTransitiveDependencies => -// _allTransitiveDependencies; - -// @override -// String? get name => r'progressStateNotifierProviderOf'; -// } - -// /// See also [ProgressStateNotifier]. -// class ProgressStateNotifierProvider extends AutoDisposeNotifierProviderImpl< -// ProgressStateNotifier, ProgressState> { -// /// See also [ProgressStateNotifier]. -// ProgressStateNotifierProvider( -// Type type, -// ) : this._internal( -// () => ProgressStateNotifier()..type = type, -// from: progressStateNotifierProviderOf, -// name: r'progressStateNotifierProviderOf', -// debugGetCreateSourceHash: -// const bool.fromEnvironment('dart.vm.product') -// ? null -// : _$progressStateNotifierHash, -// dependencies: ProgressStateNotifierFamily._dependencies, -// allTransitiveDependencies: -// ProgressStateNotifierFamily._allTransitiveDependencies, -// type: type, -// ); - -// ProgressStateNotifierProvider._internal( -// super._createNotifier, { -// required super.name, -// required super.dependencies, -// required super.allTransitiveDependencies, -// required super.debugGetCreateSourceHash, -// required super.from, -// required this.type, -// }) : super.internal(); - -// final Type type; - -// @override -// ProgressState runNotifierBuild( -// covariant ProgressStateNotifier notifier, -// ) { -// return notifier.build( -// type, -// ); -// } - -// @override -// Override overrideWith(ProgressStateNotifier Function() create) { -// return ProviderOverride( -// origin: this, -// override: ProgressStateNotifierProvider._internal( -// () => create()..type = type, -// from: from, -// name: null, -// dependencies: null, -// allTransitiveDependencies: null, -// debugGetCreateSourceHash: null, -// type: type, -// ), -// ); -// } - -// @override -// AutoDisposeNotifierProviderElement -// createElement() { -// return _ProgressStateNotifierProviderElement(this); -// } - -// @override -// bool operator ==(Object other) { -// return other is ProgressStateNotifierProvider && other.type == type; -// } - -// @override -// int get hashCode { -// var hash = _SystemHash.combine(0, runtimeType.hashCode); -// hash = _SystemHash.combine(hash, type.hashCode); - -// return _SystemHash.finish(hash); -// } -// } - -// mixin ProgressStateNotifierRef -// on AutoDisposeNotifierProviderRef { -// /// The parameter `type` of this provider. -// Type get type; -// } - -// class _ProgressStateNotifierProviderElement -// extends AutoDisposeNotifierProviderElement with ProgressStateNotifierRef { -// _ProgressStateNotifierProviderElement(super.provider); - -// @override -// Type get type => (origin as ProgressStateNotifierProvider).type; -// } -// // ignore_for_file: type=lint -// // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart index fa04f9f5f..50d28c0fb 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -1,163 +1,204 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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:mutex/mutex.dart'; -import '../../../../interfaces/repo/container_repository.dart'; -import '../../../logger.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -import '../../../../api/token_container_api_endpoint.dart'; -import '../../../../model/riverpod_states/token_state.dart'; -import '../../../../model/token_container.dart'; -import '../../../../repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; -import '../../../../repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; -import '../../../../repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart'; -import '../../../../model/tokens/container_credentials.dart'; -import 'token_notifier.dart'; - -part 'token_container_notifier.g.dart'; - -@riverpod -class TokenContainerNotifier extends _$TokenContainerNotifier { - late final TokenContainerRepository _repository; - final Mutex _stateMutex = Mutex(); // Mutex to protect the state from being accessed while still waiting for the newest state to be delivered - final Mutex _repoMutex = Mutex(); // Mutex to protect the repository from being accessed while still waiting for the newest state to be delivered - - @override - Future build({ - required ContainerCredential credential, - }) async { - Logger.info('New tokenContainerStateProvider created', name: 'TokenContainerNotifier#build'); - await _stateMutex.acquire(); - _repository = SecureTokenContainerRepository(containerId: credential.serial); - // HybridTokenContainerRepository( - // localRepository: SecureTokenContainerRepository(containerId: credential.serial), - // remoteRepository: RemoteTokenContainerRepository(apiEndpoint: TokenContainerApiEndpoint(credential: credential)), - // ); - final initialState = await _repository.loadContainerState(); - Logger.debug('Initial state: $initialState', name: 'TokenContainerNotifier#build'); - _stateMutex.release(); - return initialState; - } - - Future _saveToRepo(TokenContainer state) async { - await _repoMutex.acquire(); - final newState = await _repository.saveContainerState(state); - _repoMutex.release(); - return newState; - } - - Future _fetchFromRepo() async { - await _repoMutex.acquire(); - final newState = await _repository.loadContainerState(); - _repoMutex.release(); - return newState; - } - - Future handleTokenState(TokenState tokenState) async { - await _stateMutex.acquire(); - final localTokens = tokenState.tokens.maybePiTokens; - final oldState = state.value; - if (oldState == null) throw Exception('TokenContainer is null'); - final containerTokens = tokenState.containerTokens(oldState.serial); - final localTokenTemplates = localTokens.toTemplates(); - final containerTokenTemplates = containerTokens.toTemplates(); - final newState = oldState.copyWith(localTokenTemplates: localTokenTemplates, syncedTokenTemplates: containerTokenTemplates); - final savedState = await _saveToRepo(newState); - if (savedState is! TokenContainerSynced) { - Logger.error('Failed to save state to repo', name: 'TokenContainerNotifier#handleTokenState'); - return savedState; - } - await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); - state = AsyncValue.data(savedState); - _stateMutex.release(); - return savedState; - } - - /// Adds the given [maybePiTokenTemplates] to the localTokenTemplates of the container - /// and saves the new state to the repository. The rpository decides waht to do with the new state. - /// The saved state from the repo can contain the maybePiTokenTemplates or not. - Future tryAddLocalTemplates(List maybePiTokenTemplates) async { - Logger.info( - 'Trying to add (${maybePiTokenTemplates.length}) local templates to container (${credential.serial}).', - name: 'TokenContainerNotifier#tryAddLocalTemplates', - ); - Logger.debug('Local templates (${maybePiTokenTemplates.length})', name: 'TokenContainerNotifier#tryAddLocalTemplates'); - await _stateMutex.acquire(); - final oldState = (await future); - final newLocalTokenTemplates = [...maybePiTokenTemplates]; - final newState = oldState.copyWith(localTokenTemplates: newLocalTokenTemplates); - final savedState = await _saveToRepo(newState); - Logger.debug( - 'Saved TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', - name: 'TokenContainerNotifier#tryAddLocalTemplates', - ); - await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); - state = AsyncValue.data(savedState); - _stateMutex.release(); - return savedState; - } - - Future handleDeletedTokenTemplates(List deletedPiTokenTemplates) async { - Logger.info( - 'Removing (${deletedPiTokenTemplates.length}) deleted token templates from container (${credential.serial}).', - name: 'TokenContainerNotifier#handleDeletedTokenTemplates', - ); - Logger.debug('Deleted token templates (${deletedPiTokenTemplates.length})', name: 'TokenContainerNotifier#handleDeletedTokenTemplates'); - await _stateMutex.acquire(); - final oldState = (await future); - final newLocalTokenTemplates = oldState.localTokenTemplates.where((template) => !deletedPiTokenTemplates.contains(template)).toList(); - final newState = oldState.copyWith(localTokenTemplates: newLocalTokenTemplates); - final savedState = await _saveToRepo(newState); - Logger.debug( - 'Saved TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', - name: 'TokenContainerNotifier#handleDeletedTokenTemplates', - ); - await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); - state = AsyncValue.data(savedState); - _stateMutex.release(); - return savedState; - } - - Future fetchTokens() async { - await _stateMutex.acquire(); - final savedState = await _fetchFromRepo(); - Logger.debug( - 'Fetched TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', - name: 'TokenContainerNotifier#tryAddLocalTemplates', - ); - await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); - state = AsyncValue.data(savedState); - _stateMutex.release(); - return savedState; - } - - Future sync() async { - await _stateMutex.acquire(); - final savedState = await _fetchFromRepo(); - Logger.debug( - 'Fetched TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', - name: 'TokenContainerNotifier#tryAddLocalTemplates', - ); - await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); - state = AsyncValue.data(savedState); - _stateMutex.release(); - return savedState; - } -} +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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:mutex/mutex.dart'; +// import '../../../../interfaces/repo/container_repository.dart'; +// import '../../../logger.dart'; +// import 'package:riverpod_annotation/riverpod_annotation.dart'; + +// import '../../../../api/token_container_api_endpoint.dart'; +// import '../../../../model/riverpod_states/token_state.dart'; +// import '../../../../model/token_container.dart'; +// import '../../../../repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; +// import '../../../../repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; +// import '../../../../repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart'; +// import '../../../../model/tokens/container_credentials.dart'; +// import 'token_notifier.dart'; + +// part 'token_container_notifier.g.dart'; + +// @riverpod +// class TokenContainerNotifier extends _$TokenContainerNotifier { +// late final TokenContainerRepository _repository; +// final Mutex _stateMutex = Mutex(); // Mutex to protect the state from being accessed while still waiting for the newest state to be delivered +// final Mutex _repoMutex = Mutex(); // Mutex to protect the repository from being accessed while still waiting for the newest state to be delivered + +// @override +// Future build({ +// required ContainerCredential credential, +// }) async { +// Logger.info('New tokenContainerStateProvider created', name: 'TokenContainerNotifier#build'); +// await _stateMutex.acquire(); +// _repository = SecureTokenContainerRepository(containerId: credential.serial); +// // HybridTokenContainerRepository( +// // localRepository: SecureTokenContainerRepository(containerId: credential.serial), +// // remoteRepository: RemoteTokenContainerRepository(apiEndpoint: TokenContainerApiEndpoint(credential: credential)), +// // ); +// final initialState = await _repository.loadContainerState(); +// Logger.debug('Initial state: $initialState', name: 'TokenContainerNotifier#build'); +// _stateMutex.release(); +// return initialState; +// } + +// Future _saveToRepo(TokenContainer state) async { +// await _repoMutex.acquire(); +// final newState = await _repository.saveContainerState(state); +// _repoMutex.release(); +// return newState; +// } + +// Future _fetchFromRepo() async { +// await _repoMutex.acquire(); +// final newState = await _repository.loadContainerState(); +// _repoMutex.release(); +// return newState; +// } + +// Future handleTokenState(TokenState tokenState) async { +// await _stateMutex.acquire(); +// final localTokens = tokenState.tokens.maybePiTokens; +// final oldState = state.value; +// if (oldState == null) throw Exception('TokenContainer is null'); +// final containerTokens = tokenState.containerTokens(oldState.serial); +// final localTokenTemplates = localTokens.toTemplates(); +// final containerTokenTemplates = containerTokens.toTemplates(); +// final newState = oldState.copyWith(localTokenTemplates: localTokenTemplates, syncedTokenTemplates: containerTokenTemplates); +// final savedState = await _saveToRepo(newState); +// if (savedState is! TokenContainerSynced) { +// Logger.error('Failed to save state to repo', name: 'TokenContainerNotifier#handleTokenState'); +// return savedState; +// } +// await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); +// state = AsyncValue.data(savedState); +// _stateMutex.release(); +// return savedState; +// } + +// /// Adds the given [maybePiTokenTemplates] to the localTokenTemplates of the container +// /// and saves the new state to the repository. The rpository decides waht to do with the new state. +// /// The saved state from the repo can contain the maybePiTokenTemplates or not. +// Future tryAddLocalTemplates(List maybePiTokenTemplates) async { +// Logger.info( +// 'Trying to add (${maybePiTokenTemplates.length}) local templates to container (${credential.serial}).', +// name: 'TokenContainerNotifier#tryAddLocalTemplates', +// ); +// Logger.debug('Local templates (${maybePiTokenTemplates.length})', name: 'TokenContainerNotifier#tryAddLocalTemplates'); +// await _stateMutex.acquire(); +// final oldState = (await future); +// final newLocalTokenTemplates = [...maybePiTokenTemplates]; +// final newState = oldState.copyWith(localTokenTemplates: newLocalTokenTemplates); +// final savedState = await _saveToRepo(newState); +// Logger.debug( +// 'Saved TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', +// name: 'TokenContainerNotifier#tryAddLocalTemplates', +// ); +// await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); +// state = AsyncValue.data(savedState); +// _stateMutex.release(); +// return savedState; +// } + +// Future handleDeletedTokenTemplates(List deletedPiTokenTemplates) async { +// Logger.info( +// 'Removing (${deletedPiTokenTemplates.length}) deleted token templates from container (${credential.serial}).', +// name: 'TokenContainerNotifier#handleDeletedTokenTemplates', +// ); +// Logger.debug('Deleted token templates (${deletedPiTokenTemplates.length})', name: 'TokenContainerNotifier#handleDeletedTokenTemplates'); +// await _stateMutex.acquire(); +// final oldState = (await future); +// final newLocalTokenTemplates = oldState.localTokenTemplates.where((template) => !deletedPiTokenTemplates.contains(template)).toList(); +// final newState = oldState.copyWith(localTokenTemplates: newLocalTokenTemplates); +// final savedState = await _saveToRepo(newState); +// Logger.debug( +// 'Saved TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', +// name: 'TokenContainerNotifier#handleDeletedTokenTemplates', +// ); +// await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); +// state = AsyncValue.data(savedState); +// _stateMutex.release(); +// return savedState; +// } + +// Future fetchTokens() async { +// await _stateMutex.acquire(); +// final savedState = await _fetchFromRepo(); +// Logger.debug( +// 'Fetched TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', +// name: 'TokenContainerNotifier#tryAddLocalTemplates', +// ); +// await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); +// state = AsyncValue.data(savedState); +// _stateMutex.release(); +// return savedState; +// } + +// Future sync() async { +// await _stateMutex.acquire(); +// final savedState = await _fetchFromRepo(); +// Logger.debug( +// 'Fetched TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', +// name: 'TokenContainerNotifier#tryAddLocalTemplates', +// ); +// await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); +// state = AsyncValue.data(savedState); +// _stateMutex.release(); +// return savedState; +// } +// } + +// final example = { +// 'container': 'CONTAINER_SERIAL', +// 'tokens': { +// 'TOKEN_SERIAL_1': {}, +// [123456, 654321]: {}, +// 'TOKEN_SERIAL_2': {}, +// [234567, 765432]: {}, +// 'TOKEN_SERIAL_3': {}, +// [345678, 876543]: {}, +// }, +// }; + +// // class ContainerResponse { +// // String serial; +// // List templatesByOtps; + +// // ContainerResponse._({ +// // required this.serial, +// // required this.templatesByOtps, +// // }); + +// // factory ContainerResponse.fromExample(Map example) { +// // final containerSerial = example['container'] as String; +// // final tokenTemplates = []; + +// // final templates = example['tokens'] as Map; +// // templates.forEach((templateKey, value) { +// // if (templateKey is String) { +// // tokenTemplates.add(TokenTemplate.withSerial(otpAuthMap: templates[templateKey], serial: templateKey)); +// // } else if (templateKey is List) { +// // tokenTemplates.add(TokenTemplate.withOtps(otpAuthMap: templates[templateKey], otps: templateKey)); +// // } +// // }); + +// // return ContainerResponse._( +// // serial: containerSerial, +// // templatesByOtps: tokenTemplates, +// // ); +// // } +// // } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart deleted file mode 100644 index 33d145432..000000000 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart +++ /dev/null @@ -1,179 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'token_container_notifier.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$tokenContainerNotifierHash() => - r'fa7e50e1970516f60df6ba4b2ccf19dad3ab5736'; - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} - -abstract class _$TokenContainerNotifier - extends BuildlessAutoDisposeAsyncNotifier { - late final ContainerCredential credential; - - FutureOr build({ - required ContainerCredential credential, - }); -} - -/// See also [TokenContainerNotifier]. -@ProviderFor(TokenContainerNotifier) -const tokenContainerNotifierProviderOf = TokenContainerNotifierFamily(); - -/// See also [TokenContainerNotifier]. -class TokenContainerNotifierFamily extends Family> { - /// See also [TokenContainerNotifier]. - const TokenContainerNotifierFamily(); - - /// See also [TokenContainerNotifier]. - TokenContainerNotifierProvider call({ - required ContainerCredential credential, - }) { - return TokenContainerNotifierProvider( - credential: credential, - ); - } - - @override - TokenContainerNotifierProvider getProviderOverride( - covariant TokenContainerNotifierProvider provider, - ) { - return call( - credential: provider.credential, - ); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'tokenContainerNotifierProviderOf'; -} - -/// See also [TokenContainerNotifier]. -class TokenContainerNotifierProvider - extends AutoDisposeAsyncNotifierProviderImpl { - /// See also [TokenContainerNotifier]. - TokenContainerNotifierProvider({ - required ContainerCredential credential, - }) : this._internal( - () => TokenContainerNotifier()..credential = credential, - from: tokenContainerNotifierProviderOf, - name: r'tokenContainerNotifierProviderOf', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$tokenContainerNotifierHash, - dependencies: TokenContainerNotifierFamily._dependencies, - allTransitiveDependencies: - TokenContainerNotifierFamily._allTransitiveDependencies, - credential: credential, - ); - - TokenContainerNotifierProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.credential, - }) : super.internal(); - - final ContainerCredential credential; - - @override - FutureOr runNotifierBuild( - covariant TokenContainerNotifier notifier, - ) { - return notifier.build( - credential: credential, - ); - } - - @override - Override overrideWith(TokenContainerNotifier Function() create) { - return ProviderOverride( - origin: this, - override: TokenContainerNotifierProvider._internal( - () => create()..credential = credential, - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - credential: credential, - ), - ); - } - - @override - AutoDisposeAsyncNotifierProviderElement createElement() { - return _TokenContainerNotifierProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is TokenContainerNotifierProvider && - other.credential == credential; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, credential.hashCode); - - return _SystemHash.finish(hash); - } -} - -mixin TokenContainerNotifierRef - on AutoDisposeAsyncNotifierProviderRef { - /// The parameter `credential` of this provider. - ContainerCredential get credential; -} - -class _TokenContainerNotifierProviderElement - extends AutoDisposeAsyncNotifierProviderElement with TokenContainerNotifierRef { - _TokenContainerNotifierProviderElement(super.provider); - - @override - ContainerCredential get credential => - (origin as TokenContainerNotifierProvider).credential; -} -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart index 59e57489c..326c422ca 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart @@ -41,7 +41,7 @@ import '../../../../model/enums/token_import_type.dart'; import '../../../../model/enums/token_origin_source_type.dart'; import '../../../../model/processor_result.dart'; import '../../../../model/riverpod_states/token_state.dart'; -import '../../../../model/token_container.dart'; +import '../../../../model/token_template.dart'; import '../../../../model/tokens/hotp_token.dart'; import '../../../../model/tokens/otp_token.dart'; import '../../../../model/tokens/push_token.dart'; @@ -156,20 +156,24 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { /// Adds a list of tokens and returns the tokens that could not be added or replaced. Future> _addOrReplaceTokens(List tokens) async { await _repoMutex.acquire(); + tokens = tokens.map((token) { + final currentId = state.currentOf(token)?.id; + if (currentId != null) return token.copyWith(id: currentId); + return token; + }).toList(); final failedTokens = await _repo.saveOrReplaceTokens(tokens); if (failedTokens.isNotEmpty) { Logger.warning( 'Saving tokens failed. Failed Tokens: ${failedTokens.length}', name: 'token_notifier.dart#_saveOrReplaceTokens', ); - // Every token that is saved should not be in the failedTokens list - final savedTokens = tokens.where((element) => !failedTokens.contains(element)).toList(); - state = state.addOrReplaceTokens(savedTokens); - return failedTokens; } - // [failedTokens] is empty, so every token was saved successfully and we dont need to filter the tokens - Logger.info('Saved ${tokens.length} Tokens to storage.', name: 'token_notifier.dart#_saveOrReplaceTokens'); - state = state.addOrReplaceTokens(tokens); + // Every token that is saved should not be in the failedTokens list + final savedTokens = tokens.where((element) => !failedTokens.contains(element)).toList(); + // Add the saved tokens to the state + Logger.info('Saved ${savedTokens.length} Tokens to storage.', name: 'token_notifier.dart#_saveOrReplaceTokens'); + state = state.addOrReplaceTokens(savedTokens); + Logger.debug('New State: ${state.tokens.length} Tokens', name: 'token_notifier.dart#_saveOrReplaceTokens'); _repoMutex.release(); return []; @@ -238,7 +242,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { return false; } _repoMutex.release(); - handlePushTokensIfExist(); + await _handlePushTokensIfExist(); return true; } @@ -261,7 +265,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { return failedTokens; } _repoMutex.release(); - handlePushTokensIfExist(); + await _handlePushTokensIfExist(); return []; } @@ -284,7 +288,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { return state; } _repoMutex.release(); - handlePushTokensIfExist(); + await _handlePushTokensIfExist(); return newState; } @@ -365,26 +369,20 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { /// Adds a new token and returns true if successful, false if not. Future addNewToken(Token token) async { final success = await _addOrReplaceToken(token); - await handlePushTokensIfExist(); + await _handlePushTokensIfExist(); return success; } - Future addNewTokens(List tokens) async { + /// Adds new tokens and returns the tokens that could not be added. + Future> addNewTokens(List tokens) async { final failedTokens = await _addOrReplaceTokens(tokens); - await handlePushTokensIfExist(); - return failedTokens.isEmpty; + await _handlePushTokensIfExist(); + return failedTokens; } /// Adds or replaces a token and returns true if successful, false if not. Future addOrReplaceToken(Token token) => _addOrReplaceToken(token); - /// Adds new tokens and returns the tokens that could not be added. - Future> addTokens(List tokens) async { - final failedTokens = await _addOrReplaceTokens(tokens); - await handlePushTokensIfExist(); - return failedTokens; - } - /// Adds or replaces a list of tokens and returns the tokens that could not be added or replaced. Future> addOrReplaceTokens(List tokens) => _addOrReplaceTokens(tokens); @@ -395,114 +393,114 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { Future> updateTokens(List tokens, T Function(T) updater) async => _updateTokens(tokens, updater); /// Adds, Updates or Removes tokens based on the [TokenContainer] and returns the updated tokens. - Future updateContainerTokens(TokenContainer container) async { - await initState; - Logger.info('Updating tokens from container.', name: 'token_notifier.dart#updateContainerTokens'); - final templatesToAdd = []; - final templatesToUpdate = []; - final templatesToRemove = []; - - final knownContainerTokens = state.tokens.fromContainer(container.serial)..addAll(state.maybePiTokens); - final serverTokenTemplates = container.syncedTokenTemplates; - Logger.debug( - 'App knows ${knownContainerTokens.length} of ${serverTokenTemplates.length} server tokens', - name: 'token_notifier.dart#updateContainerTokens', - ); - final appTokenTemplates = knownContainerTokens.toTemplates(); - Logger.debug('All server templates: ${serverTokenTemplates.join('\n')}', name: 'token_notifier.dart#updateContainerTokens'); - for (var serverTokenTemplate in serverTokenTemplates) { - Logger.debug( - 'Checking server token template: $serverTokenTemplate', - name: 'token_notifier.dart#updateContainerTokens', - ); - // Searches for tokens that are in the container but not in the app to add them. - // If the token is already in the app, it will be updated. - // Reduces the [tokenTemplatesApp] list to only the tokens that are in the app but not in the container. These tokens will be removed. - final appTemplate = appTokenTemplates.firstWhereOrNull( - (templateApp) => templateApp.isSameTokenAs(serverTokenTemplate), - ); - if (appTemplate == null) { - Logger.debug( - 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} not found in app. Adding them.', - name: 'token_notifier.dart#updateContainerTokens', - ); - templatesToAdd.add(serverTokenTemplate); - } else { - Logger.debug( - 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} found in app. Checking for update.', - name: 'token_notifier.dart#updateContainerTokens', - ); - appTokenTemplates.remove(appTemplate); - if (!appTemplate.hasSameValuesAs(serverTokenTemplate)) { - Logger.debug( - 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} found in app the Template but is different. Check update.', - name: 'token_notifier.dart#updateContainerTokens', - ); - // Only update the token if the template is different - templatesToUpdate.add(serverTokenTemplate); - } else { - Logger.debug( - 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} found in app. And is the same. Skipping.', - name: 'token_notifier.dart#updateContainerTokens', - ); - } - } - } - // Removes all tokens that are in the app but not in the container. - final remainingTokenTemplatesOfContainer = appTokenTemplates.where((template) => template.containerSerial == container.serial); - templatesToRemove.addAll(remainingTokenTemplatesOfContainer); - - Logger.debug( - 'Add(${templatesToAdd.length}) | Check update(${templatesToUpdate.length}) | Remove(${templatesToRemove.length})', - name: 'token_notifier.dart#updateContainerTokens', - ); - - final tokensToAdd = templatesToAdd.map((e) => e.toToken(container)).toList(); - final tokensToUpdate = []; - for (var template in templatesToUpdate) { - final token = knownContainerTokens.firstWhereOrNull((token) => token.serial == template.serial); - if (token == null) continue; - final needsUpdate = template.tokenWouldBeUpdated(token); - if (needsUpdate) { - tokensToUpdate.add(token); - } - } - final tokensToRemove = []; - for (var template in templatesToRemove) { - final token = knownContainerTokens.firstWhereOrNull((token) => token.serial == template.serial); - if (token == null) continue; - tokensToRemove.add(token); - } - - Logger.debug( - 'Add(${tokensToAdd.length}) | Update(${tokensToUpdate.length}) | Remove(${tokensToRemove.length})', - name: 'token_notifier.dart#updateContainerTokens', - ); - - if (tokensToAdd.isNotEmpty) { - await addOrReplaceTokens(tokensToAdd); - } - if (tokensToUpdate.isNotEmpty) { - await updateTokens(tokensToUpdate, (token) { - final template = templatesToUpdate.firstWhereOrNull((template) => template.serial == token.serial); - if (template == null) { - Logger.debug( - 'Not able to update token with id ${token.id} or serial ${token.serial}. No token serial/id matches the templates serial/id.', - name: 'token_notifier.dart#updateContainerTokens', - ); - return token; - } - Logger.debug( - 'Updating token with id:"${token.id}"/serial:"${token.serial}".', - name: 'token_notifier.dart#updateContainerTokens', - ); - return token.copyUpdateByTemplate(template); - }); - } - if (tokensToRemove.isNotEmpty) { - await removeTokens(tokensToRemove); - } - } + // Future updateContainerTokens(TokenContainer container) async { + // await initState; + // Logger.info('Updating tokens from container.', name: 'token_notifier.dart#updateContainerTokens'); + // final templatesToAdd = []; + // final templatesToUpdate = []; + // final templatesToRemove = []; + + // final knownContainerTokens = state.tokens.ofContainer(container.serial)..addAll(state.maybePiTokens); + // final serverTokenTemplates = container.syncedTokenTemplates; + // Logger.debug( + // 'App knows ${knownContainerTokens.length} of ${serverTokenTemplates.length} server tokens', + // name: 'token_notifier.dart#updateContainerTokens', + // ); + // final appTokenTemplates = knownContainerTokens.toTemplates(); + // Logger.debug('All server templates: ${serverTokenTemplates.join('\n')}', name: 'token_notifier.dart#updateContainerTokens'); + // for (var serverTokenTemplate in serverTokenTemplates) { + // Logger.debug( + // 'Checking server token template: $serverTokenTemplate', + // name: 'token_notifier.dart#updateContainerTokens', + // ); + // // Searches for tokens that are in the container but not in the app to add them. + // // If the token is already in the app, it will be updated. + // // Reduces the [tokenTemplatesApp] list to only the tokens that are in the app but not in the container. These tokens will be removed. + // final appTemplate = appTokenTemplates.firstWhereOrNull( + // (templateApp) => templateApp.isSameTokenAs(serverTokenTemplate), + // ); + // if (appTemplate == null) { + // Logger.debug( + // 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} not found in app. Adding them.', + // name: 'token_notifier.dart#updateContainerTokens', + // ); + // templatesToAdd.add(serverTokenTemplate); + // } else { + // Logger.debug( + // 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} found in app. Checking for update.', + // name: 'token_notifier.dart#updateContainerTokens', + // ); + // appTokenTemplates.remove(appTemplate); + // if (!appTemplate.hasSameValuesAs(serverTokenTemplate)) { + // Logger.debug( + // 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} found in app the Template but is different. Check update.', + // name: 'token_notifier.dart#updateContainerTokens', + // ); + // // Only update the token if the template is different + // templatesToUpdate.add(serverTokenTemplate); + // } else { + // Logger.debug( + // 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} found in app. And is the same. Skipping.', + // name: 'token_notifier.dart#updateContainerTokens', + // ); + // } + // } + // } + // // Removes all tokens that are in the app but not in the container. + // final remainingTokenTemplatesOfContainer = appTokenTemplates.where((template) => template.containerSerial == container.serial); + // templatesToRemove.addAll(remainingTokenTemplatesOfContainer); + + // Logger.debug( + // 'Add(${templatesToAdd.length}) | Check update(${templatesToUpdate.length}) | Remove(${templatesToRemove.length})', + // name: 'token_notifier.dart#updateContainerTokens', + // ); + + // final tokensToAdd = templatesToAdd.map((e) => e.toToken(container)).toList(); + // final tokensToUpdate = []; + // for (var template in templatesToUpdate) { + // final token = knownContainerTokens.firstWhereOrNull((token) => token.serial == template.serial); + // if (token == null) continue; + // final needsUpdate = template.tokenWouldBeUpdated(token); + // if (needsUpdate) { + // tokensToUpdate.add(token); + // } + // } + // final tokensToRemove = []; + // for (var template in templatesToRemove) { + // final token = knownContainerTokens.firstWhereOrNull((token) => token.serial == template.serial); + // if (token == null) continue; + // tokensToRemove.add(token); + // } + + // Logger.debug( + // 'Add(${tokensToAdd.length}) | Update(${tokensToUpdate.length}) | Remove(${tokensToRemove.length})', + // name: 'token_notifier.dart#updateContainerTokens', + // ); + + // if (tokensToAdd.isNotEmpty) { + // await addOrReplaceTokens(tokensToAdd); + // } + // if (tokensToUpdate.isNotEmpty) { + // await updateTokens(tokensToUpdate, (token) { + // final template = templatesToUpdate.firstWhereOrNull((template) => template.serial == token.serial); + // if (template == null) { + // Logger.debug( + // 'Not able to update token with id ${token.id} or serial ${token.serial}. No token serial/id matches the templates serial/id.', + // name: 'token_notifier.dart#updateContainerTokens', + // ); + // return token; + // } + // Logger.debug( + // 'Updating token with id:"${token.id}"/serial:"${token.serial}".', + // name: 'token_notifier.dart#updateContainerTokens', + // ); + // return token.copyUpdateByTemplate(template); + // }); + // } + // if (tokensToRemove.isNotEmpty) { + // await removeTokens(tokensToRemove); + // } + // } /// Increments the counter of a HOTPToken and returns the updated token if successful, the old token if not and null if the token does not exist. Future incrementCounter(HOTPToken token) => _updateToken(token, (p0) => p0.copyWith(counter: token.counter + 1)); @@ -595,6 +593,11 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { } } + Future removeTokensBySerials(List serials) async { + final tokens = state.tokens.where((token) => serials.contains(token.serial)).toList(); + await removeTokens(tokens); + } + Future _removePushToken(PushToken token) async { try { await _firebaseUtils.deleteFirebaseToken(); @@ -881,8 +884,10 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { @override Future handleProcessorResult(ProcessorResult result, Map args) { - // TODO: implement handleResult - throw UnimplementedError(); + if (result is ProcessorResult) { + return handleProcessorResults([result], args); + } + return Future.value(); } @override @@ -919,7 +924,6 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { origin: e.origin?.copyWith(source: TokenOriginSourceType.link) ?? TokenOriginSourceType.link.toTokenOrigin(data: 'No Origindata available', isPrivacyIdeaToken: null))) .toList(); - await handlePushTokensIfExist(); await addNewTokens(tokensToKeep); return null; } @@ -942,7 +946,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { } } - Future handlePushTokensIfExist() async { + Future _handlePushTokensIfExist() async { Logger.info('Handling push tokens if they exist.', name: 'token_notifier.dart#_handlePushTokensIfExist'); final pushTokens = state.pushTokens; if (pushTokens.isEmpty || state.pushTokens.isEmpty) { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart index caad2f89a..20129ccc0 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart @@ -6,7 +6,7 @@ part of 'token_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$tokenNotifierHash() => r'bfa2c87ccc80b4446c19926c565c3c8ed962d2a2'; +String _$tokenNotifierHash() => r'8593bd04a1a78bcc1ad77614a00ede5f345ba569'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index adbe275ad..dfc5776d0 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -1,64 +1,64 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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/widgets.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../logger.dart'; +// /* +// * privacyIDEA Authenticator +// * +// * Author: Frank Merkel +// * +// * Copyright (c) 2024 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/widgets.dart'; +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import '../../logger.dart'; -import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; -import '../../../model/riverpod_states/token_state.dart'; -import '../riverpod_providers/generated_providers/credential_notifier.dart'; -import '../riverpod_providers/generated_providers/token_container_notifier.dart'; +// import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; +// import '../../../model/riverpod_states/token_state.dart'; +// import '../riverpod_providers/generated_providers/credential_notifier.dart'; +// import '../riverpod_providers/generated_providers/token_container_notifier.dart'; -class ContainerListensToTokenState extends TokenStateListener { - ContainerListensToTokenState({ - required super.provider, - required WidgetRef ref, - }) : super( - onNewState: (TokenState? previous, TokenState next) => WidgetsBinding.instance.addPostFrameCallback((_) { - _onNewState(previous, next, ref); - }), - listenerName: 'Container', - ); +// class ContainerListensToTokenState extends TokenStateListener { +// ContainerListensToTokenState({ +// required super.provider, +// required WidgetRef ref, +// }) : super( +// onNewState: (TokenState? previous, TokenState next) => WidgetsBinding.instance.addPostFrameCallback((_) { +// _onNewState(previous, next, ref); +// }), +// listenerName: 'Container', +// ); - static Future _onNewState(TokenState? previousState, TokenState nextState, WidgetRef ref) async { - Logger.warning('New token state', name: 'TokenContainerTokenStateListener'); - final maybePiTokenTemplates = nextState.lastlyUpdatedTokens.maybePiTokens.toTemplates(); - final credentials = (await ref.read(containerCredentialsProvider.future)).credentials; - Logger.warning('Readed: $credentials', name: 'TokenContainerTokenStateListener'); - // if (maybePiTokenTemplates.isEmpty) return; - for (var credential in credentials) { - final deletedPiTokenTemplates = nextState.lastlyDeletedTokens.fromContainer(credential.serial).toTemplates(); - if (deletedPiTokenTemplates.isNotEmpty) { - Logger.warning( - 'Deleted (${deletedPiTokenTemplates.length}) tokens from container "${credential.serial}"', - name: 'TokenContainerTokenStateListener', - ); - await ref.read(tokenContainerNotifierProviderOf(credential: credential).notifier).handleDeletedTokenTemplates(deletedPiTokenTemplates); - } - if (maybePiTokenTemplates.isNotEmpty) { - Logger.warning( - 'Adding maybePiTokenTemplates (${maybePiTokenTemplates.length}) to container ${credential.serial}', - name: 'TokenContainerTokenStateListener', - ); - await ref.read(tokenContainerNotifierProviderOf(credential: credential).notifier).tryAddLocalTemplates(maybePiTokenTemplates); - } - } - } -} +// static Future _onNewState(TokenState? previousState, TokenState nextState, WidgetRef ref) async { +// Logger.warning('New token state', name: 'TokenContainerTokenStateListener'); +// final maybePiTokenTemplates = nextState.lastlyUpdatedTokens.maybePiTokens.toTemplates(); +// final credentials = (await ref.read(containerCredentialsProvider.future)).credentials; +// Logger.warning('Readed: $credentials', name: 'TokenContainerTokenStateListener'); +// // if (maybePiTokenTemplates.isEmpty) return; +// for (var credential in credentials) { +// final deletedPiTokenTemplates = nextState.lastlyDeletedTokens.ofContainer(credential.serial).toTemplates(); +// if (deletedPiTokenTemplates.isNotEmpty) { +// Logger.warning( +// 'Deleted (${deletedPiTokenTemplates.length}) tokens from container "${credential.serial}"', +// name: 'TokenContainerTokenStateListener', +// ); +// // await ref.read(tokenContainerNotifierProviderOf(credential: credential).notifier).handleDeletedTokenTemplates(deletedPiTokenTemplates); +// } +// if (maybePiTokenTemplates.isNotEmpty) { +// Logger.warning( +// 'Adding maybePiTokenTemplates (${maybePiTokenTemplates.length}) to container ${credential.serial}', +// name: 'TokenContainerTokenStateListener', +// ); +// // await ref.read(tokenContainerNotifierProviderOf(credential: credential).notifier).tryAddLocalTemplates(maybePiTokenTemplates); +// } +// } +// } +// } diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 4fe6184ae..6d5fa5dc1 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -30,7 +30,7 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/extensions/sortable_list.dart'; -import 'package:privacyidea_authenticator/processors/scheme_processors/scheme_processor_interface.dart'; +import 'package:privacyidea_authenticator/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart'; @@ -40,6 +40,7 @@ import '../model/mixins/sortable_mixin.dart'; import '../model/processor_result.dart'; import '../model/token_folder.dart'; import '../model/tokens/token.dart'; +import '../processors/scheme_processors/scheme_processor_interface.dart'; import 'customization/application_customization.dart' show ApplicationCustomization; import 'riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; import 'riverpod/riverpod_providers/generated_providers/token_notifier.dart'; diff --git a/lib/views/container_view/container_view.dart b/lib/views/container_view/container_view.dart index d4741603d..72e780661 100644 --- a/lib/views/container_view/container_view.dart +++ b/lib/views/container_view/container_view.dart @@ -28,6 +28,7 @@ import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/toke import '../../l10n/app_localizations.dart'; import '../../model/tokens/container_credentials.dart'; import '../../utils/customization/theme_extentions/action_theme.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../main_view/main_view_widgets/token_widgets/slideable_action.dart'; import '../../widgets/pi_slideable.dart'; import '../view_interface.dart'; @@ -88,12 +89,20 @@ class ContainerWidget extends ConsumerWidget { 'issuer: ${containerCredential.issuer}', 'finalizationState: ${containerCredential.finalizationState.name}', ], - trailing: IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - // Open Slidable - }, - ), + trailing: containerCredential is ContainerCredentialFinalized + ? IconButton( + icon: const Icon(Icons.sync), + onPressed: () { + final tokenState = ref.read(tokenProvider); + ref.read(containerCredentialsProvider.notifier).syncTokens(tokenState); + }, + ) + : IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + ref.read(containerCredentialsProvider.notifier).deleteCredential(containerCredential); + }, + ), ), ), ); diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart index 23e28b70c..413931649 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart @@ -20,8 +20,8 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/push_token_rollout_state_extension.dart'; -import '../../../../../model/enums/push_token_rollout_state.dart'; import '../../../../../model/mixins/sortable_mixin.dart'; import '../../../../../model/tokens/push_token.dart'; import '../token_widget.dart'; @@ -35,12 +35,6 @@ class PushTokenWidget extends TokenWidget { final PushToken token; final SortableMixin? previousSortable; final bool withDivider; - bool get rolloutFailed => switch (token.rolloutState) { - PushTokenRollOutState.generatingRSAKeyPairFailed => true, - PushTokenRollOutState.sendRSAPublicKeyFailed => true, - PushTokenRollOutState.parsingResponseFailed => true, - _ => false, - }; const PushTokenWidget( this.token, { @@ -62,7 +56,7 @@ class PushTokenWidget extends TokenWidget { child: ClipRect( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), - child: rolloutFailed ? RolloutFailedWidget(token: token) : RolloutWidget(token: token), + child: token.rolloutState.rollOutInProgress ? RolloutWidget(token: token) : StartRolloutWidget(token: token), ), ), ), 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 bb970b040..c77f8e476 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 @@ -24,19 +24,17 @@ 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/riverpod_providers/generated_providers/app_constraints_notifier.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; import '../../../../../widgets/press_button.dart'; -class RolloutFailedWidget extends ConsumerWidget { +class StartRolloutWidget extends ConsumerWidget { final PushToken token; - const RolloutFailedWidget({super.key, required this.token}); + const StartRolloutWidget({super.key, required this.token}); @override Widget build(BuildContext context, WidgetRef ref) { - final width = ref.read(appConstraintsNotifierProvider)?.maxWidth ?? 0; final localizations = AppLocalizations.of(context)!; return SingleChildScrollView( child: Column( @@ -55,21 +53,28 @@ class RolloutFailedWidget extends ConsumerWidget { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox( - width: width * 0.35, + const Expanded( + flex: 12, + child: SizedBox(), + ), + Expanded( + flex: 35, child: PressButton( onPressed: () => globalRef?.read(tokenProvider.notifier).rolloutPushToken(token) ?? Future.value(), child: Text( - localizations.retryRollout, + token.rolloutState.rolloutFailed ? localizations.retryRollout : localizations.startRollout, style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.fade, softWrap: false, ), ), ), - const SizedBox(width: 16), - SizedBox( - width: width * 0.35, + const Expanded( + flex: 6, + child: SizedBox(), + ), + Expanded( + flex: 35, child: PressButton( style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Theme.of(context).colorScheme.errorContainer)), onPressed: () => _showDialog(), @@ -81,6 +86,10 @@ class RolloutFailedWidget extends ConsumerWidget { ), ), ), + const Expanded( + flex: 12, + child: SizedBox(), + ), ], ), ], diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index ae1982187..01ee66c14 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -6,7 +6,7 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../interfaces/riverpod/state_listeners/notifier_provider_listener.dart'; -import '../model/token_container.dart'; +import '../model/token_template.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; @@ -93,22 +93,22 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { stateNotifierProviderListeners: const [], buildlessProviderListener: [ HomeWidgetTokenStateListener(provider: tokenProvider), - ContainerListensToTokenState(provider: tokenProvider, ref: ref), + // ContainerListensToTokenState(provider: tokenProvider, ref: ref), ], streamNotifierProviderListeners: [ NavigationDeepLinkListener(deeplinkProvider: deeplinkNotifierProvider), HomeWidgetDeepLinkListener(deeplinkProvider: deeplinkNotifierProvider), // TODO: Nochmal anschauen ], - asyncNotifierProviderListeners: [ - ...credentials.map( - (credential) { - return TokenStateListensToContainer( - containerProvider: tokenContainerNotifierProviderOf(credential: credential), - ref: ref, - ); - }, - ), - ], + // asyncNotifierProviderListeners: [ + // ...credentials.map( + // (credential) { + // return TokenStateListensToContainer( + // containerProvider: tokenContainerNotifierProviderOf(credential: credential), + // ref: ref, + // ); + // }, + // ), + // ], child: EasyDynamicThemeWidget( child: widget.child, ), @@ -117,25 +117,25 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { } } -class TokenStateListensToContainer extends AsyncContainerListener { - final WidgetRef ref; - TokenStateListensToContainer({ - required super.containerProvider, - required this.ref, - }) : super(onNewState: (previous, next) => _onNewState(previous, next, ref)); +// class TokenStateListensToContainer extends AsyncContainerListener { +// final WidgetRef ref; +// TokenStateListensToContainer({ +// required super.containerProvider, +// required this.ref, +// }) : super(onNewState: (previous, next) => _onNewState(previous, next, ref)); - static Future _onNewState(AsyncValue? previous, AsyncValue next, WidgetRef ref) async { - Logger.info('TokenState got new container state', name: 'TokenStateListensToContainer'); - final value = next.value; - if (value == null) return; - final provider = ref.read(tokenProvider.notifier); - provider.updateContainerTokens(value); - } -} +// static Future _onNewState(AsyncValue? previous, AsyncValue next, WidgetRef ref) async { +// Logger.info('TokenState got new container state', name: 'TokenStateListensToContainer'); +// final value = next.value; +// if (value == null) return; +// final provider = ref.read(tokenProvider.notifier); +// provider.updateContainerTokens(value); +// } +// } -abstract class AsyncContainerListener extends AsyncNotifierProviderListener { - const AsyncContainerListener({ - required TokenContainerNotifierProvider containerProvider, - required super.onNewState, - }) : super(provider: containerProvider); -} +// abstract class AsyncContainerListener extends AsyncNotifierProviderListener { +// const AsyncContainerListener({ +// required TokenContainerNotifierProvider containerProvider, +// required super.onNewState, +// }) : super(provider: containerProvider); +// } diff --git a/lib/widgets/default_refresh_indicator.dart b/lib/widgets/default_refresh_indicator.dart index 18ab16dce..8d7695038 100644 --- a/lib/widgets/default_refresh_indicator.dart +++ b/lib/widgets/default_refresh_indicator.dart @@ -36,7 +36,7 @@ class _DefaultRefreshIndicatorState extends ConsumerState=3.4.0 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 528428d20..c15f309cd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,7 +31,7 @@ dependencies: sdk: flutter app_minimizer: ^1.0.0+2 flutter_local_notifications: ^17.2.2 - home_widget: ^0.6.0 + home_widget: ^0.7.0 image: ^4.1.6 local_auth: ^2.1.6 local_auth_android: ^1.0.37 @@ -90,12 +90,13 @@ dependencies: simple_icons: ^10.1.3 path_provider: ^2.1.2 qr_flutter: ^4.1.0 - image_cropper: ^7.1.0 + image_cropper: ^8.0.2 riverpod_generator: ^2.4.0 riverpod_annotation: ^2.3.5 freezed_annotation: ^2.4.4 async: ^2.11.0 basic_utils: ^5.7.0 + diffie_hellman: ^1.2.0 diff --git a/test/unit_test/model/token/push_token_test.dart b/test/unit_test/model/token/push_token_test.dart index 209a6f122..c0661a131 100644 --- a/test/unit_test/model/token/push_token_test.dart +++ b/test/unit_test/model/token/push_token_test.dart @@ -24,7 +24,6 @@ void _testPushToken() { privateTokenKey: 'privateTokenKey', isRolledOut: true, rolloutState: PushTokenRollOutState.rolloutNotStarted, - type: 'type', sortIndex: 0, tokenImage: 'example.png', folderId: 0, diff --git a/test/unit_test/repo/hybrid_token_container_repo_test.dart b/test/unit_test/repo/hybrid_token_container_repo_test.dart index 4a28d1319..7c9406210 100644 --- a/test/unit_test/repo/hybrid_token_container_repo_test.dart +++ b/test/unit_test/repo/hybrid_token_container_repo_test.dart @@ -1,104 +1,104 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:privacyidea_authenticator/api/token_container_api_endpoint.dart'; -import 'package:privacyidea_authenticator/interfaces/repo/container_repository.dart'; -import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; -import 'package:privacyidea_authenticator/model/token_container.dart'; -import 'package:privacyidea_authenticator/model/tokens/container_credentials.dart'; -import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; -import 'package:privacyidea_authenticator/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; -import 'package:privacyidea_authenticator/repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; +// import 'package:flutter_test/flutter_test.dart'; +// import 'package:privacyidea_authenticator/api/token_container_api_endpoint.dart'; +// import 'package:privacyidea_authenticator/interfaces/repo/container_repository.dart'; +// import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; +// import 'package:privacyidea_authenticator/model/token_template.dart'; +// import 'package:privacyidea_authenticator/model/tokens/container_credentials.dart'; +// import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; +// import 'package:privacyidea_authenticator/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; +// import 'package:privacyidea_authenticator/repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; -class MockTokenContainerRepository implements TokenContainerRepository { - TokenContainer savedState; - bool _doesThrow = false; - MockTokenContainerRepository({TokenContainer? initialState}) : savedState = initialState ?? const TokenContainer.uninitialized(); +// class MockTokenContainerRepository implements TokenContainerRepository { +// TokenContainer savedState; +// bool _doesThrow = false; +// MockTokenContainerRepository({TokenContainer? initialState}) : savedState = initialState ?? const TokenContainer.uninitialized(); - void setThrow(bool value) { - _doesThrow = value; - } +// void setThrow(bool value) { +// _doesThrow = value; +// } - @override - Future loadContainerState() { - if (_doesThrow) throw Exception('Test exception'); - return Future.value(savedState); - } +// @override +// Future loadContainerState() { +// if (_doesThrow) throw Exception('Test exception'); +// return Future.value(savedState); +// } - @override - Future saveContainerState(TokenContainer containerState) { - if (_doesThrow) throw Exception('Test exception'); - savedState = containerState; - return Future.value(savedState); - } -} +// @override +// Future saveContainerState(TokenContainer containerState) { +// if (_doesThrow) throw Exception('Test exception'); +// savedState = containerState; +// return Future.value(savedState); +// } +// } -class MockTokenContainerApiEndpoint implements TokenContainerApiEndpoint { - TokenContainer state; - MockTokenContainerApiEndpoint({required TokenContainer initialState}) : state = initialState; +// class MockTokenContainerApiEndpoint implements PrivacyideaContainerApi { +// TokenContainer state; +// MockTokenContainerApiEndpoint({required TokenContainer initialState}) : state = initialState; - @override - Future fetch() { - return Future.value(state); - } +// @override +// Future fetch() { +// return Future.value(state); +// } - @override - Future sync(TokenContainer containerState) { - state = containerState.copyTransformInto(lastSyncAt: DateTime.now()); - return Future.value(state); - } +// @override +// Future sync(TokenContainer containerState) { +// state = containerState.copyTransformInto(lastSyncAt: DateTime.now()); +// return Future.value(state); +// } - @override - ContainerCredential get credential => throw UnimplementedError(); -} +// @override +// ContainerCredential get credential => throw UnimplementedError(); +// } -void main() { - _testHybridTokenContainerRepository(); -} +// void main() { +// _testHybridTokenContainerRepository(); +// } -void _testHybridTokenContainerRepository() { - group('HybridTokenContainerRepository test', () { - test('HybridTokenContainerRepository test', () async { - final token = TOTPToken( - period: 30, - id: 'TOTP_1', - algorithm: Algorithms.SHA1, - digits: 6, - secret: 'SECRET', - issuer: 'issuer', - ); - TokenContainer? remoteState = TokenContainer.synced( - serial: 'containerSerial', - description: 'description', - syncedTokenTemplates: [token.toTemplate()], - lastSyncAt: DateTime.now(), - localTokenTemplates: [], - ); - TokenContainer? localState = TokenContainer.modified( - lastModifiedAt: DateTime.now(), - lastSyncAt: DateTime.now().subtract(const Duration(days: 1)), - serial: 'containerSerial', - description: 'description', - syncedTokenTemplates: [], - localTokenTemplates: [token.toTemplate()], - ); +// void _testHybridTokenContainerRepository() { +// group('HybridTokenContainerRepository test', () { +// test('HybridTokenContainerRepository test', () async { +// final token = TOTPToken( +// period: 30, +// id: 'TOTP_1', +// algorithm: Algorithms.SHA1, +// digits: 6, +// secret: 'SECRET', +// issuer: 'issuer', +// ); +// TokenContainer? remoteState = TokenContainer.synced( +// serial: 'containerSerial', +// description: 'description', +// syncedTokenTemplates: [token.toTemplate()], +// lastSyncAt: DateTime.now(), +// localTokenTemplates: [], +// ); +// TokenContainer? localState = TokenContainer.modified( +// lastModifiedAt: DateTime.now(), +// lastSyncAt: DateTime.now().subtract(const Duration(days: 1)), +// serial: 'containerSerial', +// description: 'description', +// syncedTokenTemplates: [], +// localTokenTemplates: [token.toTemplate()], +// ); - final localRepo = MockTokenContainerRepository(initialState: localState); - final remoteRepo = RemoteTokenContainerRepository(apiEndpoint: MockTokenContainerApiEndpoint(initialState: remoteState)); +// final localRepo = MockTokenContainerRepository(initialState: localState); +// final remoteRepo = RemoteTokenContainerRepository(apiEndpoint: MockTokenContainerApiEndpoint(initialState: remoteState)); - final hybridRepo = HybridTokenContainerRepository( - localRepository: localRepo, - remoteRepository: remoteRepo, - ); - final dateTimeBefore = DateTime.now(); - final state = await hybridRepo.loadContainerState(); - await Future.delayed(const Duration(milliseconds: 1)); - final dateTimeAfter = DateTime.now(); - expect(state, isA()); - state as TokenContainerSynced; - expect(state.lastSyncAt.isAfter(dateTimeBefore), isTrue); - expect(state.lastSyncAt.isBefore(dateTimeAfter), isTrue); - expect(state.syncedTokenTemplates.length, 1); - final template = state.syncedTokenTemplates.first; - expect(template.data, token.toOtpAuthMap(), reason: 'Should be the remote state if both are changed since last sync'); - }); - }); -} +// final hybridRepo = HybridTokenContainerRepository( +// localRepository: localRepo, +// remoteRepository: remoteRepo, +// ); +// final dateTimeBefore = DateTime.now(); +// final state = await hybridRepo.loadContainerState(); +// await Future.delayed(const Duration(milliseconds: 1)); +// final dateTimeAfter = DateTime.now(); +// expect(state, isA()); +// state as TokenContainerSynced; +// expect(state.lastSyncAt.isAfter(dateTimeBefore), isTrue); +// expect(state.lastSyncAt.isBefore(dateTimeAfter), isTrue); +// expect(state.syncedTokenTemplates.length, 1); +// final template = state.syncedTokenTemplates.first; +// expect(template.data, token.toOtpAuthMap(), reason: 'Should be the remote state if both are changed since last sync'); +// }); +// }); +// } diff --git a/test/unit_test/state_notifiers/token_notifier_test.dart b/test/unit_test/state_notifiers/token_notifier_test.dart index dc79e9447..ea69e93b2 100644 --- a/test/unit_test/state_notifiers/token_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_notifier_test.dart @@ -297,7 +297,6 @@ void _testTokenNotifier() { id: '20663f77-a26e-41c3-8946-d0efb8b386d3', pin: false, tokenImage: null, - type: 'PIPUSH', sortIndex: null, folderId: null, serial: 'PIPU0006BF18', From 0952927983888ed951bdddf557e690885add74c9 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Fri, 13 Sep 2024 11:36:57 +0200 Subject: [PATCH 035/285] sync fixes --- lib/model/token_import/token_origin_data.dart | 1 - lib/model/token_template.dart | 2 +- lib/model/tokens/container_credentials.dart | 13 +++++++++---- lib/model/tokens/token.dart | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/model/token_import/token_origin_data.dart b/lib/model/token_import/token_origin_data.dart index e5e9dd02c..e784e1777 100644 --- a/lib/model/token_import/token_origin_data.dart +++ b/lib/model/token_import/token_origin_data.dart @@ -120,6 +120,5 @@ class TokenOriginData { appName: 'Unknown', data: data.toString(), createdAt: DateTime.now(), - isPrivacyIdeaToken: false, ); } diff --git a/lib/model/token_template.dart b/lib/model/token_template.dart index cbb6768c8..7d5935406 100644 --- a/lib/model/token_template.dart +++ b/lib/model/token_template.dart @@ -27,7 +27,6 @@ import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.d import 'package:privacyidea_authenticator/model/tokens/container_credentials.dart'; import 'package:privacyidea_authenticator/model/tokens/otp_token.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; import 'token_import/token_origin_data.dart'; import 'tokens/token.dart'; @@ -92,6 +91,7 @@ class TokenTemplate with _$TokenTemplate { factory TokenTemplate.fromJson(Map json) => _$TokenTemplateFromJson(json); Token toToken() { + final additionalData = Map.from(this.additionalData); if (container != null) { additionalData[Token.CONTAINER_SERIAL] = container!.serial; } diff --git a/lib/model/tokens/container_credentials.dart b/lib/model/tokens/container_credentials.dart index 50faee744..07cc791aa 100644 --- a/lib/model/tokens/container_credentials.dart +++ b/lib/model/tokens/container_credentials.dart @@ -184,10 +184,15 @@ class ContainerCredential with _$ContainerCredential { String? trySignMessage(String msg) => ecPrivateClientKey == null ? null : eccUtils.signWithPrivateKey(ecPrivateClientKey!, msg); Token addOriginToToken({required Token token, String? tokenData}) => token.copyWith( - containerSerial: () => serial, - origin: token.origin == null - ? TokenOriginData.fromContainer(container: this, tokenData: tokenData ?? '') - : token.origin!.copyWith(source: TokenOriginSourceType.container)); + containerSerial: () => serial, + origin: token.origin == null + ? TokenOriginData.fromContainer(container: this, tokenData: tokenData ?? '') + : token.origin!.copyWith( + source: TokenOriginSourceType.container, + isPrivacyIdeaToken: () => true, + data: token.origin!.data.isEmpty ? tokenData : token.origin!.data, + ), + ); } //be99ff65b1c38ae8a7d6caf8799a0cce3749fe0e|2024-08-27 14:30:58.371312Z|http://192.168.0.230:5000/container/register/finalize|SMPH0000D49C //be99ff65b1c38ae8a7d6caf8799a0cce3749fe0e|2024-08-27T14:30:58.371312+00:00|http://192.168.0.230:5000/container/register/finalize|SMPH0000D49C diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index dd33fb928..d0e89bdd6 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -204,7 +204,7 @@ abstract class Token with SortableMixin { Map get additionalData => { ID: id, - ORIGIN: origin?.data, + ORIGIN: origin, SORT_INDEX: sortIndex, FOLDER_ID: folderId, HIDDEN: isHidden, From a4a36a49597e8a8c21dd6a96cd706be656058a6b Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Fri, 13 Sep 2024 11:43:16 +0200 Subject: [PATCH 036/285] fix all imports --- lib/api/token_container_api_endpoint.dart | 5 ----- .../state_listeners/state_notifier_provider_listener.dart | 1 + lib/mains/main_netknights.dart | 2 +- lib/model/extensions/enums/introduction_extension.dart | 2 +- lib/model/tokens/hotp_token.dart | 2 +- lib/model/tokens/totp_token.dart | 4 +++- .../scheme_processors/container_credentials_processor.dart | 6 +++--- lib/repo/secure_container_credentials_repository.dart | 2 +- .../generated_providers/token_notifier.dart | 3 +-- lib/utils/utils.dart | 1 - .../labeled_dropdown_button.dart | 1 + lib/views/container_view/container_view.dart | 2 +- lib/views/import_tokens_view/pages/import_start_page.dart | 2 -- .../main_view_widgets/main_view_navigation_bar.dart | 2 +- .../main_view/main_view_widgets/main_view_tokens_list.dart | 1 + .../default_token_actions/default_lock_action.dart | 2 +- .../main_view_widgets/token_widgets/token_widget_base.dart | 2 +- lib/widgets/app_wrapper.dart | 4 ---- lib/widgets/default_refresh_indicator.dart | 3 +-- 19 files changed, 19 insertions(+), 28 deletions(-) diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index 6ef736ca4..3dc288962 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -20,28 +20,23 @@ * limitations under the License. */ import 'dart:convert'; -import 'dart:math'; import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:cryptography/cryptography.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:http/http.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/ec_key_algorithm_extension.dart'; import 'package:privacyidea_authenticator/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart'; import 'package:privacyidea_authenticator/utils/ecc_utils.dart'; import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; -import '../model/enums/ec_key_algorithm.dart'; import '../model/riverpod_states/token_state.dart'; -import '../model/token_import/token_origin_data.dart'; import '../model/token_template.dart'; import '../model/tokens/container_credentials.dart'; import '../model/tokens/token.dart'; import '../utils/globals.dart'; import '../utils/identifiers.dart'; import '../utils/logger.dart'; - import '../widgets/dialog_widgets/enter_passphrase_dialog.dart'; part 'token_container_api_endpoint.freezed.dart'; diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart index b81bbedba..fd6e8a5a4 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart @@ -19,6 +19,7 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; + import '../../../utils/logger.dart'; abstract class StateNotifierProviderListener, S> { diff --git a/lib/mains/main_netknights.dart b/lib/mains/main_netknights.dart index 95b60495b..8d104e280 100644 --- a/lib/mains/main_netknights.dart +++ b/lib/mains/main_netknights.dart @@ -32,8 +32,8 @@ import '../utils/customization/application_customization.dart'; import '../utils/globals.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; -import '../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../views/add_token_manually_view/add_token_manually_view.dart'; import '../views/container_view/container_view.dart'; import '../views/feedback_view/feedback_view.dart'; diff --git a/lib/model/extensions/enums/introduction_extension.dart b/lib/model/extensions/enums/introduction_extension.dart index 856d9abbe..d78f824ad 100644 --- a/lib/model/extensions/enums/introduction_extension.dart +++ b/lib/model/extensions/enums/introduction_extension.dart @@ -1,11 +1,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../riverpod_states/settings_state.dart'; import '../../../l10n/app_localizations.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../enums/introduction.dart'; import '../../riverpod_states/introduction_state.dart'; +import '../../riverpod_states/settings_state.dart'; extension IntroductionX on Introduction { /// Checks if the condition for the given state is fulfilled. diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index b9d201ebd..ea84ec8bf 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -20,7 +20,6 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:privacyidea_authenticator/utils/type_matchers.dart'; -import '../token_template.dart'; import 'package:uuid/uuid.dart'; import '../../utils/identifiers.dart'; @@ -28,6 +27,7 @@ import '../enums/algorithms.dart'; import '../enums/token_types.dart'; import '../extensions/enums/algorithms_extension.dart'; import '../token_import/token_origin_data.dart'; +import '../token_template.dart'; import 'otp_token.dart'; import 'token.dart'; diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index c1e197d22..8299085d3 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -20,16 +20,18 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:uuid/uuid.dart'; + import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; import '../../utils/type_matchers.dart'; import '../enums/algorithms.dart'; import '../enums/token_types.dart'; import '../extensions/enums/algorithms_extension.dart'; -import '../token_template.dart'; import '../token_import/token_origin_data.dart'; +import '../token_template.dart'; import 'otp_token.dart'; import 'token.dart'; + part 'totp_token.g.dart'; @JsonSerializable() diff --git a/lib/processors/scheme_processors/container_credentials_processor.dart b/lib/processors/scheme_processors/container_credentials_processor.dart index 8345f933f..e4b52827f 100644 --- a/lib/processors/scheme_processors/container_credentials_processor.dart +++ b/lib/processors/scheme_processors/container_credentials_processor.dart @@ -21,12 +21,12 @@ import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; import 'package:privacyidea_authenticator/utils/errors.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; + import '../../model/processor_result.dart'; +import '../../model/tokens/container_credentials.dart'; import '../../utils/identifiers.dart'; -import 'scheme_processor_interface.dart'; import '../../utils/logger.dart'; - -import '../../model/tokens/container_credentials.dart'; +import 'scheme_processor_interface.dart'; class ContainerCredentialsProcessor extends SchemeProcessor { static const resultHandlerType = TypeValidatorRequired(); diff --git a/lib/repo/secure_container_credentials_repository.dart b/lib/repo/secure_container_credentials_repository.dart index 6203718cb..83efc11f1 100644 --- a/lib/repo/secure_container_credentials_repository.dart +++ b/lib/repo/secure_container_credentials_repository.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:mutex/mutex.dart'; -import '../interfaces/repo/container_credentials_repository.dart'; +import '../interfaces/repo/container_credentials_repository.dart'; import '../model/riverpod_states/credentials_state.dart'; import '../model/tokens/container_credentials.dart'; import '../utils/logger.dart'; diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart index 326c422ca..32537fc67 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart @@ -41,7 +41,6 @@ import '../../../../model/enums/token_import_type.dart'; import '../../../../model/enums/token_origin_source_type.dart'; import '../../../../model/processor_result.dart'; import '../../../../model/riverpod_states/token_state.dart'; -import '../../../../model/token_template.dart'; import '../../../../model/tokens/hotp_token.dart'; import '../../../../model/tokens/otp_token.dart'; import '../../../../model/tokens/push_token.dart'; @@ -57,8 +56,8 @@ import '../../../privacyidea_io_client.dart'; import '../../../rsa_utils.dart'; import '../../../utils.dart'; import '../../../view_utils.dart'; -import 'settings_notifier.dart'; import '../state_providers/status_message_provider.dart'; +import 'settings_notifier.dart'; part 'token_notifier.g.dart'; diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 6d5fa5dc1..7a5dde1db 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -30,7 +30,6 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/extensions/sortable_list.dart'; -import 'package:privacyidea_authenticator/processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.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 bd662e8e1..b7d685100 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 @@ -18,6 +18,7 @@ * limitations under the License. */ import 'package:flutter/material.dart'; + import '../../../utils/logger.dart'; import '../../../widgets/deactivateable.dart'; import 'add_token_manually_row.dart'; diff --git a/lib/views/container_view/container_view.dart b/lib/views/container_view/container_view.dart index 72e780661..3b8fb2881 100644 --- a/lib/views/container_view/container_view.dart +++ b/lib/views/container_view/container_view.dart @@ -29,8 +29,8 @@ import '../../l10n/app_localizations.dart'; import '../../model/tokens/container_credentials.dart'; import '../../utils/customization/theme_extentions/action_theme.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; -import '../main_view/main_view_widgets/token_widgets/slideable_action.dart'; import '../../widgets/pi_slideable.dart'; +import '../main_view/main_view_widgets/token_widgets/slideable_action.dart'; import '../view_interface.dart'; const String groupTag = 'container-actions'; 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 92c5d9570..cd4a10752 100644 --- a/lib/views/import_tokens_view/pages/import_start_page.dart +++ b/lib/views/import_tokens_view/pages/import_start_page.dart @@ -39,9 +39,7 @@ import '../../../model/tokens/token.dart'; import '../../../processors/mixins/token_import_processor.dart'; import '../../../processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart'; import '../../../processors/token_import_file_processor/token_import_file_processor_interface.dart'; -import '../../../utils/globals.dart'; import '../../../utils/logger.dart'; -import '../../../utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart'; import '../../qr_scanner_view/qr_scanner_view.dart'; import '../import_tokens_view.dart'; import '../widgets/dialogs/qr_not_found_dialog.dart'; diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart index 115785088..8857202df 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_bar.dart @@ -22,8 +22,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/introduction.dart'; -import '../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; import '../../../widgets/focused_item_as_overlay.dart'; import '../../add_token_manually_view/add_token_manually_view.dart'; import '../../settings_view/settings_view.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 8ad4a640a..25df941b5 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 @@ -21,6 +21,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; + import '../../../model/mixins/sortable_mixin.dart'; import '../../../model/riverpod_states/settings_state.dart'; import '../../../model/token_folder.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 b228d36e0..1317f7a6e 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 @@ -24,8 +24,8 @@ import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/enums/introduction.dart'; import '../../../../../model/tokens/token.dart'; -import '../../../../../utils/globals.dart'; import '../../../../../utils/customization/theme_extentions/action_theme.dart'; +import '../../../../../utils/globals.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart index 2448603f0..47acf5102 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart @@ -28,11 +28,11 @@ import '../../../../utils/globals.dart'; import '../../../../utils/logger.dart'; import '../../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; import '../../../../utils/utils.dart'; +import '../../../../widgets/pi_slideable.dart'; import 'default_token_actions/default_delete_action.dart'; import 'default_token_actions/default_edit_action.dart'; import 'default_token_actions/default_lock_action.dart'; import 'slideable_action.dart'; -import '../../../../widgets/pi_slideable.dart'; import 'token_widget.dart'; class TokenWidgetBase extends ConsumerWidget { diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index 01ee66c14..1b3f2cefc 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -5,20 +5,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../interfaces/riverpod/state_listeners/notifier_provider_listener.dart'; -import '../model/token_template.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart'; -import '../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../utils/riverpod/state_listeners/home_widget_deep_link_listener.dart'; import '../utils/riverpod/state_listeners/home_widget_token_state_listener.dart'; import '../utils/riverpod/state_listeners/navigation_deep_link_listener.dart'; -import '../utils/riverpod/state_listeners/token_container_token_state_listener.dart'; import 'app_wrappers/single_touch_recognizer.dart'; import 'app_wrappers/state_observer.dart'; diff --git a/lib/widgets/default_refresh_indicator.dart b/lib/widgets/default_refresh_indicator.dart index 8d7695038..c246e867d 100644 --- a/lib/widgets/default_refresh_indicator.dart +++ b/lib/widgets/default_refresh_indicator.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; -import 'deactivateable_refresh_indicator.dart'; import '../utils/logger.dart'; import '../utils/push_provider.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; import '../views/main_view/main_view_widgets/loading_indicator.dart'; +import 'deactivateable_refresh_indicator.dart'; class DefaultRefreshIndicator extends ConsumerStatefulWidget { final Widget child; From 0f3770af7280a7812bdab8cd6750fc867fa235dc Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Fri, 13 Sep 2024 14:09:17 +0200 Subject: [PATCH 037/285] rollout push token fix --- lib/api/token_container_api_endpoint.dart | 71 +++++++++++-------- lib/model/token_template.dart | 6 +- lib/model/token_template.freezed.dart | 40 ++--------- lib/model/token_template.g.dart | 4 -- lib/model/tokens/otp_token.dart | 3 +- lib/model/tokens/push_token.dart | 9 +-- lib/model/tokens/token.dart | 2 +- .../default_edit_action_dialog.dart | 55 ++++++++++---- 8 files changed, 93 insertions(+), 97 deletions(-) diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index 3dc288962..7424f9d12 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -45,22 +45,6 @@ class PrivacyideaContainerApi { final PrivacyideaIOClient _ioClient; const PrivacyideaContainerApi({required PrivacyideaIOClient ioClient}) : _ioClient = ioClient; - Future _getChallenge(ContainerCredentialFinalized container) async { - final initResponse = await _ioClient.doGet(url: container.syncUrl, parameters: {CONTAINER_SERIAL: container.serial}); - try { - Logger.debug('Received container sync challenge: ${initResponse.body}', name: 'TokenContainerApiEndpoint#sync'); - final piResponse = PiServerResponse.fromResponse(initResponse); - if (piResponse.isError) { - Logger.error('Error while syncing container: ${piResponse.asError.resultError}', name: 'TokenContainerApiEndpoint#sync'); - return null; - } - return piResponse.asSuccess.resultValue; - } catch (e, s) { - Logger.error('Error while syncing container: $e', name: 'TokenContainerApiEndpoint#sync', stackTrace: s); - return null; - } - } - // Returns a tuple of updated/new tokens and serials of deleted tokens Future<(List, List)?> sync(ContainerCredentialFinalized container, TokenState tokenState) async { final containerTokenTemplates = tokenState.containerTokens(container.serial).toTemplates(); @@ -73,7 +57,7 @@ class PrivacyideaContainerApi { container: container, challenge: challenge, otpAuthMaps: [ - for (var template in [...containerTokenTemplates, ...maybePiTokensTemplates]) template.otpAuthMap + for (var template in [...containerTokenTemplates, ...maybePiTokensTemplates]) template.otpAuthMapSafeToSend ], ); if (decryptedContainerDictJson == null) return null; @@ -106,6 +90,7 @@ class PrivacyideaContainerApi { (mergedTemplatesWithSerial, deleteSerials) = _handlePiTokens( containerTokenTemplates: containerTokenTemplates, serverTokensWithSerial: serverTokensWithSerial, + container: container, ); final updatedTokens = []; @@ -144,6 +129,22 @@ class PrivacyideaContainerApi { ////// PRIVATE FUNCTIONS ////// ////////////////////////////// */ + Future _getChallenge(ContainerCredentialFinalized container) async { + final initResponse = await _ioClient.doGet(url: container.syncUrl, parameters: {CONTAINER_SERIAL: container.serial}); + try { + Logger.debug('Received container sync challenge: ${initResponse.body}', name: 'TokenContainerApiEndpoint#sync'); + final piResponse = PiServerResponse.fromResponse(initResponse); + if (piResponse.isError) { + Logger.error('Error while syncing container: ${piResponse.asError.resultError}', name: 'TokenContainerApiEndpoint#sync'); + return null; + } + return piResponse.asSuccess.resultValue; + } catch (e, s) { + Logger.error('Error while syncing container: $e', name: 'TokenContainerApiEndpoint#sync', stackTrace: s); + return null; + } + } + Future?> _getContainerDict({ required ContainerCredentialFinalized container, required ContainerChallenge challenge, @@ -204,6 +205,19 @@ class PrivacyideaContainerApi { return jsonDecode(utf8.decode(decryptedContainerDict)) as Map; } + Future> _parseNewTokens({required ContainerCredentialFinalized container, required List otpAuthUris}) async { + final newTokens = []; + for (var otpAuthUri in otpAuthUris) { + Logger.debug('Processing token: $otpAuthUri'); + var newToken = (await const OtpAuthProcessor().processUri(otpAuthUri)).firstOrNull?.asSuccess?.resultData; + if (newToken != null) { + newToken = container.addOriginToToken(token: newToken, tokenData: otpAuthUri.toString()); + newTokens.add(newToken); + } + } + return newTokens; + } + List _handleMaybePiTokens({ required List maybePiTokensTemplates, required List> serverTokensWithOtps, @@ -215,7 +229,13 @@ class PrivacyideaContainerApi { var mergedTemplate = maybePiTokensTemplates.firstWhere( (maybePiToken) => const IterableEquality().equals(otps, maybePiToken.otpValues), orElse: () => TokenTemplate.withOtps( - otps: serverTokenWithOtp[OTP_AUTH_OTP_VALUES]!, otpAuthMap: serverTokenWithOtp, container: container, checkedContainers: [container.serial]), + otps: serverTokenWithOtp[OTP_AUTH_OTP_VALUES]!, + otpAuthMap: serverTokenWithOtp, + container: container, + additionalData: { + Token.CHECKED_CONTAINERS: [container.serial], + }, + ), ); mergedTemplate = mergedTemplate.withOtpAuthData(serverTokenWithOtp); mergedTemplate = mergedTemplate.copyWith(container: container); @@ -224,22 +244,10 @@ class PrivacyideaContainerApi { return merged; } - Future> _parseNewTokens({required ContainerCredentialFinalized container, required List otpAuthUris}) async { - final newTokens = []; - for (var otpAuthUri in otpAuthUris) { - Logger.debug('Processing token: $otpAuthUri'); - var newToken = (await const OtpAuthProcessor().processUri(otpAuthUri)).firstOrNull?.asSuccess?.resultData; - if (newToken != null) { - newToken = container.addOriginToToken(token: newToken, tokenData: otpAuthUri.toString()); - newTokens.add(newToken); - } - } - return newTokens; - } - (List, List) _handlePiTokens({ required List containerTokenTemplates, required List> serverTokensWithSerial, + required ContainerCredentialFinalized container, }) { final deleteSerials = []; final mergedTemplatesWithSerial = []; @@ -250,6 +258,7 @@ class PrivacyideaContainerApi { deleteSerials.add(containerToken.serial!); } else { var mergedTemplate = containerToken.withOtpAuthData(serverToken); + mergedTemplate = mergedTemplate.copyWith(container: container); mergedTemplatesWithSerial.add(mergedTemplate); } } diff --git a/lib/model/token_template.dart b/lib/model/token_template.dart index 7d5935406..62c4e4567 100644 --- a/lib/model/token_template.dart +++ b/lib/model/token_template.dart @@ -45,10 +45,10 @@ class TokenTemplate with _$TokenTemplate { ContainerCredential? container, }) = _TokenTemplateWithSerial; + /// [ otpAuthMap ]: The map containing the OTP token data. May contain the secret. factory TokenTemplate.withOtps({ required Map otpAuthMap, required List otps, - required List checkedContainers, @Default({}) Map additionalData, ContainerCredential? container, }) = _TokenTemplateWithOtps; @@ -76,6 +76,8 @@ class TokenTemplate with _$TokenTemplate { name: CONTAINER_SERIAL, ); + Map get otpAuthMapSafeToSend => Map.from(otpAuthMap)..remove(OTP_AUTH_SECRET_BASE32); + @override operator ==(Object other) { if (other is! TokenTemplate) return false; @@ -98,7 +100,7 @@ class TokenTemplate with _$TokenTemplate { if (additionalData[Token.ORIGIN] != null) { additionalData[Token.ORIGIN] = container != null ? TokenOriginData( - appName: '${container!.serverName} ${container!.serial}', + appName: '${container!.serverName} (${container!.serial})', data: otpAuthMap.toString(), source: TokenOriginSourceType.container, isPrivacyIdeaToken: true, diff --git a/lib/model/token_template.freezed.dart b/lib/model/token_template.freezed.dart index b83bcda33..040f8485b 100644 --- a/lib/model/token_template.freezed.dart +++ b/lib/model/token_template.freezed.dart @@ -40,7 +40,6 @@ mixin _$TokenTemplate { required TResult Function( Map otpAuthMap, List otps, - List checkedContainers, Map additionalData, ContainerCredential? container) withOtps, @@ -57,7 +56,6 @@ mixin _$TokenTemplate { TResult? Function( Map otpAuthMap, List otps, - List checkedContainers, Map additionalData, ContainerCredential? container)? withOtps, @@ -74,7 +72,6 @@ mixin _$TokenTemplate { TResult Function( Map otpAuthMap, List otps, - List checkedContainers, Map additionalData, ContainerCredential? container)? withOtps, @@ -311,7 +308,6 @@ class _$TokenTemplateWithSerialImpl extends _TokenTemplateWithSerial required TResult Function( Map otpAuthMap, List otps, - List checkedContainers, Map additionalData, ContainerCredential? container) withOtps, @@ -331,7 +327,6 @@ class _$TokenTemplateWithSerialImpl extends _TokenTemplateWithSerial TResult? Function( Map otpAuthMap, List otps, - List checkedContainers, Map additionalData, ContainerCredential? container)? withOtps, @@ -351,7 +346,6 @@ class _$TokenTemplateWithSerialImpl extends _TokenTemplateWithSerial TResult Function( Map otpAuthMap, List otps, - List checkedContainers, Map additionalData, ContainerCredential? container)? withOtps, @@ -441,7 +435,6 @@ abstract class _$$TokenTemplateWithOtpsImplCopyWith<$Res> $Res call( {Map otpAuthMap, List otps, - List checkedContainers, Map additionalData, ContainerCredential? container}); @@ -464,7 +457,6 @@ class __$$TokenTemplateWithOtpsImplCopyWithImpl<$Res> $Res call({ Object? otpAuthMap = null, Object? otps = null, - Object? checkedContainers = null, Object? additionalData = null, Object? container = freezed, }) { @@ -477,10 +469,6 @@ class __$$TokenTemplateWithOtpsImplCopyWithImpl<$Res> ? _value._otps : otps // ignore: cast_nullable_to_non_nullable as List, - checkedContainers: null == checkedContainers - ? _value._checkedContainers - : checkedContainers // ignore: cast_nullable_to_non_nullable - as List, additionalData: null == additionalData ? _value._additionalData : additionalData // ignore: cast_nullable_to_non_nullable @@ -500,13 +488,11 @@ class _$TokenTemplateWithOtpsImpl extends _TokenTemplateWithOtps _$TokenTemplateWithOtpsImpl( {required final Map otpAuthMap, required final List otps, - required final List checkedContainers, final Map additionalData = const {}, this.container, final String? $type}) : _otpAuthMap = otpAuthMap, _otps = otps, - _checkedContainers = checkedContainers, _additionalData = additionalData, $type = $type ?? 'withOtps', super._(); @@ -530,15 +516,6 @@ class _$TokenTemplateWithOtpsImpl extends _TokenTemplateWithOtps return EqualUnmodifiableListView(_otps); } - final List _checkedContainers; - @override - List get checkedContainers { - if (_checkedContainers is EqualUnmodifiableListView) - return _checkedContainers; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_checkedContainers); - } - final Map _additionalData; @override @JsonKey() @@ -556,7 +533,7 @@ class _$TokenTemplateWithOtpsImpl extends _TokenTemplateWithOtps @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'TokenTemplate.withOtps(otpAuthMap: $otpAuthMap, otps: $otps, checkedContainers: $checkedContainers, additionalData: $additionalData, container: $container)'; + return 'TokenTemplate.withOtps(otpAuthMap: $otpAuthMap, otps: $otps, additionalData: $additionalData, container: $container)'; } @override @@ -566,7 +543,6 @@ class _$TokenTemplateWithOtpsImpl extends _TokenTemplateWithOtps ..add(DiagnosticsProperty('type', 'TokenTemplate.withOtps')) ..add(DiagnosticsProperty('otpAuthMap', otpAuthMap)) ..add(DiagnosticsProperty('otps', otps)) - ..add(DiagnosticsProperty('checkedContainers', checkedContainers)) ..add(DiagnosticsProperty('additionalData', additionalData)) ..add(DiagnosticsProperty('container', container)); } @@ -589,13 +565,11 @@ class _$TokenTemplateWithOtpsImpl extends _TokenTemplateWithOtps required TResult Function( Map otpAuthMap, List otps, - List checkedContainers, Map additionalData, ContainerCredential? container) withOtps, }) { - return withOtps( - otpAuthMap, otps, checkedContainers, additionalData, container); + return withOtps(otpAuthMap, otps, additionalData, container); } @override @@ -610,13 +584,11 @@ class _$TokenTemplateWithOtpsImpl extends _TokenTemplateWithOtps TResult? Function( Map otpAuthMap, List otps, - List checkedContainers, Map additionalData, ContainerCredential? container)? withOtps, }) { - return withOtps?.call( - otpAuthMap, otps, checkedContainers, additionalData, container); + return withOtps?.call(otpAuthMap, otps, additionalData, container); } @override @@ -631,15 +603,13 @@ class _$TokenTemplateWithOtpsImpl extends _TokenTemplateWithOtps TResult Function( Map otpAuthMap, List otps, - List checkedContainers, Map additionalData, ContainerCredential? container)? withOtps, required TResult orElse(), }) { if (withOtps != null) { - return withOtps( - otpAuthMap, otps, checkedContainers, additionalData, container); + return withOtps(otpAuthMap, otps, additionalData, container); } return orElse(); } @@ -687,7 +657,6 @@ abstract class _TokenTemplateWithOtps extends TokenTemplate { factory _TokenTemplateWithOtps( {required final Map otpAuthMap, required final List otps, - required final List checkedContainers, final Map additionalData, final ContainerCredential? container}) = _$TokenTemplateWithOtpsImpl; _TokenTemplateWithOtps._() : super._(); @@ -698,7 +667,6 @@ abstract class _TokenTemplateWithOtps extends TokenTemplate { @override Map get otpAuthMap; List get otps; - List get checkedContainers; @override Map get additionalData; @override diff --git a/lib/model/token_template.g.dart b/lib/model/token_template.g.dart index 7d9adad38..48b8f46bb 100644 --- a/lib/model/token_template.g.dart +++ b/lib/model/token_template.g.dart @@ -35,9 +35,6 @@ _$TokenTemplateWithOtpsImpl _$$TokenTemplateWithOtpsImplFromJson( _$TokenTemplateWithOtpsImpl( otpAuthMap: json['otpAuthMap'] as Map, otps: (json['otps'] as List).map((e) => e as String).toList(), - checkedContainers: (json['checkedContainers'] as List) - .map((e) => e as String) - .toList(), additionalData: json['additionalData'] as Map? ?? const {}, container: json['container'] == null @@ -52,7 +49,6 @@ Map _$$TokenTemplateWithOtpsImplToJson( { 'otpAuthMap': instance.otpAuthMap, 'otps': instance.otps, - 'checkedContainers': instance.checkedContainers, 'additionalData': instance.additionalData, 'container': instance.container, 'runtimeType': instance.$type, diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index 981f60e0f..1aef8ba91 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -65,7 +65,7 @@ abstract class OTPToken extends Token { @override bool isSameTokenAs(Token other) { - return super.isSameTokenAs(other) && other is OTPToken && other.algorithm == algorithm && other.digits == digits && other.secret == secret; + return super.isSameTokenAs(other) || (other is OTPToken && other.secret == secret); } @override @@ -136,7 +136,6 @@ abstract class OTPToken extends Token { otpAuthMap: toOtpAuthMap(), otps: [otpValue, nextValue], container: container, - checkedContainers: checkedContainers, additionalData: additionalData, ); } diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index 0399a68b7..7d7530ce5 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -112,14 +112,7 @@ class PushToken extends Token { } @override - bool isSameTokenAs(Token other) { - return super.isSameTokenAs(other) && - other is PushToken && - other.serial == serial && - other.privateTokenKey == privateTokenKey && - other.publicTokenKey == publicTokenKey && - other.publicServerKey == publicServerKey; - } + bool isSameTokenAs(Token other) => super.isSameTokenAs(other) || (other is PushToken && other.serial == serial); @override PushToken copyWith({ diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index d0e89bdd6..4a916d554 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -130,7 +130,7 @@ abstract class Token with SortableMixin { /// This is used to identify the same token even if the id is different. bool isSameTokenAs(Token other) { - return other.type == type; // && other.origin?.appName == origin?.appName && other.origin?.data == origin?.data; + return (other.id == id && other.type == type) || (other.serial == serial && other.type == type); } @override diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart index 1569e4d87..c3f9d169b 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart @@ -31,7 +31,7 @@ import '../../../../../widgets/dialog_widgets/default_dialog.dart'; class DefaultEditActionDialog extends ConsumerStatefulWidget { final Token token; - final List? additionalChildren; + final List additionalChildren; /// Should return false if an input is invalid. Name and image URL are validated regardless of whether the function is set or not. final bool additionalSaveValidation; @@ -39,7 +39,7 @@ class DefaultEditActionDialog extends ConsumerStatefulWidget { const DefaultEditActionDialog({ required this.token, this.onSaveButtonPressed, - this.additionalChildren, + this.additionalChildren = const [], this.additionalSaveValidation = true, super.key, }); @@ -104,18 +104,28 @@ class _DefaultEditActionDialogState extends ConsumerState TextFormField( + initialValue: text, + decoration: InputDecoration(labelText: labelText), + readOnly: true, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).disabledColor), + ); +} From 1ddc4d1e09b60fd8500ed9b1fa3b5d49e203c733 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 16 Sep 2024 14:26:45 +0200 Subject: [PATCH 038/285] fixed import error --- .../token_import/token_import_entry.dart | 15 ++ lib/model/tokens/otp_token.dart | 2 +- lib/model/tokens/push_token.dart | 25 +-- lib/repo/secure_token_repository.dart | 2 +- .../generated_providers/token_notifier.dart | 16 +- .../generated_providers/token_notifier.g.dart | 2 +- lib/utils/utils.dart | 2 +- .../pages/import_plain_tokens_page.dart | 90 +++++++-- .../conflicted_import_tokens_list.dart | 10 +- .../conflicted_import_tokens_tile.dart | 33 ++-- .../default_edit_action_dialog.dart | 185 +++++++++++++++--- .../actions/edit_hotp_token_action.dart | 11 +- .../actions/edit_push_token_action.dart | 10 +- .../qr_scanner_view/qr_scanner_view.dart | 2 +- .../qr_scanner_widget.dart | 6 +- .../enable_text_edit_after_many_taps.dart | 31 +-- 16 files changed, 331 insertions(+), 111 deletions(-) diff --git a/lib/model/token_import/token_import_entry.dart b/lib/model/token_import/token_import_entry.dart index 686e0b874..cecb3a9a1 100644 --- a/lib/model/token_import/token_import_entry.dart +++ b/lib/model/token_import/token_import_entry.dart @@ -39,4 +39,19 @@ class TokenImportEntry { assert(selectedToken == null || selectedToken == newToken || selectedToken == oldToken); return TokenImportEntry._(newToken, oldToken, selectedToken); } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is TokenImportEntry && + runtimeType == other.runtimeType && + newToken == other.newToken && + oldToken == other.oldToken && + selectedToken == other.selectedToken; + + @override + int get hashCode => Object.hashAll([newToken, oldToken, selectedToken]); + + @override + String toString() => 'TokenImportEntry{newToken: $newToken, \noldToken: $oldToken, \nselectedToken: $selectedToken}'; } diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index 1aef8ba91..80320c6c0 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -65,7 +65,7 @@ abstract class OTPToken extends Token { @override bool isSameTokenAs(Token other) { - return super.isSameTokenAs(other) || (other is OTPToken && other.secret == secret); + return super.isSameTokenAs(other) && (other is OTPToken && other.secret == secret) && other.algorithm == algorithm && other.digits == digits; } @override diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index 7d7530ce5..5922b58ec 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -100,19 +100,22 @@ class PushToken extends Token { super(type: type ?? TokenTypes.PIPUSH.name, serial: serial); @override - bool sameValuesAs(Token other) { - return super.sameValuesAs(other) && - other is PushToken && - other.fbToken == fbToken && - other.expirationDate == expirationDate && - other.sslVerify == sslVerify && - other.enrollmentCredentials == enrollmentCredentials && - other.url == url && - other.isRolledOut == isRolledOut; - } + bool sameValuesAs(Token other) => + super.sameValuesAs(other) && + other is PushToken && + other.fbToken == fbToken && + other.expirationDate == expirationDate && + other.sslVerify == sslVerify && + other.enrollmentCredentials == enrollmentCredentials && + other.url == url && + other.isRolledOut == isRolledOut && + other.rolloutState == rolloutState && + other.publicServerKey == publicServerKey && + other.publicTokenKey == publicTokenKey && + other.privateTokenKey == privateTokenKey; @override - bool isSameTokenAs(Token other) => super.isSameTokenAs(other) || (other is PushToken && other.serial == serial); + bool isSameTokenAs(Token other) => super.isSameTokenAs(other) && (other is PushToken && other.serial == serial); @override PushToken copyWith({ diff --git a/lib/repo/secure_token_repository.dart b/lib/repo/secure_token_repository.dart index 18074fbeb..cc32f8388 100644 --- a/lib/repo/secure_token_repository.dart +++ b/lib/repo/secure_token_repository.dart @@ -148,7 +148,7 @@ class SecureTokenRepository implements TokenRepository { stackTrace: StackTrace.current, ); } else { - Logger.info('Saved ${tokens.length}/${tokens.length} tokens to secure storage', name: 'secure_token_repository.dart#saveOrReplaceTokens'); + Logger.info('Saved ${tokens.length}/${tokens.length} tokens to secure storage', name: 'secure_token_repository.dart#saveOrReplaceTokens', stackTrace: StackTrace.current,); } return failedTokens; } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart index 32537fc67..1e0336f12 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart @@ -154,6 +154,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { /// Adds a list of tokens and returns the tokens that could not be added or replaced. Future> _addOrReplaceTokens(List tokens) async { + Logger.debug('Adding ${tokens.length} tokens.', name: 'token_notifier.dart#_addOrReplaceTokens', verbose: true); await _repoMutex.acquire(); tokens = tokens.map((token) { final currentId = state.currentOf(token)?.id; @@ -896,8 +897,8 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { final List resultTokens = tokenResults.getData(); final stateTokens = state.tokens; List? tokensToKeep; - final selectedType = (args['TokenImportType'] as TokenImportType?) ?? TokenImportType.qrScan; - + final tokenOriginSourceType = (args['TokenOriginSourceType'] as TokenOriginSourceType?); + var tokenImportType = (args['TokenImportType'] as TokenImportType?) ?? TokenImportType.qrScan; try { if (resultTokens.length > 1 || stateTokens.any((e) => resultTokens.first.isSameTokenAs(e) == true)) { Navigator.of(globalNavigatorKey.currentContext!).popUntil((route) => route.isFirst); @@ -906,7 +907,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { builder: (context) => ImportPlainTokensPage( titleName: AppLocalizations.of(context)!.importTokens, processorResults: tokenResults, - selectedType: selectedType, + selectedType: tokenImportType, ), ), ); @@ -919,9 +920,12 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { } if (tokensToKeep == null) return null; tokensToKeep = tokensToKeep - .map((e) => e.copyWith( - origin: e.origin?.copyWith(source: TokenOriginSourceType.link) ?? - TokenOriginSourceType.link.toTokenOrigin(data: 'No Origindata available', isPrivacyIdeaToken: null))) + .map( + (e) => e.copyWith( + origin: e.origin?.copyWith(source: tokenOriginSourceType) ?? + TokenOriginSourceType.unknown.toTokenOrigin(data: 'No Origindata available', isPrivacyIdeaToken: null), + ), + ) .toList(); await addNewTokens(tokensToKeep); return null; diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart index 20129ccc0..b9cf1ec81 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart @@ -6,7 +6,7 @@ part of 'token_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$tokenNotifierHash() => r'8593bd04a1a78bcc1ad77614a00ede5f345ba569'; +String _$tokenNotifierHash() => r'3f00c727bb255a1301795cddb05383a077b1d0fd'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 7a5dde1db..4adec9de0 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -237,7 +237,7 @@ Future scanQrCode(List resultHandlerList, Object? qrCode) a final results = resultHandlerTypeMap[resultHandlerType]!; final resultHandler = resultHandlerList.firstWhereOrNull((resultHandler) => resultHandlerType.isTypeOf(resultHandler)); if (resultHandler != null) { - await resultHandler.handleProcessorResults(results, {'TokenOriginSourceType': TokenOriginSourceType.qrScan}); + await resultHandler.handleProcessorResults(results, {'TokenOriginSourceType': TokenOriginSourceType.qrScan}); // TODO: use const IDENTIFIER variable } } } 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 9e254bb1b..8fa94a03e 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 @@ -35,6 +35,7 @@ import '../widgets/no_conflict_import_tokens_list.dart'; class ImportPlainTokensPage extends ConsumerStatefulWidget { final String titleName; final TokenImportType selectedType; + // final int numOfDuplicates; final List importedTokens; final List failedImports; factory ImportPlainTokensPage({ @@ -51,9 +52,17 @@ class ImportPlainTokensPage extends ConsumerStatefulWidget { failedImports: failedImports, titleName: titleName, selectedType: selectedType, + // numOfDuplicates: duplicates.length, ); } - const ImportPlainTokensPage._({super.key, required this.importedTokens, required this.failedImports, required this.titleName, required this.selectedType}); + const ImportPlainTokensPage._({ + super.key, + // this.numOfDuplicates = 0, + required this.importedTokens, + required this.failedImports, + required this.titleName, + required this.selectedType, + }); @override ConsumerState createState() => _ImportFileNoPwState(); @@ -62,9 +71,53 @@ class ImportPlainTokensPage extends ConsumerStatefulWidget { class _ImportFileNoPwState extends ConsumerState { ScrollController scrollController = ScrollController(); List? tokensToKeep; - List importTokenEntrys = []; bool isMaxScrollOffset = true; + List importTokenEntrys = []; + final List conflictedImports = []; + final List newImports = []; + final List appDuplicates = []; + final List importDuplicates = []; + + List _initBuildLists(List importTokenEntrys) { + for (var i = 0; i < importTokenEntrys.length; i++) { + final importTokenEntry = importTokenEntrys[i]; + if ([...newImports, ...appDuplicates, ...conflictedImports].any((import) => import.newToken.isSameTokenAs(importTokenEntry.newToken))) { + importDuplicates.add(importTokenEntry); + importTokenEntrys.remove(importTokenEntry); + i--; + continue; + } + if (importTokenEntry.oldToken == null) { + newImports.add(importTokenEntry); + continue; + } + if (importTokenEntry.newToken.sameValuesAs(importTokenEntry.oldToken!)) { + appDuplicates.add(importTokenEntry); + continue; + } + conflictedImports.add(importTokenEntry); + } + return importTokenEntrys; + } + + void _renewLists(List importTokenEntrys) { + newImports.clear(); + appDuplicates.clear(); + conflictedImports.clear(); + for (final importTokenEntry in importTokenEntrys) { + if (importTokenEntry.oldToken == null) { + newImports.add(importTokenEntry); + continue; + } + if (importTokenEntry.newToken.sameValuesAs(importTokenEntry.oldToken!)) { + appDuplicates.add(importTokenEntry); + continue; + } + conflictedImports.add(importTokenEntry); + } + } + @override void initState() { super.initState(); @@ -75,8 +128,9 @@ class _ImportFileNoPwState extends ConsumerState { map.forEach((key, value) { importTokenEntrys.add(TokenImportEntry(newToken: key, oldToken: value)); }); + importTokenEntrys = _initBuildLists(importTokenEntrys); + _setTokensToKeep(importTokenEntrys); }); - _setTokensToKeep(importTokenEntrys); }); scrollController.addListener(_updateIsMaxScrollExtent); _updateIsMaxScrollExtent(); @@ -110,20 +164,6 @@ class _ImportFileNoPwState extends ConsumerState { @override Widget build(BuildContext context) { _updateIsMaxScrollExtent(); - final List conflictedImports = []; - final List newImports = []; - final List duplicateImport = []; - for (final importTokenEntry in importTokenEntrys) { - if (importTokenEntry.oldToken == null) { - newImports.add(importTokenEntry); - continue; - } - if (importTokenEntry.newToken.sameValuesAs(importTokenEntry.oldToken!)) { - duplicateImport.add(importTokenEntry); - continue; - } - conflictedImports.add(importTokenEntry); - } return Scaffold( appBar: AppBar( @@ -170,14 +210,23 @@ class _ImportFileNoPwState extends ConsumerState { importEntries: newImports, onTap: _updateImportTokenEntry, ), - if (duplicateImport.isNotEmpty) + if (appDuplicates.isNotEmpty) NoConflictImportTokensList( - title: AppLocalizations.of(context)!.importExistingToken(duplicateImport.length), + title: AppLocalizations.of(context)!.importExistingToken(appDuplicates.length), titlePadding: const EdgeInsets.symmetric(horizontal: 40), leadingDivider: newImports.isNotEmpty || conflictedImports.isNotEmpty, - importEntries: duplicateImport, + importEntries: appDuplicates, // borderColor: null, ), + if (importDuplicates.isNotEmpty) + NoConflictImportTokensList( + title: 'The contained duplicates (${importDuplicates.length}) will be ignored.', + // AppLocalizations.of(context)!.importDuplicateToken(importDuplicates.length),'' + titlePadding: const EdgeInsets.symmetric(horizontal: 40), + leadingDivider: newImports.isNotEmpty || conflictedImports.isNotEmpty || appDuplicates.isNotEmpty, + importEntries: importDuplicates, + borderColor: null, + ), ], ), ], @@ -223,6 +272,7 @@ class _ImportFileNoPwState extends ConsumerState { void _updateImportTokenEntry(TokenImportEntry oldEntry, TokenImportEntry newEntry) { setState(() { importTokenEntrys[importTokenEntrys.indexOf(oldEntry)] = newEntry; + _renewLists(importTokenEntrys); _setTokensToKeep(importTokenEntrys); }); } diff --git a/lib/views/import_tokens_view/widgets/conflicted_import_tokens_list.dart b/lib/views/import_tokens_view/widgets/conflicted_import_tokens_list.dart index 7f5a0b6e8..d23bb2c58 100644 --- a/lib/views/import_tokens_view/widgets/conflicted_import_tokens_list.dart +++ b/lib/views/import_tokens_view/widgets/conflicted_import_tokens_list.dart @@ -65,14 +65,12 @@ class ConflictedImportTokensList extends StatelessWidget { ), const SizedBox(height: 8), ], - for (final entry in importEntries) + for (var i = 0; i < importEntries.length; i++) ConflictedImportTokensTile( - selectTokenCallback: (newEntry) => onTap(entry, newEntry), - importTokenEntry: entry, + selectTokenCallback: (newEntry) => onTap(importEntries[i], newEntry), + importTokenEntry: importEntries[i], initialScreenSize: MediaQuery.of(context).size, - key: Key( - entry.hashCode.toString(), - ), + key: Key('ConflictedImportTokensTile_$i'), ), ], ); diff --git a/lib/views/import_tokens_view/widgets/conflicted_import_tokens_tile.dart b/lib/views/import_tokens_view/widgets/conflicted_import_tokens_tile.dart index 5eae3ce2a..1420f355e 100644 --- a/lib/views/import_tokens_view/widgets/conflicted_import_tokens_tile.dart +++ b/lib/views/import_tokens_view/widgets/conflicted_import_tokens_tile.dart @@ -18,6 +18,7 @@ * limitations under the License. */ import 'package:flutter/material.dart'; +import 'package:privacyidea_authenticator/utils/logger.dart'; import '../../../model/token_import/token_import_entry.dart'; import '../../../model/tokens/token.dart'; @@ -49,25 +50,26 @@ class _ConflictedImportTokensTileState extends State } _setScrollPosition() { - if (scrollController.hasClients != true) return; - WidgetsBinding.instance.addPostFrameCallback((_) { + if (scrollController.hasClients != true) return; const fullScrollDuration = Duration(milliseconds: 300); double? scrolltarget; if (widget.importTokenEntry.oldToken == null) { if (scrollController.offset != 0.0) { - scrolltarget ??= 0.0; + scrolltarget = 0.0; } else { return; } } - if (widget.importTokenEntry.selectedToken == null) { + final isLandScape = widget.initialScreenSize.width > widget.initialScreenSize.height; + if (widget.importTokenEntry.selectedToken == null || isLandScape) { + // Mid of the Row scrolltarget ??= (scrollController.position.minScrollExtent + scrollController.position.maxScrollExtent) / 2; - } - if (widget.importTokenEntry.selectedToken == widget.importTokenEntry.oldToken) { + } else if (widget.importTokenEntry.selectedToken == widget.importTokenEntry.oldToken) { + // Show Right Tile scrolltarget ??= scrollController.position.maxScrollExtent; - } - if (widget.importTokenEntry.selectedToken == widget.importTokenEntry.newToken) { + } else if (widget.importTokenEntry.selectedToken == widget.importTokenEntry.newToken) { + // Show Left Tile scrolltarget ??= scrollController.position.minScrollExtent; } if (scrolltarget == null || scrollController.position.maxScrollExtent == 0.0) return; @@ -94,6 +96,9 @@ class _ConflictedImportTokensTileState extends State @override Widget build(BuildContext context) { final quarterScreenWidth = MediaQuery.of(context).size.width / 4; + final isLandScape = widget.initialScreenSize.width > widget.initialScreenSize.height; + Logger.debug('Building ConflictedImportTokensTile '); + _setScrollPosition(); return SingleChildScrollView( scrollDirection: Axis.horizontal, @@ -113,28 +118,30 @@ class _ConflictedImportTokensTileState extends State } }, child: SizedBox( - width: quarterScreenWidth * 6, + width: quarterScreenWidth * (isLandScape ? 4 : 6), child: Row( children: [ - if (widget.importTokenEntry.newToken != widget.importTokenEntry.selectedToken) + if (widget.importTokenEntry.newToken != widget.importTokenEntry.selectedToken && !isLandScape) SizedBox( width: quarterScreenWidth, ), NoConflictImportTokensTile( - width: widget.importTokenEntry.newToken == widget.importTokenEntry.selectedToken ? quarterScreenWidth * 3 : quarterScreenWidth * 2, + width: + widget.importTokenEntry.newToken == widget.importTokenEntry.selectedToken && !isLandScape ? quarterScreenWidth * 3 : quarterScreenWidth * 2, token: widget.importTokenEntry.newToken, selected: widget.importTokenEntry.selectedToken, alignment: Alignment.centerRight, onTap: widget.importTokenEntry.oldToken != null ? () => _setSelectedToken(widget.importTokenEntry.newToken) : null, ), NoConflictImportTokensTile( - width: widget.importTokenEntry.oldToken == widget.importTokenEntry.selectedToken ? quarterScreenWidth * 3 : quarterScreenWidth * 2, + width: + widget.importTokenEntry.oldToken == widget.importTokenEntry.selectedToken && !isLandScape ? quarterScreenWidth * 3 : quarterScreenWidth * 2, token: widget.importTokenEntry.oldToken!, selected: widget.importTokenEntry.selectedToken, alignment: Alignment.centerLeft, onTap: () => _setSelectedToken(widget.importTokenEntry.oldToken!), ), - if (widget.importTokenEntry.oldToken != null && widget.importTokenEntry.oldToken != widget.importTokenEntry.selectedToken) + if (widget.importTokenEntry.oldToken != null && widget.importTokenEntry.oldToken != widget.importTokenEntry.selectedToken && !isLandScape) SizedBox(width: quarterScreenWidth), ], ), diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart index c3f9d169b..366d6ccd6 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart @@ -56,6 +56,8 @@ class _DefaultEditActionDialogState extends ConsumerState _nameIsValid && _imageUrlIsValid && _additionalSaveValidation; + late final token = widget.token; + String? _validateName(String? value) { if (value == null || value.isNotEmpty) return null; return AppLocalizations.of(context)!.mustNotBeEmpty(AppLocalizations.of(context)!.name); @@ -73,6 +75,7 @@ class _DefaultEditActionDialogState extends ConsumerState[ + actions: [ TextButton( child: Text( appLocalizations.cancel, @@ -153,15 +185,14 @@ class _DefaultEditActionDialogState extends ConsumerState p0.copyWith(label: newLabel, tokenImage: newImageUrl)); + final edited = + await globalRef?.read(tokenProvider.notifier).updateToken(token, (p0) => p0.copyWith(label: newLabel, tokenImage: newImageUrl)); if (edited == null) { Logger.error('Token editing failed', name: 'DefaultEditAction#_showDialog'); return; } Logger.info( - 'Token edited: ${widget.token.label} -> ${edited.label}, ${widget.token.tokenImage} -> ${edited.tokenImage}', + 'Token edited: ${token.label} -> ${edited.label}, ${token.tokenImage} -> ${edited.tokenImage}', name: 'DefaultEditAction#_showDialog', ); if (context.mounted) Navigator.of(context).pop(); @@ -180,18 +211,126 @@ class _DefaultEditActionDialogState extends ConsumerState TextFormField( initialValue: text, - decoration: InputDecoration(labelText: labelText), + decoration: InputDecoration( + labelText: labelText, + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Theme.of(context).disabledColor), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Theme.of(context).disabledColor), + ), + border: UnderlineInputBorder( + borderSide: BorderSide(color: Theme.of(context).disabledColor), + ), + ), readOnly: true, + onTap: onTap, style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).disabledColor), ); } + +class EditActionExpansionTile extends StatefulWidget { + final List children; + final String title; + + const EditActionExpansionTile({ + required this.children, + required this.title, + super.key, + }); + + @override + State createState() => _EditActionExpansionTileState(); +} + +class _EditActionExpansionTileState extends State with SingleTickerProviderStateMixin { + AnimationController? controller; + Animation? animation; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (animation == null) { + controller = AnimationController( + duration: ExpansionTileTheme.of(context).expansionAnimationStyle?.duration ?? const Duration(milliseconds: 200), + vsync: this, + ); + animation = CurvedAnimation(parent: controller!, curve: Curves.fastOutSlowIn); + } + return AnimatedBuilder( + animation: animation!, + builder: (buildContext, _) => Container( + margin: const EdgeInsets.symmetric(vertical: 8.0), + padding: EdgeInsets.only(bottom: animation!.value * 16.0), + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: BorderRadius.circular(12.0), + boxShadow: [ + BoxShadow( + color: Theme.of(context).shadowColor.withOpacity(0.15), + blurRadius: 5.0, + offset: const Offset(0, 3.0), + ), + ], + ), + child: Theme( + data: Theme.of(context).copyWith(dividerColor: Colors.transparent), + child: ExpansionTile( + tilePadding: const EdgeInsets.symmetric(horizontal: 6.0), + title: Row( + children: [ + RotationTransition( + turns: Tween(begin: 0.0, end: 0.25).animate(animation!), + child: Icon( + Icons.arrow_forward_ios, + color: Theme.of(context).iconTheme.color, + ), + ), + const SizedBox(width: 8.0), + Text(widget.title, style: Theme.of(context).textTheme.titleSmall), + ], + ), + showTrailingIcon: false, + onExpansionChanged: (isExpanded) { + if (isExpanded) { + controller!.forward(); + } else { + controller!.reverse(); + } + }, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + children: widget.children, + ), + ) + ], + ), + ), + ), + ); + } +} 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 3a1ed32d6..d855dfaf5 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 @@ -80,10 +80,13 @@ class EditHOTPTokenAction extends PiSlideableAction { builder: (BuildContext context) => DefaultEditActionDialog( token: token, additionalChildren: [ - TextFormField( - initialValue: token.algorithm.name, - decoration: InputDecoration(labelText: AppLocalizations.of(context)!.algorithm), - enabled: false, + ReadOnlyTextFormField( + text: token.algorithm.name, + labelText: AppLocalizations.of(context)!.algorithm, + ), + ReadOnlyTextFormField( + text: token.counter.toString(), + labelText: AppLocalizations.of(context)!.counter, ), ], ), 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 3d3adf8fa..509965582 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 @@ -108,17 +108,9 @@ class EditPushTokenAction extends PiSlideableAction { Navigator.of(context).pop(); }, additionalChildren: [ - TextFormField( - initialValue: token.serial, - style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).disabledColor), - decoration: const InputDecoration( - labelText: 'Serial', - ), - readOnly: true, - ), EnableTextEditAfterManyTaps( controller: pushUrl, - decoration: InputDecoration(labelText: appLocalizations.pushEndpointUrl), + labelText: appLocalizations.pushEndpointUrl, autovalidateMode: AutovalidateMode.onUserInteraction, validator: (value) => _validatePushEndpointUrl(value, context), ), diff --git a/lib/views/qr_scanner_view/qr_scanner_view.dart b/lib/views/qr_scanner_view/qr_scanner_view.dart index 087f9e2f4..ab67c5975 100644 --- a/lib/views/qr_scanner_view/qr_scanner_view.dart +++ b/lib/views/qr_scanner_view/qr_scanner_view.dart @@ -94,7 +94,6 @@ class _QRScannerViewState extends State { return SafeArea( child: Stack( children: [ - const QRScannerWidget(), Scaffold( resizeToAvoidBottomInset: false, backgroundColor: Colors.transparent, @@ -106,6 +105,7 @@ class _QRScannerViewState extends State { extendBodyBehindAppBar: true, body: const SizedBox(), ), + const QRScannerWidget(), ], ), ); 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 1832a781c..13935527a 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 @@ -17,7 +17,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import 'package:flutter/material.dart'; + import 'package:flutter_zxing/flutter_zxing.dart'; class QRScannerWidget extends StatefulWidget { @@ -32,8 +34,8 @@ class _QRScannerWidgetState extends State { Widget build(BuildContext context) => Material( color: Colors.black, child: ReaderWidget( - showFlashlight: false, - showGallery: false, + showFlashlight: true, + showGallery: true, showToggleCamera: false, codeFormat: Format.qrCode, cropPercent: 0.70, diff --git a/lib/widgets/enable_text_edit_after_many_taps.dart b/lib/widgets/enable_text_edit_after_many_taps.dart index 7be9a3113..24a17c544 100644 --- a/lib/widgets/enable_text_edit_after_many_taps.dart +++ b/lib/widgets/enable_text_edit_after_many_taps.dart @@ -2,16 +2,18 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import '../views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart'; + class EnableTextEditAfterManyTaps extends StatefulWidget { final TextEditingController controller; - final InputDecoration decoration; + final String labelText; final AutovalidateMode? autovalidateMode; final String? Function(String?)? validator; final int maxTaps; const EnableTextEditAfterManyTaps({ required this.controller, - required this.decoration, + required this.labelText, this.maxTaps = 6, this.autovalidateMode, this.validator, @@ -33,7 +35,8 @@ class _EnableTextEditAfterManyTapsState extends State GestureDetector( - onDoubleTap: !enabled ? () => tapped(2) : null, - child: TextFormField( + Widget build(BuildContext context) => enabled + ? TextFormField( key: Key('${widget.controller.hashCode}_enableTextEditAfterManyTaps'), - readOnly: !enabled, - onTap: !enabled ? () => tapped(1) : null, - style: enabled ? null : Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).disabledColor), + style: null, controller: widget.controller, - decoration: widget.decoration, + decoration: InputDecoration(labelText: widget.labelText), autovalidateMode: widget.autovalidateMode, validator: widget.validator, - ), - ); + ) + : GestureDetector( + onDoubleTap: () => tapped(2), + child: ReadOnlyTextFormField( + text: widget.controller.text, + labelText: widget.labelText, + onTap: () => tapped(1), + ), + ); } From 4b2c929bca38b77352d9720013da9cc89f92ed72 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:43:26 +0200 Subject: [PATCH 039/285] error handling while sync container --- lib/api/token_container_api_endpoint.dart | 39 +++++++++++++++++-- lib/l10n/app_cs.arb | 4 +- lib/l10n/app_de.arb | 4 +- lib/l10n/app_en.arb | 18 +++++++-- lib/l10n/app_es.arb | 4 +- lib/l10n/app_fr.arb | 4 +- lib/l10n/app_nl.arb | 4 +- lib/l10n/app_pl.arb | 4 +- lib/utils/privacyidea_io_client.dart | 14 +++++-- lib/utils/view_utils.dart | 10 +++++ .../update_firebase_token_dialog.dart | 2 +- 11 files changed, 84 insertions(+), 23 deletions(-) diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index 7424f9d12..767b9283c 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -30,6 +30,7 @@ import 'package:privacyidea_authenticator/processors/scheme_processors/token_imp import 'package:privacyidea_authenticator/utils/ecc_utils.dart'; import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; +import '../l10n/app_localizations.dart'; import '../model/riverpod_states/token_state.dart'; import '../model/token_template.dart'; import '../model/tokens/container_credentials.dart'; @@ -37,6 +38,7 @@ import '../model/tokens/token.dart'; import '../utils/globals.dart'; import '../utils/identifiers.dart'; import '../utils/logger.dart'; +import '../utils/view_utils.dart'; import '../widgets/dialog_widgets/enter_passphrase_dialog.dart'; part 'token_container_api_endpoint.freezed.dart'; @@ -131,16 +133,21 @@ class PrivacyideaContainerApi { Future _getChallenge(ContainerCredentialFinalized container) async { final initResponse = await _ioClient.doGet(url: container.syncUrl, parameters: {CONTAINER_SERIAL: container.serial}); + if (initResponse.statusCode != 200) { + _showSyncStatusMessage(initResponse); + return null; + } + try { Logger.debug('Received container sync challenge: ${initResponse.body}', name: 'TokenContainerApiEndpoint#sync'); final piResponse = PiServerResponse.fromResponse(initResponse); if (piResponse.isError) { - Logger.error('Error while syncing container: ${piResponse.asError.resultError}', name: 'TokenContainerApiEndpoint#sync'); + Logger.error('Error while getting sync challenge: ${piResponse.asError.resultError}', name: 'TokenContainerApiEndpoint#sync'); return null; } return piResponse.asSuccess.resultValue; } catch (e, s) { - Logger.error('Error while syncing container: $e', name: 'TokenContainerApiEndpoint#sync', stackTrace: s); + Logger.error('Error while getting sync challenge: $e', name: 'TokenContainerApiEndpoint#sync', stackTrace: s); return null; } } @@ -174,9 +181,13 @@ class PrivacyideaContainerApi { }; final response = await _ioClient.doPost(url: Uri.parse(challenge.finalizeSyncUrl), body: body); + if (response.statusCode != 200) { + _showSyncStatusMessage(response); + return null; + } final containerSyncResponse = PiServerResponse.fromResponse(response); if (containerSyncResponse.isError) { - Logger.error('Error while syncing container: ${containerSyncResponse.asError.resultError}', name: 'TokenContainerApiEndpoint#sync'); + Logger.error('Error while reciving sync response: ${containerSyncResponse.asError.resultError}', name: 'TokenContainerApiEndpoint#sync'); return null; } @@ -264,6 +275,28 @@ class PrivacyideaContainerApi { } return (mergedTemplatesWithSerial, deleteSerials); } + + void _showSyncStatusMessage(Response response) { + assert(response.statusCode != 200, 'Status code should not be 200'); + + final AppLocalizations appLocalizations = AppLocalizations.of(globalNavigatorKey.currentContext!)!; + if (response.statusCode == 525) return; + if (response.statusCode == 500) { + return showStatusMessage(message: appLocalizations.syncContainerFailed, subMessage: appLocalizations.internalServerError(500)); + } + if (response.statusCode == 408) { + return showStatusMessage(message: appLocalizations.timeOut, subMessage: appLocalizations.checkYourNetwork); + } + if (response.statusCode == 404) { + return showStatusMessage(message: appLocalizations.syncContainerFailed, subMessage: appLocalizations.checkYourNetwork); + } + Logger.error( + 'Received unexpected response', + error: 'StatusCode: ${response.statusCode}', + name: 'TokenContainerApiEndpoint#_showStatusMessage', + stackTrace: StackTrace.current, + ); + } } abstract class PiServerResult { diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index 89a49f00c..01f3bd56e 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -201,8 +201,8 @@ "@allTokensSynchronized": { "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "synchronizationFailed": "Synchronizace následujících tokenů selhala, zkuste to znovu:", - "@synchronizationFailed": { + "syncFbTokenFailed": "Synchronizace následujících tokenů selhala, zkuste to znovu:", + "@syncFbTokenFailed": { "description": "Headline for the list of tokens where the synchronization failed." }, "tokensDoNotSupportSynchronization": "Následující tokeny nepodporují synchronizaci a musí být znovu zaregistrovány:", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 6869e7f7b..9e96c589f 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -199,8 +199,8 @@ "@allTokensSynchronized": { "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "synchronizationFailed": "Synchronisation ist für die folgenden Token fehlgeschlagen:", - "@synchronizationFailed": { + "syncFbTokenFailed": "Synchronisation ist für die folgenden Token fehlgeschlagen:", + "@syncFbTokenFailed": { "description": "Headline for the list of tokens where the synchronization failed." }, "tokensDoNotSupportSynchronization": "Die folgenden Token unterstützen keine Synchronisation und müssen erneut ausgerollt werden:", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index cfaaae8c3..f0d3eb7cc 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -203,8 +203,8 @@ "@allTokensSynchronized": { "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "synchronizationFailed": "Synchronization failed for the following tokens, please try again:", - "@synchronizationFailed": { + "syncFbTokenFailed": "Synchronization failed for the following tokens, please try again:", + "@syncFbTokenFailed": { "description": "Headline for the list of tokens where the synchronization failed." }, "tokensDoNotSupportSynchronization": "The following tokens do not support synchronization and must be rolled out again:", @@ -748,5 +748,17 @@ "example": "token data" } } - } + }, + "internalServerError": "Internal server error ({code})", + "@internalServerError": { + "placeholders": { + "code": { + "example": "500" + } + } + }, + "timeOut": "Time out", + "syncContainerFailed": "Failed to synchronize container", + "handshakeFailed" : "Handshake failed", + "checkServerCertificate": "Please check the server certificate" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index e2388d386..f9e36e3dc 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -199,8 +199,8 @@ "@allTokensSynchronized": { "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "synchronizationFailed": "La sincronización ha fallado para los siguientes tokens, por favor inténtelo de nuevo:", - "@synchronizationFailed": { + "syncFbTokenFailed": "La sincronización ha fallado para los siguientes tokens, por favor inténtelo de nuevo:", + "@syncFbTokenFailed": { "description": "Headline for the list of tokens where the synchronization failed." }, "tokensDoNotSupportSynchronization": "Las siguientes tokens no admiten la sincronización y deben volver a desplegarse:", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index e32dfec24..47021be0e 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -201,8 +201,8 @@ "@allTokensSynchronized": { "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "synchronizationFailed": "La synchronisation a échoué pour ces jetons, veuillez reéssayer:", - "@synchronizationFailed": { + "syncFbTokenFailed": "La synchronisation a échoué pour ces jetons, veuillez reéssayer:", + "@syncFbTokenFailed": { "description": "Headline for the list of tokens where the synchronization failed." }, "tokensDoNotSupportSynchronization": "Ces jetons ne supportent pas la synchronisation et doivent être de nouveau générés:", diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 26c7d5059..4dc5c6e31 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -195,8 +195,8 @@ "@allTokensSynchronized": { "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "synchronizationFailed": "Synchroniseren mislukt voor de volgende tokens, probeer het opnieuw:", - "@synchronizationFailed": { + "syncFbTokenFailed": "Synchroniseren mislukt voor de volgende tokens, probeer het opnieuw:", + "@syncFbTokenFailed": { "description": "Headline for the list of tokens where the synchronization failed." }, "tokensDoNotSupportSynchronization": "Voor de volgende tokens wordt synchroniseren niet ondersteunt, ze moeten opnieuw worden aangeleverd:", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 7dbc7eb61..d955a503c 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -197,8 +197,8 @@ "@allTokensSynchronized": { "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "synchronizationFailed": "Synchronizacja dla poniższych tokenów się nie udała, spróbuj ponownie:", - "@synchronizationFailed": { + "syncFbTokenFailed": "Synchronizacja dla poniższych tokenów się nie udała, spróbuj ponownie:", + "@syncFbTokenFailed": { "description": "Headline for the list of tokens where the synchronization failed." }, "tokensDoNotSupportSynchronization": "Następujące tokeny nie wspierają synchronizacji i muszą zostać wdrożone od nowa:", diff --git a/lib/utils/privacyidea_io_client.dart b/lib/utils/privacyidea_io_client.dart index 24f3bb374..ffcb3ebbe 100644 --- a/lib/utils/privacyidea_io_client.dart +++ b/lib/utils/privacyidea_io_client.dart @@ -176,17 +176,23 @@ class PrivacyideaIOClient { response = await ioClient.get(uri).timeout(const Duration(seconds: 15)); } on HandshakeException catch (e, _) { Logger.warning('Handshake failed. sslVerify: $sslVerify', name: 'utils.dart#doGet'); - showMessage(message: 'Handshake failed, please check the server certificate and try again.'); + showStatusMessage( + message: AppLocalizations.of(await globalContext)!.handshakeFailed, + subMessage: AppLocalizations.of(await globalContext)!.checkServerCertificate, + ); response = Response('${e.runtimeType}', 525); } on TimeoutException catch (e, _) { Logger.info('Get request timed out', name: 'utils.dart#doGet'); - response = Response('${e.runtimeType}', 408); + response = Response('${AppLocalizations.of(await globalContext)!.timeOut}', 408); } on SocketException catch (e, _) { Logger.info('Get request failed', name: 'utils.dart#doGet'); - response = Response('${e.runtimeType}', 404); + response = Response('${AppLocalizations.of(await globalContext)!.connectionFailed}', 404); + } on ClientException catch (e, _) { + Logger.info('Get request failed', name: 'utils.dart#doGet'); + response = Response('${AppLocalizations.of(await globalContext)!.connectionFailed}', 404); } catch (e, _) { Logger.warning('Something unexpected happened', name: 'utils.dart#doGet'); - response = Response('${e.runtimeType}', 404); + response = Response('${AppLocalizations.of(await globalContext)!.unexpectedError}', 520); } if (response.statusCode != 200) { diff --git a/lib/utils/view_utils.dart b/lib/utils/view_utils.dart index 2f0274f9c..410d6cd41 100644 --- a/lib/utils/view_utils.dart +++ b/lib/utils/view_utils.dart @@ -21,6 +21,7 @@ import 'package:flutter/material.dart'; import 'globals.dart'; import 'logger.dart'; +import 'riverpod/riverpod_providers/state_providers/status_message_provider.dart'; /// Shows a snackbar message to the user for a given `Duration`. void showMessage({ @@ -36,6 +37,15 @@ void showMessage({ ); } +void showStatusMessage({required String message, String? subMessage}) { + final ref = globalRef; + if (ref == null) { + Logger.warning('Could not show status message: globalRef is null'); + return; + } + ref.read(statusMessageProvider.notifier).state = (message, subMessage); +} + Future showAsyncDialog({ required WidgetBuilder builder, bool barrierDismissible = true, diff --git a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart index 679749edd..faa6fcfd4 100644 --- a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart +++ b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart @@ -86,7 +86,7 @@ class _UpdateFirebaseTokenDialogState extends ConsumerState Date: Mon, 16 Sep 2024 17:20:56 +0200 Subject: [PATCH 040/285] token container --- integration_test/views_test.dart | 2 +- lib/api/token_container_api_endpoint.dart | 16 +- lib/interfaces/api_endpoint.dart | 2 +- ...y.dart => token_container_repository.dart} | 18 +- .../riverpod_states/credentials_state.dart | 50 -- .../token_container_state.dart | 538 +--------------- ...art => token_container_state.freezed.dart} | 93 ++- ...te.g.dart => token_container_state.g.dart} | 8 +- ..._credentials.dart => token_container.dart} | 48 +- ...ezed.dart => token_container.freezed.dart} | 232 ++++--- ...dentials.g.dart => token_container.g.dart} | 63 +- lib/model/token_import/token_origin_data.dart | 4 +- lib/model/token_template.dart | 6 +- lib/model/token_template.freezed.dart | 132 ++-- lib/model/token_template.g.dart | 6 +- lib/model/tokens/day_password_token.dart | 10 +- lib/model/tokens/day_password_token.g.dart | 4 +- lib/model/tokens/hotp_token.dart | 10 +- lib/model/tokens/hotp_token.g.dart | 4 +- lib/model/tokens/otp_token.dart | 10 +- lib/model/tokens/push_token.dart | 12 +- lib/model/tokens/push_token.g.dart | 4 +- lib/model/tokens/steam_token.dart | 8 +- lib/model/tokens/steam_token.g.dart | 4 +- lib/model/tokens/token.dart | 14 +- lib/model/tokens/totp_token.dart | 8 +- lib/model/tokens/totp_token.g.dart | 4 +- .../scheme_processor_interface.dart | 4 +- ...or.dart => token_container_processor.dart} | 20 +- ...cure_container_credentials_repository.dart | 72 --- .../secure_token_container_repository.dart | 72 +++ .../credential_notifier.dart | 397 ------------ .../token_container_notifier.dart | 600 ++++++++++++------ ...g.dart => token_container_notifier.g.dart} | 112 ++-- .../token_container_token_state_listener.dart | 18 +- .../state_notifiers/token_notifier.dart | 2 +- lib/views/container_view/container_view.dart | 16 +- .../qr_scanner_button.dart | 2 +- lib/widgets/app_wrapper.dart | 12 +- lib/widgets/default_refresh_indicator.dart | 8 +- .../hybrid_token_container_repo_test.dart | 104 --- 41 files changed, 922 insertions(+), 1827 deletions(-) rename lib/interfaces/repo/{container_credentials_repository.dart => token_container_repository.dart} (55%) delete mode 100644 lib/model/riverpod_states/credentials_state.dart rename lib/model/riverpod_states/{credentials_state.freezed.dart => token_container_state.freezed.dart} (62%) rename lib/model/riverpod_states/{credentials_state.g.dart => token_container_state.g.dart} (70%) rename lib/model/{tokens/container_credentials.dart => token_container.dart} (83%) rename lib/model/{tokens/container_credentials.freezed.dart => token_container.freezed.dart} (83%) rename lib/model/{tokens/container_credentials.g.dart => token_container.g.dart} (77%) rename lib/processors/scheme_processors/{container_credentials_processor.dart => token_container_processor.dart} (69%) delete mode 100644 lib/repo/secure_container_credentials_repository.dart create mode 100644 lib/repo/secure_token_container_repository.dart delete mode 100644 lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart rename lib/utils/riverpod/riverpod_providers/generated_providers/{credential_notifier.g.dart => token_container_notifier.g.dart} (57%) delete mode 100644 test/unit_test/repo/hybrid_token_container_repo_test.dart diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index 2457e55c7..d25b4d0bd 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -168,7 +168,7 @@ Future _settingsViewTest(WidgetTester tester) async { expect(find.text(AppLocalizationsEn().errorLogTitle), findsOneWidget); expect(find.byType(SettingsGroup), findsNWidgets(6)); const qrCode = - 'otpauth://pipush/label?url=http%3A%2F%2Fwww.example.com&ttl=10&issuer=issuer&enrollment_credential=enrollmentCredentials&v=1&serial=serial&serial=serial&sslverify=0'; + 'otpauth://pipush/label?url=http%3A%2F%2Fwww.example.com&ttl=10&issuer=issuer&enrollment_container=enrollmentCredentials&v=1&serial=serial&serial=serial&sslverify=0'; final notifier = globalRef!.read(tokenProvider.notifier); await scanQrCode([notifier], qrCode); diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index 767b9283c..eacb39b4e 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -33,7 +33,7 @@ import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; import '../l10n/app_localizations.dart'; import '../model/riverpod_states/token_state.dart'; import '../model/token_template.dart'; -import '../model/tokens/container_credentials.dart'; +import '../model/token_container.dart'; import '../model/tokens/token.dart'; import '../utils/globals.dart'; import '../utils/identifiers.dart'; @@ -48,7 +48,7 @@ class PrivacyideaContainerApi { const PrivacyideaContainerApi({required PrivacyideaIOClient ioClient}) : _ioClient = ioClient; // Returns a tuple of updated/new tokens and serials of deleted tokens - Future<(List, List)?> sync(ContainerCredentialFinalized container, TokenState tokenState) async { + Future<(List, List)?> sync(TokenContainerFinalized container, TokenState tokenState) async { final containerTokenTemplates = tokenState.containerTokens(container.serial).toTemplates(); final maybePiTokensTemplates = tokenState.maybePiTokens.toTemplates(); @@ -105,7 +105,7 @@ class PrivacyideaContainerApi { return ([...updatedTokens, ...newTokens], deleteSerials); } - Future finalizeContainer(ContainerCredentialUnfinalized container, EccUtils eccUtils) async { + Future finalizeContainer(TokenContainerUnfinalized container, EccUtils eccUtils) async { final ecPrivateClientKey = container.ecPrivateClientKey; if (ecPrivateClientKey == null) return null; @@ -131,7 +131,7 @@ class PrivacyideaContainerApi { ////// PRIVATE FUNCTIONS ////// ////////////////////////////// */ - Future _getChallenge(ContainerCredentialFinalized container) async { + Future _getChallenge(TokenContainerFinalized container) async { final initResponse = await _ioClient.doGet(url: container.syncUrl, parameters: {CONTAINER_SERIAL: container.serial}); if (initResponse.statusCode != 200) { _showSyncStatusMessage(initResponse); @@ -153,7 +153,7 @@ class PrivacyideaContainerApi { } Future?> _getContainerDict({ - required ContainerCredentialFinalized container, + required TokenContainerFinalized container, required ContainerChallenge challenge, required List otpAuthMaps, }) async { @@ -216,7 +216,7 @@ class PrivacyideaContainerApi { return jsonDecode(utf8.decode(decryptedContainerDict)) as Map; } - Future> _parseNewTokens({required ContainerCredentialFinalized container, required List otpAuthUris}) async { + Future> _parseNewTokens({required TokenContainerFinalized container, required List otpAuthUris}) async { final newTokens = []; for (var otpAuthUri in otpAuthUris) { Logger.debug('Processing token: $otpAuthUri'); @@ -232,7 +232,7 @@ class PrivacyideaContainerApi { List _handleMaybePiTokens({ required List maybePiTokensTemplates, required List> serverTokensWithOtps, - required ContainerCredentialFinalized container, + required TokenContainerFinalized container, }) { final merged = []; for (var serverTokenWithOtp in serverTokensWithOtps) { @@ -258,7 +258,7 @@ class PrivacyideaContainerApi { (List, List) _handlePiTokens({ required List containerTokenTemplates, required List> serverTokensWithSerial, - required ContainerCredentialFinalized container, + required TokenContainerFinalized container, }) { final deleteSerials = []; final mergedTemplatesWithSerial = []; diff --git a/lib/interfaces/api_endpoint.dart b/lib/interfaces/api_endpoint.dart index ada68bc90..544cbbf4c 100644 --- a/lib/interfaces/api_endpoint.dart +++ b/lib/interfaces/api_endpoint.dart @@ -23,5 +23,5 @@ import '../utils/privacyidea_io_client.dart'; abstract class ApiEndpioint { ApiEndpioint(PrivacyideaIOClient ioClient); Future fetch(); - Future sync(Credential credential, Data data); + Future sync(Credential container, Data data); } diff --git a/lib/interfaces/repo/container_credentials_repository.dart b/lib/interfaces/repo/token_container_repository.dart similarity index 55% rename from lib/interfaces/repo/container_credentials_repository.dart rename to lib/interfaces/repo/token_container_repository.dart index 08a3e4ad6..3f7c8fca1 100644 --- a/lib/interfaces/repo/container_credentials_repository.dart +++ b/lib/interfaces/repo/token_container_repository.dart @@ -17,14 +17,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import '../../model/riverpod_states/credentials_state.dart'; -import '../../model/tokens/container_credentials.dart'; +import '../../model/riverpod_states/token_container_state.dart'; +import '../../model/token_container.dart'; -abstract class ContainerCredentialsRepository { - Future saveCredential(ContainerCredential credential); - Future saveCredentialsState(CredentialsState credentialsState); - Future loadCredentialsState(); - Future loadCredential(String serial); - Future deleteAllCredentials(); - Future deleteCredential(String serial); +abstract class TokenContainerRepository { + Future saveCredential(TokenContainer container); + Future saveCredentialsState(TokenContainerState containerState); + Future loadCredentialsState(); + Future loadCredential(String serial); + Future deleteAllCredentials(); + Future deleteCredential(String serial); } diff --git a/lib/model/riverpod_states/credentials_state.dart b/lib/model/riverpod_states/credentials_state.dart deleted file mode 100644 index f17fa5962..000000000 --- a/lib/model/riverpod_states/credentials_state.dart +++ /dev/null @@ -1,50 +0,0 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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:convert'; - -import 'package:collection/collection.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -import '../tokens/container_credentials.dart'; - -part 'credentials_state.freezed.dart'; -part 'credentials_state.g.dart'; - -@freezed -class CredentialsState with _$CredentialsState { - const CredentialsState._(); - const factory CredentialsState({ - required List credentials, - }) = _CredentialsState; - - ContainerCredential? credentialsOf(String containerSerial) => credentials.firstWhereOrNull((credential) => credential.serial == containerSerial); - static CredentialsState fromJsonStringList(List jsonStrings) { - final credentials = jsonStrings.map((jsonString) => ContainerCredential.fromJson(jsonDecode(jsonString))).toList(); - return CredentialsState(credentials: credentials); - } - - T? currentOf(T credential) { - final current = credentialsOf(credential.serial); - if (current is T) return current; - return null; - } - - factory CredentialsState.fromJson(Map json) => _$CredentialsStateFromJson(json); -} diff --git a/lib/model/riverpod_states/token_container_state.dart b/lib/model/riverpod_states/token_container_state.dart index 399b790c4..a7985093e 100644 --- a/lib/model/riverpod_states/token_container_state.dart +++ b/lib/model/riverpod_states/token_container_state.dart @@ -17,524 +17,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -// import 'package:json_annotation/json_annotation.dart'; +import 'dart:convert'; -// import '../token_container.dart'; +import 'package:collection/collection.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; -// part 'token_container_state.g.dart'; +import '../token_container.dart'; -// sealed class TokenContainer extends TokenContainer { -// final DateTime? lastSyncedAt; +part 'token_container_state.freezed.dart'; +part 'token_container_state.g.dart'; -// const TokenContainer({ -// required this.lastSyncedAt, -// // TokenContainer -// required super.serial, -// required super.description, -// required super.type, -// required super.syncedTokenTemplates, -// required super.localTokenTemplates, -// }); +@freezed +class TokenContainerState with _$TokenContainerState { + const TokenContainerState._(); + const factory TokenContainerState({ + required List container, + }) = _CredentialsState; -// factory TokenContainer.uninitialized(List localTokenTemplates) => -// TokenContainerUninitialized(localTokenTemplates: localTokenTemplates); + TokenContainer? containerOf(String containerSerial) => container.firstWhereOrNull((container) => container.serial == containerSerial); + static TokenContainerState fromJsonStringList(List jsonStrings) { + final container = jsonStrings.map((jsonString) => TokenContainer.fromJson(jsonDecode(jsonString))).toList(); + return TokenContainerState(container: container); + } -// factory TokenContainer.fromJson(Map json) => switch (json['type']) { -// const ('TokenContainerUninitialized') => _$TokenContainerUninitializedFromJson(json), -// const ('TokenContainerModified') => _$TokenContainerModifiedFromJson(json), -// const ('TokenContainerSynced') => _$TokenContainerSyncedFromJson(json), -// // const ('TokenContainerSyncing') => _$TokenContainerSyncingFromJson(json), -// const ('TokenContainerUnsynced') => _$TokenContainerUnsyncedFromJson(json), -// const ('TokenContainerError') => _$TokenContainerErrorFromJson(json), -// const ('TokenContainerDeactivated') => _$TokenContainerDeactivatedFromJson(json), -// const ('TokenContainerDeleted') => _$TokenContainerDeletedFromJson(json), -// _ => throw UnimplementedError(json['type']), -// }; + T? currentOf(T container) { + final current = containerOf(container.serial); + if (current is T) return current; + return null; + } -// factory TokenContainer.fromTypeString({ -// required String stateType, -// dynamic data, -// DateTime? dateTime, -// String? serial, -// String? description, -// String? type, -// List syncedTokenTemplates = const [], -// List localTokenTemplates = const [], -// DateTime? lastSyncedAt, -// }) => -// switch (stateType) { -// 'TokenContainerUninitialized' => TokenContainerUninitialized(localTokenTemplates: localTokenTemplates), -// 'TokenContainerModified' => TokenContainerModified( -// lastModifiedAt: dateTime ?? DateTime.now(), -// lastSyncedAt: lastSyncedAt ?? DateTime.now(), -// serial: serial ?? '', -// description: description ?? '', -// type: type ?? '', -// syncedTokenTemplates: syncedTokenTemplates, -// localTokenTemplates: localTokenTemplates, -// ), -// 'TokenContainerSynced' => TokenContainerSynced( -// lastSyncedAt: lastSyncedAt ?? DateTime.now(), -// serial: serial ?? '', -// description: description ?? '', -// type: type ?? '', -// syncedTokenTemplates: syncedTokenTemplates, -// ), -// // 'TokenContainerSyncing' => TokenContainerSyncing( -// // syncStartedAt: dateTime ?? DateTime.now(), -// // lastSyncedAt: lastSyncedAt, -// // serial: serial ?? '', -// // description: description ?? '', -// // type: type ?? '', -// // syncedTokenTemplates: tokenTemplates, -// // ), -// 'TokenContainerUnsynced' => TokenContainerUnsynced( -// syncAttempts: data is num ? data.floor() : 1, -// lastSyncedAt: lastSyncedAt, -// serial: serial ?? '', -// description: description ?? '', -// type: type ?? '', -// syncedTokenTemplates: syncedTokenTemplates, -// localTokenTemplates: localTokenTemplates, -// ), -// 'TokenContainerError' => TokenContainerError( -// error: data, -// lastSyncedAt: lastSyncedAt, -// serial: serial ?? '', -// description: description ?? '', -// type: type ?? '', -// syncedTokenTemplates: syncedTokenTemplates, -// localTokenTemplates: localTokenTemplates, -// ), -// 'TokenContainerDeactivated' => TokenContainerDeactivated( -// reason: data, -// deactivatedAt: dateTime ?? DateTime.now(), -// lastSyncedAt: lastSyncedAt, -// serial: serial ?? '', -// description: description ?? '', -// type: type ?? '', -// syncedTokenTemplates: syncedTokenTemplates, -// localTokenTemplates: localTokenTemplates, -// ), -// 'TokenContainerDeleted' => TokenContainerDeleted( -// reason: data, -// deletedAt: dateTime ?? DateTime.now(), -// lastSyncedAt: lastSyncedAt, -// serial: serial ?? '', -// description: description ?? '', -// type: type ?? '', -// syncedTokenTemplates: syncedTokenTemplates, -// localTokenTemplates: localTokenTemplates, -// ), -// _ => throw UnimplementedError(stateType), -// }; - -// T as({dynamic data, DateTime? dateTime}) => switch (T) { -// const (TokenContainerUninitialized) => TokenContainerUninitialized(localTokenTemplates: localTokenTemplates) as T, -// const (TokenContainerSynced) => TokenContainerSynced( -// lastSyncedAt: lastSyncedAt ?? lastSyncedAt ?? DateTime.now(), -// serial: serial, -// description: description, -// type: type, -// syncedTokenTemplates: syncedTokenTemplates, -// ) as T, -// // const (TokenContainerSyncing) => TokenContainerSyncing( -// // lastSyncedAt: lastSyncedAt, -// // syncStartedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), -// // serial: serial, -// // description: description, -// // type: type, -// // tokenTemplates: syncedTokenTemplates, -// // ) as T, -// const (TokenContainerUnsynced) => this is TokenContainerUnsynced -// ? (this as TokenContainerUnsynced).withIncrementedSyncAttempts() as T -// : TokenContainerUnsynced( -// lastSyncedAt: lastSyncedAt, -// syncAttempts: data is num ? data.floor() : 1, -// serial: serial, -// description: description, -// type: type, -// syncedTokenTemplates: syncedTokenTemplates, -// localTokenTemplates: localTokenTemplates, -// ) as T, -// const (TokenContainerError) => TokenContainerError( -// error: data, -// lastSyncedAt: lastSyncedAt, -// serial: serial, -// description: description, -// type: type, -// syncedTokenTemplates: syncedTokenTemplates, -// localTokenTemplates: localTokenTemplates, -// ) as T, -// const (TokenContainerDeactivated) => TokenContainerDeactivated( -// reason: data, -// deactivatedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), -// lastSyncedAt: lastSyncedAt, -// serial: serial, -// description: description, -// type: type, -// syncedTokenTemplates: syncedTokenTemplates, -// localTokenTemplates: localTokenTemplates, -// ) as T, -// const (TokenContainerDeleted) => TokenContainerDeleted( -// reason: data, -// deletedAt: dateTime ?? lastSyncedAt ?? DateTime.now(), -// lastSyncedAt: lastSyncedAt, -// serial: serial, -// description: description, -// type: type, -// syncedTokenTemplates: syncedTokenTemplates, -// localTokenTemplates: localTokenTemplates, -// ) as T, -// _ => throw UnimplementedError(), -// }; - -// @override -// Map toJson() { -// final json = switch (this) { -// TokenContainerUninitialized() => _$TokenContainerUninitializedToJson(this as TokenContainerUninitialized), -// TokenContainerModified() => _$TokenContainerModifiedToJson(this as TokenContainerModified), -// TokenContainerSynced() => _$TokenContainerSyncedToJson(this as TokenContainerSynced), -// // 'TokenContainerSyncing() => _$TokenContainerSyncingToJson(this as TokenContainerSyncing), -// TokenContainerUnsynced() => _$TokenContainerUnsyncedToJson(this as TokenContainerUnsynced), -// TokenContainerError() => _$TokenContainerErrorToJson(this as TokenContainerError), -// TokenContainerDeactivated() => _$TokenContainerDeactivatedToJson(this as TokenContainerDeactivated), -// TokenContainerDeleted() => _$TokenContainerDeletedToJson(this as TokenContainerDeleted), -// }; -// json['type'] = runtimeType.toString(); -// return json; -// } - -// @override -// TokenContainerModified copyWith({ -// String? serial, -// String? description, -// String? type, -// List? syncedTokenTemplates, -// List? localTokenTemplates, -// DateTime? lastSyncedAt, -// }); - -// T copyTransformInto({ -// dynamic data, -// DateTime? dateTime, -// DateTime? lastSyncedAt, -// String? serial, -// String? description, -// String? type, -// List? tokenTemplates, -// }) { -// final copied = copyWith( -// serial: serial, -// description: description, -// type: type, -// syncedTokenTemplates: tokenTemplates, -// ); -// return copied.as(data: data, dateTime: dateTime); -// } -// } - -// /// ContainerState is not initialized -// @JsonSerializable() -// class TokenContainerUninitialized extends TokenContainer { -// const TokenContainerUninitialized({required super.localTokenTemplates}) -// : super( -// lastSyncedAt: null, -// serial: '', -// description: '', -// type: '', -// syncedTokenTemplates: const [], -// ); - -// @override -// TokenContainerModified copyWith({ -// String? serial, -// String? description, -// String? type, -// List? syncedTokenTemplates, -// List? localTokenTemplates, -// DateTime? lastSyncedAt, -// }) { -// return TokenContainerModified( -// lastModifiedAt: DateTime.now(), -// lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, -// serial: serial ?? this.serial, -// description: description ?? this.description, -// type: type ?? this.type, -// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, -// localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, -// ); -// } -// } - -// /// ContainerState is modified -// @JsonSerializable() -// class TokenContainerModified extends TokenContainer { -// DateTime lastModifiedAt; -// TokenContainerModified({ -// required this.lastModifiedAt, -// required super.lastSyncedAt, -// required super.serial, -// required super.description, -// required super.type, -// required super.syncedTokenTemplates, -// required super.localTokenTemplates, -// }); - -// @override -// TokenContainerModified copyWith({ -// String? serial, -// String? description, -// String? type, -// List? syncedTokenTemplates, -// List? localTokenTemplates, -// DateTime? lastSyncedAt, -// DateTime? lastModifiedAt, -// }) { -// return TokenContainerModified( -// lastModifiedAt: lastModifiedAt ?? this.lastModifiedAt, -// lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt ?? DateTime.now(), -// serial: serial ?? this.serial, -// description: description ?? this.description, -// type: type ?? this.type, -// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, -// localTokenTemplates: localTokenTemplates ?? this.localTokenTemplates, -// ); -// } -// } - -// /// ContainerState is successfully synced with repo -// @JsonSerializable() -// class TokenContainerSynced extends TokenContainer { -// TokenContainerSynced({ -// required super.serial, -// required super.description, -// required super.type, -// required super.syncedTokenTemplates, -// DateTime? lastSyncedAt, -// }) : super(lastSyncedAt: lastSyncedAt ?? DateTime.now(), localTokenTemplates: const []); - -// @override -// TokenContainerModified copyWith({ -// String? serial, -// String? description, -// String? type, -// List? syncedTokenTemplates, -// List? localTokenTemplates, -// DateTime? lastSyncedAt, -// DateTime? lastModifiedAt, -// }) { -// return TokenContainerModified( -// lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, -// serial: serial ?? this.serial, -// description: description ?? this.description, -// type: type ?? this.type, -// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, -// localTokenTemplates: const [], -// lastModifiedAt: lastModifiedAt ?? DateTime.now(), -// ); -// } -// } - -// // /// ContainerState is currently syncing -// // @JsonSerializable() -// // class TokenContainerSyncing extends TokenContainer { -// // final DateTime syncStartedAt; -// // final Duration timeOut; -// // get isSyncing => DateTime.now().difference(syncStartedAt) < timeOut; -// // get isTimedOut => DateTime.now().difference(syncStartedAt) > timeOut; -// // const TokenContainerSyncing({ -// // required this.syncStartedAt, -// // this.timeOut = const Duration(seconds: 30), -// // required super.lastSyncedAt, -// // required super.serial, -// // required super.description, -// // required super.type, -// // required super.tokenTemplates, -// // }); - -// // @override -// // TokenContainerSyncing copyWith({ -// // String? serial, -// // String? description, -// // String? type, -// // List? syncedTokenTemplates, -// // DateTime? lastSyncedAt, -// // DateTime? syncStartedAt, -// // Duration? timeOut, -// // }) { -// // return TokenContainerSyncing( -// // syncStartedAt: syncStartedAt ?? this.syncStartedAt, -// // timeOut: timeOut ?? this.timeOut, -// // lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, -// // serial: serial ?? this.serial, -// // description: description ?? this.description, -// // type: type ?? this.type, -// // tokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, -// // ); -// // } -// // } - -// /// ContainerState is failed last sync attempt -// @JsonSerializable() -// class TokenContainerUnsynced extends TokenContainer { -// final int syncAttempts; -// final dynamic lastError; - -// TokenContainerUnsynced({ -// this.syncAttempts = 1, -// this.lastError, -// required super.lastSyncedAt, -// required super.serial, -// required super.description, -// required super.type, -// required super.syncedTokenTemplates, -// required super.localTokenTemplates, -// }); - -// TokenContainerUnsynced withIncrementedSyncAttempts() => withSyncAttempts(syncAttempts + 1); -// TokenContainerUnsynced withSyncAttempts(int syncAttempts) => TokenContainerUnsynced( -// syncAttempts: syncAttempts, -// lastSyncedAt: lastSyncedAt, -// serial: serial, -// description: description, -// type: type, -// syncedTokenTemplates: syncedTokenTemplates, -// localTokenTemplates: localTokenTemplates, -// ); - -// @override -// TokenContainerModified copyWith({ -// String? serial, -// String? description, -// String? type, -// List? syncedTokenTemplates, -// List? localTokenTemplates, -// DateTime? lastSyncedAt, -// DateTime? lastModifiedAt, -// int? syncAttempts, -// }) { -// return TokenContainerModified( -// lastModifiedAt: lastModifiedAt ?? DateTime.now(), -// lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, -// serial: serial ?? this.serial, -// description: description ?? this.description, -// type: type ?? this.type, -// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, -// localTokenTemplates: this.localTokenTemplates, -// ); -// } -// } - -// /// ContainerState is in error state -// @JsonSerializable() -// class TokenContainerError extends TokenContainer { -// final dynamic error; -// TokenContainerError({ -// required this.error, -// super.lastSyncedAt, -// super.serial = 'Error', -// String? description, -// super.type = 'Error', -// super.syncedTokenTemplates = const [], -// super.localTokenTemplates = const [], -// }) : super(description: description ?? error.toString()); - -// @override -// TokenContainerModified copyWith({ -// String? serial, -// String? description, -// String? type, -// List? syncedTokenTemplates, -// List? localTokenTemplates, -// DateTime? lastSyncedAt, -// DateTime? lastModifiedAt, -// }) { -// return TokenContainerModified( -// lastModifiedAt: lastModifiedAt ?? DateTime.now(), -// lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, -// serial: serial ?? this.serial, -// description: description ?? this.description, -// type: type ?? this.type, -// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, -// localTokenTemplates: this.localTokenTemplates, -// ); -// } -// } - -// /// ContainerState is deactivated -// @JsonSerializable() -// class TokenContainerDeactivated extends TokenContainer { -// final DateTime deactivatedAt; -// final dynamic reason; -// TokenContainerDeactivated({ -// required this.reason, -// required this.deactivatedAt, -// required super.lastSyncedAt, -// required super.serial, -// required super.description, -// required super.type, -// required super.syncedTokenTemplates, -// required super.localTokenTemplates, -// }); - -// @override -// TokenContainerModified copyWith({ -// String? serial, -// String? description, -// String? type, -// List? syncedTokenTemplates, -// List? localTokenTemplates, -// DateTime? lastSyncedAt, -// DateTime? lastModifiedAt, -// }) { -// return TokenContainerModified( -// lastModifiedAt: lastModifiedAt ?? DateTime.now(), -// lastSyncedAt: lastSyncedAt ?? lastSyncedAt, -// serial: serial ?? this.serial, -// description: description ?? this.description, -// type: type ?? this.type, -// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, -// localTokenTemplates: this.localTokenTemplates, -// ); -// } -// } - -// /// ContainerState is deleted repo-sseriale -// @JsonSerializable() -// class TokenContainerDeleted extends TokenContainer { -// final DateTime deletedAt; -// final dynamic reason; -// TokenContainerDeleted({ -// required this.reason, -// required this.deletedAt, -// required super.lastSyncedAt, -// required super.serial, -// required super.description, -// required super.type, -// required super.syncedTokenTemplates, -// required super.localTokenTemplates, -// }); - -// @override -// TokenContainerModified copyWith({ -// String? serial, -// String? description, -// String? type, -// List? syncedTokenTemplates, -// List? localTokenTemplates, -// DateTime? lastSyncedAt, -// DateTime? lastModifiedAt, - -// }) { -// return TokenContainerModified( -// lastModifiedAt: lastModifiedAt ?? DateTime.now(), -// lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt, -// serial: serial ?? this.serial, -// description: description ?? this.description, -// type: type ?? this.type, -// syncedTokenTemplates: syncedTokenTemplates ?? this.syncedTokenTemplates, -// localTokenTemplates: this.localTokenTemplates, -// ); -// } -// } + factory TokenContainerState.fromJson(Map json) => _$TokenContainerStateFromJson(json); +} diff --git a/lib/model/riverpod_states/credentials_state.freezed.dart b/lib/model/riverpod_states/token_container_state.freezed.dart similarity index 62% rename from lib/model/riverpod_states/credentials_state.freezed.dart rename to lib/model/riverpod_states/token_container_state.freezed.dart index 09deac54c..a0be8ab7f 100644 --- a/lib/model/riverpod_states/credentials_state.freezed.dart +++ b/lib/model/riverpod_states/token_container_state.freezed.dart @@ -3,7 +3,7 @@ // ignore_for_file: type=lint // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark -part of 'credentials_state.dart'; +part of 'token_container_state.dart'; // ************************************************************************** // FreezedGenerator @@ -14,91 +14,90 @@ T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); -CredentialsState _$CredentialsStateFromJson(Map json) { +TokenContainerState _$TokenContainerStateFromJson(Map json) { return _CredentialsState.fromJson(json); } /// @nodoc -mixin _$CredentialsState { - List get credentials => - throw _privateConstructorUsedError; +mixin _$TokenContainerState { + List get container => throw _privateConstructorUsedError; - /// Serializes this CredentialsState to a JSON map. + /// Serializes this TokenContainerState to a JSON map. Map toJson() => throw _privateConstructorUsedError; - /// Create a copy of CredentialsState + /// Create a copy of TokenContainerState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $CredentialsStateCopyWith get copyWith => + $TokenContainerStateCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class $CredentialsStateCopyWith<$Res> { - factory $CredentialsStateCopyWith( - CredentialsState value, $Res Function(CredentialsState) then) = - _$CredentialsStateCopyWithImpl<$Res, CredentialsState>; +abstract class $TokenContainerStateCopyWith<$Res> { + factory $TokenContainerStateCopyWith( + TokenContainerState value, $Res Function(TokenContainerState) then) = + _$TokenContainerStateCopyWithImpl<$Res, TokenContainerState>; @useResult - $Res call({List credentials}); + $Res call({List container}); } /// @nodoc -class _$CredentialsStateCopyWithImpl<$Res, $Val extends CredentialsState> - implements $CredentialsStateCopyWith<$Res> { - _$CredentialsStateCopyWithImpl(this._value, this._then); +class _$TokenContainerStateCopyWithImpl<$Res, $Val extends TokenContainerState> + implements $TokenContainerStateCopyWith<$Res> { + _$TokenContainerStateCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of CredentialsState + /// Create a copy of TokenContainerState /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? credentials = null, + Object? container = null, }) { return _then(_value.copyWith( - credentials: null == credentials - ? _value.credentials - : credentials // ignore: cast_nullable_to_non_nullable - as List, + container: null == container + ? _value.container + : container // ignore: cast_nullable_to_non_nullable + as List, ) as $Val); } } /// @nodoc abstract class _$$CredentialsStateImplCopyWith<$Res> - implements $CredentialsStateCopyWith<$Res> { + implements $TokenContainerStateCopyWith<$Res> { factory _$$CredentialsStateImplCopyWith(_$CredentialsStateImpl value, $Res Function(_$CredentialsStateImpl) then) = __$$CredentialsStateImplCopyWithImpl<$Res>; @override @useResult - $Res call({List credentials}); + $Res call({List container}); } /// @nodoc class __$$CredentialsStateImplCopyWithImpl<$Res> - extends _$CredentialsStateCopyWithImpl<$Res, _$CredentialsStateImpl> + extends _$TokenContainerStateCopyWithImpl<$Res, _$CredentialsStateImpl> implements _$$CredentialsStateImplCopyWith<$Res> { __$$CredentialsStateImplCopyWithImpl(_$CredentialsStateImpl _value, $Res Function(_$CredentialsStateImpl) _then) : super(_value, _then); - /// Create a copy of CredentialsState + /// Create a copy of TokenContainerState /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? credentials = null, + Object? container = null, }) { return _then(_$CredentialsStateImpl( - credentials: null == credentials - ? _value._credentials - : credentials // ignore: cast_nullable_to_non_nullable - as List, + container: null == container + ? _value._container + : container // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -106,25 +105,24 @@ class __$$CredentialsStateImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() class _$CredentialsStateImpl extends _CredentialsState { - const _$CredentialsStateImpl( - {required final List credentials}) - : _credentials = credentials, + const _$CredentialsStateImpl({required final List container}) + : _container = container, super._(); factory _$CredentialsStateImpl.fromJson(Map json) => _$$CredentialsStateImplFromJson(json); - final List _credentials; + final List _container; @override - List get credentials { - if (_credentials is EqualUnmodifiableListView) return _credentials; + List get container { + if (_container is EqualUnmodifiableListView) return _container; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_credentials); + return EqualUnmodifiableListView(_container); } @override String toString() { - return 'CredentialsState(credentials: $credentials)'; + return 'TokenContainerState(container: $container)'; } @override @@ -133,15 +131,15 @@ class _$CredentialsStateImpl extends _CredentialsState { (other.runtimeType == runtimeType && other is _$CredentialsStateImpl && const DeepCollectionEquality() - .equals(other._credentials, _credentials)); + .equals(other._container, _container)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, const DeepCollectionEquality().hash(_credentials)); + int get hashCode => + Object.hash(runtimeType, const DeepCollectionEquality().hash(_container)); - /// Create a copy of CredentialsState + /// Create a copy of TokenContainerState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @@ -158,19 +156,18 @@ class _$CredentialsStateImpl extends _CredentialsState { } } -abstract class _CredentialsState extends CredentialsState { +abstract class _CredentialsState extends TokenContainerState { const factory _CredentialsState( - {required final List credentials}) = - _$CredentialsStateImpl; + {required final List container}) = _$CredentialsStateImpl; const _CredentialsState._() : super._(); factory _CredentialsState.fromJson(Map json) = _$CredentialsStateImpl.fromJson; @override - List get credentials; + List get container; - /// Create a copy of CredentialsState + /// Create a copy of TokenContainerState /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) diff --git a/lib/model/riverpod_states/credentials_state.g.dart b/lib/model/riverpod_states/token_container_state.g.dart similarity index 70% rename from lib/model/riverpod_states/credentials_state.g.dart rename to lib/model/riverpod_states/token_container_state.g.dart index 2fdd4d4cc..48ed1eff6 100644 --- a/lib/model/riverpod_states/credentials_state.g.dart +++ b/lib/model/riverpod_states/token_container_state.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'credentials_state.dart'; +part of 'token_container_state.dart'; // ************************************************************************** // JsonSerializableGenerator @@ -9,13 +9,13 @@ part of 'credentials_state.dart'; _$CredentialsStateImpl _$$CredentialsStateImplFromJson( Map json) => _$CredentialsStateImpl( - credentials: (json['credentials'] as List) - .map((e) => ContainerCredential.fromJson(e as Map)) + container: (json['container'] as List) + .map((e) => TokenContainer.fromJson(e as Map)) .toList(), ); Map _$$CredentialsStateImplToJson( _$CredentialsStateImpl instance) => { - 'credentials': instance.credentials, + 'container': instance.container, }; diff --git a/lib/model/tokens/container_credentials.dart b/lib/model/token_container.dart similarity index 83% rename from lib/model/tokens/container_credentials.dart rename to lib/model/token_container.dart index 07cc791aa..ecbc7ab94 100644 --- a/lib/model/tokens/container_credentials.dart +++ b/lib/model/token_container.dart @@ -28,21 +28,21 @@ import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; import 'package:privacyidea_authenticator/utils/type_matchers.dart'; -import '../../utils/ecc_utils.dart'; -import '../../utils/logger.dart'; -import '../enums/container_finalization_state.dart'; -import '../enums/ec_key_algorithm.dart'; -import '../enums/token_origin_source_type.dart'; -import '../token_import/token_origin_data.dart'; +import '../utils/ecc_utils.dart'; +import '../utils/logger.dart'; +import 'enums/container_finalization_state.dart'; +import 'enums/ec_key_algorithm.dart'; +import 'enums/token_origin_source_type.dart'; +import 'token_import/token_origin_data.dart'; -part 'container_credentials.freezed.dart'; -part 'container_credentials.g.dart'; +part 'token_container.freezed.dart'; +part 'token_container.g.dart'; @Freezed(toStringOverride: false) -class ContainerCredential with _$ContainerCredential { +class TokenContainer with _$TokenContainer { static const SERIAL = 'serial'; static const eccUtils = EccUtils(); - const ContainerCredential._(); + const TokenContainer._(); // example: pia://container/SMPH00134123 // ?issuer=privacyIDEA @@ -53,7 +53,7 @@ class ContainerCredential with _$ContainerCredential { // &key_algorithm=secp384r1 // &hash_algorithm=SHA256 // &passphrase=Enter%20your%20passphrase - factory ContainerCredential.fromUriMap(Map uriMap) { + factory TokenContainer.fromUriMap(Map uriMap) { uriMap = validateMap( map: uriMap, validators: { @@ -68,7 +68,7 @@ class ContainerCredential with _$ContainerCredential { }, name: 'Container', ); - return ContainerCredential.unfinalized( + return TokenContainer.unfinalized( issuer: uriMap[CONTAINER_ISSUER], nonce: uriMap[CONTAINER_NONCE], timestamp: uriMap[CONTAINER_TIMESTAMP], @@ -80,7 +80,7 @@ class ContainerCredential with _$ContainerCredential { ); } - const factory ContainerCredential.unfinalized({ + const factory TokenContainer.unfinalized({ required String issuer, required String nonce, required DateTime timestamp, @@ -95,9 +95,9 @@ class ContainerCredential with _$ContainerCredential { String? publicServerKey, String? publicClientKey, String? privateClientKey, - }) = ContainerCredentialUnfinalized; + }) = TokenContainerUnfinalized; - const factory ContainerCredential.finalized({ + const factory TokenContainer.finalized({ required String issuer, required String nonce, required DateTime timestamp, @@ -111,14 +111,14 @@ class ContainerCredential with _$ContainerCredential { required String publicServerKey, required String publicClientKey, required String privateClientKey, - }) = ContainerCredentialFinalized; + }) = TokenContainerFinalized; - ContainerCredentialFinalized? finalize({ + TokenContainerFinalized? finalize({ ECPublicKey? publicServerKey, AsymmetricKeyPair? clientKeyPair, Uri? syncUrl, }) { - if (this is ContainerCredentialFinalized) return this as ContainerCredentialFinalized; + if (this is TokenContainerFinalized) return this as TokenContainerFinalized; if (publicServerKey == null && this.publicServerKey == null) { Logger.warning('Unable to finalize without public server key'); return null; @@ -131,7 +131,7 @@ class ContainerCredential with _$ContainerCredential { Logger.warning('Unable to finalize without sync url'); return null; } - return ContainerCredentialFinalized( + return TokenContainerFinalized( issuer: issuer, nonce: nonce, timestamp: timestamp, @@ -148,25 +148,25 @@ class ContainerCredential with _$ContainerCredential { } ECPublicKey? get ecPublicServerKey => publicServerKey == null ? null : eccUtils.deserializeECPublicKey(publicServerKey!); - ContainerCredential withPublicServerKey(ECPublicKey publicServerKey) => copyWith(publicServerKey: eccUtils.serializeECPublicKey(publicServerKey)); + TokenContainer withPublicServerKey(ECPublicKey publicServerKey) => copyWith(publicServerKey: eccUtils.serializeECPublicKey(publicServerKey)); ECPublicKey? get ecPublicClientKey => publicClientKey == null ? null : eccUtils.deserializeECPublicKey(publicClientKey!); ECPrivateKey? get ecPrivateClientKey => privateClientKey == null ? null : eccUtils.deserializeECPrivateKey(privateClientKey!); /// Add client key pair and set finalization state to generatingKeyPairCompleted - ContainerCredential withClientKeyPair(AsymmetricKeyPair keyPair) => copyWith( + TokenContainer withClientKeyPair(AsymmetricKeyPair keyPair) => copyWith( publicClientKey: eccUtils.serializeECPublicKey(keyPair.publicKey), privateClientKey: eccUtils.serializeECPrivateKey(keyPair.privateKey), finalizationState: ContainerFinalizationState.generatingKeyPairCompleted, ); - factory ContainerCredential.fromJson(Map json) => _$ContainerCredentialFromJson(json); + factory TokenContainer.fromJson(Map json) => _$TokenContainerFromJson(json); @override - String toString() => 'ContainerCredential(' + String toString() => 'TokenContainer(' 'issuer: $issuer, ' 'nonce: $nonce, ' 'timestamp: $timestamp, ' - '${(this is ContainerCredentialUnfinalized) ? ' finalizationUrl: ${(this as ContainerCredentialUnfinalized).finalizationUrl}, ' : ''}' + '${(this is TokenContainerUnfinalized) ? ' finalizationUrl: ${(this as TokenContainerUnfinalized).finalizationUrl}, ' : ''}' 'syncUrl: $syncUrl, ' 'serial: $serial, ' 'ecKeyAlgorithm: $ecKeyAlgorithm, ' diff --git a/lib/model/tokens/container_credentials.freezed.dart b/lib/model/token_container.freezed.dart similarity index 83% rename from lib/model/tokens/container_credentials.freezed.dart rename to lib/model/token_container.freezed.dart index 3f371cf5e..42c07da83 100644 --- a/lib/model/tokens/container_credentials.freezed.dart +++ b/lib/model/token_container.freezed.dart @@ -3,7 +3,7 @@ // ignore_for_file: type=lint // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark -part of 'container_credentials.dart'; +part of 'token_container.dart'; // ************************************************************************** // FreezedGenerator @@ -14,21 +14,21 @@ T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); -ContainerCredential _$ContainerCredentialFromJson(Map json) { +TokenContainer _$TokenContainerFromJson(Map json) { switch (json['runtimeType']) { case 'unfinalized': - return ContainerCredentialUnfinalized.fromJson(json); + return TokenContainerUnfinalized.fromJson(json); case 'finalized': - return ContainerCredentialFinalized.fromJson(json); + return TokenContainerFinalized.fromJson(json); default: - throw CheckedFromJsonException(json, 'runtimeType', 'ContainerCredential', + throw CheckedFromJsonException(json, 'runtimeType', 'TokenContainer', 'Invalid union type "${json['runtimeType']}"!'); } } /// @nodoc -mixin _$ContainerCredential { +mixin _$TokenContainer { String get issuer => throw _privateConstructorUsedError; String get nonce => throw _privateConstructorUsedError; DateTime get timestamp => throw _privateConstructorUsedError; @@ -151,39 +151,39 @@ mixin _$ContainerCredential { throw _privateConstructorUsedError; @optionalTypeArgs TResult map({ - required TResult Function(ContainerCredentialUnfinalized value) unfinalized, - required TResult Function(ContainerCredentialFinalized value) finalized, + required TResult Function(TokenContainerUnfinalized value) unfinalized, + required TResult Function(TokenContainerFinalized value) finalized, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? mapOrNull({ - TResult? Function(ContainerCredentialUnfinalized value)? unfinalized, - TResult? Function(ContainerCredentialFinalized value)? finalized, + TResult? Function(TokenContainerUnfinalized value)? unfinalized, + TResult? Function(TokenContainerFinalized value)? finalized, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeMap({ - TResult Function(ContainerCredentialUnfinalized value)? unfinalized, - TResult Function(ContainerCredentialFinalized value)? finalized, + TResult Function(TokenContainerUnfinalized value)? unfinalized, + TResult Function(TokenContainerFinalized value)? finalized, required TResult orElse(), }) => throw _privateConstructorUsedError; - /// Serializes this ContainerCredential to a JSON map. + /// Serializes this TokenContainer to a JSON map. Map toJson() => throw _privateConstructorUsedError; - /// Create a copy of ContainerCredential + /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $ContainerCredentialCopyWith get copyWith => + $TokenContainerCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class $ContainerCredentialCopyWith<$Res> { - factory $ContainerCredentialCopyWith( - ContainerCredential value, $Res Function(ContainerCredential) then) = - _$ContainerCredentialCopyWithImpl<$Res, ContainerCredential>; +abstract class $TokenContainerCopyWith<$Res> { + factory $TokenContainerCopyWith( + TokenContainer value, $Res Function(TokenContainer) then) = + _$TokenContainerCopyWithImpl<$Res, TokenContainer>; @useResult $Res call( {String issuer, @@ -202,16 +202,16 @@ abstract class $ContainerCredentialCopyWith<$Res> { } /// @nodoc -class _$ContainerCredentialCopyWithImpl<$Res, $Val extends ContainerCredential> - implements $ContainerCredentialCopyWith<$Res> { - _$ContainerCredentialCopyWithImpl(this._value, this._then); +class _$TokenContainerCopyWithImpl<$Res, $Val extends TokenContainer> + implements $TokenContainerCopyWith<$Res> { + _$TokenContainerCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of ContainerCredential + /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override @@ -288,12 +288,12 @@ class _$ContainerCredentialCopyWithImpl<$Res, $Val extends ContainerCredential> } /// @nodoc -abstract class _$$ContainerCredentialUnfinalizedImplCopyWith<$Res> - implements $ContainerCredentialCopyWith<$Res> { - factory _$$ContainerCredentialUnfinalizedImplCopyWith( - _$ContainerCredentialUnfinalizedImpl value, - $Res Function(_$ContainerCredentialUnfinalizedImpl) then) = - __$$ContainerCredentialUnfinalizedImplCopyWithImpl<$Res>; +abstract class _$$TokenContainerUnfinalizedImplCopyWith<$Res> + implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerUnfinalizedImplCopyWith( + _$TokenContainerUnfinalizedImpl value, + $Res Function(_$TokenContainerUnfinalizedImpl) then) = + __$$TokenContainerUnfinalizedImplCopyWithImpl<$Res>; @override @useResult $Res call( @@ -314,16 +314,15 @@ abstract class _$$ContainerCredentialUnfinalizedImplCopyWith<$Res> } /// @nodoc -class __$$ContainerCredentialUnfinalizedImplCopyWithImpl<$Res> - extends _$ContainerCredentialCopyWithImpl<$Res, - _$ContainerCredentialUnfinalizedImpl> - implements _$$ContainerCredentialUnfinalizedImplCopyWith<$Res> { - __$$ContainerCredentialUnfinalizedImplCopyWithImpl( - _$ContainerCredentialUnfinalizedImpl _value, - $Res Function(_$ContainerCredentialUnfinalizedImpl) _then) +class __$$TokenContainerUnfinalizedImplCopyWithImpl<$Res> + extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerUnfinalizedImpl> + implements _$$TokenContainerUnfinalizedImplCopyWith<$Res> { + __$$TokenContainerUnfinalizedImplCopyWithImpl( + _$TokenContainerUnfinalizedImpl _value, + $Res Function(_$TokenContainerUnfinalizedImpl) _then) : super(_value, _then); - /// Create a copy of ContainerCredential + /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override @@ -343,7 +342,7 @@ class __$$ContainerCredentialUnfinalizedImplCopyWithImpl<$Res> Object? publicClientKey = freezed, Object? privateClientKey = freezed, }) { - return _then(_$ContainerCredentialUnfinalizedImpl( + return _then(_$TokenContainerUnfinalizedImpl( issuer: null == issuer ? _value.issuer : issuer // ignore: cast_nullable_to_non_nullable @@ -406,9 +405,8 @@ class __$$ContainerCredentialUnfinalizedImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$ContainerCredentialUnfinalizedImpl - extends ContainerCredentialUnfinalized { - const _$ContainerCredentialUnfinalizedImpl( +class _$TokenContainerUnfinalizedImpl extends TokenContainerUnfinalized { + const _$TokenContainerUnfinalizedImpl( {required this.issuer, required this.nonce, required this.timestamp, @@ -427,9 +425,8 @@ class _$ContainerCredentialUnfinalizedImpl : $type = $type ?? 'unfinalized', super._(); - factory _$ContainerCredentialUnfinalizedImpl.fromJson( - Map json) => - _$$ContainerCredentialUnfinalizedImplFromJson(json); + factory _$TokenContainerUnfinalizedImpl.fromJson(Map json) => + _$$TokenContainerUnfinalizedImplFromJson(json); @override final String issuer; @@ -469,7 +466,7 @@ class _$ContainerCredentialUnfinalizedImpl bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$ContainerCredentialUnfinalizedImpl && + other is _$TokenContainerUnfinalizedImpl && (identical(other.issuer, issuer) || other.issuer == issuer) && (identical(other.nonce, nonce) || other.nonce == nonce) && (identical(other.timestamp, timestamp) || @@ -515,15 +512,14 @@ class _$ContainerCredentialUnfinalizedImpl publicClientKey, privateClientKey); - /// Create a copy of ContainerCredential + /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$ContainerCredentialUnfinalizedImplCopyWith< - _$ContainerCredentialUnfinalizedImpl> - get copyWith => __$$ContainerCredentialUnfinalizedImplCopyWithImpl< - _$ContainerCredentialUnfinalizedImpl>(this, _$identity); + _$$TokenContainerUnfinalizedImplCopyWith<_$TokenContainerUnfinalizedImpl> + get copyWith => __$$TokenContainerUnfinalizedImplCopyWithImpl< + _$TokenContainerUnfinalizedImpl>(this, _$identity); @override @optionalTypeArgs @@ -688,8 +684,8 @@ class _$ContainerCredentialUnfinalizedImpl @override @optionalTypeArgs TResult map({ - required TResult Function(ContainerCredentialUnfinalized value) unfinalized, - required TResult Function(ContainerCredentialFinalized value) finalized, + required TResult Function(TokenContainerUnfinalized value) unfinalized, + required TResult Function(TokenContainerFinalized value) finalized, }) { return unfinalized(this); } @@ -697,8 +693,8 @@ class _$ContainerCredentialUnfinalizedImpl @override @optionalTypeArgs TResult? mapOrNull({ - TResult? Function(ContainerCredentialUnfinalized value)? unfinalized, - TResult? Function(ContainerCredentialFinalized value)? finalized, + TResult? Function(TokenContainerUnfinalized value)? unfinalized, + TResult? Function(TokenContainerFinalized value)? finalized, }) { return unfinalized?.call(this); } @@ -706,8 +702,8 @@ class _$ContainerCredentialUnfinalizedImpl @override @optionalTypeArgs TResult maybeMap({ - TResult Function(ContainerCredentialUnfinalized value)? unfinalized, - TResult Function(ContainerCredentialFinalized value)? finalized, + TResult Function(TokenContainerUnfinalized value)? unfinalized, + TResult Function(TokenContainerFinalized value)? finalized, required TResult orElse(), }) { if (unfinalized != null) { @@ -718,14 +714,14 @@ class _$ContainerCredentialUnfinalizedImpl @override Map toJson() { - return _$$ContainerCredentialUnfinalizedImplToJson( + return _$$TokenContainerUnfinalizedImplToJson( this, ); } } -abstract class ContainerCredentialUnfinalized extends ContainerCredential { - const factory ContainerCredentialUnfinalized( +abstract class TokenContainerUnfinalized extends TokenContainer { + const factory TokenContainerUnfinalized( {required final String issuer, required final String nonce, required final DateTime timestamp, @@ -739,11 +735,11 @@ abstract class ContainerCredentialUnfinalized extends ContainerCredential { final String? passphraseQuestion, final String? publicServerKey, final String? publicClientKey, - final String? privateClientKey}) = _$ContainerCredentialUnfinalizedImpl; - const ContainerCredentialUnfinalized._() : super._(); + final String? privateClientKey}) = _$TokenContainerUnfinalizedImpl; + const TokenContainerUnfinalized._() : super._(); - factory ContainerCredentialUnfinalized.fromJson(Map json) = - _$ContainerCredentialUnfinalizedImpl.fromJson; + factory TokenContainerUnfinalized.fromJson(Map json) = + _$TokenContainerUnfinalizedImpl.fromJson; @override String get issuer; @@ -773,22 +769,21 @@ abstract class ContainerCredentialUnfinalized extends ContainerCredential { @override String? get privateClientKey; - /// Create a copy of ContainerCredential + /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$ContainerCredentialUnfinalizedImplCopyWith< - _$ContainerCredentialUnfinalizedImpl> + _$$TokenContainerUnfinalizedImplCopyWith<_$TokenContainerUnfinalizedImpl> get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class _$$ContainerCredentialFinalizedImplCopyWith<$Res> - implements $ContainerCredentialCopyWith<$Res> { - factory _$$ContainerCredentialFinalizedImplCopyWith( - _$ContainerCredentialFinalizedImpl value, - $Res Function(_$ContainerCredentialFinalizedImpl) then) = - __$$ContainerCredentialFinalizedImplCopyWithImpl<$Res>; +abstract class _$$TokenContainerFinalizedImplCopyWith<$Res> + implements $TokenContainerCopyWith<$Res> { + factory _$$TokenContainerFinalizedImplCopyWith( + _$TokenContainerFinalizedImpl value, + $Res Function(_$TokenContainerFinalizedImpl) then) = + __$$TokenContainerFinalizedImplCopyWithImpl<$Res>; @override @useResult $Res call( @@ -808,16 +803,15 @@ abstract class _$$ContainerCredentialFinalizedImplCopyWith<$Res> } /// @nodoc -class __$$ContainerCredentialFinalizedImplCopyWithImpl<$Res> - extends _$ContainerCredentialCopyWithImpl<$Res, - _$ContainerCredentialFinalizedImpl> - implements _$$ContainerCredentialFinalizedImplCopyWith<$Res> { - __$$ContainerCredentialFinalizedImplCopyWithImpl( - _$ContainerCredentialFinalizedImpl _value, - $Res Function(_$ContainerCredentialFinalizedImpl) _then) +class __$$TokenContainerFinalizedImplCopyWithImpl<$Res> + extends _$TokenContainerCopyWithImpl<$Res, _$TokenContainerFinalizedImpl> + implements _$$TokenContainerFinalizedImplCopyWith<$Res> { + __$$TokenContainerFinalizedImplCopyWithImpl( + _$TokenContainerFinalizedImpl _value, + $Res Function(_$TokenContainerFinalizedImpl) _then) : super(_value, _then); - /// Create a copy of ContainerCredential + /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override @@ -836,7 +830,7 @@ class __$$ContainerCredentialFinalizedImplCopyWithImpl<$Res> Object? publicClientKey = null, Object? privateClientKey = null, }) { - return _then(_$ContainerCredentialFinalizedImpl( + return _then(_$TokenContainerFinalizedImpl( issuer: null == issuer ? _value.issuer : issuer // ignore: cast_nullable_to_non_nullable @@ -895,8 +889,8 @@ class __$$ContainerCredentialFinalizedImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { - const _$ContainerCredentialFinalizedImpl( +class _$TokenContainerFinalizedImpl extends TokenContainerFinalized { + const _$TokenContainerFinalizedImpl( {required this.issuer, required this.nonce, required this.timestamp, @@ -914,9 +908,8 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { : $type = $type ?? 'finalized', super._(); - factory _$ContainerCredentialFinalizedImpl.fromJson( - Map json) => - _$$ContainerCredentialFinalizedImplFromJson(json); + factory _$TokenContainerFinalizedImpl.fromJson(Map json) => + _$$TokenContainerFinalizedImplFromJson(json); @override final String issuer; @@ -954,7 +947,7 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$ContainerCredentialFinalizedImpl && + other is _$TokenContainerFinalizedImpl && (identical(other.issuer, issuer) || other.issuer == issuer) && (identical(other.nonce, nonce) || other.nonce == nonce) && (identical(other.timestamp, timestamp) || @@ -997,15 +990,14 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { publicClientKey, privateClientKey); - /// Create a copy of ContainerCredential + /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$ContainerCredentialFinalizedImplCopyWith< - _$ContainerCredentialFinalizedImpl> - get copyWith => __$$ContainerCredentialFinalizedImplCopyWithImpl< - _$ContainerCredentialFinalizedImpl>(this, _$identity); + _$$TokenContainerFinalizedImplCopyWith<_$TokenContainerFinalizedImpl> + get copyWith => __$$TokenContainerFinalizedImplCopyWithImpl< + _$TokenContainerFinalizedImpl>(this, _$identity); @override @optionalTypeArgs @@ -1167,8 +1159,8 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { @override @optionalTypeArgs TResult map({ - required TResult Function(ContainerCredentialUnfinalized value) unfinalized, - required TResult Function(ContainerCredentialFinalized value) finalized, + required TResult Function(TokenContainerUnfinalized value) unfinalized, + required TResult Function(TokenContainerFinalized value) finalized, }) { return finalized(this); } @@ -1176,8 +1168,8 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { @override @optionalTypeArgs TResult? mapOrNull({ - TResult? Function(ContainerCredentialUnfinalized value)? unfinalized, - TResult? Function(ContainerCredentialFinalized value)? finalized, + TResult? Function(TokenContainerUnfinalized value)? unfinalized, + TResult? Function(TokenContainerFinalized value)? finalized, }) { return finalized?.call(this); } @@ -1185,8 +1177,8 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { @override @optionalTypeArgs TResult maybeMap({ - TResult Function(ContainerCredentialUnfinalized value)? unfinalized, - TResult Function(ContainerCredentialFinalized value)? finalized, + TResult Function(TokenContainerUnfinalized value)? unfinalized, + TResult Function(TokenContainerFinalized value)? finalized, required TResult orElse(), }) { if (finalized != null) { @@ -1197,32 +1189,31 @@ class _$ContainerCredentialFinalizedImpl extends ContainerCredentialFinalized { @override Map toJson() { - return _$$ContainerCredentialFinalizedImplToJson( + return _$$TokenContainerFinalizedImplToJson( this, ); } } -abstract class ContainerCredentialFinalized extends ContainerCredential { - const factory ContainerCredentialFinalized( - {required final String issuer, - required final String nonce, - required final DateTime timestamp, - required final Uri syncUrl, - required final String serial, - required final EcKeyAlgorithm ecKeyAlgorithm, - required final Algorithms hashAlgorithm, - final String serverName, - final ContainerFinalizationState finalizationState, - final String? passphraseQuestion, - required final String publicServerKey, - required final String publicClientKey, - required final String privateClientKey}) = - _$ContainerCredentialFinalizedImpl; - const ContainerCredentialFinalized._() : super._(); +abstract class TokenContainerFinalized extends TokenContainer { + const factory TokenContainerFinalized( + {required final String issuer, + required final String nonce, + required final DateTime timestamp, + required final Uri syncUrl, + required final String serial, + required final EcKeyAlgorithm ecKeyAlgorithm, + required final Algorithms hashAlgorithm, + final String serverName, + final ContainerFinalizationState finalizationState, + final String? passphraseQuestion, + required final String publicServerKey, + required final String publicClientKey, + required final String privateClientKey}) = _$TokenContainerFinalizedImpl; + const TokenContainerFinalized._() : super._(); - factory ContainerCredentialFinalized.fromJson(Map json) = - _$ContainerCredentialFinalizedImpl.fromJson; + factory TokenContainerFinalized.fromJson(Map json) = + _$TokenContainerFinalizedImpl.fromJson; @override String get issuer; @@ -1251,11 +1242,10 @@ abstract class ContainerCredentialFinalized extends ContainerCredential { @override String get privateClientKey; - /// Create a copy of ContainerCredential + /// Create a copy of TokenContainer /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$ContainerCredentialFinalizedImplCopyWith< - _$ContainerCredentialFinalizedImpl> + _$$TokenContainerFinalizedImplCopyWith<_$TokenContainerFinalizedImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/model/tokens/container_credentials.g.dart b/lib/model/token_container.g.dart similarity index 77% rename from lib/model/tokens/container_credentials.g.dart rename to lib/model/token_container.g.dart index c7a66e427..211e59e1d 100644 --- a/lib/model/tokens/container_credentials.g.dart +++ b/lib/model/token_container.g.dart @@ -1,40 +1,37 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'container_credentials.dart'; +part of 'token_container.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** -_$ContainerCredentialUnfinalizedImpl - _$$ContainerCredentialUnfinalizedImplFromJson(Map json) => - _$ContainerCredentialUnfinalizedImpl( - issuer: json['issuer'] as String, - nonce: json['nonce'] as String, - timestamp: DateTime.parse(json['timestamp'] as String), - finalizationUrl: Uri.parse(json['finalizationUrl'] as String), - syncUrl: json['syncUrl'] == null - ? null - : Uri.parse(json['syncUrl'] as String), - serial: json['serial'] as String, - ecKeyAlgorithm: - $enumDecode(_$EcKeyAlgorithmEnumMap, json['ecKeyAlgorithm']), - hashAlgorithm: - $enumDecode(_$AlgorithmsEnumMap, json['hashAlgorithm']), - serverName: json['serverName'] as String? ?? 'privacyIDEA', - finalizationState: $enumDecodeNullable( - _$ContainerFinalizationStateEnumMap, - json['finalizationState']) ?? - ContainerFinalizationState.uninitialized, - passphraseQuestion: json['passphraseQuestion'] as String?, - publicServerKey: json['publicServerKey'] as String?, - publicClientKey: json['publicClientKey'] as String?, - privateClientKey: json['privateClientKey'] as String?, - $type: json['runtimeType'] as String?, - ); +_$TokenContainerUnfinalizedImpl _$$TokenContainerUnfinalizedImplFromJson( + Map json) => + _$TokenContainerUnfinalizedImpl( + issuer: json['issuer'] as String, + nonce: json['nonce'] as String, + timestamp: DateTime.parse(json['timestamp'] as String), + finalizationUrl: Uri.parse(json['finalizationUrl'] as String), + syncUrl: + json['syncUrl'] == null ? null : Uri.parse(json['syncUrl'] as String), + serial: json['serial'] as String, + ecKeyAlgorithm: + $enumDecode(_$EcKeyAlgorithmEnumMap, json['ecKeyAlgorithm']), + hashAlgorithm: $enumDecode(_$AlgorithmsEnumMap, json['hashAlgorithm']), + serverName: json['serverName'] as String? ?? 'privacyIDEA', + finalizationState: $enumDecodeNullable( + _$ContainerFinalizationStateEnumMap, json['finalizationState']) ?? + ContainerFinalizationState.uninitialized, + passphraseQuestion: json['passphraseQuestion'] as String?, + publicServerKey: json['publicServerKey'] as String?, + publicClientKey: json['publicClientKey'] as String?, + privateClientKey: json['privateClientKey'] as String?, + $type: json['runtimeType'] as String?, + ); -Map _$$ContainerCredentialUnfinalizedImplToJson( - _$ContainerCredentialUnfinalizedImpl instance) => +Map _$$TokenContainerUnfinalizedImplToJson( + _$TokenContainerUnfinalizedImpl instance) => { 'issuer': instance.issuer, 'nonce': instance.nonce, @@ -121,9 +118,9 @@ const _$ContainerFinalizationStateEnumMap = { ContainerFinalizationState.finalized: 'finalized', }; -_$ContainerCredentialFinalizedImpl _$$ContainerCredentialFinalizedImplFromJson( +_$TokenContainerFinalizedImpl _$$TokenContainerFinalizedImplFromJson( Map json) => - _$ContainerCredentialFinalizedImpl( + _$TokenContainerFinalizedImpl( issuer: json['issuer'] as String, nonce: json['nonce'] as String, timestamp: DateTime.parse(json['timestamp'] as String), @@ -143,8 +140,8 @@ _$ContainerCredentialFinalizedImpl _$$ContainerCredentialFinalizedImplFromJson( $type: json['runtimeType'] as String?, ); -Map _$$ContainerCredentialFinalizedImplToJson( - _$ContainerCredentialFinalizedImpl instance) => +Map _$$TokenContainerFinalizedImplToJson( + _$TokenContainerFinalizedImpl instance) => { 'issuer': instance.issuer, 'nonce': instance.nonce, diff --git a/lib/model/token_import/token_origin_data.dart b/lib/model/token_import/token_origin_data.dart index e784e1777..cf2cce83e 100644 --- a/lib/model/token_import/token_origin_data.dart +++ b/lib/model/token_import/token_origin_data.dart @@ -18,7 +18,7 @@ * limitations under the License. */ import 'package:json_annotation/json_annotation.dart'; -import 'package:privacyidea_authenticator/model/tokens/container_credentials.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; import '../enums/token_origin_source_type.dart'; import '../version.dart'; @@ -107,7 +107,7 @@ class TokenOriginData { factory TokenOriginData.fromJson(Map json) => _$TokenOriginDataFromJson(json); Map toJson() => _$TokenOriginDataToJson(this); - factory TokenOriginData.fromContainer({required ContainerCredential container, required String tokenData}) => TokenOriginData( + factory TokenOriginData.fromContainer({required TokenContainer container, required String tokenData}) => TokenOriginData( source: TokenOriginSourceType.container, appName: container.issuer, data: tokenData, diff --git a/lib/model/token_template.dart b/lib/model/token_template.dart index 62c4e4567..078401f76 100644 --- a/lib/model/token_template.dart +++ b/lib/model/token_template.dart @@ -24,7 +24,7 @@ import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:privacyidea_authenticator/model/enums/token_origin_source_type.dart'; -import 'package:privacyidea_authenticator/model/tokens/container_credentials.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:privacyidea_authenticator/model/tokens/otp_token.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; @@ -42,7 +42,7 @@ class TokenTemplate with _$TokenTemplate { required Map otpAuthMap, required String serial, @Default({}) Map additionalData, - ContainerCredential? container, + TokenContainer? container, }) = _TokenTemplateWithSerial; /// [ otpAuthMap ]: The map containing the OTP token data. May contain the secret. @@ -50,7 +50,7 @@ class TokenTemplate with _$TokenTemplate { required Map otpAuthMap, required List otps, @Default({}) Map additionalData, - ContainerCredential? container, + TokenContainer? container, }) = _TokenTemplateWithOtps; List get keys => otpAuthMap.keys.toList(); diff --git a/lib/model/token_template.freezed.dart b/lib/model/token_template.freezed.dart index 040f8485b..a0eb05dec 100644 --- a/lib/model/token_template.freezed.dart +++ b/lib/model/token_template.freezed.dart @@ -31,49 +31,37 @@ TokenTemplate _$TokenTemplateFromJson(Map json) { mixin _$TokenTemplate { Map get otpAuthMap => throw _privateConstructorUsedError; Map get additionalData => throw _privateConstructorUsedError; - ContainerCredential? get container => throw _privateConstructorUsedError; + TokenContainer? get container => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ required TResult Function(Map otpAuthMap, String serial, - Map additionalData, ContainerCredential? container) + Map additionalData, TokenContainer? container) withSerial, required TResult Function( Map otpAuthMap, List otps, Map additionalData, - ContainerCredential? container) + TokenContainer? container) withOtps, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - Map otpAuthMap, - String serial, - Map additionalData, - ContainerCredential? container)? + TResult? Function(Map otpAuthMap, String serial, + Map additionalData, TokenContainer? container)? withSerial, - TResult? Function( - Map otpAuthMap, - List otps, - Map additionalData, - ContainerCredential? container)? + TResult? Function(Map otpAuthMap, List otps, + Map additionalData, TokenContainer? container)? withOtps, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function( - Map otpAuthMap, - String serial, - Map additionalData, - ContainerCredential? container)? + TResult Function(Map otpAuthMap, String serial, + Map additionalData, TokenContainer? container)? withSerial, - TResult Function( - Map otpAuthMap, - List otps, - Map additionalData, - ContainerCredential? container)? + TResult Function(Map otpAuthMap, List otps, + Map additionalData, TokenContainer? container)? withOtps, required TResult orElse(), }) => @@ -117,9 +105,9 @@ abstract class $TokenTemplateCopyWith<$Res> { $Res call( {Map otpAuthMap, Map additionalData, - ContainerCredential? container}); + TokenContainer? container}); - $ContainerCredentialCopyWith<$Res>? get container; + $TokenContainerCopyWith<$Res>? get container; } /// @nodoc @@ -153,7 +141,7 @@ class _$TokenTemplateCopyWithImpl<$Res, $Val extends TokenTemplate> container: freezed == container ? _value.container : container // ignore: cast_nullable_to_non_nullable - as ContainerCredential?, + as TokenContainer?, ) as $Val); } @@ -161,12 +149,12 @@ class _$TokenTemplateCopyWithImpl<$Res, $Val extends TokenTemplate> /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') - $ContainerCredentialCopyWith<$Res>? get container { + $TokenContainerCopyWith<$Res>? get container { if (_value.container == null) { return null; } - return $ContainerCredentialCopyWith<$Res>(_value.container!, (value) { + return $TokenContainerCopyWith<$Res>(_value.container!, (value) { return _then(_value.copyWith(container: value) as $Val); }); } @@ -185,10 +173,10 @@ abstract class _$$TokenTemplateWithSerialImplCopyWith<$Res> {Map otpAuthMap, String serial, Map additionalData, - ContainerCredential? container}); + TokenContainer? container}); @override - $ContainerCredentialCopyWith<$Res>? get container; + $TokenContainerCopyWith<$Res>? get container; } /// @nodoc @@ -226,7 +214,7 @@ class __$$TokenTemplateWithSerialImplCopyWithImpl<$Res> container: freezed == container ? _value.container : container // ignore: cast_nullable_to_non_nullable - as ContainerCredential?, + as TokenContainer?, )); } } @@ -269,7 +257,7 @@ class _$TokenTemplateWithSerialImpl extends _TokenTemplateWithSerial } @override - final ContainerCredential? container; + final TokenContainer? container; @JsonKey(name: 'runtimeType') final String $type; @@ -303,13 +291,13 @@ class _$TokenTemplateWithSerialImpl extends _TokenTemplateWithSerial @optionalTypeArgs TResult when({ required TResult Function(Map otpAuthMap, String serial, - Map additionalData, ContainerCredential? container) + Map additionalData, TokenContainer? container) withSerial, required TResult Function( Map otpAuthMap, List otps, Map additionalData, - ContainerCredential? container) + TokenContainer? container) withOtps, }) { return withSerial(otpAuthMap, serial, additionalData, container); @@ -318,17 +306,11 @@ class _$TokenTemplateWithSerialImpl extends _TokenTemplateWithSerial @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - Map otpAuthMap, - String serial, - Map additionalData, - ContainerCredential? container)? + TResult? Function(Map otpAuthMap, String serial, + Map additionalData, TokenContainer? container)? withSerial, - TResult? Function( - Map otpAuthMap, - List otps, - Map additionalData, - ContainerCredential? container)? + TResult? Function(Map otpAuthMap, List otps, + Map additionalData, TokenContainer? container)? withOtps, }) { return withSerial?.call(otpAuthMap, serial, additionalData, container); @@ -337,17 +319,11 @@ class _$TokenTemplateWithSerialImpl extends _TokenTemplateWithSerial @override @optionalTypeArgs TResult maybeWhen({ - TResult Function( - Map otpAuthMap, - String serial, - Map additionalData, - ContainerCredential? container)? + TResult Function(Map otpAuthMap, String serial, + Map additionalData, TokenContainer? container)? withSerial, - TResult Function( - Map otpAuthMap, - List otps, - Map additionalData, - ContainerCredential? container)? + TResult Function(Map otpAuthMap, List otps, + Map additionalData, TokenContainer? container)? withOtps, required TResult orElse(), }) { @@ -401,7 +377,7 @@ abstract class _TokenTemplateWithSerial extends TokenTemplate { {required final Map otpAuthMap, required final String serial, final Map additionalData, - final ContainerCredential? container}) = _$TokenTemplateWithSerialImpl; + final TokenContainer? container}) = _$TokenTemplateWithSerialImpl; _TokenTemplateWithSerial._() : super._(); factory _TokenTemplateWithSerial.fromJson(Map json) = @@ -413,7 +389,7 @@ abstract class _TokenTemplateWithSerial extends TokenTemplate { @override Map get additionalData; @override - ContainerCredential? get container; + TokenContainer? get container; /// Create a copy of TokenTemplate /// with the given fields replaced by the non-null parameter values. @@ -436,10 +412,10 @@ abstract class _$$TokenTemplateWithOtpsImplCopyWith<$Res> {Map otpAuthMap, List otps, Map additionalData, - ContainerCredential? container}); + TokenContainer? container}); @override - $ContainerCredentialCopyWith<$Res>? get container; + $TokenContainerCopyWith<$Res>? get container; } /// @nodoc @@ -476,7 +452,7 @@ class __$$TokenTemplateWithOtpsImplCopyWithImpl<$Res> container: freezed == container ? _value.container : container // ignore: cast_nullable_to_non_nullable - as ContainerCredential?, + as TokenContainer?, )); } } @@ -526,7 +502,7 @@ class _$TokenTemplateWithOtpsImpl extends _TokenTemplateWithOtps } @override - final ContainerCredential? container; + final TokenContainer? container; @JsonKey(name: 'runtimeType') final String $type; @@ -560,13 +536,13 @@ class _$TokenTemplateWithOtpsImpl extends _TokenTemplateWithOtps @optionalTypeArgs TResult when({ required TResult Function(Map otpAuthMap, String serial, - Map additionalData, ContainerCredential? container) + Map additionalData, TokenContainer? container) withSerial, required TResult Function( Map otpAuthMap, List otps, Map additionalData, - ContainerCredential? container) + TokenContainer? container) withOtps, }) { return withOtps(otpAuthMap, otps, additionalData, container); @@ -575,17 +551,11 @@ class _$TokenTemplateWithOtpsImpl extends _TokenTemplateWithOtps @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - Map otpAuthMap, - String serial, - Map additionalData, - ContainerCredential? container)? + TResult? Function(Map otpAuthMap, String serial, + Map additionalData, TokenContainer? container)? withSerial, - TResult? Function( - Map otpAuthMap, - List otps, - Map additionalData, - ContainerCredential? container)? + TResult? Function(Map otpAuthMap, List otps, + Map additionalData, TokenContainer? container)? withOtps, }) { return withOtps?.call(otpAuthMap, otps, additionalData, container); @@ -594,17 +564,11 @@ class _$TokenTemplateWithOtpsImpl extends _TokenTemplateWithOtps @override @optionalTypeArgs TResult maybeWhen({ - TResult Function( - Map otpAuthMap, - String serial, - Map additionalData, - ContainerCredential? container)? + TResult Function(Map otpAuthMap, String serial, + Map additionalData, TokenContainer? container)? withSerial, - TResult Function( - Map otpAuthMap, - List otps, - Map additionalData, - ContainerCredential? container)? + TResult Function(Map otpAuthMap, List otps, + Map additionalData, TokenContainer? container)? withOtps, required TResult orElse(), }) { @@ -658,7 +622,7 @@ abstract class _TokenTemplateWithOtps extends TokenTemplate { {required final Map otpAuthMap, required final List otps, final Map additionalData, - final ContainerCredential? container}) = _$TokenTemplateWithOtpsImpl; + final TokenContainer? container}) = _$TokenTemplateWithOtpsImpl; _TokenTemplateWithOtps._() : super._(); factory _TokenTemplateWithOtps.fromJson(Map json) = @@ -670,7 +634,7 @@ abstract class _TokenTemplateWithOtps extends TokenTemplate { @override Map get additionalData; @override - ContainerCredential? get container; + TokenContainer? get container; /// Create a copy of TokenTemplate /// with the given fields replaced by the non-null parameter values. diff --git a/lib/model/token_template.g.dart b/lib/model/token_template.g.dart index 48b8f46bb..47bcfc83e 100644 --- a/lib/model/token_template.g.dart +++ b/lib/model/token_template.g.dart @@ -15,8 +15,7 @@ _$TokenTemplateWithSerialImpl _$$TokenTemplateWithSerialImplFromJson( json['additionalData'] as Map? ?? const {}, container: json['container'] == null ? null - : ContainerCredential.fromJson( - json['container'] as Map), + : TokenContainer.fromJson(json['container'] as Map), $type: json['runtimeType'] as String?, ); @@ -39,8 +38,7 @@ _$TokenTemplateWithOtpsImpl _$$TokenTemplateWithOtpsImplFromJson( json['additionalData'] as Map? ?? const {}, container: json['container'] == null ? null - : ContainerCredential.fromJson( - json['container'] as Map), + : TokenContainer.fromJson(json['container'] as Map), $type: json['runtimeType'] as String?, ); diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index abb55a977..dc72d3553 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -50,7 +50,7 @@ class DayPasswordToken extends OTPToken { required super.secret, super.serial, super.containerSerial, - super.checkedContainers, + super.checkedContainer, this.viewMode = DayPasswordTokenViewMode.VALIDFOR, String? type, // just for @JsonSerializable(): type of DayPasswordToken is always TokenTypes.DAYPASSWORD super.tokenImage, @@ -93,7 +93,7 @@ class DayPasswordToken extends OTPToken { String? label, String? issuer, String? Function()? containerSerial, - List? checkedContainers, + List? checkedContainer, String? id, Algorithms? algorithm, int? digits, @@ -113,7 +113,7 @@ class DayPasswordToken extends OTPToken { label: label ?? this.label, issuer: issuer ?? this.issuer, containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, - checkedContainers: checkedContainers ?? this.checkedContainers, + checkedContainer: checkedContainer ?? this.checkedContainer, id: id ?? this.id, type: TokenTypes.DAYPASSWORD.name, algorithm: algorithm ?? this.algorithm, @@ -214,7 +214,7 @@ class DayPasswordToken extends OTPToken { isLocked: uriMap[OTP_AUTH_PIN], id: validatedAdditionalData[Token.ID] ?? const Uuid().v4(), containerSerial: validatedAdditionalData[Token.CONTAINER_SERIAL], - checkedContainers: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], + checkedContainer: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], sortIndex: validatedAdditionalData[Token.SORT_INDEX], folderId: validatedAdditionalData[Token.FOLDER_ID], origin: validatedAdditionalData[Token.ORIGIN], @@ -229,7 +229,7 @@ class DayPasswordToken extends OTPToken { /// | OTP_AUTH_LABEL: label, | /// | OTP_AUTH_ISSUER: issuer, | /// | CONTAINER_SERIAL: containerSerial, (optional) | - /// | CHECKED_CONTAINERS: checkedContainers, | + /// | CHECKED_CONTAINERS: checkedContainer, | /// | TOKEN_ID: id, | /// | OTP_AUTH_TYPE: type, | /// | OTP_AUTH_IMAGE: tokenImage, (optional) | diff --git a/lib/model/tokens/day_password_token.g.dart b/lib/model/tokens/day_password_token.g.dart index 5b261a626..9f64ba8d9 100644 --- a/lib/model/tokens/day_password_token.g.dart +++ b/lib/model/tokens/day_password_token.g.dart @@ -15,7 +15,7 @@ DayPasswordToken _$DayPasswordTokenFromJson(Map json) => secret: json['secret'] as String, serial: json['serial'] as String?, containerSerial: json['containerSerial'] as String?, - checkedContainers: (json['checkedContainers'] as List?) + checkedContainer: (json['checkedContainer'] as List?) ?.map((e) => e as String) .toList() ?? const [], @@ -38,7 +38,7 @@ DayPasswordToken _$DayPasswordTokenFromJson(Map json) => Map _$DayPasswordTokenToJson(DayPasswordToken instance) => { - 'checkedContainers': instance.checkedContainers, + 'checkedContainer': instance.checkedContainer, 'label': instance.label, 'issuer': instance.issuer, 'containerSerial': instance.containerSerial, diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index ea84ec8bf..2c1021628 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -44,7 +44,7 @@ class HOTPToken extends OTPToken { HOTPToken({ this.counter = 0, super.containerSerial, - super.checkedContainers, + super.checkedContainer, required super.id, required super.algorithm, required super.digits, @@ -94,7 +94,7 @@ class HOTPToken extends OTPToken { String? label, String? issuer, String? Function()? containerSerial, - List? checkedContainers, + List? checkedContainer, String? id, Algorithms? algorithm, int? digits, @@ -113,7 +113,7 @@ class HOTPToken extends OTPToken { label: label ?? this.label, issuer: issuer ?? this.issuer, containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, - checkedContainers: checkedContainers ?? this.checkedContainers, + checkedContainer: checkedContainer ?? this.checkedContainer, id: id ?? this.id, algorithm: algorithm ?? this.algorithm, digits: digits ?? this.digits, @@ -195,7 +195,7 @@ class HOTPToken extends OTPToken { id: validatedAdditionalData[Token.ID] ?? const Uuid().v4(), origin: validatedAdditionalData[Token.ORIGIN], isHidden: validatedAdditionalData[Token.HIDDEN], - checkedContainers: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], + checkedContainer: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], folderId: validatedAdditionalData[Token.FOLDER_ID], sortIndex: validatedAdditionalData[Token.SORT_INDEX], ); @@ -208,7 +208,7 @@ class HOTPToken extends OTPToken { /// | OTP_AUTH_LABEL: label, | /// | OTP_AUTH_ISSUER: issuer, | /// | CONTAINER_SERIAL: containerSerial, (optional) | - /// | CHECKED_CONTAINERS: checkedContainers, | + /// | CHECKED_CONTAINERS: checkedContainer, | /// | TOKEN_ID: id, | /// | OTP_AUTH_TYPE: type, | /// | OTP_AUTH_IMAGE: tokenImage, (optional) | diff --git a/lib/model/tokens/hotp_token.g.dart b/lib/model/tokens/hotp_token.g.dart index b8499cabe..64f8c26c8 100644 --- a/lib/model/tokens/hotp_token.g.dart +++ b/lib/model/tokens/hotp_token.g.dart @@ -9,7 +9,7 @@ part of 'hotp_token.dart'; HOTPToken _$HOTPTokenFromJson(Map json) => HOTPToken( counter: (json['counter'] as num?)?.toInt() ?? 0, containerSerial: json['containerSerial'] as String?, - checkedContainers: (json['checkedContainers'] as List?) + checkedContainer: (json['checkedContainer'] as List?) ?.map((e) => e as String) .toList() ?? const [], @@ -33,7 +33,7 @@ HOTPToken _$HOTPTokenFromJson(Map json) => HOTPToken( ); Map _$HOTPTokenToJson(HOTPToken instance) => { - 'checkedContainers': instance.checkedContainers, + 'checkedContainer': instance.checkedContainer, 'label': instance.label, 'issuer': instance.issuer, 'containerSerial': instance.containerSerial, diff --git a/lib/model/tokens/otp_token.dart b/lib/model/tokens/otp_token.dart index 80320c6c0..c1f6e0715 100644 --- a/lib/model/tokens/otp_token.dart +++ b/lib/model/tokens/otp_token.dart @@ -24,7 +24,7 @@ import '../../utils/logger.dart'; import '../enums/algorithms.dart'; import '../token_template.dart'; import '../token_import/token_origin_data.dart'; -import 'container_credentials.dart'; +import '../token_container.dart'; import 'token.dart'; abstract class OTPToken extends Token { @@ -46,7 +46,7 @@ abstract class OTPToken extends Token { required super.id, required super.type, super.containerSerial, - super.checkedContainers, + super.checkedContainer, super.serial, super.pin, super.tokenImage, @@ -74,7 +74,7 @@ abstract class OTPToken extends Token { String? label, String? issuer, String? Function()? containerSerial, - List? checkedContainers, + List? checkedContainer, String? id, Algorithms? algorithm, int? digits, @@ -100,7 +100,7 @@ abstract class OTPToken extends Token { /// | OTP_AUTH_LABEL: label, | /// | OTP_AUTH_ISSUER: issuer, | /// | CONTAINER_SERIAL: containerSerial, (optional) | - /// | CHECKED_CONTAINERS: checkedContainers, | + /// | CHECKED_CONTAINERS: checkedContainer, | /// | TOKEN_ID: id, | /// | OTP_AUTH_TYPE: type, | /// | OTP_AUTH_IMAGE: tokenImage, (optional) | @@ -130,7 +130,7 @@ abstract class OTPToken extends Token { } @override - TokenTemplate toTemplate({ContainerCredential? container}) => + TokenTemplate toTemplate({TokenContainer? container}) => super.toTemplate(container: container) ?? TokenTemplate.withOtps( otpAuthMap: toOtpAuthMap(), diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index 5922b58ec..be2d83000 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -30,7 +30,7 @@ import '../../utils/type_matchers.dart'; import '../enums/push_token_rollout_state.dart'; import '../enums/token_types.dart'; import '../token_import/token_origin_data.dart'; -import 'container_credentials.dart'; +import '../token_container.dart'; import 'token.dart'; part 'push_token.g.dart'; @@ -74,7 +74,7 @@ class PushToken extends Token { super.label, super.issuer, super.containerSerial, - super.checkedContainers, + super.checkedContainer, required super.id, this.fbToken, this.url, @@ -123,7 +123,7 @@ class PushToken extends Token { String? serial, String? issuer, String? Function()? containerSerial, - List? checkedContainers, + List? checkedContainer, String? id, String? tokenImage, String? fbToken, @@ -151,7 +151,7 @@ class PushToken extends Token { tokenImage: tokenImage ?? this.tokenImage, fbToken: fbToken ?? this.fbToken, containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, - checkedContainers: checkedContainers ?? this.checkedContainers, + checkedContainer: checkedContainer ?? this.checkedContainer, id: id ?? this.id, pin: pin ?? this.pin, isLocked: isLocked ?? this.isLocked, @@ -234,7 +234,7 @@ class PushToken extends Token { id: validatedAdditionalData[Token.ID] ?? const Uuid().v4(), origin: validatedAdditionalData[Token.ORIGIN], isHidden: validatedAdditionalData[Token.HIDDEN], - checkedContainers: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], + checkedContainer: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], folderId: validatedAdditionalData[Token.FOLDER_ID], sortIndex: validatedAdditionalData[Token.SORT_INDEX], ), @@ -317,7 +317,7 @@ class PushToken extends Token { } @override - TokenTemplate? toTemplate({ContainerCredential? container}) => expirationDate != null + TokenTemplate? toTemplate({TokenContainer? container}) => expirationDate != null ? super.toTemplate(container: container) : super.toTemplate(container: container)?.withAditionalData({ EXPIRATION_DATE: expirationDate!, diff --git a/lib/model/tokens/push_token.g.dart b/lib/model/tokens/push_token.g.dart index 3453c02d5..a85b983e4 100644 --- a/lib/model/tokens/push_token.g.dart +++ b/lib/model/tokens/push_token.g.dart @@ -11,7 +11,7 @@ PushToken _$PushTokenFromJson(Map json) => PushToken( label: json['label'] as String? ?? '', issuer: json['issuer'] as String? ?? '', containerSerial: json['containerSerial'] as String?, - checkedContainers: (json['checkedContainers'] as List?) + checkedContainer: (json['checkedContainer'] as List?) ?.map((e) => e as String) .toList() ?? const [], @@ -42,7 +42,7 @@ PushToken _$PushTokenFromJson(Map json) => PushToken( ); Map _$PushTokenToJson(PushToken instance) => { - 'checkedContainers': instance.checkedContainers, + 'checkedContainer': instance.checkedContainer, 'label': instance.label, 'issuer': instance.issuer, 'containerSerial': instance.containerSerial, diff --git a/lib/model/tokens/steam_token.dart b/lib/model/tokens/steam_token.dart index c6505e9c0..2d2606d00 100644 --- a/lib/model/tokens/steam_token.dart +++ b/lib/model/tokens/steam_token.dart @@ -46,7 +46,7 @@ class SteamToken extends TOTPToken { required super.secret, super.serial, super.containerSerial, - super.checkedContainers, + super.checkedContainer, String? type, super.tokenImage, super.pin, @@ -70,7 +70,7 @@ class SteamToken extends TOTPToken { String? label, String? issuer, String? Function()? containerSerial, - List? checkedContainers, + List? checkedContainer, String? id, bool? isLocked, bool? isHidden, @@ -89,7 +89,7 @@ class SteamToken extends TOTPToken { label: label ?? this.label, issuer: issuer ?? this.issuer, containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, - checkedContainers: checkedContainers ?? this.checkedContainers, + checkedContainer: checkedContainer ?? this.checkedContainer, id: id ?? this.id, secret: secret ?? this.secret, tokenImage: tokenImage ?? this.tokenImage, @@ -185,7 +185,7 @@ class SteamToken extends TOTPToken { isLocked: uriMap[OTP_AUTH_PIN], id: validatedAdditionalData[Token.ID] ?? const Uuid().v4(), containerSerial: validatedAdditionalData[Token.CONTAINER_SERIAL], - checkedContainers: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], + checkedContainer: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], sortIndex: validatedAdditionalData[Token.SORT_INDEX], folderId: validatedAdditionalData[Token.FOLDER_ID], origin: validatedAdditionalData[Token.ORIGIN], diff --git a/lib/model/tokens/steam_token.g.dart b/lib/model/tokens/steam_token.g.dart index 7398f992a..8b1463e63 100644 --- a/lib/model/tokens/steam_token.g.dart +++ b/lib/model/tokens/steam_token.g.dart @@ -11,7 +11,7 @@ SteamToken _$SteamTokenFromJson(Map json) => SteamToken( secret: json['secret'] as String, serial: json['serial'] as String?, containerSerial: json['containerSerial'] as String?, - checkedContainers: (json['checkedContainers'] as List?) + checkedContainer: (json['checkedContainer'] as List?) ?.map((e) => e as String) .toList() ?? const [], @@ -31,7 +31,7 @@ SteamToken _$SteamTokenFromJson(Map json) => SteamToken( Map _$SteamTokenToJson(SteamToken instance) => { - 'checkedContainers': instance.checkedContainers, + 'checkedContainer': instance.checkedContainer, 'label': instance.label, 'issuer': instance.issuer, 'containerSerial': instance.containerSerial, diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index 4a916d554..70a23263c 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -20,7 +20,7 @@ * limitations under the License. */ import 'package:flutter/material.dart'; -import 'package:privacyidea_authenticator/model/tokens/container_credentials.dart'; +import 'package:privacyidea_authenticator/model/token_container.dart'; import '../token_template.dart'; import '../../utils/identifiers.dart'; @@ -40,13 +40,13 @@ abstract class Token with SortableMixin { static const ID = 'id'; static const ORIGIN = 'origin'; static const HIDDEN = 'hidden'; - static const CHECKED_CONTAINERS = 'checkedContainers'; + static const CHECKED_CONTAINERS = 'checkedContainer'; static const FOLDER_ID = 'folderId'; static const SORT_INDEX = SortableMixin.SORT_INDEX; bool? get isPrivacyIdeaToken => origin?.isPrivacyIdeaToken; final String tokenVersion = 'v1.0.0'; // The version of this token, this is used for serialization. - final List checkedContainers; // The serials of the containers this token should not be in. + final List checkedContainer; // The serials of the container this token should not be in. final String label; // the name of the token, it cannot be uses as an identifier final String issuer; // The issuer of this token, currently unused. final String? containerSerial; // The serial of the container this token belongs to. @@ -109,7 +109,7 @@ abstract class Token with SortableMixin { this.label = '', this.issuer = '', this.containerSerial, - this.checkedContainers = const [], + this.checkedContainer = const [], required this.id, required this.type, this.tokenImage, @@ -139,7 +139,7 @@ abstract class Token with SortableMixin { String? label, String? issuer, String? Function()? containerSerial, - List? checkedContainers, + List? checkedContainer, String? id, bool? isLocked, bool? isHidden, @@ -208,10 +208,10 @@ abstract class Token with SortableMixin { SORT_INDEX: sortIndex, FOLDER_ID: folderId, HIDDEN: isHidden, - CHECKED_CONTAINERS: checkedContainers, + CHECKED_CONTAINERS: checkedContainer, }; - TokenTemplate? toTemplate({ContainerCredential? container}) => serial != null + TokenTemplate? toTemplate({TokenContainer? container}) => serial != null ? TokenTemplate.withSerial( otpAuthMap: toOtpAuthMap(), additionalData: additionalData, diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index 8299085d3..aee5f8b18 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -70,7 +70,7 @@ class TOTPToken extends OTPToken { required super.secret, super.serial, super.containerSerial, - super.checkedContainers, + super.checkedContainer, String? type, super.tokenImage, super.pin, @@ -97,7 +97,7 @@ class TOTPToken extends OTPToken { String? label, String? issuer, String? Function()? containerSerial, - List? checkedContainers, + List? checkedContainer, String? id, Algorithms? algorithm, int? digits, @@ -116,7 +116,7 @@ class TOTPToken extends OTPToken { label: label ?? this.label, issuer: issuer ?? this.issuer, containerSerial: containerSerial != null ? containerSerial() : this.containerSerial, - checkedContainers: checkedContainers ?? this.checkedContainers, + checkedContainer: checkedContainer ?? this.checkedContainer, id: id ?? this.id, algorithm: algorithm ?? this.algorithm, digits: digits ?? this.digits, @@ -198,7 +198,7 @@ class TOTPToken extends OTPToken { isLocked: validatedMap[OTP_AUTH_PIN] as bool?, id: validatedAdditionalData[Token.ID] ?? const Uuid().v4(), containerSerial: validatedAdditionalData[Token.CONTAINER_SERIAL], - checkedContainers: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], + checkedContainer: validatedAdditionalData[Token.CHECKED_CONTAINERS] ?? [], sortIndex: validatedAdditionalData[Token.SORT_INDEX], folderId: validatedAdditionalData[Token.FOLDER_ID], origin: validatedAdditionalData[Token.ORIGIN], diff --git a/lib/model/tokens/totp_token.g.dart b/lib/model/tokens/totp_token.g.dart index 49c2f6289..58c6fb203 100644 --- a/lib/model/tokens/totp_token.g.dart +++ b/lib/model/tokens/totp_token.g.dart @@ -14,7 +14,7 @@ TOTPToken _$TOTPTokenFromJson(Map json) => TOTPToken( secret: json['secret'] as String, serial: json['serial'] as String?, containerSerial: json['containerSerial'] as String?, - checkedContainers: (json['checkedContainers'] as List?) + checkedContainer: (json['checkedContainer'] as List?) ?.map((e) => e as String) .toList() ?? const [], @@ -33,7 +33,7 @@ TOTPToken _$TOTPTokenFromJson(Map json) => TOTPToken( ); Map _$TOTPTokenToJson(TOTPToken instance) => { - 'checkedContainers': instance.checkedContainers, + 'checkedContainer': instance.checkedContainer, 'label': instance.label, 'issuer': instance.issuer, 'containerSerial': instance.containerSerial, diff --git a/lib/processors/scheme_processors/scheme_processor_interface.dart b/lib/processors/scheme_processors/scheme_processor_interface.dart index c2be871f8..278bfbca7 100644 --- a/lib/processors/scheme_processors/scheme_processor_interface.dart +++ b/lib/processors/scheme_processors/scheme_processor_interface.dart @@ -20,9 +20,9 @@ import 'package:privacyidea_authenticator/model/processor_result.dart'; import '../../utils/logger.dart'; -import 'container_credentials_processor.dart'; import 'home_widget_processor.dart'; import 'navigation_scheme_processors/navigation_scheme_processor_interface.dart'; +import 'token_container_processor.dart'; import 'token_import_scheme_processors/token_import_scheme_processor_interface.dart'; /// On new impelementations, add them to the [SchemeProcessor.implementations] list @@ -35,7 +35,7 @@ abstract class SchemeProcessor { const HomeWidgetProcessor(), ...NavigationSchemeProcessor.implementations, ...TokenImportSchemeProcessor.implementations, - const ContainerCredentialsProcessor(), + const TokenContainerProcessor(), ]; static Future>?> processUriByAny(Uri uri, {bool fromInit = false}) async { for (SchemeProcessor processor in implementations) { diff --git a/lib/processors/scheme_processors/container_credentials_processor.dart b/lib/processors/scheme_processors/token_container_processor.dart similarity index 69% rename from lib/processors/scheme_processors/container_credentials_processor.dart rename to lib/processors/scheme_processors/token_container_processor.dart index e4b52827f..7a3f7797e 100644 --- a/lib/processors/scheme_processors/container_credentials_processor.dart +++ b/lib/processors/scheme_processors/token_container_processor.dart @@ -20,39 +20,39 @@ import 'package:privacyidea_authenticator/l10n/app_localizations.dart'; import 'package:privacyidea_authenticator/utils/errors.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; import '../../model/processor_result.dart'; -import '../../model/tokens/container_credentials.dart'; +import '../../model/token_container.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import 'scheme_processor_interface.dart'; -class ContainerCredentialsProcessor extends SchemeProcessor { - static const resultHandlerType = TypeValidatorRequired(); +class TokenContainerProcessor extends SchemeProcessor { + static const resultHandlerType = TypeValidatorRequired(); static const scheme = 'pia'; static const host = 'container'; @override Set get supportedSchemes => {scheme}; - const ContainerCredentialsProcessor(); + const TokenContainerProcessor(); @override - Future>?> processUri(Uri uri, {bool fromInit = false}) async { + Future>?> processUri(Uri uri, {bool fromInit = false}) async { if (!supportedSchemes.contains(uri.scheme)) return null; if (uri.host != host) return null; try { - final credential = ContainerCredential.fromUriMap(uri.queryParameters); - Logger.info('Successfully parsed container credential', name: 'ContainerCredentialsProcessor#processUri'); + final container = TokenContainer.fromUriMap(uri.queryParameters); + Logger.info('Successfully parsed container container', name: 'TokenContainerProcessor#processUri'); return [ ProcessorResult.success( - credential, + container, resultHandlerType: resultHandlerType, ) ]; } on LocalizedArgumentError catch (e) { - Logger.warning('Error while processing URI ${uri.scheme}', error: e.message, name: 'ContainerCredentialsProcessor#processUri'); + Logger.warning('Error while processing URI ${uri.scheme}', error: e.message, name: 'TokenContainerProcessor#processUri'); return [ ProcessorResult.failed( e.localizedMessage(AppLocalizations.of(await globalContext)!), diff --git a/lib/repo/secure_container_credentials_repository.dart b/lib/repo/secure_container_credentials_repository.dart deleted file mode 100644 index 83efc11f1..000000000 --- a/lib/repo/secure_container_credentials_repository.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:mutex/mutex.dart'; - -import '../interfaces/repo/container_credentials_repository.dart'; -import '../model/riverpod_states/credentials_state.dart'; -import '../model/tokens/container_credentials.dart'; -import '../utils/logger.dart'; - -class SecureContainerCredentialsRepository extends ContainerCredentialsRepository { - String get containerCredentialsKey => 'containerCredentials'; - String _keyOfSerial(String id) => '$containerCredentialsKey.$id'; - final Mutex _m = Mutex(); - Future _protect(Future Function() f) => _m.protect(f); - final FlutterSecureStorage _storage = const FlutterSecureStorage(); - - Future _write(String key, String value) => _protect(() => _storage.write(key: key, value: value)); - Future _read(String key) async => await _protect(() async => await _storage.read(key: key)); - Future> _readAll() async => - await _protect(() async => (await _storage.readAll())..removeWhere((key, value) => !key.startsWith(containerCredentialsKey))); - Future _delete(String key) => _protect(() => _storage.delete(key: key)); - - @override - Future loadCredentialsState() async { - final credentialsJsonString = await _readAll(); - Logger.warning('Loaded credentials: $credentialsJsonString', name: 'SecureContainerCredentialsRepository'); - return CredentialsState.fromJsonStringList(credentialsJsonString.values.toList()); - } - - @override - Future saveCredentialsState(CredentialsState credentialsState) async { - Logger.warning('Saving credentials: $credentialsState', name: 'SecureContainerCredentialsRepository'); - final futures = []; - for (var credential in credentialsState.credentials) { - futures.add(saveCredential(credential)); - } - await Future.wait(futures); - return await loadCredentialsState(); - } - - @override - Future deleteCredential(String serial) async { - await _delete(_keyOfSerial(serial)); - return await loadCredentialsState(); - } - - @override - Future deleteAllCredentials() async { - final credentialKeys = (await _readAll()).keys.where((key) => key.startsWith(containerCredentialsKey)); - final futures = []; - for (var key in credentialKeys) { - futures.add(_delete(key)); - } - await Future.wait(futures); - return await loadCredentialsState(); - } - - @override - Future loadCredential(String serial) async { - final credentialJsonString = await _read(_keyOfSerial(serial)); - if (credentialJsonString == null) return null; - return ContainerCredential.fromJson(jsonDecode(credentialJsonString)); - } - - @override - Future saveCredential(ContainerCredential credential) async { - final credentialJsonString = jsonEncode(credential.toJson()); - await _write(_keyOfSerial(credential.serial), credentialJsonString); - return await loadCredentialsState(); - } -} diff --git a/lib/repo/secure_token_container_repository.dart b/lib/repo/secure_token_container_repository.dart new file mode 100644 index 000000000..e1186fdd7 --- /dev/null +++ b/lib/repo/secure_token_container_repository.dart @@ -0,0 +1,72 @@ +import 'dart:convert'; + +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:mutex/mutex.dart'; + +import '../interfaces/repo/token_container_repository.dart'; +import '../model/riverpod_states/token_container_state.dart'; +import '../model/token_container.dart'; +import '../utils/logger.dart'; + +class SecureTokenContainerRepository extends TokenContainerRepository { + String get containerCredentialsKey => 'containerCredentials'; + String _keyOfSerial(String id) => '$containerCredentialsKey.$id'; + final Mutex _m = Mutex(); + Future _protect(Future Function() f) => _m.protect(f); + final FlutterSecureStorage _storage = const FlutterSecureStorage(); + + Future _write(String key, String value) => _protect(() => _storage.write(key: key, value: value)); + Future _read(String key) async => await _protect(() async => await _storage.read(key: key)); + Future> _readAll() async => + await _protect(() async => (await _storage.readAll())..removeWhere((key, value) => !key.startsWith(containerCredentialsKey))); + Future _delete(String key) => _protect(() => _storage.delete(key: key)); + + @override + Future loadCredentialsState() async { + final containerJsonString = await _readAll(); + Logger.warning('Loaded container: $containerJsonString', name: 'SecureTokenContainerRepository'); + return TokenContainerState.fromJsonStringList(containerJsonString.values.toList()); + } + + @override + Future saveCredentialsState(TokenContainerState containerState) async { + Logger.warning('Saving container: $containerState', name: 'SecureTokenContainerRepository'); + final futures = []; + for (var container in containerState.container) { + futures.add(saveCredential(container)); + } + await Future.wait(futures); + return await loadCredentialsState(); + } + + @override + Future deleteCredential(String serial) async { + await _delete(_keyOfSerial(serial)); + return await loadCredentialsState(); + } + + @override + Future deleteAllCredentials() async { + final containerKeys = (await _readAll()).keys.where((key) => key.startsWith(containerCredentialsKey)); + final futures = []; + for (var key in containerKeys) { + futures.add(_delete(key)); + } + await Future.wait(futures); + return await loadCredentialsState(); + } + + @override + Future loadCredential(String serial) async { + final containerJsonString = await _read(_keyOfSerial(serial)); + if (containerJsonString == null) return null; + return TokenContainer.fromJson(jsonDecode(containerJsonString)); + } + + @override + Future saveCredential(TokenContainer container) async { + final containerJsonString = jsonEncode(container.toJson()); + await _write(_keyOfSerial(container.serial), containerJsonString); + return await loadCredentialsState(); + } +} diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart deleted file mode 100644 index 78a60cc76..000000000 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart +++ /dev/null @@ -1,397 +0,0 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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:convert'; - -import 'package:basic_utils/basic_utils.dart'; -import 'package:collection/collection.dart'; -import 'package:http/http.dart'; -import 'package:mutex/mutex.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/ec_key_algorithm_extension.dart'; -import 'package:privacyidea_authenticator/model/processor_result.dart'; -import 'package:privacyidea_authenticator/model/tokens/token.dart'; -import 'package:privacyidea_authenticator/utils/globals.dart'; -import 'package:privacyidea_authenticator/utils/identifiers.dart'; -import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -import '../../../../api/token_container_api_endpoint.dart'; -import '../../../../interfaces/repo/container_credentials_repository.dart'; -import '../../../../l10n/app_localizations.dart'; -import '../../../../model/enums/container_finalization_state.dart'; -import '../../../../model/riverpod_states/credentials_state.dart'; -import '../../../../model/riverpod_states/token_state.dart'; -import '../../../../model/tokens/container_credentials.dart'; -import '../../../../repo/secure_container_credentials_repository.dart'; -import '../../../ecc_utils.dart'; -import '../../../errors.dart'; -import '../../../logger.dart'; - -part 'credential_notifier.g.dart'; - -final containerCredentialsProvider = containerCredentialsNotifierProviderOf( - repo: SecureContainerCredentialsRepository(), - containerApi: const PrivacyideaContainerApi(ioClient: PrivacyideaIOClient()), - eccUtils: const EccUtils(), -); - -@Riverpod(keepAlive: true) -class ContainerCredentialsNotifier extends _$ContainerCredentialsNotifier with ResultHandler { - final _stateMutex = Mutex(); - final _repoMutex = Mutex(); - - ContainerCredentialsNotifier({ - ContainerCredentialsRepository? repoOverride, - PrivacyideaContainerApi? containerApiOverride, - EccUtils? eccUtilsOverride, - }) : _repoOverride = repoOverride, - _containerApiOverride = containerApiOverride, - _eccUtilsOverride = eccUtilsOverride; - - @override - ContainerCredentialsRepository get repo => _repo; - late ContainerCredentialsRepository _repo; - final ContainerCredentialsRepository? _repoOverride; - - @override - PrivacyideaContainerApi get containerApi => _containerApi; - late PrivacyideaContainerApi _containerApi; - final PrivacyideaContainerApi? _containerApiOverride; - - @override - EccUtils get eccUtils => _eccUtils; - late EccUtils _eccUtils; - final EccUtils? _eccUtilsOverride; - - @override - Future build({ - required ContainerCredentialsRepository repo, - required PrivacyideaContainerApi containerApi, - required EccUtils eccUtils, - }) async { - await _stateMutex.acquire(); - _repo = _repoOverride ?? repo; - _containerApi = _containerApiOverride ?? containerApi; - _eccUtils = _eccUtilsOverride ?? eccUtils; - Logger.warning('Building credentialsProvider', name: 'CredentialsNotifier'); - - final initState = await _repo.loadCredentialsState(); - for (var credential in initState.credentials.whereType()) { - finalize(credential); - } - _stateMutex.release(); - return initState; - } - -////////////////////////////////////////////////////////////////// -////////////////////////// REPO METHODS ////////////////////////// -////////////////////////////////////////////////////////////////// - - Future _saveCredentialToRepo(ContainerCredential credential) async { - return await _repoMutex.protect(() async => await _repo.saveCredential(credential)); - } - - Future _saveCredentialsStateToRepo(CredentialsState credentialsState) async { - return await _repoMutex.protect(() async => await _repo.saveCredentialsState(credentialsState)); - } - - Future _deleteCredentialFromRepo(ContainerCredential credential) async { - return await _repoMutex.protect(() async => await _repo.deleteCredential(credential.serial)); - } - - Future _deleteCredentialsStateToRepo() async { - return await _repoMutex.protect(() async => await _repo.deleteAllCredentials()); - } - -/*////////////////////////////////////////////////////////////////// -////////////////////////// PUBLIC METHODS ////////////////////////// -///////////////////////////////////////////////////////////////// */ - -// ADD CREDENTIALS - - Future addCredential(ContainerCredential credential) async { - await _stateMutex.acquire(); - final newState = await _saveCredentialToRepo(credential); - await update((_) => newState); - _stateMutex.release(); - return newState; - } - - Future addCredentials(List credentials) async { - await _stateMutex.acquire(); - final newCredentials = credentials.toList(); - final oldCredentials = (await future).credentials; - Logger.debug('Loaded credentials: $oldCredentials', name: 'CredentialsNotifier#addCredentials'); - final combinedCredentials = []; - for (var oldCredential in oldCredentials) { - final newCredential = newCredentials.firstWhereOrNull((newCredential) => newCredential.serial == oldCredential.serial); - if (newCredential == null) { - combinedCredentials.add(oldCredential); - } else { - combinedCredentials.add(newCredential); - newCredentials.remove(newCredential); - } - } - combinedCredentials.addAll(newCredentials); - Logger.debug('Combined credentials: $combinedCredentials', name: 'CredentialsNotifier#addCredentials'); - final newState = await _saveCredentialsStateToRepo(CredentialsState(credentials: combinedCredentials)); - Logger.debug('Saved credentials: $newState', name: 'CredentialsNotifier#addCredentials'); - await update((_) => newState); - Logger.debug('Updated credentials: $newState', name: 'CredentialsNotifier#addCredentials'); - _stateMutex.release(); - return newState; - } - - // UPDATE CREDENTIALS - - @override - Future update( - FutureOr Function(CredentialsState state) cb, { - FutureOr Function(Object, StackTrace)? onError, - }) async { - Logger.warning('Updating credentialsProvider', name: 'CredentialsNotifier'); - return super.update(cb, onError: onError); - } - - Future updateCredential(T credential, T Function(T) updater) async { - await _stateMutex.acquire(); - final oldState = await future; - final currentCredential = oldState.currentOf(credential); - if (currentCredential == null) { - Logger.info('Failed to update credential. It was probably removed in the meantime.', name: 'CredentialsNotifier#updateCredential'); - _stateMutex.release(); - return null; - } - final updated = updater(currentCredential); - final newState = await _saveCredentialToRepo(updated); - await update((_) => newState); - _stateMutex.release(); - return updated; - } - - // DELETE CREDENTIALS - - Future deleteCredential(ContainerCredential credential) async { - await _stateMutex.acquire(); - final newState = await _deleteCredentialFromRepo(credential); - await update((_) => newState); - _stateMutex.release(); - return newState; - } - - Future deleteCredentials(List credentials) async { - await _stateMutex.acquire(); - final newCredentials = credentials.toList(); - final oldCredentials = (await future).credentials; - final combinedCredentials = []; - for (var oldCredential in oldCredentials) { - final newCredential = newCredentials.firstWhereOrNull((newCredential) => newCredential.serial == oldCredential.serial); - if (newCredential == null) { - combinedCredentials.add(oldCredential); - } else { - newCredentials.remove(newCredential); - } - } - final newState = await _saveCredentialsStateToRepo(CredentialsState(credentials: combinedCredentials)); - await update((_) => newState); - _stateMutex.release(); - return newState; - } - - // HANDLE PROCESSOR RESULTS - - @override - Future handleProcessorResult(ProcessorResult result, Map args) { - // TODO: implement handleResult - throw UnimplementedError(); - } - - @override - Future handleProcessorResults(List results, Map args) async { - Logger.info('Handling processor results', name: 'CredentialsNotifier#handleProcessorResults'); - final containerCredentials = results.getData().whereType().toList(); - if (containerCredentials.isEmpty) { - return null; - } - final currentState = await future; - final stateCredentials = currentState.credentials; - final stateCredentialsSerials = stateCredentials.map((e) => e.serial); - final newCredentials = containerCredentials.where((element) => !stateCredentialsSerials.contains(element.serial)).toList(); - Logger.info('Handling processor results: adding Credential', name: 'CredentialsNotifier#handleProcessorResults'); - await addCredentials(newCredentials); - Logger.info('Handling processor results: adding done (${newCredentials.length})', name: 'CredentialsNotifier#handleProcessorResults'); - for (var credential in newCredentials) { - Logger.info('Handling processor results: finalize check ()', name: 'CredentialsNotifier#handleProcessorResults'); - if (credential is! ContainerCredentialUnfinalized) continue; - Logger.info('Handling processor results: finalize', name: 'CredentialsNotifier#handleProcessorResults'); - await finalize(credential); - } - return null; - } - - final Mutex _finalizationMutex = Mutex(); - Future finalize(ContainerCredential credential) async { - await _finalizationMutex.acquire(); - if (credential is! ContainerCredentialUnfinalized) { - _finalizationMutex.release(); - throw ArgumentError('Container must not be finalized'); - } - Logger.info('Finalizing container ${credential.serial}', name: 'CredentialsNotifier#finalize'); - try { - credential = await _generateKeyPair(credential); - final Response response; - (credential, response) = await _sendPublicKey(credential); - if (response.statusCode != 200) { - if (response.piServerMessage != null) { - ref.read(statusMessageProvider.notifier).state = ( - 'Failed to finalize container: ${response.piServerMessage}', - response.piStatusCode != null ? 'PI Server code ${response.piStatusCode}' : 'Status code ${response.body}' - ); - } else { - ref.read(statusMessageProvider.notifier).state = ('Failed to finalize container: ${response.body}', 'Status code ${response.statusCode}'); - } - _finalizationMutex.release(); - return; - } - final ECPublicKey publicServerKey; - (credential, publicServerKey) = await _parseResponse(credential, response); - await updateCredential(credential, (c) => c.finalize(publicServerKey: publicServerKey)!); - } on StateError { - Logger.info('Container was removed while finalizing', name: 'CredentialsNotifier#finalize'); - } on LocalizedArgumentError catch (e) { - ref.read(statusMessageProvider.notifier).state = ('Failed to decode response body', e.localizedMessage(AppLocalizations.of(await globalContext)!)); - await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseFailed)); - } catch (e) { - Logger.error('Failed to finalize container ${credential.serial}', name: 'CredentialsNotifier#finalize', error: e); - _finalizationMutex.release(); - return; - } - _finalizationMutex.release(); - } - -/* ///////////////////////////////////////////////////////////////////////// -////////////////////////// PRIVATE HELPER METHODS ////////////////////////// -///////////////////////////////////////////////////////////////////////// */ - - /// Finalization substep 1: Generate key pair - Future _generateKeyPair(ContainerCredentialUnfinalized containerCredential) async { - // generatingKeyPair, - // generatingKeyPairFailed, - // generatingKeyPairCompleted, - ContainerCredentialUnfinalized? credential = containerCredential; - credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.generatingKeyPair)); - if (credential == null) throw StateError('Credential was removed'); - final keyPair = CryptoUtils.generateEcKeyPair(curve: credential.ecKeyAlgorithm.curveName); - credential = await updateCredential(credential, (c) => c.withClientKeyPair(keyPair) as ContainerCredentialUnfinalized); - if (credential == null) throw StateError('Credential was removed'); - return credential; - } - - /// Finalization substep 2: Send public key - Future<(ContainerCredential, Response)> _sendPublicKey(ContainerCredentialUnfinalized containerc) async { - // sendingPublicKey, - // sendingPublicKeyFailed, - // sendingPublicKeyCompleted, - - //POST /container/register/finalize - // Request: { - // 'container_serial': , - // 'public_client_key': , - // 'signature': )>, - // } - - ContainerCredentialUnfinalized? container = containerc; - - final Response response; - container = - await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKey)); - if (container == null) throw StateError('Credential was removed'); - try { - response = (await _containerApi.finalizeContainer(container, eccUtils))!; - } catch (e) { - ref.read(statusMessageProvider.notifier).state = ('Failed to finalize container', e.toString()); - await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyFailed)); - rethrow; - } - if (response.statusCode != 200) { - container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyFailed)); - if (container == null) throw StateError('Credential was removed'); - return (container, response); - } - - container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyCompleted)); - if (container == null) throw StateError('Credential was removed'); - return (container, response); - } - - /// Finalization substep 3: Parse response - Future<(ContainerCredential, ECPublicKey)> _parseResponse(ContainerCredential containerCredential, Response response) async { - // parsingResponse, - // parsingResponseFailed, - // parsingResponseCompleted, - - ContainerCredential? credential = containerCredential; - ECPublicKey publicServerKey; - String responseBody = response.body; - Map responseJson; - credential = await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponse)); - if (credential == null) throw StateError('Credential was removed'); - responseJson = jsonDecode(responseBody); - Logger.debug('Response JSON: $responseJson', name: 'CredentialsNotifier#_parseResponse'); - final result = validate(value: responseJson['result'], validator: const TypeValidatorRequired>(), name: 'result'); - final value = validate(value: result['value'], validator: const TypeValidatorRequired>(), name: 'value'); - publicServerKey = validate( - value: value['public_server_key'], - validator: TypeValidatorRequired(transformer: (v) => const EccUtils().deserializeECPublicKey(v)), - name: 'public_server_key', - ); - final syncUrlUri = validate( - value: value['container_sync_url'], - validator: TypeValidatorRequired(transformer: (v) => Uri.parse(v)), - name: 'container_sync_url', - ); - credential = - await updateCredential(credential, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseCompleted, syncUrl: syncUrlUri)); - if (credential == null) throw StateError('Credential was removed'); - return (credential, publicServerKey); - } - - Future syncTokens(TokenState tokenState) async { - final containerCredentials = (await future).credentials; - final containerCredential = containerCredentials.whereType().first; - - List syncedTokens; - List deletedTokens; - final tuple = await _containerApi.sync( - containerCredential, - tokenState, - ); - if (tuple == null) { - return; - } - syncedTokens = tuple.$1; - deletedTokens = tuple.$2; - - await ref.read(tokenProvider.notifier).addOrReplaceTokens(syncedTokens); - await ref.read(tokenProvider.notifier).removeTokensBySerials(deletedTokens); - } -} diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart index 50d28c0fb..c4aa18af7 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -1,204 +1,396 @@ -// /* -// * privacyIDEA Authenticator -// * -// * Author: Frank Merkel -// * -// * Copyright (c) 2024 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:mutex/mutex.dart'; -// import '../../../../interfaces/repo/container_repository.dart'; -// import '../../../logger.dart'; -// import 'package:riverpod_annotation/riverpod_annotation.dart'; - -// import '../../../../api/token_container_api_endpoint.dart'; -// import '../../../../model/riverpod_states/token_state.dart'; -// import '../../../../model/token_container.dart'; -// import '../../../../repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart'; -// import '../../../../repo/token_container_state_repositorys/remote_token_container_state_repository.dart'; -// import '../../../../repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart'; -// import '../../../../model/tokens/container_credentials.dart'; -// import 'token_notifier.dart'; - -// part 'token_container_notifier.g.dart'; - -// @riverpod -// class TokenContainerNotifier extends _$TokenContainerNotifier { -// late final TokenContainerRepository _repository; -// final Mutex _stateMutex = Mutex(); // Mutex to protect the state from being accessed while still waiting for the newest state to be delivered -// final Mutex _repoMutex = Mutex(); // Mutex to protect the repository from being accessed while still waiting for the newest state to be delivered - -// @override -// Future build({ -// required ContainerCredential credential, -// }) async { -// Logger.info('New tokenContainerStateProvider created', name: 'TokenContainerNotifier#build'); -// await _stateMutex.acquire(); -// _repository = SecureTokenContainerRepository(containerId: credential.serial); -// // HybridTokenContainerRepository( -// // localRepository: SecureTokenContainerRepository(containerId: credential.serial), -// // remoteRepository: RemoteTokenContainerRepository(apiEndpoint: TokenContainerApiEndpoint(credential: credential)), -// // ); -// final initialState = await _repository.loadContainerState(); -// Logger.debug('Initial state: $initialState', name: 'TokenContainerNotifier#build'); -// _stateMutex.release(); -// return initialState; -// } - -// Future _saveToRepo(TokenContainer state) async { -// await _repoMutex.acquire(); -// final newState = await _repository.saveContainerState(state); -// _repoMutex.release(); -// return newState; -// } - -// Future _fetchFromRepo() async { -// await _repoMutex.acquire(); -// final newState = await _repository.loadContainerState(); -// _repoMutex.release(); -// return newState; -// } - -// Future handleTokenState(TokenState tokenState) async { -// await _stateMutex.acquire(); -// final localTokens = tokenState.tokens.maybePiTokens; -// final oldState = state.value; -// if (oldState == null) throw Exception('TokenContainer is null'); -// final containerTokens = tokenState.containerTokens(oldState.serial); -// final localTokenTemplates = localTokens.toTemplates(); -// final containerTokenTemplates = containerTokens.toTemplates(); -// final newState = oldState.copyWith(localTokenTemplates: localTokenTemplates, syncedTokenTemplates: containerTokenTemplates); -// final savedState = await _saveToRepo(newState); -// if (savedState is! TokenContainerSynced) { -// Logger.error('Failed to save state to repo', name: 'TokenContainerNotifier#handleTokenState'); -// return savedState; -// } -// await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); -// state = AsyncValue.data(savedState); -// _stateMutex.release(); -// return savedState; -// } - -// /// Adds the given [maybePiTokenTemplates] to the localTokenTemplates of the container -// /// and saves the new state to the repository. The rpository decides waht to do with the new state. -// /// The saved state from the repo can contain the maybePiTokenTemplates or not. -// Future tryAddLocalTemplates(List maybePiTokenTemplates) async { -// Logger.info( -// 'Trying to add (${maybePiTokenTemplates.length}) local templates to container (${credential.serial}).', -// name: 'TokenContainerNotifier#tryAddLocalTemplates', -// ); -// Logger.debug('Local templates (${maybePiTokenTemplates.length})', name: 'TokenContainerNotifier#tryAddLocalTemplates'); -// await _stateMutex.acquire(); -// final oldState = (await future); -// final newLocalTokenTemplates = [...maybePiTokenTemplates]; -// final newState = oldState.copyWith(localTokenTemplates: newLocalTokenTemplates); -// final savedState = await _saveToRepo(newState); -// Logger.debug( -// 'Saved TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', -// name: 'TokenContainerNotifier#tryAddLocalTemplates', -// ); -// await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); -// state = AsyncValue.data(savedState); -// _stateMutex.release(); -// return savedState; -// } - -// Future handleDeletedTokenTemplates(List deletedPiTokenTemplates) async { -// Logger.info( -// 'Removing (${deletedPiTokenTemplates.length}) deleted token templates from container (${credential.serial}).', -// name: 'TokenContainerNotifier#handleDeletedTokenTemplates', -// ); -// Logger.debug('Deleted token templates (${deletedPiTokenTemplates.length})', name: 'TokenContainerNotifier#handleDeletedTokenTemplates'); -// await _stateMutex.acquire(); -// final oldState = (await future); -// final newLocalTokenTemplates = oldState.localTokenTemplates.where((template) => !deletedPiTokenTemplates.contains(template)).toList(); -// final newState = oldState.copyWith(localTokenTemplates: newLocalTokenTemplates); -// final savedState = await _saveToRepo(newState); -// Logger.debug( -// 'Saved TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', -// name: 'TokenContainerNotifier#handleDeletedTokenTemplates', -// ); -// await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); -// state = AsyncValue.data(savedState); -// _stateMutex.release(); -// return savedState; -// } - -// Future fetchTokens() async { -// await _stateMutex.acquire(); -// final savedState = await _fetchFromRepo(); -// Logger.debug( -// 'Fetched TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', -// name: 'TokenContainerNotifier#tryAddLocalTemplates', -// ); -// await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); -// state = AsyncValue.data(savedState); -// _stateMutex.release(); -// return savedState; -// } - -// Future sync() async { -// await _stateMutex.acquire(); -// final savedState = await _fetchFromRepo(); -// Logger.debug( -// 'Fetched TokenContainer: Synced(${savedState.syncedTokenTemplates.length}) | Local(${savedState.localTokenTemplates.length})', -// name: 'TokenContainerNotifier#tryAddLocalTemplates', -// ); -// await ref.read(tokenProvider.notifier).updateContainerTokens(savedState); -// state = AsyncValue.data(savedState); -// _stateMutex.release(); -// return savedState; -// } -// } - -// final example = { -// 'container': 'CONTAINER_SERIAL', -// 'tokens': { -// 'TOKEN_SERIAL_1': {}, -// [123456, 654321]: {}, -// 'TOKEN_SERIAL_2': {}, -// [234567, 765432]: {}, -// 'TOKEN_SERIAL_3': {}, -// [345678, 876543]: {}, -// }, -// }; - -// // class ContainerResponse { -// // String serial; -// // List templatesByOtps; - -// // ContainerResponse._({ -// // required this.serial, -// // required this.templatesByOtps, -// // }); - -// // factory ContainerResponse.fromExample(Map example) { -// // final containerSerial = example['container'] as String; -// // final tokenTemplates = []; - -// // final templates = example['tokens'] as Map; -// // templates.forEach((templateKey, value) { -// // if (templateKey is String) { -// // tokenTemplates.add(TokenTemplate.withSerial(otpAuthMap: templates[templateKey], serial: templateKey)); -// // } else if (templateKey is List) { -// // tokenTemplates.add(TokenTemplate.withOtps(otpAuthMap: templates[templateKey], otps: templateKey)); -// // } -// // }); - -// // return ContainerResponse._( -// // serial: containerSerial, -// // templatesByOtps: tokenTemplates, -// // ); -// // } -// // } +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; + +import 'package:basic_utils/basic_utils.dart'; +import 'package:collection/collection.dart'; +import 'package:http/http.dart'; +import 'package:mutex/mutex.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/ec_key_algorithm_extension.dart'; +import 'package:privacyidea_authenticator/model/processor_result.dart'; +import 'package:privacyidea_authenticator/model/tokens/token.dart'; +import 'package:privacyidea_authenticator/utils/globals.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; +import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../../api/token_container_api_endpoint.dart'; +import '../../../../interfaces/repo/token_container_repository.dart'; +import '../../../../l10n/app_localizations.dart'; +import '../../../../model/enums/container_finalization_state.dart'; +import '../../../../model/riverpod_states/token_container_state.dart'; +import '../../../../model/riverpod_states/token_state.dart'; +import '../../../../model/token_container.dart'; +import '../../../../repo/secure_token_container_repository.dart'; +import '../../../ecc_utils.dart'; +import '../../../errors.dart'; +import '../../../logger.dart'; + +part 'token_container_notifier.g.dart'; + +final containerCredentialsProvider = tokenContainerNotifierProviderOf( + repo: SecureTokenContainerRepository(), + containerApi: const PrivacyideaContainerApi(ioClient: PrivacyideaIOClient()), + eccUtils: const EccUtils(), +); + +@Riverpod(keepAlive: true) +class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler { + final _stateMutex = Mutex(); + final _repoMutex = Mutex(); + + TokenContainerNotifier({ + TokenContainerRepository? repoOverride, + PrivacyideaContainerApi? containerApiOverride, + EccUtils? eccUtilsOverride, + }) : _repoOverride = repoOverride, + _containerApiOverride = containerApiOverride, + _eccUtilsOverride = eccUtilsOverride; + + @override + TokenContainerRepository get repo => _repo; + late TokenContainerRepository _repo; + final TokenContainerRepository? _repoOverride; + + @override + PrivacyideaContainerApi get containerApi => _containerApi; + late PrivacyideaContainerApi _containerApi; + final PrivacyideaContainerApi? _containerApiOverride; + + @override + EccUtils get eccUtils => _eccUtils; + late EccUtils _eccUtils; + final EccUtils? _eccUtilsOverride; + + @override + Future build({ + required TokenContainerRepository repo, + required PrivacyideaContainerApi containerApi, + required EccUtils eccUtils, + }) async { + await _stateMutex.acquire(); + _repo = _repoOverride ?? repo; + _containerApi = _containerApiOverride ?? containerApi; + _eccUtils = _eccUtilsOverride ?? eccUtils; + Logger.warning('Building containerProvider', name: 'CredentialsNotifier'); + + final initState = await _repo.loadCredentialsState(); + for (var container in initState.container.whereType()) { + finalize(container); + } + _stateMutex.release(); + return initState; + } + +////////////////////////////////////////////////////////////////// +////////////////////////// REPO METHODS ////////////////////////// +////////////////////////////////////////////////////////////////// + + Future _saveCredentialToRepo(TokenContainer container) async { + return await _repoMutex.protect(() async => await _repo.saveCredential(container)); + } + + Future _saveCredentialsStateToRepo(TokenContainerState containerState) async { + return await _repoMutex.protect(() async => await _repo.saveCredentialsState(containerState)); + } + + Future _deleteCredentialFromRepo(TokenContainer container) async { + return await _repoMutex.protect(() async => await _repo.deleteCredential(container.serial)); + } + + Future _deleteCredentialsStateToRepo() async { + return await _repoMutex.protect(() async => await _repo.deleteAllCredentials()); + } + +/*////////////////////////////////////////////////////////////////// +////////////////////////// PUBLIC METHODS ////////////////////////// +///////////////////////////////////////////////////////////////// */ + +// ADD CREDENTIALS + + Future addCredential(TokenContainer container) async { + await _stateMutex.acquire(); + final newState = await _saveCredentialToRepo(container); + await update((_) => newState); + _stateMutex.release(); + return newState; + } + + Future addCredentials(List container) async { + await _stateMutex.acquire(); + final newCredentials = container.toList(); + final oldCredentials = (await future).container; + Logger.debug('Loaded container: $oldCredentials', name: 'CredentialsNotifier#addCredentials'); + final combinedCredentials = []; + for (var oldCredential in oldCredentials) { + final newCredential = newCredentials.firstWhereOrNull((newCredential) => newCredential.serial == oldCredential.serial); + if (newCredential == null) { + combinedCredentials.add(oldCredential); + } else { + combinedCredentials.add(newCredential); + newCredentials.remove(newCredential); + } + } + combinedCredentials.addAll(newCredentials); + Logger.debug('Combined container: $combinedCredentials', name: 'CredentialsNotifier#addCredentials'); + final newState = await _saveCredentialsStateToRepo(TokenContainerState(container: combinedCredentials)); + Logger.debug('Saved container: $newState', name: 'CredentialsNotifier#addCredentials'); + await update((_) => newState); + Logger.debug('Updated container: $newState', name: 'CredentialsNotifier#addCredentials'); + _stateMutex.release(); + return newState; + } + + // UPDATE CREDENTIALS + + @override + Future update( + FutureOr Function(TokenContainerState state) cb, { + FutureOr Function(Object, StackTrace)? onError, + }) async { + Logger.warning('Updating containerProvider', name: 'CredentialsNotifier'); + return super.update(cb, onError: onError); + } + + Future updateCredential(T container, T Function(T) updater) async { + await _stateMutex.acquire(); + final oldState = await future; + final currentCredential = oldState.currentOf(container); + if (currentCredential == null) { + Logger.info('Failed to update container. It was probably removed in the meantime.', name: 'CredentialsNotifier#updateCredential'); + _stateMutex.release(); + return null; + } + final updated = updater(currentCredential); + final newState = await _saveCredentialToRepo(updated); + await update((_) => newState); + _stateMutex.release(); + return updated; + } + + // DELETE CREDENTIALS + + Future deleteCredential(TokenContainer container) async { + await _stateMutex.acquire(); + final newState = await _deleteCredentialFromRepo(container); + await update((_) => newState); + _stateMutex.release(); + return newState; + } + + Future deleteCredentials(List container) async { + await _stateMutex.acquire(); + final newCredentials = container.toList(); + final oldCredentials = (await future).container; + final combinedCredentials = []; + for (var oldCredential in oldCredentials) { + final newCredential = newCredentials.firstWhereOrNull((newCredential) => newCredential.serial == oldCredential.serial); + if (newCredential == null) { + combinedCredentials.add(oldCredential); + } else { + newCredentials.remove(newCredential); + } + } + final newState = await _saveCredentialsStateToRepo(TokenContainerState(container: combinedCredentials)); + await update((_) => newState); + _stateMutex.release(); + return newState; + } + + // HANDLE PROCESSOR RESULTS + + @override + Future handleProcessorResult(ProcessorResult result, Map args) { + // TODO: implement handleResult + throw UnimplementedError(); + } + + @override + Future handleProcessorResults(List results, Map args) async { + Logger.info('Handling processor results', name: 'CredentialsNotifier#handleProcessorResults'); + final containerCredentials = results.getData().whereType().toList(); + if (containerCredentials.isEmpty) { + return null; + } + final currentState = await future; + final stateCredentials = currentState.container; + final stateCredentialsSerials = stateCredentials.map((e) => e.serial); + final newCredentials = containerCredentials.where((element) => !stateCredentialsSerials.contains(element.serial)).toList(); + Logger.info('Handling processor results: adding Credential', name: 'CredentialsNotifier#handleProcessorResults'); + await addCredentials(newCredentials); + Logger.info('Handling processor results: adding done (${newCredentials.length})', name: 'CredentialsNotifier#handleProcessorResults'); + for (var container in newCredentials) { + Logger.info('Handling processor results: finalize check ()', name: 'CredentialsNotifier#handleProcessorResults'); + if (container is! TokenContainerUnfinalized) continue; + Logger.info('Handling processor results: finalize', name: 'CredentialsNotifier#handleProcessorResults'); + await finalize(container); + } + return null; + } + + final Mutex _finalizationMutex = Mutex(); + Future finalize(TokenContainer container) async { + await _finalizationMutex.acquire(); + if (container is! TokenContainerUnfinalized) { + _finalizationMutex.release(); + throw ArgumentError('Container must not be finalized'); + } + Logger.info('Finalizing container ${container.serial}', name: 'CredentialsNotifier#finalize'); + try { + container = await _generateKeyPair(container); + final Response response; + (container, response) = await _sendPublicKey(container); + if (response.statusCode != 200) { + if (response.piServerMessage != null) { + ref.read(statusMessageProvider.notifier).state = ( + 'Failed to finalize container: ${response.piServerMessage}', + response.piStatusCode != null ? 'PI Server code ${response.piStatusCode}' : 'Status code ${response.body}' + ); + } else { + ref.read(statusMessageProvider.notifier).state = ('Failed to finalize container: ${response.body}', 'Status code ${response.statusCode}'); + } + _finalizationMutex.release(); + return; + } + final ECPublicKey publicServerKey; + (container, publicServerKey) = await _parseResponse(container, response); + await updateCredential(container, (c) => c.finalize(publicServerKey: publicServerKey)!); + } on StateError { + Logger.info('Container was removed while finalizing', name: 'CredentialsNotifier#finalize'); + } on LocalizedArgumentError catch (e) { + ref.read(statusMessageProvider.notifier).state = ('Failed to decode response body', e.localizedMessage(AppLocalizations.of(await globalContext)!)); + await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseFailed)); + } catch (e) { + Logger.error('Failed to finalize container ${container.serial}', name: 'CredentialsNotifier#finalize', error: e); + _finalizationMutex.release(); + return; + } + _finalizationMutex.release(); + } + +/* ///////////////////////////////////////////////////////////////////////// +////////////////////////// PRIVATE HELPER METHODS ////////////////////////// +///////////////////////////////////////////////////////////////////////// */ + + /// Finalization substep 1: Generate key pair + Future _generateKeyPair(TokenContainerUnfinalized containerCredential) async { + // generatingKeyPair, + // generatingKeyPairFailed, + // generatingKeyPairCompleted, + TokenContainerUnfinalized? container = containerCredential; + container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.generatingKeyPair)); + if (container == null) throw StateError('Credential was removed'); + final keyPair = CryptoUtils.generateEcKeyPair(curve: container.ecKeyAlgorithm.curveName); + container = await updateCredential(container, (c) => c.withClientKeyPair(keyPair) as TokenContainerUnfinalized); + if (container == null) throw StateError('Credential was removed'); + return container; + } + + /// Finalization substep 2: Send public key + Future<(TokenContainer, Response)> _sendPublicKey(TokenContainerUnfinalized containerc) async { + // sendingPublicKey, + // sendingPublicKeyFailed, + // sendingPublicKeyCompleted, + + //POST /container/register/finalize + // Request: { + // 'container_serial': , + // 'public_client_key': , + // 'signature': )>, + // } + + TokenContainerUnfinalized? container = containerc; + + final Response response; + container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKey)); + if (container == null) throw StateError('Credential was removed'); + try { + response = (await _containerApi.finalizeContainer(container, eccUtils))!; + } catch (e) { + ref.read(statusMessageProvider.notifier).state = ('Failed to finalize container', e.toString()); + await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyFailed)); + rethrow; + } + if (response.statusCode != 200) { + container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyFailed)); + if (container == null) throw StateError('Credential was removed'); + return (container, response); + } + + container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyCompleted)); + if (container == null) throw StateError('Credential was removed'); + return (container, response); + } + + /// Finalization substep 3: Parse response + Future<(TokenContainer, ECPublicKey)> _parseResponse(TokenContainer containerCredential, Response response) async { + // parsingResponse, + // parsingResponseFailed, + // parsingResponseCompleted, + + TokenContainer? container = containerCredential; + ECPublicKey publicServerKey; + String responseBody = response.body; + Map responseJson; + container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponse)); + if (container == null) throw StateError('Credential was removed'); + responseJson = jsonDecode(responseBody); + Logger.debug('Response JSON: $responseJson', name: 'CredentialsNotifier#_parseResponse'); + final result = validate(value: responseJson['result'], validator: const TypeValidatorRequired>(), name: 'result'); + final value = validate(value: result['value'], validator: const TypeValidatorRequired>(), name: 'value'); + publicServerKey = validate( + value: value['public_server_key'], + validator: TypeValidatorRequired(transformer: (v) => const EccUtils().deserializeECPublicKey(v)), + name: 'public_server_key', + ); + final syncUrlUri = validate( + value: value['container_sync_url'], + validator: TypeValidatorRequired(transformer: (v) => Uri.parse(v)), + name: 'container_sync_url', + ); + container = + await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseCompleted, syncUrl: syncUrlUri)); + if (container == null) throw StateError('Credential was removed'); + return (container, publicServerKey); + } + + Future syncTokens(TokenState tokenState) async { + final containerCredentials = (await future).container; + final containerCredential = containerCredentials.whereType().first; + + List syncedTokens; + List deletedTokens; + final tuple = await _containerApi.sync( + containerCredential, + tokenState, + ); + if (tuple == null) { + return; + } + syncedTokens = tuple.$1; + deletedTokens = tuple.$2; + + await ref.read(tokenProvider.notifier).addOrReplaceTokens(syncedTokens); + await ref.read(tokenProvider.notifier).removeTokensBySerials(deletedTokens); + } +} diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart similarity index 57% rename from lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart rename to lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart index f7cc82d6e..04ec570b2 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart @@ -1,13 +1,13 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'credential_notifier.dart'; +part of 'token_container_notifier.dart'; // ************************************************************************** // RiverpodGenerator // ************************************************************************** -String _$containerCredentialsNotifierHash() => - r'83daeabb57839b956f33171c95c8f45393049d29'; +String _$tokenContainerNotifierHash() => + r'cf5aaebd080b94a5bff442a564a8f29280f56871'; /// Copied from Dart SDK class _SystemHash { @@ -30,37 +30,36 @@ class _SystemHash { } } -abstract class _$ContainerCredentialsNotifier - extends BuildlessAsyncNotifier { - late final ContainerCredentialsRepository repo; +abstract class _$TokenContainerNotifier + extends BuildlessAsyncNotifier { + late final TokenContainerRepository repo; late final PrivacyideaContainerApi containerApi; late final EccUtils eccUtils; - FutureOr build({ - required ContainerCredentialsRepository repo, + FutureOr build({ + required TokenContainerRepository repo, required PrivacyideaContainerApi containerApi, required EccUtils eccUtils, }); } -/// See also [ContainerCredentialsNotifier]. -@ProviderFor(ContainerCredentialsNotifier) -const containerCredentialsNotifierProviderOf = - ContainerCredentialsNotifierFamily(); +/// See also [TokenContainerNotifier]. +@ProviderFor(TokenContainerNotifier) +const tokenContainerNotifierProviderOf = TokenContainerNotifierFamily(); -/// See also [ContainerCredentialsNotifier]. -class ContainerCredentialsNotifierFamily - extends Family> { - /// See also [ContainerCredentialsNotifier]. - const ContainerCredentialsNotifierFamily(); +/// See also [TokenContainerNotifier]. +class TokenContainerNotifierFamily + extends Family> { + /// See also [TokenContainerNotifier]. + const TokenContainerNotifierFamily(); - /// See also [ContainerCredentialsNotifier]. - ContainerCredentialsNotifierProvider call({ - required ContainerCredentialsRepository repo, + /// See also [TokenContainerNotifier]. + TokenContainerNotifierProvider call({ + required TokenContainerRepository repo, required PrivacyideaContainerApi containerApi, required EccUtils eccUtils, }) { - return ContainerCredentialsNotifierProvider( + return TokenContainerNotifierProvider( repo: repo, containerApi: containerApi, eccUtils: eccUtils, @@ -68,8 +67,8 @@ class ContainerCredentialsNotifierFamily } @override - ContainerCredentialsNotifierProvider getProviderOverride( - covariant ContainerCredentialsNotifierProvider provider, + TokenContainerNotifierProvider getProviderOverride( + covariant TokenContainerNotifierProvider provider, ) { return call( repo: provider.repo, @@ -90,37 +89,37 @@ class ContainerCredentialsNotifierFamily _allTransitiveDependencies; @override - String? get name => r'containerCredentialsNotifierProviderOf'; + String? get name => r'tokenContainerNotifierProviderOf'; } -/// See also [ContainerCredentialsNotifier]. -class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< - ContainerCredentialsNotifier, CredentialsState> { - /// See also [ContainerCredentialsNotifier]. - ContainerCredentialsNotifierProvider({ - required ContainerCredentialsRepository repo, +/// See also [TokenContainerNotifier]. +class TokenContainerNotifierProvider extends AsyncNotifierProviderImpl< + TokenContainerNotifier, TokenContainerState> { + /// See also [TokenContainerNotifier]. + TokenContainerNotifierProvider({ + required TokenContainerRepository repo, required PrivacyideaContainerApi containerApi, required EccUtils eccUtils, }) : this._internal( - () => ContainerCredentialsNotifier() + () => TokenContainerNotifier() ..repo = repo ..containerApi = containerApi ..eccUtils = eccUtils, - from: containerCredentialsNotifierProviderOf, - name: r'containerCredentialsNotifierProviderOf', + from: tokenContainerNotifierProviderOf, + name: r'tokenContainerNotifierProviderOf', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$containerCredentialsNotifierHash, - dependencies: ContainerCredentialsNotifierFamily._dependencies, + : _$tokenContainerNotifierHash, + dependencies: TokenContainerNotifierFamily._dependencies, allTransitiveDependencies: - ContainerCredentialsNotifierFamily._allTransitiveDependencies, + TokenContainerNotifierFamily._allTransitiveDependencies, repo: repo, containerApi: containerApi, eccUtils: eccUtils, ); - ContainerCredentialsNotifierProvider._internal( + TokenContainerNotifierProvider._internal( super._createNotifier, { required super.name, required super.dependencies, @@ -132,13 +131,13 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< required this.eccUtils, }) : super.internal(); - final ContainerCredentialsRepository repo; + final TokenContainerRepository repo; final PrivacyideaContainerApi containerApi; final EccUtils eccUtils; @override - FutureOr runNotifierBuild( - covariant ContainerCredentialsNotifier notifier, + FutureOr runNotifierBuild( + covariant TokenContainerNotifier notifier, ) { return notifier.build( repo: repo, @@ -148,10 +147,10 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< } @override - Override overrideWith(ContainerCredentialsNotifier Function() create) { + Override overrideWith(TokenContainerNotifier Function() create) { return ProviderOverride( origin: this, - override: ContainerCredentialsNotifierProvider._internal( + override: TokenContainerNotifierProvider._internal( () => create() ..repo = repo ..containerApi = containerApi @@ -169,14 +168,14 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< } @override - AsyncNotifierProviderElement + AsyncNotifierProviderElement createElement() { - return _ContainerCredentialsNotifierProviderElement(this); + return _TokenContainerNotifierProviderElement(this); } @override bool operator ==(Object other) { - return other is ContainerCredentialsNotifierProvider && + return other is TokenContainerNotifierProvider && other.repo == repo && other.containerApi == containerApi && other.eccUtils == eccUtils; @@ -193,10 +192,10 @@ class ContainerCredentialsNotifierProvider extends AsyncNotifierProviderImpl< } } -mixin ContainerCredentialsNotifierRef - on AsyncNotifierProviderRef { +mixin TokenContainerNotifierRef + on AsyncNotifierProviderRef { /// The parameter `repo` of this provider. - ContainerCredentialsRepository get repo; + TokenContainerRepository get repo; /// The parameter `containerApi` of this provider. PrivacyideaContainerApi get containerApi; @@ -205,20 +204,19 @@ mixin ContainerCredentialsNotifierRef EccUtils get eccUtils; } -class _ContainerCredentialsNotifierProviderElement - extends AsyncNotifierProviderElement with ContainerCredentialsNotifierRef { - _ContainerCredentialsNotifierProviderElement(super.provider); +class _TokenContainerNotifierProviderElement + extends AsyncNotifierProviderElement with TokenContainerNotifierRef { + _TokenContainerNotifierProviderElement(super.provider); @override - ContainerCredentialsRepository get repo => - (origin as ContainerCredentialsNotifierProvider).repo; + TokenContainerRepository get repo => + (origin as TokenContainerNotifierProvider).repo; @override PrivacyideaContainerApi get containerApi => - (origin as ContainerCredentialsNotifierProvider).containerApi; + (origin as TokenContainerNotifierProvider).containerApi; @override - EccUtils get eccUtils => - (origin as ContainerCredentialsNotifierProvider).eccUtils; + EccUtils get eccUtils => (origin as TokenContainerNotifierProvider).eccUtils; } // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart index dfc5776d0..ce9ba2d77 100644 --- a/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart +++ b/lib/utils/riverpod/state_listeners/token_container_token_state_listener.dart @@ -23,7 +23,7 @@ // import '../../../interfaces/riverpod/state_listeners/state_notifier_provider_listeners/token_state_listener.dart'; // import '../../../model/riverpod_states/token_state.dart'; -// import '../riverpod_providers/generated_providers/credential_notifier.dart'; +// import '../riverpod_providers/generated_providers/container_notifier.dart'; // import '../riverpod_providers/generated_providers/token_container_notifier.dart'; // class ContainerListensToTokenState extends TokenStateListener { @@ -40,24 +40,24 @@ // static Future _onNewState(TokenState? previousState, TokenState nextState, WidgetRef ref) async { // Logger.warning('New token state', name: 'TokenContainerTokenStateListener'); // final maybePiTokenTemplates = nextState.lastlyUpdatedTokens.maybePiTokens.toTemplates(); -// final credentials = (await ref.read(containerCredentialsProvider.future)).credentials; -// Logger.warning('Readed: $credentials', name: 'TokenContainerTokenStateListener'); +// final container = (await ref.read(containerCredentialsProvider.future)).container; +// Logger.warning('Readed: $container', name: 'TokenContainerTokenStateListener'); // // if (maybePiTokenTemplates.isEmpty) return; -// for (var credential in credentials) { -// final deletedPiTokenTemplates = nextState.lastlyDeletedTokens.ofContainer(credential.serial).toTemplates(); +// for (var container in container) { +// final deletedPiTokenTemplates = nextState.lastlyDeletedTokens.ofContainer(container.serial).toTemplates(); // if (deletedPiTokenTemplates.isNotEmpty) { // Logger.warning( -// 'Deleted (${deletedPiTokenTemplates.length}) tokens from container "${credential.serial}"', +// 'Deleted (${deletedPiTokenTemplates.length}) tokens from container "${container.serial}"', // name: 'TokenContainerTokenStateListener', // ); -// // await ref.read(tokenContainerNotifierProviderOf(credential: credential).notifier).handleDeletedTokenTemplates(deletedPiTokenTemplates); +// // await ref.read(tokenContainerNotifierProviderOf(container: container).notifier).handleDeletedTokenTemplates(deletedPiTokenTemplates); // } // if (maybePiTokenTemplates.isNotEmpty) { // Logger.warning( -// 'Adding maybePiTokenTemplates (${maybePiTokenTemplates.length}) to container ${credential.serial}', +// 'Adding maybePiTokenTemplates (${maybePiTokenTemplates.length}) to container ${container.serial}', // name: 'TokenContainerTokenStateListener', // ); -// // await ref.read(tokenContainerNotifierProviderOf(credential: credential).notifier).tryAddLocalTemplates(maybePiTokenTemplates); +// // await ref.read(tokenContainerNotifierProviderOf(container: container).notifier).tryAddLocalTemplates(maybePiTokenTemplates); // } // } // } diff --git a/lib/utils/riverpod/state_notifiers/token_notifier.dart b/lib/utils/riverpod/state_notifiers/token_notifier.dart index f6be22c8c..e27872e32 100644 --- a/lib/utils/riverpod/state_notifiers/token_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_notifier.dart @@ -667,7 +667,7 @@ // sslVerify: pushToken.sslVerify, // url: pushToken.url!, // body: { -// 'enrollment_credential': pushToken.enrollmentCredentials, +// 'enrollment_container': pushToken.enrollmentCredentials, // 'serial': pushToken.serial, // 'fbtoken': fbToken, // 'pubkey': _rsaUtils.serializeRSAPublicKeyPKCS8(pushToken.rsaPublicTokenKey!), diff --git a/lib/views/container_view/container_view.dart b/lib/views/container_view/container_view.dart index 3b8fb2881..112160945 100644 --- a/lib/views/container_view/container_view.dart +++ b/lib/views/container_view/container_view.dart @@ -21,13 +21,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/token_widget_tile.dart'; import '../../l10n/app_localizations.dart'; -import '../../model/tokens/container_credentials.dart'; +import '../../model/token_container.dart'; import '../../utils/customization/theme_extentions/action_theme.dart'; +import '../../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../widgets/pi_slideable.dart'; import '../main_view/main_view_widgets/token_widgets/slideable_action.dart'; @@ -45,7 +45,7 @@ class ContainerView extends ConsumerView { @override Widget build(BuildContext context, WidgetRef ref) { - final credentials = ref.watch(containerCredentialsProvider).whenOrNull(data: (data) => data.credentials) ?? []; + final container = ref.watch(containerCredentialsProvider).whenOrNull(data: (data) => data.container) ?? []; return Scaffold( appBar: AppBar(title: const Text('Container')), floatingActionButton: const QrScannerButton(), @@ -54,7 +54,7 @@ class ContainerView extends ConsumerView { child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - for (var containerCredential in credentials) ContainerWidget(containerCredential: containerCredential), + for (var containerCredential in container) ContainerWidget(containerCredential: containerCredential), ], ), ), @@ -63,7 +63,7 @@ class ContainerView extends ConsumerView { } class ContainerWidget extends ConsumerWidget { - final ContainerCredential containerCredential; + final TokenContainer containerCredential; final List stack; @@ -89,7 +89,7 @@ class ContainerWidget extends ConsumerWidget { 'issuer: ${containerCredential.issuer}', 'finalizationState: ${containerCredential.finalizationState.name}', ], - trailing: containerCredential is ContainerCredentialFinalized + trailing: containerCredential is TokenContainerFinalized ? IconButton( icon: const Icon(Icons.sync), onPressed: () { @@ -109,7 +109,7 @@ class ContainerWidget extends ConsumerWidget { } class DeleteContainerAction extends PiSlideableAction { - final ContainerCredential container; + final TokenContainer container; const DeleteContainerAction({ required this.container, @@ -136,7 +136,7 @@ class DeleteContainerAction extends PiSlideableAction { } class EditContainerAction extends PiSlideableAction { - final ContainerCredential container; + final TokenContainer container; const EditContainerAction({ required this.container, diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart index f4c79be0a..f46159abd 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart @@ -20,12 +20,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../model/processor_result.dart'; import '../../../../utils/globals.dart'; +import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import '../../../../utils/utils.dart'; import '../../../../utils/view_utils.dart'; import '../../../../widgets/dialog_widgets/default_dialog.dart'; diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index 1b3f2cefc..a5e420b4e 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -7,9 +7,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; -import '../utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../utils/riverpod/state_listeners/home_widget_deep_link_listener.dart'; @@ -82,8 +82,8 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { @override Widget build(BuildContext context) { - final credentials = ref.watch(containerCredentialsProvider).value?.credentials ?? []; - Logger.debug('Credentials: $credentials', name: 'AppWrapper#build'); + final container = ref.watch(containerCredentialsProvider).value?.container ?? []; + Logger.debug('Credentials: $container', name: 'AppWrapper#build'); return SingleTouchRecognizer( child: StateObserver( stateNotifierProviderListeners: const [], @@ -96,10 +96,10 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { HomeWidgetDeepLinkListener(deeplinkProvider: deeplinkNotifierProvider), // TODO: Nochmal anschauen ], // asyncNotifierProviderListeners: [ - // ...credentials.map( - // (credential) { + // ...container.map( + // (container) { // return TokenStateListensToContainer( - // containerProvider: tokenContainerNotifierProviderOf(credential: credential), + // containerProvider: tokenContainerNotifierProviderOf(container: container), // ref: ref, // ); // }, diff --git a/lib/widgets/default_refresh_indicator.dart b/lib/widgets/default_refresh_indicator.dart index c246e867d..4ded61ec4 100644 --- a/lib/widgets/default_refresh_indicator.dart +++ b/lib/widgets/default_refresh_indicator.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../utils/logger.dart'; import '../utils/push_provider.dart'; -import '../utils/riverpod/riverpod_providers/generated_providers/credential_notifier.dart'; +import '../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import '../views/main_view/main_view_widgets/loading_indicator.dart'; import 'deactivateable_refresh_indicator.dart'; @@ -31,11 +31,11 @@ class _DefaultRefreshIndicatorState extends ConsumerState loadContainerState() { -// if (_doesThrow) throw Exception('Test exception'); -// return Future.value(savedState); -// } - -// @override -// Future saveContainerState(TokenContainer containerState) { -// if (_doesThrow) throw Exception('Test exception'); -// savedState = containerState; -// return Future.value(savedState); -// } -// } - -// class MockTokenContainerApiEndpoint implements PrivacyideaContainerApi { -// TokenContainer state; -// MockTokenContainerApiEndpoint({required TokenContainer initialState}) : state = initialState; - -// @override -// Future fetch() { -// return Future.value(state); -// } - -// @override -// Future sync(TokenContainer containerState) { -// state = containerState.copyTransformInto(lastSyncAt: DateTime.now()); -// return Future.value(state); -// } - -// @override -// ContainerCredential get credential => throw UnimplementedError(); -// } - -// void main() { -// _testHybridTokenContainerRepository(); -// } - -// void _testHybridTokenContainerRepository() { -// group('HybridTokenContainerRepository test', () { -// test('HybridTokenContainerRepository test', () async { -// final token = TOTPToken( -// period: 30, -// id: 'TOTP_1', -// algorithm: Algorithms.SHA1, -// digits: 6, -// secret: 'SECRET', -// issuer: 'issuer', -// ); -// TokenContainer? remoteState = TokenContainer.synced( -// serial: 'containerSerial', -// description: 'description', -// syncedTokenTemplates: [token.toTemplate()], -// lastSyncAt: DateTime.now(), -// localTokenTemplates: [], -// ); -// TokenContainer? localState = TokenContainer.modified( -// lastModifiedAt: DateTime.now(), -// lastSyncAt: DateTime.now().subtract(const Duration(days: 1)), -// serial: 'containerSerial', -// description: 'description', -// syncedTokenTemplates: [], -// localTokenTemplates: [token.toTemplate()], -// ); - -// final localRepo = MockTokenContainerRepository(initialState: localState); -// final remoteRepo = RemoteTokenContainerRepository(apiEndpoint: MockTokenContainerApiEndpoint(initialState: remoteState)); - -// final hybridRepo = HybridTokenContainerRepository( -// localRepository: localRepo, -// remoteRepository: remoteRepo, -// ); -// final dateTimeBefore = DateTime.now(); -// final state = await hybridRepo.loadContainerState(); -// await Future.delayed(const Duration(milliseconds: 1)); -// final dateTimeAfter = DateTime.now(); -// expect(state, isA()); -// state as TokenContainerSynced; -// expect(state.lastSyncAt.isAfter(dateTimeBefore), isTrue); -// expect(state.lastSyncAt.isBefore(dateTimeAfter), isTrue); -// expect(state.syncedTokenTemplates.length, 1); -// final template = state.syncedTokenTemplates.first; -// expect(template.data, token.toOtpAuthMap(), reason: 'Should be the remote state if both are changed since last sync'); -// }); -// }); -// } From f657bed0c76e302557ccae5b60ea6def07eb32e7 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:53:05 +0200 Subject: [PATCH 041/285] fixed based on the tests --- lib/l10n/app_en.arb | 2 +- lib/model/processor_result.dart | 1 + lib/model/processor_result.freezed.dart | 45 ++++++++++++------- lib/model/tokens/day_password_token.dart | 2 +- lib/model/tokens/hotp_token.dart | 2 +- lib/model/tokens/push_token.dart | 2 +- lib/model/tokens/steam_token.dart | 2 +- lib/model/tokens/token.dart | 2 +- lib/model/tokens/totp_token.dart | 2 +- .../otp_auth_processor.dart | 23 +++++++--- .../aegis_import_file_processor.dart | 2 +- .../free_otp_plus_import_file_processor.dart | 3 +- .../two_fas_import_file_processor.dart | 13 ++---- .../push_request_provider.dart | 3 +- .../push_request_provider.g.dart | 2 +- .../token_folder_notifier.dart | 8 +++- .../token_folder_notifier.g.dart | 17 ++++--- .../generated_providers/token_notifier.dart | 6 ++- .../generated_providers/token_notifier.g.dart | 2 +- pubspec.lock | 16 ------- pubspec.yaml | 3 +- .../free_otp_plus_qr_processor_test.dart | 4 +- .../otp_auth_processor_test.dart | 39 ++++++++-------- .../aegis_import_file_processor_test.dart | 4 +- .../deeplink_notifier_test.dart | 31 ++++++------- .../push_request_notifier_test.dart | 6 +-- .../sortable_notifier_test.dart | 34 ++++++++------ .../token_folder_notifier_test.dart | 8 ++-- .../state_notifiers/token_notifier_test.dart | 30 +++++++------ .../application_customization_test.dart | 6 +-- 30 files changed, 172 insertions(+), 148 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f0d3eb7cc..76f41b042 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -79,7 +79,7 @@ "@scanQrCode": { "description": "The button to scan otpauth qr-codes." }, - "enterDetailsForToken": "Enter details for token", + "enterDetailsForToken": "Enter token details", "@enterDetailsForToken": { "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." }, diff --git a/lib/model/processor_result.dart b/lib/model/processor_result.dart index 1bb887d34..0c1a12dd4 100644 --- a/lib/model/processor_result.dart +++ b/lib/model/processor_result.dart @@ -38,6 +38,7 @@ abstract class ProcessorResult with _$ProcessorResult { }) = ProcessorResultSuccess; const factory ProcessorResult.failed( String message, { + dynamic error, TypeValidatorRequired? resultHandlerType, }) = ProcessorResultFailed; diff --git a/lib/model/processor_result.freezed.dart b/lib/model/processor_result.freezed.dart index b34be9d50..0365448a1 100644 --- a/lib/model/processor_result.freezed.dart +++ b/lib/model/processor_result.freezed.dart @@ -23,7 +23,7 @@ mixin _$ProcessorResult { required TResult Function(T resultData, TypeValidatorRequired? resultHandlerType) success, - required TResult Function(String message, + required TResult Function(String message, dynamic error, TypeValidatorRequired? resultHandlerType) failed, }) => @@ -33,7 +33,7 @@ mixin _$ProcessorResult { TResult? Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, - TResult? Function(String message, + TResult? Function(String message, dynamic error, TypeValidatorRequired? resultHandlerType)? failed, }) => @@ -43,7 +43,7 @@ mixin _$ProcessorResult { TResult Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, - TResult Function(String message, + TResult Function(String message, dynamic error, TypeValidatorRequired? resultHandlerType)? failed, required TResult orElse(), @@ -201,7 +201,7 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { required TResult Function(T resultData, TypeValidatorRequired? resultHandlerType) success, - required TResult Function(String message, + required TResult Function(String message, dynamic error, TypeValidatorRequired? resultHandlerType) failed, }) { @@ -214,7 +214,7 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { TResult? Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, - TResult? Function(String message, + TResult? Function(String message, dynamic error, TypeValidatorRequired? resultHandlerType)? failed, }) { @@ -227,7 +227,7 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { TResult Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, - TResult Function(String message, + TResult Function(String message, dynamic error, TypeValidatorRequired? resultHandlerType)? failed, required TResult orElse(), @@ -299,6 +299,7 @@ abstract class _$$ProcessorResultFailedImplCopyWith @useResult $Res call( {String message, + dynamic error, TypeValidatorRequired? resultHandlerType}); } @@ -318,6 +319,7 @@ class __$$ProcessorResultFailedImplCopyWithImpl @override $Res call({ Object? message = null, + Object? error = freezed, Object? resultHandlerType = freezed, }) { return _then(_$ProcessorResultFailedImpl( @@ -325,6 +327,10 @@ class __$$ProcessorResultFailedImplCopyWithImpl ? _value.message : message // ignore: cast_nullable_to_non_nullable as String, + error: freezed == error + ? _value.error + : error // ignore: cast_nullable_to_non_nullable + as dynamic, resultHandlerType: freezed == resultHandlerType ? _value.resultHandlerType : resultHandlerType // ignore: cast_nullable_to_non_nullable @@ -336,17 +342,20 @@ class __$$ProcessorResultFailedImplCopyWithImpl /// @nodoc class _$ProcessorResultFailedImpl extends ProcessorResultFailed { - const _$ProcessorResultFailedImpl(this.message, {this.resultHandlerType}) + const _$ProcessorResultFailedImpl(this.message, + {this.error, this.resultHandlerType}) : super._(); @override final String message; @override + final dynamic error; + @override final TypeValidatorRequired? resultHandlerType; @override String toString() { - return 'ProcessorResult<$T>.failed(message: $message, resultHandlerType: $resultHandlerType)'; + return 'ProcessorResult<$T>.failed(message: $message, error: $error, resultHandlerType: $resultHandlerType)'; } @override @@ -355,12 +364,14 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { (other.runtimeType == runtimeType && other is _$ProcessorResultFailedImpl && (identical(other.message, message) || other.message == message) && + const DeepCollectionEquality().equals(other.error, error) && (identical(other.resultHandlerType, resultHandlerType) || other.resultHandlerType == resultHandlerType)); } @override - int get hashCode => Object.hash(runtimeType, message, resultHandlerType); + int get hashCode => Object.hash(runtimeType, message, + const DeepCollectionEquality().hash(error), resultHandlerType); /// Create a copy of ProcessorResult /// with the given fields replaced by the non-null parameter values. @@ -377,11 +388,11 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { required TResult Function(T resultData, TypeValidatorRequired? resultHandlerType) success, - required TResult Function(String message, + required TResult Function(String message, dynamic error, TypeValidatorRequired? resultHandlerType) failed, }) { - return failed(message, resultHandlerType); + return failed(message, error, resultHandlerType); } @override @@ -390,11 +401,11 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { TResult? Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, - TResult? Function(String message, + TResult? Function(String message, dynamic error, TypeValidatorRequired? resultHandlerType)? failed, }) { - return failed?.call(message, resultHandlerType); + return failed?.call(message, error, resultHandlerType); } @override @@ -403,13 +414,13 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { TResult Function(T resultData, TypeValidatorRequired? resultHandlerType)? success, - TResult Function(String message, + TResult Function(String message, dynamic error, TypeValidatorRequired? resultHandlerType)? failed, required TResult orElse(), }) { if (failed != null) { - return failed(message, resultHandlerType); + return failed(message, error, resultHandlerType); } return orElse(); } @@ -448,11 +459,13 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { abstract class ProcessorResultFailed extends ProcessorResult { const factory ProcessorResultFailed(final String message, - {final TypeValidatorRequired? resultHandlerType}) = + {final dynamic error, + final TypeValidatorRequired? resultHandlerType}) = _$ProcessorResultFailedImpl; const ProcessorResultFailed._() : super._(); String get message; + dynamic get error; @override TypeValidatorRequired? get resultHandlerType; diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index dc72d3553..681450f0f 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -184,7 +184,7 @@ class DayPasswordToken extends OTPToken { ); } - factory DayPasswordToken.fromOtpAuthMap(Map uriMap, {required Map additionalData}) { + factory DayPasswordToken.fromOtpAuthMap(Map uriMap, {Map additionalData = const {}}) { uriMap = validateMap( map: uriMap, validators: { diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index 2c1021628..07f5d559a 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -163,7 +163,7 @@ class HOTPToken extends OTPToken { ); } - factory HOTPToken.fromOtpAuthMap(Map otpAuthMap, {required Map additionalData}) { + factory HOTPToken.fromOtpAuthMap(Map otpAuthMap, {Map additionalData = const {}}) { final validatedMap = validateMap( map: otpAuthMap, validators: { diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index be2d83000..569a0d3e7 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -192,7 +192,7 @@ class PushToken extends Token { 'publicTokenKey: $publicTokenKey}'; } - factory PushToken.fromOtpAuthMap(Map otpAuthMap, {required Map additionalData}) { + factory PushToken.fromOtpAuthMap(Map otpAuthMap, {Map additionalData = const {}}) { // Validate map for Push token final validatedMap = validateMap( map: otpAuthMap, diff --git a/lib/model/tokens/steam_token.dart b/lib/model/tokens/steam_token.dart index 2d2606d00..fe35cd397 100644 --- a/lib/model/tokens/steam_token.dart +++ b/lib/model/tokens/steam_token.dart @@ -161,7 +161,7 @@ class SteamToken extends TOTPToken { ); } - static SteamToken fromOtpAuthMap(Map uriMap, {required Map additionalData}) { + static SteamToken fromOtpAuthMap(Map uriMap, {Map additionalData = const {}}) { uriMap = validateMap( map: uriMap, validators: { diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index 70a23263c..d036792eb 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -77,7 +77,7 @@ abstract class Token with SortableMixin { } /// Creates a token from a uri map. - factory Token.fromOtpAuthMap(Map otpAuthMap, {required Map additionalData}) { + factory Token.fromOtpAuthMap(Map otpAuthMap, {Map additionalData = const {}}) { String? type = otpAuthMap[OTP_AUTH_TYPE]; if (type == null) throw ArgumentError.value(otpAuthMap, 'Token#fromUriMap', 'Token type is not defined in the uri map'); if (TokenTypes.HOTP.isName(type, caseSensitive: false)) return HOTPToken.fromOtpAuthMap(otpAuthMap, additionalData: additionalData); diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index aee5f8b18..3864ef320 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -168,7 +168,7 @@ class TOTPToken extends OTPToken { return 'T${super.toString()}period: $period}'; } - factory TOTPToken.fromOtpAuthMap(Map otpAuthMap, {required Map additionalData}) { + factory TOTPToken.fromOtpAuthMap(Map otpAuthMap, {Map additionalData = const {}}) { final validatedMap = validateMap( map: otpAuthMap, validators: { 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 c032d2d02..b6799ef69 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 @@ -78,13 +78,22 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { // Update the secret with the two step secret. queryParameters[OTP_AUTH_SECRET_BASE32] = twoStepSecretString; } - final newToken = Token.fromOtpAuthMap(queryParameters, additionalData: {Token.ORIGIN: _parseCreatorToOrigin(uri)}); - return [ - ProcessorResultSuccess( - newToken, - resultHandlerType: resultHandlerType, - ) - ]; + try { + return [ + ProcessorResultSuccess( + Token.fromOtpAuthMap(queryParameters, additionalData: {Token.ORIGIN: _parseCreatorToOrigin(uri)}), + resultHandlerType: resultHandlerType, + ) + ]; + } catch (e) { + return [ + ProcessorResultFailed( + 'The token could not be created.', + error: e, + resultHandlerType: resultHandlerType, + ) + ]; + } } } 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 8d26bcd78..a1733fca0 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 @@ -263,7 +263,7 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { OTP_AUTH_TYPE: const TypeValidatorRequired(), OTP_AUTH_LABEL: const TypeValidatorRequired(defaultValue: ''), OTP_AUTH_ISSUER: const TypeValidatorRequired(defaultValue: ''), - OTP_AUTH_SECRET_BASE32: TypeValidatorRequired(transformer: (v) => Encodings.none.encodeStringTo(Encodings.base32, v)), + OTP_AUTH_SECRET_BASE32: TypeValidatorRequired(transformer: (v) => Encodings.base32.encodeStringTo(Encodings.base32, v)), OTP_AUTH_ALGORITHM: const TypeValidatorOptional(), OTP_AUTH_DIGITS: intToStringValidatorOptional, OTP_AUTH_PERIOD_SECONDS: intToStringValidatorOptional, diff --git a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart index 261572c04..6f2b3f331 100644 --- a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart @@ -174,7 +174,8 @@ class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { OTP_AUTH_ISSUER: const TypeValidatorRequired(), OTP_AUTH_ALGORITHM: const TypeValidatorRequired(), OTP_AUTH_DIGITS: intToStringValidator, - OTP_AUTH_COUNTER: intToStringValidatorOptional, + // FreeOTP+ saves the counter 1 less than the actual value + OTP_AUTH_COUNTER: TypeValidatorOptional(transformer: (value) => ((value as int) + 1).toString()), OTP_AUTH_PERIOD_SECONDS: intToStringValidatorOptional, }, ); 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 71afd82c7..589ed757f 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 @@ -60,7 +60,7 @@ class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { } catch (e) { throw InvalidFileContentException('No valid 2FAS import file'); } - if (password == null) return _processPlainFile(jsonString: fileContent, json: json); + if (password == null) return _processPlainFile(json: json); return processEncryptedFile(jsonString: fileContent, json: json, password: password); } @@ -123,12 +123,7 @@ class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { return await _processPlainTokens(decryptedTokensJsonList.cast>()); } - Future>> _processPlainFile({String? jsonString, Map? json}) async { - try { - json ??= jsonDecode(jsonString!) as Map; - } catch (e) { - throw InvalidFileContentException('No valid 2FAS import file'); - } + Future>> _processPlainFile({required Map json}) async { final tokensJsonList = json['services'] as List?; if (tokensJsonList == null || tokensJsonList.isEmpty) { if (json['servicesEncrypted'] == null) { @@ -180,10 +175,10 @@ class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { Map twoFasOTP = twoFasToken[TWOFAS_OTP]; return validateMap( map: { + OTP_AUTH_ISSUER: twoFasToken[TWOFAS_ISSUER], + OTP_AUTH_SECRET_BASE32: twoFasToken[TWOFAS_SECRET], OTP_AUTH_TYPE: twoFasOTP[TWOFAS_TYPE], - OTP_AUTH_ISSUER: twoFasOTP[TWOFAS_ISSUER], OTP_AUTH_LABEL: twoFasOTP[TWOFAS_LABEL], - OTP_AUTH_SECRET_BASE32: twoFasToken[TWOFAS_SECRET], OTP_AUTH_ALGORITHM: twoFasOTP[TWOFAS_ALGORITHM], OTP_AUTH_DIGITS: twoFasOTP[TWOFAS_DIGITS], OTP_AUTH_PERIOD_SECONDS: twoFasOTP[TWOFAS_PERIOD], diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart index 096a3599a..892c0bfe7 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart @@ -22,7 +22,6 @@ import 'dart:async'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; import 'package:privacyidea_authenticator/interfaces/repo/push_request_repository.dart'; -import 'package:privacyidea_authenticator/utils/custom_int_buffer.dart'; import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -97,7 +96,7 @@ class PushRequestNotifier extends _$PushRequestNotifier { _pushRepo = _pushRepoOverride ?? pushRepo; Logger.info('New PushRequestNotifier created', name: 'pushRequestProvider#build'); _pushProvider.subscribe(add); - return PushRequestState(pushRequests: [], knownPushRequests: CustomIntBuffer(list: [])); + return _loadFromRepo(); } void swapPushProvider(PushProvider newProvider) { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart index d35b5124a..88a1d1c56 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart @@ -7,7 +7,7 @@ part of 'push_request_provider.dart'; // ************************************************************************** String _$pushRequestNotifierHash() => - r'83bb88bfb1fbc8623da1d368a5bb8808734b1e88'; + r'145f1ff477137512bef2f71a19da7bc857823d73'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart index 6fe3d9866..2372195c0 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart @@ -30,7 +30,7 @@ part 'token_folder_notifier.g.dart'; final tokenFolderProvider = tokenFolderNotifierProviderOf(repo: PreferenceTokenFolderRepository()); -@riverpod +@Riverpod(keepAlive: true) class TokenFolderNotifier extends _$TokenFolderNotifier { late final Future initState; final Mutex _repoMutex = Mutex(); @@ -48,8 +48,12 @@ class TokenFolderNotifier extends _$TokenFolderNotifier { @override TokenFolderState build({required TokenFolderRepository repo}) { _repo = _repoOverride ?? repo; + _stateMutex.acquire(); Logger.info('Initializing token folder state', name: 'TokenFolderNotifier#initTokenFolder'); - initState = _loadFromRepo().then((newState) => state = newState); + initState = _loadFromRepo().then((newState) { + _stateMutex.release(); + return state = newState; + }); return const TokenFolderState(folders: []); } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.g.dart index 2f67c8c8a..7b28951a5 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.g.dart @@ -7,7 +7,7 @@ part of 'token_folder_notifier.dart'; // ************************************************************************** String _$tokenFolderNotifierHash() => - r'cba0fe40fa6c71aa6c3436b8fd9bde0e7cf94388'; + r'f1f961ff173b88705b6c2dfc893703a7644e6afc'; /// Copied from Dart SDK class _SystemHash { @@ -31,7 +31,7 @@ class _SystemHash { } abstract class _$TokenFolderNotifier - extends BuildlessAutoDisposeNotifier { + extends BuildlessNotifier { late final TokenFolderRepository repo; TokenFolderState build({ @@ -82,8 +82,8 @@ class TokenFolderNotifierFamily extends Family { } /// See also [TokenFolderNotifier]. -class TokenFolderNotifierProvider extends AutoDisposeNotifierProviderImpl< - TokenFolderNotifier, TokenFolderState> { +class TokenFolderNotifierProvider + extends NotifierProviderImpl { /// See also [TokenFolderNotifier]. TokenFolderNotifierProvider({ required TokenFolderRepository repo, @@ -139,7 +139,7 @@ class TokenFolderNotifierProvider extends AutoDisposeNotifierProviderImpl< } @override - AutoDisposeNotifierProviderElement + NotifierProviderElement createElement() { return _TokenFolderNotifierProviderElement(this); } @@ -158,15 +158,14 @@ class TokenFolderNotifierProvider extends AutoDisposeNotifierProviderImpl< } } -mixin TokenFolderNotifierRef - on AutoDisposeNotifierProviderRef { +mixin TokenFolderNotifierRef on NotifierProviderRef { /// The parameter `repo` of this provider. TokenFolderRepository get repo; } class _TokenFolderNotifierProviderElement - extends AutoDisposeNotifierProviderElement with TokenFolderNotifierRef { + extends NotifierProviderElement + with TokenFolderNotifierRef { _TokenFolderNotifierProviderElement(super.provider); @override diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart index 1e0336f12..48e5d87c8 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart @@ -117,7 +117,11 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { _rsaUtils = _rsaUtilsOverride ?? rsaUtils; _ioClient = _ioClientOverride ?? ioClient; _firebaseUtils = _firebaseUtilsOverride ?? firebaseUtils; - initState = _loadStateFromRepo().then((newState) => state = newState); + _stateMutex.acquire(); + initState = _loadStateFromRepo().then((newState) { + _stateMutex.release(); + return state = newState; + }); return const TokenState(tokens: [], lastlyUpdatedTokens: []); } // /* diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart index b9cf1ec81..77422287b 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart @@ -6,7 +6,7 @@ part of 'token_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$tokenNotifierHash() => r'3f00c727bb255a1301795cddb05383a077b1d0fd'; +String _$tokenNotifierHash() => r'c82b8838167c91ce9ad8ef9638de121d225f4391'; /// Copied from Dart SDK class _SystemHash { diff --git a/pubspec.lock b/pubspec.lock index 2d61e69cf..cda0228f6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -422,14 +422,6 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" - diffie_hellman: - dependency: "direct main" - description: - name: diffie_hellman - sha256: "0dd6129eaba242d67a3cc6fd57c36e6ef5d803b1d866ae3f5a2fe150b1e96c42" - url: "https://pub.dev" - source: hosted - version: "1.2.0" easy_dynamic_theme: dependency: "direct main" description: @@ -446,14 +438,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.3" - equatable: - dependency: transitive - description: - name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 - url: "https://pub.dev" - source: hosted - version: "2.0.5" expandable: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c15f309cd..01c644c8f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,7 @@ dependencies: encrypt: ^5.0.3 cryptography: ^2.7.0 otp: ^3.0.1 + basic_utils: ^5.7.0 # Storage file_selector: ^1.0.2 shared_preferences: ^2.2.0 @@ -95,8 +96,6 @@ dependencies: riverpod_annotation: ^2.3.5 freezed_annotation: ^2.4.4 async: ^2.11.0 - basic_utils: ^5.7.0 - diffie_hellman: ^1.2.0 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 8ea234d60..a5d7c81db 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 @@ -14,7 +14,7 @@ void _testFreeOtpPlusQrProcessor() { FreeOtpPlusQrProcessor; test('processUri', () async { // Arrange - final normalOtpAuthUri = Uri.parse('otpauth://totp/FreeOTP+:alice?secret=secret&issuer=FreeOTP&algorithm=SHA1&digits=6&period=30'); + final normalOtpAuthUri = Uri.parse('otpauth://totp/FreeOTP+:alice?secret=AAAAAAAA&issuer=FreeOTP&algorithm=SHA1&digits=6&period=30'); // Act final results = await const FreeOtpPlusQrProcessor().processUri(normalOtpAuthUri); // Assert @@ -40,7 +40,7 @@ void _testFreeOtpPlusQrProcessor() { }); test('processUri without counter', () async { // Arrange - final normalOtpAuthUri = Uri.parse('otpauth://hotp/FreeOTP+:alice?secret=secret&issuer=FreeOTP&algorithm=SHA1&digits=6'); + final normalOtpAuthUri = Uri.parse('otpauth://hotp/FreeOTP+:alice?secret=AAAAAAAA&issuer=FreeOTP&algorithm=SHA1&digits=6'); // Act final results = await const FreeOtpPlusQrProcessor().processUri(normalOtpAuthUri); // Assert 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 af2fdcdf7..f49a9a005 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 @@ -17,7 +17,7 @@ void _testOtpAuthProcessor() { test('processUri', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://totp/account?secret=secret&issuer=issuer&algorithm=SHA256&digits=8&period=45'; + const uriString = 'otpauth://totp/account?secret=AAAAAAAA&issuer=issuer&algorithm=SHA256&digits=8&period=45'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -41,7 +41,7 @@ void _testOtpAuthProcessor() { test('processUri missing algorithm', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://totp/account?secret=secret&issuer=issuer&digits=6&period=30'; + const uriString = 'otpauth://totp/account?secret=AAAAAAAA&issuer=issuer&digits=6&period=30'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -65,7 +65,7 @@ void _testOtpAuthProcessor() { test('processUri missing digits', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://totp/account?secret=secret&issuer=issuer&algorithm=SHA1&period=30'; + const uriString = 'otpauth://totp/account?secret=AAAAAAAA&issuer=issuer&algorithm=SHA1&period=30'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -89,7 +89,7 @@ void _testOtpAuthProcessor() { test('processUri missing period', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://totp/account?secret=secret&issuer=issuer&algorithm=SHA1&digits=6'; + const uriString = 'otpauth://totp/account?secret=AAAAAAAA&issuer=issuer&algorithm=SHA1&digits=6'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -122,12 +122,13 @@ void _testOtpAuthProcessor() { final result0 = results[0]; expect(result0, isA()); final message = result0.asFailed!.message; - expect(message.toLowerCase().contains('secret'), isTrue); + final error = result0.asFailed!.error; + expect(message.toLowerCase().contains('secret') || error.toString().toLowerCase().contains('secret'), isTrue); }); test('processUri issuer from path', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://totp/issuer:account?secret=secret&issuer=issuer2&algorithm=SHA1&digits=6&period=30'; + const uriString = 'otpauth://totp/issuer:account?secret=AAAAAAAA&issuer=issuer2&algorithm=SHA1&digits=6&period=30'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -154,7 +155,7 @@ void _testOtpAuthProcessor() { test('processUri', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://hotp/account?secret=secret&issuer=issuer&algorithm=SHA256&digits=8&counter=5'; + const uriString = 'otpauth://hotp/account?secret=AAAAAAAA&issuer=issuer&algorithm=SHA256&digits=8&counter=5'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -178,7 +179,7 @@ void _testOtpAuthProcessor() { test('processUri missing algorithm', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://hotp/account?secret=secret&issuer=issuer&digits=8&counter=5'; + const uriString = 'otpauth://hotp/account?secret=AAAAAAAA&issuer=issuer&digits=8&counter=5'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -202,7 +203,7 @@ void _testOtpAuthProcessor() { test('processUri missing digits', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://hotp/account?secret=secret&issuer=issuer&algorithm=SHA256&counter=5'; + const uriString = 'otpauth://hotp/account?secret=AAAAAAAA&issuer=issuer&algorithm=SHA256&counter=5'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -226,7 +227,7 @@ void _testOtpAuthProcessor() { test('processUri missing counter', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://hotp/account?secret=secret&issuer=issuer&algorithm=SHA256&digits=8'; + const uriString = 'otpauth://hotp/account?secret=AAAAAAAA&issuer=issuer&algorithm=SHA256&digits=8'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -259,12 +260,13 @@ void _testOtpAuthProcessor() { final result0 = results[0]; expect(result0, isA()); final message = result0.asFailed!.message; - expect(message.toLowerCase().contains('secret'), isTrue); + final error = result0.asFailed!.error; + expect(message.toLowerCase().contains('secret') || error.toString().toLowerCase().contains('secret'), isTrue); }); test('processUri issuer from path', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://hotp/issuer:account?secret=secret&algorithm=SHA256&digits=8&counter=5'; + const uriString = 'otpauth://hotp/issuer:account?secret=AAAAAAAA&algorithm=SHA256&digits=8&counter=5'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -290,7 +292,7 @@ void _testOtpAuthProcessor() { // Arrange const processor = OtpAuthProcessor(); const uriString = - 'otpauth://hotp/issuer:account?secret=secret&algorithm=SHA256&digits=8&counter=5&2step_salt=10&2step_output=20&2step_difficulty=10000'; + 'otpauth://hotp/issuer:account?secret=AAAAAAAA&algorithm=SHA256&digits=8&counter=5&2step_salt=10&2step_output=20&2step_difficulty=10000'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -304,7 +306,7 @@ void _testOtpAuthProcessor() { test('processUri', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://daypassword/account?secret=secret&issuer=issuer&algorithm=SHA256&period=86400&digits=8'; + const uriString = 'otpauth://daypassword/account?secret=AAAAAAAA&issuer=issuer&algorithm=SHA256&period=86400&digits=8'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -329,7 +331,7 @@ void _testOtpAuthProcessor() { test('processUri missing algorithm', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://daypassword/account?secret=secret&issuer=issuer&period=86400&digits=8'; + const uriString = 'otpauth://daypassword/account?secret=AAAAAAAA&issuer=issuer&period=86400&digits=8'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -354,7 +356,7 @@ void _testOtpAuthProcessor() { test('processUri missing digits', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://daypassword/account?secret=secret&issuer=issuer&algorithm=SHA256&period=172800'; + const uriString = 'otpauth://daypassword/account?secret=AAAAAAAA&issuer=issuer&algorithm=SHA256&period=172800'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -379,7 +381,7 @@ void _testOtpAuthProcessor() { test('processUri missing period', () async { // Arrange const processor = OtpAuthProcessor(); - const uriString = 'otpauth://daypassword/account?secret=secret&issuer=issuer&algorithm=SHA256&digits=8'; + const uriString = 'otpauth://daypassword/account?secret=AAAAAAAA&issuer=issuer&algorithm=SHA256&digits=8'; final uri = Uri.parse(uriString); // Act final results = await processor.processUri(uri); @@ -413,7 +415,8 @@ void _testOtpAuthProcessor() { final result0 = results[0]; expect(result0, isA()); final message = result0.asFailed!.message; - expect(message.toLowerCase().contains('secret'), isTrue); + final error = result0.asFailed!.error; + expect(message.toLowerCase().contains('secret') || error.toString().toLowerCase().contains('secret'), isTrue); }); }); group('Push Token', () { 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 987b4ceaa..52b990109 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 @@ -20,8 +20,8 @@ void _testAegisImportFileProcessor() { test('plain', () async { // Arrange const byteDataString = - '[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, 110, 117, 108, 108, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 112, 97, 114, 97, 109, 115, 34, 58, 32, 110, 117, 108, 108, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 100, 98, 34, 58, 32, 123,' - '10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 32, 51, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 101, 110, 116, 114, 105, 101, 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, 34, 116, 111, 116, 112, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,' + '[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, 110, 117, 108, 108, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 112, 97, 114, 97, 109, 115, 34, 58, 32, 110, 117, 108, 108, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 100, 98, 34, 58, 32,' + '123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 32, 51, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 34, 101, 110, 116, 114, 105, 101, 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, 34, 116, 111, 116, 112, 34, 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, 52, 57, 51, 102, 50, 52, 97, 45, 48, 54, 102, 55, 45, 52, 54, 57, 51, 45, 57, 100, 98, 102, 45, 53, 50, 53, 102, 56, 49, 54, 54, 102, 57, 100, 97, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, 97, 109, 101, 34, 58, 32, 34, 84, 101, 115, 116, 49, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,' '32, 32, 32, 34, 105, 115, 115, 117, 101, 114, 34, 58, 32, 34, 84, 101, 115, 116, 105, 110, 103, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, 111, 116, 101, 34, 58, 32, 34, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 102, 97, 118, 111, 114, 105, 116, 101, 34, 58, 32, 102, 97, 108, 115, 101, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,' '32, 32, 32, 34, 105, 99, 111, 110, 34, 58, 32, 110, 117, 108, 108, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 105, 110, 102, 111, 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, 115, 101, 99, 114, 101, 116, 34, 58, 32, 34, 65, 65, 65, 65, 65, 65, 65, 65, 34, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,' diff --git a/test/unit_test/state_notifiers/deeplink_notifier_test.dart b/test/unit_test/state_notifiers/deeplink_notifier_test.dart index 72efc1f89..ae3a989b8 100644 --- a/test/unit_test/state_notifiers/deeplink_notifier_test.dart +++ b/test/unit_test/state_notifiers/deeplink_notifier_test.dart @@ -2,6 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:privacyidea_authenticator/model/deeplink.dart'; import 'package:privacyidea_authenticator/utils/riverpod/state_notifiers/deeplink_notifier.dart'; + void main() { _testDeeplinkNotifier(); } @@ -10,7 +11,7 @@ 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 initUri = Uri.parse('otpauth://hotp/issuer?secret=AAAAAAAA&counter=0&digits=6&algorithm=SHA1'); final deeplinkProvider = StateNotifierProvider( (ref) => DeeplinkNotifier(sources: [DeeplinkSource(name: 'test', stream: const Stream.empty(), initialUri: Future.value(initUri))]), ); @@ -22,8 +23,8 @@ void _testDeeplinkNotifier() { }); 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 initUri = Uri.parse('otpauth://hotp/issuer?secret=AAAAAAAA&counter=0&digits=6&algorithm=SHA1'); + final initUri2 = Uri.parse('otpauth://totp/issuer?secret=AAAAAAAA&period=30&digits=6&algorithm=SHA1'); final deeplinkProvider = StateNotifierProvider( (ref) => DeeplinkNotifier(sources: [ DeeplinkSource(name: 'test', stream: const Stream.empty(), initialUri: Future.value(initUri)), @@ -39,10 +40,10 @@ void _testDeeplinkNotifier() { }); 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 uri1 = Uri.parse('otpauth://hotp/issuer?secret=AAAAAAAA&counter=0&digits=6&algorithm=SHA1'); + final uri2 = Uri.parse('otpauth://totp/issuer?secret=AAAAAAAA&period=30&digits=6&algorithm=SHA1'); + final uri3 = Uri.parse('otpauth://totp/issuer?secret=AAAAAAAA&period=60&digits=6&algorithm=SHA1'); + final uri4 = Uri.parse('otpauth://totp/issuer?secret=AAAAAAAA&period=90&digits=6&algorithm=SHA1'); final list = [uri1, uri2, uri3, uri4]; Stream stream = Stream.fromIterable([...list]); final deeplinkProvider = StateNotifierProvider( @@ -57,14 +58,14 @@ void _testDeeplinkNotifier() { }); 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 hotp1 = Uri.parse('otpauth://hotp/issuer?secret=AAAAAAAA&counter=1&digits=6&algorithm=SHA1'); + final hotp2 = Uri.parse('otpauth://hotp/issuer?secret=AAAAAAAA&counter=2&digits=6&algorithm=SHA1'); + final hotp3 = Uri.parse('otpauth://hotp/issuer?secret=AAAAAAAA&counter=3&digits=6&algorithm=SHA1'); + final hotp4 = Uri.parse('otpauth://hotp/issuer?secret=AAAAAAAA&counter=4&digits=6&algorithm=SHA1'); + final totp1 = Uri.parse('otpauth://totp/issuer?secret=AAAAAAAA&period=15&digits=6&algorithm=SHA1'); + final totp2 = Uri.parse('otpauth://totp/issuer?secret=AAAAAAAA&period=30&digits=6&algorithm=SHA1'); + final totp3 = Uri.parse('otpauth://totp/issuer?secret=AAAAAAAA&period=60&digits=6&algorithm=SHA1'); + final totp4 = Uri.parse('otpauth://totp/issuer?secret=AAAAAAAA&period=90&digits=6&algorithm=SHA1'); final hotpList = [hotp1, hotp2, hotp3, hotp4]; final totpList = [totp1, totp2, totp3, totp4]; diff --git a/test/unit_test/state_notifiers/push_request_notifier_test.dart b/test/unit_test/state_notifiers/push_request_notifier_test.dart index 772dcbd8a..4b1cea19b 100644 --- a/test/unit_test/state_notifiers/push_request_notifier_test.dart +++ b/test/unit_test/state_notifiers/push_request_notifier_test.dart @@ -59,6 +59,7 @@ void _testPushRequestNotifier() { when(mockPushRepo.saveState(any)).thenAnswer((_) async {}); when(mockPushRepo.loadState()).thenAnswer((_) async => before); final initState = await container.read(pushProvider.future); + verify(mockPushRepo.loadState()).called(1); expect(initState, before); when(mockRsaUtils.trySignWithToken(any, any)).thenAnswer((_) async => 'signature'); when(mockIoClient.doPost( @@ -70,8 +71,7 @@ void _testPushRequestNotifier() { await container.read(pushProvider.notifier).accept(PushToken(serial: 'serial', id: 'id'), pr); - expect(container.read(pushProvider), after); - verify(mockPushRepo.loadState()).called(1); + expect(await container.read(pushProvider.future), after); verify(mockRsaUtils.trySignWithToken(any, any)).called(1); verify(mockIoClient.doPost( url: anyNamed('url'), @@ -205,7 +205,7 @@ void _testPushRequestNotifier() { expect(initState, before); final success = await container.read(pushProvider.notifier).remove(pr2); expect(success, true); - expect(container.read(pushProvider), after); + expect(await container.read(pushProvider.future), after); }); }); } diff --git a/test/unit_test/state_notifiers/sortable_notifier_test.dart b/test/unit_test/state_notifiers/sortable_notifier_test.dart index f79010932..b6059cf41 100644 --- a/test/unit_test/state_notifiers/sortable_notifier_test.dart +++ b/test/unit_test/state_notifiers/sortable_notifier_test.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; @@ -8,6 +9,7 @@ import 'package:privacyidea_authenticator/model/token_folder.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/utils/logger.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; @@ -30,7 +32,10 @@ void _testSortableNotifier() { TokenFolder(label: 'Folder 1', folderId: 1, sortIndex: null), TokenFolder(label: 'Folder 2', folderId: 2, sortIndex: 2), ]); - when(mockTokenFolderRepository.loadState()).thenAnswer((_) async => tokenFolderState); + when(mockTokenFolderRepository.loadState()).thenAnswer((_) async { + Logger.debug('Loading token folder state', name: 'TokenFolderNotifier#_loadFromRepo'); + return tokenFolderState; + }); when(mockTokenFolderRepository.saveState(any)).thenAnswer((newState) async { tokenFolderState = newState.positionalArguments.first as TokenFolderState; return true; @@ -41,17 +46,15 @@ void _testSortableNotifier() { HOTPToken(id: 'Token 3', algorithm: Algorithms.SHA1, digits: 6, secret: 'secret3', folderId: null, sortIndex: 1), ]; when(mockTokenRepository.loadTokens()).thenAnswer((_) async => tokenState); - when(mockTokenRepository.saveOrReplaceTokens(any)).thenAnswer((newState) async { - final newTokenState = newState.positionalArguments.first as List; - for (final token in newTokenState) { - final index = tokenState.indexWhere((element) => element.id == token.id); - if (index != -1) { - tokenState[index] = token; - } else { - tokenState.add(token); - } + when(mockTokenRepository.saveOrReplaceToken(any)).thenAnswer((newState) async { + final token = newState.positionalArguments.first as Token; + final index = tokenState.indexWhere((element) => element.id == token.id); + if (index != -1) { + tokenState[index] = token; + } else { + tokenState.add(token); } - return []; + return true; }); final container = ProviderContainer(overrides: [ @@ -59,18 +62,23 @@ void _testSortableNotifier() { tokenFolderProvider.overrideWith(() => TokenFolderNotifier(repoOverride: mockTokenFolderRepository)), tokenProvider.overrideWith(() => TokenNotifier(repoOverride: mockTokenRepository)), ]); - + WidgetsFlutterBinding.ensureInitialized(); final newToken = TOTPToken(period: 30, id: 'Token 4', algorithm: Algorithms.SHA1, digits: 6, secret: 'secret4', folderId: 1, sortIndex: 1); await container.read(tokenProvider.notifier).addNewToken(newToken); + + await container.read(tokenFolderProvider.notifier).addNewFolder('Folder 3'); + await Future.delayed(const Duration(milliseconds: 1000)); final newSortableState = container.read(sortablesProvider); - expect(newSortableState.length, 6); + expect(newSortableState.length, 7); expect(newSortableState[0], isA()); expect((newSortableState[0] as Token).id, 'Token 3'); expect(newSortableState[1], isA()); expect((newSortableState[1] as Token).id, 'Token 4'); expect(newSortableState[2], isA()); expect((newSortableState[2] as TokenFolder).label, 'Folder 2'); + expect((newSortableState.last as TokenFolder).label, 'Folder 3'); + expect((newSortableState.last as TokenFolder).sortIndex, 6); }); }); } diff --git a/test/unit_test/state_notifiers/token_folder_notifier_test.dart b/test/unit_test/state_notifiers/token_folder_notifier_test.dart index 727ce7a0f..efc06d66e 100644 --- a/test/unit_test/state_notifiers/token_folder_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_folder_notifier_test.dart @@ -25,7 +25,7 @@ void _testTokenFolderNotifier() { await notifier.initState; await notifier.addNewFolder('test'); final state = container.read(testProvider); - expect(state.folders, after); + expect(state, after); verify(mockRepo.saveState(after)).called(1); }); @@ -41,7 +41,7 @@ void _testTokenFolderNotifier() { await notifier.initState; await notifier.removeFolder(const TokenFolder(label: 'test', folderId: 1)); final state = container.read(testProvider); - expect(state.folders, after); + expect(state, after); verify(mockRepo.saveState(after)).called(1); }); test('updateFolder', () async { @@ -56,7 +56,7 @@ void _testTokenFolderNotifier() { await notifier.initState; await notifier.updateFolder(before.folders.first, (p0) => after.folders.first); final state = container.read(testProvider); - expect(state.folders, after); + expect(state, after); verify(mockRepo.saveState(after)).called(1); }); test('updateFolders', () async { @@ -77,7 +77,7 @@ void _testTokenFolderNotifier() { await notifier.initState; await notifier.addOrReplaceFolders(after.folders); final state = container.read(testProvider); - expect(state.folders, after); + expect(state, after); verify(mockRepo.saveState(after)).called(1); }); }); diff --git a/test/unit_test/state_notifiers/token_notifier_test.dart b/test/unit_test/state_notifiers/token_notifier_test.dart index ea69e93b2..ac8b6a020 100644 --- a/test/unit_test/state_notifiers/token_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_notifier_test.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart'; @@ -241,11 +242,11 @@ void _testTokenNotifier() { expect(state.tokens, after); }); test('addTokenFromOtpAuth', () async { + WidgetsFlutterBinding.ensureInitialized(); final mockSettingsRepo = MockSettingsRepository(); when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); - final container = ProviderContainer(overrides: [settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo))]); + final mockRepo = MockTokenRepository(); - final mockFirebaseUtils = MockFirebaseUtils(); final before = [ HOTPToken(label: 'label', issuer: 'issuer', id: 'id', algorithm: Algorithms.SHA1, digits: 6, secret: 'secret'), ]; @@ -255,22 +256,23 @@ void _testTokenNotifier() { ]; when(mockRepo.loadTokens()).thenAnswer((_) async => before); when(mockRepo.saveOrReplaceTokens(any)).thenAnswer((_) async => []); - final testProvider = tokenNotifierProviderOf( - repo: mockRepo, - rsaUtils: const RsaUtils(), - ioClient: const PrivacyideaIOClient(), - firebaseUtils: mockFirebaseUtils, - ); - final testNotifier = container.read(tokenProvider.notifier); - const qrCode = 'otpauth://totp/issuer2:label2?secret=secret2&issuer=issuer2&algorithm=SHA256&digits=6&period=30'; - await scanQrCode([testNotifier], qrCode); - final state = container.read(testProvider); + + final container = ProviderContainer(overrides: [ + settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo)), + tokenProvider.overrideWith(() => TokenNotifier(repoOverride: mockRepo)), + ]); + + const qrCode = 'otpauth://totp/issuer2:label2?secret=AAAAAAAA2&issuer=issuer2&algorithm=SHA256&digits=6&period=30'; + final tokenNotifier = container.read(tokenProvider.notifier); + await scanQrCode([tokenNotifier], qrCode); + final state = container.read(tokenProvider); expect(state, isNotNull); after.last = after.last.copyWith(id: state.tokens.last.id); expect(state.tokens, after); verify(mockRepo.saveOrReplaceTokens(any)).called(greaterThan(0)); }); test('addTokenFromOtpAuth: rolloutPushToken', () async { + WidgetsFlutterBinding.ensureInitialized(); final mockSettingsRepo = MockSettingsRepository(); when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); final container = ProviderContainer(overrides: [settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo))]); @@ -431,7 +433,9 @@ void _testTokenNotifier() { )).called(greaterThan(0)); }); test('loadFromRepo', () async { - final container = ProviderContainer(); + final mockSettingsRepo = MockSettingsRepository(); + when(mockSettingsRepo.loadSettings()).thenAnswer((_) async => SettingsState()); + final container = ProviderContainer(overrides: [settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepo))]); final mockRepo = MockTokenRepository(); final mockFirebaseUtils = MockFirebaseUtils(); final before = [ diff --git a/test/unit_test/utils/customization/application_customization_test.dart b/test/unit_test/utils/customization/application_customization_test.dart index c94e16826..6b341c740 100644 --- a/test/unit_test/utils/customization/application_customization_test.dart +++ b/test/unit_test/utils/customization/application_customization_test.dart @@ -38,7 +38,7 @@ void _testAppCustomizer() { expect(customization.appImage.getWidget, isA()); expect(customization.appImage.imageData, equals(defaultImageUint8List)); expect(() => customization.appImage.getWidget, returnsNormally); - expect(customization..appImage.getWidget, isA()); + expect(customization.appImage.getWidget, isA()); expect(customization.lightTheme, equals(ApplicationCustomization.defaultCustomization.lightTheme)); expect(customization.darkTheme, equals(ApplicationCustomization.defaultCustomization.darkTheme)); expect(customization.disabledFeatures, equals({AppFeature.patchNotes})); @@ -73,8 +73,8 @@ void _testAppCustomizer() { // 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['appIcon'], equals({'fileType': 'png', 'imageData': base64Encode(defaultIconUint8List)})); + expect(json['appImage'], equals({'fileType': 'png', 'imageData': 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})); From af9a39a326bb7d0788de0f76c93289aa50fe954f Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:36:10 +0200 Subject: [PATCH 042/285] fixed issues based on the tests --- lib/api/token_container_api_endpoint.dart | 48 ++++---- lib/l10n/app_en.arb | 31 +++++ lib/model/processor_result.dart | 4 +- lib/model/processor_result.freezed.dart | 81 +++++++------ lib/model/token_container.dart | 14 +-- lib/model/token_template.dart | 6 +- lib/model/tokens/day_password_token.dart | 34 +++--- lib/model/tokens/hotp_token.dart | 34 +++--- lib/model/tokens/push_token.dart | 38 +++--- lib/model/tokens/steam_token.dart | 24 ++-- lib/model/tokens/token.dart | 14 +-- lib/model/tokens/totp_token.dart | 30 ++--- .../mixins/token_import_processor.dart | 2 +- .../home_widget_navigate_processor.dart | 6 +- .../token_container_processor.dart | 2 +- .../otp_auth_processor.dart | 8 +- .../aegis_import_file_processor.dart | 38 +++--- ...thenticator_pro_import_file_processor.dart | 12 +- .../free_otp_plus_import_file_processor.dart | 17 ++- .../two_fas_import_file_processor.dart | 18 +-- lib/utils/identifiers.dart | 108 +++++++++++++----- lib/utils/object_validators.dart | 82 +++++++++++++ .../token_container_notifier.dart | 8 +- .../token_container_notifier.g.dart | 2 +- lib/utils/type_matchers.dart | 60 ---------- lib/utils/utils.dart | 2 +- .../pages/import_start_page.dart | 6 +- .../example/pubspec.lock | 4 +- .../pi-authenticator-legacy/pubspec.lock | 4 +- .../model/token/day_password_test.dart | 104 +++++++++-------- .../model/token/hotp_token_test.dart | 78 +++++++------ .../model/token/push_token_test.dart | 25 ++-- .../model/token/steam_token_test.dart | 30 ++--- .../model/token/totp_token_test.dart | 89 ++++++++------- .../free_otp_plus_qr_processor_test.dart | 16 +-- .../otp_auth_processor_test.dart | 20 +--- 36 files changed, 610 insertions(+), 489 deletions(-) create mode 100644 lib/utils/object_validators.dart delete mode 100644 lib/utils/type_matchers.dart diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index eacb39b4e..11221ccc9 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -350,22 +350,22 @@ class PiServerResponse with _$PiServerResponse { final map = validateMap( map: json, validators: { - RESULT: const TypeValidatorRequired>(), - ID: const TypeValidatorRequired(), - JSONRPC: const TypeValidatorRequired(), - DETAIL: const TypeValidatorOptional(), - TIME: const TypeValidatorRequired(), - VERSION: const TypeValidatorRequired(), - SIGNATURE: const TypeValidatorRequired(), + RESULT: const ObjectValidator>(), + ID: const ObjectValidator(), + JSONRPC: const ObjectValidator(), + DETAIL: const ObjectValidatorNullable(), + TIME: const ObjectValidator(), + VERSION: const ObjectValidator(), + SIGNATURE: const ObjectValidator(), }, name: 'PiServerResponse#fromJson', ); final result = validateMap( map: map[RESULT], validators: { - RESULT_STATUS: const TypeValidatorRequired(), - RESULT_VALUE: const TypeValidatorOptional>(), - RESULT_ERROR: const TypeValidatorOptional>(), + RESULT_STATUS: const ObjectValidator(), + RESULT_VALUE: const ObjectValidatorNullable>(), + RESULT_ERROR: const ObjectValidatorNullable>(), }, name: 'PiServerResponse#fromJson#result', ); @@ -422,10 +422,10 @@ class EncryptionParams { final map = validateMap( map: json, validators: { - 'algorithm': const TypeValidatorRequired(), - 'init_vector': const TypeValidatorRequired(), - 'mode': const TypeValidatorRequired(), - 'tag': const TypeValidatorRequired(), + 'algorithm': const ObjectValidator(), + 'init_vector': const ObjectValidator(), + 'mode': const ObjectValidator(), + 'tag': const ObjectValidator(), }, name: 'EncryptionParams#fromJson', ); @@ -456,8 +456,8 @@ class PiServerResultError extends PiServerResult { final map = validateMap( map: json, validators: { - PI_SERVER_ERROR_CODE: const TypeValidatorRequired(), - PI_SERVER_ERROR_MESSAGE: const TypeValidatorRequired(), + PI_SERVER_ERROR_CODE: const ObjectValidator(), + PI_SERVER_ERROR_MESSAGE: const ObjectValidator(), }, name: 'PiServerResultError#fromJson', ); @@ -504,10 +504,10 @@ class ContainerChallenge extends PiServerResultValue { final map = validateMap( map: json, validators: { - CONTAINER_SYNC_URL: const TypeValidatorRequired(), - CONTAINER_SYNC_KEY_ALGORITHM: const TypeValidatorRequired(), - CONTAINER_SYNC_NONCE: const TypeValidatorRequired(), - CONTAINER_SYNC_TIMESTAMP: const TypeValidatorRequired(), + CONTAINER_SYNC_URL: const ObjectValidator(), + CONTAINER_SYNC_KEY_ALGORITHM: const ObjectValidator(), + CONTAINER_SYNC_NONCE: const ObjectValidator(), + CONTAINER_SYNC_TIMESTAMP: const ObjectValidator(), }, name: 'ContainerChallenge#fromJson', ); @@ -538,10 +538,10 @@ class ContainerSyncResult extends PiServerResultValue { final map = validateMap( map: json, validators: { - CONTAINER_SYNC_PUBLIC_SERVER_KEY: const TypeValidatorRequired(), - CONTAINER_SYNC_ENC_ALGORITHM: const TypeValidatorRequired(), - CONTAINER_SYNC_ENC_PARAMS: TypeValidatorRequired(transformer: (v) => EncryptionParams.fromJson(v)), - CONTAINER_SYNC_DICT_SERVER: const TypeValidatorRequired(), + CONTAINER_SYNC_PUBLIC_SERVER_KEY: const ObjectValidator(), + CONTAINER_SYNC_ENC_ALGORITHM: const ObjectValidator(), + CONTAINER_SYNC_ENC_PARAMS: ObjectValidator(transformer: (v) => EncryptionParams.fromJson(v)), + CONTAINER_SYNC_DICT_SERVER: const ObjectValidator(), }, name: 'ContainerSyncResult#fromJson', ); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 76f41b042..81d411bec 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -698,7 +698,38 @@ } }, + "valueNotAllowed": "The {type} \"{value}\" is not an allowed value for \"{parameter}\"", + "@valueNotAllowed": { + "placeholders": { + "type": { + "example": "int" + }, + "value": { + "example": "-1" + }, + "parameter": { + "example": "counter" + } + } + }, + "valueNotAllowedIn": "The {type} \"{value}\" is not an allowed value for \"{parameter}\" in \"{map}\"", + "@valueNotAllowedIn": { + "placeholders": { + "type": { + "example": "int" + }, + "value": { + "example": "-1" + }, + "parameter": { + "example": "counter" + }, + "map": { + "example": "query parameters" + } + } + }, "unsupported": "The {name} [{value}] is not supported by this version of the app.", "@unsupported": { "placeholders": { diff --git a/lib/model/processor_result.dart b/lib/model/processor_result.dart index 0c1a12dd4..c52a4961c 100644 --- a/lib/model/processor_result.dart +++ b/lib/model/processor_result.dart @@ -34,12 +34,12 @@ abstract class ProcessorResult with _$ProcessorResult { const ProcessorResult._(); const factory ProcessorResult.success( T resultData, { - TypeValidatorRequired? resultHandlerType, + ObjectValidator? resultHandlerType, }) = ProcessorResultSuccess; const factory ProcessorResult.failed( String message, { dynamic error, - TypeValidatorRequired? resultHandlerType, + ObjectValidator? resultHandlerType, }) = ProcessorResultFailed; bool get isSuccess => this is ProcessorResultSuccess; diff --git a/lib/model/processor_result.freezed.dart b/lib/model/processor_result.freezed.dart index 0365448a1..6b28ff6f7 100644 --- a/lib/model/processor_result.freezed.dart +++ b/lib/model/processor_result.freezed.dart @@ -16,35 +16,35 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$ProcessorResult { - TypeValidatorRequired? get resultHandlerType => + ObjectValidator? get resultHandlerType => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function(T resultData, - TypeValidatorRequired? resultHandlerType) + required TResult Function( + T resultData, ObjectValidator? resultHandlerType) success, required TResult Function(String message, dynamic error, - TypeValidatorRequired? resultHandlerType) + ObjectValidator? resultHandlerType) failed, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(T resultData, - TypeValidatorRequired? resultHandlerType)? + TResult? Function( + T resultData, ObjectValidator? resultHandlerType)? success, TResult? Function(String message, dynamic error, - TypeValidatorRequired? resultHandlerType)? + ObjectValidator? resultHandlerType)? failed, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function(T resultData, - TypeValidatorRequired? resultHandlerType)? + TResult Function( + T resultData, ObjectValidator? resultHandlerType)? success, TResult Function(String message, dynamic error, - TypeValidatorRequired? resultHandlerType)? + ObjectValidator? resultHandlerType)? failed, required TResult orElse(), }) => @@ -82,7 +82,7 @@ abstract class $ProcessorResultCopyWith { ProcessorResult value, $Res Function(ProcessorResult) then) = _$ProcessorResultCopyWithImpl>; @useResult - $Res call({TypeValidatorRequired? resultHandlerType}); + $Res call({ObjectValidator? resultHandlerType}); } /// @nodoc @@ -106,7 +106,7 @@ class _$ProcessorResultCopyWithImpl> resultHandlerType: freezed == resultHandlerType ? _value.resultHandlerType : resultHandlerType // ignore: cast_nullable_to_non_nullable - as TypeValidatorRequired?, + as ObjectValidator?, ) as $Val); } } @@ -120,8 +120,7 @@ abstract class _$$ProcessorResultSuccessImplCopyWith __$$ProcessorResultSuccessImplCopyWithImpl; @override @useResult - $Res call( - {T resultData, TypeValidatorRequired? resultHandlerType}); + $Res call({T resultData, ObjectValidator? resultHandlerType}); } /// @nodoc @@ -150,7 +149,7 @@ class __$$ProcessorResultSuccessImplCopyWithImpl resultHandlerType: freezed == resultHandlerType ? _value.resultHandlerType : resultHandlerType // ignore: cast_nullable_to_non_nullable - as TypeValidatorRequired?, + as ObjectValidator?, )); } } @@ -164,7 +163,7 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { @override final T resultData; @override - final TypeValidatorRequired? resultHandlerType; + final ObjectValidator? resultHandlerType; @override String toString() { @@ -198,11 +197,11 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { @override @optionalTypeArgs TResult when({ - required TResult Function(T resultData, - TypeValidatorRequired? resultHandlerType) + required TResult Function( + T resultData, ObjectValidator? resultHandlerType) success, required TResult Function(String message, dynamic error, - TypeValidatorRequired? resultHandlerType) + ObjectValidator? resultHandlerType) failed, }) { return success(resultData, resultHandlerType); @@ -211,11 +210,11 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(T resultData, - TypeValidatorRequired? resultHandlerType)? + TResult? Function( + T resultData, ObjectValidator? resultHandlerType)? success, TResult? Function(String message, dynamic error, - TypeValidatorRequired? resultHandlerType)? + ObjectValidator? resultHandlerType)? failed, }) { return success?.call(resultData, resultHandlerType); @@ -224,11 +223,11 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(T resultData, - TypeValidatorRequired? resultHandlerType)? + TResult Function( + T resultData, ObjectValidator? resultHandlerType)? success, TResult Function(String message, dynamic error, - TypeValidatorRequired? resultHandlerType)? + ObjectValidator? resultHandlerType)? failed, required TResult orElse(), }) { @@ -272,13 +271,13 @@ class _$ProcessorResultSuccessImpl extends ProcessorResultSuccess { abstract class ProcessorResultSuccess extends ProcessorResult { const factory ProcessorResultSuccess(final T resultData, - {final TypeValidatorRequired? resultHandlerType}) = + {final ObjectValidator? resultHandlerType}) = _$ProcessorResultSuccessImpl; const ProcessorResultSuccess._() : super._(); T get resultData; @override - TypeValidatorRequired? get resultHandlerType; + ObjectValidator? get resultHandlerType; /// Create a copy of ProcessorResult /// with the given fields replaced by the non-null parameter values. @@ -300,7 +299,7 @@ abstract class _$$ProcessorResultFailedImplCopyWith $Res call( {String message, dynamic error, - TypeValidatorRequired? resultHandlerType}); + ObjectValidator? resultHandlerType}); } /// @nodoc @@ -334,7 +333,7 @@ class __$$ProcessorResultFailedImplCopyWithImpl resultHandlerType: freezed == resultHandlerType ? _value.resultHandlerType : resultHandlerType // ignore: cast_nullable_to_non_nullable - as TypeValidatorRequired?, + as ObjectValidator?, )); } } @@ -351,7 +350,7 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { @override final dynamic error; @override - final TypeValidatorRequired? resultHandlerType; + final ObjectValidator? resultHandlerType; @override String toString() { @@ -385,11 +384,11 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { @override @optionalTypeArgs TResult when({ - required TResult Function(T resultData, - TypeValidatorRequired? resultHandlerType) + required TResult Function( + T resultData, ObjectValidator? resultHandlerType) success, required TResult Function(String message, dynamic error, - TypeValidatorRequired? resultHandlerType) + ObjectValidator? resultHandlerType) failed, }) { return failed(message, error, resultHandlerType); @@ -398,11 +397,11 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(T resultData, - TypeValidatorRequired? resultHandlerType)? + TResult? Function( + T resultData, ObjectValidator? resultHandlerType)? success, TResult? Function(String message, dynamic error, - TypeValidatorRequired? resultHandlerType)? + ObjectValidator? resultHandlerType)? failed, }) { return failed?.call(message, error, resultHandlerType); @@ -411,11 +410,11 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(T resultData, - TypeValidatorRequired? resultHandlerType)? + TResult Function( + T resultData, ObjectValidator? resultHandlerType)? success, TResult Function(String message, dynamic error, - TypeValidatorRequired? resultHandlerType)? + ObjectValidator? resultHandlerType)? failed, required TResult orElse(), }) { @@ -460,14 +459,14 @@ class _$ProcessorResultFailedImpl extends ProcessorResultFailed { abstract class ProcessorResultFailed extends ProcessorResult { const factory ProcessorResultFailed(final String message, {final dynamic error, - final TypeValidatorRequired? resultHandlerType}) = + final ObjectValidator? resultHandlerType}) = _$ProcessorResultFailedImpl; const ProcessorResultFailed._() : super._(); String get message; dynamic get error; @override - TypeValidatorRequired? get resultHandlerType; + ObjectValidator? get resultHandlerType; /// Create a copy of ProcessorResult /// with the given fields replaced by the non-null parameter values. diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index ecbc7ab94..3803b9db8 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -26,7 +26,7 @@ import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; import 'package:privacyidea_authenticator/model/extensions/enums/ec_key_algorithm_extension.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; -import 'package:privacyidea_authenticator/utils/type_matchers.dart'; +import 'package:privacyidea_authenticator/utils/object_validators.dart'; import '../utils/ecc_utils.dart'; import '../utils/logger.dart'; @@ -57,14 +57,14 @@ class TokenContainer with _$TokenContainer { uriMap = validateMap( map: uriMap, validators: { - CONTAINER_ISSUER: const TypeValidatorRequired(), - CONTAINER_NONCE: const TypeValidatorRequired(), - CONTAINER_TIMESTAMP: TypeValidatorRequired(transformer: (v) => DateTime.parse(v)), + CONTAINER_ISSUER: const ObjectValidator(), + CONTAINER_NONCE: const ObjectValidator(), + CONTAINER_TIMESTAMP: ObjectValidator(transformer: (v) => DateTime.parse(v)), CONTAINER_FINALIZATION_URL: stringToUrivalidator, - CONTAINER_SERIAL: const TypeValidatorRequired(), - CONTAINER_EC_KEY_ALGORITHM: TypeValidatorRequired(transformer: (v) => EcKeyAlgorithm.values.byCurveName(v)), + CONTAINER_SERIAL: const ObjectValidator(), + CONTAINER_EC_KEY_ALGORITHM: ObjectValidator(transformer: (v) => EcKeyAlgorithm.values.byCurveName(v)), CONTAINER_HASH_ALGORITHM: stringToAlgorithmsValidator, - CONTAINER_PASSPHRASE_QUESTION: const TypeValidatorOptional(), + CONTAINER_PASSPHRASE_QUESTION: const ObjectValidatorNullable(), }, name: 'Container', ); diff --git a/lib/model/token_template.dart b/lib/model/token_template.dart index 078401f76..312b044ab 100644 --- a/lib/model/token_template.dart +++ b/lib/model/token_template.dart @@ -58,13 +58,13 @@ class TokenTemplate with _$TokenTemplate { String? get serial => validateOptional( value: otpAuthMap[OTP_AUTH_SERIAL], - validator: const TypeValidatorOptional(), + validator: const ObjectValidatorNullable(), name: OTP_AUTH_SERIAL, ); String? get type => validateOptional( value: otpAuthMap[OTP_AUTH_TYPE], - validator: const TypeValidatorOptional(), + validator: const ObjectValidatorNullable(), name: OTP_AUTH_TYPE, ); @@ -72,7 +72,7 @@ class TokenTemplate with _$TokenTemplate { String? get containerSerial => validateOptional( value: otpAuthMap[CONTAINER_SERIAL], - validator: const TypeValidatorOptional(), + validator: const ObjectValidatorNullable(), name: CONTAINER_SERIAL, ); diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index 681450f0f..b7a9f69d1 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -21,7 +21,7 @@ import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; import '../../utils/identifiers.dart'; -import '../../utils/type_matchers.dart'; +import '../../utils/object_validators.dart'; import '../token_template.dart'; import 'package:uuid/uuid.dart'; @@ -158,15 +158,15 @@ class DayPasswordToken extends OTPToken { final uriMap = validateMap( map: template.otpAuthMap, validators: { - OTP_AUTH_LABEL: const TypeValidatorOptional(), - OTP_AUTH_ISSUER: const TypeValidatorOptional(), - OTP_AUTH_SERIAL: const TypeValidatorOptional(), - OTP_AUTH_ALGORITHM: stringToAlgorithmsValidatorOptional, - OTP_AUTH_DIGITS: stringToIntValidatorOptional, - OTP_AUTH_SECRET_BASE32: base32SecretValidatorOptional, - OTP_AUTH_PERIOD_SECONDS: stringSecondsToDurationvalidatorOptional, - OTP_AUTH_IMAGE: const TypeValidatorOptional(), - OTP_AUTH_PIN: stringToBoolValidatorOptional, + OTP_AUTH_LABEL: const ObjectValidatorNullable(), + OTP_AUTH_ISSUER: const ObjectValidatorNullable(), + OTP_AUTH_SERIAL: const ObjectValidatorNullable(), + OTP_AUTH_ALGORITHM: stringToAlgorithmsValidatorNullable, + OTP_AUTH_DIGITS: stringToIntValidatorNullable, + OTP_AUTH_SECRET_BASE32: base32SecretValidatorNullable, + OTP_AUTH_PERIOD_SECONDS: stringSecondsToDurationValidatorNullable, + OTP_AUTH_IMAGE: const ObjectValidatorNullable(), + OTP_AUTH_PIN: stringToBoolValidatorNullable, }, name: 'DayPasswordToken', ); @@ -188,15 +188,15 @@ class DayPasswordToken extends OTPToken { uriMap = validateMap( map: uriMap, validators: { - OTP_AUTH_LABEL: const TypeValidatorRequired(defaultValue: ''), - OTP_AUTH_ISSUER: const TypeValidatorRequired(defaultValue: ''), - OTP_AUTH_SERIAL: const TypeValidatorOptional(), + OTP_AUTH_LABEL: const ObjectValidator(defaultValue: ''), + OTP_AUTH_ISSUER: const ObjectValidator(defaultValue: ''), + OTP_AUTH_SERIAL: const ObjectValidatorNullable(), OTP_AUTH_ALGORITHM: stringToAlgorithmsValidator.withDefault(Algorithms.SHA1), - OTP_AUTH_DIGITS: stringToIntvalidator.withDefault(6), + OTP_AUTH_DIGITS: otpAuthDigitsValidatorNullable, OTP_AUTH_SECRET_BASE32: base32Secretvalidator, - OTP_AUTH_PERIOD_SECONDS: stringSecondsToDurationvalidator.withDefault(const Duration(hours: 24)), - OTP_AUTH_IMAGE: const TypeValidatorOptional(), - OTP_AUTH_PIN: stringToBoolValidatorOptional, + OTP_AUTH_PERIOD_SECONDS: stringSecondsToDurationValidator.withDefault(const Duration(hours: 24)), + OTP_AUTH_IMAGE: const ObjectValidatorNullable(), + OTP_AUTH_PIN: stringToBoolValidatorNullable, }, name: 'DayPasswordToken', ); diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index 07f5d559a..475d11e5d 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -19,7 +19,7 @@ */ import 'package:json_annotation/json_annotation.dart'; -import 'package:privacyidea_authenticator/utils/type_matchers.dart'; +import 'package:privacyidea_authenticator/utils/object_validators.dart'; import 'package:uuid/uuid.dart'; import '../../utils/identifiers.dart'; @@ -137,15 +137,15 @@ class HOTPToken extends OTPToken { final uriMap = validateMap( map: template.otpAuthMap, validators: { - OTP_AUTH_LABEL: const TypeValidatorOptional(), - OTP_AUTH_ISSUER: const TypeValidatorOptional(), - OTP_AUTH_SERIAL: const TypeValidatorOptional(), - OTP_AUTH_ALGORITHM: stringToAlgorithmsValidatorOptional, - OTP_AUTH_DIGITS: stringToIntValidatorOptional, - OTP_AUTH_SECRET_BASE32: base32SecretValidatorOptional, - OTP_AUTH_COUNTER: stringToIntValidatorOptional, - OTP_AUTH_IMAGE: const TypeValidatorOptional(), - OTP_AUTH_PIN: stringToBoolValidatorOptional, + OTP_AUTH_LABEL: const ObjectValidatorNullable(), + OTP_AUTH_ISSUER: const ObjectValidatorNullable(), + OTP_AUTH_SERIAL: const ObjectValidatorNullable(), + OTP_AUTH_ALGORITHM: stringToAlgorithmsValidatorNullable, + OTP_AUTH_DIGITS: stringToIntValidatorNullable, + OTP_AUTH_SECRET_BASE32: base32SecretValidatorNullable, + OTP_AUTH_COUNTER: stringToIntValidatorNullable, + OTP_AUTH_IMAGE: const ObjectValidatorNullable(), + OTP_AUTH_PIN: stringToBoolValidatorNullable, }, name: 'HOTPToken', ); @@ -167,15 +167,15 @@ class HOTPToken extends OTPToken { final validatedMap = validateMap( map: otpAuthMap, validators: { - OTP_AUTH_LABEL: const TypeValidatorRequired(defaultValue: ''), - OTP_AUTH_ISSUER: const TypeValidatorRequired(defaultValue: ''), - OTP_AUTH_SERIAL: const TypeValidatorOptional(), + OTP_AUTH_LABEL: const ObjectValidator(defaultValue: ''), + OTP_AUTH_ISSUER: const ObjectValidator(defaultValue: ''), + OTP_AUTH_SERIAL: const ObjectValidatorNullable(), OTP_AUTH_ALGORITHM: stringToAlgorithmsValidator.withDefault(Algorithms.SHA1), - OTP_AUTH_DIGITS: stringToIntvalidator.withDefault(6), + OTP_AUTH_DIGITS: otpAuthDigitsValidatorNullable, OTP_AUTH_SECRET_BASE32: base32Secretvalidator, - OTP_AUTH_COUNTER: stringToIntvalidator.withDefault(0), - OTP_AUTH_IMAGE: const TypeValidatorOptional(), - OTP_AUTH_PIN: stringToBoolValidatorOptional, + OTP_AUTH_COUNTER: otpAuthCounterValidator, + OTP_AUTH_IMAGE: const ObjectValidatorNullable(), + OTP_AUTH_PIN: stringToBoolValidatorNullable, }, name: 'HOTPToken#otpAuthMap', ); diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index 569a0d3e7..df2985aff 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -26,7 +26,7 @@ import '../../utils/custom_int_buffer.dart'; import '../../utils/errors.dart'; import '../../utils/identifiers.dart'; import '../../utils/rsa_utils.dart'; -import '../../utils/type_matchers.dart'; +import '../../utils/object_validators.dart'; import '../enums/push_token_rollout_state.dart'; import '../enums/token_types.dart'; import '../token_import/token_origin_data.dart'; @@ -197,26 +197,26 @@ class PushToken extends Token { final validatedMap = validateMap( map: otpAuthMap, validators: { - OTP_AUTH_LABEL: const TypeValidatorOptional(defaultValue: ''), - OTP_AUTH_ISSUER: const TypeValidatorOptional(defaultValue: ''), - OTP_AUTH_SERIAL: const TypeValidatorRequired(), + OTP_AUTH_LABEL: const ObjectValidatorNullable(defaultValue: ''), + OTP_AUTH_ISSUER: const ObjectValidatorNullable(defaultValue: ''), + OTP_AUTH_SERIAL: const ObjectValidator(), OTP_AUTH_PUSH_SSL_VERIFY: stringToBoolValidator.withDefault(true), - OTP_AUTH_PUSH_TTL_MINUTES: TypeValidatorRequired( + OTP_AUTH_PUSH_TTL_MINUTES: ObjectValidator( transformer: (v) => Duration(minutes: int.parse(v)), defaultValue: const Duration(minutes: 10), ), - OTP_AUTH_PUSH_ENROLLMENT_CREDENTIAL: const TypeValidatorOptional(), + OTP_AUTH_PUSH_ENROLLMENT_CREDENTIAL: const ObjectValidatorNullable(), OTP_AUTH_PUSH_ROLLOUT_URL: stringToUrivalidator, - OTP_AUTH_IMAGE: stringToUriValidatorOptional, - OTP_AUTH_PIN: const TypeValidatorOptional(), - OTP_AUTH_VERSION: const TypeValidatorRequired(), + OTP_AUTH_IMAGE: stringToUriValidatorNullable, + OTP_AUTH_PIN: const ObjectValidatorNullable(), + OTP_AUTH_VERSION: const ObjectValidator(), }, name: 'PushToken', ); final validatedAdditionalData = Token.validateAdditionalData(additionalData); final expirationDate = validateOptional( value: additionalData[EXPIRATION_DATE], - validator: const TypeValidatorOptional(), + validator: const ObjectValidatorNullable(), name: 'PushToken#expirationDate', ); return switch (validatedMap[OTP_AUTH_VERSION]) { @@ -252,18 +252,18 @@ class PushToken extends Token { final uriMap = validateMap( map: template.otpAuthMap, validators: { - OTP_AUTH_LABEL: const TypeValidatorOptional(), - OTP_AUTH_ISSUER: const TypeValidatorOptional(), - OTP_AUTH_SERIAL: const TypeValidatorOptional(), - OTP_AUTH_PUSH_SSL_VERIFY: stringToBoolValidatorOptional, - OTP_AUTH_PUSH_TTL_MINUTES: TypeValidatorOptional( + OTP_AUTH_LABEL: const ObjectValidatorNullable(), + OTP_AUTH_ISSUER: const ObjectValidatorNullable(), + OTP_AUTH_SERIAL: const ObjectValidatorNullable(), + OTP_AUTH_PUSH_SSL_VERIFY: stringToBoolValidatorNullable, + OTP_AUTH_PUSH_TTL_MINUTES: ObjectValidatorNullable( transformer: (v) => Duration(minutes: int.parse(v)), ), - OTP_AUTH_PUSH_ENROLLMENT_CREDENTIAL: const TypeValidatorOptional(), - OTP_AUTH_PUSH_ROLLOUT_URL: stringToUriValidatorOptional, - OTP_AUTH_IMAGE: stringToUriValidatorOptional, + OTP_AUTH_PUSH_ENROLLMENT_CREDENTIAL: const ObjectValidatorNullable(), + OTP_AUTH_PUSH_ROLLOUT_URL: stringToUriValidatorNullable, + OTP_AUTH_IMAGE: stringToUriValidatorNullable, OTP_AUTH_PIN: stringToBoolValidator, - OTP_AUTH_VERSION: stringToIntValidatorOptional, + OTP_AUTH_VERSION: stringToIntValidatorNullable, }, name: 'PushToken', ); diff --git a/lib/model/tokens/steam_token.dart b/lib/model/tokens/steam_token.dart index fe35cd397..f0fd6ad86 100644 --- a/lib/model/tokens/steam_token.dart +++ b/lib/model/tokens/steam_token.dart @@ -24,7 +24,7 @@ import 'package:privacyidea_authenticator/model/token_template.dart'; import 'package:uuid/uuid.dart'; import '../../utils/identifiers.dart'; -import '../../utils/type_matchers.dart'; +import '../../utils/object_validators.dart'; import '../enums/algorithms.dart'; import '../enums/token_types.dart'; import '../extensions/int_extension.dart'; @@ -141,12 +141,12 @@ class SteamToken extends TOTPToken { final uriMap = validateMap( map: template.otpAuthMap, validators: { - OTP_AUTH_LABEL: const TypeValidatorOptional(), - OTP_AUTH_ISSUER: const TypeValidatorOptional(), - OTP_AUTH_SERIAL: const TypeValidatorOptional(), - OTP_AUTH_SECRET_BASE32: base32SecretValidatorOptional, - OTP_AUTH_IMAGE: const TypeValidatorOptional(), - OTP_AUTH_PIN: stringToBoolValidatorOptional, + OTP_AUTH_LABEL: const ObjectValidatorNullable(), + OTP_AUTH_ISSUER: const ObjectValidatorNullable(), + OTP_AUTH_SERIAL: const ObjectValidatorNullable(), + OTP_AUTH_SECRET_BASE32: base32SecretValidatorNullable, + OTP_AUTH_IMAGE: const ObjectValidatorNullable(), + OTP_AUTH_PIN: stringToBoolValidatorNullable, }, name: 'SteamToken', ); @@ -165,12 +165,12 @@ class SteamToken extends TOTPToken { uriMap = validateMap( map: uriMap, validators: { - OTP_AUTH_LABEL: const TypeValidatorRequired(defaultValue: ''), - OTP_AUTH_ISSUER: const TypeValidatorRequired(defaultValue: ''), - OTP_AUTH_SERIAL: const TypeValidatorOptional(), + OTP_AUTH_LABEL: const ObjectValidator(defaultValue: ''), + OTP_AUTH_ISSUER: const ObjectValidator(defaultValue: ''), + OTP_AUTH_SERIAL: const ObjectValidatorNullable(), OTP_AUTH_SECRET_BASE32: base32Secretvalidator, - OTP_AUTH_IMAGE: const TypeValidatorOptional(), - OTP_AUTH_PIN: stringToBoolValidatorOptional, + OTP_AUTH_IMAGE: const ObjectValidatorNullable(), + OTP_AUTH_PIN: stringToBoolValidatorNullable, }, name: 'SteamToken#otpAuthMap', ); diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index d036792eb..ef3615b10 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -93,13 +93,13 @@ abstract class Token with SortableMixin { static Map validateAdditionalData(Map additionalData) => validateMap( map: additionalData, validators: { - Token.CONTAINER_SERIAL: const TypeValidatorOptional(), - Token.ID: const TypeValidatorOptional(), - Token.ORIGIN: const TypeValidatorOptional(), - Token.HIDDEN: const TypeValidatorOptional(), - Token.CHECKED_CONTAINERS: const TypeValidatorOptional>(), - Token.FOLDER_ID: const TypeValidatorOptional(), - Token.SORT_INDEX: const TypeValidatorOptional(), + Token.CONTAINER_SERIAL: const ObjectValidatorNullable(), + Token.ID: const ObjectValidatorNullable(), + Token.ORIGIN: const ObjectValidatorNullable(), + Token.HIDDEN: const ObjectValidatorNullable(), + Token.CHECKED_CONTAINERS: const ObjectValidatorNullable>(), + Token.FOLDER_ID: const ObjectValidatorNullable(), + Token.SORT_INDEX: const ObjectValidatorNullable(), }, name: 'Token#validateAdditionalData', ); diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index 3864ef320..ee3a46476 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -23,7 +23,7 @@ import 'package:uuid/uuid.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; -import '../../utils/type_matchers.dart'; +import '../../utils/object_validators.dart'; import '../enums/algorithms.dart'; import '../enums/token_types.dart'; import '../extensions/enums/algorithms_extension.dart'; @@ -137,15 +137,15 @@ class TOTPToken extends OTPToken { final uriMap = validateMap( map: template.otpAuthMap, validators: { - OTP_AUTH_LABEL: const TypeValidatorOptional(), - OTP_AUTH_ISSUER: const TypeValidatorOptional(), - OTP_AUTH_SERIAL: const TypeValidatorOptional(), - OTP_AUTH_ALGORITHM: stringToAlgorithmsValidatorOptional, - OTP_AUTH_DIGITS: stringToIntValidatorOptional, - OTP_AUTH_SECRET_BASE32: base32SecretValidatorOptional, - OTP_AUTH_PERIOD_SECONDS: stringToIntValidatorOptional, - OTP_AUTH_IMAGE: const TypeValidatorOptional(), - OTP_AUTH_PIN: stringToBoolValidatorOptional, + OTP_AUTH_LABEL: const ObjectValidatorNullable(), + OTP_AUTH_ISSUER: const ObjectValidatorNullable(), + OTP_AUTH_SERIAL: const ObjectValidatorNullable(), + OTP_AUTH_ALGORITHM: stringToAlgorithmsValidatorNullable, + OTP_AUTH_DIGITS: stringToIntValidatorNullable, + OTP_AUTH_SECRET_BASE32: base32SecretValidatorNullable, + OTP_AUTH_PERIOD_SECONDS: stringToIntValidatorNullable, + OTP_AUTH_IMAGE: const ObjectValidatorNullable(), + OTP_AUTH_PIN: stringToBoolValidatorNullable, }, name: 'TOTPToken', ); @@ -172,15 +172,15 @@ class TOTPToken extends OTPToken { final validatedMap = validateMap( map: otpAuthMap, validators: { - OTP_AUTH_LABEL: const TypeValidatorRequired(defaultValue: ''), - OTP_AUTH_ISSUER: const TypeValidatorRequired(defaultValue: ''), - OTP_AUTH_SERIAL: const TypeValidatorOptional(), + OTP_AUTH_LABEL: const ObjectValidator(defaultValue: ''), + OTP_AUTH_ISSUER: const ObjectValidator(defaultValue: ''), + OTP_AUTH_SERIAL: const ObjectValidatorNullable(), OTP_AUTH_ALGORITHM: stringToAlgorithmsValidator.withDefault(Algorithms.SHA1), OTP_AUTH_DIGITS: stringToIntvalidator.withDefault(6), OTP_AUTH_SECRET_BASE32: base32Secretvalidator, OTP_AUTH_PERIOD_SECONDS: stringToIntvalidator.withDefault(30), - OTP_AUTH_IMAGE: const TypeValidatorOptional(), - OTP_AUTH_PIN: stringToBoolValidatorOptional, + OTP_AUTH_IMAGE: const ObjectValidatorNullable(), + OTP_AUTH_PIN: stringToBoolValidatorNullable, }, name: 'TOTPToken#otpAuthMap', ); diff --git a/lib/processors/mixins/token_import_processor.dart b/lib/processors/mixins/token_import_processor.dart index 18d73ca42..ca85f238b 100644 --- a/lib/processors/mixins/token_import_processor.dart +++ b/lib/processors/mixins/token_import_processor.dart @@ -25,7 +25,7 @@ import '../scheme_processors/token_import_scheme_processors/google_authenticator import '../token_import_file_processor/token_import_file_processor_interface.dart'; mixin TokenImportProcessor { - static const resultHandlerType = TypeValidatorRequired(); + static const resultHandlerType = ObjectValidator(); static Set implementations = { const GoogleAuthenticatorQrProcessor(), ...TokenImportFileProcessor.implementations, diff --git a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart index 53b90334a..c22bbdff3 100644 --- a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart +++ b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart @@ -30,7 +30,7 @@ import '../../../views/link_home_widget_view/link_home_widget_view.dart'; import 'navigation_scheme_processor_interface.dart'; class HomeWidgetNavigateProcessor implements NavigationSchemeProcessor { - static const resultHandlerType = TypeValidatorRequired(); + static const resultHandlerType = ObjectValidator(); HomeWidgetNavigateProcessor(); static final Map>?> Function(Uri, BuildContext, {bool fromInit})> _processors = { @@ -153,7 +153,7 @@ class NavigationHandler with ResultHandler { if (result.isFailed) return null; validate( value: args['context'], - validator: const TypeValidatorRequired(), + validator: const ObjectValidator(), name: 'context', ); final navigation = result.asSuccess!.resultData; @@ -168,7 +168,7 @@ class NavigationHandler with ResultHandler { try { validate( value: args['context'], - validator: const TypeValidatorRequired(), + validator: const ObjectValidator(), name: 'context', ); } catch (e, s) { diff --git a/lib/processors/scheme_processors/token_container_processor.dart b/lib/processors/scheme_processors/token_container_processor.dart index 7a3f7797e..53cc5720a 100644 --- a/lib/processors/scheme_processors/token_container_processor.dart +++ b/lib/processors/scheme_processors/token_container_processor.dart @@ -29,7 +29,7 @@ import '../../utils/riverpod/riverpod_providers/generated_providers/token_contai import 'scheme_processor_interface.dart'; class TokenContainerProcessor extends SchemeProcessor { - static const resultHandlerType = TypeValidatorRequired(); + static const resultHandlerType = ObjectValidator(); static const scheme = 'pia'; static const host = 'container'; 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 b6799ef69..2d9190595 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 @@ -29,7 +29,7 @@ import '../../../model/token_import/token_origin_data.dart'; import '../../../model/tokens/token.dart'; import '../../../utils/identifiers.dart'; import '../../../utils/logger.dart'; -import '../../../utils/type_matchers.dart'; +import '../../../utils/object_validators.dart'; import '../../../utils/utils.dart'; import '../../../utils/view_utils.dart'; import '../../../widgets/dialog_widgets/two_step_dialog.dart'; @@ -151,7 +151,7 @@ TokenOriginData _parseCreatorToOrigin(Uri uri) { String _parseIssuer(Uri uri) { final param = validate( value: uri.queryParameters[OTP_AUTH_ISSUER], - validator: const TypeValidatorRequired(defaultValue: ''), + validator: const ObjectValidator(defaultValue: ''), name: OTP_AUTH_ISSUER, ); try { @@ -179,7 +179,7 @@ Future? _parse2StepSecret(Uri uri) { validateMap( map: queryParameters, validators: { - OTP_AUTH_SECRET_BASE32: TypeValidatorRequired(transformer: (v) => Encodings.base32.decode(v)), + OTP_AUTH_SECRET_BASE32: ObjectValidator(transformer: (v) => Encodings.base32.decode(v)), OTP_AUTH_2STEP_SALT_LENTH: stringToIntvalidator, OTP_AUTH_2STEP_OUTPUT_LENTH: stringToIntvalidator, OTP_AUTH_2STEP_ITERATIONS: stringToIntvalidator, @@ -237,7 +237,7 @@ String _parseTokenType(Uri uri) { Logger.debug('Token type value: $value', name: 'otp_auth_processor.dart#_parseTokenType'); return validate( value: uri.queryParameters[OTP_AUTH_TYPE] ?? uri.host, - validator: TypeValidatorRequired(defaultValue: uri.host), + validator: ObjectValidator(defaultValue: uri.host), name: OTP_AUTH_TYPE, ); } 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 a1733fca0..cc5a0e9ca 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 @@ -27,7 +27,7 @@ import 'package:cryptography/cryptography.dart' as crypto; import 'package:encrypt/encrypt.dart'; import 'package:file_selector/file_selector.dart'; import 'package:pointycastle/export.dart'; -import 'package:privacyidea_authenticator/utils/type_matchers.dart'; +import 'package:privacyidea_authenticator/utils/object_validators.dart'; import '../../l10n/app_localizations.dart'; import '../../model/enums/encodings.dart'; @@ -200,15 +200,15 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { OTP_AUTH_PIN: info[AEGIS_INFO_PIN], }, validators: { - OTP_AUTH_TYPE: const TypeValidatorRequired(), - OTP_AUTH_LABEL: const TypeValidatorRequired(defaultValue: ''), - OTP_AUTH_ISSUER: const TypeValidatorRequired(defaultValue: ''), - OTP_AUTH_SECRET_BASE32: TypeValidatorRequired(transformer: (v) => Encodings.none.encodeStringTo(Encodings.base32, info[AEGIS_INFO_SECRET])), - OTP_AUTH_ALGORITHM: const TypeValidatorOptional(), - OTP_AUTH_DIGITS: TypeValidatorOptional(transformer: (v) => (v as int).toString()), - OTP_AUTH_PERIOD_SECONDS: TypeValidatorOptional(transformer: (v) => (v as int).toString()), - OTP_AUTH_COUNTER: TypeValidatorOptional(transformer: (v) => (v as int).toString()), - OTP_AUTH_PIN: const TypeValidatorOptional(), + OTP_AUTH_TYPE: const ObjectValidator(), + OTP_AUTH_LABEL: const ObjectValidator(defaultValue: ''), + OTP_AUTH_ISSUER: const ObjectValidator(defaultValue: ''), + OTP_AUTH_SECRET_BASE32: ObjectValidator(transformer: (v) => Encodings.none.encodeStringTo(Encodings.base32, info[AEGIS_INFO_SECRET])), + OTP_AUTH_ALGORITHM: const ObjectValidatorNullable(), + OTP_AUTH_DIGITS: ObjectValidatorNullable(transformer: (v) => (v as int).toString()), + OTP_AUTH_PERIOD_SECONDS: ObjectValidatorNullable(transformer: (v) => (v as int).toString()), + OTP_AUTH_COUNTER: ObjectValidatorNullable(transformer: (v) => (v as int).toString()), + OTP_AUTH_PIN: const ObjectValidatorNullable(), }, name: 'aegisV2Entry', ); @@ -260,15 +260,15 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { OTP_AUTH_PIN: info[AEGIS_INFO_PIN], }, validators: { - OTP_AUTH_TYPE: const TypeValidatorRequired(), - OTP_AUTH_LABEL: const TypeValidatorRequired(defaultValue: ''), - OTP_AUTH_ISSUER: const TypeValidatorRequired(defaultValue: ''), - OTP_AUTH_SECRET_BASE32: TypeValidatorRequired(transformer: (v) => Encodings.base32.encodeStringTo(Encodings.base32, v)), - OTP_AUTH_ALGORITHM: const TypeValidatorOptional(), - OTP_AUTH_DIGITS: intToStringValidatorOptional, - OTP_AUTH_PERIOD_SECONDS: intToStringValidatorOptional, - OTP_AUTH_COUNTER: intToStringValidatorOptional, - OTP_AUTH_PIN: const TypeValidatorOptional(), + OTP_AUTH_TYPE: const ObjectValidator(), + OTP_AUTH_LABEL: const ObjectValidator(defaultValue: ''), + OTP_AUTH_ISSUER: const ObjectValidator(defaultValue: ''), + OTP_AUTH_SECRET_BASE32: ObjectValidator(transformer: (v) => Encodings.base32.encodeStringTo(Encodings.base32, v)), + OTP_AUTH_ALGORITHM: const ObjectValidatorNullable(), + OTP_AUTH_DIGITS: intToStringValidatorNullable, + OTP_AUTH_PERIOD_SECONDS: intToStringValidatorNullable, + OTP_AUTH_COUNTER: intToStringValidatorNullable, + OTP_AUTH_PIN: const ObjectValidatorNullable(), }, name: 'aegisV3Entry', ); 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 4c896b5a0..4fd926bcc 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 @@ -23,7 +23,7 @@ import 'dart:convert'; import 'package:cryptography/cryptography.dart'; import 'package:file_selector/file_selector.dart'; -import 'package:privacyidea_authenticator/utils/type_matchers.dart'; +import 'package:privacyidea_authenticator/utils/object_validators.dart'; import '../../l10n/app_localizations.dart'; import '../../model/encryption/uint_8_buffer.dart'; @@ -333,13 +333,13 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { OTP_AUTH_COUNTER: tokenMap[_AUTHENTICATOR_PRO_COUNTER], }, validators: { - OTP_AUTH_TYPE: const TypeValidatorRequired(), - OTP_AUTH_ISSUER: const TypeValidatorRequired(), - OTP_AUTH_LABEL: const TypeValidatorRequired(), - OTP_AUTH_SECRET_BASE32: const TypeValidatorRequired(), + OTP_AUTH_TYPE: const ObjectValidator(), + OTP_AUTH_ISSUER: const ObjectValidator(), + OTP_AUTH_LABEL: const ObjectValidator(), + OTP_AUTH_SECRET_BASE32: const ObjectValidator(), OTP_AUTH_DIGITS: intToStringValidator, OTP_AUTH_PERIOD_SECONDS: intToStringValidator, - OTP_AUTH_ALGORITHM: TypeValidatorRequired(transformer: (value) => algorithmMap[value]!), + OTP_AUTH_ALGORITHM: ObjectValidator(transformer: (value) => algorithmMap[value]!), OTP_AUTH_COUNTER: intToStringValidator, }, name: 'AuthenticatorProToken', diff --git a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart index 6f2b3f331..42d72dd99 100644 --- a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart @@ -25,7 +25,7 @@ import 'dart:typed_data'; import 'package:file_selector/file_selector.dart'; import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import 'package:privacyidea_authenticator/model/enums/encodings.dart'; -import 'package:privacyidea_authenticator/utils/type_matchers.dart'; +import 'package:privacyidea_authenticator/utils/object_validators.dart'; import '../../l10n/app_localizations.dart'; import '../../model/enums/token_origin_source_type.dart'; @@ -167,16 +167,15 @@ class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { OTP_AUTH_PERIOD_SECONDS: tokenJson[_FREE_OTP_PLUS_PERIOD], }, validators: { - OTP_AUTH_TYPE: const TypeValidatorRequired(), - OTP_AUTH_LABEL: const TypeValidatorRequired(), - OTP_AUTH_SECRET_BASE32: - TypeValidatorRequired(transformer: (value) => Encodings.base32.encode(Uint8List.fromList((value as List).cast()))), - OTP_AUTH_ISSUER: const TypeValidatorRequired(), - OTP_AUTH_ALGORITHM: const TypeValidatorRequired(), + OTP_AUTH_TYPE: const ObjectValidator(), + OTP_AUTH_LABEL: const ObjectValidator(), + OTP_AUTH_SECRET_BASE32: ObjectValidator(transformer: (value) => Encodings.base32.encode(Uint8List.fromList((value as List).cast()))), + OTP_AUTH_ISSUER: const ObjectValidator(), + OTP_AUTH_ALGORITHM: const ObjectValidator(), OTP_AUTH_DIGITS: intToStringValidator, // FreeOTP+ saves the counter 1 less than the actual value - OTP_AUTH_COUNTER: TypeValidatorOptional(transformer: (value) => ((value as int) + 1).toString()), - OTP_AUTH_PERIOD_SECONDS: intToStringValidatorOptional, + OTP_AUTH_COUNTER: ObjectValidatorNullable(transformer: (value) => ((value as int) + 1).toString()), + OTP_AUTH_PERIOD_SECONDS: intToStringValidatorNullable, }, ); } 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 589ed757f..74632aead 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 @@ -23,7 +23,7 @@ import 'dart:convert'; import 'package:cryptography/cryptography.dart'; import 'package:file_selector/file_selector.dart'; -import 'package:privacyidea_authenticator/utils/type_matchers.dart'; +import 'package:privacyidea_authenticator/utils/object_validators.dart'; import '../../l10n/app_localizations.dart'; import '../../model/enums/token_origin_source_type.dart'; @@ -185,14 +185,14 @@ class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { OTP_AUTH_COUNTER: twoFasOTP[TWOFAS_COUNTER], }, validators: { - OTP_AUTH_TYPE: const TypeValidatorRequired(), - OTP_AUTH_ISSUER: const TypeValidatorOptional(), - OTP_AUTH_LABEL: const TypeValidatorOptional(), - OTP_AUTH_SECRET_BASE32: const TypeValidatorRequired(), - OTP_AUTH_ALGORITHM: const TypeValidatorOptional(), - OTP_AUTH_DIGITS: intToStringValidatorOptional, - OTP_AUTH_PERIOD_SECONDS: intToStringValidatorOptional, - OTP_AUTH_COUNTER: intToStringValidatorOptional, + OTP_AUTH_TYPE: const ObjectValidator(), + OTP_AUTH_ISSUER: const ObjectValidatorNullable(), + OTP_AUTH_LABEL: const ObjectValidatorNullable(), + OTP_AUTH_SECRET_BASE32: const ObjectValidator(), + OTP_AUTH_ALGORITHM: const ObjectValidatorNullable(), + OTP_AUTH_DIGITS: intToStringValidatorNullable, + OTP_AUTH_PERIOD_SECONDS: intToStringValidatorNullable, + OTP_AUTH_COUNTER: intToStringValidatorNullable, }, name: '2FAS token', ); diff --git a/lib/utils/identifiers.dart b/lib/utils/identifiers.dart index 0b548e573..5ebd3b384 100644 --- a/lib/utils/identifiers.dart +++ b/lib/utils/identifiers.dart @@ -42,23 +42,29 @@ const OTP_AUTH_SECRET_BASE32 = 'secret'; const OTP_AUTH_COUNTER = 'counter'; /// [String] (optional) default = '30' -const OTP_AUTH_PERIOD_SECONDS = 'period'; // int optional default 30 +const OTP_AUTH_PERIOD_SECONDS = 'period'; + /// [String] (optional) default = 'SHA1' -const OTP_AUTH_ALGORITHM = 'algorithm'; // String optional default 'SHA1' +const OTP_AUTH_ALGORITHM = 'algorithm'; + /// [String] (optional) default = '6' -const OTP_AUTH_DIGITS = 'digits'; // int optional default 6 -/// [String] (optional) default = '' -const OTP_AUTH_LABEL = 'label'; // String optional default '' +const OTP_AUTH_DIGITS = 'digits'; + /// [String] (optional) default = '' -const OTP_AUTH_ISSUER = 'issuer'; // String optional default '' +const OTP_AUTH_LABEL = 'label'; + /// [String] (optional) default = '' -const OTP_AUTH_PIN = 'pin'; // String optional default "False" +const OTP_AUTH_ISSUER = 'issuer'; + +/// [String] 'True' / 'False' (optional) default = 'False' +const OTP_AUTH_PIN = 'pin'; + /// [String] (optional) default = 'False' const OTP_AUTH_PIN_TRUE = 'True'; const OTP_AUTH_PIN_FALSE = 'False'; /// [String] (optional) default = '' -const OTP_AUTH_IMAGE = 'image'; // String optional default '' +const OTP_AUTH_IMAGE = 'image'; // OTP auth push @@ -68,7 +74,9 @@ const OTP_AUTH_PUSH_TTL_MINUTES = 'ttl'; /// [String] (optional) default = null const OTP_AUTH_PUSH_ENROLLMENT_CREDENTIAL = 'enrollment_credential'; -const OTP_AUTH_PUSH_SSL_VERIFY = 'sslverify'; // String optional default '1' + +/// [String] '1' / '0' (optional) default = '1' +const OTP_AUTH_PUSH_SSL_VERIFY = 'sslverify'; const OTP_AUTH_PUSH_SSL_VERIFY_TRUE = '1'; const OTP_AUTH_PUSH_SSL_VERIFY_FALSE = '0'; @@ -146,7 +154,7 @@ const String CONTAINER_SYNC_DICT_ENCRYPTED = 'container_dict_encrypted'; const String GLOBAL_SECURE_REPO_PREFIX = 'app_v3_'; -T? validateOptional({required dynamic value, required TypeValidatorOptional validator, required String name}) { +T? validateOptional({required dynamic value, required ObjectValidatorNullable validator, required String name}) { if (validator.isTypeOf(value)) { return validator.transform(value); } else { @@ -159,7 +167,7 @@ T? validateOptional({required dynamic value, required TypeVali } } -T validate({required dynamic value, required TypeValidatorRequired validator, required String name}) { +T validate({required dynamic value, required ObjectValidator validator, required String name}) { if (validator.isTypeOf(value)) { return validator.transform(value); } else { @@ -177,14 +185,25 @@ T validate({required dynamic value, required TypeValidatorRequ /// Throws a [LocalizedArgumentError] if the map is invalid. ///
If the validator provides a transformer function, the value will be transformed before checking the type. ///
The returned map will contain the transformed values. -Map validateMap({required Map map, required Map> validators, required String? name}) { +Map validateMap({required Map map, required Map> validators, required String? name}) { Map validatedMap = {}; for (String key in validators.keys) { final validator = validators[key]!; final mapEntry = map[key]; if (validator.isTypeOf(mapEntry)) { - final newValue = validator.transform(mapEntry); - if (newValue != null) validatedMap[key] = newValue; + if (validator.valueIsAllowed(mapEntry)) { + final newValue = validator.transform(mapEntry); + if (newValue != null) validatedMap[key] = newValue; + } else { + throw LocalizedArgumentError( + localizedMessage: name != null + ? (localizations, value, key) => localizations.valueNotAllowedIn(value.runtimeType.toString(), value.toString(), key, name) + : (localizations, value, key) => localizations.valueNotAllowed(value.runtimeType.toString(), value.toString(), key), + unlocalizedMessage: 'The ${mapEntry.runtimeType} "$mapEntry" is not an allowed value for "$key"', + invalidValue: mapEntry.toString(), + name: key, + ); + } } else { if (mapEntry == null) { throw LocalizedArgumentError( @@ -209,24 +228,23 @@ Map validateMap({required Map map, required Map return validatedMap; } -class TypeValidatorOptional { - bool get isOptional => runtimeType.toString().contains('Optional'); - +class ObjectValidatorNullable { final T Function(dynamic value)? transformer; final T? defaultValue; + final bool Function(T)? allowedValues; - const TypeValidatorOptional({ + const ObjectValidatorNullable({ this.transformer, this.defaultValue, + this.allowedValues, }); /// Checks if the value is of the correct type, or sub-type. /// If the transformer is provided, the value will be transformed before checking the type. bool isTypeOf(dynamic value) { - Logger.debug('Checking type of $value and default value $defaultValue with transformer $transformer (isOptional $isOptional)'); - if (value == null) return (defaultValue != null) ? true : isOptional; - - if (transformer == null) return value is T; + Logger.debug('Checking type (${T.runtimeType}) of nullable value "$value" and default value "$defaultValue" with transformer "$transformer".'); + if (value == null) return true; + if (transformer == null) return value is T?; try { transformer!(value); return true; @@ -235,6 +253,16 @@ class TypeValidatorOptional { } } + bool valueIsAllowed(dynamic value) { + if (!isTypeOf(value)) return false; + if (allowedValues == null) return true; + if (value == null) return true; + final transformedValue = transform(value); + if (transformedValue == null) return true; + if (allowedValues!(transformedValue)) return true; + return false; + } + /// Transforms the value if a transformer is provided, otherwise returns the value as is. /// May throw an error if the value is not of the correct type. /// To prevent an error, use isTypeOf before calling transform. @@ -248,8 +276,9 @@ class TypeValidatorOptional { } } - TypeValidatorOptional optional() => TypeValidatorOptional(transformer: transformer, defaultValue: defaultValue); - TypeValidatorOptional withDefault(T? defaultValue) => TypeValidatorOptional(transformer: transformer, defaultValue: defaultValue); + ObjectValidatorNullable nullable() => ObjectValidatorNullable(transformer: transformer, defaultValue: defaultValue, allowedValues: allowedValues); + ObjectValidatorNullable withDefault(T? defaultValue) => + ObjectValidatorNullable(transformer: transformer, defaultValue: defaultValue, allowedValues: allowedValues); String get type => RegExp('(?<=<).+(?=>)').firstMatch(toString())!.group(0)!; @@ -257,16 +286,17 @@ class TypeValidatorOptional { String toString() => runtimeType.toString(); @override - bool operator ==(Object other) => other is TypeValidatorOptional; + bool operator ==(Object other) => other is ObjectValidatorNullable; @override int get hashCode => toString().hashCode; } -class TypeValidatorRequired extends TypeValidatorOptional { - const TypeValidatorRequired({ +class ObjectValidator extends ObjectValidatorNullable { + const ObjectValidator({ super.transformer, super.defaultValue, + super.allowedValues, }); /// Transforms the value if a transformer is provided, otherwise returns the value as is. @@ -288,4 +318,28 @@ class TypeValidatorRequired extends TypeValidatorOptional { ); } } + + /// Checks if the value is of the correct type, or sub-type. + /// If the transformer is provided, the value will be transformed before checking the type. + @override + bool isTypeOf(dynamic value) { + Logger.debug('Checking type (${T.runtimeType}) of value "$value" and default value "$defaultValue" with transformer "$transformer".'); + if (value == null) return defaultValue != null; + if (transformer == null) return value is T; + try { + transformer!(value); + return true; + } catch (e) { + return false; + } + } + + @override + bool valueIsAllowed(dynamic value) { + if (!isTypeOf(value)) return false; + value ??= defaultValue; + if (value == null) return false; + if (allowedValues == null) return true; + return allowedValues!(transform(value)); + } } diff --git a/lib/utils/object_validators.dart b/lib/utils/object_validators.dart new file mode 100644 index 000000000..0ed27eeef --- /dev/null +++ b/lib/utils/object_validators.dart @@ -0,0 +1,82 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; + +import '../model/enums/algorithms.dart'; +import '../model/enums/encodings.dart'; +import 'identifiers.dart'; + +final otpAutjPeriodSecondsValidatorNullable = otpAutjPeriodSecondsValidator.nullable(); +final otpAutjPeriodSecondsValidator = ObjectValidator( + transformer: (v) => int.parse(v), + allowedValues: (v) => v > 0, +); + +final otpAuthDigitsValidatorNullable = otpAuthDigitsValidator.nullable(); +final otpAuthDigitsValidator = ObjectValidator( + transformer: (v) => int.parse(v), + defaultValue: 6, + allowedValues: (p0) => p0 > 0, +); +final otpAuthCounterValidator = ObjectValidator( + transformer: (v) => int.parse(v), + allowedValues: (v) => v >= 0, +); + +final stringToIntValidatorNullable = stringToIntvalidator.nullable(); +final stringToIntvalidator = ObjectValidator(transformer: (v) => int.parse(v)); + +final intToStringValidator = ObjectValidator(transformer: (v) => (v as int).toString()); +final intToStringValidatorNullable = intToStringValidator.nullable(); + +final stringSecondsToDurationValidatorNullable = stringSecondsToDurationValidator.nullable(); +final stringSecondsToDurationValidator = ObjectValidator( + transformer: (v) => Duration(seconds: int.parse(v)), + allowedValues: (v) => v.inSeconds > 0, +); + +final stringToUriValidatorNullable = stringToUrivalidator.nullable(); +final stringToUrivalidator = ObjectValidator(transformer: (v) => Uri.parse(v)); + +final stringToBoolValidatorNullable = stringToBoolValidator.nullable(); +final stringToBoolValidator = ObjectValidator( + transformer: (v) => switch ((v as String).toLowerCase()) { + 'true' => true, + '1' => true, + 'false' => false, + '0' => false, + _ => throw ArgumentError('Invalid boolean value: $v'), + }); + +final stringToAlgorithmsValidator = ObjectValidator( + transformer: (v) { + return Algorithms.values.byName((v as String).toUpperCase()); + }, +); +final stringToAlgorithmsValidatorNullable = stringToAlgorithmsValidator.nullable(); + +/// When value is given, it checks if the value is a base32 encoded string. +final base32SecretValidatorNullable = base32Secretvalidator.nullable(); + +/// Checks if the value is a base32 encoded string. +final base32Secretvalidator = ObjectValidator(transformer: (v) { + if (v is String) v = Encodings.base32.decode(v); + return Encodings.base32.encode(v); +}); diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart index c4aa18af7..c449697f0 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -356,16 +356,16 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler if (container == null) throw StateError('Credential was removed'); responseJson = jsonDecode(responseBody); Logger.debug('Response JSON: $responseJson', name: 'CredentialsNotifier#_parseResponse'); - final result = validate(value: responseJson['result'], validator: const TypeValidatorRequired>(), name: 'result'); - final value = validate(value: result['value'], validator: const TypeValidatorRequired>(), name: 'value'); + final result = validate(value: responseJson['result'], validator: const ObjectValidator>(), name: 'result'); + final value = validate(value: result['value'], validator: const ObjectValidator>(), name: 'value'); publicServerKey = validate( value: value['public_server_key'], - validator: TypeValidatorRequired(transformer: (v) => const EccUtils().deserializeECPublicKey(v)), + validator: ObjectValidator(transformer: (v) => const EccUtils().deserializeECPublicKey(v)), name: 'public_server_key', ); final syncUrlUri = validate( value: value['container_sync_url'], - validator: TypeValidatorRequired(transformer: (v) => Uri.parse(v)), + validator: ObjectValidator(transformer: (v) => Uri.parse(v)), name: 'container_sync_url', ); container = diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart index 04ec570b2..e43dbb05f 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart @@ -7,7 +7,7 @@ part of 'token_container_notifier.dart'; // ************************************************************************** String _$tokenContainerNotifierHash() => - r'cf5aaebd080b94a5bff442a564a8f29280f56871'; + r'd5840f35c80d04cb3c51aefb44e7a0c5d5f00416'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/type_matchers.dart b/lib/utils/type_matchers.dart deleted file mode 100644 index 29b6c4657..000000000 --- a/lib/utils/type_matchers.dart +++ /dev/null @@ -1,60 +0,0 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; - -import '../model/enums/algorithms.dart'; -import '../model/enums/encodings.dart'; -import 'identifiers.dart'; - -final stringToIntValidatorOptional = stringToIntvalidator.optional(); -final stringToIntvalidator = TypeValidatorRequired(transformer: (v) => int.parse(v)); - -final intToStringValidator = TypeValidatorRequired(transformer: (v) => (v as int).toString()); -final intToStringValidatorOptional = intToStringValidator.optional(); - -final stringSecondsToDurationvalidatorOptional = stringSecondsToDurationvalidator.optional(); -final stringSecondsToDurationvalidator = TypeValidatorRequired(transformer: (v) => Duration(seconds: int.parse(v))); - -final stringToUriValidatorOptional = stringToUrivalidator.optional(); -final stringToUrivalidator = TypeValidatorRequired(transformer: (v) => Uri.parse(v)); - -final stringToBoolValidatorOptional = stringToBoolValidator.optional(); -final stringToBoolValidator = TypeValidatorRequired( - transformer: (v) => switch ((v as String).toLowerCase()) { - 'true' => true, - '1' => true, - 'false' => false, - '0' => false, - _ => throw ArgumentError('Invalid boolean value: $v'), - }); - -final stringToAlgorithmsValidator = TypeValidatorRequired( - transformer: (v) => Algorithms.values.byName((v as String).toUpperCase()), -); -final stringToAlgorithmsValidatorOptional = stringToAlgorithmsValidator.optional(); - -/// When value is given, it checks if the value is a base32 encoded string. -final base32SecretValidatorOptional = base32Secretvalidator.optional(); - -/// Checks if the value is a base32 encoded string. -final base32Secretvalidator = TypeValidatorRequired(transformer: (v) { - if (v is String) v = Encodings.base32.decode(v); - return Encodings.base32.encode(v); -}); diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 4adec9de0..41db9c799 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -221,7 +221,7 @@ Future scanQrCode(List resultHandlerList, Object? qrCode) a } final processorResults = await SchemeProcessor.processUriByAny(uri); if (processorResults == null) return; - final resultHandlerTypeMap = , List>{}; + final resultHandlerTypeMap = , List>{}; for (var result in processorResults) { final validator = result.resultHandlerType; 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 cd4a10752..80f346bf9 100644 --- a/lib/views/import_tokens_view/pages/import_start_page.dart +++ b/lib/views/import_tokens_view/pages/import_start_page.dart @@ -228,7 +228,7 @@ class _ImportStartPageState extends ConsumerState { isPrivacyIdeaToken: false, data: t.resultData.origin?.data ?? fileString, ), - resultHandlerType: const TypeValidatorRequired(), + resultHandlerType: const ObjectValidator(), ); }).toList(); @@ -265,7 +265,7 @@ class _ImportStartPageState extends ConsumerState { token: t.resultData, data: t.resultData.origin?.data ?? uri.toString(), ), - resultHandlerType: const TypeValidatorRequired(), + resultHandlerType: const ObjectValidator(), ); }).toList(); Logger.info("QR code scanned successfully", name: "_scanQrCode#ImportStartPage"); @@ -361,7 +361,7 @@ class _ImportStartPageState extends ConsumerState { isPrivacyIdeaToken: false, data: _linkController.text, ), - resultHandlerType: const TypeValidatorRequired(), + resultHandlerType: const ObjectValidator(), ); }).toList(); if (!mounted) return; diff --git a/local_plugins/pi-authenticator-legacy/example/pubspec.lock b/local_plugins/pi-authenticator-legacy/example/pubspec.lock index 88b96a6aa..49a3deef3 100644 --- a/local_plugins/pi-authenticator-legacy/example/pubspec.lock +++ b/local_plugins/pi-authenticator-legacy/example/pubspec.lock @@ -195,10 +195,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.5" sdks: dart: ">=3.3.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/local_plugins/pi-authenticator-legacy/pubspec.lock b/local_plugins/pi-authenticator-legacy/pubspec.lock index 8b1dee4e9..df9b83888 100644 --- a/local_plugins/pi-authenticator-legacy/pubspec.lock +++ b/local_plugins/pi-authenticator-legacy/pubspec.lock @@ -180,10 +180,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.5" sdks: dart: ">=3.3.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/test/unit_test/model/token/day_password_test.dart b/test/unit_test/model/token/day_password_test.dart index fd4fed7b2..917639933 100644 --- a/test/unit_test/model/token/day_password_test.dart +++ b/test/unit_test/model/token/day_password_test.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; @@ -8,6 +7,7 @@ import 'package:privacyidea_authenticator/model/enums/encodings.dart'; import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import 'package:privacyidea_authenticator/model/tokens/day_password_token.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; void main() { _testDayPasswordToken(); @@ -77,15 +77,15 @@ void _testDayPasswordToken() { group('fromUriMap', () { test('with full map', () { final uriMap = { - 'URI_PERIOD': 30, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'SHA1', - 'URI_DIGITS': 6, - 'URI_SECRET': Uint8List.fromList(utf8.encode('secret')), - 'URI_TYPE': 'DAYPASSWORD', - 'URI_PIN': false, - 'URI_IMAGE': 'example.png', + OTP_AUTH_PERIOD_SECONDS: '30', + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'SHA1', + OTP_AUTH_DIGITS: '6', + OTP_AUTH_SECRET_BASE32: Encodings.base32.encode(utf8.encode('secret')), + OTP_AUTH_TYPE: 'DAYPASSWORD', + OTP_AUTH_PIN: 'False', + OTP_AUTH_IMAGE: 'example.png', }; final totpFromUriMap = DayPasswordToken.fromOtpAuthMap(uriMap); expect(totpFromUriMap.period, const Duration(seconds: 30)); @@ -100,56 +100,70 @@ void _testDayPasswordToken() { }); test('with missing secret', () { final uriMap = { - 'URI_PERIOD': 30, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'SHA1', - 'URI_DIGITS': 6, - 'URI_TYPE': 'DAYPASSWORD', - 'URI_PIN': false, - 'URI_IMAGE': 'example.png', + OTP_AUTH_PERIOD_SECONDS: 30, + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'SHA1', + OTP_AUTH_DIGITS: 6, + OTP_AUTH_TYPE: 'DAYPASSWORD', + OTP_AUTH_PIN: 'False', + OTP_AUTH_IMAGE: 'example.png', }; expect(() => DayPasswordToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); test('with zero period', () { final uriMap = { - 'URI_PERIOD': 0, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'SHA1', - 'URI_DIGITS': 6, - 'URI_SECRET': Uint8List.fromList(utf8.encode('secret')), - 'URI_TYPE': 'DAYPASSWORD', - 'URI_PIN': false, - 'URI_IMAGE': 'example.png', + OTP_AUTH_PERIOD_SECONDS: '0', + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'SHA1', + OTP_AUTH_DIGITS: '6', + OTP_AUTH_SECRET_BASE32: Encodings.base32.encode(utf8.encode('secret')), + OTP_AUTH_TYPE: 'DAYPASSWORD', + OTP_AUTH_PIN: 'False', + OTP_AUTH_IMAGE: 'example.png', }; expect(() => DayPasswordToken.fromOtpAuthMap(uriMap), throwsA(isA())); + var errorContainsPeriod = false; + try { + DayPasswordToken.fromOtpAuthMap(uriMap); + } catch (e) { + errorContainsPeriod = e.toString().contains(OTP_AUTH_PERIOD_SECONDS); + } + expect(errorContainsPeriod, true); }); test('with zero digits', () { final uriMap = { - 'URI_PERIOD': 30, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'SHA1', - 'URI_DIGITS': 0, - 'URI_SECRET': Uint8List.fromList(utf8.encode('secret')), - 'URI_TYPE': 'DAYPASSWORD', - 'URI_PIN': false, - 'URI_IMAGE': 'example.png', + OTP_AUTH_PERIOD_SECONDS: '30', + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'SHA1', + OTP_AUTH_DIGITS: '0', + OTP_AUTH_SECRET_BASE32: Encodings.base32.encode(utf8.encode('secret')), + OTP_AUTH_TYPE: 'DAYPASSWORD', + OTP_AUTH_PIN: 'False', + OTP_AUTH_IMAGE: 'example.png', }; expect(() => DayPasswordToken.fromOtpAuthMap(uriMap), throwsA(isA())); + var errorContainsDigits = false; + try { + DayPasswordToken.fromOtpAuthMap(uriMap); + } catch (e) { + errorContainsDigits = e.toString().contains(OTP_AUTH_DIGITS); + } + expect(errorContainsDigits, true); }); test('with lowercase algorithm', () { final uriMap = { - 'URI_PERIOD': 30, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'sha1', - 'URI_DIGITS': 6, - 'URI_SECRET': Uint8List.fromList(utf8.encode('secret')), - 'URI_TYPE': 'DAYPASSWORD', - 'URI_PIN': false, - 'URI_IMAGE': 'example.png', + OTP_AUTH_PERIOD_SECONDS: '30', + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'sha1', + OTP_AUTH_DIGITS: '6', + OTP_AUTH_SECRET_BASE32: Encodings.base32.encode(utf8.encode('secret')), + OTP_AUTH_TYPE: 'DAYPASSWORD', + OTP_AUTH_PIN: 'False', + OTP_AUTH_IMAGE: 'example.png', }; final totpFromUriMap = DayPasswordToken.fromOtpAuthMap(uriMap); expect(totpFromUriMap.algorithm, Algorithms.SHA1); diff --git a/test/unit_test/model/token/hotp_token_test.dart b/test/unit_test/model/token/hotp_token_test.dart index 2e637b16c..bb58b30a6 100644 --- a/test/unit_test/model/token/hotp_token_test.dart +++ b/test/unit_test/model/token/hotp_token_test.dart @@ -6,6 +6,7 @@ import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; import 'package:privacyidea_authenticator/model/enums/encodings.dart'; import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; void main() { _testHotpToken(); @@ -79,15 +80,15 @@ void _testHotpToken() { group('fromUriMap', () { test('with full map', () { final uriMap = { - 'URI_COUNTER': 10, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'SHA1', - 'URI_SECRET': Uint8List.fromList(utf8.encode('secret')), - 'URI_DIGITS': 6, - 'URI_TYPE': 'HOTP', - 'URI_PIN': true, - 'URI_IMAGE': 'example.png', + OTP_AUTH_COUNTER: '10', + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'SHA1', + OTP_AUTH_SECRET_BASE32: Encodings.base32.encode(utf8.encode('secret')), + OTP_AUTH_DIGITS: '6', + OTP_AUTH_TYPE: 'HOTP', + OTP_AUTH_PIN: 'True', + OTP_AUTH_IMAGE: 'example.png', }; final hotpFromUriMap = HOTPToken.fromOtpAuthMap(uriMap); expect(hotpFromUriMap.counter, 10); @@ -102,42 +103,49 @@ void _testHotpToken() { }); test('without secret', () { final uriMap = { - 'URI_COUNTER': 10, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'SHA1', - 'URI_DIGITS': 6, - 'URI_TYPE': 'HOTP', - 'URI_PIN': true, - 'URI_IMAGE': 'example.png', + OTP_AUTH_COUNTER: '10', + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'SHA1', + OTP_AUTH_DIGITS: '6', + OTP_AUTH_TYPE: 'HOTP', + OTP_AUTH_PIN: 'True', + OTP_AUTH_IMAGE: 'example.png', }; expect(() => HOTPToken.fromOtpAuthMap(uriMap), throwsArgumentError); }); test('digits is zero', () { final uriMap = { - 'URI_COUNTER': 10, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'SHA1', - 'URI_SECRET': Uint8List.fromList(utf8.encode('secret')), - 'URI_DIGITS': 0, - 'URI_TYPE': 'HOTP', - 'URI_PIN': true, - 'URI_IMAGE': 'example.png', + OTP_AUTH_COUNTER: '10', + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'SHA1', + OTP_AUTH_SECRET_BASE32: Encodings.base32.encode(utf8.encode('secret')), + OTP_AUTH_DIGITS: '0', + OTP_AUTH_TYPE: 'HOTP', + OTP_AUTH_PIN: 'True', + OTP_AUTH_IMAGE: 'example.png', }; expect(() => HOTPToken.fromOtpAuthMap(uriMap), throwsArgumentError); + bool errorContainsDigits = false; + try { + HOTPToken.fromOtpAuthMap(uriMap); + } on ArgumentError catch (e) { + errorContainsDigits = e.toString().contains(OTP_AUTH_DIGITS); + } + expect(errorContainsDigits, true); }); test('with lowercase algorithm', () { final uriMap = { - 'URI_COUNTER': 10, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'sha1', - 'URI_SECRET': Uint8List.fromList(utf8.encode('secret')), - 'URI_DIGITS': 6, - 'URI_TYPE': 'HOTP', - 'URI_PIN': true, - 'URI_IMAGE': 'example.png', + OTP_AUTH_COUNTER: '10', + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'sha1', + OTP_AUTH_SECRET_BASE32: Encodings.base32.encode(utf8.encode('secret')), + OTP_AUTH_DIGITS: '6', + OTP_AUTH_TYPE: 'HOTP', + OTP_AUTH_PIN: 'True', + OTP_AUTH_IMAGE: 'example.png', }; final hotpFromUriMap = HOTPToken.fromOtpAuthMap(uriMap); expect(hotpFromUriMap.counter, 10); diff --git a/test/unit_test/model/token/push_token_test.dart b/test/unit_test/model/token/push_token_test.dart index c0661a131..1573bb8f9 100644 --- a/test/unit_test/model/token/push_token_test.dart +++ b/test/unit_test/model/token/push_token_test.dart @@ -1,8 +1,7 @@ -import 'dart:convert'; - import 'package:flutter_test/flutter_test.dart'; import 'package:privacyidea_authenticator/model/enums/push_token_rollout_state.dart'; import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; void main() { _testPushToken(); @@ -138,6 +137,7 @@ void _testPushToken() { test('toJson', () { final tokenJson = pushToken.toJson(); final json = { + "checkedContainer": [], "containerSerial": null, "label": "label", "issuer": "issuer", @@ -162,19 +162,22 @@ void _testPushToken() { "privateTokenKey": "privateTokenKey", "publicTokenKey": "publicTokenKey" }; - expect(jsonEncode(tokenJson), jsonEncode(json)); + for (final key in json.keys) { + expect(tokenJson[key], json[key]); + } }); group('fromUriMap', () { test('with full map', () { final uriMap = { - 'URI_TYPE': 'PIPUSH', - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_SERIAL': 'serial', - 'URI_SSL_VERIFY': false, - 'URI_ENROLLMENT_CREDENTIAL': 'enrollmentCredentials', - 'URI_ROLLOUT_URL': Uri.parse('http://www.example.com'), - 'URI_TTL': 10, + OTP_AUTH_TYPE: 'PIPUSH', + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_SERIAL: 'serial', + OTP_AUTH_PUSH_SSL_VERIFY: 'False', + OTP_AUTH_PUSH_ENROLLMENT_CREDENTIAL: 'enrollmentCredentials', + OTP_AUTH_PUSH_ROLLOUT_URL: 'http://www.example.com', + OTP_AUTH_PUSH_TTL_MINUTES: '10', + OTP_AUTH_VERSION: '1', }; final token = PushToken.fromOtpAuthMap(uriMap); expect(token.type, 'PIPUSH'); diff --git a/test/unit_test/model/token/steam_token_test.dart b/test/unit_test/model/token/steam_token_test.dart index ac6be89ca..f682fc0fb 100644 --- a/test/unit_test/model/token/steam_token_test.dart +++ b/test/unit_test/model/token/steam_token_test.dart @@ -1,10 +1,12 @@ import 'dart:convert'; -import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; +import 'package:privacyidea_authenticator/model/enums/encodings.dart'; +import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import 'package:privacyidea_authenticator/model/tokens/steam_token.dart'; import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; void main() { _testSteamToken(); @@ -71,12 +73,12 @@ void _testSteamToken() { group('fromUriMap', () { test('with full map', () { final uriMap = { - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_SECRET': Uint8List.fromList(utf8.encode('secret')), - 'URI_TYPE': 'totp', - 'URI_PIN': false, - 'URI_IMAGE': 'example.png', + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_SECRET_BASE32: Encodings.base32.encode(utf8.encode('secret')), + OTP_AUTH_TYPE: 'totp', + OTP_AUTH_PIN: 'False', + OTP_AUTH_IMAGE: 'example.png', }; final totpFromUriMap = SteamToken.fromOtpAuthMap(uriMap); expect(totpFromUriMap.period, 30); @@ -91,11 +93,11 @@ void _testSteamToken() { }); test('with missing secret', () { final uriMap = { - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_TYPE': 'totp', - 'URI_PIN': false, - 'URI_IMAGE': 'example.png', + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_TYPE: 'totp', + OTP_AUTH_PIN: 'False', + OTP_AUTH_IMAGE: 'example.png', }; expect(() => SteamToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); @@ -155,8 +157,8 @@ void _testSteamToken() { id: '', secret: 'SECRETA=', ); - final otp = steamToken.otpOfTime(time); - final otpNow = steamToken.otpOfTime(DateTime.now()); + final otp = steamToken.otpFromTime(time); + final otpNow = steamToken.otpFromTime(DateTime.now()); expect(otp, equals('JGPCJ')); // Checks if the otpOfTime works correctly expect(steamToken.otpValue, equals(otpNow)); // Checks if the otpValue delivers the same value as the otpOfTime method }); diff --git a/test/unit_test/model/token/totp_token_test.dart b/test/unit_test/model/token/totp_token_test.dart index 6aaa4fb46..d52c368e6 100644 --- a/test/unit_test/model/token/totp_token_test.dart +++ b/test/unit_test/model/token/totp_token_test.dart @@ -7,6 +7,7 @@ import 'package:privacyidea_authenticator/model/enums/encodings.dart'; import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart'; import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; void main() { _testTotpToken(); @@ -75,15 +76,15 @@ void _testTotpToken() { group('fromUriMap', () { test('with full map', () { final uriMap = { - 'URI_PERIOD': 30, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'SHA1', - 'URI_DIGITS': 6, - 'URI_SECRET': Uint8List.fromList(utf8.encode('secret')), - 'URI_TYPE': 'totp', - 'URI_PIN': false, - 'URI_IMAGE': 'example.png', + OTP_AUTH_PERIOD_SECONDS: '30', + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'SHA1', + OTP_AUTH_DIGITS: '6', + OTP_AUTH_SECRET_BASE32: Encodings.base32.encode(utf8.encode('secret')), + OTP_AUTH_TYPE: 'totp', + OTP_AUTH_PIN: 'False', + OTP_AUTH_IMAGE: 'example.png', }; final totpFromUriMap = TOTPToken.fromOtpAuthMap(uriMap); expect(totpFromUriMap.period, 30); @@ -98,56 +99,56 @@ void _testTotpToken() { }); test('with missing secret', () { final uriMap = { - 'URI_PERIOD': 30, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'SHA1', - 'URI_DIGITS': 6, - 'URI_TYPE': 'totp', - 'URI_PIN': false, - 'URI_IMAGE': 'example.png', + OTP_AUTH_PERIOD_SECONDS: 30, + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'SHA1', + OTP_AUTH_DIGITS: 6, + OTP_AUTH_TYPE: 'totp', + OTP_AUTH_PIN: 'False', + OTP_AUTH_IMAGE: 'example.png', }; expect(() => TOTPToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); test('with zero period', () { final uriMap = { - 'URI_PERIOD': 0, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'SHA1', - 'URI_DIGITS': 6, - 'URI_SECRET': Uint8List.fromList(utf8.encode('secret')), - 'URI_TYPE': 'totp', - 'URI_PIN': false, - 'URI_IMAGE': 'example.png', + OTP_AUTH_PERIOD_SECONDS: 0, + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'SHA1', + OTP_AUTH_DIGITS: 6, + OTP_AUTH_SECRET_BASE32: Uint8List.fromList(utf8.encode('secret')), + OTP_AUTH_TYPE: 'totp', + OTP_AUTH_PIN: 'False', + OTP_AUTH_IMAGE: 'example.png', }; expect(() => TOTPToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); test('with zero digits', () { final uriMap = { - 'URI_PERIOD': 30, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'SHA1', - 'URI_DIGITS': 0, - 'URI_SECRET': Uint8List.fromList(utf8.encode('secret')), - 'URI_TYPE': 'totp', - 'URI_PIN': false, - 'URI_IMAGE': 'example.png', + OTP_AUTH_PERIOD_SECONDS: 30, + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'SHA1', + OTP_AUTH_DIGITS: 0, + OTP_AUTH_SECRET_BASE32: Uint8List.fromList(utf8.encode('secret')), + OTP_AUTH_TYPE: 'totp', + OTP_AUTH_PIN: 'False', + OTP_AUTH_IMAGE: 'example.png', }; expect(() => TOTPToken.fromOtpAuthMap(uriMap), throwsA(isA())); }); test('with lowercase algorithm', () { final uriMap = { - 'URI_PERIOD': 30, - 'URI_LABEL': 'label', - 'URI_ISSUER': 'issuer', - 'URI_ALGORITHM': 'sha1', - 'URI_DIGITS': 6, - 'URI_SECRET': Uint8List.fromList(utf8.encode('secret')), - 'URI_TYPE': 'totp', - 'URI_PIN': false, - 'URI_IMAGE': 'example.png', + OTP_AUTH_PERIOD_SECONDS: '30', + OTP_AUTH_LABEL: 'label', + OTP_AUTH_ISSUER: 'issuer', + OTP_AUTH_ALGORITHM: 'sha1', + OTP_AUTH_DIGITS: '6', + OTP_AUTH_SECRET_BASE32: Uint8List.fromList(utf8.encode('secret')), + OTP_AUTH_TYPE: 'totp', + OTP_AUTH_PIN: 'False', + OTP_AUTH_IMAGE: 'example.png', }; final totpFromUriMap = TOTPToken.fromOtpAuthMap(uriMap); expect(totpFromUriMap.algorithm, Algorithms.SHA1); 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 a5d7c81db..9c989e11c 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,9 +1,9 @@ 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'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; void main() { _testFreeOtpPlusQrProcessor(); @@ -45,15 +45,11 @@ void _testFreeOtpPlusQrProcessor() { final results = await const FreeOtpPlusQrProcessor().processUri(normalOtpAuthUri); // Assert expect(results.length, equals(1)); - 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)); + final result0 = results[0]; + expect(result0, isA>()); + final message = result0.asFailed!.message; + final error = result0.asFailed!.error; + expect(message.toLowerCase().contains(OTP_AUTH_COUNTER) || error.toString().toLowerCase().contains(OTP_AUTH_COUNTER), isTrue); }); }); } 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 f49a9a005..47bdd5f17 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 @@ -6,6 +6,7 @@ import 'package:privacyidea_authenticator/model/tokens/push_token.dart'; import 'package:privacyidea_authenticator/model/tokens/totp_token.dart'; import 'package:privacyidea_authenticator/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart'; import 'package:privacyidea_authenticator/utils/customization/application_customization.dart'; +import 'package:privacyidea_authenticator/utils/identifiers.dart'; void main() { _testOtpAuthProcessor(); @@ -234,19 +235,10 @@ void _testOtpAuthProcessor() { // Assert expect(results.length, equals(1)); final result0 = results[0]; - expect(result0, isA()); - final token0 = result0.asSuccess!.resultData; - expect(token0.issuer, equals('issuer')); - expect(token0.label, equals('account')); - expect(token0.type, equals('HOTP')); - expect(token0.origin, isNotNull); - expect(token0.origin!.appName, ApplicationCustomization.defaultCustomization.appName); - expect(token0.origin!.isPrivacyIdeaToken, isNull); - expect(token0.origin!.data, equals(uriString)); - final hotpToken = token0 as HOTPToken; - expect(hotpToken.counter, equals(0)); - expect(hotpToken.digits, equals(8)); - expect(hotpToken.algorithm.name, equals('SHA256')); + expect(result0, isA()); + final message = result0.asFailed!.message; + final error = result0.asFailed!.error; + expect(message.toLowerCase().contains(OTP_AUTH_COUNTER) || error.toString().toLowerCase().contains(OTP_AUTH_COUNTER), isTrue); }); test('processUri missing secret', () async { // Arrange @@ -261,7 +253,7 @@ void _testOtpAuthProcessor() { expect(result0, isA()); final message = result0.asFailed!.message; final error = result0.asFailed!.error; - expect(message.toLowerCase().contains('secret') || error.toString().toLowerCase().contains('secret'), isTrue); + expect(message.toLowerCase().contains(OTP_AUTH_SECRET_BASE32) || error.toString().toLowerCase().contains(OTP_AUTH_SECRET_BASE32), isTrue); }); test('processUri issuer from path', () async { // Arrange From 771fb44bdc6fd6799add67648d756b12e8547909 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:56:22 +0200 Subject: [PATCH 043/285] fixed based on the tests --- test/unit_test/model/processor_result_test.dart | 2 +- test/unit_test/model/token/hotp_token_test.dart | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/test/unit_test/model/processor_result_test.dart b/test/unit_test/model/processor_result_test.dart index 81aff422d..12194cdbe 100644 --- a/test/unit_test/model/processor_result_test.dart +++ b/test/unit_test/model/processor_result_test.dart @@ -20,7 +20,7 @@ void _testProcessorResult() { }); group('factories', () { test('success', () { - const result = ProcessorResult.success('data'); + ProcessorResult result = const ProcessorResult.success('data'); expect(result, isA()); expect((result as ProcessorResultSuccess).resultData, 'data'); }); diff --git a/test/unit_test/model/token/hotp_token_test.dart b/test/unit_test/model/token/hotp_token_test.dart index bb58b30a6..76c0b9a53 100644 --- a/test/unit_test/model/token/hotp_token_test.dart +++ b/test/unit_test/model/token/hotp_token_test.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; From 403e9ab41fbb02eb2de37b5d84afdd1fc3167c34 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Fri, 20 Sep 2024 15:14:21 +0200 Subject: [PATCH 044/285] l10n --- integration_test/views_test.dart | 4 +- lib/l10n/app_cs.arb | 1247 +++++++++------ lib/l10n/app_de.arb | 1244 +++++++++------ lib/l10n/app_en.arb | 1339 +++++++++-------- lib/l10n/app_es.arb | 1239 +++++++++------ lib/l10n/app_fr.arb | 1252 ++++++++------- lib/l10n/app_nl.arb | 1241 +++++++++------ lib/l10n/app_pl.arb | 1244 +++++++++------ lib/mains/main_customizer.dart | 1 + lib/mains/main_netknights.dart | 1 + ...brid_token_container_state_repository.dart | 190 --- ...mote_token_container_state_repository.dart | 50 - ...token_container_state_repository.dart.dart | 99 -- .../token_container_notifier.dart | 2 +- .../rows/add_token_button.dart | 2 +- lib/views/container_view/container_view.dart | 12 +- lib/views/feedback_view/feedback_view.dart | 82 +- .../widgets/feedback_send_row.dart | 137 ++ lib/views/main_view/main_view.dart | 17 +- .../qr_scanner_button.dart | 2 +- .../default_edit_action_dialog.dart | 33 +- .../main_view_background_icon.dart | 159 ++ .../rollout_failed_widget.dart | 6 +- .../settings_group_container.dart | 29 +- .../settings_group_error_log.dart | 38 +- .../settings_group_feedback.dart | 36 + .../settings_group_general.dart | 16 +- .../settings_group_import_export_tokens.dart | 4 +- .../settings_group_language.dart | 103 +- .../settings_group_push_token.dart | 198 +-- .../settings_groups/settings_group_theme.dart | 82 +- lib/views/settings_view/settings_view.dart | 66 +- .../settings_view_widgets/settings_group.dart | 89 ++ .../settings_groups.dart | 56 - lib/views/splash_screen/splash_screen.dart | 1 + lib/widgets/app_wrapper.dart | 2 +- .../countdown_button.dart | 2 +- .../button_widgets/default_icon_button.dart | 54 + .../{ => button_widgets}/mutex_button.dart | 0 .../{ => button_widgets}/press_button.dart | 12 +- lib/widgets/default_refresh_indicator.dart | 2 +- .../dialog_widgets/push_request_dialog.dart | 12 +- 42 files changed, 6006 insertions(+), 4399 deletions(-) delete mode 100644 lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart delete mode 100644 lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart delete mode 100644 lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart create mode 100644 lib/views/feedback_view/widgets/feedback_send_row.dart create mode 100644 lib/views/main_view/main_view_widgets/token_widgets/main_view_background_icon.dart create mode 100644 lib/views/settings_view/settings_groups/settings_group_feedback.dart create mode 100644 lib/views/settings_view/settings_view_widgets/settings_group.dart delete mode 100644 lib/views/settings_view/settings_view_widgets/settings_groups.dart rename lib/widgets/{ => button_widgets}/countdown_button.dart (98%) create mode 100644 lib/widgets/button_widgets/default_icon_button.dart rename lib/widgets/{ => button_widgets}/mutex_button.dart (100%) rename lib/widgets/{ => button_widgets}/press_button.dart (67%) diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index d25b4d0bd..87c1e145c 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -26,7 +26,7 @@ import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/gene import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; import 'package:privacyidea_authenticator/model/version.dart'; import 'package:privacyidea_authenticator/utils/utils.dart'; -import 'package:privacyidea_authenticator/views/settings_view/settings_view_widgets/settings_groups.dart'; +import 'package:privacyidea_authenticator/views/settings_view/settings_view_widgets/settings_group.dart'; import '../test/tests_app_wrapper.dart'; import '../test/tests_app_wrapper.mocks.dart'; @@ -163,7 +163,7 @@ Future _settingsViewTest(WidgetTester tester) async { await tester.tap(find.byIcon(Icons.settings)); await tester.pumpAndSettle(); expect(find.text(AppLocalizationsEn().settings), findsOneWidget); - expect(find.text(AppLocalizationsEn().theme), findsOneWidget); + expect(find.text(AppLocalizationsEn().themeMode), findsOneWidget); expect(find.text(AppLocalizationsEn().language), findsOneWidget); expect(find.text(AppLocalizationsEn().errorLogTitle), findsOneWidget); expect(find.byType(SettingsGroup), findsNWidgets(6)); diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index 01f3bd56e..b374e65d0 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -1,381 +1,362 @@ { - "@@last_modified": "2023-08-07", - "patchNotesNewFeatures": "Nové funkce", - "patchNotesImprovements": "Improvements", - "patchNotesBugFixes": "Opravy chyb", - "patchNotesV4_4_0NewFeatures1": "Nyní je možné exportovat tokeny, u kterých lze zajistit, že se nejedná o tokeny privacyIDEA. V současné době nelze vyloučit, že tokeny přidané prostřednictvím čtečky QR kódů pocházejí z aplikace privacyIDEA. Rozlišování bude v budoucích verzích vylepšeno.", - "patchNotesV4_4_0NewFeatures2": "Přidána podpora pro \"require presence\" aplikace privacyIDEA.", - "patchNotesV4_4_0Improvement1": "Byly přidány další dovozní zdroje.", - "patchNotesV4_4_0Improvement2": "Bylo vylepšeno rozpoznávání QR kódů z obrazových souborů.", - "patchNotesV4_3_1BugFix1": "Opraven problém, kdy nebyla zobrazena hodnota otp po ověření na některých zařízeních.", - "patchNotesV4_3_1Improvement1": "Skener QR kódů byl vylepšen.", - "patchNotesV4_3_0NewFeatures1": "Přidána podpora pro import tokenů z Google, Aegis a 2FAS Authenticator. Další zdroje importu budou přidány v budoucnu.", - "patchNotesV4_3_0NewFeatures2": "Do nastavení byla přidána možnost zpětné vazby.", - "patchNotesV4_3_0NewFeatures3": "Tokeny Push lze nyní skrýt ze seznamu tokenů.", - "patchNotesV4_3_0NewFeatures4": "Byly přidány úvodní informace, které novým uživatelům usnadní začátky.", - "patchNotesV4_3_0NewFeatures5": "Žetony nyní můžete vyhledávat klepnutím na lupu v pravém horním rohu.", - "patchNotesV4_3_0NewFeatures6": "Přidán token HomeWidget pro systém Android 12 a novější.", - "guide": "Průvodce", - "@guide": { - "description": "Button to open the guide screen." - }, - "retry": "Zkusit znovu", - "@retry": { - "description": "Label for e.g. a button. Something is tried to be done again." - }, - "accept": "Přijmout", + "@@last_modified": "2024-09-20", + "@@locale": "cs", "@accept": { "description": "Label for e.g. a button. Something gets accepted by the user." }, - "decline": "Odmítnout", - "@decline": { - "description": "Label for e.g. a button. Something gets declined by the user." - }, - "name": "Název", - "@name": { - "description": "Describes the field where the tokens name should be entered." - }, - "secretKey": "Tajný klíč", - "@secretKey": { - "description": "Describes the field where the tokens secret should be entered." - }, - "encoding": "Kódování", - "@encoding": { - "description": "Title of the dropdown button where the encoding is selected." + "@addToken": { + "description": "The button to open the screen to add tokens by hand." }, - "algorithm": "Algoritmus", "@algorithm": { "description": "Title of the dropdown button where the encoding is selected." }, - "digits": "Počet číslic", - "@digits": { - "description": "Title of the dropdown button where the number of digits for the opt value is selected." + "@algorithmUnsupported": { + "placeholders": { + "algorithm": { + "example": "MD5" + } + } }, - "type": "Typ", - "@type": { - "description": "Title of the dropdown button where the type of the token is selected." + "@allTokensSynchronized": { + "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "period": "Časový interval", - "@period": { - "description": "Title of the dropdown button where the period of the totp token is selected." + "@authNotSupportedBody": { + "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." }, - "rename": "Přejmenovat", - "@rename": { - "description": "Label that describes renaming the token." + "@authNotSupportedTitle": { + "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." }, - "cancel": "Zrušit", - "@cancel": { - "description": "Button to cancel an action." + "@authToAcceptPushRequest": { + "description": "Message to accepting a push request for authentication." }, - "delete": "Smazat", - "@delete": { - "description": "Label that describes deleting the token." + "@authToDeclinePushRequest": { + "description": "Message for declining a push request for authentication." }, - "dismiss": "Zavřít", - "@dismiss": { - "description": "Text of a button that closes a dialog." + "@authenticateToShowOtp": { + "description": "Reason to authenticate when trying to view a one time password." }, - "addToken": "Přidat token", - "@addToken": { - "description": "The button to open the screen to add tokens by hand." + "@authenticateToUnLockToken": { + "description": "Reason to authenticate when trying to lock or unlock a token." }, - "scanQrCode": "Naskenovat QR kód", - "@scanQrCode": { - "description": "The button to scan otpauth qr-codes." + "@biometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." }, - "enterDetailsForToken": "Vložte podrobnosti tokenu", - "@enterDetailsForToken": { - "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." + "@biometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterANameForThisToken": "Vložte název pro tento token.", - "@pleaseEnterANameForThisToken": { - "description": "Hint telling the user to enter a name for a token." + "@biometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterASecretForThisToken": "Vložte tajný klíč pro tento token.", - "@pleaseEnterASecretForThisToken": { - "description": "Hint telling the user to enter a secret for a token." + "@biometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." }, - "theSecretDoesNotFitTheCurrentEncoding": "Tajný klíč neodpovídá zvolenému kódování.", - "@theSecretDoesNotFitTheCurrentEncoding": { - "description": "Hint telling the user that the secret does not fit the selected encoding." + "@cancel": { + "description": "Button to cancel an action." }, - "renameToken": "Přejmenovat token", - "@renameToken": { - "description": "Title of the dialog where a new name for a token can be entered." + "@checkServerCertificate": { + "description": "Error message when the server certificate should be checked." + }, + "@checkYourNetwork": { + "description": "Tells the user to check the network connection." + }, + "@clearErrorLog": { + "description": "Button to clear the error log." }, - "confirmDeletion": "Potvrdit smazání", "@confirmDeletion": { "description": "Title of the dialog where a token can be deleted." }, - "confirmDeletionOf": "Opravdu chcete smazat token {name}?", "@confirmDeletionOf": { "description": "Asks for confirmation on deleting a token.", - "type": "text", "placeholders": { "name": { "example": "PUSH1234" } - } - }, - "confirmTokenDeletionHint": "Pokud tento token odstraníte, nebude již možné se přihlásit.\nProsím, ujistěte se, že se můžete přihlásit k přidruženému účtu bez tohoto tokenu.", - "@confirmTokenDeletionHint": { - "description": "Gives the user a hint about the consequences of deleting a token." + }, + "type": "text" }, - "confirmFolderDeletionHint": "Odstranění složky nemá žádný vliv na tokeny v ní.\nTokeny jsou přesunuty do hlavního seznamu.", "@confirmFolderDeletionHint": { "description": "Gives the user a hint about the consequences of deleting a folder." }, - "generatingPhonePart": "Generování klientské části", - "@generatingPhonePart": { - "description": "Title of a dialog telling the user that the phone part gets generated right now." + "@confirmTokenDeletionHint": { + "description": "Gives the user a hint about the consequences of deleting a token." }, - "phonePart": "Klientská část:", - "@phonePart": { - "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." + "@connectionFailed": { + "description": "Tells the user that the connection failed." }, - "otpValueCopiedMessage": "Heslo \"{otpValue}\" bylo zkopírováno do schránky.", - "@otpValueCopiedMessage": { - "description": "Tells the user that the otp value was copied to the clipboard.", - "type": "text", - "placeholders": { - "otpValue": { - "example": "055374" - } - } + "@container": { + "description": "Title for the container view." }, - "settings": "Nastavení", - "@settings": { - "description": "Button to open the settings page." + "@couldNotSignMessage": { + "description": "Tells the user that the message could not be signed." }, - "pushToken": "Push notifikace", - "@pushToken": { - "description": "Title for the settings block concerning the push tokens." + "@counter": { + "description": "Title of the counter field." }, - "theme": "Vzhled", - "@theme": { - "description": "Title of the setting group where the theme can be selected." + "@createdAt": { + "description": "Label for the creation date of the token." }, - "lightTheme": "Světlý", - "@lightTheme": { - "description": "The light theme." + "@creator": { + "description": "Label for the creator of the token." }, - "darkTheme": "Tmavý", "@darkTheme": { "description": "The dark theme." }, - "systemTheme": "Použít nastavení systému", - "@systemTheme": { - "description": "The systems theme." + "@decline": { + "description": "Label for e.g. a button. Something gets declined by the user." }, - "someTokensDoNotSupportPolling": "Některé tokeny jsou zastaralé a nepodporují polling", - "@someTokensDoNotSupportPolling": { - "description": "Tells the user, that the following tokens do not support polling.", - "type": "text", - "placeholders": {} + "@delete": { + "description": "Label that describes deleting the token." }, - "enablePolling": "Povolit polling", - "@enablePolling": { - "description": "Name of the setting switch that enables polling." + "@deviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." }, - "requestPushChallengesPeriodically": "Periodicky získávat výzvy ze serveru. Povolte pokud nefunguje příjem push notifikací.", - "@requestPushChallengesPeriodically": { - "description": "The description of the polling feature." + "@deviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." }, - "synchronizePushTokens": "Synchronizace push tokenů", - "@synchronizePushTokens": { - "description": "Title of synchronizing push tokens in settings." + "@digits": { + "description": "Title of the dropdown button where the number of digits for the opt value is selected." }, - "synchronizesTokensWithServer": "Synchronizovat tokeny se serverem privacyIDEA.", - "@synchronizesTokensWithServer": { - "description": "Description of synchronizing push tokens in settings." + "@dismiss": { + "description": "Text of a button that closes a dialog." }, - "sync": "Synchronizovat", - "@sync": { - "description": "Text of button that is used to synchronize push tokens." + "@enablePolling": { + "description": "Name of the setting switch that enables polling." }, - "synchronizingTokens": "Tokeny se synchronizují.", - "@synchronizingTokens": { - "description": "Title of the push synchronization dialog." + "@encoding": { + "description": "Title of the dropdown button where the encoding is selected." }, - "allTokensSynchronized": "Všechny tokeny jsou synchronizované.", - "@allTokensSynchronized": { - "description": "Content of the push synchronization dialog. Signaling the user that everything worked." + "@enterDetailsForToken": { + "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." }, - "syncFbTokenFailed": "Synchronizace následujících tokenů selhala, zkuste to znovu:", - "@syncFbTokenFailed": { - "description": "Headline for the list of tokens where the synchronization failed." + "@errorLogCleared": { + "description": "Message that tells the user that the error log was cleared." }, - "tokensDoNotSupportSynchronization": "Následující tokeny nepodporují synchronizaci a musí být znovu zaregistrovány:", - "@tokensDoNotSupportSynchronization": { - "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + "@errorLogEmpty": { + "description": "Message that tells the user that the error log is empty." + }, + "@errorLogTitle": { + "description": "Title of the error log screen." + }, + "@errorMailBody": { + "description": "Message for email body" }, - "errorRollOutFailed": "Registrace tokenu {name} selhala.", "@errorRollOutFailed": { "description": "Tells the user that the token could not be rolled out, because a network error occurred.", - "type": "text", "placeholders": { "name": { "example": "PUSH1234A" } - } + }, + "type": "text" }, - "statusCode": "Stavový kód: {statusCode}", - "@statusCode": { - "description": "Tells the user the status code of the error.", + "@errorRollOutNoConnectionToServer": { + "description": "Message for the rollout process", "placeholders": { - "statusCode": { - "example": "400" + "name": { + "example": "PUSH1234A" } } }, - "errorSynchronizationNoNetworkConnection": "Synchronizace tokenů selhala, připojení k serveru privacyIDEA se nezdařilo.", - "@errorSynchronizationNoNetworkConnection": { - "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." + "@errorRollOutSSLHandshakeFailed": { + "description": "Tells the user that the roll-out failed because the SSL handshake failed." }, - "errorRollOutUnknownError": "Vyskytla se neznámá chyba. Registrace není možná: {e}", "@errorRollOutUnknownError": { "description": "Tells the user that the roll-out failed because of an unknown error.", - "type": "text", "placeholders": { "e": { "example": "IllegalArgumentException on Line 5 ..." } - } - }, - "startRollout": "Začít zavádění", - "@startRollout": { - "description": "Label that tells the user that the token is being rolled out." - }, - "pollingChallenges": "Čekám na nové požadavky", - "@pollingChallenges": { + }, "type": "text" }, - "unexpectedError": "Nastala neočekávaná chyba.", - "@unexpectedError": { - "description": "Title of page report mode." - }, - "pollingFailed": "Dotaz se nezdařil.", - "@pollingFailed": { - "description": "Tells the user that the polling failed." + "@errorSynchronizationNoNetworkConnection": { + "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." }, - "pollingFailedFor": "Dotaz na {serial} se nezdařil.", - "@pollingFailedFor": { - "description": "Tells the user that the polling failed.", + "@errorTokenExpired": { "placeholders": { - "serial": { + "name": { "example": "PUSH1234A" } } }, - "noNetworkConnection": "Žádné připojení k síti.", - "@noNetworkConnection": { - "description": "Tells the user that there is no network connection." + "@errorUnlinkingPushToken": { + "description": "Error message when unlinking a push token failed.", + "placeholders": { + "label": { + "example": "PUSH1234A" + } + } }, - "connectionFailed": "Připojení se nezdařilo.", - "@connectionFailed": { - "description": "Tells the user that the connection failed." + "@errorWhenPullingChallenges": { + "description": "errorWhenPullingChallenges", + "placeholders": { + "name": { + "example": "PUSH1234A" + } + } }, - "checkYourNetwork": "Zkontrolujte prosím síťové připojení a zkuste to znovu.", - "@checkYourNetwork": { - "description": "Tells the user to check the network connection." + "@failedToLoad": { + "placeholders": { + "name": { + "example": "token data" + } + } }, - "serverNotReachable": "Na server se nepodařilo dovolat.", - "@serverNotReachable": { - "description": "Tells the user that the server could not be reached." + "@feedbackPrivacyPolicy2": { + "description": "Taping on this should open the privacy policy." }, - "couldNotSignMessage": "Zprávu se nepodařilo podepsat.", - "@couldNotSignMessage": { - "description": "Tells the user that the message could not be signed." + "@generatingPhonePart": { + "description": "Title of a dialog telling the user that the phone part gets generated right now." }, - "useDeviceLocaleTitle": "Použít jazyk zařízení", - "@useDeviceLocaleTitle": { - "description": "Title of the switch tile where using the devices language can be enabled." + "@generatingRSAKeyPair": { + "description": "Message for the rollout process" }, - "useDeviceLocaleDescription": "Použít jazyk zařízení, pokud je podporován, případně angličtinu.", - "@useDeviceLocaleDescription": { - "description": "Description of the switch tile where using the devices language can be enabled." + "@generatingRSAKeyPairFailed": { + "description": "Message for the rollout process" }, - "language": "Jazyk", - "@language": { - "description": "Title of language setting group." + "@goToSettingsButton": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." }, - "authenticateToShowOtp": "Pro zobrazení jednorázového kódu se přihlaste.", - "@authenticateToShowOtp": { - "description": "Reason to authenticate when trying to view a one time password." + "@goToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." }, - "authenticateToUnLockToken": "Pro změnu uzamčení tokenu se přihlaste.", - "@authenticateToUnLockToken": { - "description": "Reason to authenticate when trying to lock or unlock a token." + "@guide": { + "description": "Button to open the guide screen." }, - "biometricRequiredTitle": "Biometrické ověření není nastaveno", - "@biometricRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + "@handshakeFailed": { + "description": "Error message when the handshake failed." }, - "biometricHint": "Vyžadováno přihlášení", - "@biometricHint": { - "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + "@importedVia": { + "description": "Label for the import method of the token." }, - "biometricNotRecognized": "Ověření se nezdařilo. Zkuste to znovu.", - "@biometricNotRecognized": { - "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + "@internalServerError": { + "placeholders": { + "code": { + "example": "500" + } + } }, - "biometricSuccess": "Přihlášení bylo úspěšné", - "@biometricSuccess": { - "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + "@invalidValue": { + "description": "Error message when the value is not valid for the parameter.", + "placeholders": { + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "deviceCredentialsRequiredTitle": "Není nastaven zámek zařízení", - "@deviceCredentialsRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + "@invalidValueIn": { + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "deviceCredentialsSetupDescription": "Nastave zámek zařízení v nastavení zařízení", - "@deviceCredentialsSetupDescription": { - "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + "@isExpotableQuestion": { + "description": "Label for the question if the token is exportable." }, - "signInTitle": "Vyžadováno přihlášení", - "@signInTitle": { - "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + "@isPiTokenQuestion": { + "description": "Label for the question if the token is a privacyIDEA token." }, - "goToSettingsButton": "Otevřít nastavení", - "@goToSettingsButton": { - "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + "@language": { + "description": "Title of language setting group." }, - "goToSettingsDescription": "Není nastaveno přihlášení zámkem zařízení ani biometrické ověření. Aktivujte je v nastavení zařízení.", - "@goToSettingsDescription": { - "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." + "@legacySigningErrorMessage": { + "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." }, - "lockOut": "Biometrické ověření je deaktivováno. Pro aktivaci zamkněte a znovu odemkněte obrazovku/zařízení.", - "@lockOut": { - "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + "@legacySigningErrorTitle": { + "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", + "placeholders": { + "tokenLabel": { + "example": "PUSH1234A" + } + } }, - "authNotSupportedTitle": "Vyžadován zámek zařízení nebo biometrické ověření", - "@authNotSupportedTitle": { - "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." + "@lightTheme": { + "description": "The light theme." }, - "authNotSupportedBody": "Tato akce vyžaduje, aby bylo zařízení chráněno zámkem zařízení nebo biometrickým ověřením.", - "@authNotSupportedBody": { - "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." + "@linkedContainer": { + "description": "Label for the linked container serial number." }, - "lock": "Zamknout", "@lock": { "description": "Description of button that locks a token." }, - "unlock": "Odemknout", - "@unlock": { - "description": "Description of button that unlocks a token." + "@lockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." }, - "noResultTitle": "Nejsou nainstalovány žádné tokeny.", - "@noResultTitle": { - "description": "No tokens installed yet." + "@logMenu": { + "description": "Button to open the log menu." + }, + "@malformedData": { + "description": "Error message when the data is malformed." + }, + "@missingRequiredParameter": { + "description": "Error message when a required parameter is missing.", + "placeholders": { + "parameter": { + "example": "counter" + } + } + }, + "@missingRequiredParameterIn": { + "description": "Error message when a required parameter is missing in a specific map.", + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + } + } + }, + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, + "@name": { + "description": "Describes the field where the tokens name should be entered." + }, + "@noFbToken": { + "description": "Tells the user that there is no Firebase token available." + }, + "@noNetworkConnection": { + "description": "Tells the user that there is no network connection." }, - "noResultText1": "stiskněte tlačítko ", "@noResultText1": { "description": "first noresult text" }, - "noResultText2": " a začněte s používáním.", "@noResultText2": { "description": "second noresult text" }, - "onBoardingTitle1": "{appName}", + "@noResultTitle": { + "description": "No tokens installed yet." + }, + "@notAnInteger": { + "description": "Error message when the user entered a value that is not an integer." + }, + "@notAnNumber": { + "description": "Error message when the user entered a value that is not a number." + }, + "@ok": { + "description": "Button to confirm an action." + }, "@onBoardingTitle1": { "placeholders": { "appName": { @@ -383,296 +364,558 @@ } } }, - "onBoardingText1": "vícefázové ověření\nusnadněno", - "onBoardingTitle2": "Maximální Bezpečnost", - "onBoardingText2": "Uložte tokeny do svého zařízení\nchráněné biometrickým ověřením", - "onBoardingTitle3": "Navštivte náš profil Github", - "onBoardingText3": "Tuto aplikaci má open source", - "errorLogTitle": "Protokol chyb", - "logMenu": "Nabídka protokolu", - "showErrorLog": "Zobrazit", - "clearErrorLog": "Vymazat", - "send": "Odeslat", - "sendErrorLogDescription": "Vytvoří se připravený e-mail.\nObsahuje informace o aplikaci, chybě a zařízení.\nPřed odesláním můžete e-mail upravit.\nZde se můžete podívat, jak informace používáme:", - "@sendErrorLogDescription": { - "description": "Explanation for the user what he will send." + "@open": { + "description": "Button to open something." }, - "showPrivacyPolicy": "Zobrazit zásady ochrany osobních údajů", - "errorLogEmpty": "Protokol chyb je prázdný.", - "verboseLogging": "Zevrubné protokolování", - "errorLogCleared": "Protokol chyb vymazán.", - "ok": "Ok", - "errorMailBody": "Přiložen je soubor protokolu o chybách.\nTento text můžete nahradit dalšími informacemi o chybě.", - "@errorMailBody": { - "description": "Message for email body" + "@originApp": { + "description": "Label for the origin app." }, - "showDetails": "Zobrazit podrobnosti", - "open": "Otevřít", - "sendErrorDialogBody": "V aplikaci se vyskytla neznámá chyba. Informace uvedené níže mohou být odeslány vývojářům e-mailem pro vyřešení chyby v budoucnu.", - "@sendErrorDialogBody": { - "description": "Description shown to the user about what info the error report contains." + "@originDetails": { + "description": "Title of the origin details menu." }, - "noFbToken": "Není k dispozici žádný token Firebase.", - "firebaseToken": "Token Firebase", - "noPublicKey": "Není k dispozici žádný veřejný klíč.", - "publicKey": "Veřejný klíč", - "editToken": "Upravit token", - "edit": "Upravit", - "save": "Uložit", - "create": "Vytvořit", - "validFor": "Platné pro", - "validUntil": "Platné do", - "deleteLockedToken": "Prosím, autentifikujte se pro smazání uzamčeného tokenu.", - "editLockedToken": "Prosím, autentifikujte se pro úpravu uzamčeného tokenu.", - "expandLockedFolder": "Chcete-li otevřít uzamčenou složku, ověřte se.", - "renameTokenFolder": "Přejmenování složky", - "addANewFolder": "Vytvoření nové složky", - "folderName": "Název složky", - "retryRollout": "Zkusit znovu", - "generatingRSAKeyPair": "Generování párů klíčů RSA", - "@generatingRSAKeyPair": { - "description": "Message for the rollout process" + "@otpValueCopiedMessage": { + "description": "Tells the user that the otp value was copied to the clipboard.", + "placeholders": { + "otpValue": { + "example": "055374" + } + }, + "type": "text" }, - "generatingRSAKeyPairFailed": "Generování páru klíčů RSA se nezdařilo", - "@generatingRSAKeyPairFailed": { + "@parsingResponse": { "description": "Message for the rollout process" }, - "sendingRSAPublicKey": "Odeslání veřejného klíče RSA", - "@sendingRSAPublicKey": { + "@parsingResponseFailed": { "description": "Message for the rollout process" }, - "sendingRSAPublicKeyFailed": "Nepodařilo se odeslat veřejný klíč RSA", - "@sendingRSAPublicKeyFailed": { - "description": "Message for the rollout process" + "@period": { + "description": "Title of the dropdown button where the period of the totp token is selected." }, - "parsingResponse": "Rozbor odpovědi", - "@parsingResponse": { - "description": "Message for the rollout process" + "@phonePart": { + "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." }, - "parsingResponseFailed": "Parsování odpovědi se nezdařilo", - "@parsingResponseFailed": { - "description": "Message for the rollout process" + "@pleaseEnterANameForThisToken": { + "description": "Hint telling the user to enter a name for a token." }, - "rolloutCompleted": "Zavedení dokončeno", - "@rolloutCompleted": { - "description": "Message for the rollout process" + "@pleaseEnterASecretForThisToken": { + "description": "Hint telling the user to enter a secret for a token." }, - "errorRollOutNoConnectionToServer": "Registrace tokenu {name} selhala. Server není dostupný.", - "@errorRollOutNoConnectionToServer": { - "description": "Message for the rollout process", - "placeholders": { - "name": { - "example": "PUSH1234A" - } - } + "@pollingChallenges": { + "type": "text" }, - "authToAcceptPushRequest": "Pro přijetí požadavku na push notifikaci se přihlaste.", - "authToDeclinePushRequest": "Pro odmítnutí požadavku na push notifikaci se přihlaste.", - "pushRequestParseError": "Požadavek na odeslání se nepodařilo zpracovat.", - "imageUrl": "URL obrázku", - "errorRollOutSSLHandshakeFailed": "SSL handshake se nezdařil. Roll-out není možný.", - "errorWhenPullingChallenges": "Při dotazování na výzvy {name} došlo k chybě.", - "@errorWhenPullingChallenges": { + "@pollingFailed": { + "description": "Tells the user that the polling failed." + }, + "@pollingFailedFor": { + "description": "Tells the user that the polling failed.", "placeholders": { - "name": { + "serial": { "example": "PUSH1234A" } } }, - "couldNotConnectToServer": "Nepodařilo se připojit k serveru.", - "errorRollOutNotPossibleAnymore": "Roll-out tohoto tokenu již není možný.", - "errorTokenExpired": "Platnost tokenu {name} vypršela.", - "@errorTokenExpired": { - "placeholders": { - "name": { - "example": "PUSH1234A" + "@pushToken": { + "description": "Title for the settings block concerning the push tokens." + }, + "@rename": { + "description": "Label that describes renaming the token." + }, + "@renameToken": { + "description": "Title of the dialog where a new name for a token can be entered." + }, + "@renameTokenFolder": { + "description": "Title of the dialog where a new name for a token folder can be entered." + }, + "@requestInfo": { + "description": "Description of the authentication request.", + "placeholders": { + "account": { + "example": "GitHub" + }, + "issuer": { + "example": "privacyIDEA" } } }, - "yes": "Ano", - "no": "Ne", - "butDiscardIt": "ale zahodit jej", - "declineIt": "odmítnout jej ", - "requestTriggerdByUserQuestion": "Byl tento požadavek vyvolán vámi?", - "grantCameraPermissionDialogTitle": "Camera permission is not granted", - "grantCameraPermissionDialogContent": "Please grant camera permission to scan QR codes.", - "grantCameraPermissionDialogPermanentlyDenied": "Oprávnění kamery je trvale odepřeno. Udělte prosím oprávnění fotoaparátu v nastavení telefonu.", - "grantCameraPermissionDialogButton": "Udělit oprávnění", - "decryptErrorTitle": "Chyba dešifrování", - "decryptErrorContent": "Bohužel se aplikaci nepodařilo dešifrovat vaše tokeny. To znamená, že šifrovací klíč je poškozen. Můžete to zkusit znovu nebo odstranit data aplikace, čímž by došlo k odstranění tokenů v aplikaci.", - "decryptErrorButtonDelete": "Odstranit", - "decryptErrorButtonSendError": "Odeslat chybu", - "decryptErrorButtonRetry": "Opakování", - "decryptErrorDeleteConfirmationContent": "Jste si jisti, že chcete data aplikace odstranit?", - "hidePushTokens": "Skrýt push tokeny", - "hidePushTokensDescription": "Skrýt push tokeny ze seznamu tokenů. Tím se tokeny neodstraní a budou stále viditelné na samostatné obrazovce.", - "settingsGroupGeneral": "Obecné informace", - "licensesAndVersion": "Licence a verze", - "privacyPolicy": "Zásady ochrany osobních údajů", - "introScanQrCode": "Podporujeme všechny běžné dvoufaktorové autentizační tokeny a také tokeny privacyIDEA.", - "introAddTokenManually": "Pokud nechcete skenovat QR kód, můžete tokeny přidávat také ručně.", - "introTokenSwipe": "Přejetím po tokenech doleva zobrazíte dostupné akce.", - "introEditToken": "Zde můžete upravit název tokenu a zobrazit některé podrobnosti.", - "introLockToken": "To improve security even more, you can lock tokens. Then the token can only be used after authentication.", - "introDragToken": "Reorganizujte tokeny tak, že je na několik sekund stisknete a poté je přetáhnete na požadované místo.", - "introAddFolder": "Můžete vytvářet složky\npro uspořádání svých tokenů.", - "introPollForChallenges": "Můžete zkontrolovat nové výzvy přetažením seznamu tokenů dolů.", - "introHidePushTokens": "Vaše push tokeny jsou nyní skryté.\nNa obrazovce push tokenů je však stále vidíte.", - "legacySigningErrorTitle": "Při použití staršího tokenu došlo k chybě: {tokenLabel}", - "@legacySigningErrorTitle": { - "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", + "@requestPushChallengesPeriodically": { + "description": "The description of the polling feature." + }, + "@retry": { + "description": "Label for e.g. a button. Something is tried to be done again." + }, + "@rolloutCompleted": { + "description": "Message for the rollout process" + }, + "@scanQrCode": { + "description": "The button to scan otpauth qr-codes." + }, + "@secretIsRequired": { + "description": "Error message when the secret is missing." + }, + "@secretKey": { + "description": "Describes the field where the tokens secret should be entered." + }, + "@send": { + "description": "Button to send the error log." + }, + "@sendErrorDialogBody": { + "description": "Description shown to the user about what info the error report contains." + }, + "@sendErrorLogDescription": { + "description": "Explanation for the user what he will send." + }, + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." + }, + "@sendingRSAPublicKey": { + "description": "Message for the rollout process" + }, + "@sendingRSAPublicKeyFailed": { + "description": "Message for the rollout process" + }, + "@serverNotReachable": { + "description": "Tells the user that the server could not be reached." + }, + "@settings": { + "description": "Button to open the settings page." + }, + "@showDetails": { + "description": "Button to show details." + }, + "@showErrorLog": { + "description": "Button to show the error log." + }, + "@showPrivacyPolicy": { + "description": "Button to show the privacy policy." + }, + "@signInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "@someTokensDoNotSupportPolling": { + "description": "Tells the user, that the following tokens do not support polling.", + "type": "text" + }, + "@startRollout": { + "description": "Label that tells the user that the token is being rolled out." + }, + "@statusCode": { + "description": "Tells the user the status code of the error.", "placeholders": { - "tokenLabel": { - "example": "PUSH1234A" + "statusCode": { + "example": "400" } } }, - "legacySigningErrorMessage": "Token byl vytvořen v zastaralé verzi aplikace, což může vést k problémům při jeho používání.\nPokud problém přetrvává, doporučujeme vytvořit nový push token!", - "@legacySigningErrorMessage": { - "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." + "@sync": { + "description": "Text of button that is used to synchronize push tokens." }, - "selectImportSource": "Vyberte zdroj importu", - "selectImportType": "Jak chcete importovat žetony?", - "importTokens": "Importní token", - "importNTokens": "{count, plural, zero{Neimportujte žádné tokeny} one{Importovat jeden token} other{Importovat {count} tokenů}}", - "selectFile": "Vybrat soubor", - "decrypt": "Dešifrovat", - "tokensAreEncrypted": "Tokeny jsou zašifrované. Please enter the password to decrypt them.", - "tokensNotEncrypted": "Tokeny nejsou šifrované a lze je importovat přímo.", - "tokensSuccessfullyDecrypted": "Tokeny byly úspěšně dešifrovány a nyní je lze importovat.", - "password": "Heslo", - "wrongPassword": "Nesprávné heslo", - "qrScan": "Skenování", - "enterLink": "Zadejte odkaz", - "invalidBackupFile": "Vybraný soubor není platnou zálohou {appName}.", - "invalidQrScan": "Naskenovaný QR kód není platnou zálohou {appName}.", - "invalidQrFile": "Vybraný soubor neobsahuje platný QR kód z {appName}.", - "invalidLink": "Zadaný odkaz není platným tokenem {appName} nebo není podporován.", - "importFailedToken": "{count, plural, zero{Žádný token Nepodařilo se importovat.} one{Nepodařilo se importovat token.} other{Nepodařilo se importovat {count} tokenů.}}", - "importExistingToken": "{count, plural, zero{Nebyl nalezen žádný token, který by se již v aplikaci nacházel.} one{Byl nalezen token, který již v aplikaci existuje.} other{{count} byly nalezeny tokeny, které se již v aplikaci nacházejí.}}", - "importConflictToken": "{count, plural, zero{Není žádný konflikt s tokeny, které již existují.} one{Je konflikt s tokeny, které již existují.\nProsím, vyberte, který z nich chcete zachovat.} other{Je konflikt s tokeny, které již existují.\nProsím, vyberte, který z nich chcete zachovat.}}", - "importNewToken": "{count, plural, zero{Nebyl nalezen žádný nový token.} one{Byl nalezen nový token, který lze importovat.} other{Bylo nalezeno {count} nových tokenů, které lze importovat.}}", - "importHintPrivacyIdeaQrScan": "Chcete-li vytvořit QR kódy žetonů, přejděte do nastavení a klepněte na \"Export\". Poté vyberte \"Jako QR kód\" a klepněte na token, který chcete exportovat. Tato varianta je vhodná pouze pro přímý přenos do jiného zařízení, protože QR kód není šifrovaný.", - "importHintPrivacyIdeaFile": "Chcete-li vytvořit zálohu, přejděte do nastavení a klepněte na položku \"Export\". Vyberte \"Jako soubor\" a vyberte tokeny, které chcete exportovat. Potom klepněte na \"Exportovat\" a nastavte heslo. Úložištěm je složka pro stahování ve vašem zařízení.", - "importHint2FAS": "Vyberte zálohu 2FAS.\nPokud nemáte zálohu, vytvořte ji v aplikaci 2FAS. Doporučujeme použít heslo.", - "importHintAegisBackupFile": "Vyberte svůj export Aegis (.JSON).\nPokud nemáte export, vytvořte si jej prostřednictvím nabídky nastavení v aplikaci Aegis. Doporučujeme použít heslo.", - "importHintAegisQrScan": "Naskenujte QR kód, který obdržíte při přenosu záznamů z aplikace Aegis.", - "importHintAegisLink": "Zadejte odkaz, který obdržíte při přenosu záznamů ze systému Aegis.", - "importHintGoogleQrScan": "Naskenujte QR kód, který obdržíte při exportu účtů z Google Authenticator.", - "importHintGoogleQrFile": "Vyberte obrazový soubor s QR kódem, který obdržíte při exportu účtů z Google Authenticator.\n!! Upozorňujeme, že není bezpečné ukládat QR kód do zařízení, protože tokeny nejsou šifrovány !!", - "importHintAuthenticatorProFile": "Chcete-li vytvořit zálohu aplikace Authenticator Pro, přejděte do nastavení a klepněte na položku \"Automatické zálohování\". Vyberte umístění úložiště a nastavte heslo. Poté stiskněte \"Zálohovat nyní\" a exportujte tokeny.", - "importHintFreeOtpPlusQrScan": "Naskenujte QR kód, který obdržíte po stisknutí tří teček na dlaždici tokenu, a vyberte možnost \"Sdílet QR kód\".", - "importHintFreeOtpPlusFile": "Chcete-li vytvořit zálohu aplikace FreeOTP+, klepněte na tři tečky v pravém horním rohu a vyberte možnost \"Exportovat\". Můžete si vybrat mezi formátem JSON a URI. Zálohu doporučujeme po importu odstranit, protože není šifrovaná.", - "qrFileDecodeError": "Z vybraného obrázku nebylo možné dekódovat QR kód, použijte prosím místo toho skener QR kódů.", - "tokenLink": "Token link", - "feedback": "Zpětná vazba", - "feedbackTitle": "Vaše zpětná vazba je vždy vítána!", - "feedbackDescription": "Pokud máte nějaké dotazy, návrhy nebo problémy, dejte nám prosím vědět.", - "feedbackHint": "Otevře se připravený e-mail, který nám můžete zaslat. V případě potřeby budou doplněny informace o vašem zařízení a verzi aplikace. Před odesláním můžete e-mail zkontrolovat a upravit.", - "feedbackPrivacyPolicy1": "Odesláním zpětné vazby souhlasíte s našimi ", - "feedbackPrivacyPolicy2": "zásadami ochrany osobních údajů", - "@feedbackPrivacyPolicy2": { - "description": "Taping on this should open the privacy policy." + "@syncContainerFailed": { + "description": "Error message when synchronizing a container failed." }, - "feedbackPrivacyPolicy3": ".", - "addSystemInfo": "Přidat systémové informace", - "feedbackSentTitle": "Zpětná vazba odeslána", - "feedbackSentDescription": "Děkujeme vám za pomoc při vylepšování této aplikace!", - "patchNotesDialogTitle": "Co je nového?", - "version": "Verze", - "noMailAppTitle": "Není nainstalována žádná e-mailová aplikace", - "noMailAppDescription": "There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message.", - "authenticationRequest": "Žádost o ověření", - "requestInfo": "Odesláno {issuer} pro váš účet: \"{account}\"", - "@requestInfo": { + "@syncFbTokenFailed": { + "description": "Headline for the list of tokens where the synchronization failed." + }, + "@synchronizePushTokens": { + "description": "Title of synchronizing push tokens in settings." + }, + "@synchronizesTokensWithServer": { + "description": "Description of synchronizing push tokens in settings." + }, + "@synchronizingTokens": { + "description": "Title of the push synchronization dialog." + }, + "@systemTheme": { + "description": "The systems theme." + }, + "@theSecretDoesNotFitTheCurrentEncoding": { + "description": "Hint telling the user that the secret does not fit the selected encoding." + }, + "@theme": { + "description": "Title of the setting group where the theme can be selected." + }, + "@themeMode": { + "description": "Title of the setting group where the theme mode can be changed." + }, + "@timeOut": { + "description": "Error message when a request times out." + }, + "@tokenDataParseError": { + "description": "Error message when the token data could not be parsed." + }, + "@tokenDetails": { + "description": "Title of the token details menu." + }, + "@tokenSerial": { + "description": "Label for the token serial number." + }, + "@tokensDoNotSupportSynchronization": { + "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + }, + "@type": { + "description": "Title of the dropdown button where the type of the token is selected." + }, + "@unexpectedError": { + "description": "Title of page report mode." + }, + "@unknown": { + "description": "Tells that something is unknown." + }, + "@unlock": { + "description": "Description of button that unlocks a token." + }, + "@unsupported": { "placeholders": { - "issuer": { - "example": "privacyIDEA" + "name": { + "example": "piauth version" }, - "account": { - "example": "GitHub" + "value": { + "example": "5" } } }, - "errorUnlinkingPushToken": "Nepodařilo se odlinkovat push token {label}.", - "@errorUnlinkingPushToken": { - "description": "Error message when unlinking a push token failed.", + "@useDeviceLocaleDescription": { + "description": "Description of the switch tile where using the devices language can be enabled." + }, + "@useDeviceLocaleTitle": { + "description": "Title of the switch tile where using the devices language can be enabled." + }, + "@valueNotAllowed": { "placeholders": { - "label": { - "example": "PUSH1234A" + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "-1" } } }, - "pleaseSyncManuallyWhenNetworkIsAvailable": "Synchronizujte prosím push tokeny ručně prostřednictvím nastavení, když je k dispozici síťové připojení.", - "pushTokens": "Žetony Push", - "continueButton": "Pokračovat", - "addTokenManually": "Přidat token ručně", - "addFolder": "Přidat složku", - "searchTokens": "Hledat tokeny", - "closeSearchTokens": "Zavřít vyhledávání", - "increaseCounter": "Zvýšit počítadla", - "copyOTPToClipboard": "Zkopírovat OTP do schránky", - "licenses": "Licence", - "optionalMessage": "Volitelná zpráva", - "confirmation": "Potvrzení", - "askLogSendedDescription": "Odeslali jste protokol a chcete jej nyní vymazat?", - "algorithmUnsupported": "Algoritmus {algorithm} není podporován", - "@algorithmUnsupported": { + "@valueNotAllowedIn": { "placeholders": { - "algorithm": { - "example": "MD5" + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "-1" } } }, - "thisAppIsOpenSource": "Tato aplikace má otevřený zdrojový kód\nNavštivte nás na GitHub", - "invalidArgument": "{argument} není platná hodnota pro {type}", - "importExportTokens": "Import/Exportovat žetony", - "exportNonPrivacyIDEATokens": "Exportovat ne-privacyIDEA žetony", - "selectTokensToExport": "{count, plural, zero{} one{Vyberte žeton k exportu} other{Vyberte žetony k exportu}}", - "noTokenToExport": "Pro export není k dispozici žádný token", - "exportAllTokens": "Exportovat všechny žetony", - "export": "Export", - "exportingTokens": "Probíhá export žetonů...", - "exportTokens": "Exportovat žetony", - "enterPasswordToEncrypt": "Zadejte heslo pro šifrování žetonů. Toto heslo bude vyžadováno k importu žetonů.", - "exportLockedTokenReason": "Prosím, ověřte se, abyste mohli exportovat uzamčené žetony.", - "fileSavedToDownloadsFolder": "Soubor uložen do složky Stažené soubory", - "errorSavingFile": "Chyba při ukládání souboru", - "asQrCode": "Jako QR kód", + "@verboseLogging": { + "description": "Title of the switch tile where verbose logging can be enabled." + }, + "accept": "Přijmout", + "addANewFolder": "Vytvoření nové složky", + "addFolder": "Přidat složku", + "addSystemInfo": "Přidat systémové informace", + "addToken": "Přidat token", + "addTokenManually": "Přidat token ručně", + "algorithm": "Algoritmus", + "algorithmUnsupported": "Algoritmus {algorithm} není podporován", + "allTokensSynchronized": "Všechny tokeny jsou synchronizované.", "asFile": "Jako soubor", - "scanThisQrWithNewDevice": "Naskenujte tento QR kód svým novým zařízením pro import žetonu.", - "oneMore": "Ještě jeden", - "done": "Hotovo", + "asQrCode": "Jako QR kód", + "askLogSendedDescription": "Odeslali jste protokol a chcete jej nyní vymazat?", + "authNotSupportedBody": "Tato akce vyžaduje, aby bylo zařízení chráněno zámkem zařízení nebo biometrickým ověřením.", + "authNotSupportedTitle": "Vyžadován zámek zařízení nebo biometrické ověření", + "authToAcceptPushRequest": "Pro přijetí požadavku na push notifikaci se přihlaste.", + "authToDeclinePushRequest": "Pro odmítnutí požadavku na push notifikaci se přihlaste.", + "authenticateToShowOtp": "Pro zobrazení jednorázového kódu se přihlaste.", + "authenticateToUnLockToken": "Pro změnu uzamčení tokenu se přihlaste.", + "authenticationRequest": "Žádost o ověření", + "biometricHint": "Vyžadováno přihlášení", + "biometricNotRecognized": "Ověření se nezdařilo. Zkuste to znovu.", + "biometricRequiredTitle": "Biometrické ověření není nastaveno", + "biometricSuccess": "Přihlášení bylo úspěšné", + "butDiscardIt": "ale zahodit jej", + "cancel": "Zrušit", + "checkServerCertificate": "Zkontrolujte prosím certifikát serveru", + "checkYourNetwork": "Zkontrolujte prosím síťové připojení a zkuste to znovu.", + "clearErrorLog": "Vymazat", + "closeSearchTokens": "Zavřít vyhledávání", + "confirmDeletion": "Potvrdit smazání", + "confirmDeletionOf": "Opravdu chcete smazat token {name}?", + "confirmFolderDeletionHint": "Odstranění složky nemá žádný vliv na tokeny v ní.\nTokeny jsou přesunuty do hlavního seznamu.", "confirmPassword": "Potvrďte heslo", + "confirmTokenDeletionHint": "Pokud tento token odstraníte, nebude již možné se přihlásit.\nProsím, ujistěte se, že se můžete přihlásit k přidruženému účtu bez tohoto tokenu.", + "confirmation": "Potvrzení", + "connectionFailed": "Připojení se nezdařilo.", + "container": "Kontejner", + "continueButton": "Pokračovat", + "copyOTPToClipboard": "Zkopírovat OTP do schránky", + "couldNotConnectToServer": "Nepodařilo se připojit k serveru.", + "couldNotSignMessage": "Zprávu se nepodařilo podepsat.", + "counter": "Pult", + "create": "Vytvořit", + "createdAt": "Vytvořeno dne", + "creator": "Tvůrce", + "decline": "Odmítnout", + "declineIt": "odmítnout jej ", + "decrypt": "Dešifrovat", + "decryptErrorButtonDelete": "Odstranit", + "decryptErrorButtonRetry": "Opakování", + "decryptErrorButtonSendError": "Odeslat chybu", + "decryptErrorContent": "Bohužel se aplikaci nepodařilo dešifrovat vaše tokeny. To znamená, že šifrovací klíč je poškozen. Můžete to zkusit znovu nebo odstranit data aplikace, čímž by došlo k odstranění tokenů v aplikaci.", + "decryptErrorDeleteConfirmationContent": "Jste si jisti, že chcete data aplikace odstranit?", + "decryptErrorTitle": "Chyba dešifrování", + "delete": "Smazat", + "deleteLockedToken": "Prosím, autentifikujte se pro smazání uzamčeného tokenu.", + "deviceCredentialsRequiredTitle": "Není nastaven zámek zařízení", + "deviceCredentialsSetupDescription": "Nastave zámek zařízení v nastavení zařízení", + "digits": "Počet číslic", + "dismiss": "Zavřít", + "done": "Hotovo", + "edit": "Upravit", + "editLockedToken": "Prosím, autentifikujte se pro úpravu uzamčeného tokenu.", + "editToken": "Upravit token", + "enablePolling": "Povolit polling", + "encoding": "Kódování", + "enterDetailsForToken": "Vložte podrobnosti tokenu", + "enterLink": "Zadejte odkaz", + "enterPasswordToEncrypt": "Zadejte heslo pro šifrování žetonů. Toto heslo bude vyžadováno k importu žetonů.", + "errorLogCleared": "Protokol chyb vymazán.", + "errorLogEmpty": "Protokol chyb je prázdný.", + "errorLogTitle": "Protokol chyb", + "errorMailBody": "Přiložen je soubor protokolu o chybách.\nTento text můžete nahradit dalšími informacemi o chybě.", + "errorRollOutFailed": "Registrace tokenu {name} selhala.", + "errorRollOutNoConnectionToServer": "Registrace tokenu {name} selhala. Server není dostupný.", + "errorRollOutNotPossibleAnymore": "Roll-out tohoto tokenu již není možný.", + "errorRollOutSSLHandshakeFailed": "SSL handshake se nezdařil. Roll-out není možný.", + "errorRollOutUnknownError": "Vyskytla se neznámá chyba. Registrace není možná: {e}", + "errorSavingFile": "Chyba při ukládání souboru", + "errorSynchronizationNoNetworkConnection": "Synchronizace tokenů selhala, připojení k serveru privacyIDEA se nezdařilo.", + "errorTokenExpired": "Platnost tokenu {name} vypršela.", + "errorUnlinkingPushToken": "Nepodařilo se odlinkovat push token {label}.", + "errorWhenPullingChallenges": "Při dotazování na výzvy {name} došlo k chybě.", "exampleUrl": "Zadejte prosím platnou adresu URL, například: \"https://example.com/\"", - "pushEndpointUrl": "URL koncového bodu push", + "expandLockedFolder": "Chcete-li otevřít uzamčenou složku, ověřte se.", + "export": "Export", + "exportAllTokens": "Exportovat všechny žetony", + "exportLockedTokenReason": "Prosím, ověřte se, abyste mohli exportovat uzamčené žetony.", + "exportNonPrivacyIDEATokens": "Exportovat ne-privacyIDEA žetony", + "exportTokens": "Exportovat žetony", + "exportingTokens": "Probíhá export žetonů...", + "failedToLoad": "Nepodařilo se načíst:", + "feedback": "Zpětná vazba", + "feedbackDescription": "Pokud máte nějaké dotazy, návrhy nebo problémy, dejte nám prosím vědět.", + "feedbackHint": "Otevře se připravený e-mail, který nám můžete zaslat. V případě potřeby budou doplněny informace o vašem zařízení a verzi aplikace. Před odesláním můžete e-mail zkontrolovat a upravit.", + "feedbackPrivacyPolicy1": "Odesláním zpětné vazby souhlasíte s našimi ", + "feedbackPrivacyPolicy2": "zásadami ochrany osobních údajů", + "feedbackPrivacyPolicy3": ".", + "feedbackSentDescription": "Děkujeme vám za pomoc při vylepšování této aplikace!", + "feedbackSentTitle": "Zpětná vazba odeslána", + "feedbackTitle": "Vaše zpětná vazba je vždy vítána!", + "fileSavedToDownloadsFolder": "Soubor uložen do složky Stažené soubory", + "findingQrCodeInImage": "Hledání QR kódu v obrázku...", + "firebaseToken": "Token Firebase", + "folderName": "Název složky", + "generatingPhonePart": "Generování klientské části", + "generatingRSAKeyPair": "Generování párů klíčů RSA", + "generatingRSAKeyPairFailed": "Generování páru klíčů RSA se nezdařilo", + "goToSettingsButton": "Otevřít nastavení", + "goToSettingsDescription": "Není nastaveno přihlášení zámkem zařízení ani biometrické ověření. Aktivujte je v nastavení zařízení.", + "grantCameraPermissionDialogButton": "Udělit oprávnění", + "grantCameraPermissionDialogContent": "Please grant camera permission to scan QR codes.", + "grantCameraPermissionDialogPermanentlyDenied": "Oprávnění kamery je trvale odepřeno. Udělte prosím oprávnění fotoaparátu v nastavení telefonu.", + "grantCameraPermissionDialogTitle": "Camera permission is not granted", + "handshakeFailed": "Handshake se nezdařil", + "hidePushTokens": "Skrýt push tokeny", + "hidePushTokensDescription": "Skrýt push tokeny ze seznamu tokenů. Tím se tokeny neodstraní a budou stále viditelné na samostatné obrazovce.", + "imageUrl": "URL obrázku", + "importConflictToken": "{count, plural, zero{Není žádný konflikt s tokeny, které již existují.} one{Je konflikt s tokeny, které již existují.\nProsím, vyberte, který z nich chcete zachovat.} other{Je konflikt s tokeny, které již existují.\nProsím, vyberte, který z nich chcete zachovat.}}", + "importExistingToken": "{count, plural, zero{Nebyl nalezen žádný token, který by se již v aplikaci nacházel.} one{Byl nalezen token, který již v aplikaci existuje.} other{{count} byly nalezeny tokeny, které se již v aplikaci nacházejí.}}", + "importExportTokens": "Import/Exportovat žetony", + "importFailedToken": "{count, plural, zero{Žádný token Nepodařilo se importovat.} one{Nepodařilo se importovat token.} other{Nepodařilo se importovat {count} tokenů.}}", + "importHint2FAS": "Vyberte zálohu 2FAS.\nPokud nemáte zálohu, vytvořte ji v aplikaci 2FAS. Doporučujeme použít heslo.", + "importHintAegisBackupFile": "Vyberte svůj export Aegis (.JSON).\nPokud nemáte export, vytvořte si jej prostřednictvím nabídky nastavení v aplikaci Aegis. Doporučujeme použít heslo.", + "importHintAegisLink": "Zadejte odkaz, který obdržíte při přenosu záznamů ze systému Aegis.", + "importHintAegisQrScan": "Naskenujte QR kód, který obdržíte při přenosu záznamů z aplikace Aegis.", + "importHintAuthenticatorProFile": "Chcete-li vytvořit zálohu aplikace Authenticator Pro, přejděte do nastavení a klepněte na položku \"Automatické zálohování\". Vyberte umístění úložiště a nastavte heslo. Poté stiskněte \"Zálohovat nyní\" a exportujte tokeny.", + "importHintFreeOtpPlusFile": "Chcete-li vytvořit zálohu aplikace FreeOTP+, klepněte na tři tečky v pravém horním rohu a vyberte možnost \"Exportovat\". Můžete si vybrat mezi formátem JSON a URI. Zálohu doporučujeme po importu odstranit, protože není šifrovaná.", + "importHintFreeOtpPlusQrScan": "Naskenujte QR kód, který obdržíte po stisknutí tří teček na dlaždici tokenu, a vyberte možnost \"Sdílet QR kód\".", + "importHintGoogleQrFile": "Vyberte obrazový soubor s QR kódem, který obdržíte při exportu účtů z Google Authenticator.\n!! Upozorňujeme, že není bezpečné ukládat QR kód do zařízení, protože tokeny nejsou šifrovány !!", + "importHintGoogleQrScan": "Naskenujte QR kód, který obdržíte při exportu účtů z Google Authenticator.", + "importHintPrivacyIdeaFile": "Chcete-li vytvořit zálohu, přejděte do nastavení a klepněte na položku \"Export\". Vyberte \"Jako soubor\" a vyberte tokeny, které chcete exportovat. Potom klepněte na \"Exportovat\" a nastavte heslo. Úložištěm je složka pro stahování ve vašem zařízení.", + "importHintPrivacyIdeaQrScan": "Chcete-li vytvořit QR kódy žetonů, přejděte do nastavení a klepněte na \"Export\". Poté vyberte \"Jako QR kód\" a klepněte na token, který chcete exportovat. Tato varianta je vhodná pouze pro přímý přenos do jiného zařízení, protože QR kód není šifrovaný.", + "importNTokens": "{count, plural, zero{Neimportujte žádné tokeny} one{Importovat jeden token} other{Importovat {count} tokenů}}", + "importNewToken": "{count, plural, zero{Nebyl nalezen žádný nový token.} one{Byl nalezen nový token, který lze importovat.} other{Bylo nalezeno {count} nových tokenů, které lze importovat.}}", + "importTokens": "Importní token", + "importedVia": "Dovezeno prostřednictvím", + "increaseCounter": "Zvýšit počítadla", + "internalServerError": "Interní chyba serveru ({code})", + "introAddFolder": "Můžete vytvářet složky\npro uspořádání svých tokenů.", + "introAddTokenManually": "Pokud nechcete skenovat QR kód, můžete tokeny přidávat také ručně.", + "introDragToken": "Reorganizujte tokeny tak, že je na několik sekund stisknete a poté je přetáhnete na požadované místo.", + "introEditToken": "Zde můžete upravit název tokenu a zobrazit některé podrobnosti.", + "introHidePushTokens": "Vaše push tokeny jsou nyní skryté.\nNa obrazovce push tokenů je však stále vidíte.", + "introLockToken": "To improve security even more, you can lock tokens. Then the token can only be used after authentication.", + "introPollForChallenges": "Můžete zkontrolovat nové výzvy přetažením seznamu tokenů dolů.", + "introScanQrCode": "Podporujeme všechny běžné dvoufaktorové autentizační tokeny a také tokeny privacyIDEA.", + "introTokenSwipe": "Přejetím po tokenech doleva zobrazíte dostupné akce.", + "invalidBackupFile": "Vybraný soubor není platnou zálohou {appName}.", + "invalidLink": "Zadaný odkaz není platným tokenem {appName} nebo není podporován.", + "invalidQrFile": "Vybraný soubor neobsahuje platný QR kód z {appName}.", + "invalidQrScan": "Naskenovaný QR kód není platnou zálohou {appName}.", + "invalidValue": "untranslated", + "invalidValueIn": "untranslated", + "isExpotableQuestion": "Lze exportovat?", + "isPiTokenQuestion": "Je to token privacyIDEA?", + "language": "Jazyk", + "legacySigningErrorMessage": "Token byl vytvořen v zastaralé verzi aplikace, což může vést k problémům při jeho používání.\nPokud problém přetrvává, doporučujeme vytvořit nový push token!", + "legacySigningErrorTitle": "Při použití staršího tokenu došlo k chybě: {tokenLabel}", + "licenses": "Licence", + "licensesAndVersion": "Licence a verze", + "linkedContainer": "Propojený kontejner", + "lock": "Zamknout", + "lockOut": "Biometrické ověření je deaktivováno. Pro aktivaci zamkněte a znovu odemkněte obrazovku/zařízení.", + "logMenu": "Nabídka protokolu", + "malformedData": "Data nejsou ve správném formátu", + "markQrCode": "Označte QR kód", + "missingRequiredParameter": "Hodnota parametru [{parameter}] je povinná, ale chybí.", + "missingRequiredParameterIn": "Hodnota parametru [{parameter}] je povinná, ale v \"{map}\" chybí.", "mustNotBeEmpty": "{field} nesmí být prázdné", - "@mustNotBeEmpty": { - "placeholders": { - "field": { - "example": "Name" - } - } - }, - "sendPushRequestResponseFailed": "Odpověď se nepodařilo odeslat.", - "@sendPushRequestResponseFailed": { - "description": "Error message when the response to a push request could not be sent." - }, + "name": "Název", + "no": "Ne", + "noFbToken": "Není k dispozici žádný token Firebase.", + "noMailAppDescription": "There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message.", + "noMailAppTitle": "Není nainstalována žádná e-mailová aplikace", + "noNetworkConnection": "Žádné připojení k síti.", + "noPublicKey": "Není k dispozici žádný veřejný klíč.", + "noResultText1": "stiskněte tlačítko ", + "noResultText2": " a začněte s používáním.", + "noResultTitle": "Nejsou nainstalovány žádné tokeny.", + "noTokenToExport": "Pro export není k dispozici žádný token", + "notAnInteger": "Hodnota není celé číslo.", + "notAnNumber": "Hodnota není číslo.", + "ok": "Ok", + "oneMore": "Ještě jeden", + "open": "Otevřít", + "optionalMessage": "Volitelná zpráva", + "originApp": "Aplikace Origin", + "originDetails": "Podrobnosti o původu", + "otpValueCopiedMessage": "Heslo \"{otpValue}\" bylo zkopírováno do schránky.", + "parsingResponse": "Rozbor odpovědi", + "parsingResponseFailed": "Parsování odpovědi se nezdařilo", + "password": "Heslo", "passwordCannotBeEmpty": "Heslo nesmí být prázdné", - "passwordMustBeAtLeast8Characters": "Heslo musí obsahovat alespoň 8 znaků", "passwordCannotContainWhitespace": "Heslo nesmí obsahovat mezery", + "passwordMustBeAtLeast8Characters": "Heslo musí obsahovat alespoň 8 znaků", "passwordMustContainLowercaseLetter": "Heslo musí obsahovat malé písmeno", - "passwordMustContainUppercaseLetter": "Heslo musí obsahovat velké písmeno", "passwordMustContainNumber": "Heslo musí obsahovat číslo", "passwordMustContainSpecialCharacter": "Heslo musí obsahovat speciální znak", + "passwordMustContainUppercaseLetter": "Heslo musí obsahovat velké písmeno", "passwordsDoNotMatch": "Hesla se neshodují", - "selectTokensToExportHelpTitle": "Není váš token uveden?", - "selectTokensToExportHelpContent": "Pokud token není uveden v seznamu, není zaručeno, že se nejedná o token privacyIDEA.\nV současné době lze exportovat pouze ručně přidané a importované tokeny.", - "findingQrCodeInImage": "Hledání QR kódu v obrázku...", - "qrNotFound": "Žádný QR kód nebyl nalezen!", + "patchNotesBugFixes": "Opravy chyb", + "patchNotesDialogTitle": "Co je nového?", + "patchNotesImprovements": "Improvements", + "patchNotesNewFeatures": "Nové funkce", + "patchNotesV4_3_0NewFeatures1": "Přidána podpora pro import tokenů z Google, Aegis a 2FAS Authenticator. Další zdroje importu budou přidány v budoucnu.", + "patchNotesV4_3_0NewFeatures2": "Do nastavení byla přidána možnost zpětné vazby.", + "patchNotesV4_3_0NewFeatures3": "Tokeny Push lze nyní skrýt ze seznamu tokenů.", + "patchNotesV4_3_0NewFeatures4": "Byly přidány úvodní informace, které novým uživatelům usnadní začátky.", + "patchNotesV4_3_0NewFeatures5": "Žetony nyní můžete vyhledávat klepnutím na lupu v pravém horním rohu.", + "patchNotesV4_3_0NewFeatures6": "Přidán token HomeWidget pro systém Android 12 a novější.", + "patchNotesV4_3_1BugFix1": "Opraven problém, kdy nebyla zobrazena hodnota otp po ověření na některých zařízeních.", + "patchNotesV4_3_1Improvement1": "Skener QR kódů byl vylepšen.", + "patchNotesV4_4_0Improvement1": "Byly přidány další dovozní zdroje.", + "patchNotesV4_4_0Improvement2": "Bylo vylepšeno rozpoznávání QR kódů z obrazových souborů.", + "patchNotesV4_4_0NewFeatures1": "Nyní je možné exportovat tokeny, u kterých lze zajistit, že se nejedná o tokeny privacyIDEA. V současné době nelze vyloučit, že tokeny přidané prostřednictvím čtečky QR kódů pocházejí z aplikace privacyIDEA. Rozlišování bude v budoucích verzích vylepšeno.", + "patchNotesV4_4_0NewFeatures2": "Přidána podpora pro \"require presence\" aplikace privacyIDEA.", + "period": "Časový interval", + "phonePart": "Klientská část:", + "pleaseEnterANameForThisToken": "Vložte název pro tento token.", + "pleaseEnterASecretForThisToken": "Vložte tajný klíč pro tento token.", + "pleaseSyncManuallyWhenNetworkIsAvailable": "Synchronizujte prosím push tokeny ručně prostřednictvím nastavení, když je k dispozici síťové připojení.", + "pollingChallenges": "Čekám na nové požadavky", + "pollingFailed": "Dotaz se nezdařil.", + "pollingFailedFor": "Dotaz na {serial} se nezdařil.", + "privacyPolicy": "Zásady ochrany osobních údajů", + "publicKey": "Veřejný klíč", + "pushEndpointUrl": "URL koncového bodu push", + "pushRequestParseError": "Požadavek na odeslání se nepodařilo zpracovat.", + "pushToken": "Push notifikace", + "pushTokens": "Žetony Push", + "qrFileDecodeError": "Z vybraného obrázku nebylo možné dekódovat QR kód, použijte prosím místo toho skener QR kódů.", "qrInFileNotFound": "Ve vybraném obrázku nebyl nalezen žádný QR kód.", "qrInFileNotFound2": "Můžete mi ukázat, kde se QR kód nachází.", "qrInFileNotFound3": "Předpokládám, že kód najdu, pokud se nachází uprostřed označené oblasti.", - "markQrCode": "Označte QR kód", - "malformedData": "Data nejsou ve správném formátu" + "qrNotFound": "Žádný QR kód nebyl nalezen!", + "qrScan": "Skenování", + "rename": "Přejmenovat", + "renameToken": "Přejmenovat token", + "renameTokenFolder": "Přejmenování složky", + "requestInfo": "Odesláno {issuer} pro váš účet: \"{account}\"", + "requestPushChallengesPeriodically": "Periodicky získávat výzvy ze serveru. Povolte pokud nefunguje příjem push notifikací.", + "requestTriggerdByUserQuestion": "Byl tento požadavek vyvolán vámi?", + "retry": "Zkusit znovu", + "retryRollout": "Zkusit znovu", + "rolloutCompleted": "Zavedení dokončeno", + "save": "Uložit", + "scanQrCode": "Naskenovat QR kód", + "scanThisQrWithNewDevice": "Naskenujte tento QR kód svým novým zařízením pro import žetonu.", + "searchTokens": "Hledat tokeny", + "secretIsRequired": "Tajné je vyžadováno", + "secretKey": "Tajný klíč", + "selectFile": "Vybrat soubor", + "selectImportSource": "Vyberte zdroj importu", + "selectImportType": "Jak chcete importovat žetony?", + "selectTokensToExport": "{count, plural, zero{} one{Vyberte žeton k exportu} other{Vyberte žetony k exportu}}", + "selectTokensToExportHelpContent": "Pokud token není uveden v seznamu, není zaručeno, že se nejedná o token privacyIDEA.\nV současné době lze exportovat pouze ručně přidané a importované tokeny.", + "selectTokensToExportHelpTitle": "Není váš token uveden?", + "send": "Odeslat", + "sendErrorDialogBody": "V aplikaci se vyskytla neznámá chyba. Informace uvedené níže mohou být odeslány vývojářům e-mailem pro vyřešení chyby v budoucnu.", + "sendErrorLogDescription": "Vytvoří se připravený e-mail.\nObsahuje informace o aplikaci, chybě a zařízení.\nPřed odesláním můžete e-mail upravit.\nZde se můžete podívat, jak informace používáme:", + "sendPushRequestResponseFailed": "Odpověď se nepodařilo odeslat.", + "sendingRSAPublicKey": "Odeslání veřejného klíče RSA", + "sendingRSAPublicKeyFailed": "Nepodařilo se odeslat veřejný klíč RSA", + "serverNotReachable": "Na server se nepodařilo dovolat.", + "settings": "Nastavení", + "settingsGroupGeneral": "Obecné informace", + "showDetails": "Zobrazit podrobnosti", + "showErrorLog": "Zobrazit", + "showPrivacyPolicy": "Zobrazit zásady ochrany osobních údajů", + "signInTitle": "Vyžadováno přihlášení", + "someTokensDoNotSupportPolling": "Některé tokeny jsou zastaralé a nepodporují polling", + "startRollout": "Začít zavádění", + "statusCode": "Stavový kód: {statusCode}", + "sync": "Synchronizovat", + "syncContainerFailed": "Synchronizace kontejneru se nezdařila", + "syncFbTokenFailed": "Synchronizace následujících tokenů selhala, zkuste to znovu:", + "synchronizePushTokens": "Synchronizace push tokenů", + "synchronizesTokensWithServer": "Synchronizovat tokeny se serverem privacyIDEA.", + "synchronizingTokens": "Tokeny se synchronizují.", + "theSecretDoesNotFitTheCurrentEncoding": "Tajný klíč neodpovídá zvolenému kódování.", + "themeMode": "Vzhled", + "thisAppIsOpenSource": "Tato aplikace má otevřený zdrojový kód\nNavštivte nás na GitHub", + "timeOut": "Časový limit", + "tokenDataParseError": "Tokenová data nelze analyzovat", + "tokenDetails": "Podrobnosti o tokenu", + "tokenLink": "Token link", + "tokenSerial": "Token serial", + "tokensAreEncrypted": "Tokeny jsou zašifrované. Please enter the password to decrypt them.", + "tokensDoNotSupportSynchronization": "Následující tokeny nepodporují synchronizaci a musí být znovu zaregistrovány:", + "tokensNotEncrypted": "Tokeny nejsou šifrované a lze je importovat přímo.", + "tokensSuccessfullyDecrypted": "Tokeny byly úspěšně dešifrovány a nyní je lze importovat.", + "type": "Typ", + "unexpectedError": "Nastala neočekávaná chyba.", + "unknown": "Neznámý", + "unlock": "Odemknout", + "unsupported": "Příkaz {name} [{value}] není touto verzí aplikace podporován.", + "useDeviceLocaleDescription": "Použít jazyk zařízení, pokud je podporován, případně angličtinu.", + "useDeviceLocaleTitle": "Použít jazyk zařízení", + "validFor": "Platné pro", + "validUntil": "Platné do", + "valueNotAllowed": "untranslated", + "valueNotAllowedIn": "untranslated", + "verboseLogging": "Zevrubné protokolování", + "version": "Verze", + "wrongPassword": "Nesprávné heslo", + "yes": "Ano" } \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9e96c589f..a3eb80ab0 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1,374 +1,362 @@ { - "@@last_modified": "2023-08-07", - "patchNotesNewFeatures": "Neue Funktionen", - "patchNotesImprovements": "Verbesserungen", - "patchNotesBugFixes": "Fehlerbehebungen", - "patchNotesV4_4_0NewFeatures1": "Es ist nun möglich, Token zu exportieren, bei denen sichergestellt werden kann, dass es sich nicht um privacyIDEA Token handelt. Derzeit kann nicht ausgeschlossen werden, dass über den QR-Code-Scanner hinzugefügte Token von privacyIDEA stammen. Die Differenzierung wird in zukünftigen Versionen verbessert.", - "patchNotesV4_4_0NewFeatures2": "Unterstützung für privacyIDEA's \"require presence\" hinzugefügt.", - "patchNotesV4_4_0Improvement1": "Es wurden weitere Importquellen hinzugefügt.", - "patchNotesV4_4_0Improvement2": "Die erkennung von QR-Codes aus Bilddateien wurde verbessert.", - "patchNotesV4_3_1BugFix1": "Ein Problem wurde behoben, bei dem der otp-Wert nach der Authentifizierung auf einigen Geräten nicht angezeigt wurde.", - "patchNotesV4_3_1Improvement1": "Der QR-Code-Scanner wurde verbessert.", - "patchNotesV4_3_0NewFeatures1": "Unterstützung für den Import von Token von Google, Aegis und 2FAS Authenticator hinzugefügt. Weitere Importquellen werden in Zukunft hinzugefügt.", - "patchNotesV4_3_0NewFeatures2": "Feedback-Option zu den Einstellungen hinzugefügt.", - "patchNotesV4_3_0NewFeatures3": "Push-Token können jetzt aus der Token-Liste ausgeblendet werden.", - "patchNotesV4_3_0NewFeatures4": "Es wurden Einführungen hinzugefügt, um neuen Benutzern den Einstieg zu erleichtern.", - "patchNotesV4_3_0NewFeatures5": "Sie können jetzt nach Token suchen, indem Sie auf die Lupe in der oberen rechten Ecke tippen.", - "patchNotesV4_3_0NewFeatures6": "Ab Android 12 kann für einen Token ein Widget auf dem Homescreen erstellt werden.", - "guide": "Anleitung", - "@guide": { - "description": "Button to open the guide screen." - }, - "retry": "Erneut versuchen", - "@retry": { - "description": "Label for e.g. a button. Something is tried to be done again." - }, - "accept": "Akzeptieren", + "@@last_modified": "2024-09-20", + "@@locale": "de", "@accept": { "description": "Label for e.g. a button. Something gets accepted by the user." }, - "decline": "Ablehnen", - "@decline": { - "description": "Label for e.g. a button. Something gets declined by the user." - }, - "name": "Name", - "@name": { - "description": "Describes the field where the tokens name should be entered." - }, - "secretKey": "Geheimer Schlüssel", - "@secretKey": { - "description": "Describes the field where the tokens secret should be entered." - }, - "encoding": "Kodierung", - "@encoding": { - "description": "Title of the dropdown button where the encoding is selected." + "@addToken": { + "description": "The button to open the screen to add tokens by hand." }, - "algorithm": "Algorithmus", "@algorithm": { "description": "Title of the dropdown button where the encoding is selected." }, - "digits": "Ziffern", - "@digits": { - "description": "Title of the dropdown button where the number of digits for the opt value is selected." + "@algorithmUnsupported": { + "placeholders": { + "algorithm": { + "example": "MD5" + } + } }, - "type": "Art", - "@type": { - "description": "Title of the dropdown button where the type of the token is selected." + "@allTokensSynchronized": { + "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "period": "Periode", - "@period": { - "description": "Title of the dropdown button where the period of the totp token is selected." + "@authNotSupportedBody": { + "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." }, - "rename": "Umbenennen", - "@rename": { - "description": "Label that describes renaming the token." + "@authNotSupportedTitle": { + "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." }, - "cancel": "Abbrechen", - "@cancel": { - "description": "Button to cancel an action." + "@authToAcceptPushRequest": { + "description": "Message to accepting a push request for authentication." }, - "delete": "Löschen", - "@delete": { - "description": "Label that describes deleting the token." + "@authToDeclinePushRequest": { + "description": "Message for declining a push request for authentication." }, - "dismiss": "Schließen", - "@dismiss": { - "description": "Text of a button that closes a dialog." + "@authenticateToShowOtp": { + "description": "Reason to authenticate when trying to view a one time password." }, - "addToken": "Token hinzufügen", - "@addToken": { - "description": "The button to open the screen to add tokens by hand." + "@authenticateToUnLockToken": { + "description": "Reason to authenticate when trying to lock or unlock a token." }, - "scanQrCode": "QR-Code scannen", - "@scanQrCode": { - "description": "The button to scan otpauth qr-codes." + "@biometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." }, - "enterDetailsForToken": "Neuen Token konfigurieren", - "@enterDetailsForToken": { - "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." + "@biometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterANameForThisToken": "Bitte geben Sie einen Namen ein.", - "@pleaseEnterANameForThisToken": { - "description": "Hint telling the user to enter a name for a token." + "@biometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterASecretForThisToken": "Bitte geben Sie ein Geheimnis ein.", - "@pleaseEnterASecretForThisToken": { - "description": "Hint telling the user to enter a secret for a token." + "@biometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." }, - "theSecretDoesNotFitTheCurrentEncoding": "Das Geheimnis entspricht nicht der gewählten Verschlüsselung.", - "@theSecretDoesNotFitTheCurrentEncoding": { - "description": "Hint telling the user that the secret does not fit the selected encoding." + "@cancel": { + "description": "Button to cancel an action." }, - "renameToken": "Token umbenennen", - "@renameToken": { - "description": "Title of the dialog where a new name for a token can be entered." + "@checkServerCertificate": { + "description": "Error message when the server certificate should be checked." + }, + "@checkYourNetwork": { + "description": "Tells the user to check the network connection." + }, + "@clearErrorLog": { + "description": "Button to clear the error log." }, - "confirmDeletion": "Löschen bestätigen", "@confirmDeletion": { "description": "Title of the dialog where a token can be deleted." }, - "confirmDeletionOf": "Sind Sie sicher dass Sie {name} löschen möchten?", "@confirmDeletionOf": { "description": "Asks for confirmation on deleting a token.", "placeholders": { "name": { "example": "PUSH1234" } - } - }, - "confirmTokenDeletionHint": "Unter Umständen können Sie sich nicht mehr einloggen, wenn Sie diesen Token löschen.\nBitte stellen Sie sicher, dass Sie sich ohne diesen Token in den dazugehörigen Account einloggen können.", - "@confirmTokenDeletionHint": { - "description": "Gives the user a hint about the consequences of deleting a token." + }, + "type": "text" }, - "confirmFolderDeletionHint": "Das Löschen eines Ordners hat keine Auswirkungen auf die Token, die sich darin befinden.\nDie Token werden in die Hauptliste verschoben.", "@confirmFolderDeletionHint": { "description": "Gives the user a hint about the consequences of deleting a folder." }, - "generatingPhonePart": "Generiere Telefonanteil", - "@generatingPhonePart": { - "description": "Title of a dialog telling the user that the phone part gets generated right now." + "@confirmTokenDeletionHint": { + "description": "Gives the user a hint about the consequences of deleting a token." }, - "phonePart": "Telefonanteil:", - "@phonePart": { - "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." + "@connectionFailed": { + "description": "Tells the user that the connection failed." }, - "otpValueCopiedMessage": "Passwort \"{otpValue}\" wurde in Zwischenablage kopiert.", - "@otpValueCopiedMessage": { - "description": "Tells the user that the otp value was copied to the clipboard.", - "placeholders": { - "otpValue": { - "example": "055374" - } - } + "@container": { + "description": "Title for the container view." }, - "settings": "Einstellungen", - "@settings": { - "description": "Button to open the settings page." + "@couldNotSignMessage": { + "description": "Tells the user that the message could not be signed." }, - "pushToken": "Push Token", - "@pushToken": { - "description": "Title for the settings block concerning the push tokens." + "@counter": { + "description": "Describes the field where the tokens counter should be entered." }, - "theme": "Farbschema", - "@theme": { - "description": "Title of the setting group where the theme can be selected." + "@createdAt": { + "description": "Label for the creation date of the token." }, - "lightTheme": "Hell", - "@lightTheme": { - "description": "The light theme." + "@creator": { + "description": "Label for the creator of the token." }, - "darkTheme": "Dunkel", "@darkTheme": { "description": "The dark theme." }, - "systemTheme": "Nutze Farbschema des Geräts", - "@systemTheme": { - "description": "The systems theme." + "@decline": { + "description": "Label for e.g. a button. Something gets declined by the user." }, - "someTokensDoNotSupportPolling": "Einige der Token sind veraltet und unterstützen keine aktiven Anfragen", - "@someTokensDoNotSupportPolling": { - "description": "Tells the user, that the following tokens do not support polling.", - "type": "text", - "placeholders": {} + "@delete": { + "description": "Label that describes deleting the token." }, - "enablePolling": "Aktives Stellen von Push-Anfragen", - "@enablePolling": { - "description": "Name of the setting switch that enables polling." + "@deviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." }, - "requestPushChallengesPeriodically": "Fordert regelmäßig Push-Anfragen vom Server an. Aktivieren Sie diese Funktion, wenn Nachrichten ansonsten nicht erhalten werden.", - "@requestPushChallengesPeriodically": { - "description": "The description of the polling feature." + "@deviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." }, - "synchronizePushTokens": "Synchronisiere Push Token", - "@synchronizePushTokens": { - "description": "Title of synchronizing push tokens in settings." + "@digits": { + "description": "Title of the dropdown button where the number of digits for the opt value is selected." }, - "synchronizesTokensWithServer": "Synchronisiert Token mit dem privacyIDEA Server.", - "@synchronizesTokensWithServer": { - "description": "Description of synchronizing push tokens in settings." + "@dismiss": { + "description": "Text of a button that closes a dialog." }, - "sync": "Sync", - "@sync": { - "description": "Text of button that is used to synchronize push tokens." + "@enablePolling": { + "description": "Name of the setting switch that enables polling." }, - "synchronizingTokens": "Synchronisiere Token.", - "@synchronizingTokens": { - "description": "Title of the push synchronization dialog." + "@encoding": { + "description": "Title of the dropdown button where the encoding is selected." }, - "allTokensSynchronized": "Alle Token wurden synchronisiert.", - "@allTokensSynchronized": { - "description": "Content of the push synchronization dialog. Signaling the user that everything worked." + "@enterDetailsForToken": { + "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." }, - "syncFbTokenFailed": "Synchronisation ist für die folgenden Token fehlgeschlagen:", - "@syncFbTokenFailed": { - "description": "Headline for the list of tokens where the synchronization failed." + "@errorLogCleared": { + "description": "Message that tells the user that the error log was cleared." }, - "tokensDoNotSupportSynchronization": "Die folgenden Token unterstützen keine Synchronisation und müssen erneut ausgerollt werden:", - "@tokensDoNotSupportSynchronization": { - "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + "@errorLogEmpty": { + "description": "Message that tells the user that the error log is empty." + }, + "@errorLogTitle": { + "description": "Title of the error log screen." + }, + "@errorMailBody": { + "description": "Message for email body" }, - "errorRollOutFailed": "Ausrollen von {name} ist fehlgeschlagen.", "@errorRollOutFailed": { "description": "Tells the user that the token could not be rolled out, because a network error occurred.", "placeholders": { "name": { "example": "PUSH1234A" } - } + }, + "type": "text" }, - "statusCode": "Statuscode: {statusCode}", - "@statusCode": { - "description": "Tells the user the status code of the error.", + "@errorRollOutNoConnectionToServer": { + "description": "Message for the rollout process", "placeholders": { - "statusCode": { - "example": "400" + "name": { + "example": "PUSH1234A" } } }, - "errorSynchronizationNoNetworkConnection": "Die Synchronisation ist fehlgeschlagen, da der privacyIDEA Server nicht erreicht werden konnte.", - "@errorSynchronizationNoNetworkConnection": { - "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." + "@errorRollOutSSLHandshakeFailed": { + "description": "Tells the user that the roll-out failed because the SSL handshake failed." }, - "errorRollOutUnknownError": "Ein unbekannter Fehler ist aufgetreten. Aurollen nicht möglich: {e}", "@errorRollOutUnknownError": { "description": "Tells the user that the roll-out failed because of an unknown error.", "placeholders": { "e": { "example": "IllegalArgumentException on Line 5 ..." } - } - }, - "startRollout": "Starte Rollout", - "@startRollout": { - "description": "Label that tells the user that the token is being rolled out." + }, + "type": "text" }, - "pollingChallenges": "Frage ausstehende Authentifizierungsanfragen ab", - "unexpectedError": "Ein unerwarteter Fehler ist aufgetreten.", - "@unexpectedError": { - "description": "Title of page report mode." + "@errorSynchronizationNoNetworkConnection": { + "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." }, - "pollingFailed": "Abfrage fehlgeschlagen.", - "@pollingFailed": { - "description": "Tells the user that the polling failed." + "@errorTokenExpired": { + "placeholders": { + "name": { + "example": "PUSH1234A" + } + } }, - "pollingFailedFor": "Abfrage für {serial} fehlgeschlagen.", - "@pollingFailedFor": { - "description": "Tells the user that the polling failed.", + "@errorUnlinkingPushToken": { + "description": "Error message when unlinking a push token failed.", "placeholders": { - "serial": { + "label": { "example": "PUSH1234A" } } }, - "noNetworkConnection": "Keine Netzwerkverbindung.", - "@noNetworkConnection": { - "description": "Tells the user that there is no network connection." + "@errorWhenPullingChallenges": { + "description": "errorWhenPullingChallenges", + "placeholders": { + "name": { + "example": "PUSH1234A" + } + } }, - "connectionFailed": "Verbindung fehlgeschlagen.", - "@connectionFailed": { - "description": "Tells the user that the connection failed." + "@failedToLoad": { + "placeholders": { + "name": { + "example": "token data" + } + } }, - "checkYourNetwork": "Bitte überprüfen Sie Ihre Netzwerkverbindung und versuchen Sie es erneut.", - "@checkYourNetwork": { - "description": "Tells the user to check the network connection." + "@feedbackPrivacyPolicy2": { + "description": "Tapping on this should open the privacy policy." }, - "serverNotReachable": "Der Server konnte nicht erreicht werden.", - "@serverNotReachable": { - "description": "Tells the user that the server could not be reached." + "@generatingPhonePart": { + "description": "Title of a dialog telling the user that the phone part gets generated right now." }, - "couldNotSignMessage": "Nachricht konnte nicht signiert werden.", - "@couldNotSignMessage": { - "description": "Tells the user that the message could not be signed." + "@generatingRSAKeyPair": { + "description": "Message for the rollout process" }, - "useDeviceLocaleTitle": "Nutze Systemsprache", - "@useDeviceLocaleTitle": { - "description": "Title of the switch tile where using the devices language can be enabled." + "@generatingRSAKeyPairFailed": { + "description": "Message for the rollout process" }, - "useDeviceLocaleDescription": "Nutze Systemsprache, falls diese unterstützt wird. Anderenfalls nutze Englisch. ", - "@useDeviceLocaleDescription": { - "description": "Description of the switch tile where using the devices language can be enabled." + "@goToSettingsButton": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." }, - "language": "Sprache", - "@language": { - "description": "Title of language setting group." + "@goToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." }, - "authenticateToShowOtp": "Bitte authentifizieren Sie sich, um das Einmalpasswort anzuzeigen.", - "@authenticateToShowOtp": { - "description": "Reason to authenticate when trying to view a one time password." + "@guide": { + "description": "Button to open the guide screen." }, - "authenticateToUnLockToken": "Bitte authentifizieren Sie sich, um den Sperrstatus des Tokens zu ändern.", - "@authenticateToUnLockToken": { - "description": "Reason to authenticate when trying to lock or unlock a token." + "@handshakeFailed": { + "description": "Error message when the handshake failed." }, - "biometricRequiredTitle": "Biometrie ist nicht eingerichtet", - "@biometricRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + "@importedVia": { + "description": "Label for the import method of the token." }, - "biometricHint": "Authentifizierung wird benötigt", - "@biometricHint": { - "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + "@internalServerError": { + "placeholders": { + "code": { + "example": "500" + } + } }, - "biometricNotRecognized": "Biometrie wurde nicht erkannt, bitte versuchen Sie es erneut", - "@biometricNotRecognized": { - "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + "@invalidValue": { + "description": "Error message when the value is not valid for the parameter.", + "placeholders": { + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "biometricSuccess": "Authentifizierung erfolgreich", - "@biometricSuccess": { - "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + "@invalidValueIn": { + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "deviceCredentialsRequiredTitle": "Gerätepasswort ist nicht eingerichtet", - "@deviceCredentialsRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + "@isExpotableQuestion": { + "description": "Label for the question if the token is exportable." }, - "deviceCredentialsSetupDescription": "Setzen Sie bitte ein Gerätepasswort in den Einstellungen", - "@deviceCredentialsSetupDescription": { - "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + "@isPiTokenQuestion": { + "description": "Label for the question if the token is a privacyIDEA token." }, - "signInTitle": "Authentifizierung wird benötigt", - "@signInTitle": { - "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + "@language": { + "description": "Title of language setting group." }, - "goToSettingsButton": "Gehe zu Einstellungen", - "@goToSettingsButton": { - "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + "@legacySigningErrorMessage": { + "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." }, - "goToSettingsDescription": "Authentifizierung durch Gerätepasswort oder Biometrie ist nicht eingerichtet. Bitte aktivieren Sie dies in den Geräteeinstellungen.", - "@goToSettingsDescription": { - "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." + "@legacySigningErrorTitle": { + "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", + "placeholders": { + "tokenLabel": { + "example": "PUSH1234A" + } + } + }, + "@lightTheme": { + "description": "The light theme." + }, + "@linkedContainer": { + "description": "Label for the linked container serial number." + }, + "@lock": { + "description": "Description of button that locks a token." }, - "lockOut": "Biometrie ist deaktiviert. Bitte sperren und entsperren Sie Ihren Bildschirm um diese zu aktivieren.", "@lockOut": { "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." }, - "authNotSupportedTitle": "Gerätepasswort oder Biometrie wird benötigt", - "@authNotSupportedTitle": { - "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." + "@logMenu": { + "description": "Button to open the log menu." }, - "authNotSupportedBody": "Diese Aktion erfordert, dass auf dem Gerät ein Passwort oder Biometrie eingerichtet ist.", - "@authNotSupportedBody": { - "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." + "@malformedData": { + "description": "Error message when the data is malformed." }, - "lock": "Sperren", - "@lock": { - "description": "Description of button that locks a token." + "@missingRequiredParameter": { + "description": "Error message when a required parameter is missing.", + "placeholders": { + "parameter": { + "example": "counter" + } + } }, - "unlock": "Entsperren", - "@unlock": { - "description": "Description of button that unlocks a token." + "@missingRequiredParameterIn": { + "description": "Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.", + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + } + } }, - "noResultTitle": "Keine Token vorhanden.", - "@noResultTitle": { - "description": "No tokens installed yet." + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, + "@name": { + "description": "Describes the field where the tokens name should be entered." + }, + "@noFbToken": { + "description": "Tells the user that there is no Firebase token available." + }, + "@noNetworkConnection": { + "description": "Tells the user that there is no network connection." }, - "noResultText1": "Tippe auf das ", "@noResultText1": { "description": "first no result text" }, - "noResultText2": " Icon um loszulegen!", "@noResultText2": { "description": "second no result text" }, - "onBoardingTitle1": "{appName}", + "@noResultTitle": { + "description": "No tokens installed yet." + }, + "@notAnInteger": { + "description": "Tells the user that there is no Firebase token available." + }, + "@notAnNumber": { + "description": "Error message when the user entered a value that is not a number." + }, + "@ok": { + "description": "Button to confirm an action." + }, "@onBoardingTitle1": { "placeholders": { "appName": { @@ -376,282 +364,558 @@ } } }, - "onBoardingText1": "Zwei-Faktor-Authentifizierung\neinfach gemacht", - "onBoardingTitle2": "Maximale Sicherheit", - "onBoardingText2": "Speichern Sie Ihre Token sicher auf diesem Gerät\nGeschützt durch Ihre biometrischen Daten", - "onBoardingTitle3": "Besuchen Sie uns auf Github", - "onBoardingText3": "Diese App ist Open Source", - "errorLogTitle": "Fehlerprotokoll", - "logMenu": "Log-Menü", - "showErrorLog": "Anzeigen", - "clearErrorLog": "Löschen", - "send": "Senden", - "sendErrorLogDescription": "Es wird eine vorgefertigte E-Mail erstellt.\nSie enthält Informationen über die App, den Fehler und das Gerät.\nSie können die E-Mail vor dem Senden bearbeiten.\nWie wir die Informationen verwenden, sehen Sie hier:", - "@sendErrorLogDescription": { - "description": "Explanation for the user what he will send." + "@open": { + "description": "Button to open something." }, - "showPrivacyPolicy": "Datenschutzerklärung anzeigen", - "errorLogEmpty": "Das Fehlerprotokoll ist leer.", - "verboseLogging": "Ausführliche Protokollierung", - "errorLogCleared": "Fehlerprotokoll gelöscht.", - "ok": "Ok", - "errorMailBody": "Die Fehlerprotokolldatei ist angehängt.\nSie können diesen Text durch zusätzliche Informationen über den Fehler ersetzen.", - "@errorMailBody": { - "description": "Message for email body" + "@originApp": { + "description": "Label for the origin app." }, - "showDetails": "Details anzeigen", - "open": "Öffnen", - "sendErrorDialogBody": "Ein unbekannter Fehler ist aufgetreten. Die unten gezeigten Informationen können den Entwicklern per E-Mail zugesendet werden, um zu helfen, diesen Fehler in Zukunft zu vermeiden.", - "@sendErrorDialogBody": { - "description": "Description shown to the user about what info the error report contains." + "@originDetails": { + "description": "Title of the origin details menu." }, - "noFbToken": "Kein Firebase Token vorhanden", - "firebaseToken": "Firebase Token", - "noPublicKey": "Kein öffentlicher Schlüssel vorhanden", - "publicKey": "Öffentlicher Schlüssel", - "editToken": "Token bearbeiten", - "edit": "Bearbeiten", - "save": "Speichern", - "create": "Erstellen", - "validFor": "Gültig für", - "validUntil": "Gültig bis", - "deleteLockedToken": "Bitte authentifizieren Sie sich, um den gesperrten Token zu löschen.", - "editLockedToken": "Bitte authentifizieren Sie sich, um den gesperrten Token zu bearbeiten.", - "expandLockedFolder": "Bitte authentifizieren Sie sich, um den gesperrten Ordner zu öffnen.", - "renameTokenFolder": "Ordner umbenennen", - "addANewFolder": "Neuen Ordner anlegen", - "folderName": "Ordnername", - "retryRollout": "Erneut ausrollen", - "generatingRSAKeyPair": "Generiere RSA Schlüsselpaar", - "@generatingRSAKeyPair": { - "description": "Message for the rollout process" + "@otpValueCopiedMessage": { + "description": "Tells the user that the otp value was copied to the clipboard.", + "placeholders": { + "otpValue": { + "example": "055374" + } + }, + "type": "text" }, - "generatingRSAKeyPairFailed": "Generieren des RSA Schlüsselpaars fehlgeschlagen", - "@generatingRSAKeyPairFailed": { + "@parsingResponse": { "description": "Message for the rollout process" }, - "sendingRSAPublicKey": "Sende öffentlichen RSA Schlüssel", - "@sendingRSAPublicKey": { + "@parsingResponseFailed": { "description": "Message for the rollout process" }, - "sendingRSAPublicKeyFailed": "Senden des öffentlichen RSA Schlüssels fehlgeschlagen", - "@sendingRSAPublicKeyFailed": { - "description": "Message for the rollout process" + "@period": { + "description": "Title of the dropdown button where the period of the totp token is selected." }, - "parsingResponse": "Analysiere Antwort", - "@parsingResponse": { - "description": "Message for the rollout process" + "@phonePart": { + "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." }, - "parsingResponseFailed": "Analysieren der Antwort fehlgeschlagen", - "rolloutCompleted": "Ausrollen abgeschlossen", - "imageUrl": "Bild URL", - "errorWhenPullingChallenges": "Fehler beim Abrufen der Authentifizierungsanfragen von {name}", - "@errorWhenPullingChallenges": { + "@pleaseEnterANameForThisToken": { + "description": "Hint telling the user to enter a name for a token." + }, + "@pleaseEnterASecretForThisToken": { + "description": "Hint telling the user to enter a secret for a token." + }, + "@pollingChallenges": { + "type": "text" + }, + "@pollingFailed": { + "description": "Tells the user that the polling failed." + }, + "@pollingFailedFor": { + "description": "Tells the user that the polling failed.", "placeholders": { - "name": { + "serial": { "example": "PUSH1234A" } } }, - "couldNotConnectToServer": "Konnte keine Verbindung zum Server herstellen.", - "errorRollOutNoConnectionToServer": "Der Rollout von Token {name} ist fehlgeschlagen, der Server konnte nicht erreicht werden.", - "authToAcceptPushRequest": "Bitte authentifizieren Sie sich, um die Anfrage anzunehmen.", - "authToDeclinePushRequest": "Bitte authentifizieren Sie sich, um die Anfrage abzulehnen.", - "pushRequestParseError": "Die Push-Anfrage konnte nicht verarbeitet werden.", - "errorRollOutSSLHandshakeFailed": "SSL-Handshake fehlgeschlagen. Roll-out nicht möglich.", - "errorRollOutNotPossibleAnymore": "Das Ausrollen dieses Tokens ist nicht mehr möglich.", - "errorTokenExpired": "Der Token {name} ist abgelaufen.", - "@errorTokenExpired": { + "@pushToken": { + "description": "Title for the settings block concerning the push tokens." + }, + "@rename": { + "description": "Label that describes renaming the token." + }, + "@renameToken": { + "description": "Title of the dialog where a new name for a token can be entered." + }, + "@renameTokenFolder": { + "description": "Title of the dialog where a new name for a token folder can be entered." + }, + "@requestInfo": { + "description": "Description of the authentication request.", "placeholders": { - "name": { - "example": "PUSH1234A" + "account": { + "example": "GitHub" + }, + "issuer": { + "example": "privacyIDEA" } } }, - "yes": "Ja", - "no": "Nein", - "butDiscardIt": "aber verwerfen", - "declineIt": "ablehnen", - "requestTriggerdByUserQuestion": "Wurde diese Anfrage von Ihnen ausgelöst?", - "grantCameraPermissionDialogTitle": "Kamera-Berechtigung erforderlich", - "grantCameraPermissionDialogContent": "Um QR-Codes zu scannen, benötigt die App Zugriff auf die Kamera.", - "grantCameraPermissionDialogPermanentlyDenied": "Sie haben die Berechtigung für den Kamerazugriff permanent verweigert. Bitte aktivieren Sie die Berechtigung in den Einstellungen ihres Smartphones.", - "grantCameraPermissionDialogButton": "Berechtigung erteilen", - "decryptErrorTitle": "Entschlüsselung fehlgeschlagen", - "decryptErrorContent": "Leider konnten Ihre Token nicht entschlüsselt werden. Das deutet darauf hin, dass der Verschlüsselungsschlüssel nicht mehr verfügbar ist. Sie können es erneut versuchen oder die App Daten löschen. Dabei werden alle Token aus der App geschlöscht.", - "decryptErrorButtonDelete": "Löschen.", - "decryptErrorButtonSendError": "Fehler senden", - "decryptErrorButtonRetry": "Wiederholen", - "decryptErrorDeleteConfirmationContent": "Sind Sie sicher, dass Sie die App Daten löschen möchten?", - "hidePushTokens": "Push-Token ausblenden", - "hidePushTokensDescription": "Push-Token aus der Token-Liste ausblenden. Dadurch werden die Token nicht gelöscht und sind weiterhin auf einem separaten Bildschirm sichtbar.", - "settingsGroupGeneral": "Allgemeines", - "licensesAndVersion": "Lizenzen und Version", - "privacyPolicy": "Datenschutzerklärung", - "legacySigningErrorTitle": "Bei der Verwendung des veralteten Tokens ist ein Fehler aufgetreten: {tokenLabel}", - "introScanQrCode": "Sie können QR-Codes scannen, um Token hinzuzufügen.\nWir unterstützen alle gängigen Two-Factor-Authentication Token und auch die privacyIDEA Token.", - "introAddTokenManually": "Wenn Sie keinen QR-Code scannen möchten, können Sie Token auch manuell hinzufügen.", - "introTokenSwipe": "Wischen Sie Token nach links, um die verfügbaren Aktionen zu sehen.", - "introEditToken": "Hier können Sie den Namen des Tokens bearbeiten und einige Details einsehen.", - "introLockToken": "Um die Sicherheit noch weiter zu erhöhen, können Sie Token sperren.\nDer Token kann dann erst nach der Authentifizierung verwendet werden.", - "introDragToken": "Reorganisieren Sie Ihre Token, indem Sie sie einige Sekunden lang drücken und dann an die gewünschte Position ziehen.", - "introAddFolder": "Sie können Ordner erstellen, um Ihre Token zu organisieren.", - "introPollForChallenges": "Sie können neue Push-Anmeldungen abfragen, indem Sie die Liste der Token nach unten ziehen.", - "introHidePushTokens": "Deine Push-Token sind jetzt versteckt.\nAber du kannst sie immer noch auf dem Bildschirm mit den Push-Token sehen.", - "@legacySigningErrorTitle": { - "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", + "@requestPushChallengesPeriodically": { + "description": "The description of the polling feature." + }, + "@retry": { + "description": "Label for e.g. a button. Something is tried to be done again." + }, + "@rolloutCompleted": { + "description": "Message for the rollout process" + }, + "@scanQrCode": { + "description": "The button to scan otpauth qr-codes." + }, + "@secretIsRequired": { + "description": "Error message when the secret is missing." + }, + "@secretKey": { + "description": "Describes the field where the tokens secret should be entered." + }, + "@send": { + "description": "Button to send the error log." + }, + "@sendErrorDialogBody": { + "description": "Description shown to the user about what info the error report contains." + }, + "@sendErrorLogDescription": { + "description": "Explanation for the user what he will send." + }, + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." + }, + "@sendingRSAPublicKey": { + "description": "Message for the rollout process" + }, + "@sendingRSAPublicKeyFailed": { + "description": "Message for the rollout process" + }, + "@serverNotReachable": { + "description": "Tells the user that the server could not be reached." + }, + "@settings": { + "description": "Button to open the settings page." + }, + "@showDetails": { + "description": "Button to show details." + }, + "@showErrorLog": { + "description": "Button to show the error log." + }, + "@showPrivacyPolicy": { + "description": "Button to show the privacy policy." + }, + "@signInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "@someTokensDoNotSupportPolling": { + "description": "Tells the user, that the following tokens do not support polling.", + "type": "text" + }, + "@startRollout": { + "description": "Label that tells the user that the token is being rolled out." + }, + "@statusCode": { + "description": "Tells the user the status code of the error.", "placeholders": { - "tokenLabel": { - "example": "PUSH1234A" + "statusCode": { + "example": "400" } } }, - "legacySigningErrorMessage": "Der Token wurde in einer veralteten Version der App erstellt, was zu Problemen bei der Verwendung führen kann. Es wird empfohlen, einen neuen Push-Token zu erstellen, wenn das Problem weiterhin besteht!", - "@legacySigningErrorMessage": { - "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." + "@sync": { + "description": "Text of button that is used to synchronize push tokens." }, - "selectImportSource": "Importquelle auswählen", - "selectImportType": "Wie wollen Sie die Token importieren?", - "importTokens": "Token importieren", - "importNTokens": "{count, plural, zero{Keine Token importieren} one{Ein Token importieren} other{{count} Token importieren}}", - "selectFile": "Datei auswählen", - "decrypt": "Entschlüsseln", - "tokensAreEncrypted": "Die Token sind verschlüsselt. Bitte gib das Passwort ein, um sie zu entschlüsseln.", - "tokensNotEncrypted": "Die Token sind unverschlüsselt und können direkt importiert werden.", - "tokensSuccessfullyDecrypted": "Die Token wurden erfolgreich entschlüsselt, sie können nun importiert werden.", - "password": "Passwort", - "wrongPassword": "Falsches Passwort", - "qrScan": "Scannen", - "enterLink": "Link eingeben", - "invalidBackupFile": "Die ausgewählte Datei ist kein gültiges Backup von {appName}.", - "invalidQrScan": "Der gescannte QR-Code ist kein gültiges Backup von {appName}.", - "invalidQrFile": "Die ausgewählte Datei enthällt kein gültigen QR-Code von {appName}.", - "invalidLink": "Der eingegebene Link ist kein gültiger Token von {appName}, oder er wird nicht unterstützt.", - "importFailedToken": "{count, plural, zero{Kein Token konnte nicht importiert werden.} one{Importieren eines Tokens fehlgeschlagen.} other{Der Import von {count} Token ist fehlgeschlagen.}}", - "importExistingToken": "{count, plural, zero{Es wurde kein Token gefunden, das sich bereits in der App befindet.} one{Es wurde ein Token gefunden, das sich bereits in der Anwendung befindet.} other{Es wurden {count} Token gefunden, die sich bereits in der Anwendung befinden.}}", - "importConflictToken": "{count, plural, zero{Es besteht kein Konflikt mit bereits existierenden Token.} one{Es besteht ein Konflikt mit bereits vorhandenen Token.\nBitte wählen Sie aus, welches Sie behalten möchten.} other{Es bestehen Konflikte mit bereits vorhandenen Token.\nBitte wählen Sie die Token aus, die Sie behalten möchten.}}", - "importNewToken": "{count, plural, zero{Es wurde kein neues Token gefunden.} one{Es wurde ein neues Token gefunden, das importiert werden kann} other{Es wurden {count} neue Token gefunden, die importiert werden können.}}", - "importHintPrivacyIdeaQrScan": "Um QR-Codes der Token zu erstellen, navigieren Sie zu den Einstellungen und tippen auf \"Exportieren\". Wählen Sie dann \"Als QR-Code\" und tippen Sie auf den zu exportierenden Token. Diese Variante ist nur für die direkte Übertragung auf ein anderes Gerät geeignet, da der QR-Code nicht verschlüsselt ist.", - "importHintPrivacyIdeaFile": "Um ein Backup zu erstellen, gehen Sie zu den Einstellungen und tippen auf \"Exportieren\". Wählen Sie \"Als Datei\" aus, wählen Sie die Token aus, die Sie exportieren möchten. Anschließend tippen Sie auf \"Exportieren\" und setzen Sie ein Passwort. Der Speicherort ist der Download-Ordner auf Ihrem Gerät.", - "importHint2FAS": "Wählen Sie das 2FAS-Backup aus.\nFalls Sie kein Backup haben, erstellen Sie eins in der 2FAS-App. Wir empfehlen die Verwendung eines Passworts.", - "importHintAegisBackupFile": "Wähle dein Aegis-Export (.json) aus.\nWenn Sie keinen Export haben, erstellen Sie bitte eins über das Einstellungen Menu in der Aegis-App. Wir empfehlen die Verwendung eines Passworts.", - "importHintAegisQrScan": "Scannen Sie den QR-Code, den Sie erhalten, wenn Sie Einträge aus Aegis übertragen.", - "importHintAegisLink": "Geben Sie den Link ein, den Sie erhalten, wenn Sie Einträge aus Aegis übertragen.", - "importHintGoogleQrScan": "Scannen Sie den QR-Code, den Sie erhalten, wenn Sie Ihre Konten aus Google Authenticator exportieren.", - "importHintGoogleQrFile": "Wählen Sie eine Bilddatei mit dem QR-Code, den Sie erhalten, wenn Sie Ihre Konten aus dem Google Authenticator exportieren.\n!! Der QR-Code enthält die Token in unverschlüsselter Form. Es ist deshalb nicht sicher, diesen länger als nötig aufzubewahren !!", - "importHintAuthenticatorProFile": "Um ein Backup der Authenticator Pro-App zu erstellen navigieren Sie zu den Einstellungen und tippen Sie auf \"Automatische Sicherung\". Wählen Sie einen Speicherort und setzen Sie ein Passwort. Anschließend drücken Sie auf \"Jetzt sichern\" um die Token zu exportieren.", - "importHintFreeOtpPlusQrScan": "Scannen Sie den QR-Code, den Sie erhalten, wenn Sie auf die drei Punkte in der Kachel des Tokens drücken, und wählen Sie \"QR-Code teilen\".", - "importHintFreeOtpPlusFile": "Um ein Backup der FreeOTP+ App zu erstellen, tippen Sie auf die drei Punkte in der oberen rechten Ecke und wählen Sie \"Exportieren\". Sie können zwischen dem JSON- und dem URI-Format wählen. Wir empfehlen, das Backup nach dem Importieren zu löschen, da es nicht verschlüsselt ist.", - "qrFileDecodeError": "Es war nicht möglich, den QR-Code aus dem ausgewählten Bild zu dekodieren. Bitte verwenden Sie stattdessen den QR-Code-Scanner.", - "tokenLink": "Token Link", - "feedback": "Feedback", - "feedbackTitle": "Ihr Feedback ist immer willkommen!", - "feedbackDescription": "Wenn Sie Fragen, Anregungen oder Probleme haben, lassen Sie es uns wissen.", - "feedbackHint": "Es öffnet sich eine vorgefertigte E-Mail, die Sie an uns senden können. Falls gewünscht, werden Informationen über Ihr Gerät und die Version der Anwendung hinzugefügt. Vor dem Versenden können Sie die E-Mail überprüfen und bearbeiten.", - "feedbackPrivacyPolicy1": "Mit dem Senden des Feedbacks stimmen Sie unserer ", - "feedbackPrivacyPolicy2": "Datenschutzerklärung", - "@feedbackPrivacyPolicy2": { - "description": "Tapping on this should open the privacy policy." + "@syncContainerFailed": { + "description": "Error message when synchronizing a container failed." }, - "feedbackPrivacyPolicy3": " zu.", - "addSystemInfo": "Systeminfos hinzufügen", - "feedbackSentTitle": "Feedback gesendet", - "feedbackSentDescription": "Vielen Dank für Ihre Hilfe bei der Verbesserung dieser App!", - "patchNotesDialogTitle": "Was ist neu?", - "version": "Version", - "noMailAppTitle": "Keine Mail-App gefunden", - "noMailAppDescription": "Auf diesem Gerät ist keine E-Mail-App installiert oder initialisiert, bitte versuchen Sie es erneut, wenn Sie eine E-Mail-Nachricht senden können.", - "authenticationRequest": "Authentifizierung", - "requestInfo": "Gesendet von {issuer} für Ihr Konto: \"{account}\"", - "@requestInfo": { + "@syncFbTokenFailed": { + "description": "Headline for the list of tokens where the synchronization failed." + }, + "@synchronizePushTokens": { + "description": "Title of synchronizing push tokens in settings." + }, + "@synchronizesTokensWithServer": { + "description": "Description of synchronizing push tokens in settings." + }, + "@synchronizingTokens": { + "description": "Title of the push synchronization dialog." + }, + "@systemTheme": { + "description": "The systems theme." + }, + "@theSecretDoesNotFitTheCurrentEncoding": { + "description": "Hint telling the user that the secret does not fit the selected encoding." + }, + "@theme": { + "description": "Title of the setting group where the theme can be selected." + }, + "@themeMode": { + "description": "Title of the setting group where the theme mode can be changed." + }, + "@timeOut": { + "description": "Error message when a request times out." + }, + "@tokenDataParseError": { + "description": "Error message when the token data could not be parsed." + }, + "@tokenDetails": { + "description": "Title of the token details menu." + }, + "@tokenSerial": { + "description": "Label for the token serial number." + }, + "@tokensDoNotSupportSynchronization": { + "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + }, + "@type": { + "description": "Title of the dropdown button where the type of the token is selected." + }, + "@unexpectedError": { + "description": "Title of page report mode." + }, + "@unknown": { + "description": "Tells that something is unknown." + }, + "@unlock": { + "description": "Description of button that unlocks a token." + }, + "@unsupported": { "placeholders": { - "issuer": { - "example": "privacyIDEA" + "name": { + "example": "piauth version" }, - "Konto": { - "example": "GitHub" + "value": { + "example": "5" } } }, - "errorUnlinkingPushToken": "Entkoppeln des Push Tokens {label} fehlgeschlagen.", - "@errorUnlinkingPushToken": { - "description": "Error message when unlinking a push token failed.", - "placeholders": { - "label": { - "example": "PUSH1234A" - } - } + "@useDeviceLocaleDescription": { + "description": "Description of the switch tile where using the devices language can be enabled." }, - "pleaseSyncManuallyWhenNetworkIsAvailable": "Bitte synchronisieren Sie die Push Token über die Einstellungen manuell, wenn eine Netzwerkverbindung verfügbar ist.", - "pushTokens": "Push-Token", - "continueButton": "Weiter", - "addTokenManually": "Token manuell hinzufügen", - "addFolder": "Ordner hinzufügen", - "searchTokens": "Token suchen", - "closeSearchTokens": "Suche schließen", - "increaseCounter": "Zähler erhöhen", - "copyOTPToClipboard": "OTP in die Zwischenablage kopieren", - "licenses": "Lizenzen", - "optionalMessage": "Optionale Nachricht", - "confirmation": "Confirmation", - "askLogSendedDescription": "Haben Sie das Protokoll gesendet, und möchten Sie es jetzt löschen?", - "algorithmUnsupported": "Der Algorithmus {algorithm} wird nicht unterstützt", - "@algorithmUnsupported": { + "@useDeviceLocaleTitle": { + "description": "Title of the switch tile where using the devices language can be enabled." + }, + "@valueNotAllowed": { "placeholders": { - "algorithm": { - "example": "MD5" + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "-1" } } }, - "thisAppIsOpenSource": "Diese App ist Open Source\nBesuchen Sie uns auf GitHub", - "invalidArgument": "{argument} ist kein gültiger Wert für {type}", - "importExportTokens": "Token importieren/exportieren", - "exportNonPrivacyIDEATokens": "Nicht-privacyIDEA-Token exportieren", - "selectTokensToExport": "{count, plural, zero{} one{Wählen Sie das zu exportierende Token aus} other{Wählen Sie die zu exportierenden Tokens aus}}", - "noTokenToExport": "Kein Token zum Exportieren verfügbar", - "exportAllTokens": "Alle Tokens exportieren", - "export": "Exportieren", - "exportingTokens": "Tokens werden exportiert...", - "exportTokens": "Tokens exportieren", - "enterPasswordToEncrypt": "Geben Sie ein Passwort ein, um die Tokens zu verschlüsseln. Dieses Passwort wird benötigt, um die Tokens zu importieren.", - "exportLockedTokenReason": "Bitte authentifizieren Sie sich, um gesperrte Tokens zu exportieren.", - "fileSavedToDownloadsFolder": "Datei wurde im Download-Ordner gespeichert", - "errorSavingFile": "Fehler beim Speichern der Datei", - "asQrCode": "Als QR-Code", - "asFile": "Als Datei", - "scanThisQrWithNewDevice": "Scannen Sie diesen QR-Code mit Ihrem neuen Gerät, um das Token zu importieren.", - "oneMore": "Noch eins", - "done": "Fertig", - "confirmPassword": "Passwort bestätigen", - "exampleUrl": "Bitte geben Sie eine gültige URL ein wie: \"https://example.com\"", - "pushEndpointUrl": "Push-Endpunkt URL", - "mustNotBeEmpty": "{field} darf nicht leer sein", - "@mustNotBeEmpty": { + "@valueNotAllowedIn": { "placeholders": { - "field": { - "example": "Name" + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "-1" } } }, - "sendPushRequestResponseFailed": "Senden der Antwort fehlgeschlagen.", - "@sendPushRequestResponseFailed": { - "description": "Error message when the response to a push request could not be sent." + "@verboseLogging": { + "description": "Title of the switch tile where verbose logging can be enabled." }, - "passwordCannotBeEmpty": "Das Passwort darf nicht leer sein", - "passwordMustBeAtLeast8Characters": "Das Passwort muss mindestens 8 Zeichen lang sein", - "passwordCannotContainWhitespace": "Das Passwort darf keine Leerzeichen enthalten", - "passwordMustContainLowercaseLetter": "Das Passwort muss einen Kleinbuchstaben enthalten", - "passwordMustContainUppercaseLetter": "Das Passwort muss einen Großbuchstaben enthalten", - "passwordMustContainNumber": "Das Passwort muss eine Zahl enthalten", - "passwordMustContainSpecialCharacter": "Das Passwort muss ein Sonderzeichen enthalten", - "passwordsDoNotMatch": "Die Passwörter stimmen nicht überein", - "selectTokensToExportHelpTitle": "Ist Ihr Token nicht aufgelistet?", - "selectTokensToExportHelpContent": "Wenn ein Token nicht aufgelistet ist, ist nicht garantiert, dass es sich nicht um ein privacyIDEA-Token handelt.\nZurzeit können nur manuell hinzugefügte und importierte Token exportiert werden.", + "accept": "Akzeptieren", + "addANewFolder": "Neuen Ordner anlegen", + "addFolder": "Ordner hinzufügen", + "addSystemInfo": "Systeminfos hinzufügen", + "addToken": "Token hinzufügen", + "addTokenManually": "Token manuell hinzufügen", + "algorithm": "Algorithmus", + "algorithmUnsupported": "Der Algorithmus {algorithm} wird nicht unterstützt", + "allTokensSynchronized": "Alle Token wurden synchronisiert.", + "asFile": "Als Datei", + "asQrCode": "Als QR-Code", + "askLogSendedDescription": "Haben Sie das Protokoll gesendet, und möchten Sie es jetzt löschen?", + "authNotSupportedBody": "Diese Aktion erfordert, dass auf dem Gerät ein Passwort oder Biometrie eingerichtet ist.", + "authNotSupportedTitle": "Gerätepasswort oder Biometrie wird benötigt", + "authToAcceptPushRequest": "Bitte authentifizieren Sie sich, um die Anfrage anzunehmen.", + "authToDeclinePushRequest": "Bitte authentifizieren Sie sich, um die Anfrage abzulehnen.", + "authenticateToShowOtp": "Bitte authentifizieren Sie sich, um das Einmalpasswort anzuzeigen.", + "authenticateToUnLockToken": "Bitte authentifizieren Sie sich, um den Sperrstatus des Tokens zu ändern.", + "authenticationRequest": "Authentifizierung", + "biometricHint": "Authentifizierung wird benötigt", + "biometricNotRecognized": "Biometrie wurde nicht erkannt, bitte versuchen Sie es erneut", + "biometricRequiredTitle": "Biometrie ist nicht eingerichtet", + "biometricSuccess": "Authentifizierung erfolgreich", + "butDiscardIt": "aber verwerfen", + "cancel": "Abbrechen", + "checkServerCertificate": "Bitte überprüfen Sie das Serverzertifikat", + "checkYourNetwork": "Bitte überprüfen Sie Ihre Netzwerkverbindung und versuchen Sie es erneut.", + "clearErrorLog": "Löschen", + "closeSearchTokens": "Suche schließen", + "confirmDeletion": "Löschen bestätigen", + "confirmDeletionOf": "Sind Sie sicher dass Sie {name} löschen möchten?", + "confirmFolderDeletionHint": "Das Löschen eines Ordners hat keine Auswirkungen auf die Token, die sich darin befinden.\nDie Token werden in die Hauptliste verschoben.", + "confirmPassword": "Passwort bestätigen", + "confirmTokenDeletionHint": "Unter Umständen können Sie sich nicht mehr einloggen, wenn Sie diesen Token löschen.\nBitte stellen Sie sicher, dass Sie sich ohne diesen Token in den dazugehörigen Account einloggen können.", + "confirmation": "Confirmation", + "connectionFailed": "Verbindung fehlgeschlagen.", + "container": "Container", + "continueButton": "Weiter", + "copyOTPToClipboard": "OTP in die Zwischenablage kopieren", + "couldNotConnectToServer": "Konnte keine Verbindung zum Server herstellen.", + "couldNotSignMessage": "Nachricht konnte nicht signiert werden.", + "counter": "Zähler", + "create": "Erstellen", + "createdAt": "Erstellt am", + "creator": "Ersteller", + "decline": "Ablehnen", + "declineIt": "ablehnen", + "decrypt": "Entschlüsseln", + "decryptErrorButtonDelete": "Löschen.", + "decryptErrorButtonRetry": "Wiederholen", + "decryptErrorButtonSendError": "Fehler senden", + "decryptErrorContent": "Leider konnten Ihre Token nicht entschlüsselt werden. Das deutet darauf hin, dass der Verschlüsselungsschlüssel nicht mehr verfügbar ist. Sie können es erneut versuchen oder die App Daten löschen. Dabei werden alle Token aus der App geschlöscht.", + "decryptErrorDeleteConfirmationContent": "Sind Sie sicher, dass Sie die App Daten löschen möchten?", + "decryptErrorTitle": "Entschlüsselung fehlgeschlagen", + "delete": "Löschen", + "deleteLockedToken": "Bitte authentifizieren Sie sich, um den gesperrten Token zu löschen.", + "deviceCredentialsRequiredTitle": "Gerätepasswort ist nicht eingerichtet", + "deviceCredentialsSetupDescription": "Setzen Sie bitte ein Gerätepasswort in den Einstellungen", + "digits": "Ziffern", + "dismiss": "Schließen", + "done": "Fertig", + "edit": "Bearbeiten", + "editLockedToken": "Bitte authentifizieren Sie sich, um den gesperrten Token zu bearbeiten.", + "editToken": "Token bearbeiten", + "enablePolling": "Aktives Stellen von Push-Anfragen", + "encoding": "Kodierung", + "enterDetailsForToken": "Neuen Token konfigurieren", + "enterLink": "Link eingeben", + "enterPasswordToEncrypt": "Geben Sie ein Passwort ein, um die Tokens zu verschlüsseln. Dieses Passwort wird benötigt, um die Tokens zu importieren.", + "errorLogCleared": "Fehlerprotokoll gelöscht.", + "errorLogEmpty": "Das Fehlerprotokoll ist leer.", + "errorLogTitle": "Fehlerprotokoll", + "errorMailBody": "Die Fehlerprotokolldatei ist angehängt.\nSie können diesen Text durch zusätzliche Informationen über den Fehler ersetzen.", + "errorRollOutFailed": "Ausrollen von {name} ist fehlgeschlagen.", + "errorRollOutNoConnectionToServer": "Der Rollout von Token {name} ist fehlgeschlagen, der Server konnte nicht erreicht werden.", + "errorRollOutNotPossibleAnymore": "Das Ausrollen dieses Tokens ist nicht mehr möglich.", + "errorRollOutSSLHandshakeFailed": "SSL-Handshake fehlgeschlagen. Roll-out nicht möglich.", + "errorRollOutUnknownError": "Ein unbekannter Fehler ist aufgetreten. Aurollen nicht möglich: {e}", + "errorSavingFile": "Fehler beim Speichern der Datei", + "errorSynchronizationNoNetworkConnection": "Die Synchronisation ist fehlgeschlagen, da der privacyIDEA Server nicht erreicht werden konnte.", + "errorTokenExpired": "Der Token {name} ist abgelaufen.", + "errorUnlinkingPushToken": "Entkoppeln des Push Tokens {label} fehlgeschlagen.", + "errorWhenPullingChallenges": "Fehler beim Abrufen der Authentifizierungsanfragen von {name}", + "exampleUrl": "Bitte geben Sie eine gültige URL ein wie: \"https://example.com\"", + "expandLockedFolder": "Bitte authentifizieren Sie sich, um den gesperrten Ordner zu öffnen.", + "export": "Exportieren", + "exportAllTokens": "Alle Tokens exportieren", + "exportLockedTokenReason": "Bitte authentifizieren Sie sich, um gesperrte Tokens zu exportieren.", + "exportNonPrivacyIDEATokens": "Nicht-privacyIDEA-Token exportieren", + "exportTokens": "Tokens exportieren", + "exportingTokens": "Tokens werden exportiert...", + "failedToLoad": "Fehlgeschlagen zu laden: ", + "feedback": "Feedback", + "feedbackDescription": "Wenn Sie Fragen, Anregungen oder Probleme haben, lassen Sie es uns wissen.", + "feedbackHint": "Es öffnet sich eine vorgefertigte E-Mail, die Sie an uns senden können. Falls gewünscht, werden Informationen über Ihr Gerät und die Version der Anwendung hinzugefügt. Vor dem Versenden können Sie die E-Mail überprüfen und bearbeiten.", + "feedbackPrivacyPolicy1": "Mit dem Senden des Feedbacks stimmen Sie unserer ", + "feedbackPrivacyPolicy2": "Datenschutzerklärung", + "feedbackPrivacyPolicy3": " zu.", + "feedbackSentDescription": "Vielen Dank für Ihre Hilfe bei der Verbesserung dieser App!", + "feedbackSentTitle": "Feedback gesendet", + "feedbackTitle": "Ihr Feedback ist immer willkommen!", + "fileSavedToDownloadsFolder": "Datei wurde im Download-Ordner gespeichert", "findingQrCodeInImage": "Suche nach QR-Code im Bild...", - "qrNotFound": "Kein QR-Code gefunden!", + "firebaseToken": "Firebase Token", + "folderName": "Ordnername", + "generatingPhonePart": "Generiere Telefonanteil", + "generatingRSAKeyPair": "Generiere RSA Schlüsselpaar", + "generatingRSAKeyPairFailed": "Generieren des RSA Schlüsselpaars fehlgeschlagen", + "goToSettingsButton": "Gehe zu Einstellungen", + "goToSettingsDescription": "Authentifizierung durch Gerätepasswort oder Biometrie ist nicht eingerichtet. Bitte aktivieren Sie dies in den Geräteeinstellungen.", + "grantCameraPermissionDialogButton": "Berechtigung erteilen", + "grantCameraPermissionDialogContent": "Um QR-Codes zu scannen, benötigt die App Zugriff auf die Kamera.", + "grantCameraPermissionDialogPermanentlyDenied": "Sie haben die Berechtigung für den Kamerazugriff permanent verweigert. Bitte aktivieren Sie die Berechtigung in den Einstellungen ihres Smartphones.", + "grantCameraPermissionDialogTitle": "Kamera-Berechtigung erforderlich", + "handshakeFailed": "Handshake fehlgeschlagen", + "hidePushTokens": "Push-Token ausblenden", + "hidePushTokensDescription": "Push-Token aus der Token-Liste ausblenden. Dadurch werden die Token nicht gelöscht und sind weiterhin auf einem separaten Bildschirm sichtbar.", + "imageUrl": "Bild URL", + "importConflictToken": "{count, plural, zero{Es besteht kein Konflikt mit bereits existierenden Token.} one{Es besteht ein Konflikt mit bereits vorhandenen Token.\nBitte wählen Sie aus, welches Sie behalten möchten.} other{Es bestehen Konflikte mit bereits vorhandenen Token.\nBitte wählen Sie die Token aus, die Sie behalten möchten.}}", + "importExistingToken": "{count, plural, zero{Es wurde kein Token gefunden, das sich bereits in der App befindet.} one{Es wurde ein Token gefunden, das sich bereits in der Anwendung befindet.} other{Es wurden {count} Token gefunden, die sich bereits in der Anwendung befinden.}}", + "importExportTokens": "Token importieren/exportieren", + "importFailedToken": "{count, plural, zero{Kein Token konnte nicht importiert werden.} one{Importieren eines Tokens fehlgeschlagen.} other{Der Import von {count} Token ist fehlgeschlagen.}}", + "importHint2FAS": "Wählen Sie das 2FAS-Backup aus.\nFalls Sie kein Backup haben, erstellen Sie eins in der 2FAS-App. Wir empfehlen die Verwendung eines Passworts.", + "importHintAegisBackupFile": "Wähle dein Aegis-Export (.json) aus.\nWenn Sie keinen Export haben, erstellen Sie bitte eins über das Einstellungen Menu in der Aegis-App. Wir empfehlen die Verwendung eines Passworts.", + "importHintAegisLink": "Geben Sie den Link ein, den Sie erhalten, wenn Sie Einträge aus Aegis übertragen.", + "importHintAegisQrScan": "Scannen Sie den QR-Code, den Sie erhalten, wenn Sie Einträge aus Aegis übertragen.", + "importHintAuthenticatorProFile": "Um ein Backup der Authenticator Pro-App zu erstellen navigieren Sie zu den Einstellungen und tippen Sie auf \"Automatische Sicherung\". Wählen Sie einen Speicherort und setzen Sie ein Passwort. Anschließend drücken Sie auf \"Jetzt sichern\" um die Token zu exportieren.", + "importHintFreeOtpPlusFile": "Um ein Backup der FreeOTP+ App zu erstellen, tippen Sie auf die drei Punkte in der oberen rechten Ecke und wählen Sie \"Exportieren\". Sie können zwischen dem JSON- und dem URI-Format wählen. Wir empfehlen, das Backup nach dem Importieren zu löschen, da es nicht verschlüsselt ist.", + "importHintFreeOtpPlusQrScan": "Scannen Sie den QR-Code, den Sie erhalten, wenn Sie auf die drei Punkte in der Kachel des Tokens drücken, und wählen Sie \"QR-Code teilen\".", + "importHintGoogleQrFile": "Wählen Sie eine Bilddatei mit dem QR-Code, den Sie erhalten, wenn Sie Ihre Konten aus dem Google Authenticator exportieren.\n!! Der QR-Code enthält die Token in unverschlüsselter Form. Es ist deshalb nicht sicher, diesen länger als nötig aufzubewahren !!", + "importHintGoogleQrScan": "Scannen Sie den QR-Code, den Sie erhalten, wenn Sie Ihre Konten aus Google Authenticator exportieren.", + "importHintPrivacyIdeaFile": "Um ein Backup zu erstellen, gehen Sie zu den Einstellungen und tippen auf \"Exportieren\". Wählen Sie \"Als Datei\" aus, wählen Sie die Token aus, die Sie exportieren möchten. Anschließend tippen Sie auf \"Exportieren\" und setzen Sie ein Passwort. Der Speicherort ist der Download-Ordner auf Ihrem Gerät.", + "importHintPrivacyIdeaQrScan": "Um QR-Codes der Token zu erstellen, navigieren Sie zu den Einstellungen und tippen auf \"Exportieren\". Wählen Sie dann \"Als QR-Code\" und tippen Sie auf den zu exportierenden Token. Diese Variante ist nur für die direkte Übertragung auf ein anderes Gerät geeignet, da der QR-Code nicht verschlüsselt ist.", + "importNTokens": "{count, plural, zero{Keine Token importieren} one{Ein Token importieren} other{{count} Token importieren}}", + "importNewToken": "{count, plural, zero{Es wurde kein neues Token gefunden.} one{Es wurde ein neues Token gefunden, das importiert werden kann} other{Es wurden {count} neue Token gefunden, die importiert werden können.}}", + "importTokens": "Token importieren", + "importedVia": "Importiert über", + "increaseCounter": "Zähler erhöhen", + "internalServerError": "Interner Serverfehler ({code})", + "introAddFolder": "Sie können Ordner erstellen, um Ihre Token zu organisieren.", + "introAddTokenManually": "Wenn Sie keinen QR-Code scannen möchten, können Sie Token auch manuell hinzufügen.", + "introDragToken": "Reorganisieren Sie Ihre Token, indem Sie sie einige Sekunden lang drücken und dann an die gewünschte Position ziehen.", + "introEditToken": "Hier können Sie den Namen des Tokens bearbeiten und einige Details einsehen.", + "introHidePushTokens": "Deine Push-Token sind jetzt versteckt.\nAber du kannst sie immer noch auf dem Bildschirm mit den Push-Token sehen.", + "introLockToken": "Um die Sicherheit noch weiter zu erhöhen, können Sie Token sperren.\nDer Token kann dann erst nach der Authentifizierung verwendet werden.", + "introPollForChallenges": "Sie können neue Push-Anmeldungen abfragen, indem Sie die Liste der Token nach unten ziehen.", + "introScanQrCode": "Sie können QR-Codes scannen, um Token hinzuzufügen.\nWir unterstützen alle gängigen Two-Factor-Authentication Token und auch die privacyIDEA Token.", + "introTokenSwipe": "Wischen Sie Token nach links, um die verfügbaren Aktionen zu sehen.", + "invalidBackupFile": "Die ausgewählte Datei ist kein gültiges Backup von {appName}.", + "invalidLink": "Der eingegebene Link ist kein gültiger Token von {appName}, oder er wird nicht unterstützt.", + "invalidQrFile": "Die ausgewählte Datei enthällt kein gültigen QR-Code von {appName}.", + "invalidQrScan": "Der gescannte QR-Code ist kein gültiges Backup von {appName}.", + "invalidValue": "untranslated", + "invalidValueIn": "untranslated", + "isExpotableQuestion": "Ist exportierbar?", + "isPiTokenQuestion": "Ist ein privacyIDEA-Token?", + "language": "Sprache", + "legacySigningErrorMessage": "Der Token wurde in einer veralteten Version der App erstellt, was zu Problemen bei der Verwendung führen kann. Es wird empfohlen, einen neuen Push-Token zu erstellen, wenn das Problem weiterhin besteht!", + "legacySigningErrorTitle": "Bei der Verwendung des veralteten Tokens ist ein Fehler aufgetreten: {tokenLabel}", + "licenses": "Lizenzen", + "licensesAndVersion": "Lizenzen und Version", + "linkedContainer": "Verknüpfter Container", + "lock": "Sperren", + "lockOut": "Biometrie ist deaktiviert. Bitte sperren und entsperren Sie Ihren Bildschirm um diese zu aktivieren.", + "logMenu": "Log-Menü", + "malformedData": "Fehlerhafte Daten", + "markQrCode": "QR-Code markieren", + "missingRequiredParameter": "Der Wert für den Parameter [{parameter}] ist erforderlich, fehlt aber.", + "missingRequiredParameterIn": "Der Wert für den Parameter [{parameter}] ist erforderlich, fehlt aber in \"{map}\".", + "mustNotBeEmpty": "{field} darf nicht leer sein", + "name": "Name", + "no": "Nein", + "noFbToken": "Kein Firebase Token vorhanden", + "noMailAppDescription": "Auf diesem Gerät ist keine E-Mail-App installiert oder initialisiert, bitte versuchen Sie es erneut, wenn Sie eine E-Mail-Nachricht senden können.", + "noMailAppTitle": "Keine Mail-App gefunden", + "noNetworkConnection": "Keine Netzwerkverbindung.", + "noPublicKey": "Kein öffentlicher Schlüssel vorhanden", + "noResultText1": "Tippe auf das ", + "noResultText2": " Icon um loszulegen!", + "noResultTitle": "Keine Token vorhanden.", + "noTokenToExport": "Kein Token zum Exportieren verfügbar", + "notAnInteger": "Der Wert ist keine Ganzzahl.", + "notAnNumber": "Der Wert ist keine Zahl.", + "ok": "Ok", + "oneMore": "Noch eins", + "open": "Öffnen", + "optionalMessage": "Optionale Nachricht", + "originApp": "Ursprungs-App", + "originDetails": "Angaben zur Herkunft", + "otpValueCopiedMessage": "Passwort \"{otpValue}\" wurde in Zwischenablage kopiert.", + "parsingResponse": "Analysiere Antwort", + "parsingResponseFailed": "Analysieren der Antwort fehlgeschlagen", + "password": "Passwort", + "passwordCannotBeEmpty": "Das Passwort darf nicht leer sein", + "passwordCannotContainWhitespace": "Das Passwort darf keine Leerzeichen enthalten", + "passwordMustBeAtLeast8Characters": "Das Passwort muss mindestens 8 Zeichen lang sein", + "passwordMustContainLowercaseLetter": "Das Passwort muss einen Kleinbuchstaben enthalten", + "passwordMustContainNumber": "Das Passwort muss eine Zahl enthalten", + "passwordMustContainSpecialCharacter": "Das Passwort muss ein Sonderzeichen enthalten", + "passwordMustContainUppercaseLetter": "Das Passwort muss einen Großbuchstaben enthalten", + "passwordsDoNotMatch": "Die Passwörter stimmen nicht überein", + "patchNotesBugFixes": "Fehlerbehebungen", + "patchNotesDialogTitle": "Was ist neu?", + "patchNotesImprovements": "Verbesserungen", + "patchNotesNewFeatures": "Neue Funktionen", + "patchNotesV4_3_0NewFeatures1": "Unterstützung für den Import von Token von Google, Aegis und 2FAS Authenticator hinzugefügt. Weitere Importquellen werden in Zukunft hinzugefügt.", + "patchNotesV4_3_0NewFeatures2": "Feedback-Option zu den Einstellungen hinzugefügt.", + "patchNotesV4_3_0NewFeatures3": "Push-Token können jetzt aus der Token-Liste ausgeblendet werden.", + "patchNotesV4_3_0NewFeatures4": "Es wurden Einführungen hinzugefügt, um neuen Benutzern den Einstieg zu erleichtern.", + "patchNotesV4_3_0NewFeatures5": "Sie können jetzt nach Token suchen, indem Sie auf die Lupe in der oberen rechten Ecke tippen.", + "patchNotesV4_3_0NewFeatures6": "Ab Android 12 kann für einen Token ein Widget auf dem Homescreen erstellt werden.", + "patchNotesV4_3_1BugFix1": "Ein Problem wurde behoben, bei dem der otp-Wert nach der Authentifizierung auf einigen Geräten nicht angezeigt wurde.", + "patchNotesV4_3_1Improvement1": "Der QR-Code-Scanner wurde verbessert.", + "patchNotesV4_4_0Improvement1": "Es wurden weitere Importquellen hinzugefügt.", + "patchNotesV4_4_0Improvement2": "Die erkennung von QR-Codes aus Bilddateien wurde verbessert.", + "patchNotesV4_4_0NewFeatures1": "Es ist nun möglich, Token zu exportieren, bei denen sichergestellt werden kann, dass es sich nicht um privacyIDEA Token handelt. Derzeit kann nicht ausgeschlossen werden, dass über den QR-Code-Scanner hinzugefügte Token von privacyIDEA stammen. Die Differenzierung wird in zukünftigen Versionen verbessert.", + "patchNotesV4_4_0NewFeatures2": "Unterstützung für privacyIDEA's \"require presence\" hinzugefügt.", + "period": "Periode", + "phonePart": "Telefonanteil:", + "pleaseEnterANameForThisToken": "Bitte geben Sie einen Namen ein.", + "pleaseEnterASecretForThisToken": "Bitte geben Sie ein Geheimnis ein.", + "pleaseSyncManuallyWhenNetworkIsAvailable": "Bitte synchronisieren Sie die Push Token über die Einstellungen manuell, wenn eine Netzwerkverbindung verfügbar ist.", + "pollingChallenges": "Frage ausstehende Authentifizierungsanfragen ab", + "pollingFailed": "Abfrage fehlgeschlagen.", + "pollingFailedFor": "Abfrage für {serial} fehlgeschlagen.", + "privacyPolicy": "Datenschutzerklärung", + "publicKey": "Öffentlicher Schlüssel", + "pushEndpointUrl": "Push-Endpunkt URL", + "pushRequestParseError": "Die Push-Anfrage konnte nicht verarbeitet werden.", + "pushToken": "Push Token", + "pushTokens": "Push-Token", + "qrFileDecodeError": "Es war nicht möglich, den QR-Code aus dem ausgewählten Bild zu dekodieren. Bitte verwenden Sie stattdessen den QR-Code-Scanner.", "qrInFileNotFound": "Es wurde kein QR-Code in dem ausgewählten Bild gefunden.", "qrInFileNotFound2": "Sie können mir zeigen, wo sich der QR-Code befindet.", "qrInFileNotFound3": "Ich erwarte, dass ich den Code finde, wenn er sich in der Mitte des markierten Bereichs befindet.", - "markQrCode": "QR-Code markieren", - "malformedData": "Fehlerhafte Daten" + "qrNotFound": "Kein QR-Code gefunden!", + "qrScan": "Scannen", + "rename": "Umbenennen", + "renameToken": "Token umbenennen", + "renameTokenFolder": "Ordner umbenennen", + "requestInfo": "Gesendet von {issuer} für Ihr Konto: \"{account}\"", + "requestPushChallengesPeriodically": "Fordert regelmäßig Push-Anfragen vom Server an. Aktivieren Sie diese Funktion, wenn Nachrichten ansonsten nicht erhalten werden.", + "requestTriggerdByUserQuestion": "Wurde diese Anfrage von Ihnen ausgelöst?", + "retry": "Erneut versuchen", + "retryRollout": "Erneut ausrollen", + "rolloutCompleted": "Ausrollen abgeschlossen", + "save": "Speichern", + "scanQrCode": "QR-Code scannen", + "scanThisQrWithNewDevice": "Scannen Sie diesen QR-Code mit Ihrem neuen Gerät, um das Token zu importieren.", + "searchTokens": "Token suchen", + "secretIsRequired": "Secret ist erforderlich", + "secretKey": "Geheimer Schlüssel", + "selectFile": "Datei auswählen", + "selectImportSource": "Importquelle auswählen", + "selectImportType": "Wie wollen Sie die Token importieren?", + "selectTokensToExport": "{count, plural, zero{} one{Wählen Sie das zu exportierende Token aus} other{Wählen Sie die zu exportierenden Tokens aus}}", + "selectTokensToExportHelpContent": "Wenn ein Token nicht aufgelistet ist, ist nicht garantiert, dass es sich nicht um ein privacyIDEA-Token handelt.\nZurzeit können nur manuell hinzugefügte und importierte Token exportiert werden.", + "selectTokensToExportHelpTitle": "Ist Ihr Token nicht aufgelistet?", + "send": "Senden", + "sendErrorDialogBody": "Ein unbekannter Fehler ist aufgetreten. Die unten gezeigten Informationen können den Entwicklern per E-Mail zugesendet werden, um zu helfen, diesen Fehler in Zukunft zu vermeiden.", + "sendErrorLogDescription": "Es wird eine vorgefertigte E-Mail erstellt.\nSie enthält Informationen über die App, den Fehler und das Gerät.\nSie können die E-Mail vor dem Senden bearbeiten.\nWie wir die Informationen verwenden, sehen Sie hier:", + "sendPushRequestResponseFailed": "Senden der Antwort fehlgeschlagen.", + "sendingRSAPublicKey": "Sende öffentlichen RSA Schlüssel", + "sendingRSAPublicKeyFailed": "Senden des öffentlichen RSA Schlüssels fehlgeschlagen", + "serverNotReachable": "Der Server konnte nicht erreicht werden.", + "settings": "Einstellungen", + "settingsGroupGeneral": "Allgemeines", + "showDetails": "Details anzeigen", + "showErrorLog": "Anzeigen", + "showPrivacyPolicy": "Datenschutzerklärung anzeigen", + "signInTitle": "Authentifizierung wird benötigt", + "someTokensDoNotSupportPolling": "Einige der Token sind veraltet und unterstützen keine aktiven Anfragen", + "startRollout": "Starte Rollout", + "statusCode": "Statuscode: {statusCode}", + "sync": "Sync", + "syncContainerFailed": "Container synchronisation fehlgeschlagen", + "syncFbTokenFailed": "Synchronisation ist für die folgenden Token fehlgeschlagen:", + "synchronizePushTokens": "Synchronisiere Push Token", + "synchronizesTokensWithServer": "Synchronisiert Token mit dem privacyIDEA Server.", + "synchronizingTokens": "Synchronisiere Token.", + "theSecretDoesNotFitTheCurrentEncoding": "Das Geheimnis entspricht nicht der gewählten Verschlüsselung.", + "themeMode": "Farbschema", + "thisAppIsOpenSource": "Diese App ist Open Source\nBesuchen Sie uns auf GitHub", + "timeOut": "Zeitlimit", + "tokenDataParseError": "Token-Daten konnten nicht geparst werden", + "tokenDetails": "Tokendetails", + "tokenLink": "Token Link", + "tokenSerial": "Token serial", + "tokensAreEncrypted": "Die Token sind verschlüsselt. Bitte gib das Passwort ein, um sie zu entschlüsseln.", + "tokensDoNotSupportSynchronization": "Die folgenden Token unterstützen keine Synchronisation und müssen erneut ausgerollt werden:", + "tokensNotEncrypted": "Die Token sind unverschlüsselt und können direkt importiert werden.", + "tokensSuccessfullyDecrypted": "Die Token wurden erfolgreich entschlüsselt, sie können nun importiert werden.", + "type": "Art", + "unexpectedError": "Ein unerwarteter Fehler ist aufgetreten.", + "unknown": "Unbekannt", + "unlock": "Entsperren", + "unsupported": "Der {name} [{value}] wird von dieser Version der App nicht unterstützt.", + "useDeviceLocaleDescription": "Nutze Systemsprache, falls diese unterstützt wird. Anderenfalls nutze Englisch. ", + "useDeviceLocaleTitle": "Nutze Systemsprache", + "validFor": "Gültig für", + "validUntil": "Gültig bis", + "valueNotAllowed": "untranslated", + "valueNotAllowedIn": "untranslated", + "verboseLogging": "Ausführliche Protokollierung", + "version": "Version", + "wrongPassword": "Falsches Passwort", + "yes": "Ja" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 81d411bec..a384d7367 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,385 +1,362 @@ { - "@@last_modified": "2023-08-07", - "patchNotesNewFeatures": "New features", - "patchNotesImprovements": "Improvements", - "patchNotesBugFixes": "Bug fixes", - "patchNotesV4_4_0NewFeatures1": "It is now possible to export tokens where it can be ensured that they are not privacyIDEA tokens. Currently, it cannot be ruled out that tokens added via the QR code scanner originate from privacyIDEA. The differentiation will be improved in future versions.", - "patchNotesV4_4_0NewFeatures2": "Added support for privacyIDEA's \"require presence\"", - "patchNotesV4_4_0Improvement1": "Further import sources have been added.", - "patchNotesV4_4_0Improvement2": "Improved recognition of QR codes from image files.", - "patchNotesV4_3_1BugFix1": "Fixed an issue where the otp value was not displayed after authentication on some devices.", - "patchNotesV4_3_1Improvement1": "Improved the qr code scanner.", - "patchNotesV4_3_0NewFeatures1": "Support for importing tokens from Google, Aegis and 2FAS Authenticator has been added. More import sources will be added in the future.", - "patchNotesV4_3_0NewFeatures2": "Added feedback option to the settings.", - "patchNotesV4_3_0NewFeatures3": "Push tokens can now be hidden from the token list.", - "patchNotesV4_3_0NewFeatures4": "Introductions have been added to help new users get started.", - "patchNotesV4_3_0NewFeatures5": "You can now search for tokens by tapping the magnifying glass in the upper right corner.", - "patchNotesV4_3_0NewFeatures6": "Added HomeWidget token for Android 12 and later.", - "accept": "Accept", + "@@last_modified": "2024-09-20", + "@@locale": "en", "@accept": { "description": "Label for e.g. a button. Something gets accepted by the user." }, - "decline": "Decline", - "@decline": { - "description": "Label for e.g. a button. Something gets declined by the user." - }, - "name": "Name", - "@name": { - "description": "Describes the field where the tokens name should be entered." - }, - "secretKey": "Secret key", - "@secretKey": { - "description": "Describes the field where the tokens secret should be entered." - }, - "counter": "Counter", - "@counter": { - "description": "Describes the field where the tokens counter should be entered." - }, - "encoding": "Encoding", - "@encoding": { - "description": "Title of the dropdown button where the encoding is selected." + "@addToken": { + "description": "The button to open the screen to add tokens by hand." }, - "algorithm": "Algorithm", "@algorithm": { "description": "Title of the dropdown button where the encoding is selected." }, - "digits": "Digits", - "@digits": { - "description": "Title of the dropdown button where the number of digits for the opt value is selected." + "@algorithmUnsupported": { + "placeholders": { + "algorithm": { + "example": "MD5" + } + } }, - "type": "Type", - "@type": { - "description": "Title of the dropdown button where the type of the token is selected." + "@allTokensSynchronized": { + "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "period": "Period", - "@period": { - "description": "Title of the dropdown button where the period of the totp token is selected." + "@authNotSupportedBody": { + "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." }, - "rename": "Rename", - "@rename": { - "description": "Label that describes renaming the token." + "@authNotSupportedTitle": { + "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." }, - "cancel": "Cancel", - "@cancel": { - "description": "Button to cancel an action." + "@authToAcceptPushRequest": { + "description": "Message to accepting a push request for authentication." }, - "delete": "Delete", - "@delete": { - "description": "Label that describes deleting the token." + "@authToDeclinePushRequest": { + "description": "Message for declining a push request for authentication." }, - "dismiss": "Dismiss", - "@dismiss": { - "description": "Text of a button that closes a dialog." + "@authenticateToShowOtp": { + "description": "Reason to authenticate when trying to view a one time password." }, - "addToken": "Add token", - "@addToken": { - "description": "The button to open the screen to add tokens by hand." + "@authenticateToUnLockToken": { + "description": "Reason to authenticate when trying to lock or unlock a token." }, - "scanQrCode": "Scan QR-Code", - "@scanQrCode": { - "description": "The button to scan otpauth qr-codes." + "@biometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." }, - "enterDetailsForToken": "Enter token details", - "@enterDetailsForToken": { - "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." + "@biometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterANameForThisToken": "Please enter a name for this token.", - "@pleaseEnterANameForThisToken": { - "description": "Hint telling the user to enter a name for a token." + "@biometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterASecretForThisToken": "Please enter a secret for this token.", - "@pleaseEnterASecretForThisToken": { - "description": "Hint telling the user to enter a secret for a token." + "@biometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." }, - "theSecretDoesNotFitTheCurrentEncoding": "The secret does not fit the current encoding", - "@theSecretDoesNotFitTheCurrentEncoding": { - "description": "Hint telling the user that the secret does not fit the selected encoding." + "@cancel": { + "description": "Button to cancel an action." }, - "notAnNumber": "The value is not a number.", - "@notAnNumber": { - "description": "Error message when the user entered a value that is not a number." + "@checkServerCertificate": { + "description": "Error message when the server certificate should be checked." }, - "notAnInteger" : "The value is not an integer.", - "@notAnInteger": { - "description": "Error message when the user entered a value that is not an integer." + "@checkYourNetwork": { + "description": "Tells the user to check the network connection." }, - "renameToken": "Rename token", - "@renameToken": { - "description": "Title of the dialog where a new name for a token can be entered." + "@clearErrorLog": { + "description": "Button to clear the error log." }, - "confirmDeletion": "Confirm deletion", "@confirmDeletion": { "description": "Title of the dialog where a token can be deleted." }, - "confirmDeletionOf": "Are you sure you want to delete {name}?", "@confirmDeletionOf": { "description": "Asks for confirmation on deleting a token.", "placeholders": { "name": { "example": "PUSH1234" } - } - }, - "confirmTokenDeletionHint": "You may no longer be able to log in if you delete this token.\nPlease make sure that you can log in to the associated account without this token.", - "@confirmTokenDeletionHint": { - "description": "Gives the user a hint about the consequences of deleting a token." + }, + "type": "text" }, - "confirmFolderDeletionHint": "Deleting a folder has no effect on the tokens in it.\nThe tokens are moved to the main list.", "@confirmFolderDeletionHint": { "description": "Gives the user a hint about the consequences of deleting a folder." }, - "generatingPhonePart": "Generating phone part", - "@generatingPhonePart": { - "description": "Title of a dialog telling the user that the phone part gets generated right now." + "@confirmTokenDeletionHint": { + "description": "Gives the user a hint about the consequences of deleting a token." }, - "phonePart": "Phone part:", - "@phonePart": { - "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." + "@connectionFailed": { + "description": "Tells the user that the connection failed." }, - "otpValueCopiedMessage": "Password \"{otpValue}\" copied to clipboard.", - "@otpValueCopiedMessage": { - "description": "Tells the user that the otp value was copied to the clipboard.", - "placeholders": { - "otpValue": { - "example": "055374" - } - } + "@container": { + "description": "Title for the container view." }, - "settings": "Settings", - "@settings": { - "description": "Button to open the settings page." + "@couldNotSignMessage": { + "description": "Tells the user that the message could not be signed." }, - "pushToken": "Push Token", - "@pushToken": { - "description": "Title for the settings block concerning the push tokens." + "@counter": { + "description": "Describes the field where the tokens counter should be entered." }, - "theme": "Theme", - "@theme": { - "description": "Title of the setting group where the theme can be selected." + "@createdAt": { + "description": "Label for the creation date of the token." }, - "lightTheme": "Light", - "@lightTheme": { - "description": "The light theme." + "@creator": { + "description": "Label for the creator of the token." }, - "darkTheme": "Dark", "@darkTheme": { "description": "The dark theme." }, - "systemTheme": "Use device's theme", - "@systemTheme": { - "description": "The systems theme." + "@decline": { + "description": "Label for e.g. a button. Something gets declined by the user." }, - "someTokensDoNotSupportPolling": "Some of the tokens are outdated and do not support polling", - "@someTokensDoNotSupportPolling": { - "description": "Tells the user, that the following tokens do not support polling.", - "type": "text", - "placeholders": {} + "@delete": { + "description": "Label that describes deleting the token." }, - "enablePolling": "Enable polling", - "@enablePolling": { - "description": "Name of the setting switch that enables polling." + "@deviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." }, - "requestPushChallengesPeriodically": "Request push challenges from the server periodically. Enable this if push challenges are not received normally.", - "@requestPushChallengesPeriodically": { - "description": "The description of the polling feature." + "@deviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." }, - "synchronizePushTokens": "Synchronize push tokens", - "@synchronizePushTokens": { - "description": "Title of synchronizing push tokens in settings." + "@digits": { + "description": "Title of the dropdown button where the number of digits for the opt value is selected." }, - "synchronizesTokensWithServer": "Synchronizes tokens with the privacyIDEA server.", - "@synchronizesTokensWithServer": { - "description": "Description of synchronizing push tokens in settings." + "@dismiss": { + "description": "Text of a button that closes a dialog." }, - "sync": "Sync", - "@sync": { - "description": "Text of button that is used to synchronize push tokens." + "@enablePolling": { + "description": "Name of the setting switch that enables polling." }, - "synchronizingTokens": "Synchronizing tokens.", - "@synchronizingTokens": { - "description": "Title of the push synchronization dialog." + "@encoding": { + "description": "Title of the dropdown button where the encoding is selected." }, - "allTokensSynchronized": "All tokens are synchronized.", - "@allTokensSynchronized": { - "description": "Content of the push synchronization dialog. Signaling the user that everything worked." + "@enterDetailsForToken": { + "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." }, - "syncFbTokenFailed": "Synchronization failed for the following tokens, please try again:", - "@syncFbTokenFailed": { - "description": "Headline for the list of tokens where the synchronization failed." + "@errorLogCleared": { + "description": "Message that tells the user that the error log was cleared." }, - "tokensDoNotSupportSynchronization": "The following tokens do not support synchronization and must be rolled out again:", - "@tokensDoNotSupportSynchronization": { - "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + "@errorLogEmpty": { + "description": "Message that tells the user that the error log is empty." + }, + "@errorLogTitle": { + "description": "Title of the error log screen." + }, + "@errorMailBody": { + "description": "Message for email body" }, - "errorRollOutFailed": "Rolling out token {name} failed.", "@errorRollOutFailed": { "description": "Tells the user that the token could not be rolled out, because a network error occurred.", "placeholders": { "name": { "example": "PUSH1234A" } - } + }, + "type": "text" }, - "statusCode": "Status code: {statusCode}", - "@statusCode": { - "description": "Tells the user the status code of the error.", + "@errorRollOutNoConnectionToServer": { + "description": "Tells the user that the roll-out failed because the server could not be reached.", "placeholders": { - "statusCode": { - "example": "400" + "name": { + "example": "PUSH1234A" } } }, - "errorSynchronizationNoNetworkConnection": "Synchronizing tokens failed, privacyIDEA server could not be reached.", - "@errorSynchronizationNoNetworkConnection": { - "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." - }, - "errorRollOutNoConnectionToServer": "Rolling out token {name} failed, the server could not be reached.", - "@errorRollOutNoConnectionToServer": { - "description": "Tells the user that the roll-out failed because the server could not be reached." + "@errorRollOutSSLHandshakeFailed": { + "description": "Tells the user that the roll-out failed because the SSL handshake failed." }, - "errorRollOutUnknownError": "An unknown error occurred. Roll-out not possible: {e}", "@errorRollOutUnknownError": { "description": "Tells the user that the roll-out failed because of an unknown error.", "placeholders": { "e": { "example": "IllegalArgumentException on Line 5 ..." } - } - }, - "startRollout": "Start rollout", - "@startRollout": { - "description": "Label that tells the user that the token is being rolled out." - }, - "pollingChallenges": "Polling for new challenges", - "@pollingChallenges": { - "placeholders": {} + }, + "type": "text" }, - "unexpectedError": "An unexpected error occurred.", - "@unexpectedError": { - "description": "Title of page report mode." + "@errorSynchronizationNoNetworkConnection": { + "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." }, - "pollingFailed": "Polling failed.", - "@pollingFailed": { - "description": "Tells the user that the polling failed." + "@errorTokenExpired": { + "placeholders": { + "name": { + "example": "PUSH1234A" + } + } }, - "pollingFailedFor": "Polling failed for {serial}", - "@pollingFailedFor": { - "description": "Tells the user that the polling failed.", + "@errorUnlinkingPushToken": { + "description": "Error message when unlinking a push token failed.", "placeholders": { - "serial": { + "label": { "example": "PUSH1234A" } } }, - "noNetworkConnection": "No network connection.", - "@noNetworkConnection": { - "description": "Tells the user that there is no network connection." + "@errorWhenPullingChallenges": { + "description": "errorWhenPullingChallenges", + "placeholders": { + "name": { + "example": "PUSH1234A" + } + } }, - "connectionFailed": "Connection failed.", - "@connectionFailed": { - "description": "Tells the user that the connection failed." + "@failedToLoad": { + "placeholders": { + "name": { + "example": "token data" + } + } }, - "checkYourNetwork": "Please check your network connection and try again.", - "@checkYourNetwork": { - "description": "Tells the user to check the network connection." + "@feedbackPrivacyPolicy2": { + "description": "Taping on this should open the privacy policy." }, - "serverNotReachable": "The server could not be reached.", - "@serverNotReachable": { - "description": "Tells the user that the server could not be reached." + "@generatingPhonePart": { + "description": "Title of a dialog telling the user that the phone part gets generated right now." }, - "couldNotSignMessage": "Could not sign message.", - "@couldNotSignMessage": { - "description": "Tells the user that the message could not be signed." + "@generatingRSAKeyPair": { + "description": "Message for the rollout process" }, - "useDeviceLocaleTitle": "Use device language", - "@useDeviceLocaleTitle": { - "description": "Title of the switch tile where using the devices language can be enabled." + "@generatingRSAKeyPairFailed": { + "description": "Message for the rollout process" }, - "useDeviceLocaleDescription": "Use device language if it is supported, otherwise default to english.", - "@useDeviceLocaleDescription": { - "description": "Description of the switch tile where using the devices language can be enabled." + "@goToSettingsButton": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." }, - "language": "Language", - "@language": { - "description": "Title of language setting group." + "@goToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." }, - "authenticateToShowOtp": "Please authenticate to show one time password.", - "@authenticateToShowOtp": { - "description": "Reason to authenticate when trying to view a one time password." + "@guide": { + "description": "Button to open the guide screen." }, - "authenticateToUnLockToken": "Please authenticate to change the lock status of the token.", - "@authenticateToUnLockToken": { - "description": "Reason to authenticate when trying to lock or unlock a token." + "@handshakeFailed": { + "description": "Error message when the handshake failed." }, - "biometricRequiredTitle": "Biometrics not setup", - "@biometricRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + "@importedVia": { + "description": "Label for the import method of the token." }, - "biometricHint": "Authentication required", - "@biometricHint": { - "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + "@internalServerError": { + "placeholders": { + "code": { + "example": "500" + } + } }, - "biometricNotRecognized": "Not recognized. Try again.", - "@biometricNotRecognized": { - "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + "@invalidValue": { + "description": "Error message when the value is not valid for the parameter.", + "placeholders": { + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "biometricSuccess": "Authentication successful", - "@biometricSuccess": { - "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + "@invalidValueIn": { + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "deviceCredentialsRequiredTitle": "Device credentials not set up", - "@deviceCredentialsRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + "@isExpotableQuestion": { + "description": "Label for the question if the token is exportable." }, - "deviceCredentialsSetupDescription": "Setup device credentials in the device's settings", - "@deviceCredentialsSetupDescription": { - "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + "@isPiTokenQuestion": { + "description": "Label for the question if the token is a privacyIDEA token." }, - "signInTitle": "Authentication required", - "@signInTitle": { - "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + "@language": { + "description": "Title of language setting group." }, - "goToSettingsButton": "Go to settings", - "@goToSettingsButton": { - "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + "@legacySigningErrorMessage": { + "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." }, - "goToSettingsDescription": "Authentication by credentials or biometrics is not set up on your device. Please set it up in the device's settings.", - "@goToSettingsDescription": { - "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." + "@legacySigningErrorTitle": { + "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", + "placeholders": { + "tokenLabel": { + "example": "PUSH1234A" + } + } + }, + "@lightTheme": { + "description": "The light theme." + }, + "@linkedContainer": { + "description": "Label for the linked container serial number." + }, + "@lock": { + "description": "Description of button that locks a token." }, - "lockOut": "Biometric authentication is disabled. Please lock and unlock your screen to enable it.", "@lockOut": { "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." }, - "authNotSupportedTitle": "Device credentials or biometrics required", - "@authNotSupportedTitle": { - "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." + "@logMenu": { + "description": "Button to open the log menu." }, - "authNotSupportedBody": "This action requires the device to be secured by credentials or biometrics.", - "@authNotSupportedBody": { - "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." + "@malformedData": { + "description": "Error message when the data is malformed." }, - "lock": "Lock", - "@lock": { - "description": "Description of button that locks a token." + "@missingRequiredParameter": { + "description": "Error message when a required parameter is missing.", + "placeholders": { + "parameter": { + "example": "counter" + } + } }, - "unlock": "Unlock", - "@unlock": { - "description": "Description of button that unlocks a token." + "@missingRequiredParameterIn": { + "description": "Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.", + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + } + } }, - "noResultTitle": "No tokens stored yet.", - "@noResultTitle": { - "description": "No tokens stored yet." + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, + "@name": { + "description": "Describes the field where the tokens name should be entered." + }, + "@noFbToken": { + "description": "Tells the user that there is no Firebase token available." + }, + "@noNetworkConnection": { + "description": "Tells the user that there is no network connection." }, - "noResultText1": "Tap the ", "@noResultText1": { "description": "first no result text" }, - "noResultText2": " button to get started!", "@noResultText2": { "description": "second no result text" }, - "onBoardingTitle1": "{appName}", + "@noResultTitle": { + "description": "No tokens stored yet." + }, + "@notAnInteger": { + "description": "Error message when the user entered a value that is not an integer." + }, + "@notAnNumber": { + "description": "Error message when the user entered a value that is not a number." + }, + "@ok": { + "description": "Button to confirm an action." + }, "@onBoardingTitle1": { "placeholders": { "appName": { @@ -387,350 +364,205 @@ } } }, - "onBoardingText1": "Two-factor authentication\nmade easy", - "onBoardingTitle2": "Maximum Security", - "onBoardingText2": "Store tokens on your device securely\nProtected by your biometrics", - "onBoardingTitle3": "Visit us at Github", - "onBoardingText3": "This app is open source", - "errorLogTitle": "Error log", - "logMenu": "Log menu", - "showErrorLog": "Show", - "clearErrorLog": "Clear", - "send": "Send", - "sendErrorLogDescription": "A predefined email is created.\nIt contains information about the app, the error and the device.\nYou can edit the email before sending it.\nYou can see here how we use the information:", - "@sendErrorLogDescription": { - "description": "Explanation for the user what he will send." - }, - "showPrivacyPolicy": "Show privacy policy", - "errorLogEmpty": "The error log is empty.", - "verboseLogging": "Verbose logging", - "errorLogCleared": "Error log cleared.", - "ok": "Ok", - "errorMailBody": "The error log file is attached.\nYou can replace this text with additional information about the error.", - "@errorMailBody": { - "description": "Message for email body" - }, - "showDetails": "Show details", - "open": "Open", - "sendErrorDialogBody": "An unexpected error occurred in the application. The information below can be send to the developers by email to help prevent this error in the future.", - "@sendErrorDialogBody": { - "description": "Description shown to the user about what info the error report contains." - }, - "noFbToken": "No Firebase token available", - "firebaseToken": "Firebase Token", - "noPublicKey": "No public key available", - "publicKey": "Public Key", - "editToken": "Edit Token", - "edit": "Edit", - "save": "Save", - "create": "Create", - "validFor": "Valid for", - "validUntil": "Valid until", - "deleteLockedToken": "Please authenticate to delete the locked token.", - "editLockedToken": "Please authenticate to edit the locked token.", - "expandLockedFolder": "Please authenticate to uncollapse the locked folder.", - "renameTokenFolder": "Rename folder", - "@renameTokenFolder": { - "description": "Title of the dialog where a new name for a token folder can be entered." - }, - "addANewFolder": "Create new folder", - "folderName": "Folder name", - "retryRollout": "Retry rollout", - "generatingRSAKeyPair": "Generating RSA key pair", - "@generatingRSAKeyPair": { - "description": "Message for the rollout process" + "@open": { + "description": "Button to open something." }, - "generatingRSAKeyPairFailed": "Generating RSA key pair failed", - "@generatingRSAKeyPairFailed": { - "description": "Message for the rollout process" + "@originApp": { + "description": "Label for the origin app." }, - "sendingRSAPublicKey": "Sending public RSA key", - "@sendingRSAPublicKey": { - "description": "Message for the rollout process" + "@originDetails": { + "description": "Title of the origin details menu." }, - "sendingRSAPublicKeyFailed": "Sending public RSA key failed", - "@sendingRSAPublicKeyFailed": { - "description": "Message for the rollout process" + "@otpValueCopiedMessage": { + "description": "Tells the user that the otp value was copied to the clipboard.", + "placeholders": { + "otpValue": { + "example": "055374" + } + }, + "type": "text" }, - "parsingResponse": "Parsing response", "@parsingResponse": { "description": "Message for the rollout process" }, - "parsingResponseFailed": "Parsing response failed", "@parsingResponseFailed": { "description": "Message for the rollout process" }, - "rolloutCompleted": "Rollout completed", - "@rolloutCompleted": { - "description": "Message for the rollout process" + "@period": { + "description": "Title of the dropdown button where the period of the totp token is selected." }, - "authToAcceptPushRequest": "Please authenticate to accept the push request.", - "authToDeclinePushRequest": "Please authenticate to decline the push request.", - "pushRequestParseError": "Push request could not be parsed.", - "imageUrl": "Image URL", - "errorRollOutSSLHandshakeFailed": "SSL handshake failed. Roll-out not possible.", - "@errorRollOutSSLHandshakeFailed": { - "description": "Tells the user that the roll-out failed because the SSL handshake failed." + "@phonePart": { + "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." }, - "errorWhenPullingChallenges": "An error occured when polling for challenges of {name}", - "@errorWhenPullingChallenges": { - "description": "errorWhenPullingChallenges", - "placeholders": { - "name": { - "example": "PUSH1234A" - } - } + "@pleaseEnterANameForThisToken": { + "description": "Hint telling the user to enter a name for a token." }, - "couldNotConnectToServer": "Could not connect to server", - "errorRollOutNotPossibleAnymore": "Rolling out this Token is not possible anymore.", - "errorTokenExpired": "The token {name} has expired.", - "@errorTokenExpired": { - "placeholders": { - "name": { - "example": "PUSH1234A" - } - } + "@pleaseEnterASecretForThisToken": { + "description": "Hint telling the user to enter a secret for a token." }, - "yes": "Yes", - "no": "No", - "butDiscardIt": "but discard it", - "declineIt": "decline it", - "requestTriggerdByUserQuestion": "Was this request triggered by you?", - "grantCameraPermissionDialogTitle": "Camera permission is not granted", - "grantCameraPermissionDialogContent": "Please grant camera permission to scan QR codes.", - "grantCameraPermissionDialogPermanentlyDenied": "Camera permission is permanently denied. Please grant camera permission in your Phone's settings.", - "grantCameraPermissionDialogButton": "Grant permission", - "decryptErrorTitle": "Decryption error", - "decryptErrorContent": "Unfortunately, the app was unable to decrypt your tokens. This indicates that the encryption key is broken. You can try again or delete the app data, which would delete the tokens in the app.", - "decryptErrorButtonDelete": "Delete", - "decryptErrorButtonSendError": "Send error", - "decryptErrorButtonRetry": "Retry", - "decryptErrorDeleteConfirmationContent": "Are you sure you want to delete the app data?", - "hidePushTokens": "Hide push tokens", - "hidePushTokensDescription": "Hide push tokens from the token list. This will not delete the tokens and they will still be visible on a separate screen.", - "settingsGroupGeneral": "General", - "licensesAndVersion": "Licenses and version", - "privacyPolicy": "Privacy policy", - "introScanQrCode": "You can scan QR codes to add tokens.\nWe support every common Two-Factor-Authentication token and also the privacyIDEA tokens.", - "introAddTokenManually": "If you don't want to scan a QR code, you can also add tokens manually.", - "introTokenSwipe": "Swipe tokens to the left to see available actions.", - "introEditToken": "Here you can edit the token name and see some details.", - "introLockToken": "To improve security even more, you can lock tokens.\nThen the token can only be used after authentication.", - "introDragToken": "Reorganize your tokens by pressing it for a few seconds and then dragging it to the desired position.", - "introAddFolder": "You can create folders\nto organize your tokens.", - "introPollForChallenges": "You can check for new challenges by dragging down the token list.", - "introHidePushTokens": "Your push tokens are hidden now.\nBut you can still see them on the push token screen.", - "legacySigningErrorTitle": "An error occured while using the legacy token: {tokenLabel}", - "@legacySigningErrorTitle": { - "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", + "@pollingChallenges": { + "type": "text" + }, + "@pollingFailed": { + "description": "Tells the user that the polling failed." + }, + "@pollingFailedFor": { + "description": "Tells the user that the polling failed.", "placeholders": { - "tokenLabel": { + "serial": { "example": "PUSH1234A" } } }, - "legacySigningErrorMessage": "The token was enrolled in a old version of this app, which may cause trouble using it.\nIt is suggested to enroll a new push token if the problem persist!", - "@legacySigningErrorMessage": { - "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." + "@pushToken": { + "description": "Title for the settings block concerning the push tokens." }, - "selectImportSource": "Select import source", - "selectImportType": "How do you want to import the tokens?", - "importTokens": "Import token", - "importNTokens": "{count, plural, zero{Import no token} one{Import one token} other{Import {count} tokens}}", - "selectFile": "Select file", - "decrypt": "Decrypt", - "tokensAreEncrypted": "The tokens are encrypted. Please enter the password to decrypt them.", - "tokensNotEncrypted": "The tokens are not encrypted and can be imported directly.", - "tokensSuccessfullyDecrypted": "The tokens have been successfully decrypted and can now be imported.", - "password": "Password", - "wrongPassword": "Incorrect password", - "qrScan": "Scan", - "enterLink": "Enter link", - "invalidBackupFile": "The selected file is not a valid backup of {appName}.", - "invalidQrScan": "The scanned QR code is not a valid backup of {appName}.", - "invalidQrFile": "The selected file does not contain a valid QR code from {appName}.", - "invalidLink": "The link entered is not a valid token of {appName}, or it is not supported.", - "importFailedToken": "{count, plural, zero{No token Failed to import.} one{Failed to import a token.} other{Failed to import {count} tokens.}}", - "importExistingToken": "{count, plural, zero{No token was found that is already in the application.} one{A token was found that already exists in the application.} other{{count} tokens was found that are already in the application.}}", - "importConflictToken": "{count, plural, zero{There is no conflict with existing tokens.} one{There is a conflict with existing tokens.\nPlease select which one you would like to keep.} other{There are conflicts with existing tokens.\nPlease select the tokens you wish to keep.}}", - "importNewToken": "{count, plural, zero{No new token was found.} one{A new token was found that can be imported.} other{{count} new tokens were found that can be imported.}}", - "importHintPrivacyIdeaQrScan": "Um QR-Codes der Token zu erstellen, navigieren Sie zu den Einstellungen und tippen auf \"Exportieren\". Wählen Sie dann \"Als QR-Code\" und tippen Sie auf den zu exportierenden Token. Diese Variante ist nur für die direkte Übertragung auf ein anderes Gerät geeignet, da der QR-Code nicht verschlüsselt ist.", - "importHintPrivacyIdeaFile": "To create a backup, go to the settings and tap on \"Export\". Select \"As file\", select the tokens you want to export. Then tap on \"Export\" and set a password. The storage location is the download folder on your device.", - "importHint2FAS": "Select your 2FAS backup.\nIf you do not have a backup, create one in the 2FAS app. We recommend using a password.", - "importHintAegisBackupFile": "Select your Aegis export (.JSON).\nIf you do not have an export, please create one via the settings menu in the Aegis app. The use of a password is recommended.", - "importHintAegisQrScan": "Scan the QR code you receive when you transfer entries from Aegis.", - "importHintAegisLink": "Enter the link you receive when you transfer entries from Aegis.", - "importHintGoogleQrScan": "Scan the QR code you receive when you export your accounts from Google Authenticator.", - "importHintGoogleQrFile": "Select an image file with the QR code you receive when you export your accounts from Google Authenticator.\n!! Note that it is not safe to save the QR code on your device as the tokens are not encrypted !!", - "importHintAuthenticatorProFile": "To create a backup of the Authenticator Pro app, navigate to the settings and tap on \"Auto backup\". Select a storage location and set a password. Then press \"Back up now\" to export the tokens.", - "importHintFreeOtpPlusQrScan": "Scan the QR code you receive when you press the three dots in the tile of the token and select \"Share QR code\".", - "importHintFreeOtpPlusFile": "To create a backup of the FreeOTP+ app, tap on the three dots in the upper right corner and select \"Export\". You can choose between JSON and URI format. We recommend to delete the backup after importing it, because it is not encrypted.", - "qrFileDecodeError": "It was not possible to decode the QR code from the selected image, please use the QR code scanner instead.", - "tokenLink": "Token link", - "feedback": "Feedback", - "feedbackTitle": "Your feedback is always welcome!", - "feedbackDescription": "If you have any questions, suggestions or problems, please let us know.", - "feedbackHint": "A ready-made e-mail will open, which you can send to us. If desired, information about your device and the version of the application will be added. You can check and edit the email before sending it.", - "feedbackPrivacyPolicy1": "By sending the feedback you agree to our ", - "feedbackPrivacyPolicy2": "privacy policy", - "@feedbackPrivacyPolicy2": { - "description": "Taping on this should open the privacy policy." + "@rename": { + "description": "Label that describes renaming the token." + }, + "@renameToken": { + "description": "Title of the dialog where a new name for a token can be entered." + }, + "@renameTokenFolder": { + "description": "Title of the dialog where a new name for a token folder can be entered." }, - "feedbackPrivacyPolicy3": ".", - "addSystemInfo": "Add system information", - "feedbackSentTitle": "Feedback sent", - "feedbackSentDescription": "Thank you very much for your help in making this application better!", - "patchNotesDialogTitle": "What's new?", - "version": "Version", - "noMailAppTitle": "No mail app found", - "noMailAppDescription": "There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message.", - "authenticationRequest": "Authentication request", - "requestInfo": "Sent by {issuer} for your account: \"{account}\"", "@requestInfo": { "description": "Description of the authentication request.", "placeholders": { - "issuer": { - "example": "privacyIDEA" - }, "account": { "example": "GitHub" + }, + "issuer": { + "example": "privacyIDEA" } } }, - "errorUnlinkingPushToken": "Failed to unlink the push token {label}.", - "@errorUnlinkingPushToken": { - "description": "Error message when unlinking a push token failed.", - "placeholders": { - "label": { - "example": "PUSH1234A" - } - } + "@requestPushChallengesPeriodically": { + "description": "The description of the polling feature." }, - "pleaseSyncManuallyWhenNetworkIsAvailable": "Please synchronize the push tokens manually via the settings when a network connection is available.", - "pushTokens": "Push Tokens", - "continueButton": "Continue", - "addTokenManually": "Add token manually", - "addFolder": "Add folder", - "searchTokens": "Search tokens", - "closeSearchTokens": "Close search", - "increaseCounter": "Increase counter", - "copyOTPToClipboard": "Copy OTP to clipboard", - "licenses": "Licenses", - "optionalMessage": "Optional message", - "confirmation": "Confirmation", - "askLogSendedDescription": "Did you send the log, and do you want to clear it now?", - "algorithmUnsupported": "The algorithm {algorithm} is not supported", - "@algorithmUnsupported": { - "placeholders": { - "algorithm": { - "example": "MD5" - } - } + "@retry": { + "description": "Label for e.g. a button. Something is tried to be done again." }, - "thisAppIsOpenSource": "This Application is Open Source\nVisit us on GitHub", - "importExportTokens": "Import/Export tokens", - "exportNonPrivacyIDEATokens": "Export non-privacyIDEA tokens", - "selectTokensToExport": "{count, plural, zero{} one{Select token to export} other{Select tokens to export}}", - "noTokenToExport": "No token available for export", - "exportAllTokens": "Export all tokens", - "export": "Export", - "exportingTokens": "Exporting tokens...", - "exportTokens": "Export tokens", - "enterPasswordToEncrypt": "Enter a password to encrypt the tokens. This password will be required to import the tokens.", - "exportLockedTokenReason": "Please authenticate to export locked tokens.", - "fileSavedToDownloadsFolder": "File saved to Downloads folder", - "errorSavingFile": "Saving to file failed", - "asQrCode": "As QR code", - "asFile": "As file", - "scanThisQrWithNewDevice": "Scan this QR code with your new device to import the token.", - "oneMore": "One more", - "done": "Done", - "confirmPassword": "Confirm password", - "secretIsRequired": "Secret is required", - "tokenDataParseError": "Token data could not be parsed", - "missingRequiredParameter": "Value for parameter [{counter}] is required and is missing", - "@missingRequiredParameter": { - "placeholders": { - "counter": { - "example": "counter" - } - } - }, "missingRequiredParameterIn": "Value for parameter [{parameter}] is required and is missing in \"{map}\"", - "@missingRequiredParameterIn": { - "placeholders": { - "parameter": { - "example": "counter" - }, - "map": { - "example": "query parameters" - } - } + "@rolloutCompleted": { + "description": "Message for the rollout process" }, - "invalidValue": "The {type} \"{value}\" is not valid for \"{parameter}\"", - "@invalidValue": { - "placeholders": { - "type": { - "example": "int" - }, - "value": { - "example": "5" - }, - "parameter": { - "example": "counter" - } - } + "@scanQrCode": { + "description": "The button to scan otpauth qr-codes." }, - "invalidValueIn": "The {type} \"{value}\" is not valid for \"{parameter}\" in \"{map}\"", - "@invalidValueIn": { - "placeholders": { - "type": { - "example": "int" - }, - "value": { - "example": "5" - }, - "parameter": { - "example": "counter" - }, - "map": { - "example": "query parameters" - } - - } + "@secretIsRequired": { + "description": "Error message when the secret is missing." }, - "valueNotAllowed": "The {type} \"{value}\" is not an allowed value for \"{parameter}\"", - "@valueNotAllowed": { - "placeholders": { - "type": { - "example": "int" - }, - "value": { - "example": "-1" - }, - "parameter": { - "example": "counter" - } - } + "@secretKey": { + "description": "Describes the field where the tokens secret should be entered." }, - "valueNotAllowedIn": "The {type} \"{value}\" is not an allowed value for \"{parameter}\" in \"{map}\"", - "@valueNotAllowedIn": { - "placeholders": { - "type": { - "example": "int" - }, - "value": { - "example": "-1" - }, - "parameter": { - "example": "counter" - }, - "map": { - "example": "query parameters" + "@send": { + "description": "Button to send the error log." + }, + "@sendErrorDialogBody": { + "description": "Description shown to the user about what info the error report contains." + }, + "@sendErrorLogDescription": { + "description": "Explanation for the user what he will send." + }, + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." + }, + "@sendingRSAPublicKey": { + "description": "Message for the rollout process" + }, + "@sendingRSAPublicKeyFailed": { + "description": "Message for the rollout process" + }, + "@serverNotReachable": { + "description": "Tells the user that the server could not be reached." + }, + "@settings": { + "description": "Button to open the settings page." + }, + "@showDetails": { + "description": "Button to show details." + }, + "@showErrorLog": { + "description": "Button to show the error log." + }, + "@showPrivacyPolicy": { + "description": "Button to show the privacy policy." + }, + "@signInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "@someTokensDoNotSupportPolling": { + "description": "Tells the user, that the following tokens do not support polling.", + "type": "text" + }, + "@startRollout": { + "description": "Label that tells the user that the token is being rolled out." + }, + "@statusCode": { + "description": "Tells the user the status code of the error.", + "placeholders": { + "statusCode": { + "example": "400" } - } }, - "unsupported": "The {name} [{value}] is not supported by this version of the app.", + "@sync": { + "description": "Text of button that is used to synchronize push tokens." + }, + "@syncContainerFailed": { + "description": "Error message when synchronizing a container failed." + }, + "@syncFbTokenFailed": { + "description": "Headline for the list of tokens where the synchronization failed." + }, + "@synchronizePushTokens": { + "description": "Title of synchronizing push tokens in settings." + }, + "@synchronizesTokensWithServer": { + "description": "Description of synchronizing push tokens in settings." + }, + "@synchronizingTokens": { + "description": "Title of the push synchronization dialog." + }, + "@systemTheme": { + "description": "The systems theme." + }, + "@theSecretDoesNotFitTheCurrentEncoding": { + "description": "Hint telling the user that the secret does not fit the selected encoding." + }, + "@theme": { + "description": "Title of the setting group where the theme can be selected." + }, + "@themeMode": { + "description": "Title of the setting group where the theme mode can be changed." + }, + "@timeOut": { + "description": "Error message when a request times out." + }, + "@tokenDataParseError": { + "description": "Error message when the token data could not be parsed." + }, + "@tokenDetails": { + "description": "Title of the token details menu." + }, + "@tokenSerial": { + "description": "Label for the token serial number." + }, + "@tokensDoNotSupportSynchronization": { + "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + }, + "@type": { + "description": "Title of the dropdown button where the type of the token is selected." + }, + "@unexpectedError": { + "description": "Title of page report mode." + }, + "@unknown": { + "description": "Tells that something is unknown." + }, + "@unlock": { + "description": "Description of button that unlocks a token." + }, "@unsupported": { "placeholders": { "name": { @@ -741,55 +573,348 @@ } } }, - "pushEndpointUrl": "Push endpoint URL", - "exampleUrl": "Please enter a valid URL like: \"https://example.com/\"", - "mustNotBeEmpty": "{field} must not be empty", - "@mustNotBeEmpty": { + "@useDeviceLocaleDescription": { + "description": "Description of the switch tile where using the devices language can be enabled." + }, + "@useDeviceLocaleTitle": { + "description": "Title of the switch tile where using the devices language can be enabled." + }, + "@valueNotAllowed": { "placeholders": { - "field": { - "example": "Name" + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "-1" } } }, - "sendPushRequestResponseFailed": "Failed to send the response.", - "@sendPushRequestResponseFailed": { - "description": "Error message when the response to a push request could not be sent." + "@valueNotAllowedIn": { + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "-1" + } + } + }, + "@verboseLogging": { + "description": "Title of the switch tile where verbose logging can be enabled." }, + "accept": "Accept", + "addANewFolder": "Create new folder", + "addFolder": "Add folder", + "addSystemInfo": "Add system information", + "addToken": "Add token", + "addTokenManually": "Add token manually", + "algorithm": "Algorithm", + "algorithmUnsupported": "The algorithm {algorithm} is not supported", + "allTokensSynchronized": "All tokens are synchronized.", + "asFile": "As file", + "asQrCode": "As QR code", + "askLogSendedDescription": "Did you send the log, and do you want to clear it now?", + "authNotSupportedBody": "This action requires the device to be secured by credentials or biometrics.", + "authNotSupportedTitle": "Device credentials or biometrics required", + "authToAcceptPushRequest": "Please authenticate to accept the push request.", + "authToDeclinePushRequest": "Please authenticate to decline the push request.", + "authenticateToShowOtp": "Please authenticate to show one time password.", + "authenticateToUnLockToken": "Please authenticate to change the lock status of the token.", + "authenticationRequest": "Authentication request", + "biometricHint": "Authentication required", + "biometricNotRecognized": "Not recognized. Try again.", + "biometricRequiredTitle": "Biometrics not setup", + "biometricSuccess": "Authentication successful", + "butDiscardIt": "but discard it", + "cancel": "Cancel", + "checkServerCertificate": "Please check the server certificate", + "checkYourNetwork": "Please check your network connection and try again.", + "clearErrorLog": "Clear", + "closeSearchTokens": "Close search", + "confirmDeletion": "Confirm deletion", + "confirmDeletionOf": "Are you sure you want to delete {name}?", + "confirmFolderDeletionHint": "Deleting a folder has no effect on the tokens in it.\nThe tokens are moved to the main list.", + "confirmPassword": "Confirm password", + "confirmTokenDeletionHint": "You may no longer be able to log in if you delete this token.\nPlease make sure that you can log in to the associated account without this token.", + "confirmation": "Confirmation", + "connectionFailed": "Connection failed.", + "container": "Container", + "continueButton": "Continue", + "copyOTPToClipboard": "Copy OTP to clipboard", + "couldNotConnectToServer": "Could not connect to server", + "couldNotSignMessage": "Could not sign message.", + "counter": "Counter", + "create": "Create", + "createdAt": "Created on", + "creator": "Creator", + "decline": "Decline", + "declineIt": "decline it", + "decrypt": "Decrypt", + "decryptErrorButtonDelete": "Delete", + "decryptErrorButtonRetry": "Retry", + "decryptErrorButtonSendError": "Send error", + "decryptErrorContent": "Unfortunately, the app was unable to decrypt your tokens. This indicates that the encryption key is broken. You can try again or delete the app data, which would delete the tokens in the app.", + "decryptErrorDeleteConfirmationContent": "Are you sure you want to delete the app data?", + "decryptErrorTitle": "Decryption error", + "delete": "Delete", + "deleteLockedToken": "Please authenticate to delete the locked token.", + "deviceCredentialsRequiredTitle": "Device credentials not set up", + "deviceCredentialsSetupDescription": "Setup device credentials in the device's settings", + "digits": "Digits", + "dismiss": "Dismiss", + "done": "Done", + "edit": "Edit", + "editLockedToken": "Please authenticate to edit the locked token.", + "editToken": "Edit Token", + "enablePolling": "Enable polling", + "encoding": "Encoding", + "enterDetailsForToken": "Enter token details", + "enterLink": "Enter link", + "enterPasswordToEncrypt": "Enter a password to encrypt the tokens. This password will be required to import the tokens.", + "errorLogCleared": "Error log cleared.", + "errorLogEmpty": "The error log is empty.", + "errorLogTitle": "Error log", + "errorMailBody": "The error log file is attached.\nYou can replace this text with additional information about the error.", + "errorRollOutFailed": "Rolling out token {name} failed.", + "errorRollOutNoConnectionToServer": "Rolling out token {name} failed, the server could not be reached.", + "errorRollOutNotPossibleAnymore": "Rolling out this Token is not possible anymore.", + "errorRollOutSSLHandshakeFailed": "SSL handshake failed. Roll-out not possible.", + "errorRollOutUnknownError": "An unknown error occurred. Roll-out not possible: {e}", + "errorSavingFile": "Saving to file failed", + "errorSynchronizationNoNetworkConnection": "Synchronizing tokens failed, privacyIDEA server could not be reached.", + "errorTokenExpired": "The token {name} has expired.", + "errorUnlinkingPushToken": "Failed to unlink the push token {label}.", + "errorWhenPullingChallenges": "An error occured when polling for challenges of {name}", + "exampleUrl": "Please enter a valid URL like: \"https://example.com/\"", + "expandLockedFolder": "Please authenticate to uncollapse the locked folder.", + "export": "Export", + "exportAllTokens": "Export all tokens", + "exportLockedTokenReason": "Please authenticate to export locked tokens.", + "exportNonPrivacyIDEATokens": "Export non-privacyIDEA tokens", + "exportTokens": "Export tokens", + "exportingTokens": "Exporting tokens...", + "failedToLoad": "Failed to load: \"{name}\"", + "feedback": "Feedback", + "feedbackDescription": "If you have any questions, suggestions or problems, please let us know.", + "feedbackHint": "A ready-made e-mail will open, which you can send to us. If desired, information about your device and the version of the application will be added. You can check and edit the email before sending it.", + "feedbackPrivacyPolicy1": "By sending the feedback you agree to our ", + "feedbackPrivacyPolicy2": "privacy policy", + "feedbackPrivacyPolicy3": ".", + "feedbackSentDescription": "Thank you very much for your help in making this application better!", + "feedbackSentTitle": "Feedback sent", + "feedbackTitle": "Your feedback is always welcome!", + "fileSavedToDownloadsFolder": "File saved to Downloads folder", + "findingQrCodeInImage": "Looking for QR code in image...", + "firebaseToken": "Firebase Token", + "folderName": "Folder name", + "generatingPhonePart": "Generating phone part", + "generatingRSAKeyPair": "Generating RSA key pair", + "generatingRSAKeyPairFailed": "Generating RSA key pair failed", + "goToSettingsButton": "Go to settings", + "goToSettingsDescription": "Authentication by credentials or biometrics is not set up on your device. Please set it up in the device's settings.", + "grantCameraPermissionDialogButton": "Grant permission", + "grantCameraPermissionDialogContent": "Please grant camera permission to scan QR codes.", + "grantCameraPermissionDialogPermanentlyDenied": "Camera permission is permanently denied. Please grant camera permission in your Phone's settings.", + "grantCameraPermissionDialogTitle": "Camera permission is not granted", + "handshakeFailed": "Handshake failed", + "hidePushTokens": "Hide push tokens", + "hidePushTokensDescription": "Hide push tokens from the token list. This will not delete the tokens and they will still be visible on a separate screen.", + "imageUrl": "Image URL", + "importConflictToken": "{count, plural, zero{There is no conflict with existing tokens.} one{There is a conflict with existing tokens.\nPlease select which one you would like to keep.} other{There are conflicts with existing tokens.\nPlease select the tokens you wish to keep.}}", + "importExistingToken": "{count, plural, zero{No token was found that is already in the application.} one{A token was found that already exists in the application.} other{{count} tokens was found that are already in the application.}}", + "importExportTokens": "Import/Export tokens", + "importFailedToken": "{count, plural, zero{No token Failed to import.} one{Failed to import a token.} other{Failed to import {count} tokens.}}", + "importHint2FAS": "Select your 2FAS backup.\nIf you do not have a backup, create one in the 2FAS app. We recommend using a password.", + "importHintAegisBackupFile": "Select your Aegis export (.JSON).\nIf you do not have an export, please create one via the settings menu in the Aegis app. The use of a password is recommended.", + "importHintAegisLink": "Enter the link you receive when you transfer entries from Aegis.", + "importHintAegisQrScan": "Scan the QR code you receive when you transfer entries from Aegis.", + "importHintAuthenticatorProFile": "To create a backup of the Authenticator Pro app, navigate to the settings and tap on \"Auto backup\". Select a storage location and set a password. Then press \"Back up now\" to export the tokens.", + "importHintFreeOtpPlusFile": "To create a backup of the FreeOTP+ app, tap on the three dots in the upper right corner and select \"Export\". You can choose between JSON and URI format. We recommend to delete the backup after importing it, because it is not encrypted.", + "importHintFreeOtpPlusQrScan": "Scan the QR code you receive when you press the three dots in the tile of the token and select \"Share QR code\".", + "importHintGoogleQrFile": "Select an image file with the QR code you receive when you export your accounts from Google Authenticator.\n!! Note that it is not safe to save the QR code on your device as the tokens are not encrypted !!", + "importHintGoogleQrScan": "Scan the QR code you receive when you export your accounts from Google Authenticator.", + "importHintPrivacyIdeaFile": "To create a backup, go to the settings and tap on \"Export\". Select \"As file\", select the tokens you want to export. Then tap on \"Export\" and set a password. The storage location is the download folder on your device.", + "importHintPrivacyIdeaQrScan": "Um QR-Codes der Token zu erstellen, navigieren Sie zu den Einstellungen und tippen auf \"Exportieren\". Wählen Sie dann \"Als QR-Code\" und tippen Sie auf den zu exportierenden Token. Diese Variante ist nur für die direkte Übertragung auf ein anderes Gerät geeignet, da der QR-Code nicht verschlüsselt ist.", + "importNTokens": "{count, plural, zero{Import no token} one{Import one token} other{Import {count} tokens}}", + "importNewToken": "{count, plural, zero{No new token was found.} one{A new token was found that can be imported.} other{{count} new tokens were found that can be imported.}}", + "importTokens": "Import token", + "importedVia": "Imported via", + "increaseCounter": "Increase counter", + "internalServerError": "Internal server error ({code})", + "introAddFolder": "You can create folders\nto organize your tokens.", + "introAddTokenManually": "If you don't want to scan a QR code, you can also add tokens manually.", + "introDragToken": "Reorganize your tokens by pressing it for a few seconds and then dragging it to the desired position.", + "introEditToken": "Here you can edit the token name and see some details.", + "introHidePushTokens": "Your push tokens are hidden now.\nBut you can still see them on the push token screen.", + "introLockToken": "To improve security even more, you can lock tokens.\nThen the token can only be used after authentication.", + "introPollForChallenges": "You can check for new challenges by dragging down the token list.", + "introScanQrCode": "You can scan QR codes to add tokens.\nWe support every common Two-Factor-Authentication token and also the privacyIDEA tokens.", + "introTokenSwipe": "Swipe tokens to the left to see available actions.", + "invalidBackupFile": "The selected file is not a valid backup of {appName}.", + "invalidLink": "The link entered is not a valid token of {appName}, or it is not supported.", + "invalidQrFile": "The selected file does not contain a valid QR code from {appName}.", + "invalidQrScan": "The scanned QR code is not a valid backup of {appName}.", + "invalidValue": "The {type} \"{value}\" is not valid for \"{parameter}\"", + "invalidValueIn": "The {type} \"{value}\" is not valid for \"{parameter}\" in \"{map}\"", + "isExpotableQuestion": "Is exportable?", + "isPiTokenQuestion": "It's a privacyIDEA token?", + "language": "Language", + "legacySigningErrorMessage": "The token was enrolled in a old version of this app, which may cause trouble using it.\nIt is suggested to enroll a new push token if the problem persist!", + "legacySigningErrorTitle": "An error occured while using the legacy token: {tokenLabel}", + "licenses": "Licenses", + "licensesAndVersion": "Licenses and version", + "linkedContainer": "Linked container", + "lock": "Lock", + "lockOut": "Biometric authentication is disabled. Please lock and unlock your screen to enable it.", + "logMenu": "Log menu", + "malformedData": "Malformed data", + "markQrCode": "Mark QR Code", + "missingRequiredParameter": "The value for the [{parameter}] parameter is required, but is missing.", + "missingRequiredParameterIn": "The value for the parameter [{parameter}] is required, but is missing in \"{map}\".", + "mustNotBeEmpty": "{field} must not be empty", + "name": "Name", + "no": "No", + "noFbToken": "No Firebase token available", + "noMailAppDescription": "There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message.", + "noMailAppTitle": "No mail app found", + "noNetworkConnection": "No network connection.", + "noPublicKey": "No public key available", + "noResultText1": "Tap the ", + "noResultText2": " button to get started!", + "noResultTitle": "No tokens stored yet.", + "noTokenToExport": "No token available for export", + "notAnInteger": "The value is not an integer.", + "notAnNumber": "The value is not a number.", + "ok": "Ok", + "oneMore": "One more", + "open": "Open", + "optionalMessage": "Optional message", + "originApp": "Origin app", + "originDetails": "Origin details", + "otpValueCopiedMessage": "Password \"{otpValue}\" copied to clipboard.", + "parsingResponse": "Parsing response", + "parsingResponseFailed": "Parsing response failed", + "password": "Password", "passwordCannotBeEmpty": "Password cannot be empty", - "passwordMustBeAtLeast8Characters": "Password must be at least 8 characters", "passwordCannotContainWhitespace": "Password cannot contain whitespace", + "passwordMustBeAtLeast8Characters": "Password must be at least 8 characters", "passwordMustContainLowercaseLetter": "Password must contain lowercase letter", - "passwordMustContainUppercaseLetter": "Password must contain uppercase letter", "passwordMustContainNumber": "Password must contain number", "passwordMustContainSpecialCharacter": "Password must contain special character", + "passwordMustContainUppercaseLetter": "Password must contain uppercase letter", "passwordsDoNotMatch": "Passwords do not match", - "selectTokensToExportHelpTitle": "Is your token not listed?", - "selectTokensToExportHelpContent": "If a token is not listed, it is not guaranteed that it is not a privacyIDEA token.\nCurrently only manually added and imported tokens are exportable.", - "findingQrCodeInImage": "Looking for QR code in image...", - "qrNotFound": "No QR code found!", + "patchNotesBugFixes": "Bug fixes", + "patchNotesDialogTitle": "What's new?", + "patchNotesImprovements": "Improvements", + "patchNotesNewFeatures": "New features", + "patchNotesV4_3_0NewFeatures1": "Support for importing tokens from Google, Aegis and 2FAS Authenticator has been added. More import sources will be added in the future.", + "patchNotesV4_3_0NewFeatures2": "Added feedback option to the settings.", + "patchNotesV4_3_0NewFeatures3": "Push tokens can now be hidden from the token list.", + "patchNotesV4_3_0NewFeatures4": "Introductions have been added to help new users get started.", + "patchNotesV4_3_0NewFeatures5": "You can now search for tokens by tapping the magnifying glass in the upper right corner.", + "patchNotesV4_3_0NewFeatures6": "Added HomeWidget token for Android 12 and later.", + "patchNotesV4_3_1BugFix1": "Fixed an issue where the otp value was not displayed after authentication on some devices.", + "patchNotesV4_3_1Improvement1": "Improved the qr code scanner.", + "patchNotesV4_4_0Improvement1": "Further import sources have been added.", + "patchNotesV4_4_0Improvement2": "Improved recognition of QR codes from image files.", + "patchNotesV4_4_0NewFeatures1": "It is now possible to export tokens where it can be ensured that they are not privacyIDEA tokens. Currently, it cannot be ruled out that tokens added via the QR code scanner originate from privacyIDEA. The differentiation will be improved in future versions.", + "patchNotesV4_4_0NewFeatures2": "Added support for privacyIDEA's \"require presence\"", + "period": "Period", + "phonePart": "Phone part:", + "pleaseEnterANameForThisToken": "Please enter a name for this token.", + "pleaseEnterASecretForThisToken": "Please enter a secret for this token.", + "pleaseSyncManuallyWhenNetworkIsAvailable": "Please synchronize the push tokens manually via the settings when a network connection is available.", + "pollingChallenges": "Polling for new challenges", + "pollingFailed": "Polling failed.", + "pollingFailedFor": "Polling failed for {serial}", + "privacyPolicy": "Privacy policy", + "publicKey": "Public Key", + "pushEndpointUrl": "Push endpoint URL", + "pushRequestParseError": "Push request could not be parsed.", + "pushToken": "Push Token", + "pushTokens": "Push Tokens", + "qrFileDecodeError": "It was not possible to decode the QR code from the selected image, please use the QR code scanner instead.", "qrInFileNotFound": "No QR code was found in the selected image.", "qrInFileNotFound2": "You can show me where the QR code is located.", "qrInFileNotFound3": "I expect i will find the code if it is in the middle of the marked area.", - "markQrCode": "Mark QR Code", - "malformedData": "Malformed data", - "failedToLoad": "Failed to load: \"{name}\"", - "@failedToLoad": { - "placeholders": { - "name": { - "example": "token data" - } - } - }, - "internalServerError": "Internal server error ({code})", - "@internalServerError": { - "placeholders": { - "code": { - "example": "500" - } - } - }, + "qrNotFound": "No QR code found!", + "qrScan": "Scan", + "rename": "Rename", + "renameToken": "Rename token", + "renameTokenFolder": "Rename folder", + "requestInfo": "Sent by {issuer} for your account: \"{account}\"", + "requestPushChallengesPeriodically": "Request push challenges from the server periodically. Enable this if push challenges are not received normally.", + "requestTriggerdByUserQuestion": "Was this request triggered by you?", + "retryRollout": "Retry rollout", + "rolloutCompleted": "Rollout completed", + "save": "Save", + "scanQrCode": "Scan QR-Code", + "scanThisQrWithNewDevice": "Scan this QR code with your new device to import the token.", + "searchTokens": "Search tokens", + "secretIsRequired": "Secret is required", + "secretKey": "Secret key", + "selectFile": "Select file", + "selectImportSource": "Select import source", + "selectImportType": "How do you want to import the tokens?", + "selectTokensToExport": "{count, plural, zero{} one{Select token to export} other{Select tokens to export}}", + "selectTokensToExportHelpContent": "If a token is not listed, it is not guaranteed that it is not a privacyIDEA token.\nCurrently only manually added and imported tokens are exportable.", + "selectTokensToExportHelpTitle": "Is your token not listed?", + "send": "Send", + "sendErrorDialogBody": "An unexpected error occurred in the application. The information below can be send to the developers by email to help prevent this error in the future.", + "sendErrorLogDescription": "A predefined email is created.\nIt contains information about the app, the error and the device.\nYou can edit the email before sending it.\nYou can see here how we use the information:", + "sendPushRequestResponseFailed": "Failed to send the response.", + "sendingRSAPublicKey": "Sending public RSA key", + "sendingRSAPublicKeyFailed": "Sending public RSA key failed", + "serverNotReachable": "The server could not be reached.", + "settings": "Settings", + "settingsGroupGeneral": "General", + "showDetails": "Show details", + "showErrorLog": "Show", + "showPrivacyPolicy": "Show privacy policy", + "signInTitle": "Authentication required", + "someTokensDoNotSupportPolling": "Some of the tokens are outdated and do not support polling", + "startRollout": "Start rollout", + "statusCode": "Status code: {statusCode}", + "sync": "Sync", + "syncContainerFailed": "Container synchronization failed", + "syncFbTokenFailed": "Synchronization failed for the following tokens, please try again:", + "synchronizePushTokens": "Synchronize push tokens", + "synchronizesTokensWithServer": "Synchronizes tokens with the privacyIDEA server.", + "synchronizingTokens": "Synchronizing tokens.", + "theSecretDoesNotFitTheCurrentEncoding": "The secret does not fit the current encoding", + "themeMode": "Theme mode", + "thisAppIsOpenSource": "This Application is Open Source\nVisit us on GitHub", "timeOut": "Time out", - "syncContainerFailed": "Failed to synchronize container", - "handshakeFailed" : "Handshake failed", - "checkServerCertificate": "Please check the server certificate" + "tokenDataParseError": "Token data could not be parsed", + "tokenDetails": "Token details", + "tokenLink": "Token link", + "tokenSerial": "Token serial", + "tokensAreEncrypted": "The tokens are encrypted. Please enter the password to decrypt them.", + "tokensDoNotSupportSynchronization": "The following tokens do not support synchronization and must be rolled out again:", + "tokensNotEncrypted": "The tokens are not encrypted and can be imported directly.", + "tokensSuccessfullyDecrypted": "The tokens have been successfully decrypted and can now be imported.", + "type": "Type", + "unexpectedError": "An unexpected error occurred.", + "unknown": "Unknown", + "unlock": "Unlock", + "unsupported": "The {name} [{value}] is not supported by this version of the app.", + "useDeviceLocaleDescription": "Use device language if it is supported, otherwise default to english.", + "useDeviceLocaleTitle": "Use device language", + "validFor": "Valid for", + "validUntil": "Valid until", + "valueNotAllowed": "The {type} \"{value}\" is not an allowed value for \"{parameter}\"", + "valueNotAllowedIn": "The {type} \"{value}\" is not an allowed value for \"{parameter}\" in \"{map}\"", + "verboseLogging": "Verbose logging", + "version": "Version", + "wrongPassword": "Incorrect password", + "yes": "Yes" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index f9e36e3dc..d558b1ab3 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1,377 +1,362 @@ { - "@@last_modified": "2023-08-07", - "patchNotesNewFeatures": "Nuevas características", - "patchNotesImprovements": "Mejoras", - "patchNotesBugFixes": "Corrección de errores", - "patchNotesV4_4_0NewFeatures1": "Ahora es posible exportar tokens cuando se puede garantizar que no son tokens de privacyIDEA. Actualmente, no se puede descartar que los tokens añadidos a través del escáner de código QR procedan de privacyIDEA. La diferenciación se mejorará en futuras versiones.", - "patchNotesV4_4_0NewFeatures2": "Añadido soporte para privacyIDEA's \"require presence\".", - "patchNotesV4_4_0Improvement1": "Se han añadido más fuentes de importación.", - "patchNotesV4_4_0Improvement2": "Se ha mejorado el reconocimiento de códigos QR a partir de archivos de imagen.", - "patchNotesV4_3_1BugFix1": "Se ha corregido un problema donde el valor OTP no se mostraba después de la autenticación en algunos dispositivos.", - "patchNotesV4_3_1Improvement1": "Se ha mejorado el escáner de códigos QR.", - "patchNotesV4_3_0NewFeatures1": "Añadido soporte para importar tokens de Google, Aegis y 2FAS Authenticator. En el futuro se añadirán más fuentes de importación", - "patchNotesV4_3_0NewFeatures2": "Añadida opción de feedback a los ajustes", - "patchNotesV4_3_0NewFeatures3": "Los tokens push ahora se pueden ocultar de la lista de tokens", - "patchNotesV4_3_0NewFeatures4": "Se han añadido introducciones para ayudar a los nuevos usuarios a empezar", - "patchNotesV4_3_0NewFeatures5": "Ahora puedes buscar tokens tocando la lupa de la esquina superior derecha", - "patchNotesV4_3_0NewFeatures6": "Añadido Token HomeWidget para Android 12 y posteriores", - "guide": "guía", - "@guide": { - "description": "Button to open the guide screen." - }, - "retry": "Reintentar", - "@retry": { - "description": "Label for e.g. a button. Something is tried to be done again." - }, - "accept": "Aceptar", + "@@last_modified": "2024-09-20", + "@@locale": "es", "@accept": { "description": "Label for e.g. a button. Something gets accepted by the user." }, - "decline": "Negar", - "@decline": { - "description": "Label for e.g. a button. Something gets declined by the user." - }, - "name": "Nombre", - "@name": { - "description": "Describes the field where the tokens name should be entered." - }, - "secretKey": "Clave secreta", - "@secretKey": { - "description": "Describes the field where the tokens secret should be entered." - }, - "encoding": "Codificación", - "@encoding": { - "description": "Title of the dropdown button where the encoding is selected." + "@addToken": { + "description": "The button to open the screen to add tokens by hand." }, - "algorithm": "Algorithmo", "@algorithm": { "description": "Title of the dropdown button where the encoding is selected." }, - "digits": "Dígitos", - "@digits": { - "description": "Title of the dropdown button where the number of digits for the opt value is selected." + "@algorithmUnsupported": { + "placeholders": { + "algorithm": { + "example": "MD5" + } + } }, - "type": "Tipo", - "@type": { - "description": "Title of the dropdown button where the type of the token is selected." + "@allTokensSynchronized": { + "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "period": "Periodo", - "@period": { - "description": "Title of the dropdown button where the period of the totp token is selected." + "@authNotSupportedBody": { + "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." }, - "rename": "Renombrar", - "@rename": { - "description": "Label that describes renaming the token." + "@authNotSupportedTitle": { + "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." }, - "cancel": "Anular", - "@cancel": { - "description": "Button to cancel an action." + "@authToAcceptPushRequest": { + "description": "Message to accepting a push request for authentication." }, - "delete": "Borrar", - "@delete": { - "description": "Label that describes deleting the token." + "@authToDeclinePushRequest": { + "description": "Message for declining a push request for authentication." }, - "dismiss": "Desestimar", - "@dismiss": { - "description": "Text of a button that closes a dialog." + "@authenticateToShowOtp": { + "description": "Reason to authenticate when trying to view a one time password." }, - "addToken": "Añadir token", - "@addToken": { - "description": "The button to open the screen to add tokens by hand." + "@authenticateToUnLockToken": { + "description": "Reason to authenticate when trying to lock or unlock a token." }, - "scanQrCode": "Escanear código QR", - "@scanQrCode": { - "description": "The button to scan otpauth qr-codes." + "@biometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." }, - "enterDetailsForToken": "Introduzca los datos de el token", - "@enterDetailsForToken": { - "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." + "@biometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterANameForThisToken": "Introduzca un nombre para este token.", - "@pleaseEnterANameForThisToken": { - "description": "Hint telling the user to enter a name for a token." + "@biometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterASecretForThisToken": "Por favor, introduzca un secreto para este token.", - "@pleaseEnterASecretForThisToken": { - "description": "Hint telling the user to enter a secret for a token." + "@biometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." }, - "theSecretDoesNotFitTheCurrentEncoding": "El secreto no se ajusta a la codificación actual", - "@theSecretDoesNotFitTheCurrentEncoding": { - "description": "Hint telling the user that the secret does not fit the selected encoding." + "@cancel": { + "description": "Button to cancel an action." }, - "renameToken": "Renombrar token", - "@renameToken": { - "description": "Title of the dialog where a new name for a token can be entered." + "@checkServerCertificate": { + "description": "Error message when the server certificate should be checked." + }, + "@checkYourNetwork": { + "description": "Tells the user to check the network connection." + }, + "@clearErrorLog": { + "description": "Button to clear the error log." }, - "confirmDeletion": "Confiem supresión", "@confirmDeletion": { "description": "Title of the dialog where a token can be deleted." }, - "confirmDeletionOf": "¿Está seguro de que desea eliminar {name}?", "@confirmDeletionOf": { "description": "Asks for confirmation on deleting a token.", "placeholders": { "name": { "example": "PUSH1234" } - } - }, - "confirmTokenDeletionHint": "Es posible que ya no pueda iniciar sesión si elimina este token.\nAsegúrese de que puede iniciar sesión en la cuenta asociada sin este token.", - "@confirmTokenDeletionHint": { - "description": "Gives the user a hint about the consequences of deleting a token." + }, + "type": "text" }, - "confirmFolderDeletionHint": "Eliminar una carpeta no afecta a los tokens que contiene.\nLos tokens se mueven a la lista principal.", "@confirmFolderDeletionHint": { "description": "Gives the user a hint about the consequences of deleting a folder." }, - "generatingPhonePart": "Generar parte telefónico", - "@generatingPhonePart": { - "description": "Title of a dialog telling the user that the phone part gets generated right now." + "@confirmTokenDeletionHint": { + "description": "Gives the user a hint about the consequences of deleting a token." }, - "phonePart": "Pieza de teléfono:", - "@phonePart": { - "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." + "@connectionFailed": { + "description": "Tells the user that the connection failed." }, - "otpValueCopiedMessage": "Contraseña \"{otpValue}\" copiada en el portapapeles.", - "@otpValueCopiedMessage": { - "description": "Tells the user that the otp value was copied to the clipboard.", - "placeholders": { - "otpValue": { - "example": "055374" - } - } + "@container": { + "description": "Title for the container view." }, - "settings": "Configuración", - "@settings": { - "description": "Button to open the settings page." + "@couldNotSignMessage": { + "description": "Tells the user that the message could not be signed." }, - "pushToken": "Push Token", - "@pushToken": { - "description": "Title for the settings block concerning the push tokens." + "@counter": { + "description": "Describes the field where the tokens counter should be entered." }, - "theme": "Tema", - "@theme": { - "description": "Title of the setting group where the theme can be selected." + "@createdAt": { + "description": "Label for the creation date of the token." }, - "lightTheme": "Luminoso", - "@lightTheme": { - "description": "The light theme." + "@creator": { + "description": "Label for the creator of the token." }, - "darkTheme": "Negro", "@darkTheme": { "description": "The dark theme." }, - "systemTheme": "Utilizar el tema del teléfono", - "@systemTheme": { - "description": "The systems theme." + "@decline": { + "description": "Label for e.g. a button. Something gets declined by the user." }, - "someTokensDoNotSupportPolling": "Algunos tokens están obsoletos y no admiten la consulta activa para la autenticación mediante mensaje push.", - "@someTokensDoNotSupportPolling": { - "description": "Tells the user, that the following tokens do not support polling.", - "type": "text", - "placeholders": {} + "@delete": { + "description": "Label that describes deleting the token." }, - "enablePolling": "Activar polling", - "@enablePolling": { - "description": "Name of the setting switch that enables polling." + "@deviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." }, - "requestPushChallengesPeriodically": "Solicita retos push al servidor periódicamente. Habilite esta opción si los retos push no se reciben normalmente.", - "@requestPushChallengesPeriodically": { - "description": "The description of the polling feature." + "@deviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." }, - "synchronizePushTokens": "Sinchronizar push tokens", - "@synchronizePushTokens": { - "description": "Title of synchronizing push tokens in settings." + "@digits": { + "description": "Title of the dropdown button where the number of digits for the opt value is selected." }, - "synchronizesTokensWithServer": "Sinchronizar tokens con el privacyIDEA servidor.", - "@synchronizesTokensWithServer": { - "description": "Description of synchronizing push tokens in settings." + "@dismiss": { + "description": "Text of a button that closes a dialog." }, - "sync": "Sinchronizar", - "@sync": { - "description": "Text of button that is used to synchronize push tokens." + "@enablePolling": { + "description": "Name of the setting switch that enables polling." }, - "synchronizingTokens": "Sincronización de los tokens.", - "@synchronizingTokens": { - "description": "Title of the push synchronization dialog." + "@encoding": { + "description": "Title of the dropdown button where the encoding is selected." }, - "allTokensSynchronized": "Todas los tokens están sincronizadas.", - "@allTokensSynchronized": { - "description": "Content of the push synchronization dialog. Signaling the user that everything worked." + "@enterDetailsForToken": { + "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." }, - "syncFbTokenFailed": "La sincronización ha fallado para los siguientes tokens, por favor inténtelo de nuevo:", - "@syncFbTokenFailed": { - "description": "Headline for the list of tokens where the synchronization failed." + "@errorLogCleared": { + "description": "Message that tells the user that the error log was cleared." }, - "tokensDoNotSupportSynchronization": "Las siguientes tokens no admiten la sincronización y deben volver a desplegarse:", - "@tokensDoNotSupportSynchronization": { - "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + "@errorLogEmpty": { + "description": "Message that tells the user that the error log is empty." + }, + "@errorLogTitle": { + "description": "Title of the error log screen." + }, + "@errorMailBody": { + "description": "Message for email body" }, - "errorRollOutFailed": "Error en la extracción de el token {name}.", "@errorRollOutFailed": { "description": "Tells the user that the token could not be rolled out, because a network error occurred.", "placeholders": { "name": { "example": "PUSH1234A" } - } + }, + "type": "text" }, - "statusCode": "Código de estado: {statusCode}", - "@statusCode": { - "description": "Tells the user the status code of the error.", + "@errorRollOutNoConnectionToServer": { + "description": "Message for the rollout process", "placeholders": { - "statusCode": { - "example": "400" + "name": { + "example": "PUSH1234A" } } }, - "errorSynchronizationNoNetworkConnection": "Error al sincronizar los tokens. No se ha podido acceder al servidor de PrivacyIDEA.", - "@errorSynchronizationNoNetworkConnection": { - "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." + "@errorRollOutSSLHandshakeFailed": { + "description": "Tells the user that the roll-out failed because the SSL handshake failed." }, - "errorRollOutUnknownError": "An unknown error occurred. Roll-out not possible: {e}", "@errorRollOutUnknownError": { "description": "Tells the user that the roll-out failed because of an unknown error.", "placeholders": { "e": { "example": "IllegalArgumentException on Line 5 ..." } - } - }, - "startRollout": "Iniciar despliegue", - "@startRollout": { - "description": "Label that tells the user that the token is being rolled out." + }, + "type": "text" }, - "pollingChallenges": "Sondeo para nuevos challenges", - "@pollingChallenges": { - "placeholders": {} + "@errorSynchronizationNoNetworkConnection": { + "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." }, - "unexpectedError": "Se ha producido un error inesperado.", - "@unexpectedError": { - "description": "Title of page report mode." + "@errorTokenExpired": { + "placeholders": { + "name": { + "example": "PUSH1234A" + } + } }, - "pollingFailed": "Consulta fallida.", - "@pollingFailed": { - "description": "Tells the user that the polling failed." + "@errorUnlinkingPushToken": { + "description": "Error message when unlinking a push token failed.", + "placeholders": { + "label": { + "example": "PUSH1234A" + } + } }, - "pollingFailedFor": "Fallo de sondeo para {serial}", - "@pollingFailedFor": { - "description": "Tells the user that the polling failed.", + "@errorWhenPullingChallenges": { + "description": "errorWhenPullingChallenges", "placeholders": { - "serial": { + "name": { "example": "PUSH1234A" } } }, - "noNetworkConnection": "No hay conexión a la red.", - "@noNetworkConnection": { - "description": "Tells the user that there is no network connection." + "@failedToLoad": { + "placeholders": { + "name": { + "example": "token data" + } + } }, - "connectionFailed": "Conexión fallida.", - "@connectionFailed": { - "description": "Tells the user that the connection failed." + "@feedbackPrivacyPolicy2": { + "description": "Taping on this should open the privacy policy." }, - "checkYourNetwork": "Compruebe su conexión de red e inténtelo de nuevo.", - "@checkYourNetwork": { - "description": "Tells the user to check the network connection." + "@generatingPhonePart": { + "description": "Title of a dialog telling the user that the phone part gets generated right now." }, - "serverNotReachable": "No se ha podido acceder al servidor.", - "@serverNotReachable": { - "description": "Tells the user that the server could not be reached." + "@generatingRSAKeyPair": { + "description": "Message for the rollout process" }, - "couldNotSignMessage": "No se ha podido firmar el mensaje.", - "@couldNotSignMessage": { - "description": "Tells the user that the message could not be signed." + "@generatingRSAKeyPairFailed": { + "description": "Message for the rollout process" }, - "useDeviceLocaleTitle": "Utiliza el idioma del teléfono", - "@useDeviceLocaleTitle": { - "description": "Title of the switch tile where using the devices language can be enabled." + "@goToSettingsButton": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." }, - "useDeviceLocaleDescription": "Utilizar el idioma del dispositivo si está soportado, en caso contrario por defecto inglés.", - "@useDeviceLocaleDescription": { - "description": "Description of the switch tile where using the devices language can be enabled." + "@goToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." }, - "language": "Language", - "@language": { - "description": "Título del grupo de configuración lingüística." + "@guide": { + "description": "Button to open the guide screen." }, - "authenticateToShowOtp": "Por favor, autentifíquese para mostrar la contraseña de una sola vez.", - "@authenticateToShowOtp": { - "description": "Reason to authenticate when trying to view a one time password." + "@handshakeFailed": { + "description": "Error message when the handshake failed." }, - "authenticateToUnLockToken": "Por favor, autentifíquese para cambiar el estado de bloqueo del token.", - "@authenticateToUnLockToken": { - "description": "Reason to authenticate when trying to lock or unlock a token." + "@importedVia": { + "description": "Label for the import method of the token." }, - "biometricRequiredTitle": "Biometría no configurada", - "@biometricRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + "@internalServerError": { + "placeholders": { + "code": { + "example": "500" + } + } }, - "biometricHint": "AAutenticación necesaria", - "@biometricHint": { - "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + "@invalidValue": { + "description": "Error message when the value is not valid for the parameter.", + "placeholders": { + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "biometricNotRecognized": "No reconocido. Inténtelo de nuevo.", - "@biometricNotRecognized": { - "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + "@invalidValueIn": { + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "biometricSuccess": "Autenticación correcta", - "@biometricSuccess": { - "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + "@isExpotableQuestion": { + "description": "Label for the question if the token is exportable." }, - "deviceCredentialsRequiredTitle": "No se han configurado las credenciales del dispositivo.", - "@deviceCredentialsRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + "@isPiTokenQuestion": { + "description": "Label for the question if the token is a privacyIDEA token." }, - "deviceCredentialsSetupDescription": "Configurar las credenciales del dispositivo en los ajustes del dispositivo", - "@deviceCredentialsSetupDescription": { - "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + "@language": { + "description": "Title of language setting group." }, - "signInTitle": "Autenticación necesaria", - "@signInTitle": { - "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + "@legacySigningErrorMessage": { + "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." }, - "goToSettingsButton": "Ir a la configuración", - "@goToSettingsButton": { - "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + "@legacySigningErrorTitle": { + "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", + "placeholders": { + "tokenLabel": { + "example": "PUSH1234A" + } + } }, - "goToSettingsDescription": "La autenticación por credenciales o biométrica no está configurada en tu dispositivo. Por favor, configúrala en los ajustes del dispositivo.", - "@goToSettingsDescription": { - "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." + "@lightTheme": { + "description": "The light theme." + }, + "@linkedContainer": { + "description": "Label for the linked container serial number." + }, + "@lock": { + "description": "Description of button that locks a token." }, - "lockOut": "La autenticación biométrica está desactivada. Bloquea y desbloquea la pantalla para activarla.", "@lockOut": { "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." }, - "authNotSupportedTitle": "Se requieren credenciales de dispositivo o datos biométricos", - "@authNotSupportedTitle": { - "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." + "@logMenu": { + "description": "Button to open the log menu." }, - "authNotSupportedBody": "Esta acción requiere que el dispositivo esté protegido mediante credenciales o datos biométricos.", - "@authNotSupportedBody": { - "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." + "@malformedData": { + "description": "Error message when the data is malformed." }, - "lock": "Cierre", - "@lock": { - "description": "Description of button that locks a token." + "@missingRequiredParameter": { + "description": "Error message when a required parameter is missing.", + "placeholders": { + "parameter": { + "example": "counter" + } + } }, - "unlock": "Desbloquear", - "@unlock": { - "description": "Description of button that unlocks a token." + "@missingRequiredParameterIn": { + "description": "Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.", + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + } + } }, - "noResultTitle": "Aún no hay tokens almacenadas.", - "@noResultTitle": { - "description": "No tokens stored yet." + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, + "@name": { + "description": "Describes the field where the tokens name should be entered." + }, + "@noFbToken": { + "description": "Tells the user that there is no Firebase token available." + }, + "@noNetworkConnection": { + "description": "Tells the user that there is no network connection." }, - "noResultText1": "Indique el ", "@noResultText1": { "description": "first no result text" }, - "noResultText2": " para empezar.", "@noResultText2": { "description": "second no result text" }, - "onBoardingTitle1": "{appName}", + "@noResultTitle": { + "description": "No tokens stored yet." + }, + "@notAnInteger": { + "description": "Tells the user that there is no Firebase token available." + }, + "@notAnNumber": { + "description": "Error message when the user entered a value that is not a number." + }, + "@ok": { + "description": "Button to confirm an action." + }, "@onBoardingTitle1": { "placeholders": { "appName": { @@ -379,296 +364,558 @@ } } }, - "onBoardingText1": "Autenticación de dos factores\nmuy fácil", - "onBoardingTitle2": "Máxima seguridad", - "onBoardingText2": "Almacena fichas de forma segura en tu teléfono.\nProtegido por tus datos biométricos.", - "onBoardingTitle3": "Visítenos en Github", - "onBoardingText3": "Esta aplicación es de código abierto", - "errorLogTitle": "Registro de errores", - "logMenu": "Menú de registro", - "showErrorLog": "Mostrar", - "clearErrorLog": "Borrar", - "send": "Enviar", - "sendErrorLogDescription": "Se crea un correo electrónico listo.\nContiene información sobre la app, el error y el dispositivo.\nPuedes editar el correo antes de enviarlo.\nAquí puede ver cómo utilizamos la información:", - "@sendErrorLogDescription": { - "description": "Explanation for the user what he will send." + "@open": { + "description": "Button to open something." }, - "showPrivacyPolicy": "Mostrar política de privacidad", - "errorLogEmpty": "El registro de errores está vacío", - "verboseLogging": "Registro detallado", - "errorLogCleared": "Registro de errores borrado", - "ok": "Ok", - "errorMailBody": "Se adjunta el archivo de registro de errores.\nPuede sustituir este texto por información adicional sobre el error.", - "@errorMailBody": { - "description": "Message for email body" + "@originApp": { + "description": "Label for the origin app." }, - "showDetails": "Mostrar detalles", - "open": "Abrir", - "sendErrorDialogBody": "Se ha producido un error inesperado en la aplicación. La siguiente información puede ser enviada a los desarrolladores por correo electrónico para ayudar a prevenir este error en el futuro.", - "@sendErrorDialogBody": { - "description": "Description shown to the user about what info the error report contains." + "@originDetails": { + "description": "Title of the origin details menu." }, - "noFbToken": "No hay token de Firebase.", - "firebaseToken": "Token de Firebase", - "noPublicKey": "No hay clave pública.", - "publicKey": "Clave pública", - "editToken": "Editar token", - "edit": "Editar", - "save": "Guardar", - "create": "Crear", - "validFor": "Válido para", - "validUntil": "Válido hasta", - "deleteLockedToken": "Por favor, autentíquese para eliminar el token bloqueado.", - "editLockedToken": "Por favor, autentíquese para editar el token bloqueado.", - "expandLockedFolder": "Por favor, autentifíquese para abrir la carpeta bloqueada.", - "renameTokenFolder": "Cambiar nombre de carpeta", - "addANewFolder": "Crear nueva carpeta", - "folderName": "Nombre de la carpeta", - "retryRollout": "Reintentar despliegue", - "generatingRSAKeyPair": "Generando par de claves RSA", - "@generatingRSAKeyPair": { - "description": "Message for the rollout process" + "@otpValueCopiedMessage": { + "description": "Tells the user that the otp value was copied to the clipboard.", + "placeholders": { + "otpValue": { + "example": "055374" + } + }, + "type": "text" }, - "generatingRSAKeyPairFailed": "Error al generar el par de claves RSA", - "@generatingRSAKeyPairFailed": { + "@parsingResponse": { "description": "Message for the rollout process" }, - "sendingRSAPublicKey": "Enviando clave pública RSA", - "@sendingRSAPublicKey": { + "@parsingResponseFailed": { "description": "Message for the rollout process" }, - "sendingRSAPublicKeyFailed": "Error al enviar la clave pública RSA", - "@sendingRSAPublicKeyFailed": { - "description": "Message for the rollout process" + "@period": { + "description": "Title of the dropdown button where the period of the totp token is selected." }, - "parsingResponse": "Analizando la respuesta", - "@parsingResponse": { - "description": "Message for the rollout process" + "@phonePart": { + "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." }, - "parsingResponseFailed": "Error al analizar la respuesta", - "@parsingResponseFailed": { - "description": "Message for the rollout process" + "@pleaseEnterANameForThisToken": { + "description": "Hint telling the user to enter a name for a token." }, - "rolloutCompleted": "Despliegue completado", - "@rolloutCompleted": { - "description": "Message for the rollout process" + "@pleaseEnterASecretForThisToken": { + "description": "Hint telling the user to enter a secret for a token." }, - "errorRollOutNoConnectionToServer": "El despliegue del token {name} ha fallado, no se ha podido acceder al servidor.", - "@errorRollOutNoConnectionToServer": { - "description": "Message for the rollout process", + "@pollingChallenges": { + "type": "text" + }, + "@pollingFailed": { + "description": "Tells the user that the polling failed." + }, + "@pollingFailedFor": { + "description": "Tells the user that the polling failed.", "placeholders": { - "name": { + "serial": { "example": "PUSH1234A" } } }, - "authToAcceptPushRequest": "Por favor, autentifíquese para aceptar la solicitud push.", - "authToDeclinePushRequest": "Por favor, autentifíquese para rechazar la solicitud push.", - "pushRequestParseError": "No se ha podido procesar la solicitud push.", - "imageUrl": "URL de la imagen", - "errorRollOutSSLHandshakeFailed": "Ha fallado el protocolo SSL. No es posible el despliegue.", - "errorWhenPullingChallenges": "Se ha producido un error al buscar retos de {name}", - "@errorWhenPullingChallenges": { + "@pushToken": { + "description": "Title for the settings block concerning the push tokens." + }, + "@rename": { + "description": "Label that describes renaming the token." + }, + "@renameToken": { + "description": "Title of the dialog where a new name for a token can be entered." + }, + "@renameTokenFolder": { + "description": "Title of the dialog where a new name for a token folder can be entered." + }, + "@requestInfo": { + "description": "Description of the authentication request.", "placeholders": { - "name": { - "example": "PUSH1234A" + "account": { + "example": "GitHub" + }, + "issuer": { + "example": "privacyIDEA" } } }, - "couldNotConnectToServer": "No se ha podido conectar con el servidor.", - "errorRollOutNotPossibleAnymore": "El despliegue de este token ya no es posible.", - "errorTokenExpired": "El token {name} ha caducado.", - "@errorTokenExpired": { + "@requestPushChallengesPeriodically": { + "description": "The description of the polling feature." + }, + "@retry": { + "description": "Label for e.g. a button. Something is tried to be done again." + }, + "@rolloutCompleted": { + "description": "Message for the rollout process" + }, + "@scanQrCode": { + "description": "The button to scan otpauth qr-codes." + }, + "@secretIsRequired": { + "description": "Error message when the secret is missing." + }, + "@secretKey": { + "description": "Describes the field where the tokens secret should be entered." + }, + "@send": { + "description": "Button to send the error log." + }, + "@sendErrorDialogBody": { + "description": "Description shown to the user about what info the error report contains." + }, + "@sendErrorLogDescription": { + "description": "Explanation for the user what he will send." + }, + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." + }, + "@sendingRSAPublicKey": { + "description": "Message for the rollout process" + }, + "@sendingRSAPublicKeyFailed": { + "description": "Message for the rollout process" + }, + "@serverNotReachable": { + "description": "Tells the user that the server could not be reached." + }, + "@settings": { + "description": "Button to open the settings page." + }, + "@showDetails": { + "description": "Button to show details." + }, + "@showErrorLog": { + "description": "Button to show the error log." + }, + "@showPrivacyPolicy": { + "description": "Button to show the privacy policy." + }, + "@signInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "@someTokensDoNotSupportPolling": { + "description": "Tells the user, that the following tokens do not support polling.", + "type": "text" + }, + "@startRollout": { + "description": "Label that tells the user that the token is being rolled out." + }, + "@statusCode": { + "description": "Tells the user the status code of the error.", "placeholders": { - "name": { - "example": "PUSH1234A" + "statusCode": { + "example": "400" } } }, - "yes": "Sí", - "no": "No", - "butDiscardIt": "pero descártelo", - "declineIt": "rechazar", - "requestTriggerdByUserQuestion": "¿Fue usted quien provocó esta petición?", - "grantCameraPermissionDialogTitle": "El permiso de cámara no está concedido", - "grantCameraPermissionDialogContent": "Por favor, concede permiso a la cámara para escanear códigos QR", - "grantCameraPermissionDialogPermanentlyDenied": "El permiso de cámara está denegado permanentemente. Concede el permiso de cámara en la configuración del teléfono", - "grantCameraPermissionDialogButton": "Conceder permiso", - "decryptErrorTitle": "Error de descifrado", - "decryptErrorContent": "Lamentablemente, la aplicación no ha podido descifrar tus tokens. Esto indica que la clave de cifrado está rota. Puedes volver a intentarlo o borrar los datos de la app, lo que eliminaría los tokens de la app.", - "decryptErrorButtonDelete": "Borrar", - "decryptErrorButtonSendError": "Enviar error", - "decryptErrorButtonRetry": "Reintentar", - "decryptErrorDeleteConfirmationContent": "¿Estás seguro de que quieres borrar los datos de la aplicación?", - "hidePushTokens": "Ocultar tokens push", - "hidePushTokensDescription": "Ocultar tokens push de la lista de tokens. Esto no borrará los tokens y seguirán siendo visibles en una pantalla aparte", - "settingsGroupGeneral": "Información general", - "licensesAndVersion": "Licencias y versión", - "privacyPolicy": "Política de privacidad", - "introScanQrCode": "Puedes escanear códigos QR para añadir tokens.\nSoportamos todos los tokens comunes de Two-Factor-Authentication y también los tokens privacyIDEA", - "introAddTokenManually": "Si no quieres escanear un código QR, también puedes añadir tokens manualmente", - "introTokenSwipe": "Desliza los tokens hacia la izquierda para ver las acciones disponibles", - "introEditToken": "Aquí puedes editar el nombre del token y ver algunos detalles", - "introLockToken": "Para mejorar la seguridad aún más, puedes bloquear los tokens.\nEntonces el token sólo se puede utilizar después de la autenticación.", - "introDragToken": "Reorganiza tus tokens pulsándolo durante unos segundos y arrastrándolo a la posición deseada", - "introAddFolder": "Puedes crear carpetas\npara organizar tus tokens", - "introPollForChallenges": "Puedes buscar nuevos retos arrastrando hacia abajo la lista de tokens", - "introHidePushTokens": "Tus push tokens están ahora ocultos.\nPero puedes seguir viéndolos en la pantalla de push tokens.", - "legacySigningErrorTitle": "Se ha producido un error al utilizar el token obsoleto: {tokenLabel}", - "@legacySigningErrorTitle": { - "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", + "@sync": { + "description": "Text of button that is used to synchronize push tokens." + }, + "@syncContainerFailed": { + "description": "Error message when synchronizing a container failed." + }, + "@syncFbTokenFailed": { + "description": "Headline for the list of tokens where the synchronization failed." + }, + "@synchronizePushTokens": { + "description": "Title of synchronizing push tokens in settings." + }, + "@synchronizesTokensWithServer": { + "description": "Description of synchronizing push tokens in settings." + }, + "@synchronizingTokens": { + "description": "Title of the push synchronization dialog." + }, + "@systemTheme": { + "description": "The systems theme." + }, + "@theSecretDoesNotFitTheCurrentEncoding": { + "description": "Hint telling the user that the secret does not fit the selected encoding." + }, + "@theme": { + "description": "Title of the setting group where the theme can be selected." + }, + "@themeMode": { + "description": "Title of the setting group where the theme mode can be changed." + }, + "@timeOut": { + "description": "Error message when a request times out." + }, + "@tokenDataParseError": { + "description": "Error message when the token data could not be parsed." + }, + "@tokenDetails": { + "description": "Title of the token details menu." + }, + "@tokenSerial": { + "description": "Label for the token serial number." + }, + "@tokensDoNotSupportSynchronization": { + "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + }, + "@type": { + "description": "Title of the dropdown button where the type of the token is selected." + }, + "@unexpectedError": { + "description": "Title of page report mode." + }, + "@unknown": { + "description": "Tells that something is unknown." + }, + "@unlock": { + "description": "Description of button that unlocks a token." + }, + "@unsupported": { "placeholders": { - "tokenLabel": { - "example": "PUSH1234A" + "name": { + "example": "piauth version" + }, + "value": { + "example": "5" } } }, - "legacySigningErrorMessage": "El token se creó en una versión obsoleta de la aplicación, lo que puede provocar problemas al utilizarlo.\nSe recomienda crear un nuevo token push si el problema persiste.", - "@legacySigningErrorMessage": { - "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." + "@useDeviceLocaleDescription": { + "description": "Description of the switch tile where using the devices language can be enabled." }, - "selectImportSource": "Seleccionar fuente de importación", - "selectImportType": "Jak chcete importovat žetony?", - "importTokens": "Importar token", - "importNTokens": "{count, plural, zero{No importar tokens} one{Importar un token} other{Importar {count} tokens}}", - "selectFile": "Seleccionar archivo", - "decrypt": "Descifrar", - "tokensAreEncrypted": "Los tokens están encriptados. Por favor, introduce la contraseña para descifrarlos", - "tokensNotEncrypted": "Los tokens no están encriptados y se pueden importar directamente", - "tokensSuccessfullyDecrypted": "Los tokens se han descifrado correctamente y ya se pueden importar.", - "password": "Contraseña", - "wrongPassword": "Contraseña incorrecta", - "qrScan": "Escanear", - "enterLink": "Introducir enlace", - "invalidBackupFile": "El archivo seleccionado no es una copia de seguridad válida de {appName}", - "invalidQrScan": "El código QR escaneado no es una copia de seguridad válida de {appName}", - "invalidQrFile": "El archivo seleccionado no contiene un código QR válido de {appName}", - "invalidLink": "El enlace introducido no es un token válido de {appName}, o no es compatible", - "importFailedToken": "{count, plural, zero{No token Fallo al importar.} one{Error al importar un token.} other{Error al importar {count} tokens.}}", - "importExistingToken": "{count, plural, zero{No se ha encontrado ningún token que ya esté en la aplicación.} one{Se ha encontrado un token que ya existe en la aplicación.} other{Se han encontrado {count} tokens que ya están en la aplicación.}}", - "importConflictToken": "{count, plural, zero{No hay conflicto con tokens que ya existen.} one{Hay un conflicto con tokens que ya existen.\nPor favor, seleccione cuál le gustaría conservar.} other{Hay un conflicto con tokens que ya existen.\nPor favor, seleccione cuál le gustaría conservar.}}", - "importNewToken": "{count, plural, zero{No se ha encontrado ningún token nuevo.} one{Se ha encontrado un nuevo token que se puede importar.} other{Se han encontrado {count} nuevos tokens que se pueden importar.}}", - "importHintPrivacyIdeaQrScan": "Para crear códigos QR de las fichas, vaya a la configuración y pulse sobre \"Exportar\". A continuación, seleccione \"Como código QR\" y pulse sobre la ficha que desea exportar. Esta variante sólo es adecuada para la transferencia directa a otro dispositivo, ya que el código QR no está cifrado.", - "importHintPrivacyIdeaFile": "Para crear una copia de seguridad, vaya a los ajustes y pulse sobre \"Exportar\". Seleccione \"Como archivo\", seleccione los tokens que desea exportar. A continuación, pulse \"Exportar\" y establezca una contraseña. La ubicación de almacenamiento es la carpeta de descargas de su dispositivo.", - "importHint2FAS": "Seleccione su copia de seguridad de 2FAS. Si no tiene una copia de seguridad, cree una en la aplicación 2FAS. Le recomendamos que utilice una contraseña", - "importHintAegisBackupFile": "Seleccione su exportación de Aegis (.JSON).\nSi no tiene una exportación, cree una a través del menú de configuración en la app de Aegis. Se recomienda utilizar una contraseña", - "importHintAegisQrScan": "Escanea el código QR que recibes al transferir entradas desde Aegis", - "importHintAegisLink": "Introduzca el enlace que recibe al transferir entradas desde Aegis", - "importHintGoogleQrScan": "Escanea el código QR que recibes al exportar tus cuentas desde Google Authenticator", - "importHintGoogleQrFile": "Selecciona un archivo de imagen con el código QR que recibes al exportar tus cuentas desde Google Authenticator.\n!! Tenga en cuenta que no es seguro guardar el código QR en su dispositivo, ya que los tokens no están cifrados !!", - "importHintAuthenticatorProFile": "Para crear una copia de seguridad de la aplicación Authenticator Pro, vaya a la configuración y pulse en \"Copia de seguridad automática\". Seleccione una ubicación de almacenamiento y establezca una contraseña. A continuación, pulse \"Hacer copia de seguridad ahora\" para exportar los tokens.", - "importHintFreeOtpPlusQrScan": "Escanea el código QR que recibes al pulsar los tres puntos en el azulejo de la ficha y selecciona \"Compartir código QR\".", - "importHintFreeOtpPlusFile": "Para crear una copia de seguridad de la app FreeOTP+, pulse los tres puntos de la esquina superior derecha y seleccione \"Exportar\". Puede elegir entre los formatos JSON y URI. Recomendamos eliminar la copia de seguridad después de importarla, ya que no está cifrada.", - "qrFileDecodeError": "No fue posible decodificar el código QR de la imagen seleccionada, por favor utilice el escáner de código QR en su lugar.", - "tokenLink": "Enlace token", - "feedback": "Comentarios", - "feedbackTitle": "¡Tus comentarios son siempre bienvenidos!", - "feedbackDescription": "Si tienes alguna pregunta, sugerencia o problema, háznoslo saber", - "feedbackHint": "Se abrirá un correo electrónico preparado que podrá enviarnos. Si lo desea, se añadirá información sobre su dispositivo y la versión de la aplicación. Puede comprobar y editar el correo electrónico antes de enviarlo.", - "feedbackPrivacyPolicy1": "Al enviar sus comentarios, acepta nuestra ", - "feedbackPrivacyPolicy2": "política de privacidad", - "@feedbackPrivacyPolicy2": { - "description": "Taping on this should open the privacy policy." + "@useDeviceLocaleTitle": { + "description": "Title of the switch tile where using the devices language can be enabled." }, - "feedbackPrivacyPolicy3": ".", - "addSystemInfo": "Añadir información del sistema", - "feedbackSentTitle": "Comentarios enviados", - "feedbackSentDescription": "Muchas gracias por su ayuda para mejorar esta aplicación.", - "patchNotesDialogTitle": "¿Qué hay de nuevo?", - "version": "Versión", - "noMailAppTitle": "No hay aplicación de correo electrónico", - "noMailAppDescription": "No hay ninguna app de correo electrónico instalada o inicializada en este dispositivo, inténtalo de nuevo cuando puedas enviar un mensaje de correo electrónico.", - "authenticationRequest": "Autenticación", - "requestInfo": "Enviado por {issuer} para su cuenta: \"{account}\"", - "@requestInfo": { + "@valueNotAllowed": { "placeholders": { - "issuer": { - "example": "privacyIDEA" + "parameter": { + "example": "counter" }, - "account": { - "example": "GitHub" + "type": { + "example": "int" + }, + "value": { + "example": "-1" } } }, - "errorUnlinkingPushToken": "Error al desvincular el token push {label}", - "@errorUnlinkingPushToken": { - "description": "Error message when unlinking a push token failed.", + "@valueNotAllowedIn": { "placeholders": { - "label": { - "example": "PUSH1234A" + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "-1" } } }, - "pleaseSyncManuallyWhenNetworkIsAvailable": "Por favor, sincronice los tokens push manualmente a través de los ajustes cuando haya una conexión de red disponible.", - "pushTokens": "Push Tokens", - "continueButton": "Continue", - "addTokenManually": "Añadir token manualmente", + "@verboseLogging": { + "description": "Title of the switch tile where verbose logging can be enabled." + }, + "accept": "Aceptar", + "addANewFolder": "Crear nueva carpeta", "addFolder": "Añadir carpeta", - "searchTokens": "Buscar tokens", - "closeSearchTokens": "Cerrar búsqueda", - "increaseCounter": "Incrementar contador", - "copyOTPToClipboard": "Copiar OTP al portapapeles", - "licenses": "Licencias", - "optionalMessage": "Mensaje opcional", - "confirmation": "confirmación", - "askLogSendedDescription": "¿Ha enviado el registro y desea borrarlo ahora?", + "addSystemInfo": "Añadir información del sistema", + "addToken": "Añadir token", + "addTokenManually": "Añadir token manualmente", + "algorithm": "Algorithmo", "algorithmUnsupported": "El algoritmo {algorithm} no es compatible", - "@algorithmUnsupported": { - "placeholders": { - "algorithm": { - "example": "MD5" - } - } - }, - "thisAppIsOpenSource": "Esta aplicación es de código abierto\nVisítanos en GitHub", - "invalidArgument": "{argument} no es un valor válido para {type}", - "importExportTokens": "Importar/Exportar tokens", - "exportNonPrivacyIDEATokens": "Exportar tokens no privacyIDEA", - "selectTokensToExport": "{count, plural, zero{} one{Seleccionar token para exportar} other{Seleccionar tokens para exportar}}", - "noTokenToExport": "No hay token disponible para exportar", - "exportAllTokens": "Exportar todos los tokens", - "export": "Exportar", - "exportingTokens": "Exportando tokens...", - "exportTokens": "Exportar tokens", - "enterPasswordToEncrypt": "Ingrese una contraseña para cifrar los tokens. Esta contraseña será necesaria para importar los tokens.", - "exportLockedTokenReason": "Por favor, autentíquese para exportar tokens bloqueados.", - "fileSavedToDownloadsFolder": "Archivo guardado en la carpeta de descargas", - "errorSavingFile": "Error al guardar el archivo", - "asQrCode": "Como código QR", + "allTokensSynchronized": "Todas los tokens están sincronizadas.", "asFile": "Como archivo", - "scanThisQrWithNewDevice": "Escanee este código QR con su nuevo dispositivo para importar el token.", - "oneMore": "Uno más", - "done": "Hecho", - "confirmPassword": "Confirmar contraseña", + "asQrCode": "Como código QR", + "askLogSendedDescription": "¿Ha enviado el registro y desea borrarlo ahora?", + "authNotSupportedBody": "Esta acción requiere que el dispositivo esté protegido mediante credenciales o datos biométricos.", + "authNotSupportedTitle": "Se requieren credenciales de dispositivo o datos biométricos", + "authToAcceptPushRequest": "Por favor, autentifíquese para aceptar la solicitud push.", + "authToDeclinePushRequest": "Por favor, autentifíquese para rechazar la solicitud push.", + "authenticateToShowOtp": "Por favor, autentifíquese para mostrar la contraseña de una sola vez.", + "authenticateToUnLockToken": "Por favor, autentifíquese para cambiar el estado de bloqueo del token.", + "authenticationRequest": "Autenticación", + "biometricHint": "AAutenticación necesaria", + "biometricNotRecognized": "No reconocido. Inténtelo de nuevo.", + "biometricRequiredTitle": "Biometría no configurada", + "biometricSuccess": "Autenticación correcta", + "butDiscardIt": "pero descártelo", + "cancel": "Anular", + "checkServerCertificate": "Compruebe el certificado del servidor", + "checkYourNetwork": "Compruebe su conexión de red e inténtelo de nuevo.", + "clearErrorLog": "Borrar", + "closeSearchTokens": "Cerrar búsqueda", + "confirmDeletion": "Confiem supresión", + "confirmDeletionOf": "¿Está seguro de que desea eliminar {name}?", + "confirmFolderDeletionHint": "Eliminar una carpeta no afecta a los tokens que contiene.\nLos tokens se mueven a la lista principal.", + "confirmPassword": "Confirmar contraseña", + "confirmTokenDeletionHint": "Es posible que ya no pueda iniciar sesión si elimina este token.\nAsegúrese de que puede iniciar sesión en la cuenta asociada sin este token.", + "confirmation": "confirmación", + "connectionFailed": "Conexión fallida.", + "container": "Contenedor", + "continueButton": "Continue", + "copyOTPToClipboard": "Copiar OTP al portapapeles", + "couldNotConnectToServer": "No se ha podido conectar con el servidor.", + "couldNotSignMessage": "No se ha podido firmar el mensaje.", + "counter": "Contador", + "create": "Crear", + "createdAt": "Creado el", + "creator": "Creador", + "decline": "Negar", + "declineIt": "rechazar", + "decrypt": "Descifrar", + "decryptErrorButtonDelete": "Borrar", + "decryptErrorButtonRetry": "Reintentar", + "decryptErrorButtonSendError": "Enviar error", + "decryptErrorContent": "Lamentablemente, la aplicación no ha podido descifrar tus tokens. Esto indica que la clave de cifrado está rota. Puedes volver a intentarlo o borrar los datos de la app, lo que eliminaría los tokens de la app.", + "decryptErrorDeleteConfirmationContent": "¿Estás seguro de que quieres borrar los datos de la aplicación?", + "decryptErrorTitle": "Error de descifrado", + "delete": "Borrar", + "deleteLockedToken": "Por favor, autentíquese para eliminar el token bloqueado.", + "deviceCredentialsRequiredTitle": "No se han configurado las credenciales del dispositivo.", + "deviceCredentialsSetupDescription": "Configurar las credenciales del dispositivo en los ajustes del dispositivo", + "digits": "Dígitos", + "dismiss": "Desestimar", + "done": "Hecho", + "edit": "Editar", + "editLockedToken": "Por favor, autentíquese para editar el token bloqueado.", + "editToken": "Editar token", + "enablePolling": "Activar polling", + "encoding": "Codificación", + "enterDetailsForToken": "Introduzca los datos de el token", + "enterLink": "Introducir enlace", + "enterPasswordToEncrypt": "Ingrese una contraseña para cifrar los tokens. Esta contraseña será necesaria para importar los tokens.", + "errorLogCleared": "Registro de errores borrado", + "errorLogEmpty": "El registro de errores está vacío", + "errorLogTitle": "Registro de errores", + "errorMailBody": "Se adjunta el archivo de registro de errores.\nPuede sustituir este texto por información adicional sobre el error.", + "errorRollOutFailed": "Error en la extracción de el token {name}.", + "errorRollOutNoConnectionToServer": "El despliegue del token {name} ha fallado, no se ha podido acceder al servidor.", + "errorRollOutNotPossibleAnymore": "El despliegue de este token ya no es posible.", + "errorRollOutSSLHandshakeFailed": "Ha fallado el protocolo SSL. No es posible el despliegue.", + "errorRollOutUnknownError": "An unknown error occurred. Roll-out not possible: {e}", + "errorSavingFile": "Error al guardar el archivo", + "errorSynchronizationNoNetworkConnection": "Error al sincronizar los tokens. No se ha podido acceder al servidor de PrivacyIDEA.", + "errorTokenExpired": "El token {name} ha caducado.", + "errorUnlinkingPushToken": "Error al desvincular el token push {label}", + "errorWhenPullingChallenges": "Se ha producido un error al buscar retos de {name}", "exampleUrl": "Por favor, introduzca una URL válida como: \"https://example.com/\"", - "pushEndpointUrl": "URL del punto final push", + "expandLockedFolder": "Por favor, autentifíquese para abrir la carpeta bloqueada.", + "export": "Exportar", + "exportAllTokens": "Exportar todos los tokens", + "exportLockedTokenReason": "Por favor, autentíquese para exportar tokens bloqueados.", + "exportNonPrivacyIDEATokens": "Exportar tokens no privacyIDEA", + "exportTokens": "Exportar tokens", + "exportingTokens": "Exportando tokens...", + "failedToLoad": "Fallo al cargar:", + "feedback": "Comentarios", + "feedbackDescription": "Si tienes alguna pregunta, sugerencia o problema, háznoslo saber", + "feedbackHint": "Se abrirá un correo electrónico preparado que podrá enviarnos. Si lo desea, se añadirá información sobre su dispositivo y la versión de la aplicación. Puede comprobar y editar el correo electrónico antes de enviarlo.", + "feedbackPrivacyPolicy1": "Al enviar sus comentarios, acepta nuestra ", + "feedbackPrivacyPolicy2": "política de privacidad", + "feedbackPrivacyPolicy3": ".", + "feedbackSentDescription": "Muchas gracias por su ayuda para mejorar esta aplicación.", + "feedbackSentTitle": "Comentarios enviados", + "feedbackTitle": "¡Tus comentarios son siempre bienvenidos!", + "fileSavedToDownloadsFolder": "Archivo guardado en la carpeta de descargas", + "findingQrCodeInImage": "Buscando código QR en imagen...", + "firebaseToken": "Token de Firebase", + "folderName": "Nombre de la carpeta", + "generatingPhonePart": "Generar parte telefónico", + "generatingRSAKeyPair": "Generando par de claves RSA", + "generatingRSAKeyPairFailed": "Error al generar el par de claves RSA", + "goToSettingsButton": "Ir a la configuración", + "goToSettingsDescription": "La autenticación por credenciales o biométrica no está configurada en tu dispositivo. Por favor, configúrala en los ajustes del dispositivo.", + "grantCameraPermissionDialogButton": "Conceder permiso", + "grantCameraPermissionDialogContent": "Por favor, concede permiso a la cámara para escanear códigos QR", + "grantCameraPermissionDialogPermanentlyDenied": "El permiso de cámara está denegado permanentemente. Concede el permiso de cámara en la configuración del teléfono", + "grantCameraPermissionDialogTitle": "El permiso de cámara no está concedido", + "handshakeFailed": "Handshake fallido", + "hidePushTokens": "Ocultar tokens push", + "hidePushTokensDescription": "Ocultar tokens push de la lista de tokens. Esto no borrará los tokens y seguirán siendo visibles en una pantalla aparte", + "imageUrl": "URL de la imagen", + "importConflictToken": "{count, plural, zero{No hay conflicto con tokens que ya existen.} one{Hay un conflicto con tokens que ya existen.\nPor favor, seleccione cuál le gustaría conservar.} other{Hay un conflicto con tokens que ya existen.\nPor favor, seleccione cuál le gustaría conservar.}}", + "importExistingToken": "{count, plural, zero{No se ha encontrado ningún token que ya esté en la aplicación.} one{Se ha encontrado un token que ya existe en la aplicación.} other{Se han encontrado {count} tokens que ya están en la aplicación.}}", + "importExportTokens": "Importar/Exportar tokens", + "importFailedToken": "{count, plural, zero{No token Fallo al importar.} one{Error al importar un token.} other{Error al importar {count} tokens.}}", + "importHint2FAS": "Seleccione su copia de seguridad de 2FAS. Si no tiene una copia de seguridad, cree una en la aplicación 2FAS. Le recomendamos que utilice una contraseña", + "importHintAegisBackupFile": "Seleccione su exportación de Aegis (.JSON).\nSi no tiene una exportación, cree una a través del menú de configuración en la app de Aegis. Se recomienda utilizar una contraseña", + "importHintAegisLink": "Introduzca el enlace que recibe al transferir entradas desde Aegis", + "importHintAegisQrScan": "Escanea el código QR que recibes al transferir entradas desde Aegis", + "importHintAuthenticatorProFile": "Para crear una copia de seguridad de la aplicación Authenticator Pro, vaya a la configuración y pulse en \"Copia de seguridad automática\". Seleccione una ubicación de almacenamiento y establezca una contraseña. A continuación, pulse \"Hacer copia de seguridad ahora\" para exportar los tokens.", + "importHintFreeOtpPlusFile": "Para crear una copia de seguridad de la app FreeOTP+, pulse los tres puntos de la esquina superior derecha y seleccione \"Exportar\". Puede elegir entre los formatos JSON y URI. Recomendamos eliminar la copia de seguridad después de importarla, ya que no está cifrada.", + "importHintFreeOtpPlusQrScan": "Escanea el código QR que recibes al pulsar los tres puntos en el azulejo de la ficha y selecciona \"Compartir código QR\".", + "importHintGoogleQrFile": "Selecciona un archivo de imagen con el código QR que recibes al exportar tus cuentas desde Google Authenticator.\n!! Tenga en cuenta que no es seguro guardar el código QR en su dispositivo, ya que los tokens no están cifrados !!", + "importHintGoogleQrScan": "Escanea el código QR que recibes al exportar tus cuentas desde Google Authenticator", + "importHintPrivacyIdeaFile": "Para crear una copia de seguridad, vaya a los ajustes y pulse sobre \"Exportar\". Seleccione \"Como archivo\", seleccione los tokens que desea exportar. A continuación, pulse \"Exportar\" y establezca una contraseña. La ubicación de almacenamiento es la carpeta de descargas de su dispositivo.", + "importHintPrivacyIdeaQrScan": "Para crear códigos QR de las fichas, vaya a la configuración y pulse sobre \"Exportar\". A continuación, seleccione \"Como código QR\" y pulse sobre la ficha que desea exportar. Esta variante sólo es adecuada para la transferencia directa a otro dispositivo, ya que el código QR no está cifrado.", + "importNTokens": "{count, plural, zero{No importar tokens} one{Importar un token} other{Importar {count} tokens}}", + "importNewToken": "{count, plural, zero{No se ha encontrado ningún token nuevo.} one{Se ha encontrado un nuevo token que se puede importar.} other{Se han encontrado {count} nuevos tokens que se pueden importar.}}", + "importTokens": "Importar token", + "importedVia": "Importado a través de", + "increaseCounter": "Incrementar contador", + "internalServerError": "Error interno del servidor ({code})", + "introAddFolder": "Puedes crear carpetas\npara organizar tus tokens", + "introAddTokenManually": "Si no quieres escanear un código QR, también puedes añadir tokens manualmente", + "introDragToken": "Reorganiza tus tokens pulsándolo durante unos segundos y arrastrándolo a la posición deseada", + "introEditToken": "Aquí puedes editar el nombre del token y ver algunos detalles", + "introHidePushTokens": "Tus push tokens están ahora ocultos.\nPero puedes seguir viéndolos en la pantalla de push tokens.", + "introLockToken": "Para mejorar la seguridad aún más, puedes bloquear los tokens.\nEntonces el token sólo se puede utilizar después de la autenticación.", + "introPollForChallenges": "Puedes buscar nuevos retos arrastrando hacia abajo la lista de tokens", + "introScanQrCode": "Puedes escanear códigos QR para añadir tokens.\nSoportamos todos los tokens comunes de Two-Factor-Authentication y también los tokens privacyIDEA", + "introTokenSwipe": "Desliza los tokens hacia la izquierda para ver las acciones disponibles", + "invalidBackupFile": "El archivo seleccionado no es una copia de seguridad válida de {appName}", + "invalidLink": "El enlace introducido no es un token válido de {appName}, o no es compatible", + "invalidQrFile": "El archivo seleccionado no contiene un código QR válido de {appName}", + "invalidQrScan": "El código QR escaneado no es una copia de seguridad válida de {appName}", + "invalidValue": "untranslated", + "invalidValueIn": "untranslated", + "isExpotableQuestion": "¿Es exportable?", + "isPiTokenQuestion": "¿Es un token de privacyIDEA?", + "language": "Language", + "legacySigningErrorMessage": "El token se creó en una versión obsoleta de la aplicación, lo que puede provocar problemas al utilizarlo.\nSe recomienda crear un nuevo token push si el problema persiste.", + "legacySigningErrorTitle": "Se ha producido un error al utilizar el token obsoleto: {tokenLabel}", + "licenses": "Licencias", + "licensesAndVersion": "Licencias y versión", + "linkedContainer": "Contenedor vinculado", + "lock": "Cierre", + "lockOut": "La autenticación biométrica está desactivada. Bloquea y desbloquea la pantalla para activarla.", + "logMenu": "Menú de registro", + "malformedData": "Datos mal formados", + "markQrCode": "Marcar código QR", + "missingRequiredParameter": "El valor del parámetro [{parameter}] es obligatorio, pero falta.", + "missingRequiredParameterIn": "El valor del parámetro [{parameter}] es obligatorio, pero falta en \"{map}\".", "mustNotBeEmpty": "{field} no debe estar vacío", - "@mustNotBeEmpty": { - "placeholders": { - "field": { - "example": "Name" - } - } - }, - "sendPushRequestResponseFailed": "No se ha podido enviar la respuesta.", - "@sendPushRequestResponseFailed": { - "description": "Error message when the response to a push request could not be sent." - }, + "name": "Nombre", + "no": "No", + "noFbToken": "No hay token de Firebase.", + "noMailAppDescription": "No hay ninguna app de correo electrónico instalada o inicializada en este dispositivo, inténtalo de nuevo cuando puedas enviar un mensaje de correo electrónico.", + "noMailAppTitle": "No hay aplicación de correo electrónico", + "noNetworkConnection": "No hay conexión a la red.", + "noPublicKey": "No hay clave pública.", + "noResultText1": "Indique el ", + "noResultText2": " para empezar.", + "noResultTitle": "Aún no hay tokens almacenadas.", + "noTokenToExport": "No hay token disponible para exportar", + "notAnInteger": "El valor no es un entero.", + "notAnNumber": "El valor no es un número.", + "ok": "Ok", + "oneMore": "Uno más", + "open": "Abrir", + "optionalMessage": "Mensaje opcional", + "originApp": "Aplicación Origen", + "originDetails": "Datos de origen", + "otpValueCopiedMessage": "Contraseña \"{otpValue}\" copiada en el portapapeles.", + "parsingResponse": "Analizando la respuesta", + "parsingResponseFailed": "Error al analizar la respuesta", + "password": "Contraseña", "passwordCannotBeEmpty": "La contraseña no puede estar vacía", - "passwordMustBeAtLeast8Characters": "La contraseña debe tener al menos 8 caracteres", "passwordCannotContainWhitespace": "La contraseña no puede contener espacios en blanco", + "passwordMustBeAtLeast8Characters": "La contraseña debe tener al menos 8 caracteres", "passwordMustContainLowercaseLetter": "La contraseña debe contener una letra minúscula", - "passwordMustContainUppercaseLetter": "La contraseña debe contener una letra mayúscula", "passwordMustContainNumber": "La contraseña debe contener un número", "passwordMustContainSpecialCharacter": "La contraseña debe contener un carácter especial", + "passwordMustContainUppercaseLetter": "La contraseña debe contener una letra mayúscula", "passwordsDoNotMatch": "Las contraseña no coinciden", - "selectTokensToExportHelpTitle": "¿Su ficha no figura en la lista?", - "selectTokensToExportHelpContent": "Si un token no aparece en la lista, no se garantiza que no sea un token privacyIDEA.\nActualmente sólo se pueden exportar los tokens añadidos manualmente y los importados.", - "findingQrCodeInImage": "Buscando código QR en imagen...", - "qrNotFound": "No se ha encontrado ningún código QR.", + "patchNotesBugFixes": "Corrección de errores", + "patchNotesDialogTitle": "¿Qué hay de nuevo?", + "patchNotesImprovements": "Mejoras", + "patchNotesNewFeatures": "Nuevas características", + "patchNotesV4_3_0NewFeatures1": "Añadido soporte para importar tokens de Google, Aegis y 2FAS Authenticator. En el futuro se añadirán más fuentes de importación", + "patchNotesV4_3_0NewFeatures2": "Añadida opción de feedback a los ajustes", + "patchNotesV4_3_0NewFeatures3": "Los tokens push ahora se pueden ocultar de la lista de tokens", + "patchNotesV4_3_0NewFeatures4": "Se han añadido introducciones para ayudar a los nuevos usuarios a empezar", + "patchNotesV4_3_0NewFeatures5": "Ahora puedes buscar tokens tocando la lupa de la esquina superior derecha", + "patchNotesV4_3_0NewFeatures6": "Añadido Token HomeWidget para Android 12 y posteriores", + "patchNotesV4_3_1BugFix1": "Se ha corregido un problema donde el valor OTP no se mostraba después de la autenticación en algunos dispositivos.", + "patchNotesV4_3_1Improvement1": "Se ha mejorado el escáner de códigos QR.", + "patchNotesV4_4_0Improvement1": "Se han añadido más fuentes de importación.", + "patchNotesV4_4_0Improvement2": "Se ha mejorado el reconocimiento de códigos QR a partir de archivos de imagen.", + "patchNotesV4_4_0NewFeatures1": "Ahora es posible exportar tokens cuando se puede garantizar que no son tokens de privacyIDEA. Actualmente, no se puede descartar que los tokens añadidos a través del escáner de código QR procedan de privacyIDEA. La diferenciación se mejorará en futuras versiones.", + "patchNotesV4_4_0NewFeatures2": "Añadido soporte para privacyIDEA's \"require presence\".", + "period": "Periodo", + "phonePart": "Pieza de teléfono:", + "pleaseEnterANameForThisToken": "Introduzca un nombre para este token.", + "pleaseEnterASecretForThisToken": "Por favor, introduzca un secreto para este token.", + "pleaseSyncManuallyWhenNetworkIsAvailable": "Por favor, sincronice los tokens push manualmente a través de los ajustes cuando haya una conexión de red disponible.", + "pollingChallenges": "Sondeo para nuevos challenges", + "pollingFailed": "Consulta fallida.", + "pollingFailedFor": "Fallo de sondeo para {serial}", + "privacyPolicy": "Política de privacidad", + "publicKey": "Clave pública", + "pushEndpointUrl": "URL del punto final push", + "pushRequestParseError": "No se ha podido procesar la solicitud push.", + "pushToken": "Push Token", + "pushTokens": "Push Tokens", + "qrFileDecodeError": "No fue posible decodificar el código QR de la imagen seleccionada, por favor utilice el escáner de código QR en su lugar.", "qrInFileNotFound": "No se ha encontrado ningún código QR en la imagen seleccionada.", "qrInFileNotFound2": "Puedes mostrarme dónde está el código QR.", "qrInFileNotFound3": "Espero encontrar el código si está en el centro del área marcada.", - "markQrCode": "Marcar código QR", - "malformedData": "Datos mal formados" + "qrNotFound": "No se ha encontrado ningún código QR.", + "qrScan": "Escanear", + "rename": "Renombrar", + "renameToken": "Renombrar token", + "renameTokenFolder": "Cambiar nombre de carpeta", + "requestInfo": "Enviado por {issuer} para su cuenta: \"{account}\"", + "requestPushChallengesPeriodically": "Solicita retos push al servidor periódicamente. Habilite esta opción si los retos push no se reciben normalmente.", + "requestTriggerdByUserQuestion": "¿Fue usted quien provocó esta petición?", + "retry": "Reintentar", + "retryRollout": "Reintentar despliegue", + "rolloutCompleted": "Despliegue completado", + "save": "Guardar", + "scanQrCode": "Escanear código QR", + "scanThisQrWithNewDevice": "Escanee este código QR con su nuevo dispositivo para importar el token.", + "searchTokens": "Buscar tokens", + "secretIsRequired": "Se requiere secreto", + "secretKey": "Clave secreta", + "selectFile": "Seleccionar archivo", + "selectImportSource": "Seleccionar fuente de importación", + "selectImportType": "Jak chcete importovat žetony?", + "selectTokensToExport": "{count, plural, zero{} one{Seleccionar token para exportar} other{Seleccionar tokens para exportar}}", + "selectTokensToExportHelpContent": "Si un token no aparece en la lista, no se garantiza que no sea un token privacyIDEA.\nActualmente sólo se pueden exportar los tokens añadidos manualmente y los importados.", + "selectTokensToExportHelpTitle": "¿Su ficha no figura en la lista?", + "send": "Enviar", + "sendErrorDialogBody": "Se ha producido un error inesperado en la aplicación. La siguiente información puede ser enviada a los desarrolladores por correo electrónico para ayudar a prevenir este error en el futuro.", + "sendErrorLogDescription": "Se crea un correo electrónico listo.\nContiene información sobre la app, el error y el dispositivo.\nPuedes editar el correo antes de enviarlo.\nAquí puede ver cómo utilizamos la información:", + "sendPushRequestResponseFailed": "No se ha podido enviar la respuesta.", + "sendingRSAPublicKey": "Enviando clave pública RSA", + "sendingRSAPublicKeyFailed": "Error al enviar la clave pública RSA", + "serverNotReachable": "No se ha podido acceder al servidor.", + "settings": "Configuración", + "settingsGroupGeneral": "Información general", + "showDetails": "Mostrar detalles", + "showErrorLog": "Mostrar", + "showPrivacyPolicy": "Mostrar política de privacidad", + "signInTitle": "Autenticación necesaria", + "someTokensDoNotSupportPolling": "Algunos tokens están obsoletos y no admiten la consulta activa para la autenticación mediante mensaje push.", + "startRollout": "Iniciar despliegue", + "statusCode": "Código de estado: {statusCode}", + "sync": "Sinchronizar", + "syncContainerFailed": "Error en la sincronización de contenedores", + "syncFbTokenFailed": "La sincronización ha fallado para los siguientes tokens, por favor inténtelo de nuevo:", + "synchronizePushTokens": "Sinchronizar push tokens", + "synchronizesTokensWithServer": "Sinchronizar tokens con el privacyIDEA servidor.", + "synchronizingTokens": "Sincronización de los tokens.", + "theSecretDoesNotFitTheCurrentEncoding": "El secreto no se ajusta a la codificación actual", + "themeMode": "Tema", + "thisAppIsOpenSource": "Esta aplicación es de código abierto\nVisítanos en GitHub", + "timeOut": "Tiempo de espera", + "tokenDataParseError": "No se han podido analizar los datos del token.", + "tokenDetails": "Detalles de la token", + "tokenLink": "Enlace token", + "tokenSerial": "Token serial", + "tokensAreEncrypted": "Los tokens están encriptados. Por favor, introduce la contraseña para descifrarlos", + "tokensDoNotSupportSynchronization": "Las siguientes tokens no admiten la sincronización y deben volver a desplegarse:", + "tokensNotEncrypted": "Los tokens no están encriptados y se pueden importar directamente", + "tokensSuccessfullyDecrypted": "Los tokens se han descifrado correctamente y ya se pueden importar.", + "type": "Tipo", + "unexpectedError": "Se ha producido un error inesperado.", + "unknown": "Desconocido", + "unlock": "Desbloquear", + "unsupported": "{name} [{value}] no es compatible con esta versión de la aplicación.", + "useDeviceLocaleDescription": "Utilizar el idioma del dispositivo si está soportado, en caso contrario por defecto inglés.", + "useDeviceLocaleTitle": "Utiliza el idioma del teléfono", + "validFor": "Válido para", + "validUntil": "Válido hasta", + "valueNotAllowed": "untranslated", + "valueNotAllowedIn": "untranslated", + "verboseLogging": "Registro detallado", + "version": "Versión", + "wrongPassword": "Contraseña incorrecta", + "yes": "Sí" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 47021be0e..8d2fea37d 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1,381 +1,362 @@ { - "@@last_modified": "2023-08-07", - "patchNotesNewFeatures": "Nouvelles caractéristiques", - "patchNotesImprovements": "Améliorations", - "patchNotesBugFixes": "Bug fixes", - "patchNotesV4_4_0NewFeatures1": "Il est désormais possible d'exporter des jetons dont on peut s'assurer qu'il ne s'agit pas de jetons privacyIDEA. Actuellement, il n'est pas possible d'exclure que des jetons ajoutés via le lecteur de code QR proviennent de privacyIDEA. La différenciation sera améliorée dans les versions futures.", - "patchNotesV4_4_0NewFeatures2": "Ajout du support pour privacyIDEA's \"require presence\".", - "patchNotesV4_4_0Improvement1": "D'autres sources d'importation ont été ajoutées.", - "patchNotesV4_4_0Improvement2": "La reconnaissance des codes QR à partir des fichiers image a été améliorée.", - "patchNotesV4_3_1BugFix1": "Un problème a été corrigé où la valeur OTP n'était pas affichée après l'authentification sur certains appareils.", - "patchNotesV4_3_1Improvement1": "Le scanner de codes QR a été amélioré.", - "patchNotesV4_3_0NewFeatures1": "Ajout de la prise en charge de l'importation de jetons depuis Google, Aegis et 2FAS Authenticator. D'autres sources d'importation seront ajoutées à l'avenir.", - "patchNotesV4_3_0NewFeatures2": "Ajout d'une option de retour d'information dans les paramètres", - "patchNotesV4_3_0NewFeatures3": "Les jetons de poussée peuvent maintenant être cachés de la liste des jetons", - "patchNotesV4_3_0NewFeatures4": "Des introductions ont été ajoutées pour aider les nouveaux utilisateurs à démarrer", - "patchNotesV4_3_0NewFeatures5": "Vous pouvez désormais rechercher des jetons en appuyant sur la loupe dans le coin supérieur droit", - "patchNotesV4_3_0NewFeatures6": "Ajout du jeton HomeWidget pour Android 12 et les versions ultérieures", - "guide": "Assistant", - "@guide": { - "description": "Button to open the guide screen." - }, - "retry": "Réessayer", - "@retry": { - "description": "Label for e.g. a button. Something is tried to be done again." - }, - "accept": "Accepter", + "@@last_modified": "2024-09-20", + "@@locale": "fr", "@accept": { "description": "Label for e.g. a button. Something gets accepted by the user." }, - "decline": "Refuser", - "@decline": { - "description": "Label for e.g. a button. Something gets declined by the user." - }, - "name": "Nom", - "@name": { - "description": "Describes the field where the tokens name should be entered." - }, - "secretKey": "Clé secrète", - "@secretKey": { - "description": "Describes the field where the tokens secret should be entered." - }, - "encoding": "Encodage", - "@encoding": { - "description": "Title of the dropdown button where the encoding is selected." + "@addToken": { + "description": "The button to open the screen to add tokens by hand." }, - "algorithm": "Algorithme", "@algorithm": { "description": "Title of the dropdown button where the encoding is selected." }, - "digits": "Chiffres", - "@digits": { - "description": "Title of the dropdown button where the number of digits for the opt value is selected." + "@algorithmUnsupported": { + "placeholders": { + "algorithm": { + "example": "MD5" + } + } }, - "type": "Type", - "@type": { - "description": "Title of the dropdown button where the type of the token is selected." + "@allTokensSynchronized": { + "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "period": "Période", - "@period": { - "description": "Title of the dropdown button where the period of the totp token is selected." + "@authNotSupportedBody": { + "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." }, - "rename": "Renommer", - "@rename": { - "description": "Label that describes renaming the token." + "@authNotSupportedTitle": { + "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." }, - "cancel": "Annuler", - "@cancel": { - "description": "Button to cancel an action." + "@authToAcceptPushRequest": { + "description": "Message to accepting a push request for authentication." }, - "delete": "Supprimer", - "@delete": { - "description": "Label that describes deleting the token." + "@authToDeclinePushRequest": { + "description": "Message for declining a push request for authentication." }, - "dismiss": "Fermer", - "@dismiss": { - "description": "Text of a button that closes a dialog." + "@authenticateToShowOtp": { + "description": "Reason to authenticate when trying to view a one time password." }, - "addToken": "Ajouter un jeton", - "@addToken": { - "description": "The button to open the screen to add tokens by hand." + "@authenticateToUnLockToken": { + "description": "Reason to authenticate when trying to lock or unlock a token." }, - "scanQrCode": "Numériser QR-Code", - "@scanQrCode": { - "description": "The button to scan otpauth qr-codes." + "@biometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." }, - "enterDetailsForToken": "Saisissez les détails du jeton", - "@enterDetailsForToken": { - "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." + "@biometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterANameForThisToken": "Veuillez saisir un nom pour ce jeton.", - "@pleaseEnterANameForThisToken": { - "description": "Hint telling the user to enter a name for a token." + "@biometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterASecretForThisToken": "Veuillez saisir un secret pour ce jeton.", - "@pleaseEnterASecretForThisToken": { - "description": "Hint telling the user to enter a secret for a token." + "@biometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." }, - "theSecretDoesNotFitTheCurrentEncoding": "Le secret n'est pas compatible avec \nl'encodage actuel.", - "@theSecretDoesNotFitTheCurrentEncoding": { - "description": "Hint telling the user that the secret does not fit the selected encoding." + "@cancel": { + "description": "Button to cancel an action." }, - "renameToken": "Renommer jeton", - "@renameToken": { - "description": "Title of the dialog where a new name for a token can be entered." + "@checkServerCertificate": { + "description": "Error message when the server certificate should be checked." + }, + "@checkYourNetwork": { + "description": "Tells the user to check the network connection." + }, + "@clearErrorLog": { + "description": "Button to clear the error log." }, - "confirmDeletion": "Confirmer suppression", "@confirmDeletion": { "description": "Title of the dialog where a token can be deleted." }, - "confirmDeletionOf": "Confirmer la suppression de {name}?", "@confirmDeletionOf": { "description": "Asks for confirmation on deleting a token.", - "type": "text", "placeholders": { "name": { "example": "PUSH1234" } - } - }, - "confirmTokenDeletionHint": "Il se peut que vous ne puissiez plus vous connecter si vous supprimez ce token.\nVeuillez vous assurer que vous pouvez vous connecter au compte associé sans ce token.", - "@confirmTokenDeletionHint": { - "description": "Gives the user a hint about the consequences of deleting a token." + }, + "type": "text" }, - "confirmFolderDeletionHint": "La suppression d'un dossier n'a aucun effet sur les tokens qui s'y trouvent.\nLes tokens sont déplacés dans la liste principale.", "@confirmFolderDeletionHint": { "description": "Gives the user a hint about the consequences of deleting a folder." }, - "generatingPhonePart": "Générer la part du téléphone", - "@generatingPhonePart": { - "description": "Title of a dialog telling the user that the phone part gets generated right now." + "@confirmTokenDeletionHint": { + "description": "Gives the user a hint about the consequences of deleting a token." }, - "phonePart": "Part du téléphone:", - "@phonePart": { - "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." + "@connectionFailed": { + "description": "Tells the user that the connection failed." }, - "otpValueCopiedMessage": "Le mot de passe \"{otpValue}\" a été copié dans le presse-papier.", - "@otpValueCopiedMessage": { - "description": "Tells the user that the otp value was copied to the clipboard.", - "type": "text", - "placeholders": { - "otpValue": { - "example": "055374" - } - } + "@container": { + "description": "Title for the container view." }, - "settings": "Paramètres", - "@settings": { - "description": "Button to open the settings page." + "@couldNotSignMessage": { + "description": "Tells the user that the message could not be signed." }, - "pushToken": "Jeton de type Push", - "@pushToken": { - "description": "Title for the settings block concerning the push tokens." + "@counter": { + "description": "Describes the field where the tokens counter should be entered." }, - "theme": "Thème", - "@theme": { - "description": "Title of the setting group where the theme can be selected." + "@createdAt": { + "description": "Label for the creation date of the token." }, - "lightTheme": "Thème lumineux", - "@lightTheme": { - "description": "The light theme." + "@creator": { + "description": "Label for the creator of the token." }, - "darkTheme": "Thème sombre", "@darkTheme": { "description": "The dark theme." }, - "systemTheme": "Utiliser le thème de l'appareil", - "@systemTheme": { - "description": "The systems theme." + "@decline": { + "description": "Label for e.g. a button. Something gets declined by the user." }, - "someTokensDoNotSupportPolling": "Certains jetons sont obsolètes et ne supportent pas l'interrogation due serveur.", - "@someTokensDoNotSupportPolling": { - "description": "Tells the user, that the following tokens do not support polling.", - "type": "text", - "placeholders": {} + "@delete": { + "description": "Label that describes deleting the token." }, - "enablePolling": "Activer l'interrogation du serveur.", - "@enablePolling": { - "description": "Name of the setting switch that enables polling." + "@deviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." }, - "requestPushChallengesPeriodically": "Demander des challenges push depuis le serveur périodiquement. Activer cette fonction si les challenges push ne sont pas reçus normalement.", - "@requestPushChallengesPeriodically": { - "description": "The description of the polling feature." + "@deviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." }, - "synchronizePushTokens": "Synchoniser les jetons Push", - "@synchronizePushTokens": { - "description": "Title of synchronizing push tokens in settings." + "@digits": { + "description": "Title of the dropdown button where the number of digits for the opt value is selected." }, - "synchronizesTokensWithServer": "Synchroniser les jetons Push avec le serveur privacyIDEA.", - "@synchronizesTokensWithServer": { - "description": "Description of synchronizing push tokens in settings." + "@dismiss": { + "description": "Text of a button that closes a dialog." }, - "sync": "Synchroniser", - "@sync": { - "description": "Text of button that is used to synchronize push tokens." + "@enablePolling": { + "description": "Name of the setting switch that enables polling." }, - "synchronizingTokens": "Synchroniser les jetons.", - "@synchronizingTokens": { - "description": "Title of the push synchronization dialog." + "@encoding": { + "description": "Title of the dropdown button where the encoding is selected." }, - "allTokensSynchronized": "Tous les jetons ont été synchronisés.", - "@allTokensSynchronized": { - "description": "Content of the push synchronization dialog. Signaling the user that everything worked." + "@enterDetailsForToken": { + "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." }, - "syncFbTokenFailed": "La synchronisation a échoué pour ces jetons, veuillez reéssayer:", - "@syncFbTokenFailed": { - "description": "Headline for the list of tokens where the synchronization failed." + "@errorLogCleared": { + "description": "Message that tells the user that the error log was cleared." }, - "tokensDoNotSupportSynchronization": "Ces jetons ne supportent pas la synchronisation et doivent être de nouveau générés:", - "@tokensDoNotSupportSynchronization": { - "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + "@errorLogEmpty": { + "description": "Message that tells the user that the error log is empty." + }, + "@errorLogTitle": { + "description": "Title of the error log screen." + }, + "@errorMailBody": { + "description": "Message for email body" }, - "errorRollOutFailed": "Le déploiement du jeton {name} a échoué.", "@errorRollOutFailed": { "description": "Tells the user that the token could not be rolled out, because a network error occurred.", - "type": "text", "placeholders": { "name": { "example": "PUSH1234A" } - } + }, + "type": "text" }, - "statusCode": "Code d'état : {statusCode}", - "@statusCode": { - "description": "Tells the user the status code of the error.", + "@errorRollOutNoConnectionToServer": { + "description": "Message for the rollout process", "placeholders": { - "statusCode": { - "example": "400" + "name": { + "example": "PUSH1234A" } } }, - "errorSynchronizationNoNetworkConnection": "La synchronization a échoué car le serveur est injoignable.", - "@errorSynchronizationNoNetworkConnection": { - "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." + "@errorRollOutSSLHandshakeFailed": { + "description": "Tells the user that the roll-out failed because the SSL handshake failed." }, - "errorRollOutUnknownError": "Le déploiement a échoué suite à une erreur inconnue: {e}", "@errorRollOutUnknownError": { "description": "Tells the user that the roll-out failed because of an unknown error.", - "type": "text", "placeholders": { "e": { "example": "IllegalArgumentException on Line 5 ..." } - } - }, - "startRollout": "Démarrer le déploiement", - "@startRollout": { - "description": "Label that tells the user that the token is being rolled out." - }, - "pollingChallenges": "Vérification de nouveaux challenges", - "@pollingChallenges": { + }, "type": "text" }, - "unexpectedError": "Une erreur inattendue s'est produite.", - "@unexpectedError": { - "description": "Title of page report mode." - }, - "pollingFailed": "Échec de la requête.", - "@pollingFailed": { - "description": "Tells the user that the polling failed." + "@errorSynchronizationNoNetworkConnection": { + "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." }, - "pollingFailedFor": "Echec de la requête pour {serial}.", - "@pollingFailedFor": { - "description": "Tells the user that the polling failed.", + "@errorTokenExpired": { "placeholders": { - "serial": { + "name": { "example": "PUSH1234A" } } }, - "noNetworkConnection": "Pas de connexion au réseau.", - "@noNetworkConnection": { - "description": "Tells the user that there is no network connection." + "@errorUnlinkingPushToken": { + "description": "Error message when unlinking a push token failed.", + "placeholders": { + "label": { + "example": "PUSH1234A" + } + } }, - "connectionFailed": "La connexion a échoué.", - "@connectionFailed": { - "description": "Tells the user that the connection failed." + "@errorWhenPullingChallenges": { + "description": "errorWhenPullingChallenges", + "placeholders": { + "name": { + "example": "PUSH1234A" + } + } }, - "checkYourNetwork": "Veuillez vérifier votre connexion réseau et réessayer.", - "@checkYourNetwork": { - "description": "Tells the user to check the network connection." + "@failedToLoad": { + "placeholders": { + "name": { + "example": "token data" + } + } }, - "serverNotReachable": "Le serveur n'a pas pu être atteint.", - "@serverNotReachable": { - "description": "Tells the user that the server could not be reached." + "@feedbackPrivacyPolicy2": { + "description": "Taping on this should open the privacy policy." }, - "couldNotSignMessage": "Impossible de signer le message.", - "@couldNotSignMessage": { - "description": "Tells the user that the message could not be signed." + "@generatingPhonePart": { + "description": "Title of a dialog telling the user that the phone part gets generated right now." }, - "useDeviceLocaleTitle": "Utiliser la langue de l'appareil", - "@useDeviceLocaleTitle": { - "description": "Title of the switch tile where using the devices language can be enabled." + "@generatingRSAKeyPair": { + "description": "Message for the rollout process" }, - "useDeviceLocaleDescription": "Utilisez la langue de l'appareil si elle est prise en charge, sinon l'anglais par défaut.", - "@useDeviceLocaleDescription": { - "description": "Description of the switch tile where using the devices language can be enabled." + "@generatingRSAKeyPairFailed": { + "description": "Message for the rollout process" }, - "language": "Langue", - "@language": { - "description": "Title of language setting group." + "@goToSettingsButton": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." }, - "authenticateToShowOtp": "Veuillez vous authentifier pour afficher un mot de passe à usage unique.", - "@authenticateToShowOtp": { - "description": "Reason to authenticate when trying to view a one time password." + "@goToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." }, - "authenticateToUnLockToken": "Veuillez vous authentifier pour modifier l'état de verrouillage du jeton.", - "@authenticateToUnLockToken": { - "description": "Reason to authenticate when trying to lock or unlock a token." + "@guide": { + "description": "Button to open the guide screen." }, - "biometricRequiredTitle": "La biométrie n'est pas configurée", - "@biometricRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + "@handshakeFailed": { + "description": "Error message when the handshake failed." }, - "biometricHint": "Authentification requise", - "@biometricHint": { - "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + "@importedVia": { + "description": "Label for the import method of the token." }, - "biometricNotRecognized": "Pas reconnu. Réessayer.", - "@biometricNotRecognized": { - "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + "@internalServerError": { + "placeholders": { + "code": { + "example": "500" + } + } }, - "biometricSuccess": "Authentification réussie", - "@biometricSuccess": { - "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + "@invalidValue": { + "description": "Error message when the value is not valid for the parameter.", + "placeholders": { + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "deviceCredentialsRequiredTitle": "Les informations d'identification de l'appareil ne sont pas configurées", - "@deviceCredentialsRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + "@invalidValueIn": { + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "deviceCredentialsSetupDescription": "Configurer les informations d'identification de l'appareil dans les paramètres de l'appareil", - "@deviceCredentialsSetupDescription": { - "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + "@isExpotableQuestion": { + "description": "Label for the question if the token is exportable." }, - "signInTitle": "Authentification requise", - "@signInTitle": { - "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + "@isPiTokenQuestion": { + "description": "Label for the question if the token is a privacyIDEA token." }, - "goToSettingsButton": "Aller aux paramètres", - "@goToSettingsButton": { - "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + "@language": { + "description": "Title of language setting group." }, - "goToSettingsDescription": "L'authentification par identifiants ou biométrie n'est pas configurée sur votre appareil. Veuillez le configurer dans les paramètres de l'appareil.", - "@goToSettingsDescription": { - "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." + "@legacySigningErrorMessage": { + "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." }, - "lockOut": "L'authentification biométrique est désactivée. Veuillez verrouiller et déverrouiller votre écran pour l'activer.", - "@lockOut": { - "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + "@legacySigningErrorTitle": { + "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", + "placeholders": { + "tokenLabel": { + "example": "PUSH1234A" + } + } }, - "authNotSupportedTitle": "Informations d'identification de l'appareil ou données biométriques requises", - "@authNotSupportedTitle": { - "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." + "@lightTheme": { + "description": "The light theme." }, - "authNotSupportedBody": "Cette action nécessite que l'appareil soit sécurisé par des identifiants ou des données biométriques.", - "@authNotSupportedBody": { - "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." + "@linkedContainer": { + "description": "Label for the linked container serial number." }, - "lock": "Bloquer", "@lock": { "description": "Description of button that locks a token." }, - "unlock": "Ouvrir", - "@unlock": { - "description": "Description of button that unlocks a token." + "@lockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." }, - "noResultTitle": "Aucun jeton n'est encore stocké.", - "@noResultTitle": { - "description": "No tokens stored yet." + "@logMenu": { + "description": "Button to open the log menu." + }, + "@malformedData": { + "description": "Error message when the data is malformed." + }, + "@missingRequiredParameter": { + "description": "Error message when a required parameter is missing.", + "placeholders": { + "parameter": { + "example": "counter" + } + } + }, + "@missingRequiredParameterIn": { + "description": "Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.", + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + } + } + }, + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, + "@name": { + "description": "Describes the field where the tokens name should be entered." + }, + "@noFbToken": { + "description": "Tells the user that there is no Firebase token available." + }, + "@noNetworkConnection": { + "description": "Tells the user that there is no network connection." }, - "noResultText1": "Appuyez sur le \n", "@noResultText1": { "description": "first noresult text" }, - "noResultText2": "bouton pour commencer!", "@noResultText2": { "description": "second noresult text" }, - "onBoardingTitle1": "{appName}", + "@noResultTitle": { + "description": "No tokens stored yet." + }, + "@notAnInteger": { + "description": "Tells the user that there is no Firebase token available." + }, + "@notAnNumber": { + "description": "Error message when the user entered a value that is not a number." + }, + "@ok": { + "description": "Button to confirm an action." + }, "@onBoardingTitle1": { "placeholders": { "appName": { @@ -383,297 +364,558 @@ } } }, - "onBoardingText1": "Authentification à deux facteurs\nrendue facile", - "onBoardingTitle2": "Sécurité Maximale", - "onBoardingText2": "Stockez les jetons sur votre \nappareil en toute sécurité\nProtégé par votre biométrie", - "onBoardingTitle3": "Rendez-nous visite sur Github", - "onBoardingText3": "Cette application est open source", - "errorLogTitle": "Journal d'erreur", - "logMenu": "Menu du journal", - "showErrorLog": "Afficher", - "clearErrorLog": "Effacer", - "send": "Envoyer", - "sendErrorLogDescription": "Un e-mail pré-rempli est créé.\nIl contient des informations sur l'application, l'erreur et le périphérique.\nVous pouvez modifier l'e-mail avant de l'envoyer.\nVous pouvez voir ici comment nous utilisons les informations:", - "@sendErrorLogDescription": { - "description": "Explanation for the user what he will send." - }, - "showPrivacyPolicy": "Afficher la déclaration de confidentialité", - "errorLogEmpty": "Le journal des erreurs est vide", - "verboseLogging": "Journalisation verbeuse", - "errorLogCleared": "Journal d'erreur nettoyé", - "ok": "Ok", - "errorMailBody": "Le fichier journal des erreurs est joint.\nVous pouvez remplacer ce texte par des informations supplémentaires sur l'erreur.", - "@errorMailBody": { - "description": "Message pour le corps du courriel" - }, - "showDetails": "Afficher les détails", - "open": "Ouvrir", - "sendErrorDialogBody": "Une erreur inattendue est survenue dans l'application. L'information suivante peut être transmise aux développeurs par email afin d'aider à corriger cette erreur dans le futur.", - "@sendErrorDialogBody": { - "description": "Description shown to the user about what info the error report contains." - }, - "noFbToken": "Pas de jeton Firebase", - "firebaseToken": "Jeton Firebase", - "noPublicKey": "Pas de clé publique", - "publicKey": "Clé publique", - "editToken": "Modifier le jeton", - "edit": "Modifier", - "save": "Enregistrer", - "create": "créer", - "validFor": "Valide pour", - "validUntil": "Valide jusqu'à", - "deleteLockedToken": "Veuillez vous authentifier pour supprimer le jeton verrouillé.", - "editLockedToken": "Veuillez vous authentifier pour modifier le jeton verrouillé.", - "expandLockedFolder": "Veuillez vous authentifier pour ouvrir le dossier verrouillé.", - "renameTokenFolder": "Renommer le dossier", - "addANewFolder": "Créer un nouveau dossier", - "folderName": "Nom du dossier", - "retryRollout": "Réessayer le déploiement", - "generatingRSAKeyPair": "Génération de la paire de clés RSA", - "@generatingRSAKeyPair": { - "description": "Message for the rollout process" + "@open": { + "description": "Button to open something." }, - "generatingRSAKeyPairFailed": "La génération de la paire de clés RSA a échoué", - "@generatingRSAKeyPairFailed": { - "description": "Message for the rollout process" + "@originApp": { + "description": "Label for the origin app." }, - "sendingRSAPublicKey": "Envoi de la clé publique RSA", - "@sendingRSAPublicKey": { - "description": "Message for the rollout process" + "@originDetails": { + "description": "Title of the origin details menu." }, - "sendingRSAPublicKeyFailed": "L'envoi de la clé publique RSA a échoué", - "@sendingRSAPublicKeyFailed": { - "description": "Message for the rollout process" + "@otpValueCopiedMessage": { + "description": "Tells the user that the otp value was copied to the clipboard.", + "placeholders": { + "otpValue": { + "example": "055374" + } + }, + "type": "text" }, - "parsingResponse": "Analyse de la réponse", "@parsingResponse": { "description": "Message for the rollout process" }, - "parsingResponseFailed": "L'analyse de la réponse a échoué", "@parsingResponseFailed": { "description": "Message for the rollout process" }, - "rolloutCompleted": "Déploiement terminé", - "@rolloutCompleted": { - "description": "Message for the rollout process" + "@period": { + "description": "Title of the dropdown button where the period of the totp token is selected." }, - "errorRollOutNoConnectionToServer": "El despliegue del token {name} ha fallado, no se ha podido acceder al servidor.", - "@errorRollOutNoConnectionToServer": { - "description": "Message for the rollout process" + "@phonePart": { + "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." }, - "authToAcceptPushRequest": "Veuillez vous authentifier pour accepter la demande de connexion.", - "@authToAcceptPushRequest": { - "description": "authToAcceptPushRequest" + "@pleaseEnterANameForThisToken": { + "description": "Hint telling the user to enter a name for a token." }, - "authToDeclinePushRequest": "Veuillez vous authentifier pour refuser la demande de connexion.", - "@authToDeclinePushRequest": { - "description": "authToDeclinePushRequest" + "@pleaseEnterASecretForThisToken": { + "description": "Hint telling the user to enter a secret for a token." }, - "pushRequestParseError": "La demande push n'a pas pu être traitée.", - "imageUrl": "URL de l'image", - "errorRollOutSSLHandshakeFailed": "Échec de la prise de contact SSL. Le déploiement n'est pas possible.", - "errorWhenPullingChallenges": "Une erreur s'est produite lors de l'interrogation des défis de {name}", - "@errorWhenPullingChallenges": { - "placeholders": { - "name": { - "example": "PUSH1234A" - } - } + "@pollingChallenges": { + "type": "text" }, - "couldNotConnectToServer": "Impossible de se connecter au serveur.", - "errorRollOutNotPossibleAnymore": "Le déploiement de ce jeton n'est plus possible.", - "errorTokenExpired": "Le jeton {name} a expiré.", - "@errorTokenExpired": { + "@pollingFailed": { + "description": "Tells the user that the polling failed." + }, + "@pollingFailedFor": { + "description": "Tells the user that the polling failed.", "placeholders": { - "name": { + "serial": { "example": "PUSH1234A" } } }, - "yes": "Oui", - "no": "Non", - "butDiscardIt": "mais l'écarter", - "declineIt": "refuser", - "requestTriggerdByUserQuestion": "Cette demande a-t-elle été déclenchée par vous ?", - "grantCameraPermissionDialogTitle": "L'autorisation de la caméra n'est pas accordée", - "grantCameraPermissionDialogContent": "Veuillez accorder à la caméra l'autorisation de scanner les codes QR", - "grantCameraPermissionDialogPermanentlyDenied": "L'autorisation de l'appareil photo est refusée de manière permanente. Veuillez accorder l'autorisation à l'appareil photo dans les paramètres de votre téléphone.", - "grantCameraPermissionDialogButton": "Accorder l'autorisation", - "decryptErrorTitle": "Erreur de décryptage", - "decryptErrorContent": "Malheureusement, l'application n'a pas pu décrypter vos jetons. Cela indique que la clé de cryptage est cassée. Vous pouvez réessayer ou supprimer les données de l'application, ce qui supprimera les jetons dans l'application.", - "decryptErrorButtonDelete": "Supprimer", - "decryptErrorButtonSendError": "Erreur d'envoi", - "decryptErrorButtonRetry": "Réessayer", - "decryptErrorDeleteConfirmationContent": "Êtes-vous sûr de vouloir supprimer les données de l'application ?", - "hidePushTokens": "Hide push tokens", - "hidePushTokensDescription": "Masquer les jetons de poussée de la liste des jetons. Cela ne supprimera pas les jetons et ils seront toujours visibles sur un écran séparé", - "settingsGroupGeneral": "Généralités", - "licensesAndVersion": "Licences et version", - "privacyPolicy": "Politique de confidentialité", - "introScanQrCode": "You can scan QR codes to add tokens.\nWe support every common Two-Factor-Authentication token and also the privacyIDEA tokens.", - "introAddTokenManually": "Si vous ne souhaitez pas scanner un code QR, vous pouvez également ajouter des jetons manuellement.", - "introTokenSwipe": "Balayez les tokens vers la gauche pour voir les actions disponibles", - "introEditToken": "Ici, vous pouvez modifier le nom du token et voir quelques détails", - "introLockToken": "Pour améliorer encore la sécurité, vous pouvez verrouiller les tokens. Le token ne peut alors être utilisé qu'après l'authentification.", - "introDragToken": "Réorganisez vos jetons en appuyant dessus pendant quelques secondes, puis en les faisant glisser jusqu'à la position souhaitée", - "introAddFolder": "Vous pouvez créer des dossiers pour organiser vos jetons", - "introPollForChallenges": "Vous pouvez vérifier la présence de nouveaux défis en faisant glisser la liste des jetons vers le bas", - "introHidePushTokens": "Vos jetons sont maintenant cachés, mais vous pouvez toujours les voir sur l'écran des jetons", - "legacySigningErrorTitle": "Une erreur s'est produite lors de l'utilisation du jeton obsolète : {tokenLabel}", - "@legacySigningErrorTitle": { - "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", - "placeholders": { - "tokenLabel": { - "example": "PUSH1234A" - } - } + "@pushToken": { + "description": "Title for the settings block concerning the push tokens." }, - "legacySigningErrorMessage": "Le token a été créé dans une version obsolète de l'application, ce qui peut entraîner des problèmes d'utilisation.\nIl est recommandé de créer un nouveau token push si le problème persiste !", - "@legacySigningErrorMessage": { - "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." + "@rename": { + "description": "Label that describes renaming the token." }, - "selectImportSource": "Sélectionner la source d'importation", - "selectImportType": "Comment voulez-vous importer les jetons ?", - "importTokens": "Importer un jeton", - "importNTokens": "{count, plural, zero{N'importer aucun jeton} one{Importer un jeton} other{Importer {count} tokens}}", - "selectFile": "Sélectionner un fichier", - "decrypt": "Décrypter", - "tokensAreEncrypted": "Les jetons sont cryptés. Veuillez saisir le mot de passe pour les décrypter", - "tokensNotEncrypted": "Les tokens ne sont pas cryptés, et peuvent être importés directement", - "tokensSuccessfullyDecrypted": "Les tokens ont été décryptés avec succès, ils peuvent maintenant être importés.", - "password": "Mot de passe", - "wrongPassword": "Mot de passe incorrect", - "qrScan": "Numériser", - "enterLink": "Saisir le lien", - "invalidBackupFile": "Le fichier sélectionné n'est pas une sauvegarde valide de {appName}", - "invalidQrScan": "Le code QR scanné n'est pas une sauvegarde valide de {appName}", - "invalidQrFile": "Le fichier sélectionné ne contient pas de code QR valide de {appName}", - "invalidLink": "Le lien saisi n'est pas un jeton valide de {appName}, ou il n'est pas pris en charge", - "importFailedToken": "{count, plural, zero{Pas de jeton Échec de l'importation.} one{Échec de l'importation d'un jeton.} other{Échec de l'importation des jetons {count}.}}", - "importExistingToken": "{count, plural, zero{Aucun jeton déjà présent dans l'application n'a été trouvé.} one{Un jeton qui existe déjà dans l'application a été trouvé.} other{Des jetons {count} déjà présents dans l'application ont été trouvés.}}", - "importConflictToken": "{count, plural, zero{Il n'y a pas de conflit avec des tokens déjà existants.} one{Il y a un conflit avec des tokens déjà existants.\nVeuillez choisir celui que vous voulez garder.} other{Il y a un conflit avec des tokens déjà existants.\nVeuillez choisir celui que vous voulez garder.}}", - "importNewToken": "{count, plural, zero{Aucun nouveau jeton n'a été trouvé.} one{Un nouveau token a été trouvé et peut être importé.} other{{count} nouveaux tokens ont été trouvés et peuvent être importés.}}", - "importHintPrivacyIdeaQrScan": "Pour créer des codes QR des jetons, accédez aux paramètres et appuyez sur \"Exporter\". Sélectionnez ensuite \"En tant que code QR\" et tapez sur le jeton à exporter. Cette variante ne convient que pour un transfert direct vers un autre appareil, car le code QR n'est pas crypté.", - "importHintPrivacyIdeaFile": "Pour créer une sauvegarde, allez dans les paramètres et tapez sur \"Exporter\". Choisissez \"En tant que fichier\", sélectionnez les jetons que vous souhaitez exporter. Ensuite, appuyez sur \"Exporter\" et définissez un mot de passe. L'emplacement de stockage est le dossier de téléchargement sur votre appareil.", - "importHint2FAS": "Choisissez votre sauvegarde 2FAS.\nSi vous n'avez pas de sauvegarde, créez-en une dans l'application 2FAS. Nous vous recommandons d'utiliser un mot de passe", - "importHintAegisBackupFile": "Choisissez votre exportation Aegis (.JSON).\nSi vous n'avez pas d'exportation, veuillez en créer une via le menu Paramètres dans l'application Aegis. Il est recommandé d'utiliser un mot de passe", - "importHintAegisQrScan": "Scannez le code QR que vous recevez lorsque vous transférez des entrées depuis Aegis", - "importHintAegisLink": "Saisissez le lien que vous recevez lorsque vous transférez des entrées depuis Aegis", - "importHintGoogleQrScan": "Scannez le code QR que vous recevez lorsque vous exportez vos comptes depuis Google Authenticator", - "importHintGoogleQrFile": "Sélectionnez un fichier image avec le code QR que vous obtenez lorsque vous exportez vos comptes depuis Google Authenticator.\n!! Notez qu'il n'est pas sûr d'enregistrer le code QR sur votre appareil, car les jetons ne sont pas cryptés !!", - "importHintAuthenticatorProFile": "Pour créer une sauvegarde de l'application Authenticator Pro, accédez aux paramètres et appuyez sur \"Sauvegarde automatique\". Sélectionnez un emplacement de stockage et définissez un mot de passe. Puis appuyez sur \"Sauvegarder maintenant\" pour exporter les tokens.", - "importHintFreeOtpPlusQrScan": "Scannez le code QR que vous recevez lorsque vous appuyez sur les trois points dans la tuile du jeton et sélectionnez \"Partager le code QR\".", - "importHintFreeOtpPlusFile": "Pour créer une sauvegarde de l'application FreeOTP+, appuyez sur les trois points dans le coin supérieur droit et sélectionnez \"Exporter\". Vous pouvez choisir entre les formats JSON et URI. Nous recommandons de supprimer la sauvegarde après l'avoir importée, car elle n'est pas cryptée.", - "qrFileDecodeError": "Il n'a pas été possible de décoder le code QR à partir de l'image sélectionnée, veuillez utiliser le scanner de code QR à la place", - "tokenLink": "Lien vers le token", - "feedback": "Retour d'information", - "feedbackTitle": "Vos commentaires sont toujours les bienvenus !", - "feedbackDescription": "Si vous avez des questions, des suggestions ou des problèmes, n'hésitez pas à nous en faire part", - "feedbackHint": "Un e-mail prêt à l'emploi s'ouvre, que vous pouvez nous envoyer. Si vous le souhaitez, des informations sur votre appareil et la version de l'application seront ajoutées. Vous pouvez vérifier et modifier l'e-mail avant de l'envoyer.", - "feedbackPrivacyPolicy1": "En envoyant le retour d'information, vous acceptez notre ", - "feedbackPrivacyPolicy2": "politique de confidentialité", - "@feedbackPrivacyPolicy2": { - "description": "Taping on this should open the privacy policy." + "@renameToken": { + "description": "Title of the dialog where a new name for a token can be entered." + }, + "@renameTokenFolder": { + "description": "Title of the dialog where a new name for a token folder can be entered." }, - "feedbackPrivacyPolicy3": ".", - "addSystemInfo": "Ajouter des informations sur le système", - "feedbackSentTitle": "Retour d'information envoyé", - "feedbackSentDescription": "Merci beaucoup pour votre aide dans l'amélioration de cette application !", - "patchNotesDialogTitle": "Quoi de neuf ?", - "version": "Version", - "noMailAppTitle": "Aucune application de messagerie trouvée", - "noMailAppDescription": "Aucune application de messagerie n'est installée ou initialisée sur cet appareil. Veuillez réessayer lorsque vous serez en mesure d'envoyer un message électronique.", - "authenticationRequest": "Authentification", - "requestInfo": "Envoyé par {issuer} pour votre compte : \"{account}\"", "@requestInfo": { + "description": "Description of the authentication request.", "placeholders": { - "issuer": { - "exemple": "privacyIDEA" - }, "account": { - "exemple": "GitHub" + "example": "GitHub" + }, + "issuer": { + "example": "privacyIDEA" } } }, - "errorUnlinkingPushToken": "Echec du découplage du push token {label}", - "@errorUnlinkingPushToken": { - "description": "Error message when unlinking a push token failed.", + "@requestPushChallengesPeriodically": { + "description": "The description of the polling feature." + }, + "@retry": { + "description": "Label for e.g. a button. Something is tried to be done again." + }, + "@rolloutCompleted": { + "description": "Message for the rollout process" + }, + "@scanQrCode": { + "description": "The button to scan otpauth qr-codes." + }, + "@secretIsRequired": { + "description": "Error message when the secret is missing." + }, + "@secretKey": { + "description": "Describes the field where the tokens secret should be entered." + }, + "@send": { + "description": "Button to send the error log." + }, + "@sendErrorDialogBody": { + "description": "Description shown to the user about what info the error report contains." + }, + "@sendErrorLogDescription": { + "description": "Explanation for the user what he will send." + }, + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." + }, + "@sendingRSAPublicKey": { + "description": "Message for the rollout process" + }, + "@sendingRSAPublicKeyFailed": { + "description": "Message for the rollout process" + }, + "@serverNotReachable": { + "description": "Tells the user that the server could not be reached." + }, + "@settings": { + "description": "Button to open the settings page." + }, + "@showDetails": { + "description": "Button to show details." + }, + "@showErrorLog": { + "description": "Button to show the error log." + }, + "@showPrivacyPolicy": { + "description": "Button to show the privacy policy." + }, + "@signInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "@someTokensDoNotSupportPolling": { + "description": "Tells the user, that the following tokens do not support polling.", + "type": "text" + }, + "@startRollout": { + "description": "Label that tells the user that the token is being rolled out." + }, + "@statusCode": { + "description": "Tells the user the status code of the error.", "placeholders": { - "label": { - "example": "PUSH1234A" + "statusCode": { + "example": "400" } } }, - "pleaseSyncManuallyWhenNetworkIsAvailable": "Veuillez synchroniser manuellement les jetons Push via les paramètres lorsqu'une connexion réseau est disponible", - "pushTokens": "Push Tokens", - "continueButton": "Continue", - "addTokenManually": "Add token manually", - "addFolder": "Ajouter un dossier", - "searchTokens": "Jetons de recherche", - "closeSearchTokens": "Fermer la recherche", - "increaseCounter": "Augmenter le compteur", - "copyOTPToClipboard": "Copier l'OTP dans le presse-papiers", - "licenses": "Licences", - "optionalMessage": "Message optionnel", - "confirmation": "Confirmation", - "askLogSendedDescription": "Avez-vous envoyé le journal et voulez-vous l'effacer maintenant ?", - "algorithmUnsupported": "L'algorithme {algorithm} n'est pas pris en charge", - "@algorithmUnsupported": { + "@sync": { + "description": "Text of button that is used to synchronize push tokens." + }, + "@syncContainerFailed": { + "description": "Error message when synchronizing a container failed." + }, + "@syncFbTokenFailed": { + "description": "Headline for the list of tokens where the synchronization failed." + }, + "@synchronizePushTokens": { + "description": "Title of synchronizing push tokens in settings." + }, + "@synchronizesTokensWithServer": { + "description": "Description of synchronizing push tokens in settings." + }, + "@synchronizingTokens": { + "description": "Title of the push synchronization dialog." + }, + "@systemTheme": { + "description": "The systems theme." + }, + "@theSecretDoesNotFitTheCurrentEncoding": { + "description": "Hint telling the user that the secret does not fit the selected encoding." + }, + "@theme": { + "description": "Title of the setting group where the theme can be selected." + }, + "@themeMode": { + "description": "Title of the setting group where the theme mode can be changed." + }, + "@timeOut": { + "description": "Error message when a request times out." + }, + "@tokenDataParseError": { + "description": "Error message when the token data could not be parsed." + }, + "@tokenDetails": { + "description": "Title of the token details menu." + }, + "@tokenSerial": { + "description": "Label for the token serial number." + }, + "@tokensDoNotSupportSynchronization": { + "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + }, + "@type": { + "description": "Title of the dropdown button where the type of the token is selected." + }, + "@unexpectedError": { + "description": "Title of page report mode." + }, + "@unknown": { + "description": "Tells that something is unknown." + }, + "@unlock": { + "description": "Description of button that unlocks a token." + }, + "@unsupported": { "placeholders": { - "algorithm": { - "example": "MD5" + "name": { + "example": "piauth version" + }, + "value": { + "example": "5" } } }, - "thisAppIsOpenSource": "Cette application est open source\nRendez-nous visite sur GitHub", - "invalidArgument": "{argument} n'est pas une valeur valide pour {type}", - "importExportTokens": "Importer/Exporter les jetons", - "exportNonPrivacyIDEATokens": "Exporter les jetons non privacyIDEA", - "selectTokensToExport": "{count, plural, zero{} one{Sélectionner le jeton à exporter} other{Sélectionner les jetons à exporter}}", - "noTokenToExport": "Pas de jeton disponible pour l'exportation", - "exportAllTokens": "Exporter tous les jetons", - "export": "Exporter", - "exportingTokens": "Exportation des jetons en cours...", - "exportTokens": "Exporter les jetons", - "enterPasswordToEncrypt": "Entrez un mot de passe pour chiffrer les jetons. Ce mot de passe sera requis pour importer les jetons.", - "exportLockedTokenReason": "Veuillez vous authentifier pour exporter les jetons verrouillés.", - "fileSavedToDownloadsFolder": "Fichier enregistré dans le dossier Téléchargements", - "errorSavingFile": "Erreur lors de l'enregistrement du fichier", - "asQrCode": "Sous forme de code QR", - "asFile": "En tant que fichier", - "scanThisQrWithNewDevice": "Scannez ce code QR avec votre nouvel appareil pour importer le jeton.", - "oneMore": "Encore un", - "done": "Terminé", - "confirmPassword": "Confirmer le mot de passe", - "exampleUrl": "Veuillez saisir une URL valide comme : \"https://example.com/\"", - "pushEndpointUrl": "URL de l'endpoint Push", - "mustNotBeEmpty": "{field} ne doit pas être vide", - "@mustNotBeEmpty": { + "@useDeviceLocaleDescription": { + "description": "Description of the switch tile where using the devices language can be enabled." + }, + "@useDeviceLocaleTitle": { + "description": "Title of the switch tile where using the devices language can be enabled." + }, + "@valueNotAllowed": { "placeholders": { - "field": { - "example": "Name" + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "-1" } } }, - "sendPushRequestResponseFailed": "Échec de l'envoi de la réponse.", - "@sendPushRequestResponseFailed": { - "description": "Error message when the response to a push request could not be sent." + "@valueNotAllowedIn": { + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "-1" + } + } }, + "@verboseLogging": { + "description": "Title of the switch tile where verbose logging can be enabled." + }, + "accept": "Accepter", + "addANewFolder": "Créer un nouveau dossier", + "addFolder": "Ajouter un dossier", + "addSystemInfo": "Ajouter des informations sur le système", + "addToken": "Ajouter un jeton", + "addTokenManually": "Add token manually", + "algorithm": "Algorithme", + "algorithmUnsupported": "L'algorithme {algorithm} n'est pas pris en charge", + "allTokensSynchronized": "Tous les jetons ont été synchronisés.", + "asFile": "En tant que fichier", + "asQrCode": "Sous forme de code QR", + "askLogSendedDescription": "Avez-vous envoyé le journal et voulez-vous l'effacer maintenant ?", + "authNotSupportedBody": "Cette action nécessite que l'appareil soit sécurisé par des identifiants ou des données biométriques.", + "authNotSupportedTitle": "Informations d'identification de l'appareil ou données biométriques requises", + "authToAcceptPushRequest": "Veuillez vous authentifier pour accepter la demande de connexion.", + "authToDeclinePushRequest": "Veuillez vous authentifier pour refuser la demande de connexion.", + "authenticateToShowOtp": "Veuillez vous authentifier pour afficher un mot de passe à usage unique.", + "authenticateToUnLockToken": "Veuillez vous authentifier pour modifier l'état de verrouillage du jeton.", + "authenticationRequest": "Authentification", + "biometricHint": "Authentification requise", + "biometricNotRecognized": "Pas reconnu. Réessayer.", + "biometricRequiredTitle": "La biométrie n'est pas configurée", + "biometricSuccess": "Authentification réussie", + "butDiscardIt": "mais l'écarter", + "cancel": "Annuler", + "checkServerCertificate": "Veuillez vérifier le certificat du serveur", + "checkYourNetwork": "Veuillez vérifier votre connexion réseau et réessayer.", + "clearErrorLog": "Effacer", + "closeSearchTokens": "Fermer la recherche", + "confirmDeletion": "Confirmer suppression", + "confirmDeletionOf": "Confirmer la suppression de {name}?", + "confirmFolderDeletionHint": "La suppression d'un dossier n'a aucun effet sur les tokens qui s'y trouvent.\nLes tokens sont déplacés dans la liste principale.", + "confirmPassword": "Confirmer le mot de passe", + "confirmTokenDeletionHint": "Il se peut que vous ne puissiez plus vous connecter si vous supprimez ce token.\nVeuillez vous assurer que vous pouvez vous connecter au compte associé sans ce token.", + "confirmation": "Confirmation", + "connectionFailed": "La connexion a échoué.", + "container": "Conteneur", + "continueButton": "Continue", + "copyOTPToClipboard": "Copier l'OTP dans le presse-papiers", + "couldNotConnectToServer": "Impossible de se connecter au serveur.", + "couldNotSignMessage": "Impossible de signer le message.", + "counter": "Compteur", + "create": "créer", + "createdAt": "Créé à", + "creator": "Créateur", + "decline": "Refuser", + "declineIt": "refuser", + "decrypt": "Décrypter", + "decryptErrorButtonDelete": "Supprimer", + "decryptErrorButtonRetry": "Réessayer", + "decryptErrorButtonSendError": "Erreur d'envoi", + "decryptErrorContent": "Malheureusement, l'application n'a pas pu décrypter vos jetons. Cela indique que la clé de cryptage est cassée. Vous pouvez réessayer ou supprimer les données de l'application, ce qui supprimera les jetons dans l'application.", + "decryptErrorDeleteConfirmationContent": "Êtes-vous sûr de vouloir supprimer les données de l'application ?", + "decryptErrorTitle": "Erreur de décryptage", + "delete": "Supprimer", + "deleteLockedToken": "Veuillez vous authentifier pour supprimer le jeton verrouillé.", + "deviceCredentialsRequiredTitle": "Les informations d'identification de l'appareil ne sont pas configurées", + "deviceCredentialsSetupDescription": "Configurer les informations d'identification de l'appareil dans les paramètres de l'appareil", + "digits": "Chiffres", + "dismiss": "Fermer", + "done": "Terminé", + "edit": "Modifier", + "editLockedToken": "Veuillez vous authentifier pour modifier le jeton verrouillé.", + "editToken": "Modifier le jeton", + "enablePolling": "Activer l'interrogation du serveur.", + "encoding": "Encodage", + "enterDetailsForToken": "Saisissez les détails du jeton", + "enterLink": "Saisir le lien", + "enterPasswordToEncrypt": "Entrez un mot de passe pour chiffrer les jetons. Ce mot de passe sera requis pour importer les jetons.", + "errorLogCleared": "Journal d'erreur nettoyé", + "errorLogEmpty": "Le journal des erreurs est vide", + "errorLogTitle": "Journal d'erreur", + "errorMailBody": "Le fichier journal des erreurs est joint.\nVous pouvez remplacer ce texte par des informations supplémentaires sur l'erreur.", + "errorRollOutFailed": "Le déploiement du jeton {name} a échoué.", + "errorRollOutNoConnectionToServer": "El despliegue del token {name} ha fallado, no se ha podido acceder al servidor.", + "errorRollOutNotPossibleAnymore": "Le déploiement de ce jeton n'est plus possible.", + "errorRollOutSSLHandshakeFailed": "Échec de la prise de contact SSL. Le déploiement n'est pas possible.", + "errorRollOutUnknownError": "Le déploiement a échoué suite à une erreur inconnue: {e}", + "errorSavingFile": "Erreur lors de l'enregistrement du fichier", + "errorSynchronizationNoNetworkConnection": "La synchronization a échoué car le serveur est injoignable.", + "errorTokenExpired": "Le jeton {name} a expiré.", + "errorUnlinkingPushToken": "Echec du découplage du push token {label}", + "errorWhenPullingChallenges": "Une erreur s'est produite lors de l'interrogation des défis de {name}", + "exampleUrl": "Veuillez saisir une URL valide comme : \"https://example.com/\"", + "expandLockedFolder": "Veuillez vous authentifier pour ouvrir le dossier verrouillé.", + "export": "Exporter", + "exportAllTokens": "Exporter tous les jetons", + "exportLockedTokenReason": "Veuillez vous authentifier pour exporter les jetons verrouillés.", + "exportNonPrivacyIDEATokens": "Exporter les jetons non privacyIDEA", + "exportTokens": "Exporter les jetons", + "exportingTokens": "Exportation des jetons en cours...", + "failedToLoad": "Échec du chargement :", + "feedback": "Retour d'information", + "feedbackDescription": "Si vous avez des questions, des suggestions ou des problèmes, n'hésitez pas à nous en faire part", + "feedbackHint": "Un e-mail prêt à l'emploi s'ouvre, que vous pouvez nous envoyer. Si vous le souhaitez, des informations sur votre appareil et la version de l'application seront ajoutées. Vous pouvez vérifier et modifier l'e-mail avant de l'envoyer.", + "feedbackPrivacyPolicy1": "En envoyant le retour d'information, vous acceptez notre ", + "feedbackPrivacyPolicy2": "politique de confidentialité", + "feedbackPrivacyPolicy3": ".", + "feedbackSentDescription": "Merci beaucoup pour votre aide dans l'amélioration de cette application !", + "feedbackSentTitle": "Retour d'information envoyé", + "feedbackTitle": "Vos commentaires sont toujours les bienvenus !", + "fileSavedToDownloadsFolder": "Fichier enregistré dans le dossier Téléchargements", + "findingQrCodeInImage": "Recherche d'un code QR dans une image...", + "firebaseToken": "Jeton Firebase", + "folderName": "Nom du dossier", + "generatingPhonePart": "Générer la part du téléphone", + "generatingRSAKeyPair": "Génération de la paire de clés RSA", + "generatingRSAKeyPairFailed": "La génération de la paire de clés RSA a échoué", + "goToSettingsButton": "Aller aux paramètres", + "goToSettingsDescription": "L'authentification par identifiants ou biométrie n'est pas configurée sur votre appareil. Veuillez le configurer dans les paramètres de l'appareil.", + "grantCameraPermissionDialogButton": "Accorder l'autorisation", + "grantCameraPermissionDialogContent": "Veuillez accorder à la caméra l'autorisation de scanner les codes QR", + "grantCameraPermissionDialogPermanentlyDenied": "L'autorisation de l'appareil photo est refusée de manière permanente. Veuillez accorder l'autorisation à l'appareil photo dans les paramètres de votre téléphone.", + "grantCameraPermissionDialogTitle": "L'autorisation de la caméra n'est pas accordée", + "handshakeFailed": "Échec du handshake", + "hidePushTokens": "Hide push tokens", + "hidePushTokensDescription": "Masquer les jetons de poussée de la liste des jetons. Cela ne supprimera pas les jetons et ils seront toujours visibles sur un écran séparé", + "imageUrl": "URL de l'image", + "importConflictToken": "{count, plural, zero{Il n'y a pas de conflit avec des tokens déjà existants.} one{Il y a un conflit avec des tokens déjà existants.\nVeuillez choisir celui que vous voulez garder.} other{Il y a un conflit avec des tokens déjà existants.\nVeuillez choisir celui que vous voulez garder.}}", + "importExistingToken": "{count, plural, zero{Aucun jeton déjà présent dans l'application n'a été trouvé.} one{Un jeton qui existe déjà dans l'application a été trouvé.} other{Des jetons {count} déjà présents dans l'application ont été trouvés.}}", + "importExportTokens": "Importer/Exporter les jetons", + "importFailedToken": "{count, plural, zero{Pas de jeton Échec de l'importation.} one{Échec de l'importation d'un jeton.} other{Échec de l'importation des jetons {count}.}}", + "importHint2FAS": "Choisissez votre sauvegarde 2FAS.\nSi vous n'avez pas de sauvegarde, créez-en une dans l'application 2FAS. Nous vous recommandons d'utiliser un mot de passe", + "importHintAegisBackupFile": "Choisissez votre exportation Aegis (.JSON).\nSi vous n'avez pas d'exportation, veuillez en créer une via le menu Paramètres dans l'application Aegis. Il est recommandé d'utiliser un mot de passe", + "importHintAegisLink": "Saisissez le lien que vous recevez lorsque vous transférez des entrées depuis Aegis", + "importHintAegisQrScan": "Scannez le code QR que vous recevez lorsque vous transférez des entrées depuis Aegis", + "importHintAuthenticatorProFile": "Pour créer une sauvegarde de l'application Authenticator Pro, accédez aux paramètres et appuyez sur \"Sauvegarde automatique\". Sélectionnez un emplacement de stockage et définissez un mot de passe. Puis appuyez sur \"Sauvegarder maintenant\" pour exporter les tokens.", + "importHintFreeOtpPlusFile": "Pour créer une sauvegarde de l'application FreeOTP+, appuyez sur les trois points dans le coin supérieur droit et sélectionnez \"Exporter\". Vous pouvez choisir entre les formats JSON et URI. Nous recommandons de supprimer la sauvegarde après l'avoir importée, car elle n'est pas cryptée.", + "importHintFreeOtpPlusQrScan": "Scannez le code QR que vous recevez lorsque vous appuyez sur les trois points dans la tuile du jeton et sélectionnez \"Partager le code QR\".", + "importHintGoogleQrFile": "Sélectionnez un fichier image avec le code QR que vous obtenez lorsque vous exportez vos comptes depuis Google Authenticator.\n!! Notez qu'il n'est pas sûr d'enregistrer le code QR sur votre appareil, car les jetons ne sont pas cryptés !!", + "importHintGoogleQrScan": "Scannez le code QR que vous recevez lorsque vous exportez vos comptes depuis Google Authenticator", + "importHintPrivacyIdeaFile": "Pour créer une sauvegarde, allez dans les paramètres et tapez sur \"Exporter\". Choisissez \"En tant que fichier\", sélectionnez les jetons que vous souhaitez exporter. Ensuite, appuyez sur \"Exporter\" et définissez un mot de passe. L'emplacement de stockage est le dossier de téléchargement sur votre appareil.", + "importHintPrivacyIdeaQrScan": "Pour créer des codes QR des jetons, accédez aux paramètres et appuyez sur \"Exporter\". Sélectionnez ensuite \"En tant que code QR\" et tapez sur le jeton à exporter. Cette variante ne convient que pour un transfert direct vers un autre appareil, car le code QR n'est pas crypté.", + "importNTokens": "{count, plural, zero{N'importer aucun jeton} one{Importer un jeton} other{Importer {count} tokens}}", + "importNewToken": "{count, plural, zero{Aucun nouveau jeton n'a été trouvé.} one{Un nouveau token a été trouvé et peut être importé.} other{{count} nouveaux tokens ont été trouvés et peuvent être importés.}}", + "importTokens": "Importer un jeton", + "importedVia": "Importé via", + "increaseCounter": "Augmenter le compteur", + "internalServerError": "Erreur interne du serveur ({code})", + "introAddFolder": "Vous pouvez créer des dossiers pour organiser vos jetons", + "introAddTokenManually": "Si vous ne souhaitez pas scanner un code QR, vous pouvez également ajouter des jetons manuellement.", + "introDragToken": "Réorganisez vos jetons en appuyant dessus pendant quelques secondes, puis en les faisant glisser jusqu'à la position souhaitée", + "introEditToken": "Ici, vous pouvez modifier le nom du token et voir quelques détails", + "introHidePushTokens": "Vos jetons sont maintenant cachés, mais vous pouvez toujours les voir sur l'écran des jetons", + "introLockToken": "Pour améliorer encore la sécurité, vous pouvez verrouiller les tokens. Le token ne peut alors être utilisé qu'après l'authentification.", + "introPollForChallenges": "Vous pouvez vérifier la présence de nouveaux défis en faisant glisser la liste des jetons vers le bas", + "introScanQrCode": "You can scan QR codes to add tokens.\nWe support every common Two-Factor-Authentication token and also the privacyIDEA tokens.", + "introTokenSwipe": "Balayez les tokens vers la gauche pour voir les actions disponibles", + "invalidBackupFile": "Le fichier sélectionné n'est pas une sauvegarde valide de {appName}", + "invalidLink": "Le lien saisi n'est pas un jeton valide de {appName}, ou il n'est pas pris en charge", + "invalidQrFile": "Le fichier sélectionné ne contient pas de code QR valide de {appName}", + "invalidQrScan": "Le code QR scanné n'est pas une sauvegarde valide de {appName}", + "invalidValue": "untranslated", + "invalidValueIn": "untranslated", + "isExpotableQuestion": "Est exportable ?", + "isPiTokenQuestion": "C'est un jeton privacyIDEA ?", + "language": "Langue", + "legacySigningErrorMessage": "Le token a été créé dans une version obsolète de l'application, ce qui peut entraîner des problèmes d'utilisation.\nIl est recommandé de créer un nouveau token push si le problème persiste !", + "legacySigningErrorTitle": "Une erreur s'est produite lors de l'utilisation du jeton obsolète : {tokenLabel}", + "licenses": "Licences", + "licensesAndVersion": "Licences et version", + "linkedContainer": "Conteneur lié", + "lock": "Bloquer", + "lockOut": "L'authentification biométrique est désactivée. Veuillez verrouiller et déverrouiller votre écran pour l'activer.", + "logMenu": "Menu du journal", + "malformedData": "Les données ne sont pas valides", + "markQrCode": "Marquer le code QR", + "missingRequiredParameter": "La valeur du paramètre [{parameter}] est requise, mais elle est manquante.", + "missingRequiredParameterIn": "La valeur du paramètre [{parameter}] est requise, mais elle est absente de \"{map}\".", + "mustNotBeEmpty": "{field} ne doit pas être vide", + "name": "Nom", + "no": "Non", + "noFbToken": "Pas de jeton Firebase", + "noMailAppDescription": "Aucune application de messagerie n'est installée ou initialisée sur cet appareil. Veuillez réessayer lorsque vous serez en mesure d'envoyer un message électronique.", + "noMailAppTitle": "Aucune application de messagerie trouvée", + "noNetworkConnection": "Pas de connexion au réseau.", + "noPublicKey": "Pas de clé publique", + "noResultText1": "Appuyez sur le \n", + "noResultText2": "bouton pour commencer!", + "noResultTitle": "Aucun jeton n'est encore stocké.", + "noTokenToExport": "Pas de jeton disponible pour l'exportation", + "notAnInteger": "La valeur n'est pas un nombre entier.", + "notAnNumber": "La valeur n'est pas un nombre.", + "ok": "Ok", + "oneMore": "Encore un", + "open": "Ouvrir", + "optionalMessage": "Message optionnel", + "originApp": "Application d'origine", + "originDetails": "Informations sur l'origine", + "otpValueCopiedMessage": "Le mot de passe \"{otpValue}\" a été copié dans le presse-papier.", + "parsingResponse": "Analyse de la réponse", + "parsingResponseFailed": "L'analyse de la réponse a échoué", + "password": "Mot de passe", "passwordCannotBeEmpty": "Le mot de passe ne peut pas être vide", - "passwordMustBeAtLeast8Characters": "Le mot de passe doit contenir au moins 8 caractères", "passwordCannotContainWhitespace": "Le mot de passe ne peut pas contenir d'espaces", + "passwordMustBeAtLeast8Characters": "Le mot de passe doit contenir au moins 8 caractères", "passwordMustContainLowercaseLetter": "Le mot de passe doit contenir une lettre minuscule", - "passwordMustContainUppercaseLetter": "Le mot de passe doit contenir une lettre majuscule", "passwordMustContainNumber": "Le mot de passe doit contenir un chiffre", "passwordMustContainSpecialCharacter": "Le mot de passe doit contenir un caractère spécial", + "passwordMustContainUppercaseLetter": "Le mot de passe doit contenir une lettre majuscule", "passwordsDoNotMatch": "Les mots de passe ne correspondent pas", - "selectTokensToExportHelpTitle": "Votre jeton ne figure pas dans la liste ?", - "selectTokensToExportHelpContent": "Si un jeton n'est pas répertorié, il n'est pas garanti qu'il ne s'agisse pas d'un jeton privacyIDEA.\nActuellement, seuls les jetons ajoutés manuellement et importés sont exportables.", - "findingQrCodeInImage": "Recherche d'un code QR dans une image...", - "qrNotFound": "Aucun code QR trouvé !", + "patchNotesBugFixes": "Bug fixes", + "patchNotesDialogTitle": "Quoi de neuf ?", + "patchNotesImprovements": "Améliorations", + "patchNotesNewFeatures": "Nouvelles caractéristiques", + "patchNotesV4_3_0NewFeatures1": "Ajout de la prise en charge de l'importation de jetons depuis Google, Aegis et 2FAS Authenticator. D'autres sources d'importation seront ajoutées à l'avenir.", + "patchNotesV4_3_0NewFeatures2": "Ajout d'une option de retour d'information dans les paramètres", + "patchNotesV4_3_0NewFeatures3": "Les jetons de poussée peuvent maintenant être cachés de la liste des jetons", + "patchNotesV4_3_0NewFeatures4": "Des introductions ont été ajoutées pour aider les nouveaux utilisateurs à démarrer", + "patchNotesV4_3_0NewFeatures5": "Vous pouvez désormais rechercher des jetons en appuyant sur la loupe dans le coin supérieur droit", + "patchNotesV4_3_0NewFeatures6": "Ajout du jeton HomeWidget pour Android 12 et les versions ultérieures", + "patchNotesV4_3_1BugFix1": "Un problème a été corrigé où la valeur OTP n'était pas affichée après l'authentification sur certains appareils.", + "patchNotesV4_3_1Improvement1": "Le scanner de codes QR a été amélioré.", + "patchNotesV4_4_0Improvement1": "D'autres sources d'importation ont été ajoutées.", + "patchNotesV4_4_0Improvement2": "La reconnaissance des codes QR à partir des fichiers image a été améliorée.", + "patchNotesV4_4_0NewFeatures1": "Il est désormais possible d'exporter des jetons dont on peut s'assurer qu'il ne s'agit pas de jetons privacyIDEA. Actuellement, il n'est pas possible d'exclure que des jetons ajoutés via le lecteur de code QR proviennent de privacyIDEA. La différenciation sera améliorée dans les versions futures.", + "patchNotesV4_4_0NewFeatures2": "Ajout du support pour privacyIDEA's \"require presence\".", + "period": "Période", + "phonePart": "Part du téléphone:", + "pleaseEnterANameForThisToken": "Veuillez saisir un nom pour ce jeton.", + "pleaseEnterASecretForThisToken": "Veuillez saisir un secret pour ce jeton.", + "pleaseSyncManuallyWhenNetworkIsAvailable": "Veuillez synchroniser manuellement les jetons Push via les paramètres lorsqu'une connexion réseau est disponible", + "pollingChallenges": "Vérification de nouveaux challenges", + "pollingFailed": "Échec de la requête.", + "pollingFailedFor": "Echec de la requête pour {serial}.", + "privacyPolicy": "Politique de confidentialité", + "publicKey": "Clé publique", + "pushEndpointUrl": "URL de l'endpoint Push", + "pushRequestParseError": "La demande push n'a pas pu être traitée.", + "pushToken": "Jeton de type Push", + "pushTokens": "Push Tokens", + "qrFileDecodeError": "Il n'a pas été possible de décoder le code QR à partir de l'image sélectionnée, veuillez utiliser le scanner de code QR à la place", "qrInFileNotFound": "Aucun code QR n'a été trouvé dans l'image sélectionnée.", "qrInFileNotFound2": "Vous pouvez me montrer où se trouve le code QR.", "qrInFileNotFound3": "Je pense que je trouverai le code s'il se trouve au milieu de la zone marquée.", - "markQrCode": "Marquer le code QR", - "malformedData": "Les données ne sont pas valides" + "qrNotFound": "Aucun code QR trouvé !", + "qrScan": "Numériser", + "rename": "Renommer", + "renameToken": "Renommer jeton", + "renameTokenFolder": "Renommer le dossier", + "requestInfo": "Envoyé par {issuer} pour votre compte : \"{account}\"", + "requestPushChallengesPeriodically": "Demander des challenges push depuis le serveur périodiquement. Activer cette fonction si les challenges push ne sont pas reçus normalement.", + "requestTriggerdByUserQuestion": "Cette demande a-t-elle été déclenchée par vous ?", + "retry": "Réessayer", + "retryRollout": "Réessayer le déploiement", + "rolloutCompleted": "Déploiement terminé", + "save": "Enregistrer", + "scanQrCode": "Numériser QR-Code", + "scanThisQrWithNewDevice": "Scannez ce code QR avec votre nouvel appareil pour importer le jeton.", + "searchTokens": "Jetons de recherche", + "secretIsRequired": "Secret est nécessaire", + "secretKey": "Clé secrète", + "selectFile": "Sélectionner un fichier", + "selectImportSource": "Sélectionner la source d'importation", + "selectImportType": "Comment voulez-vous importer les jetons ?", + "selectTokensToExport": "{count, plural, zero{} one{Sélectionner le jeton à exporter} other{Sélectionner les jetons à exporter}}", + "selectTokensToExportHelpContent": "Si un jeton n'est pas répertorié, il n'est pas garanti qu'il ne s'agisse pas d'un jeton privacyIDEA.\nActuellement, seuls les jetons ajoutés manuellement et importés sont exportables.", + "selectTokensToExportHelpTitle": "Votre jeton ne figure pas dans la liste ?", + "send": "Envoyer", + "sendErrorDialogBody": "Une erreur inattendue est survenue dans l'application. L'information suivante peut être transmise aux développeurs par email afin d'aider à corriger cette erreur dans le futur.", + "sendErrorLogDescription": "Un e-mail pré-rempli est créé.\nIl contient des informations sur l'application, l'erreur et le périphérique.\nVous pouvez modifier l'e-mail avant de l'envoyer.\nVous pouvez voir ici comment nous utilisons les informations:", + "sendPushRequestResponseFailed": "Échec de l'envoi de la réponse.", + "sendingRSAPublicKey": "Envoi de la clé publique RSA", + "sendingRSAPublicKeyFailed": "L'envoi de la clé publique RSA a échoué", + "serverNotReachable": "Le serveur n'a pas pu être atteint.", + "settings": "Paramètres", + "settingsGroupGeneral": "Généralités", + "showDetails": "Afficher les détails", + "showErrorLog": "Afficher", + "showPrivacyPolicy": "Afficher la déclaration de confidentialité", + "signInTitle": "Authentification requise", + "someTokensDoNotSupportPolling": "Certains jetons sont obsolètes et ne supportent pas l'interrogation due serveur.", + "startRollout": "Démarrer le déploiement", + "statusCode": "Code d'état : {statusCode}", + "sync": "Synchroniser", + "syncContainerFailed": "Échec de la synchronisation des conteneurs", + "syncFbTokenFailed": "La synchronisation a échoué pour ces jetons, veuillez reéssayer:", + "synchronizePushTokens": "Synchoniser les jetons Push", + "synchronizesTokensWithServer": "Synchroniser les jetons Push avec le serveur privacyIDEA.", + "synchronizingTokens": "Synchroniser les jetons.", + "theSecretDoesNotFitTheCurrentEncoding": "Le secret n'est pas compatible avec \nl'encodage actuel.", + "themeMode": "Thème", + "thisAppIsOpenSource": "Cette application est open source\nRendez-nous visite sur GitHub", + "timeOut": "Délai d'attente", + "tokenDataParseError": "Les données du jeton n'ont pas pu être analysées", + "tokenDetails": "Détails du jeton", + "tokenLink": "Lien vers le token", + "tokenSerial": "Token serial", + "tokensAreEncrypted": "Les jetons sont cryptés. Veuillez saisir le mot de passe pour les décrypter", + "tokensDoNotSupportSynchronization": "Ces jetons ne supportent pas la synchronisation et doivent être de nouveau générés:", + "tokensNotEncrypted": "Les tokens ne sont pas cryptés, et peuvent être importés directement", + "tokensSuccessfullyDecrypted": "Les tokens ont été décryptés avec succès, ils peuvent maintenant être importés.", + "type": "Type", + "unexpectedError": "Une erreur inattendue s'est produite.", + "unknown": "Inconnu", + "unlock": "Ouvrir", + "unsupported": "Le {name} [{value}] n'est pas pris en charge par cette version de l'application.", + "useDeviceLocaleDescription": "Utilisez la langue de l'appareil si elle est prise en charge, sinon l'anglais par défaut.", + "useDeviceLocaleTitle": "Utiliser la langue de l'appareil", + "validFor": "Valide pour", + "validUntil": "Valide jusqu'à", + "valueNotAllowed": "untranslated", + "valueNotAllowedIn": "untranslated", + "verboseLogging": "Journalisation verbeuse", + "version": "Version", + "wrongPassword": "Mot de passe incorrect", + "yes": "Oui" } \ No newline at end of file diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 4dc5c6e31..4043b214e 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -1,374 +1,362 @@ { - "@@last_modified": "2023-08-07", - "patchNotesNewFeatures": "Nieuwe functies", - "patchNotesImprovements": "Verbeteringen", - "patchNotesBugFixes": "Bugfixes", - "patchNotesV4_4_0NewFeatures1": "Het is nu mogelijk om tokens te exporteren waarvan kan worden gegarandeerd dat het geen privacyIDEA tokens zijn. Op dit moment kan niet worden uitgesloten dat tokens die zijn toegevoegd via de QR-code scanner afkomstig zijn van privacyIDEA. De differentiatie zal in toekomstige versies worden verbeterd.", - "patchNotesV4_4_0NewFeatures2": "Ondersteuning toegevoegd voor privacyIDEA's \"require presence\".", - "patchNotesV4_4_0Improvement1": "Er zijn meer invoerbronnen toegevoegd.", - "patchNotesV4_4_0Improvement2": "De herkenning van QR-codes uit afbeeldingsbestanden is verbeterd.", - "patchNotesV4_3_1BugFix1": "Een probleem is opgelost waarbij de otp-waarde niet werd weergegeven na authenticatie op sommige apparaten.", - "patchNotesV4_3_1Improvement1": "De QR-code scanner is verbeterd.", - "patchNotesV4_3_0NewFeatures1": "Ondersteuning toegevoegd voor het importeren van tokens van Google, Aegis en 2FAS Authenticator. Meer importbronnen zullen in de toekomst worden toegevoegd.", - "patchNotesV4_3_0NewFeatures2": "Feedbackoptie toegevoegd aan de instellingen.", - "patchNotesV4_3_0NewFeatures3": "Push tokens kunnen nu verborgen worden in de tokenlijst.", - "patchNotesV4_3_0NewFeatures4": "Introducties zijn toegevoegd om nieuwe gebruikers op weg te helpen.", - "patchNotesV4_3_0NewFeatures5": "Je kunt nu naar tokens zoeken door op het vergrootglas in de rechterbovenhoek te tikken.", - "patchNotesV4_3_0NewFeatures6": "HomeWidget Token toegevoegd voor Android 12 en hoger.", - "guide": "Handleiding", - "@guide": { - "description": "Button to open the guide screen." - }, - "accept": "Accepteren", + "@@last_modified": "2024-09-20", + "@@locale": "nl", "@accept": { "description": "Label for e.g. a button. Something gets accepted by the user." }, - "decline": "Weigeren", - "@decline": { - "description": "Label for e.g. a button. Something gets declined by the user." - }, - "name": "Naam", - "@name": { - "description": "Describes the field where the tokens name should be entered." - }, - "secretKey": "Geheime sleutel", - "@secretKey": { - "description": "Describes the field where the tokens secret should be entered." - }, - "encoding": "Codering", - "@encoding": { - "description": "Title of the dropdown button where the encoding is selected." + "@addToken": { + "description": "The button to open the screen to add tokens by hand." }, - "algorithm": "Algoritme", "@algorithm": { "description": "Title of the dropdown button where the encoding is selected." }, - "digits": "Cijfers", - "@digits": { - "description": "Title of the dropdown button where the number of digits for the opt value is selected." + "@algorithmUnsupported": { + "placeholders": { + "algorithm": { + "example": "MD5" + } + } }, - "type": "Type", - "@type": { - "description": "Title of the dropdown button where the type of the token is selected." + "@allTokensSynchronized": { + "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "period": "Duur", - "@period": { - "description": "Title of the dropdown button where the period of the totp token is selected." + "@authNotSupportedBody": { + "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." }, - "rename": "Wijzigen", - "@rename": { - "description": "Label that describes renaming the token." + "@authNotSupportedTitle": { + "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." }, - "cancel": "Annuleren", - "@cancel": { - "description": "Button to cancel an action." + "@authToAcceptPushRequest": { + "description": "Message to accepting a push request for authentication." }, - "delete": "Verwijderen", - "@delete": { - "description": "Label that describes deleting the token." + "@authToDeclinePushRequest": { + "description": "Message for declining a push request for authentication." }, - "dismiss": "Sluiten", - "@dismiss": { - "description": "Text of a button that closes a dialog." + "@authenticateToShowOtp": { + "description": "Reason to authenticate when trying to view a one time password." }, - "addToken": "Token toevoegen", - "@addToken": { - "description": "The button to open the screen to add tokens by hand." + "@authenticateToUnLockToken": { + "description": "Reason to authenticate when trying to lock or unlock a token." }, - "scanQrCode": "Scan QR-Code", - "@scanQrCode": { - "description": "The button to scan otpauth qr-codes." + "@biometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." }, - "enterDetailsForToken": "Voer informatie over token in", - "@enterDetailsForToken": { - "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." + "@biometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterANameForThisToken": "Voer de naam in voor deze token.", - "@pleaseEnterANameForThisToken": { - "description": "Hint telling the user to enter a name for a token." + "@biometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterASecretForThisToken": "Voer de geheime sleutel in voor deze token.", - "@pleaseEnterASecretForThisToken": { - "description": "Hint telling the user to enter a secret for a token." + "@biometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." }, - "theSecretDoesNotFitTheCurrentEncoding": "De geheime sleutel past niet bij de huidige codering", - "@theSecretDoesNotFitTheCurrentEncoding": { - "description": "Hint telling the user that the secret does not fit the selected encoding." + "@cancel": { + "description": "Button to cancel an action." }, - "renameToken": "Hernoem token", - "@renameToken": { - "description": "Title of the dialog where a new name for a token can be entered." + "@checkServerCertificate": { + "description": "Error message when the server certificate should be checked." + }, + "@checkYourNetwork": { + "description": "Tells the user to check the network connection." + }, + "@clearErrorLog": { + "description": "Button to clear the error log." }, - "confirmDeletion": "Bevestig verwijderen", "@confirmDeletion": { "description": "Title of the dialog where a token can be deleted." }, - "confirmDeletionOf": "Weet u zeker dat u {name} wilt verwijderen?", "@confirmDeletionOf": { "description": "Asks for confirmation on deleting a token.", "placeholders": { "name": { "example": "PUSH1234" } - } - }, - "confirmTokenDeletionHint": "U kunt mogelijk niet meer inloggen als u dit token verwijdert. Controleer of u zonder dit token kunt inloggen op het gekoppelde account.", - "@confirmTokenDeletionHint": { - "description": "Gives the user a hint about the consequences of deleting a token." + }, + "type": "text" }, - "confirmFolderDeletionHint": "Het verwijderen van een map heeft geen effect op de tokens in de map. De tokens worden verplaatst naar de hoofdlijst.", "@confirmFolderDeletionHint": { "description": "Gives the user a hint about the consequences of deleting a folder." }, - "generatingPhonePart": "Genereren telefoon gedeelte", - "@generatingPhonePart": { - "description": "Title of a dialog telling the user that the phone part gets generated right now." + "@confirmTokenDeletionHint": { + "description": "Gives the user a hint about the consequences of deleting a token." }, - "phonePart": "Telefoon gedeelte:", - "@phonePart": { - "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." + "@connectionFailed": { + "description": "Tells the user that the connection failed." }, - "otpValueCopiedMessage": "Wachtwoord \"{otpValue}\" gekopieerd naar het klembord.", - "@otpValueCopiedMessage": { - "description": "Tells the user that the otp value was copied to the clipboard.", - "placeholders": { - "otpValue": { - "example": "055374" - } - } + "@container": { + "description": "Title for the container view." }, - "settings": "Instellingen", - "@settings": { - "description": "Button to open the settings page." + "@couldNotSignMessage": { + "description": "Tells the user that the message could not be signed." }, - "pushToken": "Push Token", - "@pushToken": { - "description": "Title for the settings block concerning the push tokens." + "@counter": { + "description": "Describes the field where the tokens counter should be entered." }, - "theme": "Thema", - "@theme": { - "description": "Title of the setting group where the theme can be selected." + "@createdAt": { + "description": "Label for the creation date of the token." }, - "lightTheme": "Licht", - "@lightTheme": { - "description": "The light theme." + "@creator": { + "description": "Label for the creator of the token." }, - "darkTheme": "Donker", "@darkTheme": { "description": "The dark theme." }, - "systemTheme": "Gebruik thema van het apparaat", - "@systemTheme": { - "description": "The systems theme." + "@decline": { + "description": "Label for e.g. a button. Something gets declined by the user." }, - "someTokensDoNotSupportPolling": "Sommige tokens zijn verouderd en ondersteunen geen actief zoeken", - "@someTokensDoNotSupportPolling": { - "description": "Tells the user, that the following tokens do not support polling.", - "type": "text", - "placeholders": {} + "@delete": { + "description": "Label that describes deleting the token." }, - "enablePolling": "Zoeken aanzetten", - "@enablePolling": { - "description": "Name of the setting switch that enables polling." + "@deviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." }, - "requestPushChallengesPeriodically": "Activeer het zoeken naar berichten. Gebruik deze optie wanneer de push berichten niet worden ontvangen.", - "@requestPushChallengesPeriodically": { - "description": "The description of the polling feature." + "@deviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." }, - "synchronizePushTokens": "Synchroniseer push tokens", - "@synchronizePushTokens": { - "description": "Title of synchronizing push tokens in settings." + "@digits": { + "description": "Title of the dropdown button where the number of digits for the opt value is selected." }, - "synchronizesTokensWithServer": "Synchroniseert tokens met de privacyIDEA server.", - "@synchronizesTokensWithServer": { - "description": "Description of synchronizing push tokens in settings." + "@dismiss": { + "description": "Text of a button that closes a dialog." }, - "sync": "Synchroniseer", - "@sync": { - "description": "Text of button that is used to synchronize push tokens." + "@enablePolling": { + "description": "Name of the setting switch that enables polling." }, - "synchronizingTokens": "Tokens synchroniseren.", - "@synchronizingTokens": { - "description": "Title of the push synchronization dialog." + "@encoding": { + "description": "Title of the dropdown button where the encoding is selected." }, - "allTokensSynchronized": "Alle tokens zijn gesynchroniseerd.", - "@allTokensSynchronized": { - "description": "Content of the push synchronization dialog. Signaling the user that everything worked." + "@enterDetailsForToken": { + "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." }, - "syncFbTokenFailed": "Synchroniseren mislukt voor de volgende tokens, probeer het opnieuw:", - "@syncFbTokenFailed": { - "description": "Headline for the list of tokens where the synchronization failed." + "@errorLogCleared": { + "description": "Message that tells the user that the error log was cleared." }, - "tokensDoNotSupportSynchronization": "Voor de volgende tokens wordt synchroniseren niet ondersteunt, ze moeten opnieuw worden aangeleverd:", - "@tokensDoNotSupportSynchronization": { - "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + "@errorLogEmpty": { + "description": "Message that tells the user that the error log is empty." + }, + "@errorLogTitle": { + "description": "Title of the error log screen." + }, + "@errorMailBody": { + "description": "Message for email body" }, - "errorRollOutFailed": "Uitrollen van token {name} mislukt.", "@errorRollOutFailed": { "description": "Tells the user that the token could not be rolled out, because a network error occurred.", "placeholders": { "name": { "example": "PUSH1234A" } - } + }, + "type": "text" }, - "statusCode": "Statuscode: {statusCode}", - "@statusCode": { - "description": "Tells the user the status code of the error.", + "@errorRollOutNoConnectionToServer": { + "description": "Message for the rollout process", "placeholders": { - "statusCode": { - "example": "400" + "name": { + "example": "PUSH1234A" } } }, - "errorSynchronizationNoNetworkConnection": "Token synchroniseren mislukt, privacyIDEA server kan niet worden bereikt.", - "@errorSynchronizationNoNetworkConnection": { - "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." + "@errorRollOutSSLHandshakeFailed": { + "description": "Tells the user that the roll-out failed because the SSL handshake failed." }, - "errorRollOutUnknownError": "Een onbekende fout heeft plaats gevonden. Uitrollen is niet mogelijk: {e}", "@errorRollOutUnknownError": { "description": "Tells the user that the roll-out failed because of an unknown error.", - "type": "text", "placeholders": { "e": { "example": "IllegalArgumentException on Line 5 ..." } - } - }, - "startRollout": "Start uitrollen", - "@startRollout": { - "description": "Label that tells the user that the token is being rolled out." - }, - "pollingChallenges": "Zoeken naar nieuwe aanvragen", - "@pollingChallenges": { + }, "type": "text" }, - "unexpectedError": "Er is een onverwachte fout opgetreden.", - "@unexpectedError": { - "description": "Title of page report mode." - }, - "pollingFailed": "Vraag mislukt.", - "@pollingFailed": { - "description": "Tells the user that the polling failed." + "@errorSynchronizationNoNetworkConnection": { + "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." }, - "pollingFailedFor": "Query voor {serial} mislukt.", - "@pollingFailedFor": { - "description": "Tells the user that the polling failed.", + "@errorTokenExpired": { "placeholders": { - "serial": { + "name": { "example": "PUSH1234A" } } }, - "noNetworkConnection": "Geen netwerkverbinding.", - "@noNetworkConnection": { - "description": "Tells the user that there is no network connection." + "@errorUnlinkingPushToken": { + "description": "Error message when unlinking a push token failed.", + "placeholders": { + "label": { + "example": "PUSH1234A" + } + } }, - "connectionFailed": "Verbinding mislukt.", - "@connectionFailed": { - "description": "Tells the user that the connection failed." + "@errorWhenPullingChallenges": { + "description": "errorWhenPullingChallenges", + "placeholders": { + "name": { + "example": "PUSH1234A" + } + } }, - "checkYourNetwork": "Controleer je netwerkverbinding en probeer het opnieuw.", - "@checkYourNetwork": { - "description": "Tells the user to check the network connection." + "@failedToLoad": { + "placeholders": { + "name": { + "example": "token data" + } + } }, - "serverNotReachable": "De server kon niet worden bereikt.", - "@serverNotReachable": { - "description": "Tells the user that the server could not be reached." + "@feedbackPrivacyPolicy2": { + "description": "Hierop tikken moet het privacybeleid openen." }, - "couldNotSignMessage": "Bericht niet kunnen ondertekenen.", - "@couldNotSignMessage": { - "description": "Tells the user that the message could not be signed." + "@generatingPhonePart": { + "description": "Title of a dialog telling the user that the phone part gets generated right now." }, - "useDeviceLocaleTitle": "Gebruik de taal van het apparaat", - "@useDeviceLocaleTitle": { - "description": "Title of the switch tile where using the devices language can be enabled." + "@generatingRSAKeyPair": { + "description": "Message for the rollout process" }, - "useDeviceLocaleDescription": "Gebruik de taal van het apparaat wanneer het wordt ondersteund, val anders terug op Engels.", - "@useDeviceLocaleDescription": { - "description": "Description of the switch tile where using the devices language can be enabled." + "@generatingRSAKeyPairFailed": { + "description": "Message for the rollout process" }, - "language": "Taal", - "@language": { - "description": "Title of language setting group." + "@goToSettingsButton": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." }, - "authenticateToShowOtp": "Authenticeer om het eenmalige wachtwoord te tonen.", - "@authenticateToShowOtp": { - "description": "Reason to authenticate when trying to view a one time password." + "@goToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." }, - "authenticateToUnLockToken": "Authenticeer om de vergrendeling van de token te wijzigen.", - "@authenticateToUnLockToken": { - "description": "Reason to authenticate when trying to lock or unlock a token." + "@guide": { + "description": "Button to open the guide screen." }, - "biometricRequiredTitle": "Biometrie is niet ingesteld", - "@biometricRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + "@handshakeFailed": { + "description": "Error message when the handshake failed." }, - "biometricHint": "Authenticatie vereist", - "@biometricHint": { - "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + "@importedVia": { + "description": "Label for the import method of the token." }, - "biometricNotRecognized": "Niet herkend. Probeer opnieuw.", - "@biometricNotRecognized": { - "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + "@internalServerError": { + "placeholders": { + "code": { + "example": "500" + } + } }, - "biometricSuccess": "Authenticatie geslaagd", - "@biometricSuccess": { - "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + "@invalidValue": { + "description": "Error message when the value is not valid for the parameter.", + "placeholders": { + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "deviceCredentialsRequiredTitle": "Inloggevens van het apparaat zijn niet ingesteld", - "@deviceCredentialsRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + "@invalidValueIn": { + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "deviceCredentialsSetupDescription": "Stel de inloggegevens in, bij de instellingen van het apparaat", - "@deviceCredentialsSetupDescription": { - "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + "@isExpotableQuestion": { + "description": "Label for the question if the token is exportable." }, - "signInTitle": "Authenticatie vereist", - "@signInTitle": { - "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + "@isPiTokenQuestion": { + "description": "Label for the question if the token is a privacyIDEA token." }, - "goToSettingsButton": "Ga naar instellingen", - "@goToSettingsButton": { - "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + "@language": { + "description": "Title of language setting group." }, - "goToSettingsDescription": "Authenticatie via inloggegevens of biometrie is niet ingesteld. Stel het in bij de instellingen van het apparaat.", - "@goToSettingsDescription": { - "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." + "@legacySigningErrorMessage": { + "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." }, - "lockOut": "Biometrische authenticatie staat uit. Vergrendel en ontgrendel het scherm om het aan te zetten.", - "@lockOut": { - "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + "@legacySigningErrorTitle": { + "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", + "placeholders": { + "tokenLabel": { + "example": "PUSH1234A" + } + } }, - "authNotSupportedTitle": "Apparaat inloggevens of biometrie is vereist", - "@authNotSupportedTitle": { - "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." + "@lightTheme": { + "description": "The light theme." }, - "authNotSupportedBody": "Deze actie vereist dat het apparaat is beveiligd met inlogggevens of biometrie.", - "@authNotSupportedBody": { - "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." + "@linkedContainer": { + "description": "Label for the linked container serial number." }, - "lock": "Vergrendel", "@lock": { "description": "Description of button that locks a token." }, - "unlock": "Ontgrendel", - "@unlock": { - "description": "Description of button that unlocks a token." + "@lockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." }, - "noResultTitle": "Nog geen token opgeslagen.", - "@noResultTitle": { - "description": "No tokens installed yet." + "@logMenu": { + "description": "Button to open the log menu." + }, + "@malformedData": { + "description": "Error message when the data is malformed." + }, + "@missingRequiredParameter": { + "description": "Error message when a required parameter is missing.", + "placeholders": { + "parameter": { + "example": "counter" + } + } + }, + "@missingRequiredParameterIn": { + "description": "Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.", + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + } + } + }, + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, + "@name": { + "description": "Describes the field where the tokens name should be entered." + }, + "@noFbToken": { + "description": "Tells the user that there is no Firebase token available." + }, + "@noNetworkConnection": { + "description": "Tells the user that there is no network connection." }, - "noResultText1": "Tik op ", "@noResultText1": { "description": "first noresult text" }, - "noResultText2": " de knop om te beginnen!", "@noResultText2": { "description": "second noresult text" }, - "onBoardingTitle1": "{appName}", + "@noResultTitle": { + "description": "No tokens installed yet." + }, + "@notAnInteger": { + "description": "Tells the user that there is no Firebase token available." + }, + "@notAnNumber": { + "description": "Error message when the user entered a value that is not a number." + }, + "@ok": { + "description": "Button to confirm an action." + }, "@onBoardingTitle1": { "placeholders": { "appName": { @@ -376,300 +364,557 @@ } } }, - "onBoardingText1": "Twee-factoren authenticatie\nmakkelijk gemaakt", - "onBoardingTitle2": "Maximale Beveiliging", - "onBoardingText2": "Bewaar tokens op uw apparaat\nbeveiligd door uw biometrische gegevens", - "onBoardingTitle3": "Bezoek ons op Github", - "onBoardingText3": "Deze app is open source", - "errorLogTitle": "Foutenlogboek", - "logMenu": "Log menu", - "showErrorLog": "Weergeven", - "clearErrorLog": "Verwijderen", - "send": "verzenden", - "sendErrorLogDescription": "Er wordt een kant-en-klare e-mail gemaakt die informatie bevat over de app, de fout en het apparaat.\nJe kunt de e-mail bewerken voordat je hem verstuurt.\nJe kunt hier zien hoe we de informatie gebruiken:", - "@sendErrorLogDescription": { - "description": "Explanation for the user what he will send." - }, - "showPrivacyPolicy": "Privacybeleid tonen", - "errorLogEmpty": "Het foutenlogboek is leeg.", - "verboseLogging": "Verbose loggen", - "errorLogCleared": "Foutenlogboek gewist.", - "ok": "Ok", - "errorMailBody": "Het foutlogbestand is bijgevoegd.\nU kunt deze tekst vervangen door aanvullende informatie over de fout.", - "@errorMailBody": { - "description": "Message for email body" - }, - "showDetails": "Details tonen", - "open": "Openen", - "sendErrorDialogBody": "Een onverwachte fout heeft plaatsgevonden in de applicatie. De onderstaande informatie kan worden verstuurd naar de ontwikkelaars via e-mail om het probleem in de toekomst te voorkomen.", - "@sendErrorDialogBody": { - "description": "Description shown to the user about what info the error report contains." - }, - "noFbToken": "Geen Firebase Token beschikbaar", - "firebaseToken": "Firebase Token", - "noPublicKey": "Geen openbare sleutel beschikbaar", - "publicKey": "Openbare sleutel", - "editToken": "Token bewerken", - "edit": "Bewerken", - "save": "Opslaan", - "create": "Creëer", - "validFor": "Geldig voor", - "validUntil": "Geldig tot", - "deleteLockedToken": "Verifieer om het vergrendelde token te verwijderen.", - "editLockedToken": "Verifieer om het vergrendelde token te bewerken.", - "expandLockedFolder": "Verifieer om de vergrendelde map te openen.", - "renameTokenFolder": "Map hernoemen", - "addANewFolder": "Nieuwe map maken", - "folderName": "Mapnaam", - "retryRollout": "Opnieuw uitrollen", - "generatingRSAKeyPair": "Genereren RSA sleutelpaar", - "@generatingRSAKeyPair": { - "description": "Message for the rollout process" + "@open": { + "description": "Button to open something." }, - "generatingRSAKeyPairFailed": "Genereren RSA sleutelpaar mislukt", - "@generatingRSAKeyPairFailed": { - "description": "Message for the rollout process" + "@originApp": { + "description": "Label for the origin app." }, - "sendingRSAPublicKey": "Versturen van de openbare RSA sleutel", - "@sendingRSAPublicKey": { - "description": "Message for the rollout process" + "@originDetails": { + "description": "Title of the origin details menu." }, - "sendingRSAPublicKeyFailed": "Versturen van de openbare RSA sleutel mislukt", - "@sendingRSAPublicKeyFailed": { - "description": "Message for the rollout process" + "@otpValueCopiedMessage": { + "description": "Tells the user that the otp value was copied to the clipboard.", + "placeholders": { + "otpValue": { + "example": "055374" + } + }, + "type": "text" }, - "parsingResponse": "Antwoord analyseren", "@parsingResponse": { "description": "Message for the rollout process" }, - "parsingResponseFailed": "Antwoord analyseren mislukt", "@parsingResponseFailed": { "description": "Message for the rollout process" }, - "rolloutCompleted": "Uitrollen voltooid", - "@rolloutCompleted": { - "description": "Message for the rollout process" + "@period": { + "description": "Title of the dropdown button where the period of the totp token is selected." }, - "errorRollOutNoConnectionToServer": "Uitrollen mislukt. Geen verbinding met de server.", - "@errorRollOutNoConnectionToServer": { - "description": "Message for the rollout process" + "@phonePart": { + "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." }, - "authToAcceptPushRequest": "Authenticeer om de push aanvraag te accepteren.", - "@authToAcceptPushRequest": { - "description": "Message for the rollout process" + "@pleaseEnterANameForThisToken": { + "description": "Hint telling the user to enter a name for a token." }, - "authToDeclinePushRequest": "Authenticeer om de push aanvraag te weigeren.", - "@authToDeclinePushRequest": { - "description": "Message for the rollout process" + "@pleaseEnterASecretForThisToken": { + "description": "Hint telling the user to enter a secret for a token." }, - "pushRequestParseError": "Het pushverzoek kon niet worden verwerkt.", - "imageUrl": "Afbeeldings-URL", - "errorRollOutSSLHandshakeFailed": "SSL-handdruk mislukt. Uitrollen niet mogelijk.", - "@errorRollOutSSLHandshakeFailed": { - "description": "Message for the rollout process" + "@pollingChallenges": { + "type": "text" }, - "errorWhenPullingChallenges": "Er is een fout opgetreden bij het zoeken naar uitdagingen van {name}", - "@errorWhenPullingChallenges": { + "@pollingFailed": { + "description": "Tells the user that the polling failed." + }, + "@pollingFailedFor": { + "description": "Tells the user that the polling failed.", "placeholders": { - "name": { + "serial": { "example": "PUSH1234A" } } }, - "couldNotConnectToServer": "Kan geen verbinding maken met de server.", - "errorRollOutNotPossibleAnymore": "Het uitrollen van dit token is niet meer mogelijk.", - "errorTokenExpired": "Het token {name} is verlopen.", - "@errorTokenExpired": { - "placeholders": { - "name": { - "example": "PUSH1234A" + "@pushToken": { + "description": "Title for the settings block concerning the push tokens." + }, + "@rename": { + "description": "Label that describes renaming the token." + }, + "@renameToken": { + "description": "Title of the dialog where a new name for a token can be entered." + }, + "@renameTokenFolder": { + "description": "Title of the dialog where a new name for a token folder can be entered." + }, + "@requestInfo": { + "description": "Description of the authentication request.", + "placeholders": { + "account": { + "example": "GitHub" + }, + "issuer": { + "example": "privacyIDEA" } } }, - "yes": "Ja", - "no": "Nee", - "butDiscardIt": "maar verwijder", - "declineIt": "weigeren", - "requestTriggerdByUserQuestion": "Is dit verzoek door jou gedaan?", - "grantCameraPermissionDialogTitle": "Cameratoestemming is niet verleend", - "grantCameraPermissionDialogContent": "Geef de camera toestemming om QR-codes te scannen.", - "grantCameraPermissionDialogPermanentlyDenied": "Cameratoestemming is permanent geweigerd. Geef de camera toestemming in de instellingen van uw telefoon.", - "grantCameraPermissionDialogButton": "Toestemming verlenen", - "decryptErrorTitle": "Fout bij decoderen", - "decryptErrorContent": "Helaas heeft de app je tokens niet kunnen decoderen. Dit geeft aan dat de coderingssleutel is verbroken. U kunt het opnieuw proberen of de app-gegevens verwijderen, waardoor de tokens in de app worden verwijderd.", - "decryptErrorButtonDelete": "Verwijderen", - "decryptErrorButtonSendError": "Fout verzenden", - "decryptErrorButtonRetry": "Opnieuw proberen", - "decryptErrorDeleteConfirmationContent": "Weet je zeker dat je de app-gegevens wilt verwijderen?", - "hidePushTokens": "Verberg push tokens", - "hidePushTokensDescription": "Verberg push tokens uit de token lijst. Hierdoor worden de tokens niet verwijderd en blijven ze zichtbaar op een apart scherm.", - "settingsGroupGeneral": "Algemene informatie", - "licensesAndVersion": "Licenties en versie", - "privacyPolicy": "Privacybeleid", - "introScanQrCode": "Je kunt QR-codes scannen om tokens toe te voegen.We ondersteunen alle gangbare Two-Factor-Authenticatie tokens en ook de privacyIDEA tokens.", - "introAddTokenManually": "Als je geen QR-code wilt scannen, kun je tokens ook handmatig toevoegen.", - "introTokenSwipe": "Veeg tokens naar links om beschikbare acties te zien.", - "introEditToken": "Hier kun je de naam van het token bewerken en enkele details bekijken.", - "introLockToken": "Om de beveiliging nog meer te verbeteren, kun je tokens vergrendelen.¨Dan kan het token alleen gebruikt worden na authenticatie.", - "introDragToken": "Reorganiseer je tokens door er een paar seconden op te drukken en het dan naar de gewenste positie te slepen.", - "introAddFolder": "Je kunt mappen maken om je tokens te organiseren.", - "introPollForChallenges": "Je kunt controleren of er nieuwe uitdagingen zijn door de lijst met tokens naar beneden te slepen.", - "introHidePushTokens": "Je push tokens zijn nu verborgen, maar je kunt ze nog steeds zien op het push token scherm.", - "legacySigningErrorTitle": "Er is een fout opgetreden bij het gebruik van het verouderde token: {tokenLabel}", - "@legacySigningErrorTitle": { - "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", + "@requestPushChallengesPeriodically": { + "description": "The description of the polling feature." + }, + "@retry": { + "description": "Label for e.g. a button. Something is tried to be done again." + }, + "@rolloutCompleted": { + "description": "Message for the rollout process" + }, + "@scanQrCode": { + "description": "The button to scan otpauth qr-codes." + }, + "@secretIsRequired": { + "description": "Error message when the secret is missing." + }, + "@secretKey": { + "description": "Describes the field where the tokens secret should be entered." + }, + "@send": { + "description": "Button to send the error log." + }, + "@sendErrorDialogBody": { + "description": "Description shown to the user about what info the error report contains." + }, + "@sendErrorLogDescription": { + "description": "Explanation for the user what he will send." + }, + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." + }, + "@sendingRSAPublicKey": { + "description": "Message for the rollout process" + }, + "@sendingRSAPublicKeyFailed": { + "description": "Message for the rollout process" + }, + "@serverNotReachable": { + "description": "Tells the user that the server could not be reached." + }, + "@settings": { + "description": "Button to open the settings page." + }, + "@showDetails": { + "description": "Button to show details." + }, + "@showErrorLog": { + "description": "Button to show the error log." + }, + "@showPrivacyPolicy": { + "description": "Button to show the privacy policy." + }, + "@signInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "@someTokensDoNotSupportPolling": { + "description": "Tells the user, that the following tokens do not support polling.", + "type": "text" + }, + "@startRollout": { + "description": "Label that tells the user that the token is being rolled out." + }, + "@statusCode": { + "description": "Tells the user the status code of the error.", "placeholders": { - "tokenLabel": { - "example": "PUSH1234A" + "statusCode": { + "example": "400" } } }, - "legacySigningErrorMessage": "Het token is aangemaakt in een verouderde versie van de app, wat kan leiden tot problemen bij het gebruik ervan.\nHet wordt aanbevolen om een nieuw push token aan te maken als het probleem zich blijft voordoen!", - "@legacySigningErrorMessage": { - "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." + "@sync": { + "description": "Text of button that is used to synchronize push tokens." }, - "selectImportSource": "Selecteer importbron", - "selectImportType": "Hoe wilt u de tokens importeren?", - "importTokens": "Importer un jeton", - "importNTokens": "{count, plural, zero{Geen tokens importeren} one{Eén token importeren} other{Importeer {count} tokens}}", - "selectFile": "Selecteer bestand", - "decrypt": "Decoderen", - "tokensAreEncrypted": "De tokens zijn gecodeerd. Voer het wachtwoord in om ze te decoderen.", - "tokensNotEncrypted": "De tokens zijn niet versleuteld en kunnen direct worden geïmporteerd.", - "tokensSuccessfullyDecrypted": "De tokens zijn succesvol gedecodeerd en kunnen nu worden geïmporteerd.", - "password": "Wachtwoord", - "wrongPassword": "Onjuist wachtwoord", - "qrScan": "Scan", - "enterLink": "Link invoeren", - "invalidBackupFile": "Het geselecteerde bestand is geen geldige backup van {appName}.", - "invalidQrScan": "De gescande QR code is geen geldige backup van {appName}.", - "invalidQrFile": "Het geselecteerde bestand bevat geen geldige QR code van {appName}.", - "invalidLink": "De ingevoerde link is geen geldig token van {appName}, of wordt niet ondersteund.", - "importFailedToken": "{count, plural, zero{Geen token Niet geïmporteerd.} one{Kan geen token importeren.} other{Kan {count} tokens niet importeren.}}", - "importExistingToken": "{count, plural, zero{Er is geen token gevonden dat al in de toepassing aanwezig is.} one{Er is een token gevonden dat al bestaat in de applicatie.} other{{count} tokens gevonden die al in de applicatie staan.}}", - "importConflictToken": "{count, plural, zero{Er is geen conflict met tokens die al bestaan.} one{Er is een conflict met tokens die al bestaan.Selecteer welke u wilt behouden.} other{Er is een conflict met tokens die al bestaan.Selecteer welke u wilt behouden.}}", - "importNewToken": "{count, plural, zero{Er is geen nieuw token gevonden.} one{Er is een nieuw token gevonden dat geïmporteerd kan worden.} other{Er zijn {count} nieuwe tokens gevonden die geïmporteerd kunnen worden.}}", - "importHintPrivacyIdeaQrScan": "Om QR-codes van de tokens te maken, navigeer je naar de instellingen en tik je op \"Exporteren\". Selecteer vervolgens \"Als QR-code\" en tik op het muntje dat geëxporteerd moet worden. Deze variant is alleen geschikt voor directe overdracht naar een ander apparaat, omdat de QR-code niet versleuteld is.", - "importHintPrivacyIdeaFile": "Om een back-up te maken, ga je naar de instellingen en tik je op \"Exporteren\". Selecteer \"Als bestand\" en selecteer de tokens die je wilt exporteren. Tik dan op \"Exporteren\" en stel een wachtwoord in. De opslaglocatie is de downloadmap op je apparaat.", - "importHint2FAS": "Selecteer uw 2FAS-back-up. Als u geen back-up hebt, maak er dan een aan in de 2FAS-app. Wij raden u aan een wachtwoord te gebruiken.", - "importHintAegisBackupFile": "Selecteer uw Aegis-export (.JSON).Als u geen export hebt, maak er dan een aan via het instellingenmenu in de Aegis-app. Het gebruik van een wachtwoord wordt aanbevolen.", - "importHintAegisQrScan": "Scan de QR-code die u ontvangt bij het overbrengen van items uit Aegis.", - "importHintAegisLink": "Voer de link in die u ontvangt wanneer u vermeldingen van Aegis overdraagt.", - "importHintGoogleQrScan": "Scan de QR-code die u ontvangt wanneer u uw accounts exporteert vanuit Google Authenticator.", - "importHintGoogleQrFile": "Selecteer een afbeeldingsbestand met de QR-code die u ontvangt wanneer u uw accounts exporteert vanuit Google Authenticator.\n!! Let op: het is niet veilig om de QR-code op je apparaat op te slaan, omdat de tokens niet versleuteld zijn !!", - "importHintAuthenticatorProFile": "Om een back-up te maken van de Authenticator Pro app, navigeer je naar de instellingen en tik je op \"Auto back-up\". Selecteer een opslaglocatie en stel een wachtwoord in. Druk vervolgens op \"Nu back-uppen\" om de tokens te exporteren.", - "importHintFreeOtpPlusQrScan": "Scan de QR-code die u ontvangt wanneer u op de drie stippen in de tegel van de token drukt en selecteer \"QR-code delen\".", - "importHintFreeOtpPlusFile": "Om een back-up van de FreeOTP+ app te maken, tikt u op de drie puntjes in de rechterbovenhoek en selecteert u \"Exporteren\". U kunt kiezen tussen JSON en URI formaat. We raden u aan de back-up te verwijderen na het importeren, omdat deze niet versleuteld is.", - "qrFileDecodeError": "Het was niet mogelijk om de QR code te decoderen van de geselecteerde afbeelding, gebruik in plaats daarvan de QR code scanner.", - "tokenLink": "tokenlink", - "feedback": "Feedback", - "feedbackTitle": "Uw feedback is altijd welkom!", - "feedbackDescription": "Als je vragen, suggesties of problemen hebt, laat het ons dan weten.", - "feedbackHint": "Er wordt een kant-en-klare e-mail geopend die je naar ons kunt sturen. Indien gewenst wordt informatie over je apparaat en de versie van de applicatie toegevoegd. U kunt de e-mail controleren en bewerken voordat u deze verzendt.", - "feedbackPrivacyPolicy1": "Door feedback te sturen ga je akkoord met ons ", - "feedbackPrivacyPolicy2": "privacybeleid", - "@feedbackPrivacyPolicy2": { - "description": "Hierop tikken moet het privacybeleid openen." + "@syncContainerFailed": { + "description": "Error message when synchronizing a container failed." }, - "feedbackPrivacyPolicy3": ".", - "addSystemInfo": "Systeeminformatie toevoegen", - "feedbackSentTitle": "Feedback verzonden", - "feedbackSentDescription": "Hartelijk dank voor je hulp om deze applicatie beter te maken!", - "patchNotesDialogTitle": "Wat is er nieuw?", - "version": "Versie", - "noMailAppTitle": "Geen mail app gevonden", - "noMailAppDescription": "Er is geen e-mail app geïnstalleerd of geïnitialiseerd op dit apparaat, probeer het opnieuw wanneer u in staat bent om een e-mailbericht te verzenden.", - "authenticationRequest": "Verificatieverzoek", - "requestInfo": "Verzonden door {issuer} voor uw account: \"{account}\"", - "@requestInfo": { + "@syncFbTokenFailed": { + "description": "Headline for the list of tokens where the synchronization failed." + }, + "@synchronizePushTokens": { + "description": "Title of synchronizing push tokens in settings." + }, + "@synchronizesTokensWithServer": { + "description": "Description of synchronizing push tokens in settings." + }, + "@synchronizingTokens": { + "description": "Title of the push synchronization dialog." + }, + "@systemTheme": { + "description": "The systems theme." + }, + "@theSecretDoesNotFitTheCurrentEncoding": { + "description": "Hint telling the user that the secret does not fit the selected encoding." + }, + "@theme": { + "description": "Title of the setting group where the theme can be selected." + }, + "@themeMode": { + "description": "Title of the setting group where the theme mode can be changed." + }, + "@timeOut": { + "description": "Error message when a request times out." + }, + "@tokenDataParseError": { + "description": "Error message when the token data could not be parsed." + }, + "@tokenDetails": { + "description": "Title of the token details menu." + }, + "@tokenSerial": { + "description": "Label for the token serial number." + }, + "@tokensDoNotSupportSynchronization": { + "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + }, + "@type": { + "description": "Title of the dropdown button where the type of the token is selected." + }, + "@unexpectedError": { + "description": "Title of page report mode." + }, + "@unknown": { + "description": "Tells that something is unknown." + }, + "@unlock": { + "description": "Description of button that unlocks a token." + }, + "@unsupported": { "placeholders": { - "issuer": { - "example": "privacyIDEA" + "name": { + "example": "piauth version" }, - "account": { - "example": "GitHub" + "value": { + "example": "5" } } }, - "errorUnlinkingPushToken": "Het is niet gelukt om het push token {label} te ontkoppelen.", - "@errorUnlinkingPushToken": { - "description": "Error message when unlinking a push token failed.", + "@useDeviceLocaleDescription": { + "description": "Description of the switch tile where using the devices language can be enabled." + }, + "@useDeviceLocaleTitle": { + "description": "Title of the switch tile where using the devices language can be enabled." + }, + "@valueNotAllowed": { "placeholders": { - "label": { - "example": "PUSH1234A" + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "-1" } } }, - "pleaseSyncManuallyWhenNetworkIsAvailable": "Synchroniseer de push tokens handmatig via de instellingen als er een netwerkverbinding beschikbaar is.", - "pushTokens": "Push Tokens", - "continueButton": "Ga verder", - "addTokenManually": "Voeg token handmatig toe", - "addFolder": "Map toevoegen", - "searchTokens": "Zoek tokens", - "closeSearchTokens": "Zoekopdracht sluiten", - "increaseCounter": "Verhoog teller", - "copyOTPToClipboard": "Kopieer OTP naar klembord", - "licenses": "Licenties", - "optionalMessage": "Optioneel bericht", - "confirmation": "Bevestiging", - "askLogSendedDescription": "Heb je het logboek verzonden en wil je het nu wissen?", - "algorithmUnsupported": "Het algoritme {algorithm} wordt niet ondersteund", - "@algorithmUnsupported": { + "@valueNotAllowedIn": { "placeholders": { - "algorithm": { - "example": "MD5" + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "-1" } } }, - "thisAppIsOpenSource": "Deze app is open source\nBezoek ons op GitHub", - "invalidArgument": "{argument} is geen geldige waarde voor {type}", - "importExportTokens": "Tokens importeren/exporteren", - "exportNonPrivacyIDEATokens": "Niet-privacyIDEA tokens exporteren", - "selectTokensToExport": "{count, plural, zero{} one{Selecteer token om te exporteren} other{Selecteer tokens om te exporteren}}", - "noTokenToExport": "Geen token beschikbaar voor export", - "exportAllTokens": "Alle tokens exporteren", - "export": "Exporteren", - "exportingTokens": "Tokens exporteren...", - "exportTokens": "Tokens exporteren", - "enterPasswordToEncrypt": "Voer een wachtwoord in om de tokens te versleutelen. Dit wachtwoord is vereist om de tokens te importeren.", - "exportLockedTokenReason": "Authenticeer om vergrendelde tokens te exporteren.", - "fileSavedToDownloadsFolder": "Bestand opgeslagen in de map Downloads", - "errorSavingFile": "Fout bij het opslaan van het bestand", - "asQrCode": "Als QR-code", - "asFile": "Als bestand", - "scanThisQrWithNewDevice": "Scan deze QR-code met uw nieuwe apparaat om de token te importeren.", - "oneMore": "Nog een", - "done": "Klaar", + "@verboseLogging": { + "description": "Title of the switch tile where verbose logging can be enabled." + }, + "accept": "Accepteren", + "addANewFolder": "Nieuwe map maken", + "addFolder": "Map toevoegen", + "addSystemInfo": "Systeeminformatie toevoegen", + "addToken": "Token toevoegen", + "addTokenManually": "Voeg token handmatig toe", + "algorithm": "Algoritme", + "algorithmUnsupported": "Het algoritme {algorithm} wordt niet ondersteund", + "allTokensSynchronized": "Alle tokens zijn gesynchroniseerd.", + "asFile": "Als bestand", + "asQrCode": "Als QR-code", + "askLogSendedDescription": "Heb je het logboek verzonden en wil je het nu wissen?", + "authNotSupportedBody": "Deze actie vereist dat het apparaat is beveiligd met inlogggevens of biometrie.", + "authNotSupportedTitle": "Apparaat inloggevens of biometrie is vereist", + "authToAcceptPushRequest": "Authenticeer om de push aanvraag te accepteren.", + "authToDeclinePushRequest": "Authenticeer om de push aanvraag te weigeren.", + "authenticateToShowOtp": "Authenticeer om het eenmalige wachtwoord te tonen.", + "authenticateToUnLockToken": "Authenticeer om de vergrendeling van de token te wijzigen.", + "authenticationRequest": "Verificatieverzoek", + "biometricHint": "Authenticatie vereist", + "biometricNotRecognized": "Niet herkend. Probeer opnieuw.", + "biometricRequiredTitle": "Biometrie is niet ingesteld", + "biometricSuccess": "Authenticatie geslaagd", + "butDiscardIt": "maar verwijder", + "cancel": "Annuleren", + "checkServerCertificate": "Controleer het servercertificaat", + "checkYourNetwork": "Controleer je netwerkverbinding en probeer het opnieuw.", + "clearErrorLog": "Verwijderen", + "closeSearchTokens": "Zoekopdracht sluiten", + "confirmDeletion": "Bevestig verwijderen", + "confirmDeletionOf": "Weet u zeker dat u {name} wilt verwijderen?", + "confirmFolderDeletionHint": "Het verwijderen van een map heeft geen effect op de tokens in de map. De tokens worden verplaatst naar de hoofdlijst.", "confirmPassword": "Wachtwoord bevestigen", + "confirmTokenDeletionHint": "U kunt mogelijk niet meer inloggen als u dit token verwijdert. Controleer of u zonder dit token kunt inloggen op het gekoppelde account.", + "confirmation": "Bevestiging", + "connectionFailed": "Verbinding mislukt.", + "container": "Container", + "continueButton": "Ga verder", + "copyOTPToClipboard": "Kopieer OTP naar klembord", + "couldNotConnectToServer": "Kan geen verbinding maken met de server.", + "couldNotSignMessage": "Bericht niet kunnen ondertekenen.", + "counter": "Tegen", + "create": "Creëer", + "createdAt": "Gemaakt op", + "creator": "Schepper", + "decline": "Weigeren", + "declineIt": "weigeren", + "decrypt": "Decoderen", + "decryptErrorButtonDelete": "Verwijderen", + "decryptErrorButtonRetry": "Opnieuw proberen", + "decryptErrorButtonSendError": "Fout verzenden", + "decryptErrorContent": "Helaas heeft de app je tokens niet kunnen decoderen. Dit geeft aan dat de coderingssleutel is verbroken. U kunt het opnieuw proberen of de app-gegevens verwijderen, waardoor de tokens in de app worden verwijderd.", + "decryptErrorDeleteConfirmationContent": "Weet je zeker dat je de app-gegevens wilt verwijderen?", + "decryptErrorTitle": "Fout bij decoderen", + "delete": "Verwijderen", + "deleteLockedToken": "Verifieer om het vergrendelde token te verwijderen.", + "deviceCredentialsRequiredTitle": "Inloggevens van het apparaat zijn niet ingesteld", + "deviceCredentialsSetupDescription": "Stel de inloggegevens in, bij de instellingen van het apparaat", + "digits": "Cijfers", + "dismiss": "Sluiten", + "done": "Klaar", + "edit": "Bewerken", + "editLockedToken": "Verifieer om het vergrendelde token te bewerken.", + "editToken": "Token bewerken", + "enablePolling": "Zoeken aanzetten", + "encoding": "Codering", + "enterDetailsForToken": "Voer informatie over token in", + "enterLink": "Link invoeren", + "enterPasswordToEncrypt": "Voer een wachtwoord in om de tokens te versleutelen. Dit wachtwoord is vereist om de tokens te importeren.", + "errorLogCleared": "Foutenlogboek gewist.", + "errorLogEmpty": "Het foutenlogboek is leeg.", + "errorLogTitle": "Foutenlogboek", + "errorMailBody": "Het foutlogbestand is bijgevoegd.\nU kunt deze tekst vervangen door aanvullende informatie over de fout.", + "errorRollOutFailed": "Uitrollen van token {name} mislukt.", + "errorRollOutNoConnectionToServer": "Uitrollen mislukt. Geen verbinding met de server.", + "errorRollOutNotPossibleAnymore": "Het uitrollen van dit token is niet meer mogelijk.", + "errorRollOutSSLHandshakeFailed": "SSL-handdruk mislukt. Uitrollen niet mogelijk.", + "errorRollOutUnknownError": "Een onbekende fout heeft plaats gevonden. Uitrollen is niet mogelijk: {e}", + "errorSavingFile": "Fout bij het opslaan van het bestand", + "errorSynchronizationNoNetworkConnection": "Token synchroniseren mislukt, privacyIDEA server kan niet worden bereikt.", + "errorTokenExpired": "Het token {name} is verlopen.", + "errorUnlinkingPushToken": "Het is niet gelukt om het push token {label} te ontkoppelen.", + "errorWhenPullingChallenges": "Er is een fout opgetreden bij het zoeken naar uitdagingen van {name}", "exampleUrl": "Voer een geldige URL in zoals: \"https://example.com/\"", - "pushEndpointUrl": "Push Endpoint URL", + "expandLockedFolder": "Verifieer om de vergrendelde map te openen.", + "export": "Exporteren", + "exportAllTokens": "Alle tokens exporteren", + "exportLockedTokenReason": "Authenticeer om vergrendelde tokens te exporteren.", + "exportNonPrivacyIDEATokens": "Niet-privacyIDEA tokens exporteren", + "exportTokens": "Tokens exporteren", + "exportingTokens": "Tokens exporteren...", + "failedToLoad": "Mislukt bij het laden:", + "feedback": "Feedback", + "feedbackDescription": "Als je vragen, suggesties of problemen hebt, laat het ons dan weten.", + "feedbackHint": "Er wordt een kant-en-klare e-mail geopend die je naar ons kunt sturen. Indien gewenst wordt informatie over je apparaat en de versie van de applicatie toegevoegd. U kunt de e-mail controleren en bewerken voordat u deze verzendt.", + "feedbackPrivacyPolicy1": "Door feedback te sturen ga je akkoord met ons ", + "feedbackPrivacyPolicy2": "privacybeleid", + "feedbackPrivacyPolicy3": ".", + "feedbackSentDescription": "Hartelijk dank voor je hulp om deze applicatie beter te maken!", + "feedbackSentTitle": "Feedback verzonden", + "feedbackTitle": "Uw feedback is altijd welkom!", + "fileSavedToDownloadsFolder": "Bestand opgeslagen in de map Downloads", + "findingQrCodeInImage": "Op zoek naar QR code in afbeelding...", + "firebaseToken": "Firebase Token", + "folderName": "Mapnaam", + "generatingPhonePart": "Genereren telefoon gedeelte", + "generatingRSAKeyPair": "Genereren RSA sleutelpaar", + "generatingRSAKeyPairFailed": "Genereren RSA sleutelpaar mislukt", + "goToSettingsButton": "Ga naar instellingen", + "goToSettingsDescription": "Authenticatie via inloggegevens of biometrie is niet ingesteld. Stel het in bij de instellingen van het apparaat.", + "grantCameraPermissionDialogButton": "Toestemming verlenen", + "grantCameraPermissionDialogContent": "Geef de camera toestemming om QR-codes te scannen.", + "grantCameraPermissionDialogPermanentlyDenied": "Cameratoestemming is permanent geweigerd. Geef de camera toestemming in de instellingen van uw telefoon.", + "grantCameraPermissionDialogTitle": "Cameratoestemming is niet verleend", + "handshakeFailed": "Handshake mislukt", + "hidePushTokens": "Verberg push tokens", + "hidePushTokensDescription": "Verberg push tokens uit de token lijst. Hierdoor worden de tokens niet verwijderd en blijven ze zichtbaar op een apart scherm.", + "imageUrl": "Afbeeldings-URL", + "importConflictToken": "{count, plural, zero{Er is geen conflict met tokens die al bestaan.} one{Er is een conflict met tokens die al bestaan.Selecteer welke u wilt behouden.} other{Er is een conflict met tokens die al bestaan.Selecteer welke u wilt behouden.}}", + "importExistingToken": "{count, plural, zero{Er is geen token gevonden dat al in de toepassing aanwezig is.} one{Er is een token gevonden dat al bestaat in de applicatie.} other{{count} tokens gevonden die al in de applicatie staan.}}", + "importExportTokens": "Tokens importeren/exporteren", + "importFailedToken": "{count, plural, zero{Geen token Niet geïmporteerd.} one{Kan geen token importeren.} other{Kan {count} tokens niet importeren.}}", + "importHint2FAS": "Selecteer uw 2FAS-back-up. Als u geen back-up hebt, maak er dan een aan in de 2FAS-app. Wij raden u aan een wachtwoord te gebruiken.", + "importHintAegisBackupFile": "Selecteer uw Aegis-export (.JSON).Als u geen export hebt, maak er dan een aan via het instellingenmenu in de Aegis-app. Het gebruik van een wachtwoord wordt aanbevolen.", + "importHintAegisLink": "Voer de link in die u ontvangt wanneer u vermeldingen van Aegis overdraagt.", + "importHintAegisQrScan": "Scan de QR-code die u ontvangt bij het overbrengen van items uit Aegis.", + "importHintAuthenticatorProFile": "Om een back-up te maken van de Authenticator Pro app, navigeer je naar de instellingen en tik je op \"Auto back-up\". Selecteer een opslaglocatie en stel een wachtwoord in. Druk vervolgens op \"Nu back-uppen\" om de tokens te exporteren.", + "importHintFreeOtpPlusFile": "Om een back-up van de FreeOTP+ app te maken, tikt u op de drie puntjes in de rechterbovenhoek en selecteert u \"Exporteren\". U kunt kiezen tussen JSON en URI formaat. We raden u aan de back-up te verwijderen na het importeren, omdat deze niet versleuteld is.", + "importHintFreeOtpPlusQrScan": "Scan de QR-code die u ontvangt wanneer u op de drie stippen in de tegel van de token drukt en selecteer \"QR-code delen\".", + "importHintGoogleQrFile": "Selecteer een afbeeldingsbestand met de QR-code die u ontvangt wanneer u uw accounts exporteert vanuit Google Authenticator.\n!! Let op: het is niet veilig om de QR-code op je apparaat op te slaan, omdat de tokens niet versleuteld zijn !!", + "importHintGoogleQrScan": "Scan de QR-code die u ontvangt wanneer u uw accounts exporteert vanuit Google Authenticator.", + "importHintPrivacyIdeaFile": "Om een back-up te maken, ga je naar de instellingen en tik je op \"Exporteren\". Selecteer \"Als bestand\" en selecteer de tokens die je wilt exporteren. Tik dan op \"Exporteren\" en stel een wachtwoord in. De opslaglocatie is de downloadmap op je apparaat.", + "importHintPrivacyIdeaQrScan": "Om QR-codes van de tokens te maken, navigeer je naar de instellingen en tik je op \"Exporteren\". Selecteer vervolgens \"Als QR-code\" en tik op het muntje dat geëxporteerd moet worden. Deze variant is alleen geschikt voor directe overdracht naar een ander apparaat, omdat de QR-code niet versleuteld is.", + "importNTokens": "{count, plural, zero{Geen tokens importeren} one{Eén token importeren} other{Importeer {count} tokens}}", + "importNewToken": "{count, plural, zero{Er is geen nieuw token gevonden.} one{Er is een nieuw token gevonden dat geïmporteerd kan worden.} other{Er zijn {count} nieuwe tokens gevonden die geïmporteerd kunnen worden.}}", + "importTokens": "Importer un jeton", + "importedVia": "Geïmporteerd via", + "increaseCounter": "Verhoog teller", + "internalServerError": "Interne serverfout ({code})", + "introAddFolder": "Je kunt mappen maken om je tokens te organiseren.", + "introAddTokenManually": "Als je geen QR-code wilt scannen, kun je tokens ook handmatig toevoegen.", + "introDragToken": "Reorganiseer je tokens door er een paar seconden op te drukken en het dan naar de gewenste positie te slepen.", + "introEditToken": "Hier kun je de naam van het token bewerken en enkele details bekijken.", + "introHidePushTokens": "Je push tokens zijn nu verborgen, maar je kunt ze nog steeds zien op het push token scherm.", + "introLockToken": "Om de beveiliging nog meer te verbeteren, kun je tokens vergrendelen.¨Dan kan het token alleen gebruikt worden na authenticatie.", + "introPollForChallenges": "Je kunt controleren of er nieuwe uitdagingen zijn door de lijst met tokens naar beneden te slepen.", + "introScanQrCode": "Je kunt QR-codes scannen om tokens toe te voegen.We ondersteunen alle gangbare Two-Factor-Authenticatie tokens en ook de privacyIDEA tokens.", + "introTokenSwipe": "Veeg tokens naar links om beschikbare acties te zien.", + "invalidBackupFile": "Het geselecteerde bestand is geen geldige backup van {appName}.", + "invalidLink": "De ingevoerde link is geen geldig token van {appName}, of wordt niet ondersteund.", + "invalidQrFile": "Het geselecteerde bestand bevat geen geldige QR code van {appName}.", + "invalidQrScan": "De gescande QR code is geen geldige backup van {appName}.", + "invalidValue": "untranslated", + "invalidValueIn": "untranslated", + "isExpotableQuestion": "Is exporteerbaar?", + "isPiTokenQuestion": "Is het een privacyIDEA token?", + "language": "Taal", + "legacySigningErrorMessage": "Het token is aangemaakt in een verouderde versie van de app, wat kan leiden tot problemen bij het gebruik ervan.\nHet wordt aanbevolen om een nieuw push token aan te maken als het probleem zich blijft voordoen!", + "legacySigningErrorTitle": "Er is een fout opgetreden bij het gebruik van het verouderde token: {tokenLabel}", + "licenses": "Licenties", + "licensesAndVersion": "Licenties en versie", + "linkedContainer": "Gekoppelde container", + "lock": "Vergrendel", + "lockOut": "Biometrische authenticatie staat uit. Vergrendel en ontgrendel het scherm om het aan te zetten.", + "logMenu": "Log menu", + "malformedData": "De QR code bevat onjuiste gegevens.", + "markQrCode": "Markeer QR Code", + "missingRequiredParameter": "De waarde voor de parameter [{parameter}] is vereist, maar ontbreekt.", + "missingRequiredParameterIn": "De waarde voor de parameter [{parameter}] is vereist, maar ontbreekt in \"{map}\".", "mustNotBeEmpty": "{field} mag niet leeg zijn", - "@mustNotBeEmpty": { - "placeholders": { - "field": { - "example": "Name" - } - } - }, - "sendPushRequestResponseFailed": "Het verzenden van het antwoord is mislukt. ", - "@sendPushRequestResponseFailed": { - "description": "Error message when the response to a push request could not be sent." - }, + "name": "Naam", + "no": "Nee", + "noFbToken": "Geen Firebase Token beschikbaar", + "noMailAppDescription": "Er is geen e-mail app geïnstalleerd of geïnitialiseerd op dit apparaat, probeer het opnieuw wanneer u in staat bent om een e-mailbericht te verzenden.", + "noMailAppTitle": "Geen mail app gevonden", + "noNetworkConnection": "Geen netwerkverbinding.", + "noPublicKey": "Geen openbare sleutel beschikbaar", + "noResultText1": "Tik op ", + "noResultText2": " de knop om te beginnen!", + "noResultTitle": "Nog geen token opgeslagen.", + "noTokenToExport": "Geen token beschikbaar voor export", + "notAnInteger": "De waarde is geen geheel getal.", + "notAnNumber": "De waarde is geen getal.", + "ok": "Ok", + "oneMore": "Nog een", + "open": "Openen", + "optionalMessage": "Optioneel bericht", + "originApp": "Oorsprong app", + "originDetails": "Details over de oorsprong", + "otpValueCopiedMessage": "Wachtwoord \"{otpValue}\" gekopieerd naar het klembord.", + "parsingResponse": "Antwoord analyseren", + "parsingResponseFailed": "Antwoord analyseren mislukt", + "password": "Wachtwoord", "passwordCannotBeEmpty": "Wachtwoord mag niet leeg zijn", - "passwordMustBeAtLeast8Characters": "Wachtwoord moet minimaal 8 tekens lang zijn", "passwordCannotContainWhitespace": "Wachtwoord mag geen spaties bevatten", + "passwordMustBeAtLeast8Characters": "Wachtwoord moet minimaal 8 tekens lang zijn", "passwordMustContainLowercaseLetter": "Wachtwoord moet een kleine letter bevatten", - "passwordMustContainUppercaseLetter": "Wachtwoord moet een hoofdletter bevatten", "passwordMustContainNumber": "Wachtwoord moet een cijfer bevatten", "passwordMustContainSpecialCharacter": "Wachtwoord moet een speciaal teken bevatten", + "passwordMustContainUppercaseLetter": "Wachtwoord moet een hoofdletter bevatten", "passwordsDoNotMatch": "Wachtwoorden komen niet overeen", - "selectTokensToExportHelpTitle": "Staat jouw token er niet bij?", - "selectTokensToExportHelpContent": "Als een token niet in de lijst staat, is het niet gegarandeerd dat het geen privacyIDEA token is.\nMomenteel kunnen alleen handmatig toegevoegde en geïmporteerde tokens worden geëxporteerd.", - "findingQrCodeInImage": "Op zoek naar QR code in afbeelding...", - "qrNotFound": "Geen QR code gevonden!", + "patchNotesBugFixes": "Bugfixes", + "patchNotesDialogTitle": "Wat is er nieuw?", + "patchNotesImprovements": "Verbeteringen", + "patchNotesNewFeatures": "Nieuwe functies", + "patchNotesV4_3_0NewFeatures1": "Ondersteuning toegevoegd voor het importeren van tokens van Google, Aegis en 2FAS Authenticator. Meer importbronnen zullen in de toekomst worden toegevoegd.", + "patchNotesV4_3_0NewFeatures2": "Feedbackoptie toegevoegd aan de instellingen.", + "patchNotesV4_3_0NewFeatures3": "Push tokens kunnen nu verborgen worden in de tokenlijst.", + "patchNotesV4_3_0NewFeatures4": "Introducties zijn toegevoegd om nieuwe gebruikers op weg te helpen.", + "patchNotesV4_3_0NewFeatures5": "Je kunt nu naar tokens zoeken door op het vergrootglas in de rechterbovenhoek te tikken.", + "patchNotesV4_3_0NewFeatures6": "HomeWidget Token toegevoegd voor Android 12 en hoger.", + "patchNotesV4_3_1BugFix1": "Een probleem is opgelost waarbij de otp-waarde niet werd weergegeven na authenticatie op sommige apparaten.", + "patchNotesV4_3_1Improvement1": "De QR-code scanner is verbeterd.", + "patchNotesV4_4_0Improvement1": "Er zijn meer invoerbronnen toegevoegd.", + "patchNotesV4_4_0Improvement2": "De herkenning van QR-codes uit afbeeldingsbestanden is verbeterd.", + "patchNotesV4_4_0NewFeatures1": "Het is nu mogelijk om tokens te exporteren waarvan kan worden gegarandeerd dat het geen privacyIDEA tokens zijn. Op dit moment kan niet worden uitgesloten dat tokens die zijn toegevoegd via de QR-code scanner afkomstig zijn van privacyIDEA. De differentiatie zal in toekomstige versies worden verbeterd.", + "patchNotesV4_4_0NewFeatures2": "Ondersteuning toegevoegd voor privacyIDEA's \"require presence\".", + "period": "Duur", + "phonePart": "Telefoon gedeelte:", + "pleaseEnterANameForThisToken": "Voer de naam in voor deze token.", + "pleaseEnterASecretForThisToken": "Voer de geheime sleutel in voor deze token.", + "pleaseSyncManuallyWhenNetworkIsAvailable": "Synchroniseer de push tokens handmatig via de instellingen als er een netwerkverbinding beschikbaar is.", + "pollingChallenges": "Zoeken naar nieuwe aanvragen", + "pollingFailed": "Vraag mislukt.", + "pollingFailedFor": "Query voor {serial} mislukt.", + "privacyPolicy": "Privacybeleid", + "publicKey": "Openbare sleutel", + "pushEndpointUrl": "Push Endpoint URL", + "pushRequestParseError": "Het pushverzoek kon niet worden verwerkt.", + "pushToken": "Push Token", + "pushTokens": "Push Tokens", + "qrFileDecodeError": "Het was niet mogelijk om de QR code te decoderen van de geselecteerde afbeelding, gebruik in plaats daarvan de QR code scanner.", "qrInFileNotFound": "Er is geen QR code gevonden in de geselecteerde afbeelding.", "qrInFileNotFound2": "U kunt mij laten zien waar de QR code is.", "qrInFileNotFound3": "Ik verwacht dat ik de code zal vinden als het in het midden van het gemarkeerde gebied is.", - "markQrCode": "Markeer QR Code", - "malformedData": "De QR code bevat onjuiste gegevens." + "qrNotFound": "Geen QR code gevonden!", + "qrScan": "Scan", + "rename": "Wijzigen", + "renameToken": "Hernoem token", + "renameTokenFolder": "Map hernoemen", + "requestInfo": "Verzonden door {issuer} voor uw account: \"{account}\"", + "requestPushChallengesPeriodically": "Activeer het zoeken naar berichten. Gebruik deze optie wanneer de push berichten niet worden ontvangen.", + "requestTriggerdByUserQuestion": "Is dit verzoek door jou gedaan?", + "retryRollout": "Opnieuw uitrollen", + "rolloutCompleted": "Uitrollen voltooid", + "save": "Opslaan", + "scanQrCode": "Scan QR-Code", + "scanThisQrWithNewDevice": "Scan deze QR-code met uw nieuwe apparaat om de token te importeren.", + "searchTokens": "Zoek tokens", + "secretIsRequired": "Geheim is vereist", + "secretKey": "Geheime sleutel", + "selectFile": "Selecteer bestand", + "selectImportSource": "Selecteer importbron", + "selectImportType": "Hoe wilt u de tokens importeren?", + "selectTokensToExport": "{count, plural, zero{} one{Selecteer token om te exporteren} other{Selecteer tokens om te exporteren}}", + "selectTokensToExportHelpContent": "Als een token niet in de lijst staat, is het niet gegarandeerd dat het geen privacyIDEA token is.\nMomenteel kunnen alleen handmatig toegevoegde en geïmporteerde tokens worden geëxporteerd.", + "selectTokensToExportHelpTitle": "Staat jouw token er niet bij?", + "send": "verzenden", + "sendErrorDialogBody": "Een onverwachte fout heeft plaatsgevonden in de applicatie. De onderstaande informatie kan worden verstuurd naar de ontwikkelaars via e-mail om het probleem in de toekomst te voorkomen.", + "sendErrorLogDescription": "Er wordt een kant-en-klare e-mail gemaakt die informatie bevat over de app, de fout en het apparaat.\nJe kunt de e-mail bewerken voordat je hem verstuurt.\nJe kunt hier zien hoe we de informatie gebruiken:", + "sendPushRequestResponseFailed": "Het verzenden van het antwoord is mislukt. ", + "sendingRSAPublicKey": "Versturen van de openbare RSA sleutel", + "sendingRSAPublicKeyFailed": "Versturen van de openbare RSA sleutel mislukt", + "serverNotReachable": "De server kon niet worden bereikt.", + "settings": "Instellingen", + "settingsGroupGeneral": "Algemene informatie", + "showDetails": "Details tonen", + "showErrorLog": "Weergeven", + "showPrivacyPolicy": "Privacybeleid tonen", + "signInTitle": "Authenticatie vereist", + "someTokensDoNotSupportPolling": "Sommige tokens zijn verouderd en ondersteunen geen actief zoeken", + "startRollout": "Start uitrollen", + "statusCode": "Statuscode: {statusCode}", + "sync": "Synchroniseer", + "syncContainerFailed": "Container synchronisatie mislukt", + "syncFbTokenFailed": "Synchroniseren mislukt voor de volgende tokens, probeer het opnieuw:", + "synchronizePushTokens": "Synchroniseer push tokens", + "synchronizesTokensWithServer": "Synchroniseert tokens met de privacyIDEA server.", + "synchronizingTokens": "Tokens synchroniseren.", + "theSecretDoesNotFitTheCurrentEncoding": "De geheime sleutel past niet bij de huidige codering", + "themeMode": "Thema", + "thisAppIsOpenSource": "Deze app is open source\nBezoek ons op GitHub", + "timeOut": "Time-out", + "tokenDataParseError": "Token-gegevens konden niet worden verwerkt", + "tokenDetails": "Details van de token", + "tokenLink": "tokenlink", + "tokenSerial": "Token serial", + "tokensAreEncrypted": "De tokens zijn gecodeerd. Voer het wachtwoord in om ze te decoderen.", + "tokensDoNotSupportSynchronization": "Voor de volgende tokens wordt synchroniseren niet ondersteunt, ze moeten opnieuw worden aangeleverd:", + "tokensNotEncrypted": "De tokens zijn niet versleuteld en kunnen direct worden geïmporteerd.", + "tokensSuccessfullyDecrypted": "De tokens zijn succesvol gedecodeerd en kunnen nu worden geïmporteerd.", + "type": "Type", + "unexpectedError": "Er is een onverwachte fout opgetreden.", + "unknown": "Onbekend", + "unlock": "Ontgrendel", + "unsupported": "De {name} [{value}] wordt niet ondersteund door deze versie van de app.", + "useDeviceLocaleDescription": "Gebruik de taal van het apparaat wanneer het wordt ondersteund, val anders terug op Engels.", + "useDeviceLocaleTitle": "Gebruik de taal van het apparaat", + "validFor": "Geldig voor", + "validUntil": "Geldig tot", + "valueNotAllowed": "untranslated", + "valueNotAllowedIn": "untranslated", + "verboseLogging": "Verbose loggen", + "version": "Versie", + "wrongPassword": "Onjuist wachtwoord", + "yes": "Ja" } \ No newline at end of file diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index d955a503c..e03918b4b 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -1,377 +1,362 @@ { - "@@last_modified": "2023-08-07", - "patchNotesNewFeatures": "Nowe funkcje", - "patchNotesImprovements": "Ulepszenia", - "patchNotesBugFixes": "Poprawki błędów", - "patchNotesV4_4_0NewFeatures1": "Możliwe jest teraz eksportowanie tokenów, w przypadku których można upewnić się, że nie są to tokeny privacyIDEA. Obecnie nie można wykluczyć, że tokeny dodane za pomocą skanera kodów QR pochodzą z privacyIDEA. Rozróżnienie to zostanie poprawione w przyszłych wersjach", - "patchNotesV4_4_0NewFeatures2": "Dodano obsługę funkcji privacyIDEA \"require presence\".", - "patchNotesV4_4_0Improvement1": "Dodano kolejne źródła importu.", - "patchNotesV4_4_0Improvement2": "Ulepszono rozpoznawanie kodów QR z plików graficznych.", - "patchNotesV4_3_1BugFix1": "Został naprawiony problem, w którym wartość OTP nie była wyświetlana po uwierzytelnieniu na niektórych urządzeniach.", - "patchNotesV4_3_1Improvement1": "Został poprawiony skaner kodów QR.", - "patchNotesV4_3_0NewFeatures1": "Dodano obsługę importowania tokenów z Google, Aegis i 2FAS Authenticator. Więcej źródeł importu zostanie dodanych w przyszłości", - "patchNotesV4_3_0NewFeatures2": "Dodano opcję opinii do ustawień.", - "patchNotesV4_3_0NewFeatures3": "Tokeny push można teraz ukryć na liście tokenów", - "patchNotesV4_3_0NewFeatures4": "Dodano wprowadzenia, aby ułatwić nowym użytkownikom rozpoczęcie pracy.", - "patchNotesV4_3_0NewFeatures5": "Możesz teraz wyszukiwać tokeny, dotykając szkła powiększającego w prawym górnym rogu.", - "patchNotesV4_3_0NewFeatures6": "Dodano token HomeWidget dla systemu Android 12 i nowszych.", - "guide": "Przewodnik", - "@guide": { - "description": "Button to open the guide screen." - }, - "accept": "Potwierdzam", + "@@last_modified": "2024-09-20", + "@@locale": "pl", "@accept": { "description": "Label for e.g. a button. Something gets accepted by the user." }, - "decline": "Odrzucam", - "@decline": { - "description": "Label for e.g. a button. Something gets declined by the user." - }, - "name": "Nazwa", - "@name": { - "description": "Describes the field where the tokens name should be entered." - }, - "secretKey": "Tajny klucz", - "@secretKey": { - "description": "Describes the field where the tokens secret should be entered." - }, - "encoding": "Kodowanie", - "@encoding": { - "description": "Title of the dropdown button where the encoding is selected." + "@addToken": { + "description": "The button to open the screen to add tokens by hand." }, - "algorithm": "Algorytm", "@algorithm": { "description": "Title of the dropdown button where the encoding is selected." }, - "digits": "Ilość cyfr", - "@digits": { - "description": "Title of the dropdown button where the number of digits for the opt value is selected." + "@algorithmUnsupported": { + "placeholders": { + "algorithm": { + "example": "MD5" + } + } }, - "type": "Typ", - "@type": { - "description": "Title of the dropdown button where the type of the token is selected." + "@allTokensSynchronized": { + "description": "Content of the push synchronization dialog. Signaling the user that everything worked." }, - "period": "Cykl", - "@period": { - "description": "Title of the dropdown button where the period of the totp token is selected." + "@authNotSupportedBody": { + "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." }, - "rename": "Zmień nazwę", - "@rename": { - "description": "Label that describes renaming the token." + "@authNotSupportedTitle": { + "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." }, - "cancel": "Anuluj", - "@cancel": { - "description": "Button to cancel an action." + "@authToAcceptPushRequest": { + "description": "Message to accepting a push request for authentication." }, - "delete": "Usuń", - "@delete": { - "description": "Label that describes deleting the token." + "@authToDeclinePushRequest": { + "description": "Message for declining a push request for authentication." }, - "dismiss": "Odrzuć", - "@dismiss": { - "description": "Text of a button that closes a dialog." + "@authenticateToShowOtp": { + "description": "Reason to authenticate when trying to view a one time password." }, - "addToken": "Dodaj token", - "@addToken": { - "description": "The button to open the screen to add tokens by hand." + "@authenticateToUnLockToken": { + "description": "Reason to authenticate when trying to lock or unlock a token." }, - "scanQrCode": "Zeskanuj kod QR", - "@scanQrCode": { - "description": "The button to scan otpauth qr-codes." + "@biometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." }, - "enterDetailsForToken": "Wprowadź szczegóły dla tokenu", - "@enterDetailsForToken": { - "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." + "@biometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterANameForThisToken": "Wprowadź nazwę dla tokenu", - "@pleaseEnterANameForThisToken": { - "description": "Hint telling the user to enter a name for a token." + "@biometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." }, - "pleaseEnterASecretForThisToken": "Wprowadź sekret dla tokenu", - "@pleaseEnterASecretForThisToken": { - "description": "Hint telling the user to enter a secret for a token." + "@biometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." }, - "theSecretDoesNotFitTheCurrentEncoding": "Sekret nie odpowiada wybranemu sposobowi kodowania.", - "@theSecretDoesNotFitTheCurrentEncoding": { - "description": "Hint telling the user that the secret does not fit the selected encoding." + "@cancel": { + "description": "Button to cancel an action." }, - "renameToken": "Zmień nazwę tokenu", - "@renameToken": { - "description": "Title of the dialog where a new name for a token can be entered." + "@checkServerCertificate": { + "description": "Error message when the server certificate should be checked." + }, + "@checkYourNetwork": { + "description": "Tells the user to check the network connection." + }, + "@clearErrorLog": { + "description": "Button to clear the error log." }, - "confirmDeletion": "Potwierdź usunięcie", "@confirmDeletion": { "description": "Title of the dialog where a token can be deleted." }, - "confirmDeletionOf": "Jesteś pewien, że chcesz usunąć token: {name}?", "@confirmDeletionOf": { "description": "Asks for confirmation on deleting a token.", - "type": "text", "placeholders": { "name": { "example": "PUSH1234" } - } - }, - "confirmTokenDeletionHint": "Usunięcie tego tokenu może uniemożliwić zalogowanie się. Upewnij się, że możesz zalogować się na powiązane konto bez tego tokenu.", - "@confirmTokenDeletionHint": { - "description": "Gives the user a hint about the consequences of deleting a token." + }, + "type": "text" }, - "confirmFolderDeletionHint": "Usunięcie folderu nie ma wpływu na znajdujące się w nim tokeny. Tokeny są przenoszone do głównej listy.", "@confirmFolderDeletionHint": { "description": "Gives the user a hint about the consequences of deleting a folder." }, - "generatingPhonePart": "Generowanie sekretu po stronie telefonu...", - "@generatingPhonePart": { - "description": "Title of a dialog telling the user that the phone part gets generated right now." + "@confirmTokenDeletionHint": { + "description": "Gives the user a hint about the consequences of deleting a token." }, - "phonePart": "Sekret po stronie telefonu:", - "@phonePart": { - "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." + "@connectionFailed": { + "description": "Tells the user that the connection failed." }, - "otpValueCopiedMessage": "Jednorazowe hasło \"{otpValue}\" skopiowane do schowka.", - "@otpValueCopiedMessage": { - "description": "Tells the user that the otp value was copied to the clipboard.", - "type": "text", - "placeholders": { - "otpValue": { - "example": "055374" - } - } + "@container": { + "description": "Title for the container view." }, - "settings": "Ustawienia", - "@settings": { - "description": "Button to open the settings page." + "@couldNotSignMessage": { + "description": "Tells the user that the message could not be signed." }, - "pushToken": "Push token", - "@pushToken": { - "description": "Title for the settings block concerning the push tokens." + "@counter": { + "description": "Describes the field where the tokens counter should be entered." }, - "theme": "Motyw", - "@theme": { - "description": "Title of the setting group where the theme can be selected." + "@createdAt": { + "description": "Label for the creation date of the token." }, - "lightTheme": "Jasny", - "@lightTheme": { - "description": "The light theme." + "@creator": { + "description": "Label for the creator of the token." }, - "darkTheme": "Ciemny", "@darkTheme": { "description": "The dark theme." }, - "systemTheme": "Motyw systemu", - "@systemTheme": { - "description": "The systems theme." + "@decline": { + "description": "Label for e.g. a button. Something gets declined by the user." }, - "someTokensDoNotSupportPolling": "Część tokenów jest przestarzała i nie wspiera aktywnego zapytania dla autentykacji przez wiadomość push.", - "@someTokensDoNotSupportPolling": { - "description": "Tells the user, that the following tokens do not support polling.", - "type": "text", - "placeholders": {} + "@delete": { + "description": "Label that describes deleting the token." }, - "enablePolling": "Włącz autentykację przez wiadomość push.", - "@enablePolling": { - "description": "Name of the setting switch that enables polling." + "@deviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." }, - "requestPushChallengesPeriodically": "Wysyłaj zapytanie o push challenge cyklicznie. Włącz, jeśli push nie przychodzi normalnie.", - "@requestPushChallengesPeriodically": { - "description": "The description of the polling feature." + "@deviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." }, - "synchronizePushTokens": "Synchronizuj tokeny push.", - "@synchronizePushTokens": { - "description": "Title of synchronizing push tokens in settings." + "@digits": { + "description": "Title of the dropdown button where the number of digits for the opt value is selected." }, - "synchronizesTokensWithServer": "Synchronizuje tokeny push z serwerem privacyIDEA.", - "@synchronizesTokensWithServer": { - "description": "Description of synchronizing push tokens in settings." + "@dismiss": { + "description": "Text of a button that closes a dialog." }, - "sync": "Synchronizuj", - "@sync": { - "description": "Text of button that is used to synchronize push tokens." + "@enablePolling": { + "description": "Name of the setting switch that enables polling." }, - "synchronizingTokens": "Synchronizacja tokenów.", - "@synchronizingTokens": { - "description": "Title of the push synchronization dialog." + "@encoding": { + "description": "Title of the dropdown button where the encoding is selected." }, - "allTokensSynchronized": "Wszystkie tokeny są zsynchronizowane.", - "@allTokensSynchronized": { - "description": "Content of the push synchronization dialog. Signaling the user that everything worked." + "@enterDetailsForToken": { + "description": "Title of the screen where tokens are created manually, tells the user to enter all required values." }, - "syncFbTokenFailed": "Synchronizacja dla poniższych tokenów się nie udała, spróbuj ponownie:", - "@syncFbTokenFailed": { - "description": "Headline for the list of tokens where the synchronization failed." + "@errorLogCleared": { + "description": "Message that tells the user that the error log was cleared." }, - "tokensDoNotSupportSynchronization": "Następujące tokeny nie wspierają synchronizacji i muszą zostać wdrożone od nowa:", - "@tokensDoNotSupportSynchronization": { - "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + "@errorLogEmpty": { + "description": "Message that tells the user that the error log is empty." + }, + "@errorLogTitle": { + "description": "Title of the error log screen." + }, + "@errorMailBody": { + "description": "Message for email body" }, - "errorRollOutFailed": "Wdrażanie tokenu {name} nieudane.", "@errorRollOutFailed": { "description": "Tells the user that the token could not be rolled out, because a network error occurred.", - "type": "text", "placeholders": { "name": { "example": "PUSH1234A" } - } + }, + "type": "text" }, - "statusCode": "Kod statusu: {statusCode}", - "@statusCode": { - "description": "Tells the user the status code of the error.", + "@errorRollOutNoConnectionToServer": { + "description": "Message for the rollout process", "placeholders": { - "statusCode": { - "example": "400" + "name": { + "example": "PUSH1234A" } } }, - "errorSynchronizationNoNetworkConnection": "Synchronizacja tokenów push nieudana, ponieważ serwer privacyIDEA jest nieosiągalny.", - "@errorSynchronizationNoNetworkConnection": { - "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." + "@errorRollOutSSLHandshakeFailed": { + "description": "Tells the user that the roll-out failed because the SSL handshake failed." }, - "errorRollOutUnknownError": "Napotkano nieznany błąd. Wdrożenie tokenu niemożliwe: {e}", "@errorRollOutUnknownError": { "description": "Tells the user that the roll-out failed because of an unknown error.", - "type": "text", "placeholders": { "e": { "example": "IllegalArgumentException on Line 5 ..." } - } - }, - "startRollout": "Rozpocznij wdrożenie", - "@startRollout": { - "description": "Label that tells the user that the token is being rolled out." - }, - "pollingChallenges": "Sprawdzanie nowych wyzwań", - "@pollingChallenges": { + }, "type": "text" }, - "unexpectedError": "Wystąpił nieoczekiwany błąd.", - "@unexpectedError": { - "description": "Title of page report mode." + "@errorSynchronizationNoNetworkConnection": { + "description": "Tells the user that synchronizing the push tokens failed because the server could not be reached." }, - "pollingFailed": "Zapytanie nie powiodło się.", - "@pollingFailed": { - "description": "Tells the user that the polling failed." + "@errorTokenExpired": { + "placeholders": { + "name": { + "example": "PUSH1234A" + } + } }, - "pollingFailedFor": "Zapytanie dla {serial} nie powiodło się.", - "@pollingFailedFor": { - "description": "Tells the user that the polling failed.", + "@errorUnlinkingPushToken": { + "description": "Error message when unlinking a push token failed.", "placeholders": { - "serial": { + "label": { "example": "PUSH1234A" } } }, - "noNetworkConnection": "Brak połączenia sieciowego.", - "@noNetworkConnection": { - "description": "Tells the user that there is no network connection." + "@errorWhenPullingChallenges": { + "description": "errorWhenPullingChallenges", + "placeholders": { + "name": { + "example": "PUSH1234A" + } + } }, - "connectionFailed": "Połączenie nie powiodło się.", - "@connectionFailed": { - "description": "Tells the user that the connection failed." + "@failedToLoad": { + "placeholders": { + "name": { + "example": "token data" + } + } }, - "checkYourNetwork": "Sprawdź połączenie sieciowe i spróbuj ponownie.", - "@checkYourNetwork": { - "description": "Tells the user to check the network connection." + "@feedbackPrivacyPolicy2": { + "description": "Tapnięcie na to powinno otworzyć politykę prywatności." }, - "serverNotReachable": "Nie można uzyskać połączenia z serwerem.", - "@serverNotReachable": { - "description": "Tells the user that the server could not be reached." + "@generatingPhonePart": { + "description": "Title of a dialog telling the user that the phone part gets generated right now." }, - "couldNotSignMessage": "Nie można podpisać wiadomości.", - "@couldNotSignMessage": { - "description": "Tells the user that the message could not be signed." + "@generatingRSAKeyPair": { + "description": "Message for the rollout process" }, - "useDeviceLocaleTitle": "Użyj języka urządzenia.", - "@useDeviceLocaleTitle": { - "description": "Title of the switch tile where using the devices language can be enabled." + "@generatingRSAKeyPairFailed": { + "description": "Message for the rollout process" }, - "useDeviceLocaleDescription": "Użyj języka urządzenia, jeśli jest wspierany. W innym wypadku zostanie ustawiony domyślny język angielski.", - "@useDeviceLocaleDescription": { - "description": "Description of the switch tile where using the devices language can be enabled." + "@goToSettingsButton": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." }, - "language": "Język", - "@language": { - "description": "Title of language setting group." + "@goToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." }, - "authenticateToShowOtp": "Zweryfikuj tożsamość, by pokazać hasło jednorazowe.", - "@authenticateToShowOtp": { - "description": "Reason to authenticate when trying to view a one time password." + "@guide": { + "description": "Button to open the guide screen." }, - "authenticateToUnLockToken": "Zweryfikuj tożsamość, aby odblokować / zablokować token.", - "@authenticateToUnLockToken": { - "description": "Reason to authenticate when trying to lock or unlock a token." + "@handshakeFailed": { + "description": "Error message when the handshake failed." }, - "biometricRequiredTitle": "Uwierzytelnianie biometryczne nie jest skonfigurowane.", - "@biometricRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + "@importedVia": { + "description": "Label for the import method of the token." }, - "biometricHint": "Wymagana autentykacja", - "@biometricHint": { - "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + "@internalServerError": { + "placeholders": { + "code": { + "example": "500" + } + } }, - "biometricNotRecognized": "Nie rozpoznano. Spróbuj ponownie.", - "@biometricNotRecognized": { - "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + "@invalidValue": { + "description": "Error message when the value is not valid for the parameter.", + "placeholders": { + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "biometricSuccess": "Autentykacja zakończona sukcesem!", - "@biometricSuccess": { - "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + "@invalidValueIn": { + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "5" + } + } }, - "deviceCredentialsRequiredTitle": "Ustawienia zabezpieczeń urządzenia nie zostały skonfigurowane.", - "@deviceCredentialsRequiredTitle": { - "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + "@isExpotableQuestion": { + "description": "Label for the question if the token is exportable." }, - "deviceCredentialsSetupDescription": "Skonfiguruj ustawienia zabezpieczeń w ustawieniach urządzenia.", - "@deviceCredentialsSetupDescription": { - "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + "@isPiTokenQuestion": { + "description": "Label for the question if the token is a privacyIDEA token." }, - "signInTitle": "Wymagana autentykacja", - "@signInTitle": { - "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + "@language": { + "description": "Title of language setting group." }, - "goToSettingsButton": "Idź do ustawień", - "@goToSettingsButton": { - "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + "@legacySigningErrorMessage": { + "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." }, - "goToSettingsDescription": "Ustawienia zabezpieczeń, bądź uwierzytelnianie biometryczne nie są skonfigurowane w twoim urządzeniu. Skonfiguruj je w ustawieniach urządzenia.", - "@goToSettingsDescription": { - "description": "Message advising the user to go to the settings and configure device credentials or biometrics on their device." + "@legacySigningErrorTitle": { + "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", + "placeholders": { + "tokenLabel": { + "example": "PUSH1234A" + } + } + }, + "@lightTheme": { + "description": "The light theme." + }, + "@linkedContainer": { + "description": "Label for the linked container serial number." + }, + "@lock": { + "description": "Description of button that locks a token." }, - "lockOut": "Uwierzytelnianie biometryczne jest wyłączone. Zablokuj i odblokuj ponownie ekran, żeby je włączyć.", "@lockOut": { "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." }, - "authNotSupportedTitle": "Skonfigurowane ustawienia zabezpieczeń albo uwierzytelnianie biometryczne jest wymagane.", - "@authNotSupportedTitle": { - "description": "Message shown as a dialog title that tells the user that device credentials or biometrics must be setup for this action." + "@logMenu": { + "description": "Button to open the log menu." }, - "authNotSupportedBody": "To działanie wymaga skonfigurowania ustawień zabezpieczeń albo uwierzytelniania biometrycznego.", - "@authNotSupportedBody": { - "description": "Message shown as a dialog body that tells the user that device credentials or biometrics must be setup for this action." + "@malformedData": { + "description": "Error message when the data is malformed." }, - "lock": "Zablokuj", - "@lock": { - "description": "Description of button that locks a token." + "@missingRequiredParameter": { + "description": "Error message when a required parameter is missing.", + "placeholders": { + "parameter": { + "example": "counter" + } + } }, - "unlock": "Odblokuj", - "@unlock": { - "description": "Description of button that unlocks a token." + "@missingRequiredParameterIn": { + "description": "Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.Error message when a required parameter is missing in a specific map.", + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + } + } }, - "noResultTitle": "Nie zainstalowano jeszcze żadnego tokenu.", - "@noResultTitle": { - "description": "No tokens installed yet." + "@mustNotBeEmpty": { + "placeholders": { + "field": { + "example": "Name" + } + } + }, + "@name": { + "description": "Describes the field where the tokens name should be entered." + }, + "@noFbToken": { + "description": "Tells the user that there is no Firebase token available." + }, + "@noNetworkConnection": { + "description": "Tells the user that there is no network connection." }, - "noResultText1": "Dotknij ", "@noResultText1": { "description": "first noresult text" }, - "noResultText2": " przycisku, żeby zacząć!", "@noResultText2": { "description": "second noresult text" }, - "onBoardingTitle1": "{appName}", + "@noResultTitle": { + "description": "No tokens installed yet." + }, + "@notAnInteger": { + "description": "Tells the user that there is no Firebase token available." + }, + "@notAnNumber": { + "description": "Error message when the user entered a value that is not a number." + }, + "@ok": { + "description": "Button to confirm an action." + }, "@onBoardingTitle1": { "placeholders": { "appName": { @@ -379,294 +364,557 @@ } } }, - "onBoardingText1": "Uwierzytelnianie dwuskładnikowe\nuczynione prostym", - "onBoardingTitle2": "Maksymalne Bezpieczeństwo", - "onBoardingText2": "Przechowuj tokeny w swoim urządzeniu\nzabezpieczone biometrycznie", - "onBoardingTitle3": "Odwiedź nas na Github", - "onBoardingText3": "Ta aplikacja jest w open source", - "errorLogTitle": "Dziennik błędów", - "logMenu": "Menu dziennika", - "showErrorLog": "Wyświetl", - "clearErrorLog": "Usuń", - "send": "Wyślij", - "sendErrorLogDescription": "Tworzona jest gotowa wiadomość e-mail zawierająca informacje o aplikacji, błędzie i urządzeniu.\nMożesz edytować wiadomość e-mail przed jej wysłaniem.\nTutaj można zobaczyć, w jaki sposób wykorzystujemy te informacje:", - "@sendErrorLogDescription": { - "description": "Explanation for the user what he will send." + "@open": { + "description": "Button to open something." }, - "showPrivacyPolicy": "Pokaż politykę prywatności", - "errorLogEmpty": "Dziennik błędów jest pusty", - "verboseLogging": "Wyczerpujące rejestrowanie", - "errorLogCleared": "Dziennik błędów wyczyszczony.", - "ok": "Ok", - "errorMailBody": "Plik dziennika błędów jest dołączony.\nTekst ten można zastąpić dodatkowymi informacjami o błędzie.", - "@errorMailBody": { - "description": "Message for email body" + "@originApp": { + "description": "Label for the origin app." }, - "showDetails": "Pokaż szczegóły", - "open": "Otwórz", - "sendErrorDialogBody": "Napotkano nieoczekiwany błąd w aplikacji. Poniższa wiadomość może zostać wysłana do deweloperów poprzez email, żeby pomóc uniknąć tego problemu w przyszłości.", - "@sendErrorDialogBody": { - "description": "Description shown to the user about what info the error report contains." + "@originDetails": { + "description": "Title of the origin details menu." }, - "noFbToken": "Brak dostępnego tokena Firebase", - "firebaseToken": "Token Firebase", - "noPublicKey": "Brak dostępnego klucza publicznego", - "publicKey": "Klucz publiczny", - "editToken": "Edytuj token", - "edit": "Edytuj", - "save": "Zapisz", - "create": "Utwórz", - "validFor": "Ważny przez", - "validUntil": "Ważny do", - "deleteLockedToken": "Uwierzytelnij, aby usunąć zablokowany token.", - "editLockedToken": "Aby edytować zablokowany token, należy się uwierzytelnić.", - "expandLockedFolder": "Uwierzytelnij, aby otworzyć zablokowany folder.", - "renameTokenFolder": "Zmiana nazwy folderu", - "addANewFolder": "Utwórz nowy folder", - "folderName": "Nazwa folderu", - "retryRollout": "Ponowne uruchomienie", - "generatingRSAKeyPair": "Generowanie pary kluczy RSA", - "@generatingRSAKeyPair": { - "description": "Message for the rollout process" + "@otpValueCopiedMessage": { + "description": "Tells the user that the otp value was copied to the clipboard.", + "placeholders": { + "otpValue": { + "example": "055374" + } + }, + "type": "text" }, - "generatingRSAKeyPairFailed": "Generowanie pary kluczy RSA nieudane", - "@generatingRSAKeyPairFailed": { + "@parsingResponse": { "description": "Message for the rollout process" }, - "sendingRSAPublicKey": "Wysyłanie publicznego klucza RSA", - "@sendingRSAPublicKey": { + "@parsingResponseFailed": { "description": "Message for the rollout process" }, - "sendingRSAPublicKeyFailed": "Wysyłanie publicznego klucza RSA nieudane", - "@sendingRSAPublicKeyFailed": { - "description": "Message for the rollout process" + "@period": { + "description": "Title of the dropdown button where the period of the totp token is selected." }, - "parsingResponse": "Analizowanie odpowiedzi", - "@parsingResponse": { - "description": "Message for the rollout process" + "@phonePart": { + "description": "Title of a dialog telling the user that the phone was generated, and it is shown to the user." }, - "parsingResponseFailed": "Analizowanie odpowiedzi nieudane", - "@parsingResponseFailed": { - "description": "Message for the rollout process" + "@pleaseEnterANameForThisToken": { + "description": "Hint telling the user to enter a name for a token." }, - "rolloutCompleted": "Wdrożenie zakończone", - "@rolloutCompleted": { - "description": "Message for the rollout process" + "@pleaseEnterASecretForThisToken": { + "description": "Hint telling the user to enter a secret for a token." }, - "errorRollOutNoConnectionToServer": "Brak połączenia z serwerem", - "@errorRollOutNoConnectionToServer": { - "description": "Message for the rollout process" + "@pollingChallenges": { + "type": "text" }, - "authToAcceptPushRequest": "Uwierzytelnij, aby zaakceptować żądanie push.", - "@authToAcceptPushRequest": { - "description": "Message for the rollout process" + "@pollingFailed": { + "description": "Tells the user that the polling failed." }, - "authToDeclinePushRequest": "Uwierzytelnij, aby odrzucić żądanie push.", - "pushRequestParseError": "Żądanie push nie mogło zostać przetworzone.", - "imageUrl": "Adres URL obrazu", - "errorRollOutSSLHandshakeFailed": "Uścisk dłoni SSL nie powiódł się. Rozwijanie nie jest możliwe.", - "errorWhenPullingChallenges": "Wystąpił błąd podczas odpytywania o wyzwania {name}", - "@errorWhenPullingChallenges": { + "@pollingFailedFor": { + "description": "Tells the user that the polling failed.", "placeholders": { - "name": { + "serial": { "example": "PUSH1234A" } } }, - "couldNotConnectToServer": "Nie można połączyć się z serwerem.", - "errorRollOutNotPossibleAnymore": "Wstać z łóżka tego tokena nie jest już możliwe.", - "errorTokenExpired": "Token {name} wygasł.", - "@errorTokenExpired": { - "placeholders": { - "name": { - "example": "PUSH1234A" - } - } + "@pushToken": { + "description": "Title for the settings block concerning the push tokens." }, - "yes": "Tak", - "no": "Nie", - "butDiscardIt": "ale odrzucić go", - "declineIt": "odrzuć go", - "requestTriggerdByUserQuestion": "Czy ta prośba została wywołana przez Ciebie?", - "grantCameraPermissionDialogTitle": "Uprawnienie do kamery nie zostało przyznane", - "grantCameraPermissionDialogContent": "Przyznaj uprawnienia kamery do skanowania kodów QR.", - "grantCameraPermissionDialogPermanentlyDenied": "Uprawnienia do aparatu zostały trwale zablokowane. Przyznaj uprawnienia aparatu w ustawieniach telefonu.", - "grantCameraPermissionDialogButton": "Grant permission", - "decryptErrorTitle": "Decryption error", - "decryptErrorContent": "Niestety, aplikacja nie była w stanie odszyfrować tokenów. Oznacza to, że klucz szyfrowania jest uszkodzony. Możesz spróbować ponownie lub usunąć dane aplikacji, co spowoduje usunięcie tokenów w aplikacji.", - "decryptErrorButtonDelete": "Usuń", - "decryptErrorButtonSendError": "Wyślij błąd", - "decryptErrorButtonRetry": "Ponów próbę", - "decryptErrorDeleteConfirmationContent": "Czy na pewno chcesz usunąć dane aplikacji?", - "hidePushTokens": "Ukryj tokeny push", - "hidePushTokensDescription": "Ukryj tokeny push z listy tokenów. Nie spowoduje to usunięcia tokenów i będą one nadal widoczne na osobnym ekranie", - "settingsGroupGeneral": "Informacje ogólne", - "licensesAndVersion": "Licencje i wersja", - "privacyPolicy": "Polityka prywatności", - "introScanQrCode": "Możesz skanować kody QR, aby dodawać tokeny. Obsługujemy każdy popularny token uwierzytelniania dwuskładnikowego, a także tokeny privacyIDEA.", - "introAddTokenManually": "Jeśli nie chcesz skanować kodu QR, możesz również dodać tokeny ręcznie.", - "introTokenSwipe": "Przesuń tokeny w lewo, aby zobaczyć dostępne akcje.", - "introEditToken": "Tutaj możesz edytować nazwę tokena i zobaczyć kilka szczegółów.", - "introLockToken": "Aby jeszcze bardziej zwiększyć bezpieczeństwo, możesz zablokować tokeny. Wtedy token może być używany tylko po uwierzytelnieniu.", - "introDragToken": "Reorganizuj swoje tokeny, naciskając je przez kilka sekund, a następnie przeciągając je do żądanej pozycji.", - "introAddFolder": "Możesz tworzyć foldery, aby organizować swoje tokeny.", - "introPollForChallenges": "Możesz sprawdzić nowe wyzwania, przeciągając listę tokenów w dół.", - "introHidePushTokens": "Twoje tokeny push są teraz ukryte, ale nadal możesz je zobaczyć na ekranie tokenów push.", - "legacySigningErrorTitle": "Wystąpił błąd podczas korzystania z przestarzałego tokena: {tokenLabel}", - "@legacySigningErrorTitle": { - "description": "Title of the error dialog that is shown when an error occurs while using a legacy token.", - "placeholders": { - "tokenLabel": { - "example": "PUSH1234A" - } - } + "@rename": { + "description": "Label that describes renaming the token." }, - "legacySigningErrorMessage": "Token został utworzony w nieaktualnej wersji aplikacji, co może prowadzić do problemów podczas korzystania z niego.\nZaleca się utworzenie nowego tokena push, jeśli problem nadal występuje!", - "@legacySigningErrorMessage": { - "description": "Message of the error dialog that is shown when an error occurs while using a legacy token." + "@renameToken": { + "description": "Title of the dialog where a new name for a token can be entered." }, - "selectImportSource": "Wybierz źródło importu", - "selectImportType": "Jak chcesz zaimportować tokeny?", - "importTokens": "Token importu", - "importNTokens": "{count, plural, zero{Nie importuj tokenów} one{Importuj jeden token} other{Import {count} tokenów}}", - "selectFile": "Wybierz plik", - "decrypt": "Odszyfruj", - "tokensAreEncrypted": "Tokeny są zaszyfrowane. Wprowadź hasło, aby je odszyfrować", - "tokensNotEncrypted": "Tokeny nie są zaszyfrowane i mogą być importowane bezpośrednio.", - "tokensSuccessfullyDecrypted": "Tokeny zostały pomyślnie odszyfrowane i można je teraz zaimportować.", - "password": "Hasło", - "wrongPassword": "Nieprawidłowe hasło", - "qrScan": "Scan", - "enterLink": "Wprowadź link", - "invalidBackupFile": "Wybrany plik nie jest prawidłową kopią zapasową {appName}.", - "invalidQrScan": "Zeskanowany kod QR nie jest prawidłową kopią zapasową {appName}.", - "invalidQrFile": "Wybrany plik nie zawiera prawidłowego kodu QR z {appName}.", - "invalidLink": "Wprowadzony link nie jest prawidłowym tokenem {appName} lub nie jest obsługiwany.", - "importFailedToken": "{count, plural, zero{Nie udało się zaimportować tokenu.} one{Nie udało się zaimportować tokena.} other{Nie udało się zaimportować {count} tokenów.}}", - "importExistingToken": "{count, plural, zero{Nie znaleziono tokena, który już znajduje się w aplikacji.} one{Znaleziono token, który już istnieje w aplikacji.} other{Znaleziono {count} tokenów, które już znajdują się w aplikacji.}}", - "importConflictToken": "{count, plural, zero{Nie ma konfliktu z tokenami, które już istnieją.} one{Istnieje konflikt z tokenami, które już istnieją.} other{Istnieje konflikt z tokenami, które już istnieją.}}", - "importNewToken": "{count, plural, zero{Nie znaleziono nowego tokena.} one{Znaleziono nowy token, który można zaimportować.} other{Znaleziono {count} nowych tokenów, które można zaimportować.}}", - "importHintPrivacyIdeaQrScan": "Aby utworzyć kody QR tokenów, przejdź do ustawień i wybierz opcję \"Eksportuj\". Następnie wybierz opcję \"Jako kod QR\" i dotknij tokenu, który ma zostać wyeksportowany. Ten wariant jest odpowiedni tylko do bezpośredniego przesyłania na inne urządzenie, ponieważ kod QR nie jest szyfrowany.", - "importHintPrivacyIdeaFile": "Aby utworzyć kopię zapasową, przejdź do ustawień i wybierz \"Eksportuj\". Wybierz \"Jako plik\", wybierz tokeny, które chcesz wyeksportować. Następnie wybierz \"Eksportuj\" i ustaw hasło. Miejscem przechowywania jest folder pobierania na urządzeniu.", - "importHint2FAS": "Wybierz kopię zapasową 2FAS. Jeśli nie masz kopii zapasowej, utwórz ją w aplikacji 2FAS. Zalecamy użycie hasła.", - "importHintAegisBackupFile": "Wybierz swój eksport Aegis (.JSON).\nJeśli nie masz eksportu, utwórz go za pomocą menu ustawień w aplikacji Aegis. Zalecane jest użycie hasła.", - "importHintAegisQrScan": "Zeskanuj kod QR otrzymany podczas przesyłania wpisów z Aegis", - "importHintAegisLink": "Wprowadź link otrzymany podczas przesyłania wpisów z Aegis.", - "importHintGoogleQrScan": "Zeskanuj kod QR otrzymany podczas eksportowania kont z Google Authenticator", - "importHintGoogleQrFile": "Wybierz plik obrazu z kodem QR otrzymanym podczas eksportowania kont z Google Authenticator.\n!! Należy pamiętać, że zapisywanie kodu QR na urządzeniu nie jest bezpieczne, ponieważ tokeny nie są szyfrowane !!", - "importHintAuthenticatorProFile": "Aby utworzyć kopię zapasową aplikacji Authenticator Pro, przejdź do ustawień i dotknij \"Automatyczna kopia zapasowa\". Wybierz lokalizację przechowywania i ustaw hasło. Następnie naciśnij \"Utwórz teraz kopię zapasową\", aby wyeksportować tokeny.", - "importHintFreeOtpPlusQrScan": "Zeskanuj kod QR otrzymany po naciśnięciu trzech kropek na kafelku tokena i wybierz \"Udostępnij kod QR\".", - "importHintFreeOtpPlusFile": "Aby utworzyć kopię zapasową aplikacji FreeOTP+, dotknij trzech kropek w prawym górnym rogu i wybierz \"Eksportuj\". Można wybrać format JSON lub URI. Zalecamy usunięcie kopii zapasowej po jej zaimportowaniu, ponieważ nie jest ona szyfrowana.", - "qrFileDecodeError": "Nie można było zdekodować kodu QR z wybranego obrazu, zamiast tego użyj skanera kodów QR.", - "tokenLink": "TokenLink", - "feedback": "Informacje zwrotne", - "feedbackTitle": "Twoja opinia jest zawsze mile widziana!", - "feedbackDescription": "Jeśli masz jakieś pytania, sugestie lub problemy, daj nam znać.", - "feedbackHint": "Otworzy się gotowa wiadomość e-mail, którą możesz do nas wysłać. W razie potrzeby dodane zostaną informacje o urządzeniu i wersji aplikacji. Możesz sprawdzić i edytować wiadomość e-mail przed jej wysłaniem.", - "feedbackPrivacyPolicy1": "Wysyłając opinię, zgadzasz się z naszą ", - "feedbackPrivacyPolicy2": "polityką prywatności", - "@feedbackPrivacyPolicy2": { - "description": "Tapnięcie na to powinno otworzyć politykę prywatności." + "@renameTokenFolder": { + "description": "Title of the dialog where a new name for a token folder can be entered." }, - "feedbackPrivacyPolicy3": ".", - "addSystemInfo": "Dodaj informacje systemowe", - "feedbackSentTitle": "Opinia została wysłana", - "feedbackSentDescription": "Dziękujemy bardzo za pomoc w ulepszeniu tej aplikacji!", - "patchNotesDialogTitle": "Co nowego?", - "version": "Wersja", - "noMailAppTitle": "Nie znaleziono aplikacji pocztowej", - "noMailAppDescription": "Na tym urządzeniu nie zainstalowano ani nie zainicjowano aplikacji poczty e-mail, spróbuj ponownie, gdy będziesz w stanie wysłać wiadomość e-mail", - "authenticationRequest": "Żądanie uwierzytelnienia", - "requestInfo": "Wysłane przez {issuer} dla twojego konta: \"{account}\"", "@requestInfo": { + "description": "Description of the authentication request.", "placeholders": { - "issuer": { - "example": "privacyIDEA" - }, "account": { "example": "GitHub" + }, + "issuer": { + "example": "privacyIDEA" } } }, - "errorUnlinkingPushToken": "Nie udało się odłączyć tokenu push {label}.", - "@errorUnlinkingPushToken": { - "description": "Error message when unlinking a push token failed.", + "@requestPushChallengesPeriodically": { + "description": "The description of the polling feature." + }, + "@retry": { + "description": "Label for e.g. a button. Something is tried to be done again." + }, + "@rolloutCompleted": { + "description": "Message for the rollout process" + }, + "@scanQrCode": { + "description": "The button to scan otpauth qr-codes." + }, + "@secretIsRequired": { + "description": "Error message when the secret is missing." + }, + "@secretKey": { + "description": "Describes the field where the tokens secret should be entered." + }, + "@send": { + "description": "Button to send the error log." + }, + "@sendErrorDialogBody": { + "description": "Description shown to the user about what info the error report contains." + }, + "@sendErrorLogDescription": { + "description": "Explanation for the user what he will send." + }, + "@sendPushRequestResponseFailed": { + "description": "Error message when the response to a push request could not be sent." + }, + "@sendingRSAPublicKey": { + "description": "Message for the rollout process" + }, + "@sendingRSAPublicKeyFailed": { + "description": "Message for the rollout process" + }, + "@serverNotReachable": { + "description": "Tells the user that the server could not be reached." + }, + "@settings": { + "description": "Button to open the settings page." + }, + "@showDetails": { + "description": "Button to show details." + }, + "@showErrorLog": { + "description": "Button to show the error log." + }, + "@showPrivacyPolicy": { + "description": "Button to show the privacy policy." + }, + "@signInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "@someTokensDoNotSupportPolling": { + "description": "Tells the user, that the following tokens do not support polling.", + "type": "text" + }, + "@startRollout": { + "description": "Label that tells the user that the token is being rolled out." + }, + "@statusCode": { + "description": "Tells the user the status code of the error.", "placeholders": { - "label": { - "example": "PUSH1234A" + "statusCode": { + "example": "400" } } }, - "pleaseSyncManuallyWhenNetworkIsAvailable": "Zsynchronizuj tokeny push ręcznie za pomocą ustawień, gdy dostępne jest połączenie sieciowe", - "pushTokens": "Tokeny push", - "continueButton": "Kontynuuj", - "addTokenManually": "Dodaj token ręcznie", - "addFolder": "Dodaj folder", - "searchTokens": "Wyszukiwanie tokenów", - "closeSearchTokens": "Zamknij wyszukiwanie", - "increaseCounter": "Zwiększ licznik", - "copyOTPToClipboard": "Kopiowanie OTP do schowka", - "licenses": "Licencja", - "optionalMessage": "Opcjonalna wiadomość", - "confirmation": "potwierdzenie", - "askLogSendedDescription": "Czy wysłałeś dziennik i czy chcesz go teraz wyczyścić?", - "algorithmUnsupported": "Algorytm {algorithm} nie jest obsługiwany", - "@algorithmUnsupported": { + "@sync": { + "description": "Text of button that is used to synchronize push tokens." + }, + "@syncContainerFailed": { + "description": "Error message when synchronizing a container failed." + }, + "@syncFbTokenFailed": { + "description": "Headline for the list of tokens where the synchronization failed." + }, + "@synchronizePushTokens": { + "description": "Title of synchronizing push tokens in settings." + }, + "@synchronizesTokensWithServer": { + "description": "Description of synchronizing push tokens in settings." + }, + "@synchronizingTokens": { + "description": "Title of the push synchronization dialog." + }, + "@systemTheme": { + "description": "The systems theme." + }, + "@theSecretDoesNotFitTheCurrentEncoding": { + "description": "Hint telling the user that the secret does not fit the selected encoding." + }, + "@theme": { + "description": "Title of the setting group where the theme can be selected." + }, + "@themeMode": { + "description": "Title of the setting group where the theme mode can be changed." + }, + "@timeOut": { + "description": "Error message when a request times out." + }, + "@tokenDataParseError": { + "description": "Error message when the token data could not be parsed." + }, + "@tokenDetails": { + "description": "Title of the token details menu." + }, + "@tokenSerial": { + "description": "Label for the token serial number." + }, + "@tokensDoNotSupportSynchronization": { + "description": "Informs the user that the following tokens cannot be synchronized as they do not support that." + }, + "@type": { + "description": "Title of the dropdown button where the type of the token is selected." + }, + "@unexpectedError": { + "description": "Title of page report mode." + }, + "@unknown": { + "description": "Tells that something is unknown." + }, + "@unlock": { + "description": "Description of button that unlocks a token." + }, + "@unsupported": { "placeholders": { - "algorithm": { - "example": "MD5" + "name": { + "example": "piauth version" + }, + "value": { + "example": "5" } } }, - "thisAppIsOpenSource": "Ta aplikacja jest open source\nOdwiedź nas na GitHub", - "invalidArgument": "{argument} nie jest prawidłową wartością dla {type}", - "importExportTokens": "Importuj/Eksportuj tokeny", - "exportNonPrivacyIDEATokens": "Eksportuj tokeny niebędące privacyIDEA", - "selectTokensToExport": "{count, plural, zero{} one{Wybierz token do wyeksportowania} other{Wybierz tokeny do wyeksportowania}}", - "noTokenToExport": "Brak tokena dostępnego do eksportu", - "exportAllTokens": "Eksportuj wszystkie tokeny", - "export": "Eksportuj", - "exportingTokens": "Eksportowanie tokenów...", - "exportTokens": "Eksportuj tokeny", - "enterPasswordToEncrypt": "Wprowadź hasło, aby zaszyfrować tokeny. To hasło będzie wymagane do importu tokenów.", - "exportLockedTokenReason": "Proszę uwierzytelnić się, aby wyeksportować zablokowane tokeny.", - "fileSavedToDownloadsFolder": "Plik zapisano w folderze Pobrane", - "errorSavingFile": "Błąd podczas zapisywania pliku", - "asQrCode": "Jako kod QR", - "asFile": "Jako plik", - "scanThisQrWithNewDevice": "Zeskanuj ten kod QR za pomocą nowego urządzenia, aby zaimportować token.", - "oneMore": "Jeszcze jeden", - "done": "Gotowe", - "confirmPassword": "Potwierdź hasło", - "exampleUrl": "Wprowadź prawidłowy adres URL, np: \"https://example.com/\"", - "pushEndpointUrl": "Adres URL punktu końcowego push", - "mustNotBeEmpty": "{field} nie może być puste", - "@mustNotBeEmpty": { + "@useDeviceLocaleDescription": { + "description": "Description of the switch tile where using the devices language can be enabled." + }, + "@useDeviceLocaleTitle": { + "description": "Title of the switch tile where using the devices language can be enabled." + }, + "@valueNotAllowed": { "placeholders": { - "field": { - "example": "Name" + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "-1" } } }, - "sendPushRequestResponseFailed": "Nie udało się wysłać odpowiedzi.", - "@sendPushRequestResponseFailed": { - "description": "Error message when the response to a push request could not be sent." - }, - "passwordCannotBeEmpty": "Hasło nie może być puste", - "passwordMustBeAtLeast8Characters": "Hasło musi mieć co najmniej 8 znaków", + "@valueNotAllowedIn": { + "placeholders": { + "map": { + "example": "query parameters" + }, + "parameter": { + "example": "counter" + }, + "type": { + "example": "int" + }, + "value": { + "example": "-1" + } + } + }, + "@verboseLogging": { + "description": "Title of the switch tile where verbose logging can be enabled." + }, + "accept": "Potwierdzam", + "addANewFolder": "Utwórz nowy folder", + "addFolder": "Dodaj folder", + "addSystemInfo": "Dodaj informacje systemowe", + "addToken": "Dodaj token", + "addTokenManually": "Dodaj token ręcznie", + "algorithm": "Algorytm", + "algorithmUnsupported": "Algorytm {algorithm} nie jest obsługiwany", + "allTokensSynchronized": "Wszystkie tokeny są zsynchronizowane.", + "asFile": "Jako plik", + "asQrCode": "Jako kod QR", + "askLogSendedDescription": "Czy wysłałeś dziennik i czy chcesz go teraz wyczyścić?", + "authNotSupportedBody": "To działanie wymaga skonfigurowania ustawień zabezpieczeń albo uwierzytelniania biometrycznego.", + "authNotSupportedTitle": "Skonfigurowane ustawienia zabezpieczeń albo uwierzytelnianie biometryczne jest wymagane.", + "authToAcceptPushRequest": "Uwierzytelnij, aby zaakceptować żądanie push.", + "authToDeclinePushRequest": "Uwierzytelnij, aby odrzucić żądanie push.", + "authenticateToShowOtp": "Zweryfikuj tożsamość, by pokazać hasło jednorazowe.", + "authenticateToUnLockToken": "Zweryfikuj tożsamość, aby odblokować / zablokować token.", + "authenticationRequest": "Żądanie uwierzytelnienia", + "biometricHint": "Wymagana autentykacja", + "biometricNotRecognized": "Nie rozpoznano. Spróbuj ponownie.", + "biometricRequiredTitle": "Uwierzytelnianie biometryczne nie jest skonfigurowane.", + "biometricSuccess": "Autentykacja zakończona sukcesem!", + "butDiscardIt": "ale odrzucić go", + "cancel": "Anuluj", + "checkServerCertificate": "Sprawdź certyfikat serwera", + "checkYourNetwork": "Sprawdź połączenie sieciowe i spróbuj ponownie.", + "clearErrorLog": "Usuń", + "closeSearchTokens": "Zamknij wyszukiwanie", + "confirmDeletion": "Potwierdź usunięcie", + "confirmDeletionOf": "Jesteś pewien, że chcesz usunąć token: {name}?", + "confirmFolderDeletionHint": "Usunięcie folderu nie ma wpływu na znajdujące się w nim tokeny. Tokeny są przenoszone do głównej listy.", + "confirmPassword": "Potwierdź hasło", + "confirmTokenDeletionHint": "Usunięcie tego tokenu może uniemożliwić zalogowanie się. Upewnij się, że możesz zalogować się na powiązane konto bez tego tokenu.", + "confirmation": "potwierdzenie", + "connectionFailed": "Połączenie nie powiodło się.", + "container": "Pojemnik", + "continueButton": "Kontynuuj", + "copyOTPToClipboard": "Kopiowanie OTP do schowka", + "couldNotConnectToServer": "Nie można połączyć się z serwerem.", + "couldNotSignMessage": "Nie można podpisać wiadomości.", + "counter": "Licznik", + "create": "Utwórz", + "createdAt": "Utworzono dnia", + "creator": "Twórca", + "decline": "Odrzucam", + "declineIt": "odrzuć go", + "decrypt": "Odszyfruj", + "decryptErrorButtonDelete": "Usuń", + "decryptErrorButtonRetry": "Ponów próbę", + "decryptErrorButtonSendError": "Wyślij błąd", + "decryptErrorContent": "Niestety, aplikacja nie była w stanie odszyfrować tokenów. Oznacza to, że klucz szyfrowania jest uszkodzony. Możesz spróbować ponownie lub usunąć dane aplikacji, co spowoduje usunięcie tokenów w aplikacji.", + "decryptErrorDeleteConfirmationContent": "Czy na pewno chcesz usunąć dane aplikacji?", + "decryptErrorTitle": "Decryption error", + "delete": "Usuń", + "deleteLockedToken": "Uwierzytelnij, aby usunąć zablokowany token.", + "deviceCredentialsRequiredTitle": "Ustawienia zabezpieczeń urządzenia nie zostały skonfigurowane.", + "deviceCredentialsSetupDescription": "Skonfiguruj ustawienia zabezpieczeń w ustawieniach urządzenia.", + "digits": "Ilość cyfr", + "dismiss": "Odrzuć", + "done": "Gotowe", + "edit": "Edytuj", + "editLockedToken": "Aby edytować zablokowany token, należy się uwierzytelnić.", + "editToken": "Edytuj token", + "enablePolling": "Włącz autentykację przez wiadomość push.", + "encoding": "Kodowanie", + "enterDetailsForToken": "Wprowadź szczegóły dla tokenu", + "enterLink": "Wprowadź link", + "enterPasswordToEncrypt": "Wprowadź hasło, aby zaszyfrować tokeny. To hasło będzie wymagane do importu tokenów.", + "errorLogCleared": "Dziennik błędów wyczyszczony.", + "errorLogEmpty": "Dziennik błędów jest pusty", + "errorLogTitle": "Dziennik błędów", + "errorMailBody": "Plik dziennika błędów jest dołączony.\nTekst ten można zastąpić dodatkowymi informacjami o błędzie.", + "errorRollOutFailed": "Wdrażanie tokenu {name} nieudane.", + "errorRollOutNoConnectionToServer": "Brak połączenia z serwerem", + "errorRollOutNotPossibleAnymore": "Wstać z łóżka tego tokena nie jest już możliwe.", + "errorRollOutSSLHandshakeFailed": "Uścisk dłoni SSL nie powiódł się. Rozwijanie nie jest możliwe.", + "errorRollOutUnknownError": "Napotkano nieznany błąd. Wdrożenie tokenu niemożliwe: {e}", + "errorSavingFile": "Błąd podczas zapisywania pliku", + "errorSynchronizationNoNetworkConnection": "Synchronizacja tokenów push nieudana, ponieważ serwer privacyIDEA jest nieosiągalny.", + "errorTokenExpired": "Token {name} wygasł.", + "errorUnlinkingPushToken": "Nie udało się odłączyć tokenu push {label}.", + "errorWhenPullingChallenges": "Wystąpił błąd podczas odpytywania o wyzwania {name}", + "exampleUrl": "Wprowadź prawidłowy adres URL, np: \"https://example.com/\"", + "expandLockedFolder": "Uwierzytelnij, aby otworzyć zablokowany folder.", + "export": "Eksportuj", + "exportAllTokens": "Eksportuj wszystkie tokeny", + "exportLockedTokenReason": "Proszę uwierzytelnić się, aby wyeksportować zablokowane tokeny.", + "exportNonPrivacyIDEATokens": "Eksportuj tokeny niebędące privacyIDEA", + "exportTokens": "Eksportuj tokeny", + "exportingTokens": "Eksportowanie tokenów...", + "failedToLoad": "Nie udało się załadować:", + "feedback": "Informacje zwrotne", + "feedbackDescription": "Jeśli masz jakieś pytania, sugestie lub problemy, daj nam znać.", + "feedbackHint": "Otworzy się gotowa wiadomość e-mail, którą możesz do nas wysłać. W razie potrzeby dodane zostaną informacje o urządzeniu i wersji aplikacji. Możesz sprawdzić i edytować wiadomość e-mail przed jej wysłaniem.", + "feedbackPrivacyPolicy1": "Wysyłając opinię, zgadzasz się z naszą ", + "feedbackPrivacyPolicy2": "polityką prywatności", + "feedbackPrivacyPolicy3": ".", + "feedbackSentDescription": "Dziękujemy bardzo za pomoc w ulepszeniu tej aplikacji!", + "feedbackSentTitle": "Opinia została wysłana", + "feedbackTitle": "Twoja opinia jest zawsze mile widziana!", + "fileSavedToDownloadsFolder": "Plik zapisano w folderze Pobrane", + "findingQrCodeInImage": "Szukanie kodu QR na obrazku...", + "firebaseToken": "Token Firebase", + "folderName": "Nazwa folderu", + "generatingPhonePart": "Generowanie sekretu po stronie telefonu...", + "generatingRSAKeyPair": "Generowanie pary kluczy RSA", + "generatingRSAKeyPairFailed": "Generowanie pary kluczy RSA nieudane", + "goToSettingsButton": "Idź do ustawień", + "goToSettingsDescription": "Ustawienia zabezpieczeń, bądź uwierzytelnianie biometryczne nie są skonfigurowane w twoim urządzeniu. Skonfiguruj je w ustawieniach urządzenia.", + "grantCameraPermissionDialogButton": "Grant permission", + "grantCameraPermissionDialogContent": "Przyznaj uprawnienia kamery do skanowania kodów QR.", + "grantCameraPermissionDialogPermanentlyDenied": "Uprawnienia do aparatu zostały trwale zablokowane. Przyznaj uprawnienia aparatu w ustawieniach telefonu.", + "grantCameraPermissionDialogTitle": "Uprawnienie do kamery nie zostało przyznane", + "handshakeFailed": "Handshake nie powiódł się", + "hidePushTokens": "Ukryj tokeny push", + "hidePushTokensDescription": "Ukryj tokeny push z listy tokenów. Nie spowoduje to usunięcia tokenów i będą one nadal widoczne na osobnym ekranie", + "imageUrl": "Adres URL obrazu", + "importConflictToken": "{count, plural, zero{Nie ma konfliktu z tokenami, które już istnieją.} one{Istnieje konflikt z tokenami, które już istnieją.} other{Istnieje konflikt z tokenami, które już istnieją.}}", + "importExistingToken": "{count, plural, zero{Nie znaleziono tokena, który już znajduje się w aplikacji.} one{Znaleziono token, który już istnieje w aplikacji.} other{Znaleziono {count} tokenów, które już znajdują się w aplikacji.}}", + "importExportTokens": "Importuj/Eksportuj tokeny", + "importFailedToken": "{count, plural, zero{Nie udało się zaimportować tokenu.} one{Nie udało się zaimportować tokena.} other{Nie udało się zaimportować {count} tokenów.}}", + "importHint2FAS": "Wybierz kopię zapasową 2FAS. Jeśli nie masz kopii zapasowej, utwórz ją w aplikacji 2FAS. Zalecamy użycie hasła.", + "importHintAegisBackupFile": "Wybierz swój eksport Aegis (.JSON).\nJeśli nie masz eksportu, utwórz go za pomocą menu ustawień w aplikacji Aegis. Zalecane jest użycie hasła.", + "importHintAegisLink": "Wprowadź link otrzymany podczas przesyłania wpisów z Aegis.", + "importHintAegisQrScan": "Zeskanuj kod QR otrzymany podczas przesyłania wpisów z Aegis", + "importHintAuthenticatorProFile": "Aby utworzyć kopię zapasową aplikacji Authenticator Pro, przejdź do ustawień i dotknij \"Automatyczna kopia zapasowa\". Wybierz lokalizację przechowywania i ustaw hasło. Następnie naciśnij \"Utwórz teraz kopię zapasową\", aby wyeksportować tokeny.", + "importHintFreeOtpPlusFile": "Aby utworzyć kopię zapasową aplikacji FreeOTP+, dotknij trzech kropek w prawym górnym rogu i wybierz \"Eksportuj\". Można wybrać format JSON lub URI. Zalecamy usunięcie kopii zapasowej po jej zaimportowaniu, ponieważ nie jest ona szyfrowana.", + "importHintFreeOtpPlusQrScan": "Zeskanuj kod QR otrzymany po naciśnięciu trzech kropek na kafelku tokena i wybierz \"Udostępnij kod QR\".", + "importHintGoogleQrFile": "Wybierz plik obrazu z kodem QR otrzymanym podczas eksportowania kont z Google Authenticator.\n!! Należy pamiętać, że zapisywanie kodu QR na urządzeniu nie jest bezpieczne, ponieważ tokeny nie są szyfrowane !!", + "importHintGoogleQrScan": "Zeskanuj kod QR otrzymany podczas eksportowania kont z Google Authenticator", + "importHintPrivacyIdeaFile": "Aby utworzyć kopię zapasową, przejdź do ustawień i wybierz \"Eksportuj\". Wybierz \"Jako plik\", wybierz tokeny, które chcesz wyeksportować. Następnie wybierz \"Eksportuj\" i ustaw hasło. Miejscem przechowywania jest folder pobierania na urządzeniu.", + "importHintPrivacyIdeaQrScan": "Aby utworzyć kody QR tokenów, przejdź do ustawień i wybierz opcję \"Eksportuj\". Następnie wybierz opcję \"Jako kod QR\" i dotknij tokenu, który ma zostać wyeksportowany. Ten wariant jest odpowiedni tylko do bezpośredniego przesyłania na inne urządzenie, ponieważ kod QR nie jest szyfrowany.", + "importNTokens": "{count, plural, zero{Nie importuj tokenów} one{Importuj jeden token} other{Import {count} tokenów}}", + "importNewToken": "{count, plural, zero{Nie znaleziono nowego tokena.} one{Znaleziono nowy token, który można zaimportować.} other{Znaleziono {count} nowych tokenów, które można zaimportować.}}", + "importTokens": "Token importu", + "importedVia": "Importowane przez", + "increaseCounter": "Zwiększ licznik", + "internalServerError": "Wewnętrzny błąd serwera ({code})", + "introAddFolder": "Możesz tworzyć foldery, aby organizować swoje tokeny.", + "introAddTokenManually": "Jeśli nie chcesz skanować kodu QR, możesz również dodać tokeny ręcznie.", + "introDragToken": "Reorganizuj swoje tokeny, naciskając je przez kilka sekund, a następnie przeciągając je do żądanej pozycji.", + "introEditToken": "Tutaj możesz edytować nazwę tokena i zobaczyć kilka szczegółów.", + "introHidePushTokens": "Twoje tokeny push są teraz ukryte, ale nadal możesz je zobaczyć na ekranie tokenów push.", + "introLockToken": "Aby jeszcze bardziej zwiększyć bezpieczeństwo, możesz zablokować tokeny. Wtedy token może być używany tylko po uwierzytelnieniu.", + "introPollForChallenges": "Możesz sprawdzić nowe wyzwania, przeciągając listę tokenów w dół.", + "introScanQrCode": "Możesz skanować kody QR, aby dodawać tokeny. Obsługujemy każdy popularny token uwierzytelniania dwuskładnikowego, a także tokeny privacyIDEA.", + "introTokenSwipe": "Przesuń tokeny w lewo, aby zobaczyć dostępne akcje.", + "invalidBackupFile": "Wybrany plik nie jest prawidłową kopią zapasową {appName}.", + "invalidLink": "Wprowadzony link nie jest prawidłowym tokenem {appName} lub nie jest obsługiwany.", + "invalidQrFile": "Wybrany plik nie zawiera prawidłowego kodu QR z {appName}.", + "invalidQrScan": "Zeskanowany kod QR nie jest prawidłową kopią zapasową {appName}.", + "invalidValue": "untranslated", + "invalidValueIn": "untranslated", + "isExpotableQuestion": "Czy można eksportować?", + "isPiTokenQuestion": "To jest token privacyIDEA?", + "language": "Język", + "legacySigningErrorMessage": "Token został utworzony w nieaktualnej wersji aplikacji, co może prowadzić do problemów podczas korzystania z niego.\nZaleca się utworzenie nowego tokena push, jeśli problem nadal występuje!", + "legacySigningErrorTitle": "Wystąpił błąd podczas korzystania z przestarzałego tokena: {tokenLabel}", + "licenses": "Licencja", + "licensesAndVersion": "Licencje i wersja", + "linkedContainer": "Połączony pojemnik", + "lock": "Zablokuj", + "lockOut": "Uwierzytelnianie biometryczne jest wyłączone. Zablokuj i odblokuj ponownie ekran, żeby je włączyć.", + "logMenu": "Menu dziennika", + "malformedData": "Nieprawidłowe dane", + "markQrCode": "Zaznacz kod QR", + "missingRequiredParameter": "Wartość parametru [{parameter}] jest wymagana, ale jej brakuje.", + "missingRequiredParameterIn": "Wartość parametru [{parameter}] jest wymagana, ale brakuje jej w \"{map}\".", + "mustNotBeEmpty": "{field} nie może być puste", + "name": "Nazwa", + "no": "Nie", + "noFbToken": "Brak dostępnego tokena Firebase", + "noMailAppDescription": "Na tym urządzeniu nie zainstalowano ani nie zainicjowano aplikacji poczty e-mail, spróbuj ponownie, gdy będziesz w stanie wysłać wiadomość e-mail", + "noMailAppTitle": "Nie znaleziono aplikacji pocztowej", + "noNetworkConnection": "Brak połączenia sieciowego.", + "noPublicKey": "Brak dostępnego klucza publicznego", + "noResultText1": "Dotknij ", + "noResultText2": " przycisku, żeby zacząć!", + "noResultTitle": "Nie zainstalowano jeszcze żadnego tokenu.", + "noTokenToExport": "Brak tokena dostępnego do eksportu", + "notAnInteger": "Wartość nie jest liczbą całkowitą.Wartość nie jest liczbą całkowitą.", + "notAnNumber": "Wartość nie jest liczbą.", + "ok": "Ok", + "oneMore": "Jeszcze jeden", + "open": "Otwórz", + "optionalMessage": "Opcjonalna wiadomość", + "originApp": "Aplikacja Origin", + "originDetails": "Szczegóły dotyczące pochodzenia", + "otpValueCopiedMessage": "Jednorazowe hasło \"{otpValue}\" skopiowane do schowka.", + "parsingResponse": "Analizowanie odpowiedzi", + "parsingResponseFailed": "Analizowanie odpowiedzi nieudane", + "password": "Hasło", + "passwordCannotBeEmpty": "Hasło nie może być puste", "passwordCannotContainWhitespace": "Hasło nie może zawierać spacji", + "passwordMustBeAtLeast8Characters": "Hasło musi mieć co najmniej 8 znaków", "passwordMustContainLowercaseLetter": "Hasło musi zawierać małą literę", - "passwordMustContainUppercaseLetter": "Hasło musi zawierać dużą literę", "passwordMustContainNumber": "Hasło musi zawierać cyfrę", "passwordMustContainSpecialCharacter": "Hasło musi zawierać znak specjalny", + "passwordMustContainUppercaseLetter": "Hasło musi zawierać dużą literę", "passwordsDoNotMatch": "Hasła nie pasują do siebie", - "selectTokensToExportHelpTitle": "Czy twój token nie znajduje się na liście?", - "selectTokensToExportHelpContent": "Jeśli token nie znajduje się na liście, nie ma gwarancji, że nie jest tokenem privacyIDEA.\nObecnie eksportować można tylko tokeny dodane ręcznie i zaimportowane.", - "findingQrCodeInImage": "Szukanie kodu QR na obrazku...", - "qrNotFound": "Nie znaleziono kodu QR!", + "patchNotesBugFixes": "Poprawki błędów", + "patchNotesDialogTitle": "Co nowego?", + "patchNotesImprovements": "Ulepszenia", + "patchNotesNewFeatures": "Nowe funkcje", + "patchNotesV4_3_0NewFeatures1": "Dodano obsługę importowania tokenów z Google, Aegis i 2FAS Authenticator. Więcej źródeł importu zostanie dodanych w przyszłości", + "patchNotesV4_3_0NewFeatures2": "Dodano opcję opinii do ustawień.", + "patchNotesV4_3_0NewFeatures3": "Tokeny push można teraz ukryć na liście tokenów", + "patchNotesV4_3_0NewFeatures4": "Dodano wprowadzenia, aby ułatwić nowym użytkownikom rozpoczęcie pracy.", + "patchNotesV4_3_0NewFeatures5": "Możesz teraz wyszukiwać tokeny, dotykając szkła powiększającego w prawym górnym rogu.", + "patchNotesV4_3_0NewFeatures6": "Dodano token HomeWidget dla systemu Android 12 i nowszych.", + "patchNotesV4_3_1BugFix1": "Został naprawiony problem, w którym wartość OTP nie była wyświetlana po uwierzytelnieniu na niektórych urządzeniach.", + "patchNotesV4_3_1Improvement1": "Został poprawiony skaner kodów QR.", + "patchNotesV4_4_0Improvement1": "Dodano kolejne źródła importu.", + "patchNotesV4_4_0Improvement2": "Ulepszono rozpoznawanie kodów QR z plików graficznych.", + "patchNotesV4_4_0NewFeatures1": "Możliwe jest teraz eksportowanie tokenów, w przypadku których można upewnić się, że nie są to tokeny privacyIDEA. Obecnie nie można wykluczyć, że tokeny dodane za pomocą skanera kodów QR pochodzą z privacyIDEA. Rozróżnienie to zostanie poprawione w przyszłych wersjach", + "patchNotesV4_4_0NewFeatures2": "Dodano obsługę funkcji privacyIDEA \"require presence\".", + "period": "Cykl", + "phonePart": "Sekret po stronie telefonu:", + "pleaseEnterANameForThisToken": "Wprowadź nazwę dla tokenu", + "pleaseEnterASecretForThisToken": "Wprowadź sekret dla tokenu", + "pleaseSyncManuallyWhenNetworkIsAvailable": "Zsynchronizuj tokeny push ręcznie za pomocą ustawień, gdy dostępne jest połączenie sieciowe", + "pollingChallenges": "Sprawdzanie nowych wyzwań", + "pollingFailed": "Zapytanie nie powiodło się.", + "pollingFailedFor": "Zapytanie dla {serial} nie powiodło się.", + "privacyPolicy": "Polityka prywatności", + "publicKey": "Klucz publiczny", + "pushEndpointUrl": "Adres URL punktu końcowego push", + "pushRequestParseError": "Żądanie push nie mogło zostać przetworzone.", + "pushToken": "Push token", + "pushTokens": "Tokeny push", + "qrFileDecodeError": "Nie można było zdekodować kodu QR z wybranego obrazu, zamiast tego użyj skanera kodów QR.", "qrInFileNotFound": "Nie znaleziono kodu QR na wybranym obrazie.", "qrInFileNotFound2": "Możesz pokazać mi, gdzie znajduje się kod QR.", "qrInFileNotFound3": "Oczekuję, że znajdę kod, jeśli znajduje się w środku zaznaczonego obszaru.", - "markQrCode": "Zaznacz kod QR", - "malformedData": "Nieprawidłowe dane" + "qrNotFound": "Nie znaleziono kodu QR!", + "qrScan": "Scan", + "rename": "Zmień nazwę", + "renameToken": "Zmień nazwę tokenu", + "renameTokenFolder": "Zmiana nazwy folderu", + "requestInfo": "Wysłane przez {issuer} dla twojego konta: \"{account}\"", + "requestPushChallengesPeriodically": "Wysyłaj zapytanie o push challenge cyklicznie. Włącz, jeśli push nie przychodzi normalnie.", + "requestTriggerdByUserQuestion": "Czy ta prośba została wywołana przez Ciebie?", + "retryRollout": "Ponowne uruchomienie", + "rolloutCompleted": "Wdrożenie zakończone", + "save": "Zapisz", + "scanQrCode": "Zeskanuj kod QR", + "scanThisQrWithNewDevice": "Zeskanuj ten kod QR za pomocą nowego urządzenia, aby zaimportować token.", + "searchTokens": "Wyszukiwanie tokenów", + "secretIsRequired": "Wymagany jest sekret", + "secretKey": "Tajny klucz", + "selectFile": "Wybierz plik", + "selectImportSource": "Wybierz źródło importu", + "selectImportType": "Jak chcesz zaimportować tokeny?", + "selectTokensToExport": "{count, plural, zero{} one{Wybierz token do wyeksportowania} other{Wybierz tokeny do wyeksportowania}}", + "selectTokensToExportHelpContent": "Jeśli token nie znajduje się na liście, nie ma gwarancji, że nie jest tokenem privacyIDEA.\nObecnie eksportować można tylko tokeny dodane ręcznie i zaimportowane.", + "selectTokensToExportHelpTitle": "Czy twój token nie znajduje się na liście?", + "send": "Wyślij", + "sendErrorDialogBody": "Napotkano nieoczekiwany błąd w aplikacji. Poniższa wiadomość może zostać wysłana do deweloperów poprzez email, żeby pomóc uniknąć tego problemu w przyszłości.", + "sendErrorLogDescription": "Tworzona jest gotowa wiadomość e-mail zawierająca informacje o aplikacji, błędzie i urządzeniu.\nMożesz edytować wiadomość e-mail przed jej wysłaniem.\nTutaj można zobaczyć, w jaki sposób wykorzystujemy te informacje:", + "sendPushRequestResponseFailed": "Nie udało się wysłać odpowiedzi.", + "sendingRSAPublicKey": "Wysyłanie publicznego klucza RSA", + "sendingRSAPublicKeyFailed": "Wysyłanie publicznego klucza RSA nieudane", + "serverNotReachable": "Nie można uzyskać połączenia z serwerem.", + "settings": "Ustawienia", + "settingsGroupGeneral": "Informacje ogólne", + "showDetails": "Pokaż szczegóły", + "showErrorLog": "Wyświetl", + "showPrivacyPolicy": "Pokaż politykę prywatności", + "signInTitle": "Wymagana autentykacja", + "someTokensDoNotSupportPolling": "Część tokenów jest przestarzała i nie wspiera aktywnego zapytania dla autentykacji przez wiadomość push.", + "startRollout": "Rozpocznij wdrożenie", + "statusCode": "Kod statusu: {statusCode}", + "sync": "Synchronizuj", + "syncContainerFailed": "Synchronizacja kontenera nie powiodła się", + "syncFbTokenFailed": "Synchronizacja dla poniższych tokenów się nie udała, spróbuj ponownie:", + "synchronizePushTokens": "Synchronizuj tokeny push.", + "synchronizesTokensWithServer": "Synchronizuje tokeny push z serwerem privacyIDEA.", + "synchronizingTokens": "Synchronizacja tokenów.", + "theSecretDoesNotFitTheCurrentEncoding": "Sekret nie odpowiada wybranemu sposobowi kodowania.", + "themeMode": "Motyw", + "thisAppIsOpenSource": "Ta aplikacja jest open source\nOdwiedź nas na GitHub", + "timeOut": "Limit czasu", + "tokenDataParseError": "Nie można przeanalizować danych tokenu", + "tokenDetails": "Szczegóły tokena", + "tokenLink": "TokenLink", + "tokenSerial": "Token serial", + "tokensAreEncrypted": "Tokeny są zaszyfrowane. Wprowadź hasło, aby je odszyfrować", + "tokensDoNotSupportSynchronization": "Następujące tokeny nie wspierają synchronizacji i muszą zostać wdrożone od nowa:", + "tokensNotEncrypted": "Tokeny nie są zaszyfrowane i mogą być importowane bezpośrednio.", + "tokensSuccessfullyDecrypted": "Tokeny zostały pomyślnie odszyfrowane i można je teraz zaimportować.", + "type": "Typ", + "unexpectedError": "Wystąpił nieoczekiwany błąd.", + "unknown": "Nieznany", + "unlock": "Odblokuj", + "unsupported": "Opcja {name} [{value}] nie jest obsługiwana przez tę wersję aplikacji.", + "useDeviceLocaleDescription": "Użyj języka urządzenia, jeśli jest wspierany. W innym wypadku zostanie ustawiony domyślny język angielski.", + "useDeviceLocaleTitle": "Użyj języka urządzenia.", + "validFor": "Ważny przez", + "validUntil": "Ważny do", + "valueNotAllowed": "untranslated", + "valueNotAllowedIn": "untranslated", + "verboseLogging": "Wyczerpujące rejestrowanie", + "version": "Wersja", + "wrongPassword": "Nieprawidłowe hasło", + "yes": "Tak" } \ No newline at end of file diff --git a/lib/mains/main_customizer.dart b/lib/mains/main_customizer.dart index a0a952932..6bae864ce 100644 --- a/lib/mains/main_customizer.dart +++ b/lib/mains/main_customizer.dart @@ -88,6 +88,7 @@ class CustomizationAuthenticator extends ConsumerWidget { ), MainView.routeName: (context) => MainView( appIcon: applicationCustomizer.appIcon.getWidget, + appImage: applicationCustomizer.appImage.getWidget, appName: applicationCustomizer.appName, disablePatchNotes: applicationCustomizer.disabledFeatures.contains(AppFeature.patchNotes), appConstraints: constraints, diff --git a/lib/mains/main_netknights.dart b/lib/mains/main_netknights.dart index 8d104e280..b949aee7b 100644 --- a/lib/mains/main_netknights.dart +++ b/lib/mains/main_netknights.dart @@ -110,6 +110,7 @@ class PrivacyIDEAAuthenticator extends ConsumerWidget { ), MainView.routeName: (context) => MainView( appIcon: _customization.appIcon.getWidget, + appImage: _customization.appImage.getWidget, appName: _customization.appName, disablePatchNotes: _customization.disabledFeatures.contains(AppFeature.patchNotes), appConstraints: constraints, diff --git a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart deleted file mode 100644 index 8d2acb345..000000000 --- a/lib/repo/token_container_state_repositorys/hybrid_token_container_state_repository.dart +++ /dev/null @@ -1,190 +0,0 @@ -// /* -// * privacyIDEA Authenticator -// * -// * Author: Frank Merkel -// * -// * Copyright (c) 2024 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 '../../interfaces/repo/container_repository.dart'; -// import '../../model/token_container.dart'; -// import '../../utils/errors.dart'; -// import '../../utils/logger.dart'; - -// class HybridTokenContainerRepository -// implements TokenContainerRepository { -// final LocalRepo _localRepository; -// final RemoteRepo _remoteRepository; - -// HybridTokenContainerRepository({ -// required LocalRepo localRepository, -// required RemoteRepo remoteRepository, -// }) : _localRepository = localRepository, -// _remoteRepository = remoteRepository; - -// @override -// Future loadContainerState({bool isInitial = false}) async { -// Logger.warning('Loading container state', name: 'HybridTokenContainerRepository'); -// TokenContainer localState; -// TokenContainer newState; - -// try { -// localState = await _localRepository.loadContainerState(); -// } catch (e) { -// Logger.warning('Failed to load local container state'); -// return TokenContainer.error( -// error: LocalizedException( -// unlocalizedMessage: 'Failed to load local container state', -// localizedMessage: (localization) => localization.failedToLoad('local container state'), -// ), -// ); -// } -// try { -// newState = await _remoteRepository.saveContainerState(localState); -// } catch (e) { -// newState = localState.copyTransformInto(); -// } - -// try { -// await _localRepository.saveContainerState(newState); -// } catch (e) { -// Logger.error('Failed to save synced state to local repository', name: 'HybridTokenContainerRepository', error: e); -// } - -// return newState; -// } - -// @override -// Future saveContainerState(TokenContainer currentState) async { -// if (currentState is TokenContainerError) { -// Logger.warning('Cannot save error state to repository'); -// return currentState; -// } -// TokenContainer newState; - -// try { -// newState = await _remoteRepository.saveContainerState(currentState); -// } catch (e, s) { -// Logger.warning( -// 'Failed to save state to remote repository: Changed to unsynced state', -// name: 'HybridTokenContainerRepository#saveContainerState', -// error: e, -// stackTrace: s, -// ); -// newState = currentState.copyTransformInto(); -// return _localRepository.saveContainerState(newState); -// } - -// try { -// newState = await _localRepository.saveContainerState(newState); -// } catch (e) { -// Logger.error('Failed to save state to local repository'); -// return newState; -// } -// return newState; -// } - -// // Future _merge({ -// // required TokenContainer localState, -// // required TokenContainer remoteState, -// // }) async { -// // List localTemplates; -// // List remoteTemplates; -// // if (localState is TokenContainerUninitialized) { -// // // Uninitialized state is always overwritten by other states -// // localTemplates = []; -// // } else { -// // localTemplates = localState.tokenTemplates; -// // } -// // if (remoteState is TokenContainerUninitialized) { -// // // Uninitialized state is always overwritten by other states -// // remoteTemplates = []; -// // } else { -// // remoteTemplates = remoteState.tokenTemplates ?? []; -// // } - -// // final mergedTemplates = await _mergeTemplateLists( -// // localTemplates: localTemplates, -// // remoteTemplates: remoteTemplates, -// // ); - -// // final newSyncedState = TokenContainerSynced( -// // lastSyncedAt: DateTime.now(), -// // containerId: localState.containerId, -// // description: localState.description, -// // type: '', // TODO: Implement type -// // tokenTemplates: mergedTemplates, -// // ); -// // return newSyncedState; -// // } - -// // Future> _mergeTemplateLists({ -// // required List localTemplates, -// // required List remoteTemplates, -// // }) async { -// // final mergedTemplates = []; - -// // // Add all remaining local templates -// // for (var localTemplate in localTemplates) { -// // final remoteTemplate = remoteTemplates.firstWhereOrNull((template) => template.id == localTemplate.id); -// // if (remoteTemplate == null) { -// // mergedTemplates.add(localTemplate); -// // continue; -// // } -// // mergedTemplates.add(await _mergeTemplates(localTemplate, remoteTemplate)); -// // } - -// // // Add all remaining remote templates -// // mergedTemplates.addAll(remoteTemplates); - -// // return mergedTemplates; -// // } - -// /// Merges local and remote token templates with the last synced state -// /// If both local and remote templates have changed, the remote changes are prioritized -// // Future _mergeTemplates(TokenTemplate local, TokenTemplate remote) async { -// // assert(local.id == remote.id, 'Both templates must have the same id'); -// // final mergedData = {}; - -// // print('------------------------------------------------------------------'); -// // print('Local: ${local.data}'); -// // mergedData.addAll(local.data); -// // print('MergedData: $mergedData'); -// // print('------------------------------------------------------------------'); -// // print('Remote: ${remote.data}'); -// // mergedData.addAll(remote.data); -// // print('MergedData: $mergedData'); -// // print('------------------------------------------------------------------'); - -// // return TokenTemplate(data: mergedData); -// // } - -// // @override -// // Future loadTokenTemplate(String tokenTemplateId) async { -// // final state = await loadContainerState(); -// // final template = state.tokenTemplates.firstWhereOrNull((template) => template.id == tokenTemplateId); -// // return template; -// // } - -// // @override -// // Future saveTokenTemplate(TokenTemplate tokenTemplate) async { -// // final state = await loadContainerState(); -// // final templates = state.tokenTemplates; -// // templates.removeWhere((template) => template.id == tokenTemplate.id); -// // templates.add(tokenTemplate); -// // final newState = state.copyWith(tokenTemplates: templates); -// // final savedState = await saveContainerState(newState); -// // return savedState.tokenTemplates.firstWhere((template) => template.id == tokenTemplate.id); -// // } -// } diff --git a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart b/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart deleted file mode 100644 index adfa078c7..000000000 --- a/lib/repo/token_container_state_repositorys/remote_token_container_state_repository.dart +++ /dev/null @@ -1,50 +0,0 @@ -// /* -// * privacyIDEA Authenticator -// * -// * Author: Frank Merkel -// * -// * Copyright (c) 2024 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:mutex/mutex.dart'; -// import '../../utils/logger.dart'; - -// import '../../api/token_container_api_endpoint.dart'; -// import '../../interfaces/repo/container_repository.dart'; -// import '../../model/token_container.dart'; - -// class RemoteTokenContainerRepository implements TokenContainerRepository { -// final TokenContainerApiEndpoint apiEndpoint; -// final Mutex _m = Mutex(); - -// Future _protect(Future Function() f) async => _m.protect(f); - -// RemoteTokenContainerRepository({required this.apiEndpoint}); - -// @override -// Future saveContainerState(TokenContainer containerState) async => await _saveContainerState(containerState); - -// Future _saveContainerState(TokenContainer containerState) async { -// Logger.info('Saving container state', name: 'RemoteTokenContainerRepository'); -// return await _protect(() async => (await apiEndpoint.sync(containerState)).copyTransformInto()); -// } - -// @override -// Future loadContainerState() { -// Logger.info('Loading container state', name: 'RemoteTokenContainerRepository'); -// return _fetchContainerState(); -// } - -// Future _fetchContainerState() async => await _protect(() async => await apiEndpoint.fetch()); -// } diff --git a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart b/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart deleted file mode 100644 index 3ea0b7ccd..000000000 --- a/lib/repo/token_container_state_repositorys/secure_token_container_state_repository.dart.dart +++ /dev/null @@ -1,99 +0,0 @@ -// /* -// * privacyIDEA Authenticator -// * -// * Author: Frank Merkel -// * -// * Copyright (c) 2024 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:convert'; - -// import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -// import 'package:mutex/mutex.dart'; -// import '../../utils/logger.dart'; -// import '../../interfaces/repo/container_repository.dart'; -// import '../../model/token_container.dart'; - -// class SecureTokenContainerRepository implements TokenContainerRepository { -// static String prefix = 'token_container_state_'; -// String get _containerStateKey => '$prefix${containerId}_container_state'; -// final Mutex _m = Mutex(); -// Future _protect(Future Function() f) => _m.protect(f); -// final FlutterSecureStorage _storage = const FlutterSecureStorage(); - -// final String containerId; - -// SecureTokenContainerRepository({ -// required this.containerId, -// }); - -// Future _write(String key, String value) => _protect(() { -// Logger.debug('Writing key: $key, value: $value', name: 'secure_token_container_state_repository.dart.dart#_write'); -// return _storage.write(key: key, value: value); -// }); -// Future _read(String key) async { -// final value = await _protect(() async => await _storage.read(key: key)); -// Logger.debug('Reading key: $key, value: $value', name: 'secure_token_container_state_repository.dart.dart#_read'); -// return value; -// } - -// Future _delete(String key) => _protect(() => _storage.delete(key: key)); -// // Future> _readAll() async { -// // Map? keys; -// // await _protect(() async => keys = await _storage.readAll()); -// // keys!.removeWhere((key, value) => !key.startsWith(prefix + containerId)); -// // return keys!; -// // } - -// @override -// Future saveContainerState(TokenContainer containerState) async { -// Logger.info('Saving container state', name: 'secure_token_container_state_repository.dart.dart#saveContainerState'); -// if (TokenContainer is TokenContainerError) { -// Logger.error('Cannot save error state to repository', name: 'secure_token_container_state_repository.dart.dart#saveContainerState'); -// return containerState; -// } -// final json = containerState.toJson(); -// final jsonString = jsonEncode(json); -// await _write(_containerStateKey, jsonString); -// Logger.debug('Saved container state: $jsonString', name: 'secure_token_container_state_repository.dart.dart#saveContainerState'); -// return containerState; -// } - -// @override - -// /// Load the container state from the shared preferences -// Future loadContainerState() async { -// Logger.info('Loading container state', name: 'secure_token_container_state_repository.dart.dart#loadContainerState'); -// String? containerStateJsonString = await _read(_containerStateKey); -// Logger.debug('Loaded jsonString: $containerStateJsonString', name: 'secure_token_container_state_repository.dart.dart#loadContainerState'); -// if (containerStateJsonString == null) { -// return const TokenContainer.uninitialized(serial: '123'); -// } -// final json = jsonDecode(containerStateJsonString); -// try { -// final state = TokenContainer.fromJson(json); -// Logger.debug('Loaded container state: $containerStateJsonString', name: 'secure_token_container_state_repository.dart.dart#loadContainerState'); -// return state; -// } catch (e) { -// Logger.error( -// 'Failed to decode container state', -// name: 'secure_token_container_state_repository.dart.dart#loadContainerState', -// error: e, -// stackTrace: StackTrace.current, -// ); -// await _delete(_containerStateKey); -// return const TokenContainer.uninitialized(serial: '123'); -// } -// } -// } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart index c449697f0..02d4b796f 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -48,7 +48,7 @@ import '../../../logger.dart'; part 'token_container_notifier.g.dart'; -final containerCredentialsProvider = tokenContainerNotifierProviderOf( +final tokenContainerProvider = tokenContainerNotifierProviderOf( repo: SecureTokenContainerRepository(), containerApi: const PrivacyideaContainerApi(ioClient: PrivacyideaIOClient()), eccUtils: const EccUtils(), diff --git a/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/add_token_button.dart b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/add_token_button.dart index 0b4cfaf90..7cf68132e 100644 --- a/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/add_token_button.dart +++ b/lib/views/add_token_manually_view/add_token_manually_view_widgets/rows/add_token_button.dart @@ -23,7 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../model/tokens/token.dart'; import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; -import '../../../../widgets/mutex_button.dart'; +import '../../../../widgets/button_widgets/mutex_button.dart'; class AddTokenButton extends ConsumerWidget { final ValueNotifier autoValidateLabel; diff --git a/lib/views/container_view/container_view.dart b/lib/views/container_view/container_view.dart index 112160945..f7b7a7de1 100644 --- a/lib/views/container_view/container_view.dart +++ b/lib/views/container_view/container_view.dart @@ -45,9 +45,11 @@ class ContainerView extends ConsumerView { @override Widget build(BuildContext context, WidgetRef ref) { - final container = ref.watch(containerCredentialsProvider).whenOrNull(data: (data) => data.container) ?? []; + final container = ref.watch(tokenContainerProvider).whenOrNull(data: (data) => data.container) ?? []; return Scaffold( - appBar: AppBar(title: const Text('Container')), + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.container), + ), floatingActionButton: const QrScannerButton(), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, body: Center( @@ -94,13 +96,13 @@ class ContainerWidget extends ConsumerWidget { icon: const Icon(Icons.sync), onPressed: () { final tokenState = ref.read(tokenProvider); - ref.read(containerCredentialsProvider.notifier).syncTokens(tokenState); + ref.read(tokenContainerProvider.notifier).syncTokens(tokenState); }, ) : IconButton( icon: const Icon(Icons.delete), onPressed: () { - ref.read(containerCredentialsProvider.notifier).deleteCredential(containerCredential); + ref.read(tokenContainerProvider.notifier).deleteCredential(containerCredential); }, ), ), @@ -118,7 +120,7 @@ class DeleteContainerAction extends PiSlideableAction { @override CustomSlidableAction build(BuildContext context, WidgetRef ref) => CustomSlidableAction( - onPressed: (BuildContext context) => ref.read(containerCredentialsProvider.notifier).deleteCredential(container), + onPressed: (BuildContext context) => ref.read(tokenContainerProvider.notifier).deleteCredential(container), backgroundColor: Theme.of(context).extension()!.deleteColor, child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/views/feedback_view/feedback_view.dart b/lib/views/feedback_view/feedback_view.dart index 4c34ec6c7..6a742892e 100644 --- a/lib/views/feedback_view/feedback_view.dart +++ b/lib/views/feedback_view/feedback_view.dart @@ -19,15 +19,11 @@ */ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:privacyidea_authenticator/views/feedback_view/widgets/feedback_send_row.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../l10n/app_localizations.dart'; -import '../../utils/app_info_utils.dart'; import '../../utils/globals.dart'; -import '../../utils/pi_mailer.dart'; -import '../../utils/view_utils.dart'; -import '../../widgets/dialog_widgets/default_dialog.dart'; -import '../main_view/main_view.dart'; import '../view_interface.dart'; class FeedbackView extends StatefulView { @@ -44,8 +40,6 @@ class FeedbackView extends StatefulView { class _FeedbackViewState extends State { final TextEditingController _feedbackController = TextEditingController(); - bool _addDeviceInfo = false; - final FocusNode _focusNode = FocusNode(); @override @@ -126,86 +120,14 @@ class _FeedbackViewState extends State { ], ), ), + FeedbackSendRow(feedbackController: _feedbackController), ], ), ), ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TextButton( - onPressed: () => setState(() => _addDeviceInfo = !_addDeviceInfo), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - width: 70, - child: Text( - AppLocalizations.of(context)!.addSystemInfo, - textAlign: TextAlign.right, - style: Theme.of(context).textTheme.bodySmall, - ), - ), - const SizedBox(width: 4), - SizedBox( - width: 24, - height: 24, - child: Checkbox( - value: _addDeviceInfo, - onChanged: (value) { - if (value == null) return; - setState(() { - _addDeviceInfo = value; - }); - }, - ), - ) - ], - ), - ), - ElevatedButton( - onPressed: () async { - final String mailText; - if (_addDeviceInfo) { - mailText = _addDeviceInfoToMail(_feedbackController.text); - } else { - mailText = _feedbackController.text; - } - final sended = await _sendMail(mailText); - if (sended) { - showAsyncDialog( - builder: (context) => DefaultDialog( - title: Text(AppLocalizations.of(context)!.feedbackSentTitle), - content: Text(AppLocalizations.of(context)!.feedbackSentDescription), - actionsAlignment: MainAxisAlignment.center, - actions: [ - ElevatedButton( - onPressed: () => Navigator.of(context).popUntil((route) => route.settings.name == MainView.routeName), - child: Text(AppLocalizations.of(context)!.ok)) - ], - ), - barrierDismissible: false, - ); - } - }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text(AppLocalizations.of(context)!.send), - const SizedBox(width: 8), - const Icon(Icons.mail_outline), - ], - ), - ), - ], - ), ], ), ), ), ); - - String _addDeviceInfoToMail(String feedback) => '$feedback\n\n[${AppInfoUtils.currentVersionAndBuildNumber}] ${AppInfoUtils.deviceInfoString}'; - Future _sendMail(String mailText) => PiMailer.sendMail(subjectPrefix: 'Feedback', body: mailText, subjectAppVersion: false); } diff --git a/lib/views/feedback_view/widgets/feedback_send_row.dart b/lib/views/feedback_view/widgets/feedback_send_row.dart new file mode 100644 index 000000000..a746414b4 --- /dev/null +++ b/lib/views/feedback_view/widgets/feedback_send_row.dart @@ -0,0 +1,137 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; + +import '../../../l10n/app_localizations.dart'; +import '../../../utils/app_info_utils.dart'; +import '../../../utils/pi_mailer.dart'; +import '../../../utils/view_utils.dart'; +import '../../../widgets/dialog_widgets/default_dialog.dart'; +import '../../main_view/main_view.dart'; + +class FeedbackSendRow extends StatefulWidget { + final TextEditingController feedbackController; + + const FeedbackSendRow({super.key, required this.feedbackController}); + + @override + State createState() => _FeedbackSendRowState(); +} + +class _FeedbackSendRowState extends State { + late final _feedbackController = widget.feedbackController; + bool _addDeviceInfo = false; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Expanded(child: SizedBox()), + Expanded( + flex: 4, + child: TextButton( + onPressed: () => setState(() => _addDeviceInfo = !_addDeviceInfo), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + child: Text( + AppLocalizations.of(context)!.addSystemInfo, + textAlign: TextAlign.right, + style: Theme.of(context).textTheme.bodySmall, + ), + ), + const SizedBox(width: 4), + SizedBox( + width: 24, + height: 24, + child: Checkbox( + value: _addDeviceInfo, + onChanged: (value) { + if (value == null) return; + setState(() { + _addDeviceInfo = value; + }); + }), + ), + ], + ), + ), + ), + const Expanded(child: SizedBox()), + ], + ), + ), + Expanded( + child: Center( + child: ElevatedButton( + onPressed: () async { + final String mailText; + if (_addDeviceInfo) { + mailText = _addDeviceInfoToMail(_feedbackController.text); + } else { + mailText = _feedbackController.text; + } + final sended = await _sendMail(mailText); + if (sended) { + showAsyncDialog( + builder: (context) => DefaultDialog( + title: Text(AppLocalizations.of(context)!.feedbackSentTitle), + content: Text(AppLocalizations.of(context)!.feedbackSentDescription), + actionsAlignment: MainAxisAlignment.center, + actions: [ + ElevatedButton( + onPressed: () => Navigator.of(context).popUntil((route) => route.settings.name == MainView.routeName), + child: Text(AppLocalizations.of(context)!.ok)) + ], + ), + barrierDismissible: false, + ); + } + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.of(context)!.send), + const SizedBox(width: 8), + const Icon(Icons.mail_outline), + ], + ), + ), + ), + ), + ], + ); + } + + String _addDeviceInfoToMail(String feedback) => '$feedback\n\n[${AppInfoUtils.currentVersionAndBuildNumber}] ${AppInfoUtils.deviceInfoString}'; + Future _sendMail(String mailText) => PiMailer.sendMail(subjectPrefix: 'Feedback', body: mailText, subjectAppVersion: false); +} diff --git a/lib/views/main_view/main_view.dart b/lib/views/main_view/main_view.dart index ba8212bfa..f7730a7c2 100644 --- a/lib/views/main_view/main_view.dart +++ b/lib/views/main_view/main_view.dart @@ -36,6 +36,7 @@ import 'main_view_widgets/expandable_appbar.dart'; import 'main_view_widgets/main_view_navigation_bar.dart'; import 'main_view_widgets/main_view_tokens_list.dart'; import 'main_view_widgets/main_view_tokens_list_filtered.dart'; +import 'main_view_widgets/token_widgets/main_view_background_icon.dart'; export '../../views/main_view/main_view.dart'; @@ -46,11 +47,19 @@ class MainView extends ConsumerStatefulView { RouteSettings get routeSettings => const RouteSettings(name: routeName); final Widget appIcon; + final Widget appImage; final String appName; final bool disablePatchNotes; final BoxConstraints appConstraints; - const MainView({required this.appIcon, required this.appName, required this.disablePatchNotes, super.key, required this.appConstraints}); + const MainView({ + required this.appImage, + required this.appIcon, + required this.appName, + required this.disablePatchNotes, + super.key, + required this.appConstraints, + }); @override ConsumerState createState() => _MainViewState(); @@ -86,10 +95,7 @@ class _MainViewState extends ConsumerState { // maxLines: 2 only works like this. maxLines: 2, // Title can be shown on small screens too. ), - leading: Padding( - padding: const EdgeInsets.all(4), - child: widget.appIcon, - ), + leading: const SizedBox(), actions: [ hasFilter ? AppBarItem( @@ -114,6 +120,7 @@ class _MainViewState extends ConsumerState { child: !hasFilter ? Stack( children: [ + MainViewBackgroundIcon(appImage: widget.appImage), MainViewTokensList(nestedScrollViewKey: globalKey), MainViewNavigationBar(appConstraints: widget.appConstraints), ], diff --git a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart index f46159abd..4676bdbb6 100644 --- a/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart +++ b/lib/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart @@ -52,7 +52,7 @@ class QrScannerButton extends ConsumerWidget { Navigator.pushNamed(globalNavigatorKey.currentContext!, QRScannerView.routeName).then((qrCode) { final resultHandlers = [ ref.read(tokenProvider.notifier), - ref.read(containerCredentialsProvider.notifier), + ref.read(tokenContainerProvider.notifier), ]; scanQrCode(resultHandlers, qrCode); }); diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart index 366d6ccd6..2befba810 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart @@ -108,53 +108,52 @@ class _DefaultEditActionDialogState extends ConsumerState + * + * Copyright (c) 2024 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/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class MainViewBackgroundIcon extends ConsumerWidget { + final Widget appImage; + + const MainViewBackgroundIcon({super.key, required this.appImage}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + final scaffoldBackgroundColor = Theme.of(context).scaffoldBackgroundColor; + final base = isDarkMode ? 0.3 : 0.18; + final blendMode = isDarkMode ? BlendMode.darken : BlendMode.lighten; + return Positioned.fill( + child: Padding( + padding: const EdgeInsets.fromLTRB(36, 4, 36, 60), + child: + // isDarkMode + // ? + FittedBox( + child: ColorFiltered( + colorFilter: ColorFilter.mode(scaffoldBackgroundColor.withOpacity(1 - base), blendMode), + child: ColorFiltered( + colorFilter: ColorFilter.mode(scaffoldBackgroundColor, BlendMode.color), + child: appImage, + ), + ), + // ) + // : + // FittedBox( + // child: ColorFiltered( + // colorFilter: ColorFilter.mode(scaffoldBackgroundColor.withOpacity(0.9), BlendMode.lighten), + // // colorFilter: ColorFilter.matrix([ + // // // Greyscale the child then add a fraction the color value to the backgroundcolor to make it look like a brightened greyscale image + // // -0.393 * base, -0.769 * base, -0.189 * base, 0, r, + // // -0.349 * base, -0.686 * base, -0.168 * base, 0, g, + // // -0.272 * base, -0.534 * base, -0.131 * base, 0, b, + // // 0, 0, 0, 0, a, + // // ]), + // // child: Opacity( + // // opacity: 0.1, + // child: ColorFiltered( + // colorFilter: ColorFilter.mode(scaffoldBackgroundColor, BlendMode.color), + // // child: ColorFiltered( + // // colorFilter: ColorFilter.matrix([ + // // // Greyscale the child then add a fraction the color value to the backgroundcolor to make it look like a brightened greyscale image + // // 0.2126, 0.7152, 0.0722, 0, 0, + // // 0.2126, 0.7152, 0.0722, 0, 0, + // // 0.2126, 0.7152, 0.0722, 0, 0, + // // 0, 0, 0, 1, 0, + // // ]), + // child: appImage, + // ), + // ), + // ), + // ), + ), + ), + ); + } +} +/** + * + * /* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class MainViewBackgroundIcon extends ConsumerWidget { + final Widget appImage; + + const MainViewBackgroundIcon({super.key, required this.appImage}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + final scaffoldBackgroundColor = Theme.of(context).scaffoldBackgroundColor; + final r = scaffoldBackgroundColor.red.toDouble(); + final g = scaffoldBackgroundColor.green.toDouble(); + final b = scaffoldBackgroundColor.blue.toDouble(); + final a = scaffoldBackgroundColor.alpha.toDouble(); + + final base = isDarkMode ? 0.03 : 0.08; + final asd = isDarkMode ? 1 : 0; + return Positioned.fill( + child: Padding( + padding: const EdgeInsets.fromLTRB(36, 4, 36, 60), + child: FittedBox( + child: ColorFiltered( + colorFilter: ColorFilter.matrix([ + (0.393*base, (0.769*base, (0.189*base, 0, r, // + (0.349*base, (0.686*base, (0.168*base, 0, g, + (0.272*base, (0.534*base, (0.131*base, 0, b, + 0, 0, 0, 0, a, // + ]), + child: ColorFiltered( + colorFilter: ColorFilter.mode( + Colors.transparent, + // scaffoldBackgroundColor, + BlendMode.color, + ), + child: + // isDarkMode + // ? + appImage + // : ColorFiltered( + // colorFilter: ColorFilter.mode( + // scaffoldBackgroundColor, + // isDarkMode ? BlendMode.exclusion : BlendMode.exclusion, + // ), + // child: + // appImage, + ), + ), + ), + // ), + ), + ); + } +} + + */ \ No newline at end of file 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 c77f8e476..5187c65db 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 @@ -26,7 +26,7 @@ import '../../../../../model/tokens/push_token.dart'; import '../../../../../utils/globals.dart'; import '../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../widgets/dialog_widgets/default_dialog.dart'; -import '../../../../../widgets/press_button.dart'; +import '../../../../../widgets/button_widgets/press_button.dart'; class StartRolloutWidget extends ConsumerWidget { final PushToken token; @@ -59,7 +59,7 @@ class StartRolloutWidget extends ConsumerWidget { ), Expanded( flex: 35, - child: PressButton( + child: CooldownButton( onPressed: () => globalRef?.read(tokenProvider.notifier).rolloutPushToken(token) ?? Future.value(), child: Text( token.rolloutState.rolloutFailed ? localizations.retryRollout : localizations.startRollout, @@ -75,7 +75,7 @@ class StartRolloutWidget extends ConsumerWidget { ), Expanded( flex: 35, - child: PressButton( + child: CooldownButton( style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Theme.of(context).colorScheme.errorContainer)), onPressed: () => _showDialog(), child: Text( diff --git a/lib/views/settings_view/settings_groups/settings_group_container.dart b/lib/views/settings_view/settings_groups/settings_group_container.dart index e761114eb..17cf7234c 100644 --- a/lib/views/settings_view/settings_groups/settings_group_container.dart +++ b/lib/views/settings_view/settings_groups/settings_group_container.dart @@ -19,30 +19,21 @@ */ 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/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import 'package:privacyidea_authenticator/views/container_view/container_view.dart'; -import '../settings_view_widgets/settings_groups.dart'; +import '../settings_view_widgets/settings_group.dart'; -class SettingsGroupContainer extends StatelessWidget { +class SettingsGroupContainer extends ConsumerWidget { const SettingsGroupContainer({super.key}); @override - Widget build(BuildContext context) => SettingsGroup( - title: 'Container', - children: [ - TextButton( - child: ListTile( - title: Text( - 'Container', - style: Theme.of(context).textTheme.bodyMedium, - overflow: TextOverflow.fade, - softWrap: false, - ), - style: ListTileStyle.list, - trailing: Icon(Icons.arrow_forward_ios), - ), - onPressed: () => Navigator.of(context).pushNamed(ContainerView.routeName), - ), - ], + Widget build(BuildContext context, WidgetRef ref) => SettingsGroup( + title: AppLocalizations.of(context)!.container, + onPressed: () => Navigator.of(context).pushNamed(ContainerView.routeName), + isActive: ref.watch(tokenContainerProvider).whenOrNull(data: (data) => data)?.container.isNotEmpty ?? false, + trailingIcon: Icons.arrow_forward_ios, // TODO: Change to container icon when we have one ); } diff --git a/lib/views/settings_view/settings_groups/settings_group_error_log.dart b/lib/views/settings_view/settings_groups/settings_group_error_log.dart index 8e5e2dba2..efa308b63 100644 --- a/lib/views/settings_view/settings_groups/settings_group_error_log.dart +++ b/lib/views/settings_view/settings_groups/settings_group_error_log.dart @@ -21,7 +21,7 @@ import 'package:flutter/material.dart'; import '../../../l10n/app_localizations.dart'; import '../settings_view_widgets/logging_menu.dart'; -import '../settings_view_widgets/settings_groups.dart'; +import '../settings_view_widgets/settings_group.dart'; class SettingsGroupErrorLog extends StatelessWidget { const SettingsGroupErrorLog({super.key}); @@ -29,28 +29,18 @@ class SettingsGroupErrorLog extends StatelessWidget { @override Widget build(BuildContext context) => SettingsGroup( title: AppLocalizations.of(context)!.errorLogTitle, - children: [ - ListTile( - title: Text( - AppLocalizations.of(context)!.logMenu, - style: Theme.of(context).textTheme.bodyMedium, - overflow: TextOverflow.fade, - softWrap: false, - ), - style: ListTileStyle.list, - trailing: ElevatedButton( - child: Text( - AppLocalizations.of(context)!.open, - overflow: TextOverflow.fade, - softWrap: false, - ), - onPressed: () => showDialog( - context: context, - builder: (context) => const LoggingMenu(), - useRootNavigator: false, - ), - ), - ), - ], + onPressed: () => showDialog( + useRootNavigator: false, + context: context, + builder: (_) => const SettingsGroupErrorLogDialog(), + ), + trailingIcon: Icons.error_outline, ); } + +class SettingsGroupErrorLogDialog extends StatelessWidget { + const SettingsGroupErrorLogDialog({super.key}); + + @override + Widget build(BuildContext context) => const LoggingMenu(); +} diff --git a/lib/views/settings_view/settings_groups/settings_group_feedback.dart b/lib/views/settings_view/settings_groups/settings_group_feedback.dart new file mode 100644 index 000000000..11ff111fa --- /dev/null +++ b/lib/views/settings_view/settings_groups/settings_group_feedback.dart @@ -0,0 +1,36 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; + +import '../../../l10n/app_localizations.dart'; +import '../../feedback_view/feedback_view.dart'; +import '../settings_view_widgets/settings_group.dart'; + +class SettingsGroupFeedback extends StatelessWidget { + const SettingsGroupFeedback({super.key}); + + @override + Widget build(BuildContext context) => SettingsGroup( + title: AppLocalizations.of(context)!.feedback, + onPressed: () => Navigator.pushNamed(context, FeedbackView.routeName), + trailingIcon: Icons.feedback, + ); +} diff --git a/lib/views/settings_view/settings_groups/settings_group_general.dart b/lib/views/settings_view/settings_groups/settings_group_general.dart index c520fa3e5..1297f9c55 100644 --- a/lib/views/settings_view/settings_groups/settings_group_general.dart +++ b/lib/views/settings_view/settings_groups/settings_group_general.dart @@ -17,16 +17,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:simple_icons/simple_icons.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../l10n/app_localizations.dart'; import '../../../utils/globals.dart'; -import '../../feedback_view/feedback_view.dart'; import '../../license_view/license_view.dart'; -import '../settings_view_widgets/settings_groups.dart'; +import '../settings_view_widgets/settings_group.dart'; import '../settings_view_widgets/settings_list_tile_button.dart'; class SettingsGroupGeneral extends StatelessWidget { @@ -71,18 +69,6 @@ class SettingsGroupGeneral extends StatelessWidget { ), icon: const Icon(SimpleIcons.github), ), - SettingsListTileButton( - onPressed: () { - Navigator.pushNamed(context, FeedbackView.routeName); - }, - title: Text( - 'Feedback', - style: Theme.of(context).textTheme.bodyMedium, - overflow: TextOverflow.fade, - softWrap: false, - ), - icon: const Icon(FluentIcons.chat_32_regular), - ), ], ); } diff --git a/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart b/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart index a2b322a97..0b7361f29 100644 --- a/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart +++ b/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart @@ -24,10 +24,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/introduction.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart'; -import '../../../widgets/countdown_button.dart'; +import '../../../widgets/button_widgets/countdown_button.dart'; import '../../../widgets/dialog_widgets/default_dialog.dart'; import '../../import_tokens_view/import_tokens_view.dart'; -import '../settings_view_widgets/settings_groups.dart'; +import '../settings_view_widgets/settings_group.dart'; import '../settings_view_widgets/settings_list_tile_button.dart'; import 'import_export_tokens_widgets/dialogs/select_export_type_dialog.dart'; diff --git a/lib/views/settings_view/settings_groups/settings_group_language.dart b/lib/views/settings_view/settings_groups/settings_group_language.dart index 775a7bcc9..d8f0f1d02 100644 --- a/lib/views/settings_view/settings_groups/settings_group_language.dart +++ b/lib/views/settings_view/settings_groups/settings_group_language.dart @@ -20,60 +20,85 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart'; +import 'package:privacyidea_authenticator/widgets/dialog_widgets/default_dialog.dart'; import '../../../l10n/app_localizations.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; -import '../settings_view_widgets/settings_groups.dart'; + +import '../settings_view_widgets/settings_group.dart'; class SettingsGroupLanguage extends ConsumerWidget { const SettingsGroupLanguage({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + return SettingsGroup( + title: AppLocalizations.of(context)!.language, + onPressed: () => showDialog( + useRootNavigator: false, + context: context, + builder: (context) => const SettingsGroupLanguageDialog(), + ), + trailingIcon: Icons.language, + ); + } +} + +class SettingsGroupLanguageDialog extends ConsumerWidget { + const SettingsGroupLanguageDialog({ + super.key, + }); + @override Widget build(BuildContext context, WidgetRef ref) { final localizations = AppLocalizations.of(context)!; final settings = ref.watch(settingsProvider).whenOrNull(data: (data) => data); final useSystemLocale = settings?.useSystemLocale ?? SettingsState.useSystemLocaleDefault; final currentLocale = settings?.currentLocale ?? SettingsState.localeDefault; - return SettingsGroup( - title: localizations.language, - children: [ - SwitchListTile( - title: Text( - localizations.useDeviceLocaleTitle, - style: Theme.of(context).textTheme.bodyMedium, - ), - subtitle: Text( - localizations.useDeviceLocaleDescription, - overflow: TextOverflow.fade, - ), - value: useSystemLocale, - onChanged: (value) => ref.read(settingsProvider.notifier).setUseSystemLocale(value)), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: DropdownButton( - disabledHint: Text( - '$currentLocale', - style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.grey), - overflow: TextOverflow.fade, - softWrap: false, + return DefaultDialog( + title: Text(localizations.language), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + SwitchListTile( + title: Text( + localizations.useDeviceLocaleTitle, + style: Theme.of(context).textTheme.bodyMedium, + ), + subtitle: Text( + localizations.useDeviceLocaleDescription, + overflow: TextOverflow.fade, + ), + value: useSystemLocale, + onChanged: (value) => ref.read(settingsProvider.notifier).setUseSystemLocale(value)), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: DropdownButton( + disabledHint: Text( + '$currentLocale', + style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.grey), + overflow: TextOverflow.fade, + softWrap: false, + ), + isExpanded: true, + value: currentLocale, + items: AppLocalizations.supportedLocales.map>((Locale itemLocale) { + return DropdownMenuItem( + value: itemLocale, + child: Text( + '$itemLocale', + overflow: TextOverflow.fade, + style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: useSystemLocale ? Colors.grey : null), + softWrap: false, + ), + ); + }).toList(), + onChanged: useSystemLocale ? null : (value) => ref.read(settingsProvider.notifier).setLocalePreference(value!), ), - isExpanded: true, - value: currentLocale, - items: AppLocalizations.supportedLocales.map>((Locale itemLocale) { - return DropdownMenuItem( - value: itemLocale, - child: Text( - '$itemLocale', - overflow: TextOverflow.fade, - style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: useSystemLocale ? Colors.grey : null), - softWrap: false, - ), - ); - }).toList(), - onChanged: useSystemLocale ? null : (value) => ref.read(settingsProvider.notifier).setLocalePreference(value!), ), - ), - ], + ], + ), ); } } 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 e8915c967..3de11cfe6 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 @@ -19,123 +19,143 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/widgets/dialog_widgets/default_dialog.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/riverpod_states/settings_state.dart'; import '../../../model/tokens/push_token.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; -import '../settings_view_widgets/settings_groups.dart'; +import '../settings_view_widgets/settings_group.dart'; import '../settings_view_widgets/update_firebase_token_dialog.dart'; class SettingsGroupPushToken extends ConsumerWidget { - final bool enablePushSettingsGroup; - final List unsupportedPushTokens; const SettingsGroupPushToken({ - required this.enablePushSettingsGroup, - required this.unsupportedPushTokens, super.key, }); @override Widget build(BuildContext context, WidgetRef ref) { - final settingsState = ref.watch(settingsProvider).whenOrNull(data: (data) => data); + final tokens = ref.watch(tokenProvider).tokens; + final enrolledPushTokenList = tokens.whereType().where((e) => e.isRolledOut).toList(); + final unsupportedPushTokens = enrolledPushTokenList.where((e) => e.url == null).toList(); return SettingsGroup( - isActive: enablePushSettingsGroup, title: AppLocalizations.of(context)!.pushToken, - children: [ - ListTile( - title: Text( - AppLocalizations.of(context)!.synchronizePushTokens, - style: Theme.of(context).textTheme.bodyMedium, - ), - subtitle: Text( - AppLocalizations.of(context)!.synchronizesTokensWithServer, - overflow: TextOverflow.fade, - ), - trailing: ElevatedButton( - onPressed: enablePushSettingsGroup - ? () { - showDialog( - useRootNavigator: false, - context: context, - barrierDismissible: false, - builder: (context) => const UpdateFirebaseTokenDialog(), - ); - } - : null, - child: Text( - AppLocalizations.of(context)!.sync, + isActive: enrolledPushTokenList.isNotEmpty, + onPressed: () => showDialog( + useRootNavigator: false, + context: context, + builder: (_) => SettingsGroupPushTokenDialog( + unsupportedPushTokens: unsupportedPushTokens, + ), + ), + trailingIcon: Icons.notifications, + ); + } +} + +class SettingsGroupPushTokenDialog extends ConsumerWidget { + final List unsupportedPushTokens; + const SettingsGroupPushTokenDialog({ + super.key, + required this.unsupportedPushTokens, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final settingsState = ref.watch(settingsProvider).whenOrNull(data: (data) => data); + return DefaultDialog( + title: Text(AppLocalizations.of(context)!.pushToken), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: Text( + AppLocalizations.of(context)!.synchronizePushTokens, + style: Theme.of(context).textTheme.bodyMedium, + ), + subtitle: Text( + AppLocalizations.of(context)!.synchronizesTokensWithServer, overflow: TextOverflow.fade, - softWrap: false, + ), + trailing: ElevatedButton( + onPressed: () => showDialog( + useRootNavigator: false, + context: context, + barrierDismissible: false, + builder: (context) => const UpdateFirebaseTokenDialog(), + ), + child: Text( + AppLocalizations.of(context)!.sync, + overflow: TextOverflow.fade, + softWrap: false, + ), ), ), - ), - ListTile( - title: RichText( - text: TextSpan( - children: [ - TextSpan( - text: AppLocalizations.of(context)!.enablePolling, - style: Theme.of(context).textTheme.bodyMedium, - ), - // Add clickable icon to inform user of unsupported push tokens (for polling) - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(left: 10), - child: unsupportedPushTokens.isNotEmpty && enablePushSettingsGroup - ? GestureDetector( - onTap: () => _showPollingInfo(context, unsupportedPushTokens), - child: const Icon( - Icons.info_outline, - color: Colors.red, - ), - ) - : null, + ListTile( + title: RichText( + text: TextSpan( + children: [ + TextSpan( + text: AppLocalizations.of(context)!.enablePolling, + style: Theme.of(context).textTheme.bodyMedium, ), - ), - ], + // Add clickable icon to inform user of unsupported push tokens (for polling) + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(left: 10), + child: unsupportedPushTokens.isNotEmpty + ? GestureDetector( + onTap: () => _showPollingInfo(context, unsupportedPushTokens), + child: const Icon( + Icons.info_outline, + color: Colors.red, + ), + ) + : null, + ), + ), + ], + ), ), - ), - subtitle: Text( - AppLocalizations.of(context)!.requestPushChallengesPeriodically, - overflow: TextOverflow.fade, - ), - trailing: Switch( - value: settingsState?.enablePolling ?? SettingsState.enablePollingDefault, - onChanged: enablePushSettingsGroup ? (value) => ref.read(settingsProvider.notifier).setPolling(value) : null, - ), - ), - ListTile( - title: RichText( - text: TextSpan( - children: [ - TextSpan( - text: AppLocalizations.of(context)!.hidePushTokens, - style: Theme.of(context).textTheme.bodyMedium, - ), - ], + subtitle: Text( + AppLocalizations.of(context)!.requestPushChallengesPeriodically, + overflow: TextOverflow.fade, + ), + trailing: Switch( + value: settingsState?.enablePolling ?? SettingsState.enablePollingDefault, + onChanged: (value) => ref.read(settingsProvider.notifier).setPolling(value), ), ), - subtitle: Text( - AppLocalizations.of(context)!.hidePushTokensDescription, - overflow: TextOverflow.fade, - ), - trailing: Switch( - value: settingsState?.hidePushTokens ?? SettingsState.hidePushTokensDefault, - onChanged: enablePushSettingsGroup && ref.watch(tokenProvider).hasOTPTokens - ? (value) => ref.read(settingsProvider.notifier).setHidePushTokens(value) - : null, + ListTile( + title: RichText( + text: TextSpan( + children: [ + TextSpan( + text: AppLocalizations.of(context)!.hidePushTokens, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + ), + subtitle: Text( + AppLocalizations.of(context)!.hidePushTokensDescription, + overflow: TextOverflow.fade, + ), + trailing: Switch( + value: settingsState?.hidePushTokens ?? SettingsState.hidePushTokensDefault, + onChanged: (value) => ref.read(settingsProvider.notifier).setHidePushTokens(value), + ), ), - ) - ], + ], + ), ); } /// Shows a dialog to the user that displays all push tokens that do not /// support polling. - void _showPollingInfo(BuildContext context, List unsupported) { - showDialog( + void _showPollingInfo(BuildContext context, List unsupported) => showDialog( + useRootNavigator: false, context: context, builder: (BuildContext context) { return AlertDialog( @@ -158,6 +178,6 @@ class SettingsGroupPushToken extends ConsumerWidget { ), ], ); - }); - } + }, + ); } diff --git a/lib/views/settings_view/settings_groups/settings_group_theme.dart b/lib/views/settings_view/settings_groups/settings_group_theme.dart index 38c93f8e7..995165de7 100644 --- a/lib/views/settings_view/settings_groups/settings_group_theme.dart +++ b/lib/views/settings_view/settings_groups/settings_group_theme.dart @@ -22,58 +22,42 @@ import 'package:flutter/material.dart'; import '../../../l10n/app_localizations.dart'; import '../../../utils/home_widget_utils.dart'; -import '../settings_view_widgets/settings_groups.dart'; +import '../settings_view_widgets/settings_group.dart'; class SettingsGroupTheme extends StatelessWidget { const SettingsGroupTheme({super.key}); @override - Widget build(BuildContext context) => SettingsGroup( - title: AppLocalizations.of(context)!.theme, - children: [ - RadioListTile( - title: Text( - AppLocalizations.of(context)!.lightTheme, - style: Theme.of(context).textTheme.bodyMedium, - overflow: TextOverflow.fade, - softWrap: false, - ), - value: ThemeMode.light, - groupValue: EasyDynamicTheme.of(context).themeMode, - controlAffinity: ListTileControlAffinity.trailing, - onChanged: (dynamic value) { - EasyDynamicTheme.of(context).changeTheme(dynamic: false, dark: false); - HomeWidgetUtils().setCurrentThemeMode(ThemeMode.light); - }, - ), - RadioListTile( - title: Text( - AppLocalizations.of(context)!.darkTheme, - style: Theme.of(context).textTheme.bodyMedium, - overflow: TextOverflow.fade, - softWrap: false, - ), - value: ThemeMode.dark, - groupValue: EasyDynamicTheme.of(context).themeMode, - controlAffinity: ListTileControlAffinity.trailing, - onChanged: (dynamic value) { - EasyDynamicTheme.of(context).changeTheme(dynamic: false, dark: true); - HomeWidgetUtils().setCurrentThemeMode(ThemeMode.dark); - }, - ), - RadioListTile( - title: Text( - AppLocalizations.of(context)!.systemTheme, - style: Theme.of(context).textTheme.bodyMedium, - ), - value: ThemeMode.system, - groupValue: EasyDynamicTheme.of(context).themeMode, - controlAffinity: ListTileControlAffinity.trailing, - onChanged: (dynamic value) { - EasyDynamicTheme.of(context).changeTheme(dynamic: true, dark: false); - HomeWidgetUtils().setCurrentThemeMode(ThemeMode.system); - }, - ), - ], - ); + Widget build(BuildContext context) { + final current = EasyDynamicTheme.of(context).themeMode; + return SettingsGroup( + title: AppLocalizations.of(context)!.themeMode, + onPressed: () { + switch (current) { + case ThemeMode.light: + EasyDynamicTheme.of(context).changeTheme(dynamic: false, dark: true); + HomeWidgetUtils().setCurrentThemeMode(ThemeMode.dark); + break; + case ThemeMode.dark: + EasyDynamicTheme.of(context).changeTheme(dynamic: true); + HomeWidgetUtils().setCurrentThemeMode(ThemeMode.system); + break; + case ThemeMode.system: + EasyDynamicTheme.of(context).changeTheme(dynamic: false, dark: false); + HomeWidgetUtils().setCurrentThemeMode(ThemeMode.light); + break; + case null: + EasyDynamicTheme.of(context).changeTheme(dynamic: false, dark: false); + HomeWidgetUtils().setCurrentThemeMode(ThemeMode.light); + break; + } + }, + trailingIcon: switch (current) { + ThemeMode.light => Icons.brightness_5, + ThemeMode.dark => Icons.brightness_4, + ThemeMode.system => Icons.brightness_auto, + null => Icons.question_mark, + }, + ); + } } diff --git a/lib/views/settings_view/settings_view.dart b/lib/views/settings_view/settings_view.dart index 7d85af3b9..a2139125a 100644 --- a/lib/views/settings_view/settings_view.dart +++ b/lib/views/settings_view/settings_view.dart @@ -18,11 +18,8 @@ * limitations under the License. */ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../l10n/app_localizations.dart'; -import '../../model/tokens/push_token.dart'; -import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../widgets/push_request_listener.dart'; import '../view_interface.dart'; import 'settings_groups/settings_group_container.dart'; @@ -32,6 +29,7 @@ import 'settings_groups/settings_group_import_export_tokens.dart'; import 'settings_groups/settings_group_language.dart'; import 'settings_groups/settings_group_push_token.dart'; import 'settings_groups/settings_group_theme.dart'; +import 'settings_groups/settings_group_feedback.dart'; class SettingsView extends ConsumerView { @override @@ -41,45 +39,31 @@ class SettingsView extends ConsumerView { const SettingsView({super.key}); @override - Widget build(BuildContext context, WidgetRef ref) { - final tokens = ref.watch(tokenProvider).tokens; - final enrolledPushTokenList = tokens.whereType().where((e) => e.isRolledOut).toList(); - final unsupportedPushTokens = enrolledPushTokenList.where((e) => e.url == null).toList(); - final enablePushSettingsGroup = enrolledPushTokenList.isNotEmpty; - - return PushRequestListener( - child: Scaffold( - appBar: AppBar( - title: Text( - AppLocalizations.of(context)!.settings, - overflow: TextOverflow.ellipsis, // maxLines: 2 only works like this. - maxLines: 2, // Title can be shown on small screens too. + Widget build(BuildContext context, WidgetRef ref) => PushRequestListener( + child: Scaffold( + appBar: AppBar( + title: Text( + AppLocalizations.of(context)!.settings, + overflow: TextOverflow.ellipsis, // maxLines: 2 only works like this. + maxLines: 2, // Title can be shown on small screens too. + ), ), - ), - body: SingleChildScrollView( - padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SettingsGroupGeneral(), - const Divider(), - const SettingsGroupImportExportTokens(), - const Divider(), - const SettingsGroupTheme(), - const Divider(), - const SettingsGroupLanguage(), - SettingsGroupPushToken( - enablePushSettingsGroup: enablePushSettingsGroup, - unsupportedPushTokens: unsupportedPushTokens, - ), - const Divider(), - const SettingsGroupErrorLog(), - const Divider(), - const SettingsGroupContainer(), - ], + body: const SingleChildScrollView( + padding: EdgeInsets.symmetric(vertical: 20, horizontal: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SettingsGroupFeedback(), + SettingsGroupImportExportTokens(), + SettingsGroupPushToken(), + SettingsGroupContainer(), + SettingsGroupTheme(), + SettingsGroupLanguage(), + SettingsGroupErrorLog(), + SettingsGroupGeneral(), + ], + ), ), ), - ), - ); - } + ); } diff --git a/lib/views/settings_view/settings_view_widgets/settings_group.dart b/lib/views/settings_view/settings_view_widgets/settings_group.dart new file mode 100644 index 000000000..d7055e291 --- /dev/null +++ b/lib/views/settings_view/settings_view_widgets/settings_group.dart @@ -0,0 +1,89 @@ +/* + privacyIDEA Authenticator + + Authors: Timo Sturm + Frank Merkel + Copyright (c) 2017-2024 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/material.dart'; + +import '../../../widgets/button_widgets/default_icon_button.dart'; + +/// Widget that defines the structure and look of groups on the settings screen. +class SettingsGroup extends StatelessWidget { + final String title; + final List children; + final bool isActive; + final void Function()? onPressed; + final IconData? trailingIcon; + + const SettingsGroup({ + super.key, + required this.title, + this.children = const [], + this.isActive = true, + this.onPressed, + this.trailingIcon, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider(), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + onPressed != null + ? GestureDetector( + onTap: isActive ? onPressed : null, + child: ListTile( + dense: true, + leading: Text( + title, + style: theme.textTheme.titleLarge?.copyWith(color: isActive ? null : Colors.grey), + overflow: TextOverflow.fade, + softWrap: false, + ), + trailing: DefaultIconButton( + tooltip: title, + onPressed: isActive ? onPressed! : null, + icon: trailingIcon ?? Icons.arrow_forward_ios, + ), + ), + ) + : ListTile( + dense: true, + leading: Text( + title, + style: theme.textTheme.titleLarge?.copyWith(color: isActive ? null : Colors.grey), + overflow: TextOverflow.fade, + softWrap: false, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Column(children: children), + ), + ], + ), + const Divider(), + ], + ); + } +} diff --git a/lib/views/settings_view/settings_view_widgets/settings_groups.dart b/lib/views/settings_view/settings_view_widgets/settings_groups.dart deleted file mode 100644 index 097547f4a..000000000 --- a/lib/views/settings_view/settings_view_widgets/settings_groups.dart +++ /dev/null @@ -1,56 +0,0 @@ -/* - privacyIDEA Authenticator - - Authors: Timo Sturm - Frank Merkel - Copyright (c) 2017-2024 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/material.dart'; - -/// Widget that defines the structure and look of groups on the settings screen. -class SettingsGroup extends StatelessWidget { - final String _title; - final List _children; - final bool _isActive; - - const SettingsGroup({super.key, required String title, required List children, bool isActive = true}) - : _title = title, - _children = children, - _isActive = isActive; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListTile( - dense: true, - leading: Text( - _title, - style: theme.textTheme.titleLarge?.copyWith(color: _isActive ? null : Colors.grey), - overflow: TextOverflow.fade, - softWrap: false, - ), - ), - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Column(children: _children), - ), - ], - ); - } -} diff --git a/lib/views/splash_screen/splash_screen.dart b/lib/views/splash_screen/splash_screen.dart index 9f4d79c30..e3a650a8e 100644 --- a/lib/views/splash_screen/splash_screen.dart +++ b/lib/views/splash_screen/splash_screen.dart @@ -100,6 +100,7 @@ class _SplashScreenState extends ConsumerState { final ViewWidget nextView = MainView( appName: _customization.appName, appIcon: _customization.appIcon.getWidget, + appImage: _customization.appImage.getWidget, disablePatchNotes: _customization.disabledFeatures.contains(AppFeature.patchNotes), appConstraints: widget.appConstraints, ); diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index a5e420b4e..51a8bb3fe 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -82,7 +82,7 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { @override Widget build(BuildContext context) { - final container = ref.watch(containerCredentialsProvider).value?.container ?? []; + final container = ref.watch(tokenContainerProvider).value?.container ?? []; Logger.debug('Credentials: $container', name: 'AppWrapper#build'); return SingleTouchRecognizer( child: StateObserver( diff --git a/lib/widgets/countdown_button.dart b/lib/widgets/button_widgets/countdown_button.dart similarity index 98% rename from lib/widgets/countdown_button.dart rename to lib/widgets/button_widgets/countdown_button.dart index 33d84cb65..a50a86dda 100644 --- a/lib/widgets/countdown_button.dart +++ b/lib/widgets/button_widgets/countdown_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../model/extensions/color_extension.dart'; +import '../../model/extensions/color_extension.dart'; class CountdownButton extends StatefulWidget { final int countdownSeconds; diff --git a/lib/widgets/button_widgets/default_icon_button.dart b/lib/widgets/button_widgets/default_icon_button.dart new file mode 100644 index 000000000..72f0a6de9 --- /dev/null +++ b/lib/widgets/button_widgets/default_icon_button.dart @@ -0,0 +1,54 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; + +class DefaultIconButton extends StatelessWidget { + final IconData icon; + final Function()? onPressed; + final String tooltip; + final Color? color; + final double size; + final double padding; + + const DefaultIconButton({ + super.key, + required this.icon, + required this.onPressed, + required this.tooltip, + this.color, + this.size = 24, + this.padding = 0, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.all(padding), + child: IconButton( + icon: Icon(icon), + onPressed: onPressed, + tooltip: tooltip, + color: color, + iconSize: size, + splashRadius: size + padding, + ), + ); + } +} diff --git a/lib/widgets/mutex_button.dart b/lib/widgets/button_widgets/mutex_button.dart similarity index 100% rename from lib/widgets/mutex_button.dart rename to lib/widgets/button_widgets/mutex_button.dart diff --git a/lib/widgets/press_button.dart b/lib/widgets/button_widgets/press_button.dart similarity index 67% rename from lib/widgets/press_button.dart rename to lib/widgets/button_widgets/press_button.dart index 8877f9d26..cddb80a9d 100644 --- a/lib/widgets/press_button.dart +++ b/lib/widgets/button_widgets/press_button.dart @@ -1,18 +1,18 @@ import 'package:flutter/material.dart'; -class PressButton extends StatefulWidget { +class CooldownButton extends StatefulWidget { final Future Function() onPressed; final Widget child; - final int delayInMilliseconds; + final int buttonCooldown; final ButtonStyle? style; - const PressButton({super.key, required this.onPressed, required this.child, this.delayInMilliseconds = 1000, this.style}); + const CooldownButton({super.key, required this.onPressed, required this.child, this.buttonCooldown = 1000, this.style}); @override - State createState() => _PressButtonState(); + State createState() => _CooldownButtonState(); } -class _PressButtonState extends State { +class _CooldownButtonState extends State { bool isPressable = true; void press() async { @@ -23,7 +23,7 @@ class _PressButtonState extends State { await Future.wait( [ widget.onPressed(), - Future.delayed(Duration(milliseconds: widget.delayInMilliseconds)), + Future.delayed(Duration(milliseconds: widget.buttonCooldown)), ], ); if (mounted) { diff --git a/lib/widgets/default_refresh_indicator.dart b/lib/widgets/default_refresh_indicator.dart index 4ded61ec4..7a3cc929c 100644 --- a/lib/widgets/default_refresh_indicator.dart +++ b/lib/widgets/default_refresh_indicator.dart @@ -31,7 +31,7 @@ class _DefaultRefreshIndicatorState extends ConsumerState { ) : SizedBox( // Accept button - child: PressButton( + child: CooldownButton( style: ButtonStyle( backgroundColor: WidgetStateProperty.all(pushRequestTheme.acceptColor), shape: buttonShape(context), @@ -157,7 +157,7 @@ class _PushRequestDialogState extends ConsumerState { ), SizedBox( // Decline button - child: PressButton( + child: CooldownButton( style: ButtonStyle( backgroundColor: WidgetStateProperty.all(pushRequestTheme.declineColor), shape: buttonShape(context), @@ -226,7 +226,7 @@ class _PushRequestDialogState extends ConsumerState { const Expanded(child: SizedBox()), Expanded( flex: 6, - child: PressButton( + child: CooldownButton( style: ButtonStyle( shape: buttonShape(context), backgroundColor: WidgetStateProperty.all(pushRequestTheme.acceptColor), @@ -269,7 +269,7 @@ class _PushRequestDialogState extends ConsumerState { const Expanded(flex: 2, child: SizedBox()), Expanded( flex: 6, - child: PressButton( + child: CooldownButton( style: ButtonStyle( backgroundColor: WidgetStateProperty.all(pushRequestTheme.declineColor), shape: buttonShape(context), @@ -351,7 +351,7 @@ class _AnswerSelectionWidgetState extends State> { for (final possibleAnswer in answersThisRow) Expanded( flex: answersThisRow.length * 2, - child: PressButton( + child: CooldownButton( style: ButtonStyle( shape: _PushRequestDialogState.buttonShape(context), backgroundColor: WidgetStateProperty.all(pushRequestTheme.acceptColor), From e7254fe1953163ef8e5a4231962b46e0bf88eb0d Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Fri, 20 Sep 2024 15:18:29 +0200 Subject: [PATCH 045/285] workflow flutter version --- .github/workflows/flutter_build.yml | 4 ++-- .github/workflows/flutter_test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/flutter_build.yml b/.github/workflows/flutter_build.yml index 4c18932ab..3a5786ebb 100644 --- a/.github/workflows/flutter_build.yml +++ b/.github/workflows/flutter_build.yml @@ -4,7 +4,7 @@ on: branches: - master pull_request: - + jobs: build_ios: @@ -27,7 +27,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: 'stable' - flutter-version: '3.22.0' + flutter-version: '3.24.3' - run: "flutter --version" - run: "flutter pub get" - run: "flutter build ios -t 'lib/mains/main_netknights.dart' --debug --flavor netknights --no-codesign" diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index 60ef7ae7f..1c1222f61 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -1,5 +1,5 @@ name: Flutter Test -on: +on: push: branches: - master @@ -19,7 +19,7 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: 'stable' - flutter-version: '3.22.0' + flutter-version: '3.24.3' - run: "flutter --version" - run: flutter pub get - run: flutter gen-l10n From dffb58ecc1a3d0ecb7f6ec72be8f31e3f7b911f1 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Fri, 20 Sep 2024 15:45:39 +0200 Subject: [PATCH 046/285] fix ecc utils --- lib/utils/ecc_utils.dart | 3 ++- .../generated_providers/token_container_notifier.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/utils/ecc_utils.dart b/lib/utils/ecc_utils.dart index 46ce7de6e..e42470db2 100644 --- a/lib/utils/ecc_utils.dart +++ b/lib/utils/ecc_utils.dart @@ -37,5 +37,6 @@ class EccUtils { return CryptoUtils.ecSignatureToBase64(ecSignature); } - AsymmetricKeyPair generateKeyPair(EcKeyAlgorithm keyAlgorithm) => CryptoUtils.generateEcKeyPair(curve: keyAlgorithm.curveName); + AsymmetricKeyPair generateKeyPair(EcKeyAlgorithm keyAlgorithm) => + CryptoUtils.generateEcKeyPair(curve: keyAlgorithm.curveName) as AsymmetricKeyPair; } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart index 02d4b796f..8ed4b52a8 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -300,7 +300,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler TokenContainerUnfinalized? container = containerCredential; container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.generatingKeyPair)); if (container == null) throw StateError('Credential was removed'); - final keyPair = CryptoUtils.generateEcKeyPair(curve: container.ecKeyAlgorithm.curveName); + final keyPair = CryptoUtils.generateEcKeyPair(curve: container.ecKeyAlgorithm.curveName) as AsymmetricKeyPair; container = await updateCredential(container, (c) => c.withClientKeyPair(keyPair) as TokenContainerUnfinalized); if (container == null) throw StateError('Credential was removed'); return container; From 4b2868524b4d421a44d0a4939df535e2f0ca5228 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:28:04 +0200 Subject: [PATCH 047/285] implementd _getCurrentMethodName to prevent worng method names in logging --- integration_test/add_tokens_test.dart | 19 +- lib/api/token_container_api_endpoint.dart | 17 +- .../riverpod/buildless_listener.dart | 2 +- .../notifier_provider_listener.dart | 2 +- .../state_notifier_provider_listener.dart | 2 +- .../deep_link_listener.dart | 2 +- lib/mains/main_netknights.dart | 2 +- lib/model/processor_result.dart | 2 +- lib/model/push_request.dart | 11 +- lib/model/riverpod_states/token_state.dart | 20 +- lib/model/token_template.dart | 6 +- lib/model/widget_image.dart | 2 +- .../home_widget_navigate_processor.dart | 10 +- ...navigation_scheme_processor_interface.dart | 8 +- .../scheme_processor_interface.dart | 4 +- .../token_container_processor.dart | 4 +- .../google_authenticator_qr_processor.dart | 1 - .../otp_auth_processor.dart | 10 +- ...rivacyidea_authenticator_qr_processor.dart | 6 +- .../aegis_import_file_processor.dart | 4 +- ...thenticator_pro_import_file_processor.dart | 12 +- .../free_otp_plus_import_file_processor.dart | 4 +- ...google_authenticator_qrfile_processor.dart | 10 +- ...a_authenticator_import_file_processor.dart | 2 +- ...token_import_file_processor_interface.dart | 3 +- .../two_fas_import_file_processor.dart | 10 +- .../preference_introduction_repository.dart | 2 - .../preference_token_folder_repository.dart | 10 +- lib/repo/secure_push_request_repository.dart | 2 +- .../secure_token_container_repository.dart | 4 +- lib/repo/secure_token_repository.dart | 32 +-- lib/utils/encryption/token_encryption.dart | 22 +- lib/utils/firebase_utils.dart | 17 +- lib/utils/home_widget_utils.dart | 18 +- lib/utils/logger.dart | 71 +++-- lib/utils/patch_notes_utils.dart | 7 +- lib/utils/pi_mailer.dart | 4 +- lib/utils/privacyidea_io_client.dart | 29 +- lib/utils/push_provider.dart | 67 ++--- .../app_constraints_notifier.dart | 4 +- .../deeplink_notifier.dart | 2 +- .../introduction_provider.dart | 14 +- .../progress_state_provider.dart | 12 +- .../push_request_provider.dart | 57 ++-- .../settings_notifier.dart | 38 +-- .../token_container_notifier.dart | 32 +-- .../token_folder_notifier.dart | 37 +-- .../generated_providers/token_notifier.dart | 258 +++++------------- .../dragging_sortable_provider.dart | 2 +- .../state_providers/home_widget_provider.dart | 2 +- .../status_message_provider.dart | 2 +- .../connectivity_provider.dart | 4 +- .../completed_introduction_notifier.dart | 6 +- .../progress_state_notifier.dart | 10 +- .../push_request_notifier.dart | 40 +-- .../state_notifiers/settings_notifier.dart | 34 +-- .../state_notifiers/sortable_notifier.dart | 2 +- .../state_notifiers/token_notifier.dart | 98 +++---- lib/utils/rsa_utils.dart | 6 +- lib/utils/utils.dart | 2 +- .../labeled_dropdown_button.dart | 3 +- .../pages/import_start_page.dart | 18 +- .../widgets/dialogs/qr_not_found_dialog.dart | 4 +- lib/views/main_view/main_view.dart | 2 +- .../rename_token_folder_action.dart | 2 - .../folder_widgets/token_folder_widget.dart | 4 +- .../main_view_widgets/loading_indicator.dart | 4 +- .../main_view_tokens_list_filtered.dart | 2 +- .../default_edit_action.dart | 7 +- .../default_edit_action_dialog.dart | 7 +- .../default_lock_action.dart | 2 +- .../token_widgets/token_widget_base.dart | 4 +- .../update_firebase_token_dialog.dart | 2 +- lib/views/splash_screen/splash_screen.dart | 10 +- lib/widgets/app_wrapper.dart | 16 +- .../dialog_widgets/push_request_dialog.dart | 8 +- .../sortable_notifier_test.dart | 2 +- 77 files changed, 543 insertions(+), 676 deletions(-) diff --git a/integration_test/add_tokens_test.dart b/integration_test/add_tokens_test.dart index 23707b2ac..fb8fd3e88 100644 --- a/integration_test/add_tokens_test.dart +++ b/integration_test/add_tokens_test.dart @@ -20,6 +20,8 @@ import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/gene import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.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'; +import 'package:privacyidea_authenticator/views/add_token_manually_view/add_token_manually_view_widgets/rows/label_input_field.dart'; +import 'package:privacyidea_authenticator/views/add_token_manually_view/add_token_manually_view_widgets/rows/secret_input_field.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/app_bar_item.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/drag_target_divider.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart'; @@ -110,7 +112,7 @@ Future _addHotpToken(WidgetTester tester) async { await tester.tap(find.byIcon(Icons.add_moderator)); await tester.pump(const Duration(milliseconds: 1000)); expect(find.byType(AddTokenManuallyView), findsOneWidget); - expect(find.byType(TextField), findsNWidgets(2)); + expect(find.byType(TextField), findsNWidgets(3)); expect(find.byType(LabeledDropdownButton), findsOneWidget); expect(find.byType(LabeledDropdownButton), findsOneWidget); expect(find.byType(LabeledDropdownButton), findsOneWidget); @@ -118,11 +120,11 @@ Future _addHotpToken(WidgetTester tester) async { expect(find.text(AppLocalizationsEn().addToken), findsOneWidget); await tester.tap(find.text(AppLocalizationsEn().name)); await tester.pump(); - await tester.enterText(find.byType(TextField).first, 'test'); + await tester.enterText(find.byType(LabelInputField), 'test'); await tester.pump(); await tester.tap(find.text(AppLocalizationsEn().secretKey)); await tester.pump(); - await tester.enterText(find.byType(TextField).last, 'test'); + await tester.enterText(find.byType(SecretInputField), 'AAAABBBB'); await tester.pump(); await tester.tap(find.text(AppLocalizationsEn().addToken)); await tester.pump(const Duration(milliseconds: 1000)); @@ -134,17 +136,18 @@ Future _addTotpToken(WidgetTester tester) async { await tester.pump(const Duration(milliseconds: 1000)); await tester.tap(find.text(AppLocalizationsEn().name)); await tester.pump(); - await tester.enterText(find.byType(TextField).first, 'test'); + await tester.enterText(find.byType(LabelInputField), 'test'); await tester.pump(); await tester.tap(find.text(AppLocalizationsEn().secretKey)); await tester.pump(); - await tester.enterText(find.byType(TextField).last, 'test'); + await tester.enterText(find.byType(SecretInputField), 'AAAABBBB'); await tester.pump(); await tester.tap(find.byType(DropdownButton)); await tester.pump(); await tester.tap(find.text('TOTP')); await tester.pump(); - expect(find.byType(DropdownButton), findsNWidgets(2)); + expect(find.byType(DropdownButton), findsNWidgets(1)); + expect(find.byType(DropdownButton), findsNWidgets(1)); await tester.tap(find.text(AppLocalizationsEn().addToken)); await tester.pump(const Duration(milliseconds: 1000)); } @@ -153,11 +156,11 @@ Future _addDaypasswordToken(WidgetTester tester) async { await tester.pump(); await tester.tap(find.byIcon(Icons.add_moderator)); await tester.pump(const Duration(milliseconds: 1000)); - await tester.enterText(find.byType(TextField).first, 'test'); + await tester.enterText(find.byType(LabelInputField), 'test'); await tester.pump(); await tester.tap(find.text(AppLocalizationsEn().secretKey)); await tester.pump(); - await tester.enterText(find.byType(TextField).last, 'test'); + await tester.enterText(find.byType(SecretInputField), 'AAAABBBB'); await tester.pump(); await tester.tap(find.byType(DropdownButton)); await tester.pump(); diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index 11221ccc9..a1185496c 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -139,15 +139,15 @@ class PrivacyideaContainerApi { } try { - Logger.debug('Received container sync challenge: ${initResponse.body}', name: 'TokenContainerApiEndpoint#sync'); + Logger.debug('Received container sync challenge: ${initResponse.body}'); final piResponse = PiServerResponse.fromResponse(initResponse); if (piResponse.isError) { - Logger.error('Error while getting sync challenge: ${piResponse.asError.resultError}', name: 'TokenContainerApiEndpoint#sync'); + Logger.error('Error while getting sync challenge: ${piResponse.asError.resultError}'); return null; } return piResponse.asSuccess.resultValue; } catch (e, s) { - Logger.error('Error while getting sync challenge: $e', name: 'TokenContainerApiEndpoint#sync', stackTrace: s); + Logger.error('Error while getting sync challenge: $e', stackTrace: s); return null; } } @@ -187,7 +187,7 @@ class PrivacyideaContainerApi { } final containerSyncResponse = PiServerResponse.fromResponse(response); if (containerSyncResponse.isError) { - Logger.error('Error while reciving sync response: ${containerSyncResponse.asError.resultError}', name: 'TokenContainerApiEndpoint#sync'); + Logger.error('Error while reciving sync response: ${containerSyncResponse.asError.resultError}'); return null; } @@ -293,7 +293,6 @@ class PrivacyideaContainerApi { Logger.error( 'Received unexpected response', error: 'StatusCode: ${response.statusCode}', - name: 'TokenContainerApiEndpoint#_showStatusMessage', stackTrace: StackTrace.current, ); } @@ -346,7 +345,7 @@ class PiServerResponse with _$PiServerResponse { PiServerResponseError get asError => this as PiServerResponseError; factory PiServerResponse.fromJson(Map json) { - Logger.debug('Received container sync response: $json', name: 'PiServerResponse#fromJson'); + Logger.debug('Received container sync response: $json'); final map = validateMap( map: json, validators: { @@ -391,11 +390,9 @@ class PiServerResponse with _$PiServerResponse { signature: map[SIGNATURE], ); } - Logger.info( - 'Status: ${result[RESULT_STATUS]}' + Logger.info('Status: ${result[RESULT_STATUS]}' '\nContains error: ${result.containsKey(RESULT_ERROR)}' - '\nContains value: ${result.containsKey(RESULT_VALUE)}', - name: 'PiServerResponse#fromJson'); + '\nContains value: ${result.containsKey(RESULT_VALUE)}'); throw UnimplementedError('Unknown PiServerResponse type'); } diff --git a/lib/interfaces/riverpod/buildless_listener.dart b/lib/interfaces/riverpod/buildless_listener.dart index cd67bdc4c..6edd3a6de 100644 --- a/lib/interfaces/riverpod/buildless_listener.dart +++ b/lib/interfaces/riverpod/buildless_listener.dart @@ -33,7 +33,7 @@ abstract class BuildlessListener, S> { final void Function(S? previous, S next) onNewState; const BuildlessListener({required this.provider, required this.onNewState, required this.listenerName}); void buildListen(WidgetRef ref) { - Logger.debug('("$listenerName") listening to provider ("$provider")', name: 'StateNotifierProviderListener#buildListen'); + Logger.debug('("$listenerName") listening to provider ("$provider")'); ref.listen(provider, (previous, next) { WidgetsBinding.instance.addPostFrameCallback((_) => onNewState(previous, next)); }); diff --git a/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart b/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart index 17d83eef5..b53ea8b68 100644 --- a/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/notifier_provider_listener.dart @@ -29,7 +29,7 @@ abstract class AsyncNotifierProviderListener? previous, AsyncValue next)? onNewState; const AsyncNotifierProviderListener({this.provider, this.onNewState}); void buildListen(WidgetRef ref) { - Logger.debug('Listening to $provider', name: 'AsyncNotifierProviderListener#buildListen'); + Logger.debug('Listening to $provider'); if (provider == null || onNewState == null) return; ref.listen(provider!, onNewState!); } diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart index fd6e8a5a4..d594abf8b 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listener.dart @@ -28,7 +28,7 @@ abstract class StateNotifierProviderListener, S> { final void Function(S? previous, S next) onNewState; const StateNotifierProviderListener({required this.provider, required this.onNewState, required this.listenerName}); void buildListen(WidgetRef ref) { - Logger.debug('("$listenerName") listening to provider ("$provider")', name: 'StateNotifierProviderListener#buildListen'); + Logger.debug('("$listenerName") listening to provider ("$provider")'); ref.listen(provider, (previous, next) { WidgetsBinding.instance.addPostFrameCallback((_) => onNewState(previous, next)); }); diff --git a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart index e8b4ce4e8..7fad76348 100644 --- a/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart +++ b/lib/interfaces/riverpod/state_listeners/state_notifier_provider_listeners/deep_link_listener.dart @@ -38,7 +38,7 @@ abstract class StreamNotifierProviderListener, S> { final void Function(AsyncValue? previous, AsyncValue next) onNewState; const StreamNotifierProviderListener({required this.provider, required this.onNewState, required this.listenerName}); void buildListen(WidgetRef ref) { - Logger.debug('("$listenerName") listening to provider ("$provider")', name: 'StateNotifierProviderListener#buildListen'); + Logger.debug('("$listenerName") listening to provider ("$provider")'); ref.listen(provider, (previous, next) { WidgetsBinding.instance.addPostFrameCallback((_) => onNewState(previous, next)); }); diff --git a/lib/mains/main_netknights.dart b/lib/mains/main_netknights.dart index b949aee7b..e74dd0d6f 100644 --- a/lib/mains/main_netknights.dart +++ b/lib/mains/main_netknights.dart @@ -59,7 +59,7 @@ void main() async { ); await app.setAutomaticDataCollectionEnabled(false); if (app.isAutomaticDataCollectionEnabled) { - Logger.error('Automatic data collection should not be enabled', name: 'main.dart#main'); + Logger.error('Automatic data collection should not be enabled'); } runApp(AppWrapper(child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization))); }); diff --git a/lib/model/processor_result.dart b/lib/model/processor_result.dart index c52a4961c..63974ad0c 100644 --- a/lib/model/processor_result.dart +++ b/lib/model/processor_result.dart @@ -56,7 +56,7 @@ extension ListProcessorResult on List> { final results = toList(); if (results.isEmpty) { showMessage(message: 'No data found in QR code.', duration: const Duration(seconds: 3)); - Logger.warning('No data found in QR code.', name: 'token_notifier.dart#_getData'); + Logger.warning('No data found in QR code.'); return []; } final failedResults = results.whereType().toList(); diff --git a/lib/model/push_request.dart b/lib/model/push_request.dart index eccd5d8ca..82e8b18f5 100644 --- a/lib/model/push_request.dart +++ b/lib/model/push_request.dart @@ -114,7 +114,7 @@ class PushRequest { try { verifyData(data); } catch (e, s) { - Logger.error('Invalid push request data.', name: 'push_request.dart#fromMessageData', error: e, stackTrace: s); + Logger.error('Invalid push request data.', error: e, stackTrace: s); } return PushRequest( title: data[PUSH_REQUEST_TITLE], @@ -159,12 +159,12 @@ class PushRequest { if (data[PUSH_REQUEST_ANSWERS] is! String?) { throw ArgumentError('Push request answers is ${data[PUSH_REQUEST_ANSWERS].runtimeType}. Expected List or null.'); } - Logger.debug('Push request data ($data) is valid.', name: 'push_request.dart#verifyData'); + Logger.debug('Push request data ($data) is valid.'); } Future verifySignature(PushToken token, {RsaUtils rsaUtils = const RsaUtils()}) async { //5NV6KJCFCLNQURT2ZTBRHHGY6FDXOCOR|http://192.168.178.22:5000/ttype/push|PIPU0000E793|Pick a Number!|privacyIDEA|0|["A", "B", "C"] - Logger.info('Adding push request to token', name: 'push_request_notifier.dart#newRequest'); + Logger.info('Adding push request to token'); String signedData = '$nonce|' '$uri|' '$serial|' @@ -172,7 +172,7 @@ class PushRequest { '$title|' '${sslVerify ? '1' : '0'}' '${possibleAnswers != null ? '|${possibleAnswers!.join(",")}' : ''}'; - Logger.warning('Signed data: $signedData', name: 'push_request_notifier.dart#newRequest'); + Logger.warning('Signed data: $signedData'); // Re-add url and sslverify to android legacy tokens: if (token.url == null) { @@ -184,12 +184,11 @@ class PushRequest { if (!isVerified) { Logger.warning( 'Validating incoming message failed.', - name: 'token_notifier.dart#addPushRequestToToken', error: 'Signature does not match signed data.', ); return false; } - Logger.info('Validating incoming message was successful.', name: 'token_notifier.dart#addPushRequestToToken'); + Logger.info('Validating incoming message was successful.'); return true; } } diff --git a/lib/model/riverpod_states/token_state.dart b/lib/model/riverpod_states/token_state.dart index 7c5eeb7c3..41a03c642 100644 --- a/lib/model/riverpod_states/token_state.dart +++ b/lib/model/riverpod_states/token_state.dart @@ -56,7 +56,7 @@ class TokenState { List get tokensNotInContainer { final tokensNotInContainer = tokens.maybePiTokens.where((token) => token.containerSerial != null).toList(); - Logger.debug('${tokensNotInContainer.length}/${tokens.length} tokens not in container', name: 'token_state.dart#tokensNotInContainer'); + Logger.debug('${tokensNotInContainer.length}/${tokens.length} tokens not in container'); return tokensNotInContainer; } @@ -102,7 +102,7 @@ class TokenState { TokenState withoutTokens(List tokens) { final newTokens = List.from(this.tokens); newTokens.removeWhere((element) => tokens.any((token) { - Logger.debug('token.id ${token.id} == element.id ${element.id}', name: 'token_state.dart#withoutTokens'); + Logger.debug('token.id ${token.id} == element.id ${element.id}'); return token.id == element.id; })); return TokenState(tokens: newTokens, lastlyUpdatedTokens: const [], lastlyDeletedTokens: tokens); @@ -129,7 +129,7 @@ class TokenState { final newTokens = tokens.toList(); final index = newTokens.indexWhere((element) => element.id == token.id); if (index == -1) { - Logger.warning('Tried to replace a token that does not exist.', name: 'token_state.dart#replaceToken'); + Logger.warning('Tried to replace a token that does not exist.'); return (this, false); } newTokens[index] = token; @@ -163,7 +163,7 @@ class TokenState { for (var token in tokens) { final index = newTokens.indexWhere((element) => element.id == token.id); if (index == -1) { - Logger.warning('Tried to replace a token that does not exist.', name: 'token_state.dart#replaceToken'); + Logger.warning('Tried to replace a token that does not exist.'); failedToReplace.add(token); continue; } @@ -179,9 +179,9 @@ class TokenState { List containerTokens(String containerSerial) { final piTokens = tokens.piTokens; - Logger.debug('PiTokens: ${piTokens}', name: 'token_state.dart#containerTokens'); + Logger.debug('PiTokens: ${piTokens}'); final containerTokens = piTokens.ofContainer(containerSerial); - Logger.debug('${containerTokens.length}/${piTokens.length} tokens with containerSerial: $containerSerial', name: 'token_state.dart#containerTokens'); + Logger.debug('${containerTokens.length}/${piTokens.length} tokens with containerSerial: $containerSerial'); return containerTokens; } } @@ -189,19 +189,19 @@ class TokenState { extension TokenListExtension on List { List get piTokens { final piTokens = where((token) => token.isPrivacyIdeaToken == true).toList(); - Logger.debug('${piTokens.length}/$length tokens with "isPrivacyIdeaToken == true"', name: 'token_state.dart#piTokens'); + Logger.debug('${piTokens.length}/$length tokens with "isPrivacyIdeaToken == true"'); return piTokens; } List get nonPiTokens { final nonPiTokens = where((token) => token.isPrivacyIdeaToken == false).toList(); - Logger.debug('${nonPiTokens.length}/$length tokens with "isPrivacyIdeaToken == false"', name: 'token_state.dart#nonPiTokens'); + Logger.debug('${nonPiTokens.length}/$length tokens with "isPrivacyIdeaToken == false"'); return nonPiTokens; } List get maybePiTokens { final maybePiTokens = where((token) => token.isPrivacyIdeaToken == null).toList(); - Logger.debug('${maybePiTokens.length}/$length tokens with "isPrivacyIdeaToken == null"', name: 'token_state.dart#maybePiTokens'); + Logger.debug('${maybePiTokens.length}/$length tokens with "isPrivacyIdeaToken == null"'); return maybePiTokens; } @@ -221,7 +221,7 @@ extension TokenListExtension on List { List ofContainer(String containerSerial) { final filtered = where((token) => token.origin?.source == TokenOriginSourceType.container && token.containerSerial == containerSerial).toList(); - Logger.debug('${filtered.length}/$length tokens with containerSerial: $containerSerial', name: 'token_state.dart#fromContainer'); + Logger.debug('${filtered.length}/$length tokens with containerSerial: $containerSerial'); return filtered; } diff --git a/lib/model/token_template.dart b/lib/model/token_template.dart index 312b044ab..75bbb9fc5 100644 --- a/lib/model/token_template.dart +++ b/lib/model/token_template.dart @@ -146,7 +146,7 @@ class TokenTemplate with _$TokenTemplate { } // bool hasSameValuesAs(TokenTemplate serverTokenTemplate) { - // Logger.debug('serverTokenTemplate.keys: ${serverTokenTemplate.keys}', name: 'TokenTemplate#hasSameValuesAs'); + // Logger.debug('serverTokenTemplate.keys: ${serverTokenTemplate.keys}'); // for (var key in serverTokenTemplate.keys) { // if (otpAuthMap[key] != serverTokenTemplate.otpAuthMap[key]) { // Logger.debug('TokenTemplate has different values for key "$key": ${otpAuthMap[key]} != ${serverTokenTemplate.otpAuthMap[key]}', @@ -162,9 +162,9 @@ class TokenTemplate with _$TokenTemplate { // } // bool tokenWouldBeUpdated(Token token) { - // Logger.debug('Checking if token would be updated', name: 'TokenTemplate#tokenWouldBeUpdated'); + // Logger.debug('Checking if token would be updated'); // final tokenTemplate = token.toTemplate(this); - // Logger.debug('TokenTemplate: \n$tokenTemplate\n has same values as \n$this\n ?', name: 'TokenTemplate#tokenWouldBeUpdated'); + // Logger.debug('TokenTemplate: \n$tokenTemplate\n has same values as \n$this\n ?'); // return tokenTemplate?.hasSameValuesAs(this) == false; // } } diff --git a/lib/model/widget_image.dart b/lib/model/widget_image.dart index 4bcd4d801..0c22b0bcb 100644 --- a/lib/model/widget_image.dart +++ b/lib/model/widget_image.dart @@ -62,7 +62,7 @@ class WidgetImage { try { return fileType.buildImageWidget(imageData); } catch (e) { - Logger.error('File type $fileType is not supported or does not match the image data.', name: 'WidgetImage#_buildImageWidget'); + Logger.error('File type $fileType is not supported or does not match the image data.'); rethrow; } } diff --git a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart index c22bbdff3..561d42740 100644 --- a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart +++ b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart @@ -99,11 +99,11 @@ class HomeWidgetNavigateProcessor implements NavigationSchemeProcessor { static Future>?> _showLockedHomeWidgetProcessor(Uri uri, BuildContext context, {bool fromInit = false}) async { if (uri.host != 'showlocked') { - Logger.warning('Invalid host for showlocked: ${uri.host}', name: 'home_widget_processor.dart#_showLockedHomeWidgetProcessor'); + Logger.warning('Invalid host for showlocked: ${uri.host}'); return []; } if (uri.queryParameters['id'] == null) { - Logger.warning('Invalid query parameters for showlocked: ${uri.queryParameters}', name: 'home_widget_processor.dart#_showLockedHomeWidgetProcessor'); + Logger.warning('Invalid query parameters for showlocked: ${uri.queryParameters}'); return [ ProcessorResult.failed( 'Missing id for showlocked: ${uri.host}', @@ -111,12 +111,12 @@ class HomeWidgetNavigateProcessor implements NavigationSchemeProcessor { ) ]; } - Logger.info('Showing otp of locked Token of homeWidget: ${uri.queryParameters['id']}', name: 'home_widget_processor.dart#_showLockedHomeWidgetProcessor'); + Logger.info('Showing otp of locked Token of homeWidget: ${uri.queryParameters['id']}'); Navigator.popUntil(context, (route) => route.isFirst); final tokenId = await HomeWidgetUtils().getTokenIdOfWidgetId(uri.queryParameters['id']!); if (tokenId == null) { - Logger.warning('Could not find token for widget id: ${uri.queryParameters['id']}', name: 'home_widget_processor.dart#_showLockedHomeWidgetProcessor'); + Logger.warning('Could not find token for widget id: ${uri.queryParameters['id']}'); return [ ProcessorResult.failed( 'Could not find token for widget id: ${uri.queryParameters['id}']}', @@ -126,7 +126,7 @@ class HomeWidgetNavigateProcessor implements NavigationSchemeProcessor { } await Future.delayed(const Duration(milliseconds: 200)); if (globalRef == null) { - Logger.warning('Could not find globalRef', name: 'home_widget_processor.dart#_showLockedHomeWidgetProcessor'); + Logger.warning('Could not find globalRef'); return [ const ProcessorResult.failed( 'Could not find globalRef', diff --git a/lib/processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart b/lib/processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart index fc1b13164..07a333e8d 100644 --- a/lib/processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart +++ b/lib/processors/scheme_processors/navigation_scheme_processors/navigation_scheme_processor_interface.dart @@ -37,16 +37,16 @@ abstract class NavigationSchemeProcessor implements SchemeProcessor { static Future processUriByAny(Uri uri, {BuildContext? context, required bool fromInit}) async { if (context == null) { - Logger.info('Current context is null, waiting for navigator context', name: 'processUri#NavigationSchemeProcessor'); + Logger.info('Current context is null, waiting for navigator context'); final key = await contextedGlobalNavigatorKey; context = key.currentContext; } - Logger.info('Processing scheme: ${uri.scheme}', name: 'processUri#NavigationSchemeProcessor'); + Logger.info('Processing scheme: ${uri.scheme}'); final futures = >[]; for (final processor in implementations) { - Logger.info('Supported schemes [${processor.supportedSchemes}] for processor ${processor.runtimeType}', name: 'processUri#NavigationSchemeProcessor'); + Logger.info('Supported schemes [${processor.supportedSchemes}] for processor ${processor.runtimeType}'); if (processor.supportedSchemes.contains(uri.scheme)) { - Logger.info('Processing scheme ${uri.scheme} with ${processor.runtimeType}', name: 'processUri#NavigationSchemeProcessor'); + Logger.info('Processing scheme ${uri.scheme} with ${processor.runtimeType}'); // ignoring use_build_context_synchronously is ok because we got the context after the await. The Context cannot be expired. // ignore: use_build_context_synchronously futures.add(processor.processUri(uri, context: context, fromInit: fromInit)); diff --git a/lib/processors/scheme_processors/scheme_processor_interface.dart b/lib/processors/scheme_processors/scheme_processor_interface.dart index 278bfbca7..8a07bbf66 100644 --- a/lib/processors/scheme_processors/scheme_processor_interface.dart +++ b/lib/processors/scheme_processors/scheme_processor_interface.dart @@ -40,14 +40,14 @@ abstract class SchemeProcessor { static Future>?> processUriByAny(Uri uri, {bool fromInit = false}) async { for (SchemeProcessor processor in implementations) { if (processor.supportedSchemes.contains(uri.scheme)) { - Logger.info('Processing URI with processor: $processor', name: 'SchemeProcessor#processUriByAny'); + Logger.info('Processing URI with processor: $processor'); final result = await processor.processUri(uri, fromInit: fromInit); if (result != null) { return result; } } } - Logger.warning('Unsupported scheme', name: 'SchemeProcessor#processUriByAny'); + Logger.warning('Unsupported scheme'); return null; } } diff --git a/lib/processors/scheme_processors/token_container_processor.dart b/lib/processors/scheme_processors/token_container_processor.dart index 53cc5720a..f2ab7ebb6 100644 --- a/lib/processors/scheme_processors/token_container_processor.dart +++ b/lib/processors/scheme_processors/token_container_processor.dart @@ -44,7 +44,7 @@ class TokenContainerProcessor extends SchemeProcessor { try { final container = TokenContainer.fromUriMap(uri.queryParameters); - Logger.info('Successfully parsed container container', name: 'TokenContainerProcessor#processUri'); + Logger.info('Successfully parsed container container'); return [ ProcessorResult.success( container, @@ -52,7 +52,7 @@ class TokenContainerProcessor extends SchemeProcessor { ) ]; } on LocalizedArgumentError catch (e) { - Logger.warning('Error while processing URI ${uri.scheme}', error: e.message, name: 'TokenContainerProcessor#processUri'); + Logger.warning('Error while processing URI ${uri.scheme}', error: e.message); return [ ProcessorResult.failed( e.localizedMessage(AppLocalizations.of(await globalContext)!), diff --git a/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart b/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart index 6ac05a12f..e831b51e4 100644 --- a/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart +++ b/lib/processors/scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart @@ -120,7 +120,6 @@ class GoogleAuthenticatorQrProcessor extends TokenImportSchemeProcessor { } catch (e) { Logger.error( "Skipping token ${param.name} due to error: $e", - name: "GoogleAuthenticatorQrProcessor#processUri", error: e, stackTrace: StackTrace.current, ); 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 2d9190595..d2ea66c6e 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 @@ -53,7 +53,7 @@ class OtpAuthProcessor extends TokenImportSchemeProcessor { ) ]; } - Logger.info('Try to handle otpAuth:', name: 'token_notifier.dart#addTokenFromOtpAuth'); + Logger.info('Try to handle otpAuth:'); // The values from queryParameters are always strings. Map queryParameters = {...uri.queryParameters}; @@ -216,7 +216,7 @@ void _logInfo(Uri uri) { queryParameters.forEach((key, value) { infoLog += '\n${key.padLeft(9)} | $value'; }); - Logger.info(infoLog, name: 'parsing_utils.dart#_parseOtpAuth'); + Logger.info(infoLog); } // This is a fix for omitted padding in base32 encoded secrets. @@ -231,10 +231,10 @@ Map _secretAddPadding(Map queryParameters) { String _parseTokenType(Uri uri) { if (_parseIssuer(uri) == "Steam") return TokenTypes.STEAM.name; - Logger.debug('Token type host: ${uri.host}', name: 'otp_auth_processor.dart#_parseTokenType'); - Logger.debug('Token type queryParameters: ${uri.queryParameters[OTP_AUTH_TYPE]}', name: 'otp_auth_processor.dart#_parseTokenType'); + Logger.debug('Token type host: ${uri.host}'); + Logger.debug('Token type queryParameters: ${uri.queryParameters[OTP_AUTH_TYPE]}'); final value = uri.queryParameters[OTP_AUTH_TYPE] ?? uri.host; - Logger.debug('Token type value: $value', name: 'otp_auth_processor.dart#_parseTokenType'); + Logger.debug('Token type value: $value'); return validate( value: uri.queryParameters[OTP_AUTH_TYPE] ?? uri.host, validator: ObjectValidator(defaultValue: uri.host), 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 6c68c5300..b800c1582 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 @@ -36,10 +36,10 @@ class PrivacyIDEAAuthenticatorQrProcessor extends TokenImportSchemeProcessor { Future>?> processUri(Uri uri, {bool fromInit = false}) async { if (!supportedSchemes.contains(uri.scheme)) return null; if (uri.host != host) return null; - Logger.info('Processing URI with scheme: ${uri.scheme} and host: ${uri.host}', name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); + Logger.info('Processing URI with scheme: ${uri.scheme} and host: ${uri.host}'); try { final token = TokenEncryption.fromExportUri(uri); - Logger.info('Processing URI ${uri.scheme} succeded', name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); + Logger.info('Processing URI ${uri.scheme} succeded'); return [ ProcessorResult.success( token, @@ -47,7 +47,7 @@ class PrivacyIDEAAuthenticatorQrProcessor extends TokenImportSchemeProcessor { ) ]; } catch (e) { - Logger.error('Error while processing URI ${uri.scheme}', error: e, name: 'PrivacyIDEAAuthenticatorQrProcessor#processUri'); + Logger.error('Error while processing URI ${uri.scheme}', error: e); return [ ProcessorResult.failed( 'Invalid URI', 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 cc5a0e9ca..38d13e9fb 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 @@ -230,7 +230,7 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { resultHandlerType: resultHandlerType, )); } catch (e) { - Logger.error('Failed to parse token.', name: 'AegisImportFileProcessor#_processPlain', error: e, stackTrace: StackTrace.current); + Logger.error('Failed to parse token.', error: e, stackTrace: StackTrace.current); results.add(ProcessorResult.failed( e.toString(), resultHandlerType: resultHandlerType, @@ -291,7 +291,7 @@ class AegisImportFileProcessor extends TokenImportFileProcessor { resultHandlerType: resultHandlerType, )); } catch (e) { - Logger.error('Failed to parse token.', name: 'AegisImportFileProcessor#_processPlain', error: e, stackTrace: StackTrace.current); + Logger.error('Failed to parse token.', error: e, stackTrace: StackTrace.current); results.add(ProcessorResultFailed( e.toString(), resultHandlerType: resultHandlerType, 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 4fd926bcc..703fe7a1d 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 @@ -95,7 +95,6 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { 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) { @@ -111,7 +110,6 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { 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 @@ -145,7 +143,7 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { Uint8Buffer uint8buffer = Uint8Buffer(data: bytes); final headerByteLength = utf8.encode(header).length; final fileHeader = utf8.decode(uint8buffer.readBytes(headerByteLength)); - Logger.info('File header: $fileHeader', name: 'authenticator_pro_import_file_processor#processFile'); + Logger.info('File header: $fileHeader'); final plainText = switch (fileHeader) { header => await _processEncrypted(uint8buffer: uint8buffer, password: password ?? ''), headerLegacy => await _processEncryptedLegacy(uint8buffer: uint8buffer, password: password ?? ''), @@ -258,7 +256,7 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { resultHandlerType: resultHandlerType, )); } catch (e) { - Logger.error('Failed to parse token.', name: 'authenticator_pro_import_file_processor#_processUriList', error: e, stackTrace: StackTrace.current); + Logger.error('Failed to parse token.', error: e, stackTrace: StackTrace.current); results.add(ProcessorResultFailed( e.toString(), resultHandlerType: resultHandlerType, @@ -300,7 +298,7 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { resultHandlerType: resultHandlerType, )); } catch (e) { - Logger.error('Failed to parse token.', name: 'authenticator_pro_import_file_processor#_processHtml', error: e, stackTrace: StackTrace.current); + Logger.error('Failed to parse token.', error: e, stackTrace: StackTrace.current); results.add(ProcessorResultFailed( e.toString(), resultHandlerType: resultHandlerType, @@ -310,7 +308,7 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { } Future>> _processJson({required List> tokensMap}) async { - Logger.info('Processing plain file', name: 'authenticator_pro_import_file_processor#_processAuthPro'); + Logger.info('Processing plain file'); final result = >[]; for (var tokenMap in tokensMap) { try { @@ -365,7 +363,7 @@ class AuthenticatorProImportFileProcessor extends TokenImportFileProcessor { resultHandlerType: resultHandlerType, )); } catch (e) { - Logger.error('Failed to parse token.', name: 'authenticator_pro_import_file_processor#_processAuthPro', error: e, stackTrace: StackTrace.current); + Logger.error('Failed to parse token.', error: e, stackTrace: StackTrace.current); result.add(ProcessorResultFailed( e.toString(), resultHandlerType: resultHandlerType, diff --git a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart index 42d72dd99..289122d3a 100644 --- a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart @@ -91,7 +91,7 @@ class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { final uri = Uri.parse(line); results.addAll(await const FreeOtpPlusQrProcessor().processUri(uri)); } catch (e) { - Logger.error('Failed to process line: $line', name: 'FreeOtpPlusFileProcessor#processFile', error: e, stackTrace: StackTrace.current); + Logger.error('Failed to process line: $line', error: e, stackTrace: StackTrace.current); results.add(ProcessorResultFailed( e.toString(), resultHandlerType: resultHandlerType, @@ -145,7 +145,7 @@ class FreeOtpPlusImportFileProcessor extends TokenImportFileProcessor { resultHandlerType: resultHandlerType, ); } catch (e, s) { - Logger.warning('Failed to parse token.', name: 'FreeOtpPlusFileProcessor#_processJsonToken', error: e, stackTrace: s); + Logger.warning('Failed to parse token.', error: e, stackTrace: s); return ProcessorResultFailed( e.toString(), resultHandlerType: resultHandlerType, diff --git a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart index 499d1ae4e..f3312d12c 100644 --- a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart +++ b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart @@ -43,7 +43,7 @@ class GoogleAuthenticatorQrfileProcessor extends TokenImportFileProcessor { try { final img_lib.Image? qrImage = img_lib.decodeImage(await file.readAsBytes()); if (qrImage == null) { - Logger.warning("Error decoding file to image..", name: "_pickQrFile#ImportStartPage"); + Logger.warning("Error decoding file to image.."); return Future.value(false); } return Future.value(true); @@ -60,7 +60,7 @@ class GoogleAuthenticatorQrfileProcessor extends TokenImportFileProcessor { Result? qrResult; img_lib.Image? qrImage = img_lib.decodeImage(await file.readAsBytes()); if (qrImage == null) { - Logger.warning("Error decoding file to image..", name: "_pickQrFile#ImportStartPage"); + Logger.warning("Error decoding file to image.."); throw Exception("Error decoding file to image.."); //TODO: Better error handling } int maxZoomLevel = 10; @@ -90,7 +90,7 @@ class GoogleAuthenticatorQrfileProcessor extends TokenImportFileProcessor { } } if (qrResult == null) { - Logger.warning("Error decoding QR file..", name: "_pickQrFile#ImportStartPage"); + Logger.warning("Error decoding QR file.."); throw NotFoundException(); } @@ -98,12 +98,12 @@ class GoogleAuthenticatorQrfileProcessor extends TokenImportFileProcessor { try { uri = Uri.parse(qrResult.text); } on FormatException catch (_) { - Logger.warning("Error parsing QR file content..", name: "_pickQrFile#ImportStartPage"); + Logger.warning("Error parsing QR file content.."); throw FormatReaderException(); } var processorResults = await const GoogleAuthenticatorQrProcessor().processUri(uri); if (processorResults.isEmpty) { - Logger.warning("Error processing QR file content..", name: "_pickQrFile#ImportStartPage"); + Logger.warning("Error processing QR file content.."); throw FormatReaderException(); } processorResults = processorResults.map>((t) { 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 9adf79b9b..f32f6ebff 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 @@ -69,7 +69,7 @@ class PrivacyIDEAAuthenticatorImportFileProcessor extends TokenImportFileProcess return results; } catch (e) { if (e is BadDecryptionPasswordException) rethrow; - Logger.error('Failed to process file', name: 'PrivacyIDEAAuthenticatorImportFileProcessor#processFile', error: e, stackTrace: StackTrace.current); + Logger.error('Failed to process file', error: e, stackTrace: StackTrace.current); return [ ProcessorResult.failed( e.toString(), 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 09fc782c9..576ce2d58 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 @@ -49,8 +49,7 @@ abstract class TokenImportFileProcessor with TokenImportProcessor>()); } @@ -160,14 +160,14 @@ class TwoFasAuthenticatorImportFileProcessor extends TokenImportFileProcessor { resultHandlerType: resultHandlerType, )); } catch (e) { - Logger.error('Failed to parse token.', name: 'two_fas_import_file_processor.dart#_processPlainTokens', error: e, stackTrace: StackTrace.current); + Logger.error('Failed to parse token.', error: e, stackTrace: StackTrace.current); results.add(ProcessorResultFailed( e.toString(), resultHandlerType: resultHandlerType, )); } } - Logger.info('successfully imported ${results.length} tokens', name: 'two_fas_import_file_processor.dart#processPlainTokens'); + Logger.info('successfully imported ${results.length} tokens'); return results; } diff --git a/lib/repo/preference_introduction_repository.dart b/lib/repo/preference_introduction_repository.dart index 73aecdbd8..0ab4aa065 100644 --- a/lib/repo/preference_introduction_repository.dart +++ b/lib/repo/preference_introduction_repository.dart @@ -46,7 +46,6 @@ class PreferenceIntroductionRepository implements IntroductionRepository { } catch (e, s) { Logger.warning( 'Failed to load completed introductions', - name: 'PreferenceIntroductionRepository#loadCompletedIntroductions', error: e, stackTrace: s, verbose: true, @@ -65,7 +64,6 @@ class PreferenceIntroductionRepository implements IntroductionRepository { } catch (e, s) { Logger.warning( 'Failed to load completed introductions', - name: 'PreferenceIntroductionRepository#loadCompletedIntroductions', error: e, stackTrace: s, verbose: true, diff --git a/lib/repo/preference_token_folder_repository.dart b/lib/repo/preference_token_folder_repository.dart index 84ea64ae3..70bca3db7 100644 --- a/lib/repo/preference_token_folder_repository.dart +++ b/lib/repo/preference_token_folder_repository.dart @@ -44,11 +44,11 @@ class PreferenceTokenFolderRepository extends TokenFolderRepository { if (foldersString == null) return const TokenFolderState(folders: []); final jsons = jsonDecode(foldersString) as List; final folders = jsons.map((e) => TokenFolder.fromJson(e)).toList(); - Logger.info('Loaded ${folders.length} folders from preferences', name: 'PreferenceTokenFolderRepository#loadFolders'); + Logger.info('Loaded ${folders.length} folders from preferences'); return TokenFolderState(folders: folders); } catch (e, s) { - Logger.error('Failed to load folders', name: 'PreferenceTokenFolderRepository#loadFolders', error: e, stackTrace: s); + Logger.error('Failed to load folders', error: e, stackTrace: s); return const TokenFolderState(folders: []); } } @@ -57,16 +57,16 @@ class PreferenceTokenFolderRepository extends TokenFolderRepository { Future saveState(TokenFolderState state) => _protect(() => _saveReplaceList(state)); Future _saveReplaceList(TokenFolderState state) async { final folders = state.folders; - Logger.info('Saving ${folders.length} folders to preferences...', name: 'PreferenceTokenFolderRepository#saveReplaceList'); + Logger.info('Saving ${folders.length} folders to preferences...'); try { final jsons = folders.map((e) => e.toJson()).toList(); final json = jsonEncode(jsons); await _prefs.then((prefs) => prefs.setString(_tokenFoldersKey, json)); - Logger.info('Saved ${folders.length} folders to preferences', name: 'PreferenceTokenFolderRepository#saveFolders'); + Logger.info('Saved ${folders.length} folders to preferences'); return true; } catch (e, s) { - Logger.error('Failed to save folders', name: 'PreferenceTokenFolderRepository#saveFolders', error: e, stackTrace: s); + Logger.error('Failed to save folders', error: e, stackTrace: s); return false; } diff --git a/lib/repo/secure_push_request_repository.dart b/lib/repo/secure_push_request_repository.dart index 31e17b7a2..81a2c9870 100644 --- a/lib/repo/secure_push_request_repository.dart +++ b/lib/repo/secure_push_request_repository.dart @@ -50,7 +50,7 @@ class SecurePushRequestRepository implements PushRequestRepository { Future saveState(PushRequestState pushRequestState) => protect(() => _saveState(pushRequestState)); Future _saveState(PushRequestState pushRequestState) async { final stateJson = jsonEncode(pushRequestState.toJson()); - Logger.debug('Saving state: $stateJson', name: 'SecurePushRequestRepository'); + Logger.debug('Saving state: $stateJson'); await _storage.write(key: _securePushRequestKey, value: stateJson); } diff --git a/lib/repo/secure_token_container_repository.dart b/lib/repo/secure_token_container_repository.dart index e1186fdd7..a464c82d9 100644 --- a/lib/repo/secure_token_container_repository.dart +++ b/lib/repo/secure_token_container_repository.dart @@ -24,13 +24,13 @@ class SecureTokenContainerRepository extends TokenContainerRepository { @override Future loadCredentialsState() async { final containerJsonString = await _readAll(); - Logger.warning('Loaded container: $containerJsonString', name: 'SecureTokenContainerRepository'); + Logger.warning('Loaded container: $containerJsonString'); return TokenContainerState.fromJsonStringList(containerJsonString.values.toList()); } @override Future saveCredentialsState(TokenContainerState containerState) async { - Logger.warning('Saving container: $containerState', name: 'SecureTokenContainerRepository'); + Logger.warning('Saving container: $containerState'); final futures = []; for (var container in containerState.container) { futures.add(saveCredential(container)); diff --git a/lib/repo/secure_token_repository.dart b/lib/repo/secure_token_repository.dart index cc32f8388..449b8234c 100644 --- a/lib/repo/secure_token_repository.dart +++ b/lib/repo/secure_token_repository.dart @@ -61,9 +61,9 @@ class SecureTokenRepository implements TokenRepository { Future loadToken(String id) => _protect(() => _loadToken(id)); Future _loadToken(String id) async { final token = await _storage.read(key: _TOKEN_PREFIX + id); - Logger.info('Loading token from secure storage: $id', name: 'secure_token_repository.dart#loadToken'); + Logger.info('Loading token from secure storage: $id'); if (token == null) { - Logger.warning('Token not found in secure storage', name: 'secure_token_repository.dart#loadToken'); + Logger.warning('Token not found in secure storage'); return null; } return Token.fromJson(jsonDecode(token)); @@ -79,7 +79,7 @@ class SecureTokenRepository implements TokenRepository { try { keyValueMap = await _storage.readAll(); } on PlatformException catch (e, s) { - Logger.warning("Token found, but could not be decrypted.", name: 'secure_token_repository.dart#loadTokens', error: e, stackTrace: s, verbose: true); + Logger.warning("Token found, but could not be decrypted.", error: e, stackTrace: s, verbose: true); _decryptErrorDialog(); return []; } @@ -100,28 +100,28 @@ class SecureTokenRepository implements TokenRepository { valueJson = jsonDecode(value); } on FormatException catch (_) { // Value should be a json. Skip everything that is not a json. - Logger.debug('Value is not a json', name: 'secure_token_repository.dart#loadTokens'); + Logger.debug('Value is not a json'); continue; } if (valueJson == null) { // If valueJson is null or does not contain a type, it can't be a token. Skip it. - Logger.debug('Value Json is null', name: 'secure_token_repository.dart#loadTokens'); + Logger.debug('Value Json is null'); continue; } if (!valueJson.containsKey('type')) { // If valueJson is null or does not contain a type, it can't be a token. Skip it. - Logger.debug('Value Json does not contain a type', name: 'secure_token_repository.dart#loadTokens'); + Logger.debug('Value Json does not contain a type'); continue; } // TODO token.version might be deprecated, is there a reason to use it? // TODO when the token version (token.version) changed handle this here. - Logger.info('Loading token from secure storage: ${valueJson['id']}', name: 'secure_token_repository.dart#loadTokens'); + Logger.info('Loading token from secure storage: ${valueJson['id']}'); try { tokenList.add(Token.fromJson(valueJson)); } catch (e, s) { - Logger.error('Could not load token from secure storage', name: 'secure_token_repository.dart#loadTokens', error: e, stackTrace: s); + Logger.error('Could not load token from secure storage', error: e, stackTrace: s); } } @@ -144,11 +144,13 @@ class SecureTokenRepository implements TokenRepository { if (failedTokens.isNotEmpty) { Logger.error( 'Could not save all tokens (${tokens.length - failedTokens.length}/${tokens.length}) to secure storage', - name: 'secure_token_repository.dart#saveOrReplaceTokens', stackTrace: StackTrace.current, ); } else { - Logger.info('Saved ${tokens.length}/${tokens.length} tokens to secure storage', name: 'secure_token_repository.dart#saveOrReplaceTokens', stackTrace: StackTrace.current,); + Logger.info( + 'Saved ${tokens.length}/${tokens.length} tokens to secure storage', + stackTrace: StackTrace.current, + ); } return failedTokens; } @@ -159,7 +161,7 @@ class SecureTokenRepository implements TokenRepository { try { await _storage.write(key: _TOKEN_PREFIX + token.id, value: jsonEncode(token.toJson())); } catch (e) { - Logger.warning('Could not save token to secure storage', error: e, name: 'secure_token_repository.dart#saveOrReplaceToken', verbose: true); + Logger.warning('Could not save token to secure storage', error: e, verbose: true); return false; } return true; @@ -176,8 +178,7 @@ class SecureTokenRepository implements TokenRepository { } } if (failedTokens.isNotEmpty) { - Logger.warning('Could not delete all tokens from secure storage', - name: 'secure_token_repository.dart#deleteTokens', error: 'Failed tokens: $failedTokens', stackTrace: StackTrace.current); + Logger.warning('Could not delete all tokens from secure storage', error: 'Failed tokens: $failedTokens', stackTrace: StackTrace.current); } return failedTokens; } @@ -189,7 +190,7 @@ class SecureTokenRepository implements TokenRepository { try { _storage.delete(key: _TOKEN_PREFIX + token.id); } catch (e, s) { - Logger.warning('Could not delete token from secure storage', name: 'secure_token_repository.dart#deleteToken', error: e, stackTrace: s); + Logger.warning('Could not delete token from secure storage', error: e, stackTrace: s); return false; } Logger.info('Token deleted from secure storage'); @@ -223,7 +224,7 @@ class SecureTokenRepository implements TokenRepository { DefaultDialogButton( child: Text(AppLocalizations.of(context)!.decryptErrorButtonSendError), onPressed: () async { - Logger.info('Sending error report', name: 'secure_token_repository.dart#_decryptErrorDialog'); + Logger.info('Sending error report'); await showDialog( context: context, builder: (context) => const SendErrorDialog(), @@ -271,7 +272,6 @@ class SecureTokenRepository implements TokenRepository { onPressed: () async { Logger.info( 'Deleting all tokens from secure storage', - name: 'secure_token_repository.dart#_decryptErrorDeleteTokenConfirmationDialog', verbose: true, ); Navigator.pop(context, true); diff --git a/lib/utils/encryption/token_encryption.dart b/lib/utils/encryption/token_encryption.dart index 440f19f74..3aceedd7f 100644 --- a/lib/utils/encryption/token_encryption.dart +++ b/lib/utils/encryption/token_encryption.dart @@ -28,7 +28,7 @@ import '../../utils/logger.dart'; class TokenEncryption { static Future encrypt({required Iterable tokens, required String password}) async { - Logger.info('Encrypting tokens', name: 'TokenEncryption#encrypt'); + Logger.info('Encrypting tokens'); final String jsonString; try { final jsonsList = tokens.map((e) => e.toJson()).toList(); @@ -36,7 +36,7 @@ class TokenEncryption { final encrypted = (await AesEncrypted.encrypt(data: encoded, password: password)).toJson(); jsonString = jsonEncode(encrypted); } catch (e, s) { - Logger.error('Failed to encrypt tokens', error: e, stackTrace: s, name: 'TokenEncryption#encrypt'); + Logger.error('Failed to encrypt tokens', error: e, stackTrace: s); rethrow; } Logger.info('Encrypted ${tokens.length} tokens'); @@ -44,7 +44,7 @@ class TokenEncryption { } static Future> decrypt({required String encryptedTokens, required String password}) async { - Logger.info('Decrypting tokens', name: 'TokenEncryption#decrypt'); + Logger.info('Decrypting tokens'); List tokens = []; try { final json = jsonDecode(encryptedTokens); @@ -53,7 +53,7 @@ class TokenEncryption { tokens = tokenJsonsList.map((e) => Token.fromJson(e).copyWith(folderId: () => null)).toList(); } catch (e, s) { // Does not has to be an error, if the password is wrong. - Logger.warning('Failed to decrypt tokens', error: e, stackTrace: s, name: 'TokenEncryption#decrypt'); + Logger.warning('Failed to decrypt tokens', error: e, stackTrace: s); rethrow; } Logger.info('Decrypted ${tokens.length} tokens'); @@ -61,7 +61,7 @@ class TokenEncryption { } static Uri generateExportUri({required Token token}) { - Logger.info('Generating export URI for token ${token.label}', name: 'TokenEncryption#generateExportUri'); + Logger.info('Generating export URI for token ${token.label}'); Uri uri; try { final tokenJson = token.toJson(); @@ -70,7 +70,7 @@ class TokenEncryption { final base64 = base64Url.encode(bytes); uri = Uri.parse('${PrivacyIDEAAuthenticatorQrProcessor.scheme}://${PrivacyIDEAAuthenticatorQrProcessor.host}?data=$base64'); } catch (e, s) { - Logger.error('Failed to generate export URI', error: e, stackTrace: s, name: 'TokenEncryption#generateExportUri'); + Logger.error('Failed to generate export URI', error: e, stackTrace: s); rethrow; } Logger.info('Generated export URI for token ${token.label}'); @@ -78,7 +78,7 @@ class TokenEncryption { } static QRCode toQrCode(Token token) { - Logger.info('Generating QR code for token ${token.label}', name: 'TokenEncryption#toQrCode'); + Logger.info('Generating QR code for token ${token.label}'); final QRCode qrCode; try { qrCode = Encoder.encode( @@ -87,7 +87,7 @@ class TokenEncryption { hints: EncodeHints()..put(EncodeHintType.characterSet, CharacterSetECI.ASCII), ); } catch (e, s) { - Logger.error('Failed to generate QR code', error: e, stackTrace: s, name: 'TokenEncryption#toQrCode'); + Logger.error('Failed to generate QR code', error: e, stackTrace: s); rethrow; } Logger.info('Generated QR code for token ${token.label}'); @@ -96,7 +96,7 @@ class TokenEncryption { static Token fromExportUri(Uri uri) { final Token token; - Logger.info('Parsing exportUri (${uri.scheme})', name: 'TokenEncryption#fromExportUri'); + Logger.info('Parsing exportUri (${uri.scheme})'); try { final base64String = uri.queryParameters['data']; final bytes = base64Url.decode(base64String!); @@ -104,10 +104,10 @@ class TokenEncryption { final tokenJson = json.decode(jsonString) as Map; token = Token.fromJson(tokenJson); } catch (e, s) { - Logger.error('Failed to parse token from URI', error: e, stackTrace: s, name: 'TokenEncryption#fromExportUri'); + Logger.error('Failed to parse token from URI', error: e, stackTrace: s); rethrow; } - Logger.info('Parsed token ${token.label}', name: 'TokenEncryption#fromExportUri'); + Logger.info('Parsed token ${token.label}'); return token; } } diff --git a/lib/utils/firebase_utils.dart b/lib/utils/firebase_utils.dart index 4fca041b8..b6664d7ad 100644 --- a/lib/utils/firebase_utils.dart +++ b/lib/utils/firebase_utils.dart @@ -52,7 +52,7 @@ class FirebaseUtils { return; } _initialized = true; - Logger.info('Initializing Firebase', name: 'firebase_utils.dart#initFirebase'); + Logger.info('Initializing Firebase'); try { // await FirebaseMessaging.instance.requestPermission(); @@ -61,7 +61,6 @@ class FirebaseUtils { 'e.code: ${e.code}, ' 'e.message: ${e.message}, ' 'e.plugin: ${e.plugin},', - name: 'push_provider.dart#_initFirebase', error: e, stackTrace: s, ); @@ -141,14 +140,14 @@ class FirebaseUtils { firebaseToken = await FirebaseMessaging.instance.getToken(); } on FirebaseException catch (e, s) { String errorMessage = e.message ?? 'no error message'; - Logger.warning('Unable to retrieve Firebase token! ($errorMessage: ${e.code})', name: 'push_provider.dart#getFBToken', error: e, stackTrace: s); + Logger.warning('Unable to retrieve Firebase token! ($errorMessage: ${e.code})', error: e, stackTrace: s); } // Fall back to the last known firebase token if (firebaseToken == null) { firebaseToken = await getCurrentFirebaseToken(); } else { - Logger.info('New Firebase token retrieved', name: 'push_provider.dart#getFBToken'); + Logger.info('New Firebase token retrieved'); await setNewFirebaseToken(firebaseToken); } @@ -197,25 +196,25 @@ class FirebaseUtils { // } Future deleteFirebaseToken() async { - Logger.info('Deleting firebase token..', name: 'firebase_utils.dart#deleteFBToken'); + Logger.info('Deleting firebase token..'); try { final app = await Firebase.initializeApp(); await app.setAutomaticDataCollectionEnabled(false); await FirebaseMessaging.instance.deleteToken(); - Logger.warning('Firebase token deleted from Firebase', name: 'firebase_utils.dart#deleteFBToken'); + Logger.warning('Firebase token deleted from Firebase'); } on FirebaseException catch (e) { if (e.message?.contains('IOException') == true) throw SocketException(e.message!); rethrow; } await _storage.delete(key: _CURRENT_APP_TOKEN_KEY); await _storage.delete(key: _NEW_APP_TOKEN_KEY); - Logger.info('Firebase token deleted from secure storage', name: 'firebase_utils.dart#deleteFBToken'); + Logger.info('Firebase token deleted from secure storage'); return true; } // FIXME: WHY CURRENT AND NEW TOKEN? Future setCurrentFirebaseToken(String str) { - Logger.info('Setting current firebase token', name: 'secure_token_repository.dart#setCurrentFirebaseToken'); + Logger.info('Setting current firebase token'); return _protect(() => _storage.write(key: _CURRENT_APP_TOKEN_KEY, value: str)); } @@ -223,7 +222,7 @@ class FirebaseUtils { // This is used for checking if the token was updated. Future setNewFirebaseToken(String str) => _protect(() { - Logger.info('Setting new firebase token', name: 'secure_token_repository.dart#setNewFirebaseToken'); + Logger.info('Setting new firebase token'); return _storage.write(key: _NEW_APP_TOKEN_KEY, value: str); }); Future getNewFirebaseToken() => _protect(() => _storage.read(key: _NEW_APP_TOKEN_KEY)); diff --git a/lib/utils/home_widget_utils.dart b/lib/utils/home_widget_utils.dart index 8ed054344..48a2deb85 100644 --- a/lib/utils/home_widget_utils.dart +++ b/lib/utils/home_widget_utils.dart @@ -206,7 +206,7 @@ class HomeWidgetUtils { } } } - Logger.info('Found ${tokenWigetPairs.length} linked Widgets', name: 'home_widget_utils.dart#_getWidgetIdsOfTokens'); + Logger.info('Found ${tokenWigetPairs.length} linked Widgets'); return tokenWigetPairs; } @@ -375,19 +375,19 @@ class HomeWidgetUtils { if (tokenAction == null) return; final actionTimer = _actionTimers[tokenId]; if (actionTimer != null && actionTimer.isActive) { - Logger.info('Action blocked', name: 'home_widget_utils.dart#performAction'); + Logger.info('Action blocked'); return; } HomeWidget.saveWidgetData('$keyActionBlocked$tokenId', true); final widgetIds = (await _getWidgetIdsOfTokens([token.id])).keys.toList(); _actionTimers[tokenId] = Timer(const Duration(seconds: 1), () async { - Logger.info('Unblocked action', name: 'home_widget_utils.dart#performAction'); + Logger.info('Unblocked action'); await HomeWidget.saveWidgetData('$keyActionBlocked$tokenId', false); await _notifyUpdate(widgetIds); }); await _mapTokenAction[token.type]?.call(widgetId); - Logger.info('Performing action', name: 'home_widget_utils.dart#performAction'); + Logger.info('Performing action'); await _notifyUpdate(widgetIds); } @@ -455,7 +455,7 @@ class HomeWidgetUtils { } Future _unlink(String widgetId) async { - Logger.info('Unlinking HomeWidget with id $widgetId', name: 'home_widget_utils.dart#_unlink'); + Logger.info('Unlinking HomeWidget with id $widgetId'); await HomeWidget.saveWidgetData('$keyTokenId$widgetId', null); await _updateHomeWidgetUnlinked(); await _removeTokenType(widgetId); @@ -629,19 +629,19 @@ class HomeWidgetUtils { /// This method has to be called after change to the HomeWidget to notify the HomeWidget to update Future _notifyUpdate(Iterable updatedWidgetIds) async { if (updatedWidgetIds.isEmpty) return; - Logger.info('Update requested for: $updatedWidgetIds', name: 'home_widget_utils.dart#_notifyUpdate'); + Logger.info('Update requested for: $updatedWidgetIds'); if (await _widgetIsRebuilding || _lastUpdate != null && DateTime.now().difference(_lastUpdate!) < _updateDelay) { - Logger.info('Update delayed: $updatedWidgetIds', name: 'home_widget_utils.dart#_notifyUpdate'); + Logger.info('Update delayed: $updatedWidgetIds'); _updatedWidgetIds.addAll(updatedWidgetIds); _updateTimer?.cancel(); final nextDelayInMs = _updateDelay.inMilliseconds - DateTime.now().difference(_lastUpdate!).inMilliseconds; _updateTimer = Timer(nextDelayInMs < 1 ? _updateDelay : Duration(milliseconds: nextDelayInMs), () async { - Logger.info('Call Update from Timer', name: 'home_widget_utils.dart#_notifyUpdate'); + Logger.info('Call Update from Timer'); await _notifyUpdate(_updatedWidgetIds.toList()); }); return; } - Logger.info('Notify Update: $updatedWidgetIds', name: 'home_widget_utils.dart#_notifyUpdate'); + Logger.info('Notify Update: $updatedWidgetIds'); _lastUpdate = DateTime.now(); await HomeWidget.saveWidgetData(keyRebuildingWidgetIds, updatedWidgetIds.join(',')); await HomeWidget.updateWidget(qualifiedAndroidName: '$_packageId.AppWidgetProvider', iOSName: 'AppWidgetProvider'); diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index a13491d0e..52ff3e848 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -22,7 +22,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:isolate'; -import 'dart:math'; +import 'dart:math' show min; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -53,7 +53,7 @@ class Logger { }, colors: true, printEmojis: true, - printTime: false, + dateTimeFormat: printer.DateTimeFormat.dateAndTime, ), ); @@ -130,8 +130,17 @@ class Logger { /*----------- LOGGING METHODS -----------*/ - void logInfo(String message, {dynamic stackTrace, String? name, bool verbose = false}) { - String infoString = _convertLogToSingleString(message, stackTrace: stackTrace, name: name, logLevel: LogLevel.INFO); + static void info(String message, {dynamic error, dynamic stackTrace, String? name, bool verbose = false}) => + instance._logInfo(message, stackTrace: stackTrace, name: name, verbose: verbose); + + void _logInfo(String message, {dynamic stackTrace, String? name, bool verbose = false}) { + if (_verboseLogging == false && kDebugMode == false && verbose == false) return; + String infoString = _convertLogToSingleString( + message, + stackTrace: stackTrace, + name: name ?? _getCallerMethodName(depth: 2), + logLevel: LogLevel.INFO, + ); infoString = _textFilter(infoString); if (_verboseLogging || verbose) { _logToFile(infoString); @@ -139,11 +148,18 @@ class Logger { _print(infoString); } - static void info(String message, {dynamic error, dynamic stackTrace, String? name, bool verbose = false}) => - instance.logInfo(message, stackTrace: stackTrace, name: name, verbose: verbose); + static void warning(String message, {dynamic error, dynamic stackTrace, String? name, bool verbose = false}) => + instance._logWarning(message, error: error, stackTrace: stackTrace, name: name, verbose: verbose); - void logWarning(String message, {dynamic error, dynamic stackTrace, String? name, bool verbose = false}) { - String warningString = _convertLogToSingleString(message, error: error, stackTrace: stackTrace, name: name, logLevel: LogLevel.WARNING); + void _logWarning(String message, {dynamic error, dynamic stackTrace, String? name, bool verbose = false}) { + if (_verboseLogging == false && kDebugMode == false && verbose == false) return; + String warningString = _convertLogToSingleString( + message, + error: error, + stackTrace: stackTrace, + name: name ?? _getCallerMethodName(depth: 2), + logLevel: LogLevel.WARNING, + ); warningString = _textFilter(warningString); if (_verboseLogging || verbose) { instance._logToFile(warningString); @@ -154,20 +170,35 @@ class Logger { /// Does nothing if in production/release mode static void debug(String message, {dynamic error, dynamic stackTrace, String? name, bool verbose = false}) { if (!kDebugMode) return; + instance._logDebug(message, error: error, stackTrace: stackTrace, name: name, verbose: verbose); + } + + void _logDebug(String message, {dynamic error, dynamic stackTrace, String? name, bool verbose = false}) { + if (_verboseLogging == false && kDebugMode == false && verbose == false) return; String debugString = instance._convertLogToSingleString( message, stackTrace: stackTrace ?? (_verboseLogging || verbose) ? StackTrace.current : null, - name: name, + name: name ?? _getCallerMethodName(depth: 2), logLevel: LogLevel.DEBUG, ); + debugString = _textFilter(debugString); + if (_verboseLogging || verbose) { + instance._logToFile(debugString); + } _printDebug(debugString); } - static void warning(String message, {dynamic error, dynamic stackTrace, String? name, bool verbose = false}) => - instance.logWarning(message, error: error, stackTrace: stackTrace, name: name, verbose: verbose); + static void error(String? message, {dynamic error, dynamic stackTrace, String? name}) => + instance._logError(message, error: error, stackTrace: stackTrace, name: name); - void logError(String? message, {dynamic error, dynamic stackTrace, String? name}) { - String errorString = _convertLogToSingleString(message, error: error, stackTrace: stackTrace, name: name, logLevel: LogLevel.ERROR); + void _logError(String? message, {dynamic error, dynamic stackTrace, String? name}) { + String errorString = _convertLogToSingleString( + message, + error: error, + stackTrace: stackTrace, + name: name ?? _getCallerMethodName(depth: 2), + logLevel: LogLevel.ERROR, + ); errorString = _textFilter(errorString); if (message != null) { _lastError = message.substring(0, min(message.length, 100)); @@ -185,9 +216,6 @@ class Logger { _printError(message, error: error, stackTrace: stackTraceObject, name: name); } - static void error(String? message, {dynamic error, dynamic stackTrace, String? name}) => - instance.logError(message, error: error, stackTrace: stackTrace, name: name); - Future _logToFile(String fileMessage) async { if (_enableLoggingToFile == false) return; await _mutexWriteFile.acquire(); @@ -396,6 +424,17 @@ Device Parameters $deviceInfo"""; } return fileMessage; } + + static String? _getCallerMethodName({int depth = 1}) => _getCurrentMethodName(deph: depth + 2); + static String? _getCurrentMethodName({int deph = 1}) { + final frames = StackTrace.current.toString().split('\n'); + final frame = frames.elementAtOrNull(deph); + if (frame == null) return null; + final entry = frame.split(' '); + final methodName = entry.elementAtOrNull(entry.length - 2); + if (methodName == 'closure>') return RegExp(r'(?<=\s\s)\w+.*(?=\s\()').firstMatch(frame)?.group(0); + return methodName; + } } final filterParameterKeys = ['fbtoken', 'new_fb_token', 'secret']; diff --git a/lib/utils/patch_notes_utils.dart b/lib/utils/patch_notes_utils.dart index 225112837..bad1d79d0 100644 --- a/lib/utils/patch_notes_utils.dart +++ b/lib/utils/patch_notes_utils.dart @@ -44,13 +44,10 @@ class PatchNotesUtils { static void showPatchNotesIfNeeded(BuildContext context, Version latestStartedVersion) { if (latestStartedVersion < AppInfoUtils.currentVersion) { - Logger.info('Showing patch notes between $latestStartedVersion and ${AppInfoUtils.currentVersion}', name: 'main_view.dart#showPatchNotesIfNeeded'); + Logger.info('Showing patch notes between $latestStartedVersion and ${AppInfoUtils.currentVersion}'); return _showPatchNotes(context: context, latestStartedVersion: latestStartedVersion); } - Logger.info( - 'No patch notes to show. Latest version: $latestStartedVersion. Current version: ${AppInfoUtils.currentVersion}', - name: 'main_view.dart#showPatchNotesIfNeeded', - ); + Logger.info('No patch notes to show. Latest version: $latestStartedVersion. Current version: ${AppInfoUtils.currentVersion}'); } static void _showPatchNotes({required BuildContext context, required Version latestStartedVersion}) { diff --git a/lib/utils/pi_mailer.dart b/lib/utils/pi_mailer.dart index 00bf6336a..e5abcae27 100644 --- a/lib/utils/pi_mailer.dart +++ b/lib/utils/pi_mailer.dart @@ -68,10 +68,10 @@ class PiMailer { ); return false; } - Logger.error('Was not able to send the Email', error: e, stackTrace: stackTrace, name: 'pi_mailer.dart#sendMail'); + Logger.error('Was not able to send the Email', error: e, stackTrace: stackTrace); return false; } catch (e, stackTrace) { - Logger.error('Was not able to send the Email', error: e, stackTrace: stackTrace, name: 'pi_mailer.dart#sendMail'); + Logger.error('Was not able to send the Email', error: e, stackTrace: stackTrace); return false; } return true; diff --git a/lib/utils/privacyidea_io_client.dart b/lib/utils/privacyidea_io_client.dart index ffcb3ebbe..3ff804686 100644 --- a/lib/utils/privacyidea_io_client.dart +++ b/lib/utils/privacyidea_io_client.dart @@ -53,7 +53,7 @@ class PrivacyideaIOClient { try { await ioClient.post(url, body: '').timeout(const Duration(seconds: 15)); } on ClientException { - Logger.warning('ClientException', name: 'utils.dart#triggerNetworkAccessPermission'); + Logger.warning('ClientException'); ioClient.close(); if (globalNavigatorKey.currentState?.context == null) return false; globalRef?.read(statusMessageProvider.notifier).state = ( @@ -66,7 +66,7 @@ class PrivacyideaIOClient { rethrow; } if (isRetry) { - Logger.warning('SocketException while retrying', name: 'utils.dart#triggerNetworkAccessPermission'); + Logger.warning('SocketException while retrying'); if (globalNavigatorKey.currentState?.context != null) { globalRef?.read(statusMessageProvider.notifier).state = ( AppLocalizations.of(await globalContext)!.connectionFailed, @@ -90,7 +90,7 @@ class PrivacyideaIOClient { /// 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'); + Logger.info('Sending post request (SSLVerify: $sslVerify)'); List entries = body.entries.where((element) => element.value == null).toList(); if (entries.isNotEmpty) { @@ -115,24 +115,23 @@ class PrivacyideaIOClient { try { response = await ioClient.post(url, body: body).timeout(const Duration(seconds: 15)); } on HandshakeException catch (e, _) { - Logger.warning('Handshake failed. sslVerify: $sslVerify', name: 'utils.dart#doPost'); + Logger.warning('Handshake failed. sslVerify: $sslVerify'); showMessage(message: 'Handshake failed, please check the server certificate and try again.'); response = Response('Handshake failed', 525); } on TimeoutException catch (e, _) { - Logger.info('Post request timed out', name: 'utils.dart#doPost'); + Logger.info('Post request timed out'); response = Response('Request timed out', 408); } on SocketException catch (e, _) { - Logger.info('Post request failed', name: 'utils.dart#doPost'); + Logger.info('Post request failed'); response = Response('Failed to send request', 404); } catch (e, _) { - Logger.warning('Something unexpected happened', name: 'utils.dart#doPost'); + Logger.warning('Something unexpected happened'); response = Response('Failed to send request', 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', stackTrace: StackTrace.current, ); @@ -144,7 +143,7 @@ class PrivacyideaIOClient { 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'); + Logger.info('Sending get request (SSLVerify: $sslVerify)'); List entries = parameters.entries.where((element) => element.value == null).toList(); if (entries.isNotEmpty) { List nullEntries = []; @@ -175,28 +174,28 @@ class PrivacyideaIOClient { try { response = await ioClient.get(uri).timeout(const Duration(seconds: 15)); } on HandshakeException catch (e, _) { - Logger.warning('Handshake failed. sslVerify: $sslVerify', name: 'utils.dart#doGet'); + Logger.warning('Handshake failed. sslVerify: $sslVerify'); showStatusMessage( message: AppLocalizations.of(await globalContext)!.handshakeFailed, subMessage: AppLocalizations.of(await globalContext)!.checkServerCertificate, ); response = Response('${e.runtimeType}', 525); } on TimeoutException catch (e, _) { - Logger.info('Get request timed out', name: 'utils.dart#doGet'); + Logger.info('Get request timed out'); response = Response('${AppLocalizations.of(await globalContext)!.timeOut}', 408); } on SocketException catch (e, _) { - Logger.info('Get request failed', name: 'utils.dart#doGet'); + Logger.info('Get request failed'); response = Response('${AppLocalizations.of(await globalContext)!.connectionFailed}', 404); } on ClientException catch (e, _) { - Logger.info('Get request failed', name: 'utils.dart#doGet'); + Logger.info('Get request failed'); response = Response('${AppLocalizations.of(await globalContext)!.connectionFailed}', 404); } catch (e, _) { - Logger.warning('Something unexpected happened', name: 'utils.dart#doGet'); + Logger.warning('Something unexpected happened'); response = Response('${AppLocalizations.of(await globalContext)!.unexpectedError}', 520); } if (response.statusCode != 200) { - Logger.warning('Received unexpected response: ${response.statusCode}', name: 'utils.dart#doGet'); + Logger.warning('Received unexpected response: ${response.statusCode}'); } ioClient.close(); diff --git a/lib/utils/push_provider.dart b/lib/utils/push_provider.dart index 6942fd15d..90d4337c8 100644 --- a/lib/utils/push_provider.dart +++ b/lib/utils/push_provider.dart @@ -83,10 +83,10 @@ class PushProvider { updateFirebaseToken: updateFirebaseToken, ); _firebaseInitialized = true; - Logger.info('Firebase initialized.', name: 'push_provider.dart#_init'); + Logger.info('Firebase initialized.'); } on IOException catch (e, s) { if (e.toString().contains('SERVICE_NOT_AVAILABLE')) { - Logger.warning('Could not initialize Firebase.', name: 'push_provider.dart#_init', error: e, stackTrace: s); + Logger.warning('Could not initialize Firebase.', error: e, stackTrace: s); } else { rethrow; } @@ -123,7 +123,7 @@ class PushProvider { instance!._firebaseUtils = firebaseUtils; instance!._initFirebase(); } else { - Logger.warning('Firebase is already initialized.', name: 'push_provider.dart#PushProvider'); + Logger.warning('Firebase is already initialized.'); } } } @@ -137,7 +137,7 @@ class PushProvider { data = remoteMessage.data; PushRequest.verifyData(data); } on ArgumentError catch (e) { - Logger.warning('Could not parse push request data.', name: 'push_provider.dart#_getAndValidateDataFromRemoteMessage', error: e, verbose: true); + Logger.warning('Could not parse push request data.', error: e, verbose: true); rethrow; } return data; @@ -153,7 +153,7 @@ class PushProvider { PushRequest.verifyData(dataUnit); } } on ArgumentError catch (e) { - Logger.warning('Could not parse push request data.', name: 'push_provider.dart#_getAndValidateDataFromResponse', error: e, verbose: true); + Logger.warning('Could not parse push request data.', error: e, verbose: true); rethrow; } return data; @@ -161,13 +161,13 @@ class PushProvider { // FOREGROUND HANDLING Future _foregroundHandler(RemoteMessage remoteMessage) async { - Logger.info('Foreground message received.', name: 'push_provider.dart#_foregroundHandler'); + Logger.info('Foreground message received.'); Map data; try { data = _getAndValidateDataFromRemoteMessage(remoteMessage); } on ArgumentError catch (_) { - Logger.info('Failed to parse push request data. Trying to poll for challenges.', name: 'push_provider.dart#_foregroundHandler'); + Logger.info('Failed to parse push request data. Trying to poll for challenges.'); await pollForChallenges(isManually: true); return; } @@ -177,7 +177,6 @@ class PushProvider { } catch (e, s) { Logger.error( AppLocalizations.of(globalNavigatorKey.currentContext!)!.unexpectedError, - name: 'push_provider.dart#_foregroundHandler', error: e, stackTrace: s, ); @@ -187,7 +186,7 @@ class PushProvider { // BACKGROUND HANDLING @pragma('vm:entry-point') static Future _backgroundHandler(RemoteMessage remoteMessage) async { - Logger.info('Background message received.', name: 'push_provider.dart#_backgroundHandler'); + Logger.info('Background message received.'); Map data; try { @@ -200,12 +199,15 @@ class PushProvider { try { success = await _handleIncomingRequestBackground(data); } catch (e, s) { - Logger.error('Something went wrong while handling the push request in the background.', - name: 'push_provider.dart#_backgroundHandler', error: e, stackTrace: s); + Logger.error( + 'Something went wrong while handling the push request in the background.', + error: e, + stackTrace: s, + ); return; } if (!success) { - Logger.warning('Handling the push request in the background failed.', name: 'push_provider.dart#_backgroundHandler'); + Logger.warning('Handling the push request in the background failed.'); return; } // PiNotifications.show('Push request', 'A new push request has been received.'); @@ -223,20 +225,20 @@ class PushProvider { /// Handles incoming push requests by verifying the challenge and adding it /// to the token. This should be guarded by a lock. Future _handleIncomingRequestForeground(Map data) async { - Logger.info('Incoming push challenge.', name: 'push_provider.dart#_handleIncomingRequestForeground'); + Logger.info('Incoming push challenge.'); PushRequest pushRequest = PushRequest.fromMessageData(data); - Logger.info("PushRequest.possibleAnswers: ${pushRequest.possibleAnswers}", name: 'push_provider.dart#_handleIncomingRequestForeground'); - Logger.info('Parsing data of push request succeeded.', name: 'push_provider.dart#_handleIncomingRequestForeground'); + Logger.info("PushRequest.possibleAnswers: ${pushRequest.possibleAnswers}"); + Logger.info('Parsing data of push request succeeded.'); final pushToken = globalRef?.read(tokenProvider).getTokenBySerial(pushRequest.serial); if (pushToken == null) { - Logger.warning('No token found for serial ${pushRequest.serial}.', name: 'push_provider.dart#_handleIncomingRequestForeground'); + Logger.warning('No token found for serial ${pushRequest.serial}.'); return; } if (!await pushRequest.verifySignature(pushToken, rsaUtils: _rsaUtils)) { - Logger.warning('Signature verification failed.', name: 'push_provider.dart#_handleIncomingRequestForeground'); + Logger.warning('Signature verification failed.'); return; } - Logger.info('Signature verification succeeded, notifying ${_subscribers.length} subscribers.', name: 'push_provider.dart#_handleIncomingRequestForeground'); + Logger.info('Signature verification succeeded, notifying ${_subscribers.length} subscribers.'); for (var subscriber in _subscribers) { subscriber(pushRequest); } @@ -246,22 +248,22 @@ class PushProvider { /// Handles incoming push requests by verifying the challenge and adding it /// to the token. This should be guarded by a lock. static Future _handleIncomingRequestBackground(Map data) async { - Logger.info('Incoming push challenge.', name: 'push_provider.dart#_handleIncomingRequestBackground'); + Logger.info('Incoming push challenge.'); PushRequest pushRequest = PushRequest.fromMessageData(data); final pushToken = (await _defaultTokenRepo.loadTokens()).whereType().firstWhereOrNull((t) => t.serial == pushRequest.serial); if (pushToken == null) { - Logger.warning('No token found for serial ${pushRequest.serial}.', name: 'push_provider.dart#_handleIncomingRequestBackground'); + Logger.warning('No token found for serial ${pushRequest.serial}.'); return false; } if (!await pushRequest.verifySignature(pushToken)) { - Logger.warning('Signature verification failed.', name: 'push_provider.dart#_handleIncomingRequestBackground'); + Logger.warning('Signature verification failed.'); return false; } try { await _defaultPushRequestRepo.addRequest(pushRequest); } catch (e) { - Logger.error('Could not save push request state.', name: 'push_provider.dart#_handleIncomingRequestBackground', error: e); + Logger.error('Could not save push request state.', error: e); return false; } return true; @@ -270,14 +272,14 @@ class PushProvider { void _startOrStopPolling(bool pollingEnabled) { // Start polling if enabled and not already polling if (pollingEnabled && _pollTimer == null) { - Logger.info('Polling is enabled.', name: 'push_provider.dart#_startPollingIfEnabled'); + Logger.info('Polling is enabled.'); _pollTimer = Timer.periodic(const Duration(seconds: 3), (_) => pollForChallenges(isManually: false)); pollForChallenges(isManually: false); return; } // Stop polling if it's disabled and currently polling if (!pollingEnabled && _pollTimer != null) { - Logger.info('Polling is disabled.', name: 'push_provider.dart#_startPollingIfEnabled'); + Logger.info('Polling is disabled.'); _pollTimer?.cancel(); _pollTimer = null; return; @@ -293,7 +295,7 @@ class PushProvider { // Disable polling if no push tokens exist if (pushTokens.isEmpty) { if ((await globalRef?.read(settingsProvider.future))?.enablePolling == true) { - Logger.info('No push token is available for polling, polling is disabled.', name: 'push_provider.dart#pollForChallenges'); + Logger.info('No push token is available for polling, polling is disabled.'); globalRef?.read(settingsProvider.notifier).setPolling(false); } return; @@ -302,7 +304,7 @@ class PushProvider { final connectivityResult = await (Connectivity().checkConnectivity()); if (connectivityResult.contains(ConnectivityResult.none)) { if (isManually) { - Logger.info('Tried to poll without any internet connection available.', name: 'push_provider.dart#pollForChallenges'); + Logger.info('Tried to poll without any internet connection available.'); globalRef?.read(statusMessageProvider.notifier).state = ( AppLocalizations.of(globalNavigatorKey.currentContext!)!.pollingFailed, AppLocalizations.of(globalNavigatorKey.currentContext!)!.noNetworkConnection, @@ -312,7 +314,7 @@ class PushProvider { } // Start request for each token - Logger.info('Polling for challenges: ${pushTokens.length} Tokens', name: 'push_provider.dart#pollForChallenges'); + Logger.info('Polling for challenges: ${pushTokens.length} Tokens'); final List> futures = []; for (PushToken p in pushTokens) { futures.add(pollForChallenge(p, isManually: isManually)); @@ -323,7 +325,7 @@ class PushProvider { Future pollForChallenge(PushToken token, {bool isManually = true}) async { if (instance == null) { - Logger.warning('Polling push tokens failed. PushProvider is not initialized.', name: 'push_provider.dart#pollForChallenge'); + Logger.warning('Polling push tokens failed. PushProvider is not initialized.'); return; } String timestamp = DateTime.now().toUtc().toIso8601String(); @@ -331,14 +333,14 @@ class PushProvider { String message = '${token.serial}|$timestamp'; RsaUtils rsaUtils = instance!._rsaUtils; - Logger.info(rsaUtils.runtimeType.toString(), name: 'push_provider.dart#pollForChallenge'); + Logger.info(rsaUtils.runtimeType.toString()); String? signature = await rsaUtils.trySignWithToken(token, message); if (signature == null) { globalRef?.read(statusMessageProvider.notifier).state = ( AppLocalizations.of(globalNavigatorKey.currentContext!)!.pollingFailedFor(token.serial), AppLocalizations.of(globalNavigatorKey.currentContext!)!.couldNotSignMessage, ); - Logger.warning('Polling push tokens failed because signing the message failed.', name: 'push_provider.dart#pollForChallenge'); + Logger.warning('Polling push tokens failed because signing the message failed.'); return; } Map parameters = { @@ -388,8 +390,7 @@ class PushProvider { error ?? AppLocalizations.of(globalNavigatorKey.currentContext!)!.statusCode(response.statusCode), ); } - Logger.warning('Polling push token failed with status code ${response.statusCode}', - name: 'push_provider.dart#pollForChallenge', error: getErrorMessageFromResponse(response)); + Logger.warning('Polling push token failed with status code ${response.statusCode}', error: getErrorMessageFromResponse(response)); return; default: @@ -402,7 +403,7 @@ class PushProvider { } return; } - Logger.info('Received ${challengeList.length} challenge(s) for ${token.label}', name: 'push_provider.dart#pollForChallenge'); + Logger.info('Received ${challengeList.length} challenge(s) for ${token.label}'); for (Map challengeData in challengeList) { _handleIncomingRequestForeground((challengeData)); diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart index dfeaba79a..6e4d31a77 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.dart @@ -28,12 +28,12 @@ part 'app_constraints_notifier.g.dart'; class AppConstraintsNotifier extends _$AppConstraintsNotifier { @override BoxConstraints? build() { - Logger.info("New AppConstraints created", name: 'AppConstraintsNotifier#build'); + Logger.info("New AppConstraints created"); return null; } void update(BoxConstraints constraints) { - Logger.info("AppConstraints updated", name: 'AppConstraintsNotifier#update'); + Logger.info("AppConstraints updated"); state = constraints; } } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart index 28e7115e9..8dd93328d 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.dart @@ -48,7 +48,7 @@ final sources = [ class DeeplinkNotifier extends _$DeeplinkNotifier { @override Stream build() async* { - Logger.info('New DeeplinkNotifier created', name: 'DeeplinkNotifier#build'); + Logger.info('New DeeplinkNotifier created'); final initial = await _handleInitialUri(sources); if (initial != null) yield initial; await for (var dl in _handleIncomingLinks(sources)) { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart index 9a7fc206e..8302a1f02 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart @@ -39,7 +39,7 @@ class IntroductionNotifier extends _$IntroductionNotifier { @override Future build({required IntroductionRepository repo}) async { - Logger.info('New IntroductionNotifier created', name: 'introduction_provider.dart#build'); + Logger.info('New IntroductionNotifier created'); _repo = _repositoryOverride ?? repo; return await _loadFromRepo(); } @@ -47,21 +47,21 @@ class IntroductionNotifier extends _$IntroductionNotifier { Future _loadFromRepo() async { final newState = await _repo.loadCompletedIntroductions(); - Logger.info('Loading completed introductions from repo: $newState', name: 'introduction_provider.dart#_loadFromRepo'); + Logger.info('Loading completed introductions from repo: $newState'); return newState; } Future _saveToRepo(IntroductionState state) async { final success = await _repo.saveCompletedIntroductions(state); if (success) { - Logger.info('Saving completed introductions to repo: $state', name: 'introduction_provider.dart#_saveToRepo'); + Logger.info('Saving completed introductions to repo: $state'); } else { - Logger.warning('Failed to save completed introductions to repo: $state', name: 'introduction_provider.dart#_saveToRepo'); + Logger.warning('Failed to save completed introductions to repo: $state'); } } Future complete(Introduction introduction) async { - Logger.info('Completing introduction: $introduction', name: 'introduction_provider.dart#complete'); + Logger.info('Completing introduction: $introduction'); final newState = (await future).withCompletedIntroduction(introduction); await _saveToRepo(newState); state = AsyncValue.data(newState); @@ -69,14 +69,14 @@ class IntroductionNotifier extends _$IntroductionNotifier { } Future uncomplete(Introduction introduction) async { - Logger.info('Uncompleting introduction: $introduction', name: 'introduction_provider.dart#uncomplete'); + Logger.info('Uncompleting introduction: $introduction'); final newState = (await future).withoutCompletedIntroduction(introduction); await _saveToRepo(newState); state = AsyncValue.data(newState); } Future completeAll() async { - Logger.info('Completing all introductions', name: 'introduction_provider.dart#completeAll'); + Logger.info('Completing all introductions'); final newState = IntroductionState.withAllCompleted(); await _saveToRepo(newState); state = AsyncValue.data(newState); diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart index 247a41d9a..800fb12c8 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/progress_state_provider.dart @@ -31,27 +31,27 @@ // class ProgressStateNotifier extends _$ProgressStateNotifier { // @override // ProgressState build(Type type) { -// Logger.info('Initializing progress state', name: 'ProgressStateNotifier#initProgress'); +// Logger.info('Initializing progress state'); // return const ProgressState.uninitialized(); // } // double? get progress => state.progress; // ProgressState initProgress(int max, int value) { -// Logger.info('Initializing progress state', name: 'ProgressStateNotifier#initProgress'); +// Logger.info('Initializing progress state'); // final newState = ProgressState(max: max, value: value); // state = newState; // return newState; // } // void deleteProgress() { -// Logger.info('Deleting progress state', name: 'ProgressStateNotifier#deleteProgress'); +// Logger.info('Deleting progress state'); // state = const ProgressState.uninitialized(); // } // ProgressState? resetProgress() { // if (state is ProgressStateUninitialized) return state; -// Logger.info('Resetting progress state', name: 'ProgressStateNotifier#resetProgress'); +// Logger.info('Resetting progress state'); // final newState = state.copyWith(value: 0); // state = newState; // return newState; @@ -60,7 +60,7 @@ // ProgressState? setProgressMax(int max) { // assert(state is! ProgressStateUninitialized, 'Progress state is uninitialized'); // assert(max > 0, 'Max value must be greater than 0'); -// Logger.info('Setting progress max to $max', name: 'ProgressStateNotifier#setProgressMax'); +// Logger.info('Setting progress max to $max'); // final newState = state.copyWith(max: max); // state = newState; // return newState; @@ -70,7 +70,7 @@ // assert(state is! ProgressStateUninitialized, 'Progress state is uninitialized'); // assert(value >= 0, 'Value must be greater than or equal to 0'); // if (value > state.max) value = state.max; -// Logger.info('Setting progress value to $value/${state.max}', name: 'ProgressStateNotifier#setProgressValue'); +// Logger.info('Setting progress value to $value/${state.max}'); // final newState = state.copyWith(value: value); // state = newState; // return newState; diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart index 892c0bfe7..47e58a2ba 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.dart @@ -94,7 +94,7 @@ class PushRequestNotifier extends _$PushRequestNotifier { _ioClient = _ioClientOverride ?? ioClient; _pushProvider = _pushProviderOverride ?? pushProvider; _pushRepo = _pushRepoOverride ?? pushRepo; - Logger.info('New PushRequestNotifier created', name: 'pushRequestProvider#build'); + Logger.info('New PushRequestNotifier created'); _pushProvider.subscribe(add); return _loadFromRepo(); } @@ -118,7 +118,7 @@ class PushRequestNotifier extends _$PushRequestNotifier { try { loadedState = await _pushRepo.loadState(); } catch (e) { - Logger.error('Failed to load push request state from repo.', name: 'push_request_notifier.dart#_loadFromRepo', error: e); + Logger.error('Failed to load push request state from repo.', error: e); loadingRepoMutex.release(); return (await future); } @@ -139,7 +139,6 @@ class PushRequestNotifier extends _$PushRequestNotifier { } catch (e) { Logger.warning( 'Failed to save push request: $pushRequest', - name: 'push_request_notifier.dart#_addOrReplacePushRequest', error: e, ); loadingRepoMutex.release(); @@ -158,7 +157,6 @@ class PushRequestNotifier extends _$PushRequestNotifier { if (!replaced) { Logger.warning( 'Tried to replace a push request that does not exist.', - name: 'push_request_notifier.dart#_replacePushRequest', ); loadingRepoMutex.release(); return false; @@ -168,7 +166,6 @@ class PushRequestNotifier extends _$PushRequestNotifier { } catch (e) { Logger.warning( 'Failed to save push request: $pushRequest', - name: 'push_request_notifier.dart#_replacePushRequest', error: e, ); loadingRepoMutex.release(); @@ -189,7 +186,6 @@ class PushRequestNotifier extends _$PushRequestNotifier { } catch (e) { Logger.error( 'Failed to save push request state after removing push request.', - name: 'push_request_notifier.dart#_remove', error: e, ); loadingRepoMutex.release(); @@ -212,7 +208,7 @@ class PushRequestNotifier extends _$PushRequestNotifier { await updatingRequestMutex.acquire(); final current = (await future).currentOf(pushRequest); if (current == null) { - Logger.warning('Tried to update a push request that does not exist.', name: 'push_request_notifier.dart#updatePushRequest'); + Logger.warning('Tried to update a push request that does not exist.'); updatingRequestMutex.release(); return null; } @@ -238,22 +234,22 @@ class PushRequestNotifier extends _$PushRequestNotifier { /// It should be still in the CustomIntBuffer of the state. Future accept(PushToken pushToken, PushRequest pushRequest, {String? selectedAnswer}) async { if (pushRequest.accepted != null) { - Logger.warning('The push request is already accepted or declined.', name: 'push_request_notifier.dart#accept'); + Logger.warning('The push request is already accepted or declined.'); return false; } - Logger.info('Accept push request.', name: 'push_request_notifier.dart#accept'); + Logger.info('Accept push request.'); final updated = await _updatePushRequest(pushRequest, (p0) async { final updated = p0.copyWith(accepted: true, selectedAnswer: () => selectedAnswer); final success = await _handleReaction(pushRequest: updated, token: pushToken); if (!success) { - Logger.warning('Failed to handle push request reaction.', name: 'push_request_notifier.dart#accept'); + Logger.warning('Failed to handle push request reaction.'); return p0; } return updated; }); if (updated == null || updated.accepted != true) { - Logger.warning('Failed to accept push request.', name: 'push_request_notifier.dart#accept'); + Logger.warning('Failed to accept push request.'); return false; } await _remove(updated); @@ -262,21 +258,21 @@ class PushRequestNotifier extends _$PushRequestNotifier { Future decline(PushToken pushToken, PushRequest pushRequest) async { if (pushRequest.accepted != null) { - Logger.warning('The push request is already accepted or declined.', name: 'push_request_notifier.dart#decline'); + Logger.warning('The push request is already accepted or declined.'); return false; } - Logger.info('Decline push request.', name: 'push_request_notifier.dart#decline'); + Logger.info('Decline push request.'); final updated = await _updatePushRequest(pushRequest, (p0) async { final updated = p0.copyWith(accepted: false, selectedAnswer: () => null); final success = await _handleReaction(pushRequest: updated, token: pushToken); if (!success) { - Logger.warning('Failed to handle push request reaction.', name: 'push_request_notifier.dart#accept'); + Logger.warning('Failed to handle push request reaction.'); return p0; } return updated; }); if (updated == null || updated.accepted != false) { - Logger.warning('Failed to decline push request.', name: 'push_request_notifier.dart#decline'); + Logger.warning('Failed to decline push request.'); return false; } await _remove(updated); @@ -287,7 +283,6 @@ class PushRequestNotifier extends _$PushRequestNotifier { if ((await future).knowsRequestId(pr.id)) { Logger.info( 'The push request already exists.', - name: 'token_notifier.dart#addPushRequestToToken', ); return false; } @@ -296,7 +291,7 @@ class PushRequestNotifier extends _$PushRequestNotifier { // Remove the request after it expires. if (success) _setupTimer(pr); - Logger.info('Added push request ${pr.id} to state', name: 'token_notifier.dart#addPushRequestToToken'); + Logger.info('Added push request ${pr.id} to state'); return true; } @@ -312,16 +307,16 @@ class PushRequestNotifier extends _$PushRequestNotifier { } void _cancelTimer(PushRequest pr) { - Logger.info('Canceling timer for push request ${pr.id}', name: 'push_request_notifier.dart#_cancelTimer'); + Logger.info('Canceling timer for push request ${pr.id}'); final timer = _expirationTimers.remove(pr.id.toString())?..cancel(); if (timer == null) { - Logger.warning('Timer for push request ${pr.id} not found.', name: 'push_request_notifier.dart#_cancelTimer'); + Logger.warning('Timer for push request ${pr.id} not found.'); } } void _cancalAllTimers() { if (_expirationTimers.keys.isNotEmpty) { - Logger.info('Canceling all timers: [${_expirationTimers.keys}]', name: 'push_request_notifier.dart#_cancelAllTimers'); + Logger.info('Canceling all timers: [${_expirationTimers.keys}]'); } final ids = _expirationTimers.keys.toList(); for (var id in ids) { @@ -355,7 +350,7 @@ class PushRequestNotifier extends _$PushRequestNotifier { Future _handleReaction({required PushRequest pushRequest, required PushToken token}) async { if (pushRequest.accepted == null) return false; - Logger.info('Push auth request accepted=${pushRequest.accepted}, sending response to privacyidea', name: 'token_widgets.dart#handleReaction'); + Logger.info('Push auth request accepted=${pushRequest.accepted}, sending response to privacyidea'); // POST https://privacyideaserver/validate/check // nonce= // serial= @@ -376,10 +371,10 @@ class PushRequestNotifier extends _$PushRequestNotifier { body['presence_answer'] = pushRequest.selectedAnswer!; msg += '|${pushRequest.selectedAnswer!}'; } - Logger.warning('Signature message: $msg', name: 'token_widgets.dart#handleReaction'); + Logger.warning('Signature message: $msg'); String? signature = await _rsaUtils.trySignWithToken(token, msg); if (signature == null) { - Logger.warning('Failed to sign push request response.', name: 'token_widgets.dart#handleReaction'); + Logger.warning('Failed to sign push request response.'); return false; } @@ -387,14 +382,14 @@ class PushRequestNotifier extends _$PushRequestNotifier { Response response; try { - Logger.info('Sending push request response.', name: 'token_widgets.dart#_handleReaction'); + Logger.info('Sending push request response.'); response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body); } catch (_) { - Logger.warning('Sending push request response failed. Retrying.', name: 'token_widgets.dart#handleReaction'); + Logger.warning('Sending push request response failed. Retrying.'); try { response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body); } catch (e) { - Logger.warning('Sending push request response failed consistently.', name: 'token_widgets.dart#handleReaction', error: e); + Logger.warning('Sending push request response failed consistently.', error: e); ref.read(statusMessageProvider.notifier).state = (AppLocalizations.of(await globalContext)!.connectionFailed, null); return false; } @@ -405,7 +400,7 @@ class PushRequestNotifier extends _$PushRequestNotifier { '${appLocalizations.sendPushRequestResponseFailed}\n${appLocalizations.statusCode(response.statusCode)}', tryJsonDecode(response.body)?["result"]?["error"]?["message"], ); - Logger.warning('Sending push request response failed.', name: 'token_widgets.dart#handleReaction'); + Logger.warning('Sending push request response failed.'); return false; } return true; @@ -430,20 +425,20 @@ class PushRequestNotifier extends _$PushRequestNotifier { // ref.listen(tokenProvider, (previous, next) { // if (previous?.hasPushTokens == true && next.hasPushTokens == false) { // /// Last push token was deleted -// Logger.info('Last push token was deleted. Deactivating push provider and deleting firebase token.', name: 'pushRequestProvider#tokenProvider'); +// Logger.info('Last push token was deleted. Deactivating push provider and deleting firebase token.', name: 'PushRequestNotifier.tokenProvider'); // pushRequestNotifier.swapPushProvider(PlaceholderPushProvider()); // pushProvider.firebaseUtils.deleteFirebaseToken(); // } // if (previous?.hasPushTokens != true && next.hasPushTokens == true) { // /// First push token was added -// Logger.info('First push token was added. Activating push provider.', name: 'pushRequestProvider#tokenProvider'); +// Logger.info('First push token was added. Activating push provider.', name: 'PushRequestNotifier.tokenProvider'); // pushRequestNotifier.swapPushProvider(PushProvider()); // } // }); // ref.listen(settingsProvider, (previous, next) { // if (previous?.enablePolling != next.enablePolling) { -// Logger.info("Polling enabled changed from ${previous?.enablePolling} to ${next.enablePolling}", name: 'pushRequestProvider#settingsProvider'); +// Logger.info("Polling enabled changed from ${previous?.enablePolling} to ${next.enablePolling}", name: 'PushRequestNotifier.settingsProvider'); // pushRequestNotifier.pushProvider.setPollingEnabled(next.enablePolling); // } // }); @@ -537,7 +532,7 @@ class PushRequestNotifier extends StateNotifier { initState = initialState != null ? Future.value(initialState) : _loadFromRepo(); _pushProvider.subscribe(add); await initState; - Logger.info('PushRequestNotifier initialized', name: 'push_request_notifier.dart#_init'); + Logger.info('PushRequestNotifier initialized'); } @override diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart index cefe96492..31bf3167a 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart @@ -49,7 +49,7 @@ class SettingsNotifier extends _$SettingsNotifier { Future build({ required SettingsRepository repo, }) async { - // Logger.info('New settings notifier created', name: 'settings_notifier.dart#build'); + // Logger.info('New settings notifier created'); _repo = _repoOverride ?? repo; final newState = await _loadFromRepo(); return newState; @@ -58,14 +58,14 @@ class SettingsNotifier extends _$SettingsNotifier { Future _loadFromRepo() async { await _repoMutex.acquire(); final newState = await _repo.loadSettings(); - Logger.info('Loading settings from repo: $newState', name: 'settings_notifier.dart#_loadFromRepo'); + Logger.info('Loading settings from repo: $newState'); _repoMutex.release(); return newState; } Future _saveToRepo(SettingsState state) async { await _repoMutex.acquire(); - Logger.info('Saving settings to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); + Logger.info('Saving settings to repo: $state'); final success = await _repo.saveSettings(state); _repoMutex.release(); return success; @@ -87,7 +87,7 @@ class SettingsNotifier extends _$SettingsNotifier { } Future addCrashReportRecipient(String email) async { - Logger.info('Crash report recipient added: $email', name: 'settings_notifier.dart#addCrashReportRecipient'); + Logger.info('Crash report recipient added: $email'); return updateState((oldState) { final updatedSet = oldState.crashReportRecipients..add(email); return oldState.copyWith(crashReportRecipients: updatedSet); @@ -95,7 +95,7 @@ class SettingsNotifier extends _$SettingsNotifier { } Future removeCrashReportRecipient(String email) async { - Logger.info('Crash report recipient removed: $email', name: 'settings_notifier.dart#removeCrashReportRecipient'); + Logger.info('Crash report recipient removed: $email'); return updateState((oldState) { final updatedSet = oldState.crashReportRecipients..remove(email); return oldState.copyWith(crashReportRecipients: updatedSet); @@ -103,74 +103,74 @@ class SettingsNotifier extends _$SettingsNotifier { } Future setisFirstRun(bool value) { - Logger.info('First run set to $value', name: 'settings_notifier.dart#setFirstRun'); + Logger.info('First run set to $value'); return updateState((oldState) => oldState.copyWith(isFirstRun: value)); } Future sethideOTPs(bool value) { - Logger.info('Hide OTPs set to $value', name: 'settings_notifier.dart#setHideOTPs'); + Logger.info('Hide OTPs set to $value'); return updateState((oldState) => oldState.copyWith(hideOpts: value)); } Future setshowGuideOnStart(bool value) { - Logger.info('Show guide on start set to $value', name: 'settings_notifier.dart#setShowGuideOnStart'); + Logger.info('Show guide on start set to $value'); return updateState((oldState) => oldState.copyWith(showGuideOnStart: value)); } Future setLocalePreference(Locale locale) { - Logger.info('Locale set to $locale', name: 'settings_notifier.dart#setLocalePreference'); + Logger.info('Locale set to $locale'); return updateState((oldState) => oldState.copyWith(localePreference: locale)); } Future setUseSystemLocale(bool value) { - Logger.info('Use system locale set to $value', name: 'settings_notifier.dart#setUseSystemLocale'); + Logger.info('Use system locale set to $value'); return updateState((oldState) => oldState.copyWith(useSystemLocale: value)); } Future enablePolling() { - Logger.info('Polling set to true', name: 'settings_notifier.dart#enablePolling'); + Logger.info('Polling set to true'); return updateState((oldState) => oldState.copyWith(enablePolling: true)); } Future disablePolling() { - Logger.info('Polling set to false', name: 'settings_notifier.dart#disablePolling'); + Logger.info('Polling set to false'); return updateState((oldState) => oldState.copyWith(enablePolling: false)); } Future setPolling(bool value) { - Logger.info('Polling set to $value', name: 'settings_notifier.dart#setPolling'); + Logger.info('Polling set to $value'); return updateState((oldState) => oldState.copyWith(enablePolling: value)); } Future setLocale(Locale locale) { - Logger.info('Locale set to $locale', name: 'settings_notifier.dart#setLocale'); + Logger.info('Locale set to $locale'); return updateState((oldState) => oldState.copyWith(localePreference: locale)); } Future setVerboseLogging(bool value) async { - Logger.info('Verbose logging set to $value', name: 'settings_notifier.dart#setVerboseLogging'); + Logger.info('Verbose logging set to $value'); final updatedState = await updateState((oldState) => oldState.copyWith(verboseLogging: value)); Logger.setVerboseLogging(updatedState.verboseLogging); return updatedState; } Future toggleVerboseLogging() { - Logger.info('Toggling verbose logging', name: 'settings_notifier.dart#toggleVerboseLogging'); + Logger.info('Toggling verbose logging'); return updateState((oldState) => oldState.copyWith(verboseLogging: !oldState.verboseLogging)); } Future setFirstRun(bool value) { - Logger.info('First run set to $value', name: 'settings_notifier.dart#setFirstRun'); + Logger.info('First run set to $value'); return updateState((oldState) => oldState.copyWith(isFirstRun: value)); } Future setHidePushTokens(bool value) { - Logger.info('Hide push tokens set to $value', name: 'settings_notifier.dart#setHidePushTokens'); + Logger.info('Hide push tokens set to $value'); return updateState((oldState) => oldState.copyWith(hidePushTokens: value)); } Future setLatestStartedVersion(Version version) { - Logger.info('Latest started version set to $version', name: 'settings_notifier.dart#setLatestStartedVersion'); + Logger.info('Latest started version set to $version'); return updateState((oldState) => oldState.copyWith(latestStartedVersion: version)); } } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart index 8ed4b52a8..bdd010bb3 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -92,7 +92,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler _repo = _repoOverride ?? repo; _containerApi = _containerApiOverride ?? containerApi; _eccUtils = _eccUtilsOverride ?? eccUtils; - Logger.warning('Building containerProvider', name: 'CredentialsNotifier'); + Logger.warning('Building containerProvider'); final initState = await _repo.loadCredentialsState(); for (var container in initState.container.whereType()) { @@ -140,7 +140,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler await _stateMutex.acquire(); final newCredentials = container.toList(); final oldCredentials = (await future).container; - Logger.debug('Loaded container: $oldCredentials', name: 'CredentialsNotifier#addCredentials'); + Logger.debug('Loaded container: $oldCredentials'); final combinedCredentials = []; for (var oldCredential in oldCredentials) { final newCredential = newCredentials.firstWhereOrNull((newCredential) => newCredential.serial == oldCredential.serial); @@ -152,11 +152,11 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler } } combinedCredentials.addAll(newCredentials); - Logger.debug('Combined container: $combinedCredentials', name: 'CredentialsNotifier#addCredentials'); + Logger.debug('Combined container: $combinedCredentials'); final newState = await _saveCredentialsStateToRepo(TokenContainerState(container: combinedCredentials)); - Logger.debug('Saved container: $newState', name: 'CredentialsNotifier#addCredentials'); + Logger.debug('Saved container: $newState'); await update((_) => newState); - Logger.debug('Updated container: $newState', name: 'CredentialsNotifier#addCredentials'); + Logger.debug('Updated container: $newState'); _stateMutex.release(); return newState; } @@ -168,7 +168,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler FutureOr Function(TokenContainerState state) cb, { FutureOr Function(Object, StackTrace)? onError, }) async { - Logger.warning('Updating containerProvider', name: 'CredentialsNotifier'); + Logger.warning('Updating containerProvider'); return super.update(cb, onError: onError); } @@ -177,7 +177,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler final oldState = await future; final currentCredential = oldState.currentOf(container); if (currentCredential == null) { - Logger.info('Failed to update container. It was probably removed in the meantime.', name: 'CredentialsNotifier#updateCredential'); + Logger.info('Failed to update container. It was probably removed in the meantime.'); _stateMutex.release(); return null; } @@ -227,7 +227,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler @override Future handleProcessorResults(List results, Map args) async { - Logger.info('Handling processor results', name: 'CredentialsNotifier#handleProcessorResults'); + Logger.info('Handling processor results'); final containerCredentials = results.getData().whereType().toList(); if (containerCredentials.isEmpty) { return null; @@ -236,13 +236,13 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler final stateCredentials = currentState.container; final stateCredentialsSerials = stateCredentials.map((e) => e.serial); final newCredentials = containerCredentials.where((element) => !stateCredentialsSerials.contains(element.serial)).toList(); - Logger.info('Handling processor results: adding Credential', name: 'CredentialsNotifier#handleProcessorResults'); + Logger.info('Handling processor results: adding Credential'); await addCredentials(newCredentials); - Logger.info('Handling processor results: adding done (${newCredentials.length})', name: 'CredentialsNotifier#handleProcessorResults'); + Logger.info('Handling processor results: adding done (${newCredentials.length})'); for (var container in newCredentials) { - Logger.info('Handling processor results: finalize check ()', name: 'CredentialsNotifier#handleProcessorResults'); + Logger.info('Handling processor results: finalize check ()'); if (container is! TokenContainerUnfinalized) continue; - Logger.info('Handling processor results: finalize', name: 'CredentialsNotifier#handleProcessorResults'); + Logger.info('Handling processor results: finalize'); await finalize(container); } return null; @@ -255,7 +255,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler _finalizationMutex.release(); throw ArgumentError('Container must not be finalized'); } - Logger.info('Finalizing container ${container.serial}', name: 'CredentialsNotifier#finalize'); + Logger.info('Finalizing container ${container.serial}'); try { container = await _generateKeyPair(container); final Response response; @@ -276,12 +276,12 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler (container, publicServerKey) = await _parseResponse(container, response); await updateCredential(container, (c) => c.finalize(publicServerKey: publicServerKey)!); } on StateError { - Logger.info('Container was removed while finalizing', name: 'CredentialsNotifier#finalize'); + Logger.info('Container was removed while finalizing'); } on LocalizedArgumentError catch (e) { ref.read(statusMessageProvider.notifier).state = ('Failed to decode response body', e.localizedMessage(AppLocalizations.of(await globalContext)!)); await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseFailed)); } catch (e) { - Logger.error('Failed to finalize container ${container.serial}', name: 'CredentialsNotifier#finalize', error: e); + Logger.error('Failed to finalize container ${container.serial}', error: e); _finalizationMutex.release(); return; } @@ -355,7 +355,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponse)); if (container == null) throw StateError('Credential was removed'); responseJson = jsonDecode(responseBody); - Logger.debug('Response JSON: $responseJson', name: 'CredentialsNotifier#_parseResponse'); + Logger.debug('Response JSON: $responseJson'); final result = validate(value: responseJson['result'], validator: const ObjectValidator>(), name: 'result'); final value = validate(value: result['value'], validator: const ObjectValidator>(), name: 'value'); publicServerKey = validate( diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart index 2372195c0..f74673385 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart @@ -49,7 +49,7 @@ class TokenFolderNotifier extends _$TokenFolderNotifier { TokenFolderState build({required TokenFolderRepository repo}) { _repo = _repoOverride ?? repo; _stateMutex.acquire(); - Logger.info('Initializing token folder state', name: 'TokenFolderNotifier#initTokenFolder'); + Logger.info('Initializing token folder state'); initState = _loadFromRepo().then((newState) { _stateMutex.release(); return state = newState; @@ -68,10 +68,7 @@ class TokenFolderNotifier extends _$TokenFolderNotifier { await _repoMutex.acquire(); final success = await _repo.saveState(state); if (!success) { - Logger.warning( - 'Failed to save folders', - name: 'TokenFolderNotifier#_saveToRepo', - ); + Logger.warning('Failed to save folders'); _repoMutex.release(); return false; } @@ -85,10 +82,7 @@ class TokenFolderNotifier extends _$TokenFolderNotifier { final newState = oldState.addNewFolder(name); final success = await _saveToRepo(newState); if (!success) { - Logger.warning( - 'Failed to add new folder', - name: 'TokenFolderNotifier#_addNewFolder', - ); + Logger.warning('Failed to add new folder'); _stateMutex.release(); return oldState; } @@ -103,10 +97,7 @@ class TokenFolderNotifier extends _$TokenFolderNotifier { final newState = oldState.removeFolder(folder); final success = await _saveToRepo(newState); if (!success) { - Logger.warning( - 'Failed to remove folder', - name: 'TokenFolderNotifier#_removeFolder', - ); + Logger.warning('Failed to remove folder'); _stateMutex.release(); return oldState; } @@ -124,10 +115,7 @@ class TokenFolderNotifier extends _$TokenFolderNotifier { final newState = oldState.update(folder, updater); final success = await _saveToRepo(newState); if (!success) { - Logger.warning( - 'Failed to add or replace folders', - name: 'TokenFolderNotifier#addOrReplaceFolders', - ); + Logger.warning('Failed to add or replace folders'); _stateMutex.release(); return oldState; } @@ -146,10 +134,7 @@ class TokenFolderNotifier extends _$TokenFolderNotifier { final newState = oldState.update(folder, updater); final success = await _saveToRepo(newState); if (!success) { - Logger.warning( - 'Failed to add or replace folders', - name: 'TokenFolderNotifier#addOrReplaceFolders', - ); + Logger.warning('Failed to add or replace folders'); _stateMutex.release(); return oldState; } @@ -164,10 +149,7 @@ class TokenFolderNotifier extends _$TokenFolderNotifier { final newState = oldState.addOrReplaceFolders(folders); final success = await _saveToRepo(newState); if (!success) { - Logger.warning( - 'Failed to add or replace folders', - name: 'TokenFolderNotifier#addOrReplaceFolders', - ); + Logger.warning('Failed to add or replace folders'); _stateMutex.release(); return oldState; } @@ -194,10 +176,7 @@ class TokenFolderNotifier extends _$TokenFolderNotifier { final newState = oldState.addOrReplaceFolders(lockedFolders); final success = await _saveToRepo(newState); if (!success) { - Logger.warning( - 'Failed to add or replace folders', - name: 'TokenFolderNotifier#addOrReplaceFolders', - ); + Logger.warning('Failed to add or replace folders'); _stateMutex.release(); return oldState; } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart index 48e5d87c8..2fc555822 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart @@ -144,10 +144,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { await _repoMutex.acquire(); final success = await _repo.saveOrReplaceToken(token); if (!success) { - Logger.warning( - 'Saving token failed. Token: ${token.id}', - name: 'token_notifier.dart#_addOrReplaceToken', - ); + Logger.warning('Saving token failed. Token: ${token.id}'); _repoMutex.release(); return false; } @@ -158,7 +155,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { /// Adds a list of tokens and returns the tokens that could not be added or replaced. Future> _addOrReplaceTokens(List tokens) async { - Logger.debug('Adding ${tokens.length} tokens.', name: 'token_notifier.dart#_addOrReplaceTokens', verbose: true); + Logger.debug('Adding ${tokens.length} tokens.', verbose: true); await _repoMutex.acquire(); tokens = tokens.map((token) { final currentId = state.currentOf(token)?.id; @@ -167,18 +164,15 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { }).toList(); final failedTokens = await _repo.saveOrReplaceTokens(tokens); if (failedTokens.isNotEmpty) { - Logger.warning( - 'Saving tokens failed. Failed Tokens: ${failedTokens.length}', - name: 'token_notifier.dart#_saveOrReplaceTokens', - ); + Logger.warning('Saving tokens failed. Failed Tokens: ${failedTokens.length}'); } // Every token that is saved should not be in the failedTokens list final savedTokens = tokens.where((element) => !failedTokens.contains(element)).toList(); // Add the saved tokens to the state - Logger.info('Saved ${savedTokens.length} Tokens to storage.', name: 'token_notifier.dart#_saveOrReplaceTokens'); + Logger.info('Saved ${savedTokens.length} Tokens to storage.'); state = state.addOrReplaceTokens(savedTokens); - Logger.debug('New State: ${state.tokens.length} Tokens', name: 'token_notifier.dart#_saveOrReplaceTokens'); + Logger.debug('New State: ${state.tokens.length} Tokens'); _repoMutex.release(); return []; } @@ -188,16 +182,13 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { await _repoMutex.acquire(); final (newState, replaced) = state.replaceToken(token); if (!replaced) { - Logger.warning('Tried to replace a token that does not exist.', name: 'token_notifier.dart#_replaceToken'); + Logger.warning('Tried to replace a token that does not exist.'); _repoMutex.release(); return false; } final saved = await _repo.saveOrReplaceToken(token); if (!saved) { - Logger.warning( - 'Saving token failed. Token: ${token.id}', - name: 'token_notifier.dart#_replaceToken', - ); + Logger.warning('Saving token failed. Token: ${token.id}'); _repoMutex.release(); return false; } @@ -217,10 +208,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { } final failedToSave = await _repo.saveOrReplaceTokens(tokens); if (failedToSave.isNotEmpty) { - Logger.warning( - 'Saving tokens failed. Failed Tokens: ${failedToSave.length}', - name: 'token_notifier.dart#_saveOrReplaceTokens', - ); + Logger.warning('Saving tokens failed. Failed Tokens: ${failedToSave.length}'); final recovered = oldState.tokens.whereType().where((oldToken) => failedToSave.contains(oldToken)).toList(); state = state.addOrReplaceTokens(recovered); _repoMutex.release(); @@ -237,10 +225,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { final success = await _repo.deleteToken(token); if (!success) { - Logger.warning( - 'Deleting token failed. Token: ${token.id}', - name: 'token_notifier.dart#_deleteTokensRepo', - ); + Logger.warning('Deleting token failed. Token: ${token.id}'); state = state.addOrReplaceToken(token); _repoMutex.release(); return false; @@ -252,17 +237,14 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { /// Removes a list of tokens and returns the tokens that could not be removed. Future> _removeTokens(List tokens) async { - Logger.info('Removing ${tokens.length} tokens.', name: 'token_notifier.dart#_removeTokens'); + Logger.info('Removing ${tokens.length} tokens.'); await _repoMutex.acquire(); final oldState = state; state = state.withoutTokens(tokens); final failedTokens = await _repo.deleteTokens(tokens); if (failedTokens.isNotEmpty) { - Logger.warning( - 'Deleting tokens failed. Failed Tokens: ${failedTokens.length}', - name: 'token_notifier.dart#_deleteTokensRepo', - ); + Logger.warning('Deleting tokens failed. Failed Tokens: ${failedTokens.length}'); final recoveredTokens = oldState.tokens.where((oldToken) => failedTokens.contains(oldToken)).toList(); state = state.addOrReplaceTokens(recoveredTokens); _repoMutex.release(); @@ -285,7 +267,6 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { } catch (e) { Logger.error( 'Loading tokens from storage failed.', - name: 'token_notifier.dart#_loadFromRepo', error: e, ); _repoMutex.release(); @@ -303,7 +284,6 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { } catch (e) { Logger.error( 'Saving tokens to storage failed.', - name: 'token_notifier.dart#_saveStateToRepo', error: e, ); _repoMutex.release(); @@ -327,7 +307,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { _repoMutex.release(); final current = state.currentOf(token); if (current == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#updateToken'); + Logger.warning('Tried to update a token that does not exist.'); _stateMutex.release(); return null; } @@ -396,116 +376,6 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { /// Updates a list of tokens and returns the updated tokens if successful, the old tokens if not and an empty list if the tokens does not exist. Future> updateTokens(List tokens, T Function(T) updater) async => _updateTokens(tokens, updater); - /// Adds, Updates or Removes tokens based on the [TokenContainer] and returns the updated tokens. - // Future updateContainerTokens(TokenContainer container) async { - // await initState; - // Logger.info('Updating tokens from container.', name: 'token_notifier.dart#updateContainerTokens'); - // final templatesToAdd = []; - // final templatesToUpdate = []; - // final templatesToRemove = []; - - // final knownContainerTokens = state.tokens.ofContainer(container.serial)..addAll(state.maybePiTokens); - // final serverTokenTemplates = container.syncedTokenTemplates; - // Logger.debug( - // 'App knows ${knownContainerTokens.length} of ${serverTokenTemplates.length} server tokens', - // name: 'token_notifier.dart#updateContainerTokens', - // ); - // final appTokenTemplates = knownContainerTokens.toTemplates(); - // Logger.debug('All server templates: ${serverTokenTemplates.join('\n')}', name: 'token_notifier.dart#updateContainerTokens'); - // for (var serverTokenTemplate in serverTokenTemplates) { - // Logger.debug( - // 'Checking server token template: $serverTokenTemplate', - // name: 'token_notifier.dart#updateContainerTokens', - // ); - // // Searches for tokens that are in the container but not in the app to add them. - // // If the token is already in the app, it will be updated. - // // Reduces the [tokenTemplatesApp] list to only the tokens that are in the app but not in the container. These tokens will be removed. - // final appTemplate = appTokenTemplates.firstWhereOrNull( - // (templateApp) => templateApp.isSameTokenAs(serverTokenTemplate), - // ); - // if (appTemplate == null) { - // Logger.debug( - // 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} not found in app. Adding them.', - // name: 'token_notifier.dart#updateContainerTokens', - // ); - // templatesToAdd.add(serverTokenTemplate); - // } else { - // Logger.debug( - // 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} found in app. Checking for update.', - // name: 'token_notifier.dart#updateContainerTokens', - // ); - // appTokenTemplates.remove(appTemplate); - // if (!appTemplate.hasSameValuesAs(serverTokenTemplate)) { - // Logger.debug( - // 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} found in app the Template but is different. Check update.', - // name: 'token_notifier.dart#updateContainerTokens', - // ); - // // Only update the token if the template is different - // templatesToUpdate.add(serverTokenTemplate); - // } else { - // Logger.debug( - // 'Token with serial ${serverTokenTemplate.serial} or otps ${serverTokenTemplate.otpValues} found in app. And is the same. Skipping.', - // name: 'token_notifier.dart#updateContainerTokens', - // ); - // } - // } - // } - // // Removes all tokens that are in the app but not in the container. - // final remainingTokenTemplatesOfContainer = appTokenTemplates.where((template) => template.containerSerial == container.serial); - // templatesToRemove.addAll(remainingTokenTemplatesOfContainer); - - // Logger.debug( - // 'Add(${templatesToAdd.length}) | Check update(${templatesToUpdate.length}) | Remove(${templatesToRemove.length})', - // name: 'token_notifier.dart#updateContainerTokens', - // ); - - // final tokensToAdd = templatesToAdd.map((e) => e.toToken(container)).toList(); - // final tokensToUpdate = []; - // for (var template in templatesToUpdate) { - // final token = knownContainerTokens.firstWhereOrNull((token) => token.serial == template.serial); - // if (token == null) continue; - // final needsUpdate = template.tokenWouldBeUpdated(token); - // if (needsUpdate) { - // tokensToUpdate.add(token); - // } - // } - // final tokensToRemove = []; - // for (var template in templatesToRemove) { - // final token = knownContainerTokens.firstWhereOrNull((token) => token.serial == template.serial); - // if (token == null) continue; - // tokensToRemove.add(token); - // } - - // Logger.debug( - // 'Add(${tokensToAdd.length}) | Update(${tokensToUpdate.length}) | Remove(${tokensToRemove.length})', - // name: 'token_notifier.dart#updateContainerTokens', - // ); - - // if (tokensToAdd.isNotEmpty) { - // await addOrReplaceTokens(tokensToAdd); - // } - // if (tokensToUpdate.isNotEmpty) { - // await updateTokens(tokensToUpdate, (token) { - // final template = templatesToUpdate.firstWhereOrNull((template) => template.serial == token.serial); - // if (template == null) { - // Logger.debug( - // 'Not able to update token with id ${token.id} or serial ${token.serial}. No token serial/id matches the templates serial/id.', - // name: 'token_notifier.dart#updateContainerTokens', - // ); - // return token; - // } - // Logger.debug( - // 'Updating token with id:"${token.id}"/serial:"${token.serial}".', - // name: 'token_notifier.dart#updateContainerTokens', - // ); - // return token.copyUpdateByTemplate(template); - // }); - // } - // if (tokensToRemove.isNotEmpty) { - // await removeTokens(tokensToRemove); - // } - // } - /// Increments the counter of a HOTPToken and returns the updated token if successful, the old token if not and null if the token does not exist. Future incrementCounter(HOTPToken token) => _updateToken(token, (p0) => p0.copyWith(counter: token.counter + 1)); @@ -530,11 +400,11 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { Future showTokenById(String tokenId) { final token = getTokenById(tokenId); if (token == null) { - Logger.warning('Tried to show a token that does not exist.', name: 'token_notifier.dart#showTokenById'); + Logger.warning('Tried to show a token that does not exist.'); return Future.value(null); } if (token is! OTPToken) { - Logger.warning('Tried to show a token that is not an OTPToken.', name: 'token_notifier.dart#showTokenById'); + Logger.warning('Tried to show a token that is not an OTPToken.'); return Future.value(null); } return showToken(token); @@ -544,7 +414,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { try { return await _loadFromRepo(); } catch (_) { - Logger.warning('Loading tokens from storage failed.', name: 'token_notifier.dart#loadStateFromRepo'); + Logger.warning('Loading tokens from storage failed.'); return null; } } @@ -552,10 +422,10 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { Future saveStateToRepo() async { try { await _saveStateToRepo(state); - Logger.info('Saved ${state.tokens.length} Tokens to storage.', name: 'token_notifier.dart#saveStateToRepo'); + Logger.info('Saved ${state.tokens.length} Tokens to storage.'); return true; } catch (_) { - Logger.error('Saving tokens to storage failed.', name: 'token_notifier.dart#saveStateToRepo'); + Logger.error('Saving tokens to storage failed.'); return false; } } @@ -588,7 +458,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { /// Removes a list of tokens from the state and the repository. Future removeTokens(List tokens) async { - Logger.info('Removing ${tokens.length} tokens.', name: 'token_notifier.dart#removeTokens'); + Logger.info('Removing ${tokens.length} tokens.'); final pushTokens = tokens.whereType().toList(); final otherTokens = tokens.whereType().toList(); await _removeTokens(otherTokens); @@ -606,7 +476,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { try { await _firebaseUtils.deleteFirebaseToken(); } on SocketException { - Logger.warning('Could not delete firebase token.', name: 'token_notifier.dart#_removePushToken'); + Logger.warning('Could not delete firebase token.'); ref.read(statusMessageProvider.notifier).state = ( AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorUnlinkingPushToken(token.label), AppLocalizations.of(globalNavigatorKey.currentContext!)!.checkYourNetwork, @@ -616,7 +486,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { _firebaseUtils.getFBToken().then((fbToken) async { if (fbToken == null) { await _updateTokens(state.pushTokens, (p0) => p0.copyWith(fbToken: null)); - Logger.warning('Could not update firebase token because no firebase token is available.', name: 'token_notifier.dart#_removePushToken'); + Logger.warning('Could not update firebase token because no firebase token is available.'); ref.read(statusMessageProvider.notifier).state = ( AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorSynchronizationNoNetworkConnection, AppLocalizations.of(globalNavigatorKey.currentContext!)!.pleaseSyncManuallyWhenNetworkIsAvailable, @@ -627,30 +497,29 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { return; }); await _removeToken(token); - Logger.info('Push token "${token.id}" removed successfully.', name: 'token_notifier.dart#_removePushToken'); + Logger.info('Push token "${token.id}" removed successfully.'); } Future rolloutPushToken(PushToken token) async { PushToken? pushToken; pushToken = (getTokenById(token.id)) as PushToken?; if (pushToken == null) { - Logger.warning('Tried to rollout a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + Logger.warning('Tried to rollout a token that does not exist.'); return false; } assert(pushToken.url != null, 'Token url is null. Cannot rollout token without url.'); - Logger.info('Rolling out token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); + Logger.info('Rolling out token "${pushToken.id}"'); if (pushToken.isRolledOut) { - Logger.info('Ignoring rollout request: Token "${pushToken.id}" already rolled out.', name: 'token_notifier.dart#rolloutPushToken'); + Logger.info('Ignoring rollout request: Token "${pushToken.id}" already rolled out.'); return true; } if (pushToken.rolloutState.rollOutInProgress) { - Logger.info('Ignoring rollout request: Rollout of token "${pushToken.id}" already started. Tokenstate: ${pushToken.rolloutState} ', - name: 'token_notifier.dart#rolloutPushToken'); + Logger.info('Ignoring rollout request: Rollout of token "${pushToken.id}" already started. Tokenstate: ${pushToken.rolloutState} '); return false; } if (pushToken.expirationDate?.isBefore(DateTime.now()) == true) { - Logger.info('Ignoring rollout request: Token "${pushToken.id}" is expired. ', name: 'token_notifier.dart#rolloutPushToken'); + Logger.info('Ignoring rollout request: Token "${pushToken.id}" is expired. '); if (globalNavigatorKey.currentContext != null) { ref.read(statusMessageProvider.notifier).state = ( @@ -663,13 +532,13 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { } if (pushToken.privateTokenKey == null) { - Logger.info('Updating rollout state of token "${pushToken.id}" to generatingRSAKeyPair', name: 'token_notifier.dart#rolloutPushToken'); + Logger.info('Updating rollout state of token "${pushToken.id}" to generatingRSAKeyPair'); pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.generatingRSAKeyPair)); if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + Logger.warning('Tried to update a token that does not exist.'); return false; } - Logger.info('Updated token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); + Logger.info('Updated token "${pushToken.id}"'); try { final keyPair = await _rsaUtils.generateRSAKeyPair(); pushToken = pushToken.withPrivateTokenKey(keyPair.privateKey); @@ -679,11 +548,15 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { return p0.withPublicTokenKey(keyPair.publicKey); }) ?? pushToken; - Logger.info('Updated token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); + Logger.info('Updated token "${pushToken.id}"'); } catch (e, s) { - Logger.error('Error while generating RSA key pair.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); + Logger.error( + 'Error while generating RSA key pair.', + error: e, + stackTrace: s, + ); if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + Logger.warning('Tried to update a token that does not exist.'); return false; } pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.generatingRSAKeyPairFailed)); @@ -693,22 +566,22 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKey)); if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + Logger.warning('Tried to update a token that does not exist.'); return false; } if (!kIsWeb && Platform.isIOS) { - Logger.warning('Triggering network access permission for token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); + Logger.warning('Triggering network access permission for token "${pushToken.id}"'); if (await _ioClient.triggerNetworkAccessPermission(url: pushToken.url!, sslVerify: pushToken.sslVerify) == false) { - Logger.warning('Network access permission for token "${pushToken.id}" failed.', name: 'token_notifier.dart#rolloutPushToken'); + Logger.warning('Network access permission for token "${pushToken.id}" failed.'); _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKeyFailed)); return false; } - Logger.warning('Network access permission for token "${pushToken.id}" successful.', name: 'token_notifier.dart#rolloutPushToken'); + Logger.warning('Network access permission for token "${pushToken.id}" successful.'); } try { // TODO What to do with poll only tokens if google-services is used? - Logger.warning('SSLVerify: ${pushToken.sslVerify}', name: 'token_notifier.dart#rolloutPushToken'); + Logger.warning('SSLVerify: ${pushToken.sslVerify}'); final fbToken = await _firebaseUtils.getFBToken(); Response response = await _ioClient.doPost( sslVerify: pushToken.sslVerify, @@ -724,36 +597,37 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { if (response.statusCode == 200) { pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.parsingResponse, fbToken: fbToken)); if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + Logger.warning('Tried to update a token that does not exist.'); return false; } try { RSAPublicKey publicServerKey = await _parseRollOutResponse(response); pushToken = await _updateToken(pushToken, (p0) => p0.withPublicServerKey(publicServerKey)); if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + Logger.warning('Tried to update a token that does not exist.'); return false; } } on FormatException catch (e, s) { showMessage(message: "Couldn't parsing RSA public key: ${e.message}", duration: const Duration(seconds: 3)); - Logger.warning('Error while parsing RSA public key.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); + Logger.warning('Error while parsing RSA public key.', error: e, stackTrace: s); if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + Logger.warning('Tried to update a token that does not exist.'); return false; } pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.parsingResponseFailed)); return false; } - Logger.info('Roll out successful', name: 'token_notifier.dart#rolloutPushToken'); + Logger.info('Roll out successful'); pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(isRolledOut: true, rolloutState: PushTokenRollOutState.rolloutComplete)); checkNotificationPermission(); return true; } else { - Logger.warning('Post request on roll out failed.', - name: 'token_notifier.dart#rolloutPushToken', - error: 'Token: ${pushToken.serial}\nStatus code: ${response.statusCode},\nURL:${response.request?.url}\nBody: ${response.body}'); + Logger.warning( + 'Post request on roll out failed.', + error: 'Token: ${pushToken.serial}\nStatus code: ${response.statusCode},\nURL:${response.request?.url}\nBody: ${response.body}', + ); try { final message = response.body.isNotEmpty ? (json.decode(response.body)['result']?['error']?['message']) : ''; @@ -775,22 +649,22 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { } } catch (e, s) { if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + Logger.warning('Tried to update a token that does not exist.'); return false; } pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKeyFailed)); if (pushToken == null) { - Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); + Logger.warning('Tried to update a token that does not exist.'); return false; } if (e is PlatformException && e.code == FIREBASE_TOKEN_ERROR_CODE || e is SocketException || e is TimeoutException || e is FirebaseException) { - Logger.warning('Connection error: Roll out push token failed.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); + Logger.warning('Connection error: Roll out push token failed.', error: e, stackTrace: s); showMessage( message: AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutNoConnectionToServer(pushToken.label), duration: const Duration(seconds: 3), ); } else if (e is HandshakeException) { - Logger.warning('SSL error: Roll out push token failed.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); + Logger.warning('SSL error: Roll out push token failed.', error: e, stackTrace: s); showMessage( message: AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorRollOutSSLHandshakeFailed, duration: const Duration(seconds: 3), @@ -802,7 +676,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { duration: const Duration(seconds: 3), ); } - Logger.error('Roll out push token failed.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); + Logger.error('Roll out push token failed.', error: e, stackTrace: s); } return false; } @@ -821,14 +695,14 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { /// as this can not be guaranteed to work. There is a manual option available /// through the settings also. Future<(List, List)?> updateFirebaseToken([String? firebaseToken]) async { - Logger.info('Updating firebase token for all push tokens.', name: 'push_provider.dart#updateFirebaseToken'); + Logger.info('Updating firebase token for all push tokens.'); firebaseToken ??= await _firebaseUtils.getFBToken(); if (firebaseToken == null) { - Logger.warning('Could not update firebase token because no firebase token is available.', name: 'push_provider.dart#updateFirebaseToken'); + Logger.warning('Could not update firebase token because no firebase token is available.'); return null; } List tokenList = state.pushTokens.where((t) => t.isRolledOut && t.fbToken != firebaseToken).toList(); - Logger.info('Updating firebase token for ${tokenList.length} push tokens.', name: 'push_provider.dart#updateFirebaseToken'); + Logger.info('Updating firebase token for ${tokenList.length} push tokens.'); bool allUpdated = true; final List failedTokens = []; final List unsuportedTokens = []; @@ -845,7 +719,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { //serial=element //timestamp= //signature=SIGNATURE(||) - Logger.warning('Updating firebase token for push token "${p.serial}"', name: 'push_provider.dart#updateFirebaseToken'); + Logger.warning('Updating firebase token for push token "${p.serial}"'); String timestamp = DateTime.now().toUtc().toIso8601String(); String message = '$firebaseToken|${p.serial}|$timestamp'; String? signature = await _rsaUtils.trySignWithToken(p, message); @@ -860,10 +734,10 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { sslVerify: p.sslVerify, ); if (response.statusCode == 200) { - Logger.info('Updating firebase token for push token succeeded!', name: 'push_provider.dart#updateFirebaseToken'); + Logger.info('Updating firebase token for push token succeeded!'); _updateToken(p, (p0) => p0.copyWith(fbToken: firebaseToken)); } else { - Logger.warning('Updating firebase token for push token failed!', name: 'push_provider.dart#updateFirebaseToken'); + Logger.warning('Updating firebase token for push token failed!'); failedTokens.add(p); allUpdated = false; } @@ -919,7 +793,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { tokensToKeep = resultTokens; } } catch (error, stackTrace) { - Logger.error('Error while processing QR code.', name: 'token_notifier.dart#handleQrCode', error: error, stackTrace: stackTrace); + Logger.error('Error while processing QR code.', error: error, stackTrace: stackTrace); return null; } if (tokensToKeep == null) return null; @@ -940,12 +814,12 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { ///////////////////////////////////////////////////////////////////////////// */ Future _parseRollOutResponse(Response response) async { - Logger.info('Parsing rollout response, try to extract public_key.', name: 'token_notifier.dart#_parseRollOutResponse'); + Logger.info('Parsing rollout response, try to extract public_key.'); try { String key = json.decode(response.body)['detail']['public_key']; key = key.replaceAll('\n', ''); - Logger.info('Extracting public key was successful.', name: 'token_notifier.dart#_parseRollOutResponse'); + Logger.info('Extracting public key was successful.'); return _rsaUtils.deserializeRSAPublicKeyPKCS1(key); } on FormatException catch (e) { @@ -954,7 +828,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { } Future _handlePushTokensIfExist() async { - Logger.info('Handling push tokens if they exist.', name: 'token_notifier.dart#_handlePushTokensIfExist'); + Logger.info('Handling push tokens if they exist.'); final pushTokens = state.pushTokens; if (pushTokens.isEmpty || state.pushTokens.isEmpty) { if ((await ref.read(settingsProvider.future)).hidePushTokens == true) { @@ -969,7 +843,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { checkNotificationPermission(); } for (final element in state.pushTokensToRollOut) { - Logger.info('Handling push token "${element.id}"', name: 'token_notifier.dart#_handlePushTokensIfExist'); + Logger.info('Handling push token "${element.id}"'); await rolloutPushToken(element); } } diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart index f02354693..fdb125772 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart @@ -24,7 +24,7 @@ import '../../../logger.dart'; final draggingSortableProvider = StateProvider( (ref) { - Logger.info("New draggingSortableProvider created", name: 'draggingSortableProvider'); + Logger.info("New draggingSortableProvider created"); return null; }, name: 'draggingSortableProvider', diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart index 7a6dcfe16..7878fff6e 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/home_widget_provider.dart @@ -26,7 +26,7 @@ import '../generated_providers/token_notifier.dart'; final homeWidgetProvider = StateProvider>( (ref) { - Logger.info("New homeWidgetProvider created", name: 'homeWidgetProvider'); + Logger.info("New homeWidgetProvider created"); ref.listen(tokenProvider, (previous, next) { HomeWidgetUtils().updateTokensIfLinked(next.lastlyUpdatedTokens); }); diff --git a/lib/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart b/lib/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart index 610d31e94..6a9587bdb 100644 --- a/lib/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart @@ -23,7 +23,7 @@ import '../../../logger.dart'; final statusMessageProvider = StateProvider<(String, String?)?>( (ref) { - Logger.info("New statusMessageProvider created", name: 'statusMessageProvider'); + Logger.info("New statusMessageProvider created"); return null; }, ); diff --git a/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart b/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart index 06803aa12..4a1f975c6 100644 --- a/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart +++ b/lib/utils/riverpod/riverpod_providers/stream_providers/connectivity_provider.dart @@ -28,11 +28,11 @@ import '../state_providers/status_message_provider.dart'; final connectivityProvider = StreamProvider>( (ref) { - Logger.info("New connectivityProvider created", name: 'connectivityProvider'); + Logger.info("New connectivityProvider created"); ref.read(tokenProvider.notifier).initState.then( (newState) { Connectivity().checkConnectivity().then((connectivity) { - Logger.info("First connectivity check: $connectivity", name: 'connectivityProvider#initialCheck'); + Logger.info("First connectivity check: $connectivity"); final hasNoConnection = connectivity.contains(ConnectivityResult.none); if (hasNoConnection && newState.hasPushTokens && globalNavigatorKey.currentContext != null) { ref.read(statusMessageProvider.notifier).state = (AppLocalizations.of(globalNavigatorKey.currentContext!)!.noNetworkConnection, null); diff --git a/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart b/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart index 98f93af38..eee04d1d4 100644 --- a/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/completed_introduction_notifier.dart @@ -46,7 +46,7 @@ // loadingRepo = Future(() async { // final newState = await _repo.loadCompletedIntroductions(); // state = newState; -// Logger.info('Loading completed introductions from repo: $state', name: 'settings_notifier.dart#_loadFromRepo'); +// Logger.info('Loading completed introductions from repo: $state'); // return newState; // }); // await loadingRepo; @@ -56,9 +56,9 @@ // loadingRepo = Future(() async { // final success = await _repo.saveCompletedIntroductions(state); // if (success) { -// Logger.info('Saving completed introductions to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); +// Logger.info('Saving completed introductions to repo: $state'); // } else { -// Logger.warning('Failed to save completed introductions to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); +// Logger.warning('Failed to save completed introductions to repo: $state'); // } // return state; // }); diff --git a/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart b/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart index ad4e7cf22..f5fe93a1d 100644 --- a/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/progress_state_notifier.dart @@ -28,20 +28,20 @@ class ProgressStateNotifier extends StateNotifier { double? get progress => state?.progress; ProgressState initProgress(int max, int value) { - Logger.info('Initializing progress state', name: 'ProgressStateNotifier#initProgress'); + Logger.info('Initializing progress state'); final newState = ProgressState(max: max, value: value); state = newState; return newState; } void deleteProgress() { - Logger.info('Deleting progress state', name: 'ProgressStateNotifier#deleteProgress'); + Logger.info('Deleting progress state'); state = null; } ProgressState? resetProgress() { if (state == null) return state; - Logger.info('Resetting progress state', name: 'ProgressStateNotifier#resetProgress'); + Logger.info('Resetting progress state'); final newState = state!.copyWith(value: 0); state = newState; return newState; @@ -49,7 +49,7 @@ class ProgressStateNotifier extends StateNotifier { ProgressState? setProgressMax(int max) { if (state == null) return state; - Logger.info('Setting progress max to $max', name: 'ProgressStateNotifier#setProgressMax'); + Logger.info('Setting progress max to $max'); final newState = state!.copyWith(max: max); state = newState; return newState; @@ -57,7 +57,7 @@ class ProgressStateNotifier extends StateNotifier { ProgressState? setProgressValue(int value) { if (state == null) return state; - Logger.info('Setting progress value to $value/${state!.max}', name: 'ProgressStateNotifier#setProgressValue'); + Logger.info('Setting progress value to $value/${state!.max}'); final newState = state!.copyWith(value: value); state = newState; return newState; diff --git a/lib/utils/riverpod/state_notifiers/push_request_notifier.dart b/lib/utils/riverpod/state_notifiers/push_request_notifier.dart index 5fe573b1f..5b25150ea 100644 --- a/lib/utils/riverpod/state_notifiers/push_request_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/push_request_notifier.dart @@ -80,7 +80,7 @@ // initState = initialState != null ? Future.value(initialState) : _loadFromRepo(); // _pushProvider.subscribe(add); // await initState; -// Logger.info('PushRequestNotifier initialized', name: 'push_request_notifier.dart#_init'); +// Logger.info('PushRequestNotifier initialized'); // } // @override @@ -197,7 +197,7 @@ // await updatingRequestMutex.acquire(); // final current = state.currentOf(pushRequest); // if (current == null) { -// Logger.warning('Tried to update a push request that does not exist.', name: 'push_request_notifier.dart#updatePushRequest'); +// Logger.warning('Tried to update a push request that does not exist.'); // updatingRequestMutex.release(); // return null; // } @@ -223,22 +223,22 @@ // /// It should be still in the CustomIntBuffer of the state. // Future accept(PushToken pushToken, PushRequest pushRequest, {String? selectedAnswer}) async { // if (pushRequest.accepted != null) { -// Logger.warning('The push request is already accepted or declined.', name: 'push_request_notifier.dart#accept'); +// Logger.warning('The push request is already accepted or declined.'); // return false; // } -// Logger.info('Accept push request.', name: 'push_request_notifier.dart#accept'); +// Logger.info('Accept push request.'); // final updated = await _updatePushRequest(pushRequest, (p0) async { // final updated = p0.copyWith(accepted: true, selectedAnswer: () => selectedAnswer); // final success = await _handleReaction(pushRequest: updated, token: pushToken); // if (!success) { -// Logger.warning('Failed to handle push request reaction.', name: 'push_request_notifier.dart#accept'); +// Logger.warning('Failed to handle push request reaction.'); // return p0; // } // return updated; // }); // if (updated == null || updated.accepted != true) { -// Logger.warning('Failed to accept push request.', name: 'push_request_notifier.dart#accept'); +// Logger.warning('Failed to accept push request.'); // return false; // } // await _remove(updated); @@ -247,21 +247,21 @@ // Future decline(PushToken pushToken, PushRequest pushRequest) async { // if (pushRequest.accepted != null) { -// Logger.warning('The push request is already accepted or declined.', name: 'push_request_notifier.dart#decline'); +// Logger.warning('The push request is already accepted or declined.'); // return false; // } -// Logger.info('Decline push request.', name: 'push_request_notifier.dart#decline'); +// Logger.info('Decline push request.'); // final updated = await _updatePushRequest(pushRequest, (p0) async { // final updated = p0.copyWith(accepted: false, selectedAnswer: () => null); // final success = await _handleReaction(pushRequest: updated, token: pushToken); // if (!success) { -// Logger.warning('Failed to handle push request reaction.', name: 'push_request_notifier.dart#accept'); +// Logger.warning('Failed to handle push request reaction.'); // return p0; // } // return updated; // }); // if (updated == null || updated.accepted != false) { -// Logger.warning('Failed to decline push request.', name: 'push_request_notifier.dart#decline'); +// Logger.warning('Failed to decline push request.'); // return false; // } // await _remove(updated); @@ -281,7 +281,7 @@ // // Remove the request after it expires. // if (success) _setupTimer(pr); -// Logger.info('Added push request ${pr.id} to state', name: 'token_notifier.dart#addPushRequestToToken'); +// Logger.info('Added push request ${pr.id} to state'); // return true; // } @@ -297,16 +297,16 @@ // } // void _cancelTimer(PushRequest pr) { -// Logger.info('Canceling timer for push request ${pr.id}', name: 'push_request_notifier.dart#_cancelTimer'); +// Logger.info('Canceling timer for push request ${pr.id}'); // final timer = _expirationTimers.remove(pr.id.toString())?..cancel(); // if (timer == null) { -// Logger.warning('Timer for push request ${pr.id} not found.', name: 'push_request_notifier.dart#_cancelTimer'); +// Logger.warning('Timer for push request ${pr.id} not found.'); // } // } // void _cancalAllTimers() { // if (_expirationTimers.keys.isNotEmpty) { -// Logger.info('Canceling all timers: [${_expirationTimers.keys}]', name: 'push_request_notifier.dart#_cancelAllTimers'); +// Logger.info('Canceling all timers: [${_expirationTimers.keys}]'); // } // final ids = _expirationTimers.keys.toList(); // for (var id in ids) { @@ -341,7 +341,7 @@ // Future _handleReaction({required PushRequest pushRequest, required PushToken token}) async { // if (pushRequest.accepted == null) return false; -// Logger.info('Push auth request accepted=${pushRequest.accepted}, sending response to privacyidea', name: 'token_widgets.dart#handleReaction'); +// Logger.info('Push auth request accepted=${pushRequest.accepted}, sending response to privacyidea'); // // POST https://privacyideaserver/validate/check // // nonce= // // serial= @@ -362,10 +362,10 @@ // body['presence_answer'] = pushRequest.selectedAnswer!; // msg += '|${pushRequest.selectedAnswer!}'; // } -// Logger.warning('Signature message: $msg', name: 'token_widgets.dart#handleReaction'); +// Logger.warning('Signature message: $msg'); // String? signature = await _rsaUtils.trySignWithToken(token, msg); // if (signature == null) { -// Logger.warning('Failed to sign push request response.', name: 'token_widgets.dart#handleReaction'); +// Logger.warning('Failed to sign push request response.'); // return false; // } @@ -373,10 +373,10 @@ // Response response; // try { -// Logger.info('Sending push request response.', name: 'token_widgets.dart#_handleReaction'); +// Logger.info('Sending push request response.'); // response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body); // } catch (e) { -// Logger.warning('Sending push request response failed. Retrying.', name: 'token_widgets.dart#handleReaction'); +// Logger.warning('Sending push request response failed. Retrying.'); // try { // response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body); // } catch (e) { @@ -391,7 +391,7 @@ // '${appLocalizations.sendPushRequestResponseFailed}\n${appLocalizations.statusCode(response.statusCode)}', // tryJsonDecode(response.body)?["result"]?["error"]?["message"], // ); -// Logger.warning('Sending push request response failed.', name: 'token_widgets.dart#handleReaction'); +// Logger.warning('Sending push request response failed.'); // return false; // } // return true; diff --git a/lib/utils/riverpod/state_notifiers/settings_notifier.dart b/lib/utils/riverpod/state_notifiers/settings_notifier.dart index 5a45b5292..d9a9bbc13 100644 --- a/lib/utils/riverpod/state_notifiers/settings_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/settings_notifier.dart @@ -46,7 +46,7 @@ // final newState = await _repo.loadSettings(); // PushProvider.instance?.setPollingEnabled(state.enablePolling); // state = newState; -// Logger.info('Loading settings from repo: $newState', name: 'settings_notifier.dart#_loadFromRepo'); +// Logger.info('Loading settings from repo: $newState'); // return newState; // }); // await loadingRepo; @@ -55,100 +55,100 @@ // void _saveToRepo() async { // loadingRepo = Future(() async { // await _repo.saveSettings(state); -// Logger.info('Saving settings to repo: $state', name: 'settings_notifier.dart#_saveToRepo'); +// Logger.info('Saving settings to repo: $state'); // return state; // }); // } // void addCrashReportRecipient(String email) { -// Logger.info('Crash report recipient added: $email', name: 'settings_notifier.dart#addCrashReportRecipient'); +// Logger.info('Crash report recipient added: $email'); // var updatedSet = state.crashReportRecipients..add(email); // state = state.copyWith(crashReportRecipients: updatedSet); // _saveToRepo(); // } // set isFirstRun(bool value) { -// Logger.info('First run set to $value', name: 'settings_notifier.dart#setFirstRun'); +// Logger.info('First run set to $value'); // state = state.copyWith(isFirstRun: value); // _saveToRepo(); // } // set hideOTPs(bool value) { -// Logger.info('Hide OTPs set to $value', name: 'settings_notifier.dart#setHideOTPs'); +// Logger.info('Hide OTPs set to $value'); // state = state.copyWith(hideOpts: value); // _saveToRepo(); // } // set showGuideOnStart(bool value) { -// Logger.info('Show guide on start set to $value', name: 'settings_notifier.dart#setShowGuideOnStart'); +// Logger.info('Show guide on start set to $value'); // state = state.copyWith(showGuideOnStart: value); // _saveToRepo(); // } // void setLocalePreference(Locale locale) { -// Logger.info('Locale set to $locale', name: 'settings_notifier.dart#setLocalePreference'); +// Logger.info('Locale set to $locale'); // state = state.copyWith(localePreference: locale); // _saveToRepo(); // } // void setUseSystemLocale(bool value) { -// Logger.info('Use system locale set to $value', name: 'settings_notifier.dart#setUseSystemLocale'); +// Logger.info('Use system locale set to $value'); // state = state.copyWith(useSystemLocale: value); // _saveToRepo(); // } // void enablePolling() { -// Logger.info('Polling set to true', name: 'settings_notifier.dart#enablePolling'); +// Logger.info('Polling set to true'); // state = state.copyWith(enablePolling: true); // _saveToRepo(); // } // void disablePolling() { -// Logger.info('Polling set to false', name: 'settings_notifier.dart#disablePolling'); +// Logger.info('Polling set to false'); // state = state.copyWith(enablePolling: false); // _saveToRepo(); // } // void setPolling(bool value) { -// Logger.info('Polling set to $value', name: 'settings_notifier.dart#setPolling'); +// Logger.info('Polling set to $value'); // state = state.copyWith(enablePolling: value); // _saveToRepo(); // } // void setLocale(Locale locale) { -// Logger.info('Locale set to $locale', name: 'settings_notifier.dart#setLocale'); +// Logger.info('Locale set to $locale'); // state = state.copyWith(localePreference: locale); // _saveToRepo(); // } // void setVerboseLogging(bool value) { -// Logger.info('Verbose logging set to $value', name: 'settings_notifier.dart#setVerboseLogging'); +// Logger.info('Verbose logging set to $value'); // state = state.copyWith(verboseLogging: value); // _saveToRepo(); // } // void toggleVerboseLogging() { // final value = !state.verboseLogging; -// Logger.info('Verbose logging set to $value', name: 'settings_notifier.dart#setVerboseLogging'); +// Logger.info('Verbose logging set to $value'); // state = state.copyWith(verboseLogging: value); // _saveToRepo(); // } // void setFirstRun(bool value) { -// Logger.info('First run set to $value', name: 'settings_notifier.dart#setFirstRun'); +// Logger.info('First run set to $value'); // state = state.copyWith(isFirstRun: value); // _saveToRepo(); // } // void setHidePushTokens(bool value) { -// Logger.info('Hide push tokens set to $value', name: 'settings_notifier.dart#setHidePushTokens'); +// Logger.info('Hide push tokens set to $value'); // state = state.copyWith(hidePushTokens: value); // _saveToRepo(); // } // void setLatestStartedVersion(Version version) { // if (state.latestStartedVersion >= version) return; -// Logger.info('Latest started version set to $version', name: 'settings_notifier.dart#setLatestStartedVersion'); +// Logger.info('Latest started version set to $version'); // state = state.copyWith(latestStartedVersion: version); // _saveToRepo(); // } diff --git a/lib/utils/riverpod/state_notifiers/sortable_notifier.dart b/lib/utils/riverpod/state_notifiers/sortable_notifier.dart index 6451f54ea..4407192f4 100644 --- a/lib/utils/riverpod/state_notifiers/sortable_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/sortable_notifier.dart @@ -54,7 +54,7 @@ // // /// Handles a new list of [T]. // // /// First removes all elements of type [T] from the current state and then adds the new list. // // Future> handleNewStateList(List newList) async { -// // Logger.info('Handling new state list of type $T', name: 'SortableNotifier#handleNewStateList'); +// // Logger.info('Handling new state list of type $T'); // // await _waitInit(); // // var newState = List.from(state); // // newState.removeWhere((element) => element is T); diff --git a/lib/utils/riverpod/state_notifiers/token_notifier.dart b/lib/utils/riverpod/state_notifiers/token_notifier.dart index e27872e32..e92bfcf6f 100644 --- a/lib/utils/riverpod/state_notifiers/token_notifier.dart +++ b/lib/utils/riverpod/state_notifiers/token_notifier.dart @@ -92,7 +92,7 @@ // initState = initialState != null ? Future.value(initialState) : _loadFromRepo(); // await initState; // await hideLockedTokens(); -// Logger.info('TokenNotifier initialized.', name: 'token_notifier.dart#_init'); +// Logger.info('TokenNotifier initialized.'); // } // /* @@ -134,9 +134,9 @@ // return failedTokens; // } // // [failedTokens] is empty, so every token was saved successfully and we dont need to filter the tokens -// Logger.info('Saved ${tokens.length} Tokens to storage.', name: 'token_notifier.dart#_saveOrReplaceTokens'); +// Logger.info('Saved ${tokens.length} Tokens to storage.'); // state = state.addOrReplaceTokens(tokens); -// Logger.debug('New State: ${state.tokens.length} Tokens', name: 'token_notifier.dart#_saveOrReplaceTokens'); +// Logger.debug('New State: ${state.tokens.length} Tokens'); // _loadingRepoMutex.release(); // return []; // } @@ -146,7 +146,7 @@ // await _loadingRepoMutex.acquire(); // final (newState, replaced) = state.replaceToken(token); // if (!replaced) { -// Logger.warning('Tried to replace a token that does not exist.', name: 'token_notifier.dart#_replaceToken'); +// Logger.warning('Tried to replace a token that does not exist.'); // _loadingRepoMutex.release(); // return false; // } @@ -210,7 +210,7 @@ // /// Removes a list of tokens and returns the tokens that could not be removed. // Future> _removeTokens(List tokens) async { -// Logger.info('Removing ${tokens.length} tokens.', name: 'token_notifier.dart#_removeTokens'); +// Logger.info('Removing ${tokens.length} tokens.'); // await _loadingRepoMutex.acquire(); // final oldState = state; // state = state.withoutTokens(tokens); @@ -285,7 +285,7 @@ // _loadingRepoMutex.release(); // final current = state.currentOf(token); // if (current == null) { -// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#updateToken'); +// Logger.warning('Tried to update a token that does not exist.'); // _updatingTokensMutex.release(); // return null; // } @@ -357,7 +357,7 @@ // /// Adds, Updates or Removes tokens based on the [TokenContainer] and returns the updated tokens. // Future updateContainerTokens(TokenContainer container) async { // await initState; -// Logger.info('Updating tokens from container.', name: 'token_notifier.dart#updateContainerTokens'); +// Logger.info('Updating tokens from container.'); // final templatesToAdd = []; // final templatesToUpdate = []; // final templatesToRemove = []; @@ -369,7 +369,7 @@ // name: 'token_notifier.dart#updateContainerTokens', // ); // final appTokenTemplates = knownContainerTokens.toTemplates(); -// Logger.debug('All server templates: ${serverTokenTemplates.join('\n')}', name: 'token_notifier.dart#updateContainerTokens'); +// Logger.debug('All server templates: ${serverTokenTemplates.join('\n')}'); // for (var serverTokenTemplate in serverTokenTemplates) { // Logger.debug( // 'Checking server token template: $serverTokenTemplate', @@ -488,11 +488,11 @@ // Future showTokenById(String tokenId) { // final token = getTokenById(tokenId); // if (token == null) { -// Logger.warning('Tried to show a token that does not exist.', name: 'token_notifier.dart#showTokenById'); +// Logger.warning('Tried to show a token that does not exist.'); // return Future.value(null); // } // if (token is! OTPToken) { -// Logger.warning('Tried to show a token that is not an OTPToken.', name: 'token_notifier.dart#showTokenById'); +// Logger.warning('Tried to show a token that is not an OTPToken.'); // return Future.value(null); // } // return showToken(token); @@ -502,7 +502,7 @@ // try { // return await _loadFromRepo(); // } catch (_) { -// Logger.warning('Loading tokens from storage failed.', name: 'token_notifier.dart#loadStateFromRepo'); +// Logger.warning('Loading tokens from storage failed.'); // return null; // } // } @@ -510,10 +510,10 @@ // Future saveStateToRepo() async { // try { // await _saveStateToRepo(state); -// Logger.info('Saved ${state.tokens.length} Tokens to storage.', name: 'token_notifier.dart#saveStateToRepo'); +// Logger.info('Saved ${state.tokens.length} Tokens to storage.'); // return true; // } catch (_) { -// Logger.error('Saving tokens to storage failed.', name: 'token_notifier.dart#saveStateToRepo'); +// Logger.error('Saving tokens to storage failed.'); // return false; // } // } @@ -546,7 +546,7 @@ // /// Removes a list of tokens from the state and the repository. // Future removeTokens(List tokens) async { -// Logger.info('Removing ${tokens.length} tokens.', name: 'token_notifier.dart#removeTokens'); +// Logger.info('Removing ${tokens.length} tokens.'); // final pushTokens = tokens.whereType().toList(); // final otherTokens = tokens.whereType().toList(); // await _removeTokens(otherTokens); @@ -559,7 +559,7 @@ // try { // await _firebaseUtils.deleteFirebaseToken(); // } on SocketException { -// Logger.warning('Could not delete firebase token.', name: 'token_notifier.dart#_removePushToken'); +// Logger.warning('Could not delete firebase token.'); // ref.read(statusMessageProvider.notifier).state = ( // AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorUnlinkingPushToken(token.label), // AppLocalizations.of(globalNavigatorKey.currentContext!)!.checkYourNetwork, @@ -569,7 +569,7 @@ // _firebaseUtils.getFBToken().then((fbToken) async { // if (fbToken == null) { // await _updateTokens(state.pushTokens, (p0) => p0.copyWith(fbToken: null)); -// Logger.warning('Could not update firebase token because no firebase token is available.', name: 'token_notifier.dart#_removePushToken'); +// Logger.warning('Could not update firebase token because no firebase token is available.'); // ref.read(statusMessageProvider.notifier).state = ( // AppLocalizations.of(globalNavigatorKey.currentContext!)!.errorSynchronizationNoNetworkConnection, // AppLocalizations.of(globalNavigatorKey.currentContext!)!.pleaseSyncManuallyWhenNetworkIsAvailable, @@ -580,21 +580,21 @@ // return; // }); // await _removeToken(token); -// Logger.info('Push token "${token.id}" removed successfully.', name: 'token_notifier.dart#_removePushToken'); +// Logger.info('Push token "${token.id}" removed successfully.'); // } // Future rolloutPushToken(PushToken token) async { // PushToken? pushToken; // pushToken = (getTokenById(token.id)) as PushToken?; // if (pushToken == null) { -// Logger.warning('Tried to rollout a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.warning('Tried to rollout a token that does not exist.'); // return false; // } // assert(pushToken.url != null, 'Token url is null. Cannot rollout token without url.'); -// Logger.info('Rolling out token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.info('Rolling out token "${pushToken.id}"'); // if (pushToken.isRolledOut) { -// Logger.info('Ignoring rollout request: Token "${pushToken.id}" already rolled out.', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.info('Ignoring rollout request: Token "${pushToken.id}" already rolled out.'); // return true; // } // if (pushToken.rolloutState.rollOutInProgress) { @@ -603,7 +603,7 @@ // return false; // } // if (pushToken.expirationDate?.isBefore(DateTime.now()) == true) { -// Logger.info('Ignoring rollout request: Token "${pushToken.id}" is expired. ', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.info('Ignoring rollout request: Token "${pushToken.id}" is expired. '); // if (globalNavigatorKey.currentContext != null) { // ref.read(statusMessageProvider.notifier).state = ( @@ -616,13 +616,13 @@ // } // if (pushToken.privateTokenKey == null) { -// Logger.info('Updating rollout state of token "${pushToken.id}" to generatingRSAKeyPair', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.info('Updating rollout state of token "${pushToken.id}" to generatingRSAKeyPair'); // pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.generatingRSAKeyPair)); // if (pushToken == null) { -// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.warning('Tried to update a token that does not exist.'); // return false; // } -// Logger.info('Updated token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.info('Updated token "${pushToken.id}"'); // try { // final keyPair = await _rsaUtils.generateRSAKeyPair(); // pushToken = pushToken.withPrivateTokenKey(keyPair.privateKey); @@ -632,11 +632,11 @@ // return p0.withPublicTokenKey(keyPair.publicKey); // }) ?? // pushToken; -// Logger.info('Updated token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.info('Updated token "${pushToken.id}"'); // } catch (e, s) { // Logger.error('Error while generating RSA key pair.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); // if (pushToken == null) { -// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.warning('Tried to update a token that does not exist.'); // return false; // } // pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.generatingRSAKeyPairFailed)); @@ -646,22 +646,22 @@ // pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKey)); // if (pushToken == null) { -// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.warning('Tried to update a token that does not exist.'); // return false; // } // if (!kIsWeb && Platform.isIOS) { -// Logger.warning('Triggering network access permission for token "${pushToken.id}"', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.warning('Triggering network access permission for token "${pushToken.id}"'); // if (await _ioClient.triggerNetworkAccessPermission(url: pushToken.url!, sslVerify: pushToken.sslVerify) == false) { -// Logger.warning('Network access permission for token "${pushToken.id}" failed.', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.warning('Network access permission for token "${pushToken.id}" failed.'); // _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKeyFailed)); // return false; // } -// Logger.warning('Network access permission for token "${pushToken.id}" successful.', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.warning('Network access permission for token "${pushToken.id}" successful.'); // } // try { // // TODO What to do with poll only tokens if google-services is used? -// Logger.warning('SSLVerify: ${pushToken.sslVerify}', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.warning('SSLVerify: ${pushToken.sslVerify}'); // final fbToken = await _firebaseUtils.getFBToken(); // Response response = await _ioClient.doPost( // sslVerify: pushToken.sslVerify, @@ -677,14 +677,14 @@ // if (response.statusCode == 200) { // pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.parsingResponse, fbToken: fbToken)); // if (pushToken == null) { -// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.warning('Tried to update a token that does not exist.'); // return false; // } // try { // RSAPublicKey publicServerKey = await _parseRollOutResponse(response); // pushToken = await _updateToken(pushToken, (p0) => p0.withPublicServerKey(publicServerKey)); // if (pushToken == null) { -// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.warning('Tried to update a token that does not exist.'); // return false; // } // } on FormatException catch (e, s) { @@ -692,13 +692,13 @@ // Logger.warning('Error while parsing RSA public key.', name: 'token_notifier.dart#rolloutPushToken', error: e, stackTrace: s); // if (pushToken == null) { -// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.warning('Tried to update a token that does not exist.'); // return false; // } // pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.parsingResponseFailed)); // return false; // } -// Logger.info('Roll out successful', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.info('Roll out successful'); // pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(isRolledOut: true, rolloutState: PushTokenRollOutState.rolloutComplete)); // checkNotificationPermission(); @@ -728,12 +728,12 @@ // } // } catch (e, s) { // if (pushToken == null) { -// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.warning('Tried to update a token that does not exist.'); // return false; // } // pushToken = await _updateToken(pushToken, (p0) => p0.copyWith(rolloutState: PushTokenRollOutState.sendRSAPublicKeyFailed)); // if (pushToken == null) { -// Logger.warning('Tried to update a token that does not exist.', name: 'token_notifier.dart#rolloutPushToken'); +// Logger.warning('Tried to update a token that does not exist.'); // return false; // } // if (e is PlatformException && e.code == FIREBASE_TOKEN_ERROR_CODE || e is SocketException || e is TimeoutException || e is FirebaseException) { @@ -774,14 +774,14 @@ // /// as this can not be guaranteed to work. There is a manual option available // /// through the settings also. // Future<(List, List)?> updateFirebaseToken([String? firebaseToken]) async { -// Logger.info('Updating firebase token for all push tokens.', name: 'push_provider.dart#updateFirebaseToken'); +// Logger.info('Updating firebase token for all push tokens.'); // firebaseToken ??= await _firebaseUtils.getFBToken(); // if (firebaseToken == null) { -// Logger.warning('Could not update firebase token because no firebase token is available.', name: 'push_provider.dart#updateFirebaseToken'); +// Logger.warning('Could not update firebase token because no firebase token is available.'); // return null; // } // List tokenList = state.pushTokens.where((t) => t.isRolledOut && t.fbToken != firebaseToken).toList(); -// Logger.info('Updating firebase token for ${tokenList.length} push tokens.', name: 'push_provider.dart#updateFirebaseToken'); +// Logger.info('Updating firebase token for ${tokenList.length} push tokens.'); // bool allUpdated = true; // final List failedTokens = []; // final List unsuportedTokens = []; @@ -798,7 +798,7 @@ // //serial=element // //timestamp= // //signature=SIGNATURE(||) -// Logger.warning('Updating firebase token for push token "${p.serial}"', name: 'push_provider.dart#updateFirebaseToken'); +// Logger.warning('Updating firebase token for push token "${p.serial}"'); // String timestamp = DateTime.now().toUtc().toIso8601String(); // String message = '$firebaseToken|${p.serial}|$timestamp'; // String? signature = await _rsaUtils.trySignWithToken(p, message); @@ -813,10 +813,10 @@ // sslVerify: p.sslVerify, // ); // if (response.statusCode == 200) { -// Logger.info('Updating firebase token for push token succeeded!', name: 'push_provider.dart#updateFirebaseToken'); +// Logger.info('Updating firebase token for push token succeeded!'); // _updateToken(p, (p0) => p0.copyWith(fbToken: firebaseToken)); // } else { -// Logger.warning('Updating firebase token for push token failed!', name: 'push_provider.dart#updateFirebaseToken'); +// Logger.warning('Updating firebase token for push token failed!'); // failedTokens.add(p); // allUpdated = false; // } @@ -842,7 +842,7 @@ // uri = Uri.parse(qrCode); // } catch (_) { // showMessage(message: 'The scanned QR code is not a valid URI.', duration: const Duration(seconds: 3)); -// Logger.warning('Scanned Data: $qrCode', error: 'Scanned QR code is not a valid URI.', name: 'token_notifier.dart#handleQrCode'); +// Logger.warning('Scanned Data: $qrCode'); // return; // } // List tokens = await _tokensFromUri(uri); @@ -878,7 +878,7 @@ // final results = await TokenImportSchemeProcessor.processUriByAny(uri); // if (results == null || results.isEmpty) { // showMessage(message: 'The scanned QR code is not a valid URI.', duration: const Duration(seconds: 3)); -// Logger.warning('Scanned Data: $uri', error: 'Scanned QR code is not a valid URI.', name: 'token_notifier.dart#handleQrCode'); +// Logger.warning('Scanned Data: $uri'); // return []; // } // final failedResults = results.whereType().toList(); @@ -914,12 +914,12 @@ // ///////////////////////////////////////////////////////////////////////////// */ // Future _parseRollOutResponse(Response response) async { -// Logger.info('Parsing rollout response, try to extract public_key.', name: 'token_notifier.dart#_parseRollOutResponse'); +// Logger.info('Parsing rollout response, try to extract public_key.'); // try { // String key = json.decode(response.body)['detail']['public_key']; // key = key.replaceAll('\n', ''); -// Logger.info('Extracting public key was successful.', name: 'token_notifier.dart#_parseRollOutResponse'); +// Logger.info('Extracting public key was successful.'); // return _rsaUtils.deserializeRSAPublicKeyPKCS1(key); // } on FormatException catch (e) { @@ -928,7 +928,7 @@ // } // Future _handlePushTokensIfExist() async { -// Logger.info('Handling push tokens if they exist.', name: 'token_notifier.dart#_handlePushTokensIfExist'); +// Logger.info('Handling push tokens if they exist.'); // final pushTokens = state.pushTokens; // if (pushTokens.isEmpty || state.pushTokens.isEmpty) { // if ((await ref.read(settingsProvider.future)).hidePushTokens == true) { @@ -943,7 +943,7 @@ // checkNotificationPermission(); // } // for (final element in state.pushTokensToRollOut) { -// Logger.info('Handling push token "${element.id}"', name: 'token_notifier.dart#_handlePushTokensIfExist'); +// Logger.info('Handling push token "${element.id}"'); // await rolloutPushToken(element); // } // } diff --git a/lib/utils/rsa_utils.dart b/lib/utils/rsa_utils.dart index 56a5b8e98..f8c5b39eb 100644 --- a/lib/utils/rsa_utils.dart +++ b/lib/utils/rsa_utils.dart @@ -200,7 +200,7 @@ class RsaUtils { try { isVerified = signer.verifySignature(signedMessage, RSASignature(signature)); } on ArgumentError catch (e, s) { - Logger.warning('Verifying signature failed due to ${e.name}', name: 'crypto_utils.dart#verifyRSASignature', error: e, stackTrace: s); + Logger.warning('Verifying signature failed due to ${e.name}', error: e, stackTrace: s); } return isVerified; @@ -220,9 +220,9 @@ class RsaUtils { } Future> generateRSAKeyPair() async { - Logger.info('Start generating RSA key pair', name: 'crypto_utils.dart#generateRSAKeyPair'); + Logger.info('Start generating RSA key pair'); AsymmetricKeyPair keyPair = await compute(_generateRSAKeyPairIsolate, 4096); - Logger.info('Finished generating RSA key pair', name: 'crypto_utils.dart#generateRSAKeyPair'); + Logger.info('Finished generating RSA key pair'); return keyPair; } diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 41db9c799..412ab243d 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -216,7 +216,7 @@ Future scanQrCode(List resultHandlerList, Object? qrCode) a }; } catch (e) { showMessage(message: 'The scanned QR code is not a valid URI.', duration: const Duration(seconds: 3)); - Logger.warning('Scanned Data: $qrCode', error: 'Scanned QR code is not a valid URI: $e', name: 'utils.dart#scanQrCode'); + Logger.warning('Scanned Data: $qrCode'); return; } final processorResults = await SchemeProcessor.processUriByAny(uri); 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 b7d685100..38728fb44 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 @@ -78,8 +78,7 @@ class _LabeledDropdownButtonState extends State> { DropdownMenuItem( value: widget.values[i], child: Text( - '${widget.valueLabels != null && i < widget.valueLabels!.length ? widget.valueLabels![i] : widget.values[i].toString()}' - ' ${widget.postFix}', + '${widget.valueLabels != null && i < widget.valueLabels!.length ? widget.valueLabels![i] : widget.values[i].toString()}${widget.postFix.isNotEmpty ? ' ${widget.postFix}' : ''}', style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.fade, softWrap: false, 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 80f346bf9..f13feacbc 100644 --- a/lib/views/import_tokens_view/pages/import_start_page.dart +++ b/lib/views/import_tokens_view/pages/import_start_page.dart @@ -192,7 +192,7 @@ class _ImportStartPageState extends ConsumerState { final XTypeGroup typeGroup = XTypeGroup(label: localizations.selectFile); final XFile? file = await openFile(acceptedTypeGroups: [typeGroup]); if (file == null) { - Logger.warning("No file selected", name: "_pickAFile#ImportSelectFilePage"); + Logger.warning("No file selected"); return; } if (await fileProcessor.fileIsValid(file) == false) { @@ -232,7 +232,7 @@ class _ImportStartPageState extends ConsumerState { ); }).toList(); - Logger.info("Backup file imported successfully", name: "_pickBackupFile#ImportStartPage"); + Logger.info("Backup file imported successfully"); await _routeImportPlainTokensPage(importResults: importResults); } @@ -268,13 +268,13 @@ class _ImportStartPageState extends ConsumerState { resultHandlerType: const ObjectValidator(), ); }).toList(); - Logger.info("QR code scanned successfully", name: "_scanQrCode#ImportStartPage"); + Logger.info("QR code scanned successfully"); _routeImportPlainTokensPage(importResults: results); return; } Future _saveCopyOfXFile(XFile xFile) async { - Logger.warning("Saving copy of file", name: "_saveCopyOfXFile#ImportStartPage"); + Logger.warning("Saving copy of file"); final path = '${(await getApplicationCacheDirectory()).path}/copy_of_xFile'; await xFile.saveTo(path); _copyShouldExist = true; @@ -309,7 +309,7 @@ class _ImportStartPageState extends ConsumerState { } on NotFoundException catch (_) { if (!mounted) return; xFile = await QrNotFoundDialog(xFile: xFile).show(context); - Logger.warning('Got cropped file: $xFile', name: '_pickQrFile#ImportStartPage'); + Logger.warning('Got cropped file: $xFile'); if (xFile != null) processorResults = await schemeProcessor.processFile(xFile); if (processorResults == null) { if (!mounted) return; @@ -327,7 +327,7 @@ class _ImportStartPageState extends ConsumerState { return; } - Logger.info("QR file imported successfully", name: "_pickQrFile#ImportStartPage"); + Logger.info("QR file imported successfully"); _routeImportPlainTokensPage(importResults: processorResults); } @@ -366,7 +366,7 @@ class _ImportStartPageState extends ConsumerState { }).toList(); if (!mounted) return; setState(() => FocusScope.of(context).unfocus()); - Logger.info("Link imported successfully", name: "_validateLink#ImportStartPage"); + Logger.info("Link imported successfully"); _routeImportPlainTokensPage(importResults: results); } @@ -381,7 +381,7 @@ class _ImportStartPageState extends ConsumerState { ); }), ); - Logger.info('Imported tokens: ${tokensToImport?.length}', name: '_routeImportPlainTokensPage#ImportStartPage'); + Logger.info('Imported tokens: ${tokensToImport?.length}'); if (tokensToImport != null) { if (!mounted) return; Navigator.of(context).pop(tokensToImport); @@ -400,7 +400,7 @@ class _ImportStartPageState extends ConsumerState { ); }), ); - Logger.info('Imported encrypted tokens: ${tokensToImport?.length}', name: '_routeEncryptedData#ImportStartPage'); + Logger.info('Imported encrypted tokens: ${tokensToImport?.length}'); if (tokensToImport != null) { if (!mounted) return; Navigator.of(context).pop(tokensToImport); diff --git a/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart b/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart index b3890ccac..0b3ba27bb 100644 --- a/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart +++ b/lib/views/import_tokens_view/widgets/dialogs/qr_not_found_dialog.dart @@ -78,10 +78,10 @@ class QrNotFoundDialog extends StatelessWidget { } if (!context.mounted) return; if (croppedFile == null) { - Logger.warning("No croppedFile", name: "_pickAFile#ImportSelectFilePage"); + Logger.warning("No croppedFile"); return Navigator.of(context).pop(); } - Logger.warning("Cropped file: ${croppedFile.path}", name: "_pickAFile#ImportSelectFilePage"); + Logger.warning("Cropped file: ${croppedFile.path}"); return Navigator.of(context).pop(XFile(croppedFile.path)); }, child: Text(appLocalizations.markQrCode), diff --git a/lib/views/main_view/main_view.dart b/lib/views/main_view/main_view.dart index f7730a7c2..2f2ddcc85 100644 --- a/lib/views/main_view/main_view.dart +++ b/lib/views/main_view/main_view.dart @@ -72,7 +72,7 @@ class _MainViewState extends ConsumerState { void initState() { super.initState(); final latestStartedVersion = globalRef?.read(settingsProvider).whenOrNull(data: (data) => data)?.latestStartedVersion; - Logger.info('Latest started version: $latestStartedVersion', name: 'main_view.dart#initState'); + Logger.info('Latest started version: $latestStartedVersion'); if (latestStartedVersion == null || widget.disablePatchNotes) return; WidgetsBinding.instance.addPostFrameCallback((_) { PatchNotesUtils.showPatchNotesIfNeeded(context, latestStartedVersion); 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 9c434b401..e4dd239a9 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 @@ -101,13 +101,11 @@ class RenameTokenFolderAction extends StatelessWidget { if (success != null) { Logger.info( 'Renamed token:', - name: 'token_widget_base.dart#TextButton#renameClicked', error: '\'${folder.label}\' changed to \'$newLabel\'', ); } else { Logger.warning( 'Failed to rename token', - name: 'token_widget_base.dart#TextButton#renameClicked', error: '\'${folder.label}\' to \'$newLabel\'', ); } diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart index 37facab71..c8812332b 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_widget.dart @@ -52,11 +52,11 @@ class TokenFolderWidget extends ConsumerWidget { }, onDragStarted: () => draggingSortableNotifier.state = folder, onDragCompleted: () { - Logger.info('Draggable completed', name: 'TokenFolderWidget#build'); + Logger.info('Draggable completed'); // Will be handled by the sortableNotifier }, onDraggableCanceled: (velocity, offset) { - Logger.info('Draggable canceled', name: 'TokenFolderWidget#build'); + Logger.info('Draggable canceled'); draggingSortableNotifier.state = null; }, data: folder, diff --git a/lib/views/main_view/main_view_widgets/loading_indicator.dart b/lib/views/main_view/main_view_widgets/loading_indicator.dart index c0b7dce9b..68615f38a 100644 --- a/lib/views/main_view/main_view_widgets/loading_indicator.dart +++ b/lib/views/main_view/main_view_widgets/loading_indicator.dart @@ -32,10 +32,10 @@ class LoadingIndicator extends StatelessWidget { builder: (context) => const LoadingIndicator._(), ); Overlay.of(context).insert(_overlayEntry!); - Logger.info('Showing loading indicator', name: 'loading_indicator.dart#show'); + Logger.info('Showing loading indicator'); final T result = await future().then((value) { - Logger.info('Hiding loading indicator', name: 'loading_indicator.dart#show'); + Logger.info('Hiding loading indicator'); _overlayEntry?.remove(); _overlayEntry = null; return value; diff --git a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart index f050de719..a1e7bfba3 100644 --- a/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart +++ b/lib/views/main_view/main_view_widgets/main_view_tokens_list_filtered.dart @@ -72,7 +72,7 @@ class MainViewTokensListFiltered extends ConsumerWidget { List _buildFilteredFolder({required WidgetRef ref, required TokenFolder folder, required TokenFilter filter}) { if (filter.filterTokens(ref.watch(tokenProvider).tokensInFolder(folder)).isEmpty) return []; final expanded = filter.searchQuery.isNotEmpty && !folder.isLocked ? true : null; // Auto expand if search query is not empty and folder is not locked. - Logger.warning('Expanded: $expanded', name: 'main_view_tokens_list_filtered.dart#_buildFilteredFolder'); + Logger.warning('Expanded: $expanded'); final List widgets = []; widgets.add( TokenFolderExpandable(folder: folder, filter: filter, expandOverride: expanded, key: ValueKey('filteredFolder:${folder.folderId}')), 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 73e5b0f68..472eb2a19 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 @@ -84,13 +84,10 @@ class DefaultEditAction extends PiSlideableAction { if (newLabel.isEmpty) return; final edited = await globalRef?.read(tokenProvider.notifier).updateToken(token, (p0) => p0.copyWith(label: newLabel, tokenImage: newImageUrl)); if (edited == null) { - Logger.error('Token editing failed', name: 'DefaultEditAction#_showDialog'); + Logger.error('Token editing failed'); return; } - Logger.info( - 'Token edited: ${token.label} -> ${edited.label}, ${token.tokenImage} -> ${edited.tokenImage}', - name: 'DefaultEditAction#_showDialog', - ); + Logger.info('Token edited: ${token.label} -> ${edited.label}, ${token.tokenImage} -> ${edited.tokenImage}'); if (context.mounted) Navigator.of(context).pop(); }, ); diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart index 2befba810..4e4bb6c90 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart @@ -187,13 +187,10 @@ class _DefaultEditActionDialogState extends ConsumerState p0.copyWith(label: newLabel, tokenImage: newImageUrl)); if (edited == null) { - Logger.error('Token editing failed', name: 'DefaultEditAction#_showDialog'); + Logger.error('Token editing failed'); return; } - Logger.info( - 'Token edited: ${token.label} -> ${edited.label}, ${token.tokenImage} -> ${edited.tokenImage}', - name: 'DefaultEditAction#_showDialog', - ); + Logger.info('Token edited: ${token.label} -> ${edited.label}, ${token.tokenImage} -> ${edited.tokenImage}'); if (context.mounted) Navigator.of(context).pop(); }, child: Text( 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 1317f7a6e..0a879c18b 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 @@ -45,7 +45,7 @@ class DefaultLockAction extends PiSlideableAction { foregroundColor: Theme.of(context).extension()!.foregroundColor, onPressed: (context) async { if (await lockAuth(localizedReason: AppLocalizations.of(context)!.authenticateToUnLockToken) == false) return; - Logger.info('Changing lock status of token to isLocked = ${!token.isLocked}', name: 'token_widgets.dart#_changeLockStatus'); + Logger.info('Changing lock status of token to isLocked = ${!token.isLocked}'); globalRef?.read(tokenProvider.notifier).updateToken(token, (p0) => p0.copyWith(isLocked: !token.isLocked, isHidden: !token.isLocked)); }, diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart index 47acf5102..572fdf56b 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart @@ -72,11 +72,11 @@ class TokenWidgetBase extends ConsumerWidget { maxSimultaneousDrags: 1, onDragStarted: () => ref.read(draggingSortableProvider.notifier).state = token, onDragCompleted: () { - Logger.info('Draggable completed', name: 'TokenWidgetBase#build'); + Logger.info('Draggable completed'); // Will be handled by the sortableNotifier }, onDraggableCanceled: (velocity, offset) { - Logger.info('Draggable canceled', name: 'TokenWidgetBase#build'); + Logger.info('Draggable canceled'); globalRef?.read(draggingSortableProvider.notifier).state = null; }, dragAnchorStrategy: (Draggable d, BuildContext context, Offset point) { diff --git a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart index faa6fcfd4..2a3fa70c7 100644 --- a/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart +++ b/lib/views/settings_view/settings_view_widgets/update_firebase_token_dialog.dart @@ -64,7 +64,7 @@ class _UpdateFirebaseTokenDialogState extends ConsumerState { } }); - Logger.info('Starting app.', name: 'main.dart#initState'); + Logger.info('Starting app.'); Future.delayed(_splashScreenDelay, () { if (mounted) setState(() => _appIconIsVisible = true); @@ -72,11 +72,11 @@ class _SplashScreenState extends ConsumerState { }, ).catchError((error) async { if (error is Error) { - Logger.error('Error while loading the app.', error: error, stackTrace: error.stackTrace, name: 'main.dart#initState'); + Logger.error('Error while loading the app.'); return []; } if (error is Exception) { - Logger.error('Error while loading the app.', error: error, stackTrace: StackTrace.current, name: 'main.dart#initState'); + Logger.error('Error while loading the app.'); return []; } return []; @@ -89,13 +89,13 @@ class _SplashScreenState extends ConsumerState { @override void dispose() { - Logger.info('Disposing Splash Screen', name: 'main.dart#dispose'); + Logger.info('Disposing Splash Screen'); super.dispose(); } void _navigate() async { if (_customization.disabledFeatures.isNotEmpty) { - Logger.info('Disabled features: ${_customization.disabledFeatures}', name: 'main.dart#_pushReplace'); + Logger.info('Disabled features: ${_customization.disabledFeatures}'); } final ViewWidget nextView = MainView( appName: _customization.appName, diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index 51a8bb3fe..ca9c53bc5 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -44,31 +44,31 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { _listener = AppLifecycleListener( onResume: () async { await ref.read(tokenProvider.notifier).loadStateFromRepo(); - Logger.info('Refreshed tokens on resume', name: 'tokenProvider#appStateProvider'); + Logger.info('Refreshed tokens on resume'); final prProvider = ref.read(pushRequestProvider.notifier); await prProvider.loadStateFromRepo(); await prProvider.pollForChallenges(isManually: false); - Logger.info('Polled for challenges on resume', name: 'pushRequestProvider#appStateProvider'); + Logger.info('Polled for challenges on resume'); final hidden = await HomeWidgetUtils().hideAllOtps(); - if (hidden) Logger.info('Hid all HomeWidget OTPs on resume', name: 'tokenProvider#appStateProvider'); + if (hidden) Logger.info('Hid all HomeWidget OTPs on resume'); }, // onInactive: () => log('App inactive'), onHide: () async { if (await ref.read(tokenProvider.notifier).saveStateOnMinimizeApp() == false) { - Logger.error('Failed to save tokens on Hide', name: 'tokenProvider#appStateProvider'); + Logger.error('Failed to save tokens on Hide'); } if (await ref.read(tokenFolderProvider.notifier).collapseLockedFolders() == false) { - Logger.error('Failed to collapse locked folders on Hide', name: 'tokenFolderProvider#appStateProvider'); + Logger.error('Failed to collapse locked folders on Hide'); } await FlutterLocalNotificationsPlugin().cancelAll(); - Logger.info('Collapsed locked folders on Hide', name: 'tokenFolderProvider#appStateProvider'); + Logger.info('Collapsed locked folders on Hide'); }, // onShow: () => log('App shown'), // onPause: () => log('App paused'), // onRestart: () => log('App restarted'), // onDetach: () => log('App detached'), onExitRequested: () async { - Logger.info('Exit requested', name: 'onExitRequested#AppWrapper'); + Logger.info('Exit requested'); return AppExitResponse.exit; }, ); @@ -83,7 +83,7 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { @override Widget build(BuildContext context) { final container = ref.watch(tokenContainerProvider).value?.container ?? []; - Logger.debug('Credentials: $container', name: 'AppWrapper#build'); + Logger.debug('Credentials: $container'); return SingleTouchRecognizer( child: StateObserver( stateNotifierProviderListeners: const [], diff --git a/lib/widgets/dialog_widgets/push_request_dialog.dart b/lib/widgets/dialog_widgets/push_request_dialog.dart index c55c99482..a99b808c4 100644 --- a/lib/widgets/dialog_widgets/push_request_dialog.dart +++ b/lib/widgets/dialog_widgets/push_request_dialog.dart @@ -107,7 +107,7 @@ class _PushRequestDialogState extends ConsumerState { return; } ref.read(pushRequestProvider.notifier).accept(token, widget.pushRequest, selectedAnswer: selectedAnswer).then((success) { - Logger.info('accept push request success: $success', name: 'push_request_dialog.dart#AnswerSelectionWidget'); + Logger.info('accept push request success: $success'); if (!success && mounted) setState(() => isHandled = false); }); if (mounted) setState(() => isHandled = true); @@ -128,7 +128,7 @@ class _PushRequestDialogState extends ConsumerState { return; } ref.read(pushRequestProvider.notifier).accept(token, widget.pushRequest).then((success) { - Logger.info('accept push request success: $success', name: 'push_request_dialog.dart#_PushRequestDialogState'); + Logger.info('accept push request success: $success'); if (!success && mounted) setState(() => isHandled = false); }); if (mounted) setState(() => isHandled = true); @@ -237,7 +237,7 @@ class _PushRequestDialogState extends ConsumerState { } ref.read(pushRequestProvider.notifier).remove(widget.pushRequest).then((success) { - Logger.info('remove push request success: $success', name: 'push_request_dialog.dart#_showConfirmationDialog'); + Logger.info('remove push request success: $success'); if (!success && mounted) setState(() => isHandled = false); }); if (context.mounted) Navigator.of(context).pop(); @@ -281,7 +281,7 @@ class _PushRequestDialogState extends ConsumerState { } ref.read(pushRequestProvider.notifier).decline(pushToken, widget.pushRequest).then((success) { - Logger.info('decline push request success: $success', name: 'push_request_dialog.dart#_showConfirmationDialog'); + Logger.info('decline push request success: $success'); if (!success && mounted) setState(() => isHandled = false); }); if (context.mounted) Navigator.of(context).pop(); diff --git a/test/unit_test/state_notifiers/sortable_notifier_test.dart b/test/unit_test/state_notifiers/sortable_notifier_test.dart index b6059cf41..504517095 100644 --- a/test/unit_test/state_notifiers/sortable_notifier_test.dart +++ b/test/unit_test/state_notifiers/sortable_notifier_test.dart @@ -33,7 +33,7 @@ void _testSortableNotifier() { TokenFolder(label: 'Folder 2', folderId: 2, sortIndex: 2), ]); when(mockTokenFolderRepository.loadState()).thenAnswer((_) async { - Logger.debug('Loading token folder state', name: 'TokenFolderNotifier#_loadFromRepo'); + Logger.debug('Loading token folder state'); return tokenFolderState; }); when(mockTokenFolderRepository.saveState(any)).thenAnswer((newState) async { From dfed2e5afa3d67e55ff0a0908f925b7107e05adc Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 23 Sep 2024 12:33:48 +0200 Subject: [PATCH 048/285] container handleProcessorResult --- .../repo/token_container_repository.dart | 12 +-- .../secure_token_container_repository.dart | 22 +++--- lib/utils/logger.dart | 5 +- .../app_constraints_notifier.g.dart | 2 +- .../deeplink_notifier.g.dart | 2 +- .../introduction_provider.g.dart | 2 +- .../push_request_provider.g.dart | 2 +- .../settings_notifier.g.dart | 2 +- .../token_container_notifier.dart | 74 ++++++++++--------- .../token_container_notifier.g.dart | 2 +- .../token_folder_notifier.g.dart | 2 +- .../generated_providers/token_notifier.g.dart | 2 +- lib/views/container_view/container_view.dart | 4 +- 13 files changed, 70 insertions(+), 63 deletions(-) diff --git a/lib/interfaces/repo/token_container_repository.dart b/lib/interfaces/repo/token_container_repository.dart index 3f7c8fca1..eee4cc362 100644 --- a/lib/interfaces/repo/token_container_repository.dart +++ b/lib/interfaces/repo/token_container_repository.dart @@ -21,10 +21,10 @@ import '../../model/riverpod_states/token_container_state.dart'; import '../../model/token_container.dart'; abstract class TokenContainerRepository { - Future saveCredential(TokenContainer container); - Future saveCredentialsState(TokenContainerState containerState); - Future loadCredentialsState(); - Future loadCredential(String serial); - Future deleteAllCredentials(); - Future deleteCredential(String serial); + Future saveContainer(TokenContainer container); + Future saveContainerState(TokenContainerState containerState); + Future loadContainerState(); + Future loadContainer(String serial); + Future deleteAllContainer(); + Future deleteContainer(String serial); } diff --git a/lib/repo/secure_token_container_repository.dart b/lib/repo/secure_token_container_repository.dart index a464c82d9..795510cdc 100644 --- a/lib/repo/secure_token_container_repository.dart +++ b/lib/repo/secure_token_container_repository.dart @@ -22,51 +22,51 @@ class SecureTokenContainerRepository extends TokenContainerRepository { Future _delete(String key) => _protect(() => _storage.delete(key: key)); @override - Future loadCredentialsState() async { + Future loadContainerState() async { final containerJsonString = await _readAll(); Logger.warning('Loaded container: $containerJsonString'); return TokenContainerState.fromJsonStringList(containerJsonString.values.toList()); } @override - Future saveCredentialsState(TokenContainerState containerState) async { + Future saveContainerState(TokenContainerState containerState) async { Logger.warning('Saving container: $containerState'); final futures = []; for (var container in containerState.container) { - futures.add(saveCredential(container)); + futures.add(saveContainer(container)); } await Future.wait(futures); - return await loadCredentialsState(); + return await loadContainerState(); } @override - Future deleteCredential(String serial) async { + Future deleteContainer(String serial) async { await _delete(_keyOfSerial(serial)); - return await loadCredentialsState(); + return await loadContainerState(); } @override - Future deleteAllCredentials() async { + Future deleteAllContainer() async { final containerKeys = (await _readAll()).keys.where((key) => key.startsWith(containerCredentialsKey)); final futures = []; for (var key in containerKeys) { futures.add(_delete(key)); } await Future.wait(futures); - return await loadCredentialsState(); + return await loadContainerState(); } @override - Future loadCredential(String serial) async { + Future loadContainer(String serial) async { final containerJsonString = await _read(_keyOfSerial(serial)); if (containerJsonString == null) return null; return TokenContainer.fromJson(jsonDecode(containerJsonString)); } @override - Future saveCredential(TokenContainer container) async { + Future saveContainer(TokenContainer container) async { final containerJsonString = jsonEncode(container.toJson()); await _write(_keyOfSerial(container.serial), containerJsonString); - return await loadCredentialsState(); + return await loadContainerState(); } } diff --git a/lib/utils/logger.dart b/lib/utils/logger.dart index 52ff3e848..8279dfcf4 100644 --- a/lib/utils/logger.dart +++ b/lib/utils/logger.dart @@ -53,7 +53,6 @@ class Logger { }, colors: true, printEmojis: true, - dateTimeFormat: printer.DateTimeFormat.dateAndTime, ), ); @@ -425,10 +424,10 @@ Device Parameters $deviceInfo"""; return fileMessage; } - static String? _getCallerMethodName({int depth = 1}) => _getCurrentMethodName(deph: depth + 2); + static String? _getCallerMethodName({int depth = 1}) => _getCurrentMethodName(deph: depth + 1); static String? _getCurrentMethodName({int deph = 1}) { final frames = StackTrace.current.toString().split('\n'); - final frame = frames.elementAtOrNull(deph); + final frame = frames.elementAtOrNull(deph + 1); if (frame == null) return null; final entry = frame.split(' '); final methodName = entry.elementAtOrNull(entry.length - 2); diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.g.dart index c96e565a8..6f8f080c6 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/app_constraints_notifier.g.dart @@ -7,7 +7,7 @@ part of 'app_constraints_notifier.dart'; // ************************************************************************** String _$appConstraintsNotifierHash() => - r'6b6633ada94116eb933767ab1e29aeecf3e4397c'; + r'7ee476bc79277d5b032420984c5eec5d782210a8'; /// See also [AppConstraintsNotifier]. @ProviderFor(AppConstraintsNotifier) diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.g.dart index 0d611c110..2180d3adf 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/deeplink_notifier.g.dart @@ -6,7 +6,7 @@ part of 'deeplink_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$deeplinkNotifierHash() => r'be8127ccfb6d48a9cd5c00776c306db860fa49a1'; +String _$deeplinkNotifierHash() => r'4d2679c2d7edd3efe7b05aa4b64aa1be4b8cdbb4'; /// See also [DeeplinkNotifier]. @ProviderFor(DeeplinkNotifier) diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart index d2fc1de95..c12b98566 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.g.dart @@ -7,7 +7,7 @@ part of 'introduction_provider.dart'; // ************************************************************************** String _$introductionNotifierHash() => - r'ea8251fe045cfdcf4e0ed40ed3e5dae135bb58d5'; + r'7b60b259a94bafa9de1dff73189b79b685271cb8'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart index 88a1d1c56..d9fc9597c 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/push_request_provider.g.dart @@ -7,7 +7,7 @@ part of 'push_request_provider.dart'; // ************************************************************************** String _$pushRequestNotifierHash() => - r'145f1ff477137512bef2f71a19da7bc857823d73'; + r'1a2cb2d1f2e197a93cc7a836346c1a2ff749c676'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.g.dart index a8c826024..5b67e46e9 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.g.dart @@ -6,7 +6,7 @@ part of 'settings_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$settingsNotifierHash() => r'cbd75880d5ec70ee5910ac34e5c65eb51099b874'; +String _$settingsNotifierHash() => r'1e9ec972e6e3554daec3a5e72ae3dd9266ded066'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart index bdd010bb3..caedcaefe 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -94,7 +94,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler _eccUtils = _eccUtilsOverride ?? eccUtils; Logger.warning('Building containerProvider'); - final initState = await _repo.loadCredentialsState(); + final initState = await _repo.loadContainerState(); for (var container in initState.container.whereType()) { finalize(container); } @@ -107,28 +107,28 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler ////////////////////////////////////////////////////////////////// Future _saveCredentialToRepo(TokenContainer container) async { - return await _repoMutex.protect(() async => await _repo.saveCredential(container)); + return await _repoMutex.protect(() async => await _repo.saveContainer(container)); } Future _saveCredentialsStateToRepo(TokenContainerState containerState) async { - return await _repoMutex.protect(() async => await _repo.saveCredentialsState(containerState)); + return await _repoMutex.protect(() async => await _repo.saveContainerState(containerState)); } Future _deleteCredentialFromRepo(TokenContainer container) async { - return await _repoMutex.protect(() async => await _repo.deleteCredential(container.serial)); + return await _repoMutex.protect(() async => await _repo.deleteContainer(container.serial)); } Future _deleteCredentialsStateToRepo() async { - return await _repoMutex.protect(() async => await _repo.deleteAllCredentials()); + return await _repoMutex.protect(() async => await _repo.deleteAllContainer()); } /*////////////////////////////////////////////////////////////////// ////////////////////////// PUBLIC METHODS ////////////////////////// ///////////////////////////////////////////////////////////////// */ -// ADD CREDENTIALS +// ADD CONTAINER - Future addCredential(TokenContainer container) async { + Future addContainer(TokenContainer container) async { await _stateMutex.acquire(); final newState = await _saveCredentialToRepo(container); await update((_) => newState); @@ -136,7 +136,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler return newState; } - Future addCredentials(List container) async { + Future addContainerList(List container) async { await _stateMutex.acquire(); final newCredentials = container.toList(); final oldCredentials = (await future).container; @@ -161,7 +161,9 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler return newState; } - // UPDATE CREDENTIALS + /* ///////////////////////////////////////////////////////////////////////// + ////////////////////////// UPDATE CONTAINER //////////////////////////////// + ///////////////////////////////////////////////////////////////////////// */ @override Future update( @@ -172,7 +174,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler return super.update(cb, onError: onError); } - Future updateCredential(T container, T Function(T) updater) async { + Future updateContainer(T container, T Function(T) updater) async { await _stateMutex.acquire(); final oldState = await future; final currentCredential = oldState.currentOf(container); @@ -188,9 +190,11 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler return updated; } - // DELETE CREDENTIALS + /* ///////////////////////////////////////////////////////////////////////// + ///////////////////////// DELETE CONTAINER ///////////////////////////////// + ///////////////////////////////////////////////////////////////////////// */ - Future deleteCredential(TokenContainer container) async { + Future deleteContainer(TokenContainer container) async { await _stateMutex.acquire(); final newState = await _deleteCredentialFromRepo(container); await update((_) => newState); @@ -198,7 +202,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler return newState; } - Future deleteCredentials(List container) async { + Future deleteContainerList(List container) async { await _stateMutex.acquire(); final newCredentials = container.toList(); final oldCredentials = (await future).container; @@ -217,35 +221,39 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler return newState; } - // HANDLE PROCESSOR RESULTS + /* ///////////////////////////////////////////////////////////////////////// + /////////////////////// HANDLE PROCESSOR RESULTS /////////////////////////// + ///////////////////////////////////////////////////////////////////////// */ @override - Future handleProcessorResult(ProcessorResult result, Map args) { - // TODO: implement handleResult - throw UnimplementedError(); - } + Future handleProcessorResult(ProcessorResult result, Map args) async => + (await handleProcessorResults([result], args))?.isEmpty == true; @override Future handleProcessorResults(List results, Map args) async { Logger.info('Handling processor results'); final containerCredentials = results.getData().whereType().toList(); - if (containerCredentials.isEmpty) { - return null; - } + if (containerCredentials.isEmpty) return null; final currentState = await future; final stateCredentials = currentState.container; final stateCredentialsSerials = stateCredentials.map((e) => e.serial); final newCredentials = containerCredentials.where((element) => !stateCredentialsSerials.contains(element.serial)).toList(); Logger.info('Handling processor results: adding Credential'); - await addCredentials(newCredentials); + final stateAfterAdding = await addContainerList(newCredentials); + final failedToAdd = []; Logger.info('Handling processor results: adding done (${newCredentials.length})'); for (var container in newCredentials) { Logger.info('Handling processor results: finalize check ()'); + + if (!stateAfterAdding.container.contains(container)) { + failedToAdd.add(container); + continue; + } if (container is! TokenContainerUnfinalized) continue; Logger.info('Handling processor results: finalize'); await finalize(container); } - return null; + return failedToAdd; } final Mutex _finalizationMutex = Mutex(); @@ -274,12 +282,12 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler } final ECPublicKey publicServerKey; (container, publicServerKey) = await _parseResponse(container, response); - await updateCredential(container, (c) => c.finalize(publicServerKey: publicServerKey)!); + await updateContainer(container, (c) => c.finalize(publicServerKey: publicServerKey)!); } on StateError { Logger.info('Container was removed while finalizing'); } on LocalizedArgumentError catch (e) { ref.read(statusMessageProvider.notifier).state = ('Failed to decode response body', e.localizedMessage(AppLocalizations.of(await globalContext)!)); - await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseFailed)); + await updateContainer(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseFailed)); } catch (e) { Logger.error('Failed to finalize container ${container.serial}', error: e); _finalizationMutex.release(); @@ -298,10 +306,10 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler // generatingKeyPairFailed, // generatingKeyPairCompleted, TokenContainerUnfinalized? container = containerCredential; - container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.generatingKeyPair)); + container = await updateContainer(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.generatingKeyPair)); if (container == null) throw StateError('Credential was removed'); final keyPair = CryptoUtils.generateEcKeyPair(curve: container.ecKeyAlgorithm.curveName) as AsymmetricKeyPair; - container = await updateCredential(container, (c) => c.withClientKeyPair(keyPair) as TokenContainerUnfinalized); + container = await updateContainer(container, (c) => c.withClientKeyPair(keyPair) as TokenContainerUnfinalized); if (container == null) throw StateError('Credential was removed'); return container; } @@ -322,22 +330,22 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler TokenContainerUnfinalized? container = containerc; final Response response; - container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKey)); + container = await updateContainer(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKey)); if (container == null) throw StateError('Credential was removed'); try { response = (await _containerApi.finalizeContainer(container, eccUtils))!; } catch (e) { ref.read(statusMessageProvider.notifier).state = ('Failed to finalize container', e.toString()); - await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyFailed)); + await updateContainer(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyFailed)); rethrow; } if (response.statusCode != 200) { - container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyFailed)); + container = await updateContainer(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyFailed)); if (container == null) throw StateError('Credential was removed'); return (container, response); } - container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyCompleted)); + container = await updateContainer(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.sendingPublicKeyCompleted)); if (container == null) throw StateError('Credential was removed'); return (container, response); } @@ -352,7 +360,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler ECPublicKey publicServerKey; String responseBody = response.body; Map responseJson; - container = await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponse)); + container = await updateContainer(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponse)); if (container == null) throw StateError('Credential was removed'); responseJson = jsonDecode(responseBody); Logger.debug('Response JSON: $responseJson'); @@ -369,7 +377,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler name: 'container_sync_url', ); container = - await updateCredential(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseCompleted, syncUrl: syncUrlUri)); + await updateContainer(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.parsingResponseCompleted, syncUrl: syncUrlUri)); if (container == null) throw StateError('Credential was removed'); return (container, publicServerKey); } diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart index e43dbb05f..c985f1ab7 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart @@ -7,7 +7,7 @@ part of 'token_container_notifier.dart'; // ************************************************************************** String _$tokenContainerNotifierHash() => - r'd5840f35c80d04cb3c51aefb44e7a0c5d5f00416'; + r'9997634617c8165dd0df6aaba13e7fa1f80149ce'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.g.dart index 7b28951a5..2ff89af40 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.g.dart @@ -7,7 +7,7 @@ part of 'token_folder_notifier.dart'; // ************************************************************************** String _$tokenFolderNotifierHash() => - r'f1f961ff173b88705b6c2dfc893703a7644e6afc'; + r'1302c08e0a69caa5abda317a509b3c40f84b24e1'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart index 77422287b..b1fb8010f 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart @@ -6,7 +6,7 @@ part of 'token_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$tokenNotifierHash() => r'c82b8838167c91ce9ad8ef9638de121d225f4391'; +String _$tokenNotifierHash() => r'36184dfce444c961e046e32ee07c9951e0e1e25a'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/views/container_view/container_view.dart b/lib/views/container_view/container_view.dart index f7b7a7de1..b4781d5b8 100644 --- a/lib/views/container_view/container_view.dart +++ b/lib/views/container_view/container_view.dart @@ -102,7 +102,7 @@ class ContainerWidget extends ConsumerWidget { : IconButton( icon: const Icon(Icons.delete), onPressed: () { - ref.read(tokenContainerProvider.notifier).deleteCredential(containerCredential); + ref.read(tokenContainerProvider.notifier).deleteContainer(containerCredential); }, ), ), @@ -120,7 +120,7 @@ class DeleteContainerAction extends PiSlideableAction { @override CustomSlidableAction build(BuildContext context, WidgetRef ref) => CustomSlidableAction( - onPressed: (BuildContext context) => ref.read(tokenContainerProvider.notifier).deleteCredential(container), + onPressed: (BuildContext context) => ref.read(tokenContainerProvider.notifier).deleteContainer(container), backgroundColor: Theme.of(context).extension()!.deleteColor, child: Column( mainAxisAlignment: MainAxisAlignment.center, From 76b2f87a24a1e5fc505c647d53d04f4ab03fff86 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:51:59 +0200 Subject: [PATCH 049/285] refactoring --- integration_test/views_test.dart | 2 +- lib/l10n/app_cs.arb | 6 +- lib/l10n/app_de.arb | 6 +- lib/l10n/app_en.arb | 6 +- lib/l10n/app_es.arb | 6 +- lib/l10n/app_fr.arb | 6 +- lib/l10n/app_nl.arb | 6 +- lib/l10n/app_pl.arb | 6 +- ...google_authenticator_qrfile_processor.dart | 150 ------- lib/repo/secure_token_repository.dart | 6 +- .../customization/theme_customization.dart | 4 +- lib/utils/image_converter.dart | 388 ------------------ .../generated_providers/token_notifier.dart | 5 + .../generated_providers/token_notifier.g.dart | 2 +- lib/utils/token_import_origins.dart | 3 +- .../import_tokens_view.dart | 4 +- .../pages/import_start_page.dart | 223 ++++------ lib/views/main_view/main_view.dart | 7 +- .../custom_paint_navigation_bar.dart | 2 +- .../main_view_widgets/expandable_appbar.dart | 2 +- .../token_folder_expandable.dart | 210 +--------- .../token_folder_expandable_body.dart | 87 ++++ .../token_folder_expandable_header.dart | 178 ++++++++ .../token_folder_expandable_header_icon.dart | 82 ++++ .../main_view_widgets/loading_indicator.dart | 2 +- ...n.dart => main_view_background_image.dart} | 6 +- .../main_view_tokens_list.dart | 4 +- .../default_edit_action_dialog.dart | 2 +- .../qr_scanner_view/qr_scanner_view.dart | 91 ++-- .../settings_group_import_export_tokens.dart | 11 +- .../settings_groups/settings_group_theme.dart | 2 +- .../settings_view_widgets/settings_group.dart | 86 ++-- pubspec.lock | 50 +-- pubspec.yaml | 6 +- 34 files changed, 609 insertions(+), 1048 deletions(-) delete mode 100644 lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart delete mode 100644 lib/utils/image_converter.dart create mode 100644 lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_body.dart create mode 100644 lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header.dart create mode 100644 lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header_icon.dart rename lib/views/main_view/main_view_widgets/{token_widgets/main_view_background_icon.dart => main_view_background_image.dart} (97%) diff --git a/integration_test/views_test.dart b/integration_test/views_test.dart index 87c1e145c..ca17dc88d 100644 --- a/integration_test/views_test.dart +++ b/integration_test/views_test.dart @@ -163,7 +163,7 @@ Future _settingsViewTest(WidgetTester tester) async { await tester.tap(find.byIcon(Icons.settings)); await tester.pumpAndSettle(); expect(find.text(AppLocalizationsEn().settings), findsOneWidget); - expect(find.text(AppLocalizationsEn().themeMode), findsOneWidget); + expect(find.text(AppLocalizationsEn().theme), findsOneWidget); expect(find.text(AppLocalizationsEn().language), findsOneWidget); expect(find.text(AppLocalizationsEn().errorLogTitle), findsOneWidget); expect(find.byType(SettingsGroup), findsNWidgets(6)); diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index b374e65d0..acd7f5e4c 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -533,9 +533,6 @@ "@theme": { "description": "Title of the setting group where the theme can be selected." }, - "@themeMode": { - "description": "Title of the setting group where the theme mode can be changed." - }, "@timeOut": { "description": "Error message when a request times out." }, @@ -792,6 +789,7 @@ "noResultText2": " a začněte s používáním.", "noResultTitle": "Nejsou nainstalovány žádné tokeny.", "noTokenToExport": "Pro export není k dispozici žádný token", + "noTokenToImport": "Nebyl nalezen žádný token pro import", "notAnInteger": "Hodnota není celé číslo.", "notAnNumber": "Hodnota není číslo.", "ok": "Ok", @@ -892,7 +890,7 @@ "synchronizesTokensWithServer": "Synchronizovat tokeny se serverem privacyIDEA.", "synchronizingTokens": "Tokeny se synchronizují.", "theSecretDoesNotFitTheCurrentEncoding": "Tajný klíč neodpovídá zvolenému kódování.", - "themeMode": "Vzhled", + "theme": "Vzhled", "thisAppIsOpenSource": "Tato aplikace má otevřený zdrojový kód\nNavštivte nás na GitHub", "timeOut": "Časový limit", "tokenDataParseError": "Tokenová data nelze analyzovat", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index a3eb80ab0..c796c9829 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -533,9 +533,6 @@ "@theme": { "description": "Title of the setting group where the theme can be selected." }, - "@themeMode": { - "description": "Title of the setting group where the theme mode can be changed." - }, "@timeOut": { "description": "Error message when a request times out." }, @@ -792,6 +789,7 @@ "noResultText2": " Icon um loszulegen!", "noResultTitle": "Keine Token vorhanden.", "noTokenToExport": "Kein Token zum Exportieren verfügbar", + "noTokenToImport": "Kein Token zum Importieren gefunden", "notAnInteger": "Der Wert ist keine Ganzzahl.", "notAnNumber": "Der Wert ist keine Zahl.", "ok": "Ok", @@ -892,7 +890,7 @@ "synchronizesTokensWithServer": "Synchronisiert Token mit dem privacyIDEA Server.", "synchronizingTokens": "Synchronisiere Token.", "theSecretDoesNotFitTheCurrentEncoding": "Das Geheimnis entspricht nicht der gewählten Verschlüsselung.", - "themeMode": "Farbschema", + "theme": "Farbschema", "thisAppIsOpenSource": "Diese App ist Open Source\nBesuchen Sie uns auf GitHub", "timeOut": "Zeitlimit", "tokenDataParseError": "Token-Daten konnten nicht geparst werden", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index a384d7367..b2bcc2825 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -533,9 +533,6 @@ "@theme": { "description": "Title of the setting group where the theme can be selected." }, - "@themeMode": { - "description": "Title of the setting group where the theme mode can be changed." - }, "@timeOut": { "description": "Error message when a request times out." }, @@ -792,6 +789,7 @@ "noResultText2": " button to get started!", "noResultTitle": "No tokens stored yet.", "noTokenToExport": "No token available for export", + "noTokenToImport": "No token found to import", "notAnInteger": "The value is not an integer.", "notAnNumber": "The value is not a number.", "ok": "Ok", @@ -891,7 +889,7 @@ "synchronizesTokensWithServer": "Synchronizes tokens with the privacyIDEA server.", "synchronizingTokens": "Synchronizing tokens.", "theSecretDoesNotFitTheCurrentEncoding": "The secret does not fit the current encoding", - "themeMode": "Theme mode", + "theme": "Theme", "thisAppIsOpenSource": "This Application is Open Source\nVisit us on GitHub", "timeOut": "Time out", "tokenDataParseError": "Token data could not be parsed", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index d558b1ab3..317b82de9 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -533,9 +533,6 @@ "@theme": { "description": "Title of the setting group where the theme can be selected." }, - "@themeMode": { - "description": "Title of the setting group where the theme mode can be changed." - }, "@timeOut": { "description": "Error message when a request times out." }, @@ -792,6 +789,7 @@ "noResultText2": " para empezar.", "noResultTitle": "Aún no hay tokens almacenadas.", "noTokenToExport": "No hay token disponible para exportar", + "noTokenToImport": "No se ha encontrado ningún token para importar", "notAnInteger": "El valor no es un entero.", "notAnNumber": "El valor no es un número.", "ok": "Ok", @@ -892,7 +890,7 @@ "synchronizesTokensWithServer": "Sinchronizar tokens con el privacyIDEA servidor.", "synchronizingTokens": "Sincronización de los tokens.", "theSecretDoesNotFitTheCurrentEncoding": "El secreto no se ajusta a la codificación actual", - "themeMode": "Tema", + "theme": "Tema", "thisAppIsOpenSource": "Esta aplicación es de código abierto\nVisítanos en GitHub", "timeOut": "Tiempo de espera", "tokenDataParseError": "No se han podido analizar los datos del token.", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 8d2fea37d..f2e9619ef 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -533,9 +533,6 @@ "@theme": { "description": "Title of the setting group where the theme can be selected." }, - "@themeMode": { - "description": "Title of the setting group where the theme mode can be changed." - }, "@timeOut": { "description": "Error message when a request times out." }, @@ -792,6 +789,7 @@ "noResultText2": "bouton pour commencer!", "noResultTitle": "Aucun jeton n'est encore stocké.", "noTokenToExport": "Pas de jeton disponible pour l'exportation", + "noTokenToImport": "Aucun jeton n'a été trouvé pour l'importation", "notAnInteger": "La valeur n'est pas un nombre entier.", "notAnNumber": "La valeur n'est pas un nombre.", "ok": "Ok", @@ -892,7 +890,7 @@ "synchronizesTokensWithServer": "Synchroniser les jetons Push avec le serveur privacyIDEA.", "synchronizingTokens": "Synchroniser les jetons.", "theSecretDoesNotFitTheCurrentEncoding": "Le secret n'est pas compatible avec \nl'encodage actuel.", - "themeMode": "Thème", + "theme": "Thème", "thisAppIsOpenSource": "Cette application est open source\nRendez-nous visite sur GitHub", "timeOut": "Délai d'attente", "tokenDataParseError": "Les données du jeton n'ont pas pu être analysées", diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 4043b214e..c7a4f6782 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -533,9 +533,6 @@ "@theme": { "description": "Title of the setting group where the theme can be selected." }, - "@themeMode": { - "description": "Title of the setting group where the theme mode can be changed." - }, "@timeOut": { "description": "Error message when a request times out." }, @@ -792,6 +789,7 @@ "noResultText2": " de knop om te beginnen!", "noResultTitle": "Nog geen token opgeslagen.", "noTokenToExport": "Geen token beschikbaar voor export", + "noTokenToImport": "Geen token gevonden om te importeren", "notAnInteger": "De waarde is geen geheel getal.", "notAnNumber": "De waarde is geen getal.", "ok": "Ok", @@ -891,7 +889,7 @@ "synchronizesTokensWithServer": "Synchroniseert tokens met de privacyIDEA server.", "synchronizingTokens": "Tokens synchroniseren.", "theSecretDoesNotFitTheCurrentEncoding": "De geheime sleutel past niet bij de huidige codering", - "themeMode": "Thema", + "theme": "Thema", "thisAppIsOpenSource": "Deze app is open source\nBezoek ons op GitHub", "timeOut": "Time-out", "tokenDataParseError": "Token-gegevens konden niet worden verwerkt", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index e03918b4b..f5c97d83b 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -533,9 +533,6 @@ "@theme": { "description": "Title of the setting group where the theme can be selected." }, - "@themeMode": { - "description": "Title of the setting group where the theme mode can be changed." - }, "@timeOut": { "description": "Error message when a request times out." }, @@ -792,6 +789,7 @@ "noResultText2": " przycisku, żeby zacząć!", "noResultTitle": "Nie zainstalowano jeszcze żadnego tokenu.", "noTokenToExport": "Brak tokena dostępnego do eksportu", + "noTokenToImport": "Nie znaleziono tokenu do zaimportowania", "notAnInteger": "Wartość nie jest liczbą całkowitą.Wartość nie jest liczbą całkowitą.", "notAnNumber": "Wartość nie jest liczbą.", "ok": "Ok", @@ -891,7 +889,7 @@ "synchronizesTokensWithServer": "Synchronizuje tokeny push z serwerem privacyIDEA.", "synchronizingTokens": "Synchronizacja tokenów.", "theSecretDoesNotFitTheCurrentEncoding": "Sekret nie odpowiada wybranemu sposobowi kodowania.", - "themeMode": "Motyw", + "theme": "Motyw", "thisAppIsOpenSource": "Ta aplikacja jest open source\nOdwiedź nas na GitHub", "timeOut": "Limit czasu", "tokenDataParseError": "Nie można przeanalizować danych tokenu", diff --git a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart b/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart deleted file mode 100644 index f3312d12c..000000000 --- a/lib/processors/token_import_file_processor/google_authenticator_qrfile_processor.dart +++ /dev/null @@ -1,150 +0,0 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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 'dart:math'; - -import 'package:file_selector/file_selector.dart'; -import 'package:flutter/foundation.dart'; -import 'package:image/image.dart' as img_lib; -import 'package:zxing2/qrcode.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/logger.dart'; -import '../../utils/token_import_origins.dart'; -import '../scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart'; -import 'token_import_file_processor_interface.dart'; - -class GoogleAuthenticatorQrfileProcessor extends TokenImportFileProcessor { - static get resultHandlerType => TokenImportFileProcessor.resultHandlerType; - const GoogleAuthenticatorQrfileProcessor(); - @override - Future fileIsValid(XFile file) async { - try { - final img_lib.Image? qrImage = img_lib.decodeImage(await file.readAsBytes()); - if (qrImage == null) { - Logger.warning("Error decoding file to image.."); - return Future.value(false); - } - return Future.value(true); - } catch (e) { - return Future.value(false); - } - } - - @override - Future fileNeedsPassword(XFile file) => Future.value(false); - - @override - Future>> processFile(XFile file, {String? password}) async { - Result? qrResult; - img_lib.Image? qrImage = img_lib.decodeImage(await file.readAsBytes()); - if (qrImage == null) { - Logger.warning("Error decoding file to image.."); - throw Exception("Error decoding file to image.."); //TODO: Better error handling - } - int maxZoomLevel = 10; - if (!kIsWeb && Platform.isAndroid) { - final size = min(qrImage.width, qrImage.height); - Logger.info("Cropping image to square: from ${qrImage.width}x${qrImage.height} to ${size}x$size"); - qrImage = img_lib.copyCrop(qrImage, x: (qrImage.width - size) ~/ 2, y: (qrImage.height - size) ~/ 2, width: size, height: size); - Logger.info("Cropped image to square: ${qrImage.width}x${qrImage.height}"); - } - - // var progress = globalRef?.read(progressStateProvider.notifier).initProgress(maxZoomLevel * 360, 0).progress; - for (var zoomLevel = 0; zoomLevel <= maxZoomLevel && qrResult == null; zoomLevel++) { - for (var rotation = 0; rotation < 360; rotation += 90) { - // await Future.delayed(const Duration(milliseconds: 1)); - // globalRef?.read(progressStateProvider.notifier).setProgressValue(zoomLevel * 360 + rotation); - try { - qrResult = await compute(_decodeQrImageIsolate, [qrImage, rotation, zoomLevel]); - - break; - } on FormatReaderException catch (_) { - Logger.info("Qr-Code detected but not valid. Zoom level: $zoomLevel|rotation: $rotation"); - if (zoomLevel != maxZoomLevel) break; - } on NotFoundException catch (_) { - Logger.info("Qr-Code not detected. Zoom level: $zoomLevel|rotation: $rotation"); - } - // progress = globalRef?.read(progressStateProvider).progress; - } - } - if (qrResult == null) { - Logger.warning("Error decoding QR file.."); - throw NotFoundException(); - } - - final Uri uri; - try { - uri = Uri.parse(qrResult.text); - } on FormatException catch (_) { - Logger.warning("Error parsing QR file content.."); - throw FormatReaderException(); - } - var processorResults = await const GoogleAuthenticatorQrProcessor().processUri(uri); - if (processorResults.isEmpty) { - Logger.warning("Error processing QR file content.."); - throw FormatReaderException(); - } - processorResults = processorResults.map>((t) { - if (t is! ProcessorResultSuccess) return t; - return ProcessorResultSuccess( - TokenOriginSourceType.qrFile.addOriginToToken( - appName: TokenImportOrigins.googleAuthenticator.appName, - token: t.resultData, - isPrivacyIdeaToken: false, - data: t.resultData.origin?.data ?? qrResult!.text, - ), - resultHandlerType: resultHandlerType, - ); - }).toList(); - return processorResults; - } -} - -Future _decodeQrImageIsolate(List args) async { - var image = args[0] as img_lib.Image; - final int rotation = args[1] as int; - final int zoomLevel = args[2] as int; - if (zoomLevel > 0) { - final cropWidth = (image.width * 0.02).floor() * zoomLevel; - final cropHeight = (image.height * 0.02).floor() * zoomLevel; - image = img_lib.copyCrop(image, x: cropWidth, y: cropHeight, width: image.width - cropWidth, height: image.height - cropHeight); - } - if (rotation > 0) { - image = img_lib.copyRotate(image, angle: rotation); - } - - LuminanceSource source = RGBLuminanceSource( - image.width, - image.height, - image.convert(numChannels: 4).getBytes(order: img_lib.ChannelOrder.abgr).buffer.asInt32List(), - ); - - return QRCodeReader().decode( - BinaryBitmap(GlobalHistogramBinarizer(source)), - hints: DecodeHints() - ..put(DecodeHintType.tryHarder) - ..put(DecodeHintType.possibleFormats, [BarcodeFormat.qrCode]), - ); -} diff --git a/lib/repo/secure_token_repository.dart b/lib/repo/secure_token_repository.dart index 449b8234c..4415a69e2 100644 --- a/lib/repo/secure_token_repository.dart +++ b/lib/repo/secure_token_repository.dart @@ -135,6 +135,7 @@ class SecureTokenRepository implements TokenRepository { @override Future> saveOrReplaceTokens(List tokens) => _protect>(() => _saveOrReplaceTokens(tokens)); Future> _saveOrReplaceTokens(List tokens) async { + if (tokens.isEmpty) return []; final failedTokens = []; for (var element in tokens) { if (!await _saveOrReplaceToken(element)) { @@ -147,10 +148,7 @@ class SecureTokenRepository implements TokenRepository { stackTrace: StackTrace.current, ); } else { - Logger.info( - 'Saved ${tokens.length}/${tokens.length} tokens to secure storage', - stackTrace: StackTrace.current, - ); + Logger.info('Saved ${tokens.length}/${tokens.length} tokens to secure storage'); } return failedTokens; } diff --git a/lib/utils/customization/theme_customization.dart b/lib/utils/customization/theme_customization.dart index 731bdf2d2..610c35201 100644 --- a/lib/utils/customization/theme_customization.dart +++ b/lib/utils/customization/theme_customization.dart @@ -86,7 +86,7 @@ class ThemeCustomization { subtitleColor = subtitleColor ?? const Color(0xff9E9E9E), backgroundColor = backgroundColor ?? const Color(0xffEFEFEF), foregroundColor = foregroundColor ?? const Color(0xff282828), - shadowColor = shadowColor ?? const Color(0xff303030), + shadowColor = shadowColor ?? const Color(0x4C303030), deleteColor = deleteColor ?? const Color(0xffE04D2D), renameColor = renameColor ?? const Color(0xff6A8FE5), lockColor = lockColor ?? const Color(0xffFFD633), @@ -129,7 +129,7 @@ class ThemeCustomization { subtitleColor = subtitleColor ?? const Color(0xFF9E9E9E), backgroundColor = backgroundColor ?? const Color(0xFF303030), foregroundColor = foregroundColor ?? const Color(0xffF5F5F5), - shadowColor = shadowColor ?? const Color(0xFFEFEFEF), + shadowColor = shadowColor ?? const Color(0x4CEFEFEF), deleteColor = deleteColor ?? const Color(0xffCD3C14), renameColor = renameColor ?? const Color(0xff527EDB), lockColor = lockColor ?? const Color(0xffFFCC00), diff --git a/lib/utils/image_converter.dart b/lib/utils/image_converter.dart deleted file mode 100644 index 8716af93d..000000000 --- a/lib/utils/image_converter.dart +++ /dev/null @@ -1,388 +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; - -// import '../utils/logger.dart'; - -// 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? cropLeft, int? cropRight, int? cropTop, int? cropBottom}) { -// return switch (image.format.group) { -// ImageFormatGroup.yuv420 => ImageConverter._fromYUV420(image, rotation, isFrontCamera, cropLeft ?? 0, cropRight ?? 0, cropTop ?? 0, cropBottom ?? 0), -// ImageFormatGroup.bgra8888 => ImageConverter._fromBGRA8888(image, rotation, isFrontCamera, cropLeft ?? 0, cropRight ?? 0, cropTop ?? 0, cropBottom ?? 0), -// ImageFormatGroup.jpeg => ImageConverter._fromJPEG(image), -// ImageFormatGroup.nv21 => ImageConverter._fromNV21(image, -// rotation: rotation, mirror: isFrontCamera, cropLeft: cropLeft ?? 0, cropRight: cropRight ?? 0, cropTop: cropTop ?? 0, cropBottom: cropBottom ?? 0), -// ImageFormatGroup.unknown => throw ArgumentError('Unknown image format', 'image.format.group'), -// }; -// } - -// factory ImageConverter._fromNV21(CameraImage image, -// {int rotation = 0, bool mirror = false, int cropLeft = 0, int cropRight = 0, int cropTop = 0, int cropBottom = 0}) { -// Uint8List yuv420sp = image.planes[0].bytes; -// rotation = 360 - (rotation % 360); // if the rotation is 90, we need to rotate by 270 to get the correct rotation -// Logger.info( -// 'Converting NV21 image with rotation: $rotation, mirror: $mirror, cropLeft: $cropLeft, cropRight: $cropRight, cropTop: $cropTop, cropBottom: $cropBottom, width: ${image.width}, height: ${image.height}'); -// final height = image.height; -// final width = image.width; -// final int frameSize = width * height; - -// final int outputWidth; -// final int outputHeight; -// final int rotatedCropLeft; -// final int rotatedCropRight; -// final int rotatedCropTop; -// final int rotatedCropBottom; - -// 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 degrees and flip horizontally -// getNewX = (x, y) => height - y - 1; -// getNewY = (x, y) => width - x - 1; -// rotatedCropRight = cropBottom; -// rotatedCropBottom = cropRight; -// rotatedCropLeft = cropTop; -// rotatedCropTop = cropLeft; -// } else { -// // rotate by 90 degrees -// getNewX = (x, y) => y; -// getNewY = (x, y) => width - x - 1; -// rotatedCropRight = cropTop; -// rotatedCropBottom = cropRight; -// rotatedCropLeft = cropBottom; -// rotatedCropTop = cropLeft; -// } -// break; -// case 180: -// outputWidth = width; -// outputHeight = height; -// if (mirror) { -// // rotate by 180 degrees and flip horizontally -// getNewX = (x, y) => x; -// getNewY = (x, y) => height - y - 1; - -// rotatedCropBottom = cropTop; -// rotatedCropLeft = cropLeft; -// rotatedCropTop = cropBottom; -// rotatedCropRight = cropRight; -// } else { -// // rotate by 180 degrees -// getNewX = (x, y) => width - x - 1; -// getNewY = (x, y) => height - y - 1; -// rotatedCropBottom = cropTop; -// rotatedCropLeft = cropRight; -// rotatedCropTop = cropBottom; -// rotatedCropRight = cropLeft; -// } -// break; -// case 270: -// outputWidth = height; -// outputHeight = width; -// if (mirror) { -// // rotate by 270 degrees and flip horizontally -// getNewX = (x, y) => y; -// getNewY = (x, y) => height - x; - -// rotatedCropLeft = cropBottom; -// rotatedCropTop = cropRight; -// rotatedCropRight = cropTop; -// rotatedCropBottom = cropLeft; -// } else { -// // rotate by 270 degrees -// getNewX = (x, y) => height - y - 1; -// getNewY = (x, y) => x; -// rotatedCropLeft = cropTop; -// rotatedCropTop = cropRight; -// rotatedCropRight = cropBottom; -// rotatedCropBottom = cropLeft; -// } -// break; - -// default: -// outputWidth = width; -// outputHeight = height; -// if (mirror) { -// // do not rotate, flip horizontally -// getNewX = (x, y) => x; -// getNewY = (x, y) => height - y - 1; -// rotatedCropTop = cropTop; -// rotatedCropRight = cropLeft; -// rotatedCropBottom = cropBottom; -// rotatedCropLeft = cropRight; -// } else { -// // do not rotate -// getNewX = (x, y) => x; -// getNewY = (x, y) => y; -// rotatedCropTop = cropTop; -// rotatedCropRight = cropRight; -// rotatedCropBottom = cropBottom; -// rotatedCropLeft = cropLeft; -// } -// break; -// } - -// // imgLib -> Image package from https://pub.dartlang.org/packages/image -// var convertedImage = imglib.Image(width: outputWidth, height: outputHeight); // Create Image buffer - -// for (int yAxisPixel = cropTop, yp = cropLeft + cropTop * width; yAxisPixel < height - cropBottom; yAxisPixel++, yp += cropLeft + cropRight) { -// int uvp = frameSize + (yAxisPixel >> 1) * width + cropLeft, u = 0, v = 0; -// for (int xAxisPixel = cropLeft; xAxisPixel < width - cropRight; xAxisPixel++, yp++) { -// int y = (0xff & yuv420sp[yp]) - 16; -// if (y < 0) y = 0; -// if ((xAxisPixel & 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( -// getNewX(xAxisPixel, yAxisPixel), -// getNewY(xAxisPixel, yAxisPixel), -// ((r << 6) & 0xff0000) >> 16, -// ((g >> 2) & 0xff00) >> 8, -// (b >> 10) & 0xff, -// 0xff, -// ); -// } -// } - -// return ImageConverter( -// image: imglib.copyCrop( -// convertedImage, -// x: rotatedCropLeft, -// y: rotatedCropTop, -// width: convertedImage.width - rotatedCropLeft - rotatedCropRight, -// height: convertedImage.height - rotatedCropTop - rotatedCropBottom, -// ), -// ); -// } - -// factory ImageConverter._fromJPEG(CameraImage image) { -// Logger.info('Converting JPEG image to 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) { -// Logger.info('Converting BGRA8888 image to Image'); -// 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, -// order: imglib.ChannelOrder.bgra, -// ); -// if (rotation != 0) { -// 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 cropLeft = 0, -// int cropRight = 0, -// int cropTop = 0, -// int cropBottom = 0, -// ]) { -// Logger.info('Converting YUV420 image to Image'); -// 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 rotatedCropLeft; -// final int rotatedCropRight; -// final int rotatedCropTop; -// final int rotatedCropBottom; - -// 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; -// rotatedCropRight = cropBottom; -// rotatedCropBottom = cropRight; -// rotatedCropLeft = cropTop; -// rotatedCropTop = cropLeft; -// } else { -// getNewX = (x, y) => y; -// getNewY = (x, y) => width - x - 1; -// rotatedCropRight = cropTop; -// rotatedCropBottom = cropRight; -// rotatedCropLeft = cropBottom; -// rotatedCropTop = cropLeft; -// } -// 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; - -// rotatedCropBottom = cropTop; -// rotatedCropLeft = cropLeft; -// rotatedCropTop = cropBottom; -// rotatedCropRight = cropRight; -// } else { -// getNewX = (x, y) => width - x - 1; -// getNewY = (x, y) => height - y - 1; -// rotatedCropBottom = cropTop; -// rotatedCropLeft = cropRight; -// rotatedCropTop = cropBottom; -// rotatedCropRight = cropLeft; -// } -// break; -// case 270: -// outputWidth = height; -// outputHeight = width; -// if (mirror) { -// // rotate by 270 and flip horizontally -// getNewX = (x, y) => y; -// getNewY = (x, y) => height - x; - -// rotatedCropLeft = cropBottom; -// rotatedCropTop = cropRight; -// rotatedCropRight = cropTop; -// rotatedCropBottom = cropLeft; -// } else { -// getNewX = (x, y) => height - y - 1; -// getNewY = (x, y) => x; -// rotatedCropLeft = cropTop; -// rotatedCropTop = cropRight; -// rotatedCropRight = cropBottom; -// rotatedCropBottom = cropLeft; -// } -// break; - -// default: -// outputWidth = width; -// outputHeight = height; -// if (mirror) { -// // flip horizontally -// getNewX = (x, y) => x; -// getNewY = (x, y) => height - y - 1; -// rotatedCropTop = cropTop; -// rotatedCropRight = cropLeft; -// rotatedCropBottom = cropBottom; -// rotatedCropLeft = cropRight; -// } else { -// getNewX = (x, y) => x; -// getNewY = (x, y) => y; -// rotatedCropTop = cropTop; -// rotatedCropRight = cropRight; -// rotatedCropBottom = cropBottom; -// rotatedCropLeft = cropLeft; -// } -// 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 = cropTop; y < height - cropBottom; y++) { -// for (int x = cropLeft; x < width - cropRight; 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 cropedImg = imglib.copyCrop( -// img, -// x: rotatedCropLeft, -// y: rotatedCropTop, -// width: img.width - rotatedCropLeft - rotatedCropRight, -// height: img.height - rotatedCropTop - rotatedCropBottom, -// ); -// return ImageConverter(image: cropedImg); -// } - -// 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/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart index 2fc555822..701e616fe 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart @@ -143,6 +143,10 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { Future _addOrReplaceToken(Token token) async { await _repoMutex.acquire(); final success = await _repo.saveOrReplaceToken(token); + final currentId = state.currentOf(token)?.id; + if (currentId != null) { + token = token.copyWith(id: currentId); + } if (!success) { Logger.warning('Saving token failed. Token: ${token.id}'); _repoMutex.release(); @@ -155,6 +159,7 @@ class TokenNotifier extends _$TokenNotifier with ResultHandler { /// Adds a list of tokens and returns the tokens that could not be added or replaced. Future> _addOrReplaceTokens(List tokens) async { + if (tokens.isEmpty) return []; Logger.debug('Adding ${tokens.length} tokens.', verbose: true); await _repoMutex.acquire(); tokens = tokens.map((token) { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart index b1fb8010f..4c6db03e8 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart @@ -6,7 +6,7 @@ part of 'token_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$tokenNotifierHash() => r'36184dfce444c961e046e32ee07c9951e0e1e25a'; +String _$tokenNotifierHash() => r'572081984e3a8431279f47717fdf5e0f07aaa652'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/token_import_origins.dart b/lib/utils/token_import_origins.dart index d258112a2..b3f82399b 100644 --- a/lib/utils/token_import_origins.dart +++ b/lib/utils/token_import_origins.dart @@ -28,7 +28,6 @@ import '../processors/scheme_processors/token_import_scheme_processors/privacyid 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_import_file_processor.dart'; -import '../processors/token_import_file_processor/google_authenticator_qrfile_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'; @@ -71,7 +70,7 @@ class TokenImportOrigins { importHint: (localizations) => localizations.importHintGoogleQrScan, ), TokenImportSource( - processor: const GoogleAuthenticatorQrfileProcessor(), + processor: const GoogleAuthenticatorQrProcessor(), type: TokenImportType.qrFile, importHint: (localizations) => localizations.importHintGoogleQrFile, ), diff --git a/lib/views/import_tokens_view/import_tokens_view.dart b/lib/views/import_tokens_view/import_tokens_view.dart index fbd606e78..f3445118d 100644 --- a/lib/views/import_tokens_view/import_tokens_view.dart +++ b/lib/views/import_tokens_view/import_tokens_view.dart @@ -42,9 +42,7 @@ class ImportTokensView extends ConsumerStatefulView { static const double itemSpacingVertical = 10; static const double iconSize = 100; - final TokenImportOrigin? selectedOrigin; - - const ImportTokensView({this.selectedOrigin, super.key}); + const ImportTokensView({super.key}); @override ConsumerState createState() => _ImportTokensViewState(); 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 f13feacbc..23972133c 100644 --- a/lib/views/import_tokens_view/pages/import_start_page.dart +++ b/lib/views/import_tokens_view/pages/import_start_page.dart @@ -17,16 +17,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'dart:io'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:path_provider/path_provider.dart'; +import 'package:flutter_zxing/flutter_zxing.dart'; +import 'package:flutter_zxing/flutter_zxing.dart' as zxing; +import 'package:image_picker/image_picker.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; -import 'package:zxing2/qrcode.dart'; -import 'package:zxing2/zxing2.dart'; import '../../../l10n/app_localizations.dart'; import '../../../model/enums/token_import_type.dart'; @@ -42,7 +41,6 @@ import '../../../processors/token_import_file_processor/token_import_file_proces import '../../../utils/logger.dart'; import '../../qr_scanner_view/qr_scanner_view.dart'; import '../import_tokens_view.dart'; -import '../widgets/dialogs/qr_not_found_dialog.dart'; import 'import_encrypted_data_page.dart'; import 'import_plain_tokens_page.dart'; @@ -62,28 +60,21 @@ class ImportStartPage extends ConsumerStatefulWidget { class _ImportStartPageState extends ConsumerState { final _linkController = TextEditingController(); - bool _copyShouldExist = false; - String? _progessLabel; - // Widget? _buttonWhileProcessing; String? _errorText; - Future? future; + Future? future; @override void dispose() { _linkController.dispose(); - _deleteCopyOfXFile(); future?.ignore(); - // WidgetsBinding.instance.addPostFrameCallback((_) { - // globalRef?.read(progressStateProvider.notifier).deleteProgress(); - // }); + super.dispose(); } @override Widget build(BuildContext context) { final localizations = AppLocalizations.of(context)!; - // final currentLoadingProgress = ref.watch(progressStateProvider).progress; return Scaffold( appBar: AppBar( title: Text(widget.appName), @@ -134,49 +125,21 @@ class _ImportStartPageState extends ConsumerState { future = Future(() => switch (widget.selectedSource.type) { const (TokenImportType.backupFile) => _pickBackupFile(widget.selectedSource.processor), const (TokenImportType.qrScan) => _scanQrCode(widget.selectedSource.processor), - const (TokenImportType.qrFile) => _pickQrFile(widget.selectedSource.processor), + const (TokenImportType.qrFile) => _pickQrImage(widget.selectedSource.processor), const (TokenImportType.link) => _validateLink(widget.selectedSource.processor), }); - future?.then((value) { + future!.then((errorText) { if (!mounted) return; - setState(() => future = null); + setState(() { + future = null; + _errorText = errorText; + }); }); }); }, ), ) - : Column( - children: [ - Stack( - children: [ - // LinearProgressIndicator( - // minHeight: _progessLabel != null ? 16 : null, - // value: currentLoadingProgress, - // semanticsLabel: _progessLabel, - // ), - if (_progessLabel != null) - Positioned.fill( - child: Center( - child: FittedBox( - fit: BoxFit.fitHeight, - child: Text( - _progessLabel!, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - shadows: [ - Shadow( - color: Theme.of(context).colorScheme.onPrimary, - blurRadius: 5, - ), - ], - ), - ), - ), - ), - ), - ], - ), - ], - ), + : const CircularProgressIndicator(), ], ), ), @@ -185,7 +148,7 @@ class _ImportStartPageState extends ConsumerState { ); } - Future _pickBackupFile(TokenImportProcessor? processor) async { + Future _pickBackupFile(TokenImportProcessor? processor) async { assert(processor is TokenImportFileProcessor); final fileProcessor = processor as TokenImportFileProcessor; final localizations = AppLocalizations.of(context)!; @@ -193,23 +156,18 @@ class _ImportStartPageState extends ConsumerState { final XFile? file = await openFile(acceptedTypeGroups: [typeGroup]); if (file == null) { Logger.warning("No file selected"); - return; + return null; } if (await fileProcessor.fileIsValid(file) == false) { - if (mounted == false) return; - setState(() => _errorText = localizations.invalidBackupFile(widget.appName)); - return; + return localizations.invalidBackupFile(widget.appName); } setState(() => _errorText = null); if (await fileProcessor.fileNeedsPassword(file)) { - await _routeEncryptedData(data: file, processor: fileProcessor); - return; + return _routeEncryptedData(data: file, processor: fileProcessor); } var importResults = await fileProcessor.processFile(file); if (importResults.isEmpty) { - if (mounted == false) return; - setState(() => _errorText = localizations.invalidBackupFile(widget.appName)); - return; + return localizations.invalidBackupFile(widget.appName); } String fileString; try { @@ -233,28 +191,24 @@ class _ImportStartPageState extends ConsumerState { }).toList(); Logger.info("Backup file imported successfully"); - await _routeImportPlainTokensPage(importResults: importResults); + return _routeImportPlainTokensPage(importResults: importResults); } - Future _scanQrCode(TokenImportProcessor? processor) async { + Future _scanQrCode(TokenImportProcessor? processor) async { assert(processor is TokenImportSchemeProcessor); final localizations = AppLocalizations.of(context)!; final schemeProcessor = processor as TokenImportSchemeProcessor; final result = await Navigator.of(context).pushNamed(QRScannerView.routeName); - if (result is! String) return; + if (result is! String) return localizations.invalidQrScan(widget.appName); final Uri uri; try { uri = Uri.parse(result); } on FormatException catch (_) { - if (mounted == false) return; - setState(() => _errorText = localizations.invalidQrScan(widget.appName)); - return; + return localizations.invalidQrScan(widget.appName); } var results = await schemeProcessor.processUri(uri); if (results == null || results.isEmpty) { - if (mounted == false) return; - setState(() => _errorText = localizations.invalidQrScan(widget.appName)); - return; + return localizations.invalidQrScan(widget.appName); } results = results.map>((t) { if (t is! ProcessorResultSuccess) return t; @@ -269,88 +223,70 @@ class _ImportStartPageState extends ConsumerState { ); }).toList(); Logger.info("QR code scanned successfully"); - _routeImportPlainTokensPage(importResults: results); - return; + return _routeImportPlainTokensPage(importResults: results); } - Future _saveCopyOfXFile(XFile xFile) async { - Logger.warning("Saving copy of file"); - final path = '${(await getApplicationCacheDirectory()).path}/copy_of_xFile'; - await xFile.saveTo(path); - _copyShouldExist = true; - return XFile(path); - } - - Future _deleteCopyOfXFile() async { - if (_copyShouldExist == false) return; - File('${(await getApplicationCacheDirectory()).path}/copy_of_xFile').delete(); - _copyShouldExist = false; + Future _pickQrImage(TokenImportProcessor? processor) async { + final file = await ImagePicker().pickImage(source: ImageSource.gallery); + if (file == null) { + Logger.info("No file selected"); + return null; + } + return await _processQrImage(processor: processor, file: file); } - Future _pickQrFile(TokenImportProcessor? processor) async { - assert(processor is TokenImportFileProcessor); - final schemeProcessor = processor as TokenImportFileProcessor; + Future _processQrImage({ + required TokenImportProcessor? processor, + required XFile file, + bool tryHarder = false, + bool tryInverted = false, + }) async { + assert(processor is TokenImportSchemeProcessor); + final schemeProcessor = processor as TokenImportSchemeProcessor; final localizations = AppLocalizations.of(context)!; - List>? processorResults; - var xFile = await openFile(); - if (xFile == null) return; - xFile = await _saveCopyOfXFile(xFile); - if (!mounted) return; - setState(() => _progessLabel = localizations.findingQrCodeInImage); - if (await schemeProcessor.fileIsValid(xFile) == false) { - if (!mounted) return; - setState(() => _errorText = localizations.invalidQrFile(widget.appName)); - return; - } - try { - try { - processorResults = await schemeProcessor.processFile(xFile); - } on NotFoundException catch (_) { - if (!mounted) return; - xFile = await QrNotFoundDialog(xFile: xFile).show(context); - Logger.warning('Got cropped file: $xFile'); - if (xFile != null) processorResults = await schemeProcessor.processFile(xFile); - if (processorResults == null) { - if (!mounted) return; - setState(() => _errorText = localizations.qrNotFound); - return; - } + final DecodeParams params = DecodeParams( + imageFormat: zxing.ImageFormat.rgb, + format: Format.any, + tryHarder: tryHarder, + tryInverted: tryInverted, + isMultiScan: false, + ); + final text = (await zx.readBarcodeImagePath(file, params)).text; + if (text == null) { + if (!mounted) return null; + if (tryHarder == false) { + return _processQrImage(processor: processor, tryHarder: true, tryInverted: tryInverted, file: file); + } + if (tryInverted == false) { + return _processQrImage(processor: processor, tryHarder: tryHarder, tryInverted: true, file: file); } - } on FormatReaderException catch (_) { - if (!mounted) return; - setState(() => _errorText = localizations.invalidQrFile(widget.appName)); - return; - } on NotFoundException catch (_) { - if (!mounted) return; - setState(() => _errorText = localizations.qrNotFound); - return; + return localizations.invalidQrFile(widget.appName); } + final uri = Uri.tryParse(text); + if (uri == null) return localizations.invalidQrFile(widget.appName); + + final processorResults = await schemeProcessor.processUri(uri); + if (processorResults == null || processorResults.isEmpty) return _errorText; Logger.info("QR file imported successfully"); - _routeImportPlainTokensPage(importResults: processorResults); + return _routeImportPlainTokensPage(importResults: processorResults); } - Future _validateLink(TokenImportProcessor? processor) async { - if (_linkController.text.isEmpty) { - return; - } - assert(processor is TokenImportSchemeProcessor); + Future _validateLink(TokenImportProcessor? processor) async { final localizations = AppLocalizations.of(context)!; + if (_linkController.text.isEmpty) return localizations.mustNotBeEmpty(localizations.tokenLink); + assert(processor is TokenImportSchemeProcessor); final schemeProcessor = processor as TokenImportSchemeProcessor; final Uri uri; try { uri = Uri.parse(_linkController.text); } on FormatException catch (_) { - if (!mounted) return; - setState(() => _errorText = localizations.invalidLink(widget.appName)); - return; + return localizations.invalidLink(widget.appName); } var results = await schemeProcessor.processUri(uri); if (results == null || results.isEmpty) { - if (!mounted) return; - setState(() => _errorText = localizations.invalidLink(widget.appName)); - return; + return localizations.invalidLink(widget.appName); } results = results.map>((t) { if (t is! ProcessorResultSuccess) return t; @@ -364,14 +300,14 @@ class _ImportStartPageState extends ConsumerState { resultHandlerType: const ObjectValidator(), ); }).toList(); - if (!mounted) return; + if (!mounted) return null; setState(() => FocusScope.of(context).unfocus()); Logger.info("Link imported successfully"); - _routeImportPlainTokensPage(importResults: results); + return _routeImportPlainTokensPage(importResults: results); } - Future _routeImportPlainTokensPage({required List> importResults}) async { - if (mounted == false) return; + Future _routeImportPlainTokensPage({required List> importResults}) async { + if (mounted == false) return null; final tokensToImport = await Navigator.of(context).push>( MaterialPageRoute(builder: (context) { return ImportPlainTokensPage( @@ -382,14 +318,15 @@ class _ImportStartPageState extends ConsumerState { }), ); Logger.info('Imported tokens: ${tokensToImport?.length}'); - if (tokensToImport != null) { - if (!mounted) return; - Navigator.of(context).pop(tokensToImport); - } + + if (!mounted) return null; + if (tokensToImport == null) return null; + Navigator.of(context).pop(tokensToImport); + return null; } - Future _routeEncryptedData({required T data, required TokenImportProcessor processor}) async { - if (mounted == false) return; + Future _routeEncryptedData({required T data, required TokenImportProcessor processor}) async { + if (mounted == false) return null; final tokensToImport = await Navigator.of(context).push( MaterialPageRoute(builder: (context) { return ImportEncryptedDataPage( @@ -401,9 +338,9 @@ class _ImportStartPageState extends ConsumerState { }), ); Logger.info('Imported encrypted tokens: ${tokensToImport?.length}'); - if (tokensToImport != null) { - if (!mounted) return; - Navigator.of(context).pop(tokensToImport); - } + if (!mounted) return null; + if (tokensToImport == null) return null; + Navigator.of(context).pop(tokensToImport); + return null; } } diff --git a/lib/views/main_view/main_view.dart b/lib/views/main_view/main_view.dart index 2f2ddcc85..febfd8446 100644 --- a/lib/views/main_view/main_view.dart +++ b/lib/views/main_view/main_view.dart @@ -36,7 +36,7 @@ import 'main_view_widgets/expandable_appbar.dart'; import 'main_view_widgets/main_view_navigation_bar.dart'; import 'main_view_widgets/main_view_tokens_list.dart'; import 'main_view_widgets/main_view_tokens_list_filtered.dart'; -import 'main_view_widgets/token_widgets/main_view_background_icon.dart'; +import 'main_view_widgets/main_view_background_image.dart'; export '../../views/main_view/main_view.dart'; @@ -82,6 +82,7 @@ class _MainViewState extends ConsumerState { @override Widget build(BuildContext context) { final hasFilter = ref.watch(tokenFilterProvider) != null; + return PushRequestListener( child: Scaffold( resizeToAvoidBottomInset: false, @@ -95,7 +96,7 @@ class _MainViewState extends ConsumerState { // maxLines: 2 only works like this. maxLines: 2, // Title can be shown on small screens too. ), - leading: const SizedBox(), + leading: widget.appIcon, actions: [ hasFilter ? AppBarItem( @@ -120,7 +121,7 @@ class _MainViewState extends ConsumerState { child: !hasFilter ? Stack( children: [ - MainViewBackgroundIcon(appImage: widget.appImage), + MainViewBackgroundImage(appImage: widget.appImage), MainViewTokensList(nestedScrollViewKey: globalKey), MainViewNavigationBar(appConstraints: widget.appConstraints), ], diff --git a/lib/views/main_view/main_view_widgets/custom_paint_navigation_bar.dart b/lib/views/main_view/main_view_widgets/custom_paint_navigation_bar.dart index 570fce195..e941da121 100644 --- a/lib/views/main_view/main_view_widgets/custom_paint_navigation_bar.dart +++ b/lib/views/main_view/main_view_widgets/custom_paint_navigation_bar.dart @@ -62,7 +62,7 @@ class CustomPaintNavigationBar extends CustomPainter { ..close(); // point 1 canvas.translate(0, -elevation); - canvas.drawShadow(path, shadowColor, elevation, false); + canvas.drawShadow(path, shadowColor, elevation, true); canvas.translate(0, elevation); canvas.drawPath(path, paint); } diff --git a/lib/views/main_view/main_view_widgets/expandable_appbar.dart b/lib/views/main_view/main_view_widgets/expandable_appbar.dart index c2379d53e..4eded77bb 100644 --- a/lib/views/main_view/main_view_widgets/expandable_appbar.dart +++ b/lib/views/main_view/main_view_widgets/expandable_appbar.dart @@ -110,7 +110,7 @@ class _ExpandableAppBarState extends State { color: Theme.of(context).canvasColor, boxShadow: [ BoxShadow( - color: Theme.of(context).shadowColor.withOpacity(0.3), + color: Theme.of(context).shadowColor, blurRadius: 2, offset: const Offset(0, 2), ), 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 6524eb07c..1a1dd2ae7 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 @@ -17,34 +17,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'dart:async'; import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import '../../../../l10n/app_localizations.dart'; import '../../../../model/riverpod_states/settings_state.dart'; import '../../../../model/riverpod_states/token_filter.dart'; import '../../../../model/token_folder.dart'; import '../../../../model/tokens/push_token.dart'; -import '../../../../model/tokens/token.dart'; -import '../../../../utils/customization/theme_extentions/action_theme.dart'; import '../../../../utils/globals.dart'; -import '../../../../utils/lock_auth.dart'; import '../../../../utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart'; import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; -import '../../../../utils/utils.dart'; -import '../../../../widgets/custom_trailing.dart'; -import '../drag_target_divider.dart'; -import '../token_widgets/token_widget_builder.dart'; -import 'token_folder_actions.dart/delete_token_folder_action.dart'; -import 'token_folder_actions.dart/lock_token_folder_action.dart'; -import 'token_folder_actions.dart/rename_token_folder_action.dart'; +import 'token_folder_expandable_widgets/token_folder_expandable_header.dart'; +import 'token_folder_expandable_widgets/token_folder_expandable_body.dart'; class TokenFolderExpandable extends ConsumerStatefulWidget { final TokenFolder folder; @@ -58,7 +46,6 @@ class TokenFolderExpandable extends ConsumerStatefulWidget { } class _TokenFolderExpandableState extends ConsumerState with SingleTickerProviderStateMixin { - Timer? _expandTimer; late final AnimationController animationController; late final ExpandableController expandableController; @@ -88,7 +75,6 @@ class _TokenFolderExpandableState extends ConsumerState w @override void dispose() { - _expandTimer?.cancel(); animationController.dispose(); expandableController.dispose(); super.dispose(); @@ -111,186 +97,28 @@ class _TokenFolderExpandableState extends ConsumerState w expandableController.value = widget.expandOverride!; } return ExpandablePanel( - theme: const ExpandableThemeData(hasIcon: false, tapBodyToCollapse: false, tapBodyToExpand: false), + theme: const ExpandableThemeData( + useInkWell: false, + hasIcon: false, + tapBodyToCollapse: false, + tapBodyToExpand: false, + ), controller: expandableController, - header: GestureDetector( - onTap: () async { - if (widget.expandOverride != null) return; - if (expandableController.value) { - expandableController.value = false; - return; - } - if (tokens.isEmpty || (tokens.length == 1 && tokens.first == draggingSortable)) return; - if (widget.folder.isLocked && await lockAuth(localizedReason: AppLocalizations.of(context)!.expandLockedFolder) == false) { - return; - } - if (!mounted) return; - expandableController.value = true; - }, - child: Slidable( - key: ValueKey('tokenFolder-${widget.folder.folderId}'), - groupTag: 'myTag', - endActionPane: ActionPane( - motion: const DrawerMotion(), - extentRatio: 1, - children: [ - DeleteTokenFolderAction(folder: widget.folder), - RenameTokenFolderAction(folder: widget.folder), - LockTokenFolderAction(folder: widget.folder), - ], - ), - child: Padding( - padding: const EdgeInsets.only(left: 15, right: 0), - child: DragTarget( - onWillAcceptWithDetails: (details) { - if (details.data.folderId != widget.folder.folderId) { - if (widget.folder.isLocked || tokens.isEmpty) return true; - if (expandableController.value) return true; - _expandTimer?.cancel(); - _expandTimer = Timer(const Duration(milliseconds: 500), () { - if (!mounted) return; - expandableController.value = true; - }); - return true; - } - return false; - }, - onLeave: (data) => _expandTimer?.cancel(), - onAcceptWithDetails: (details) => dragSortableOnAccept( - previousSortable: widget.folder, - dragedSortable: details.data, - nextSortable: null, - dependingFolder: widget.folder, - ref: ref, - ), - builder: (context, willAccept, willReject) => Center( - child: Container( - margin: widget.folder.isExpanded ? null : const EdgeInsets.only(right: 8), - padding: widget.folder.isExpanded ? const EdgeInsets.only(right: 8) : null, - height: 50, - decoration: BoxDecoration( - color: willAccept.isNotEmpty ? Theme.of(context).dividerColor : null, - borderRadius: BorderRadius.only( - topRight: widget.folder.isExpanded ? const Radius.circular(0) : const Radius.circular(8), - topLeft: const Radius.circular(8), - bottomRight: widget.folder.isExpanded ? const Radius.circular(0) : const Radius.circular(8), - bottomLeft: widget.folder.isExpanded ? const Radius.circular(0) : const Radius.circular(8), - ), - ), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - RotationTransition( - turns: Tween(begin: 0.25, end: 0.0).animate(animationController), - child: SizedBox.square( - dimension: 25, - child: - (tokens.isEmpty || (tokens.length == 1 && tokens.first == draggingSortable)) ? null : const Icon(Icons.arrow_forward_ios_sharp), - )), - const SizedBox(width: 8), - Expanded( - flex: 2, - child: Text( - widget.folder.label, - style: Theme.of(context).textTheme.titleLarge, - overflow: TextOverflow.fade, - softWrap: false, - ), - ), - CustomTrailing( - child: Center( - child: (tokens.isEmpty || (tokens.length == 1 && tokens.first == draggingSortable)) - ? Icon( - Icons.folder_open, - color: Theme.of(context).listTileTheme.iconColor, - ) - : Stack( - alignment: Alignment.center, - children: [ - Icon( - Icons.folder, - color: Theme.of(context).listTileTheme.iconColor, - ), - if (widget.folder.isLocked) - Positioned.fill( - child: LayoutBuilder( - builder: (context, constraints) { - return Container( - padding: EdgeInsets.only(left: widget.folder.isExpanded ? 2 : 0, top: 1), - child: Icon( - widget.folder.isExpanded ? MdiIcons.lockOpenVariant : MdiIcons.lock, - color: Theme.of(context).extension()?.lockColor, - size: constraints.maxHeight / 2.1, - shadows: [ - Shadow( - color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.3), - offset: const Offset(0.5, 0.5), - blurRadius: 2, - ) - ], - ), - ); - }, - ), - ), - ], - ), - ), - ), - ], - ), - ), - ), - ), - ), - ), + header: TokenFolderExpandableHeader( + tokens: tokens, + expandableController: expandableController, + animationController: animationController, + expandOverride: widget.expandOverride, + folder: widget.folder, ), collapsed: const SizedBox(), expanded: tokens.isEmpty || (tokens.length == 1 && tokens.first == draggingSortable) ? const SizedBox() - : Container( - margin: const EdgeInsets.only(left: 15, bottom: 10), - decoration: BoxDecoration( - color: Theme.of(context).focusColor, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(4), - ), - ), - child: Card( - color: Theme.of(context).scaffoldBackgroundColor, - //Only bottom left corner round the other corners sharp - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(4), - ), - ), - margin: const EdgeInsets.only( - left: 4, - bottom: 2, - ), - semanticContainer: false, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - for (var i = 0; i < tokens.length; i++) ...[ - if (draggingSortable != tokens[i] && (i != 0 || draggingSortable is Token)) - widget.filter == null - ? DragTargetDivider( - dependingFolder: widget.folder, - previousSortable: (i - 1) < 0 ? null : tokens[i - 1], - nextSortable: tokens[i], - ) - : const Divider(), - TokenWidgetBuilder.fromToken(tokens[i]), - ], - if (tokens.isNotEmpty && draggingSortable is Token) - widget.filter == null - ? DragTargetDivider(dependingFolder: widget.folder, previousSortable: tokens.last, nextSortable: null) - : const Divider(), - if (tokens.isNotEmpty && draggingSortable is! Token) const SizedBox(height: 8), - ], - ), - ), + : TokenFolderExpandableBody( + tokens: tokens, + draggingSortable: draggingSortable, + folder: widget.folder, + filter: widget.filter, ), ); } diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_body.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_body.dart new file mode 100644 index 000000000..0ecdfde6e --- /dev/null +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_body.dart @@ -0,0 +1,87 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; + +import '../../../../../model/mixins/sortable_mixin.dart'; +import '../../../../../model/riverpod_states/token_filter.dart'; +import '../../../../../model/token_folder.dart'; +import '../../../../../model/tokens/token.dart'; +import '../../drag_target_divider.dart'; +import '../../token_widgets/token_widget_builder.dart'; + +class TokenFolderExpandableBody extends StatelessWidget { + final List tokens; + final SortableMixin? draggingSortable; + final TokenFolder folder; + final TokenFilter? filter; + + const TokenFolderExpandableBody({ + super.key, + required this.tokens, + required this.draggingSortable, + required this.folder, + required this.filter, + }); + + @override + Widget build(BuildContext context) => Container( + margin: const EdgeInsets.only(left: 15, bottom: 10), + decoration: BoxDecoration( + color: Theme.of(context).focusColor, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(4), + ), + ), + child: Card( + color: Theme.of(context).scaffoldBackgroundColor, + //Only bottom left corner round the other corners sharp + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(4), + ), + ), + margin: const EdgeInsets.only( + left: 4, + bottom: 2, + ), + semanticContainer: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + for (var i = 0; i < tokens.length; i++) ...[ + if (draggingSortable != tokens[i] && (i != 0 || draggingSortable is Token)) + filter == null + ? DragTargetDivider( + dependingFolder: folder, + previousSortable: (i - 1) < 0 ? null : tokens[i - 1], + nextSortable: tokens[i], + ) + : const Divider(), + TokenWidgetBuilder.fromToken(tokens[i]), + ], + if (tokens.isNotEmpty && draggingSortable is Token) + filter == null ? DragTargetDivider(dependingFolder: folder, previousSortable: tokens.last, nextSortable: null) : const Divider(), + if (tokens.isNotEmpty && draggingSortable is! Token) const SizedBox(height: 8), + ], + ), + ), + ); +} diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header.dart new file mode 100644 index 000000000..89b428822 --- /dev/null +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header.dart @@ -0,0 +1,178 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 'package:expandable/expandable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; + +import '../../../../../l10n/app_localizations.dart'; +import '../../../../../model/token_folder.dart'; +import '../../../../../model/tokens/token.dart'; +import '../../../../../utils/lock_auth.dart'; +import '../../../../../utils/riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; +import '../../../../../utils/utils.dart'; +import '../token_folder_actions.dart/delete_token_folder_action.dart'; +import '../token_folder_actions.dart/lock_token_folder_action.dart'; +import '../token_folder_actions.dart/rename_token_folder_action.dart'; +import 'token_folder_expandable_header_icon.dart'; + +class TokenFolderExpandableHeader extends ConsumerStatefulWidget { + final TokenFolder folder; + final List tokens; + final ExpandableController expandableController; + final bool? expandOverride; + final AnimationController animationController; + + const TokenFolderExpandableHeader({ + required this.tokens, + required this.expandableController, + required this.expandOverride, + required this.animationController, + required this.folder, + super.key, + }); + + @override + ConsumerState createState() => _TokenFolderExpandableHeaderState(); +} + +class _TokenFolderExpandableHeaderState extends ConsumerState { + Timer? _expandTimer; + + @override + void dispose() { + _expandTimer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final isExpanded = widget.expandableController.value; + final draggingSortable = ref.watch(draggingSortableProvider); + return Slidable( + key: ValueKey('tokenFolder-${widget.folder.folderId}'), + groupTag: 'myTag', + endActionPane: ActionPane( + motion: const DrawerMotion(), + extentRatio: 1, + children: [ + DeleteTokenFolderAction(folder: widget.folder), + RenameTokenFolderAction(folder: widget.folder), + LockTokenFolderAction(folder: widget.folder), + ], + ), + child: Padding( + padding: const EdgeInsets.only(left: 15, right: 0), + child: DragTarget( + onWillAcceptWithDetails: (details) { + if (details.data.folderId != widget.folder.folderId) { + if (widget.folder.isLocked || widget.tokens.isEmpty) return true; + if (isExpanded) return true; + _expandTimer?.cancel(); + _expandTimer = Timer(const Duration(milliseconds: 500), () { + if (!mounted) return; + widget.expandableController.value = true; + }); + return true; + } + return false; + }, + onLeave: (data) => _expandTimer?.cancel(), + onAcceptWithDetails: (details) => dragSortableOnAccept( + previousSortable: widget.folder, + dragedSortable: details.data, + nextSortable: null, + dependingFolder: widget.folder, + ref: ref, + ), + builder: (context, willAccept, willReject) => Center( + child: Container( + margin: widget.folder.isExpanded ? null : const EdgeInsets.only(right: 8), + padding: widget.folder.isExpanded ? const EdgeInsets.only(right: 8) : null, + height: 50, + decoration: BoxDecoration( + color: willAccept.isNotEmpty + ? Theme.of(context).dividerColor + : isExpanded + ? Theme.of(context).scaffoldBackgroundColor + : null, + borderRadius: BorderRadius.only( + topRight: widget.folder.isExpanded ? const Radius.circular(0) : const Radius.circular(8), + topLeft: const Radius.circular(8), + bottomRight: widget.folder.isExpanded ? const Radius.circular(0) : const Radius.circular(8), + bottomLeft: widget.folder.isExpanded ? const Radius.circular(0) : const Radius.circular(8), + ), + ), + child: Material( + // Material to draw on for the InkWell + color: Colors.transparent, + child: InkWell( + onTap: () async { + if (widget.expandOverride != null) return; + if (isExpanded) { + widget.expandableController.value = false; + return; + } + if (widget.tokens.isEmpty || (widget.tokens.length == 1 && widget.tokens.first == draggingSortable)) return; + if (widget.folder.isLocked && await lockAuth(localizedReason: AppLocalizations.of(context)!.expandLockedFolder) == false) { + return; + } + if (!mounted) return; + widget.expandableController.value = true; + }, + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + RotationTransition( + turns: Tween(begin: 0.25, end: 0.0).animate(widget.animationController), + child: SizedBox.square( + dimension: 25, + child: (widget.tokens.isEmpty || (widget.tokens.length == 1 && widget.tokens.first == draggingSortable)) + ? null + : const Icon(Icons.arrow_forward_ios_sharp), + )), + const SizedBox(width: 8), + Expanded( + flex: 2, + child: Text( + widget.folder.label, + style: Theme.of(context).textTheme.titleLarge, + overflow: TextOverflow.fade, + softWrap: false, + ), + ), + TokenFolderExpandableHeaderIcon( + showEmptyFolderIcon: (widget.tokens.isEmpty || (widget.tokens.length == 1 && widget.tokens.first == draggingSortable)), + isLocked: widget.folder.isLocked, + isExpanded: isExpanded, + ), + ], + ), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header_icon.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header_icon.dart new file mode 100644 index 000000000..7b2b95011 --- /dev/null +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header_icon.dart @@ -0,0 +1,82 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; + +import '../../../../../utils/customization/theme_extentions/action_theme.dart'; +import '../../../../../widgets/custom_trailing.dart'; + +class TokenFolderExpandableHeaderIcon extends StatelessWidget { + final bool showEmptyFolderIcon; + final bool isLocked; + final bool isExpanded; + + const TokenFolderExpandableHeaderIcon({super.key, required this.showEmptyFolderIcon, required this.isLocked, required this.isExpanded}); + + @override + Widget build(BuildContext context) { + if (showEmptyFolderIcon) { + return CustomTrailing( + child: Center( + child: Icon( + Icons.folder_open, + color: Theme.of(context).listTileTheme.iconColor, + ), + ), + ); + } + return CustomTrailing( + child: Center( + child: Stack( + alignment: Alignment.center, + children: [ + Icon( + Icons.folder, + color: Theme.of(context).listTileTheme.iconColor, + ), + if (isLocked) + Positioned.fill( + child: LayoutBuilder( + builder: (context, constraints) { + return Container( + padding: EdgeInsets.only(left: isExpanded ? 2 : 0, top: 1), + child: Icon( + isExpanded ? MdiIcons.lockOpenVariant : MdiIcons.lock, + color: Theme.of(context).extension()?.lockColor, + size: constraints.maxHeight / 2.1, + shadows: [ + Shadow( + color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.3), + offset: const Offset(0.5, 0.5), + blurRadius: 2, + ) + ], + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/main_view/main_view_widgets/loading_indicator.dart b/lib/views/main_view/main_view_widgets/loading_indicator.dart index 68615f38a..946f4198f 100644 --- a/lib/views/main_view/main_view_widgets/loading_indicator.dart +++ b/lib/views/main_view/main_view_widgets/loading_indicator.dart @@ -61,7 +61,7 @@ class LoadingIndicator extends StatelessWidget { borderRadius: BorderRadius.circular(99), boxShadow: [ BoxShadow( - color: Theme.of(context).shadowColor.withOpacity(0.3), + color: Theme.of(context).shadowColor, blurRadius: 2, offset: const Offset(0, 2), ), diff --git a/lib/views/main_view/main_view_widgets/token_widgets/main_view_background_icon.dart b/lib/views/main_view/main_view_widgets/main_view_background_image.dart similarity index 97% rename from lib/views/main_view/main_view_widgets/token_widgets/main_view_background_icon.dart rename to lib/views/main_view/main_view_widgets/main_view_background_image.dart index 1ffc5a360..613aad164 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/main_view_background_icon.dart +++ b/lib/views/main_view/main_view_widgets/main_view_background_image.dart @@ -20,16 +20,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -class MainViewBackgroundIcon extends ConsumerWidget { +class MainViewBackgroundImage extends ConsumerWidget { final Widget appImage; - const MainViewBackgroundIcon({super.key, required this.appImage}); + const MainViewBackgroundImage({super.key, required this.appImage}); @override Widget build(BuildContext context, WidgetRef ref) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; final scaffoldBackgroundColor = Theme.of(context).scaffoldBackgroundColor; - final base = isDarkMode ? 0.3 : 0.18; + final base = isDarkMode ? 0.3 : 0.08; final blendMode = isDarkMode ? BlendMode.darken : BlendMode.lighten; return Positioned.fill( child: Padding( 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 25df941b5..887bb3444 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 @@ -60,7 +60,7 @@ class MainViewTokensList extends ConsumerStatefulWidget { // 4. Ignore 2. and 3. if there is a sortable that is dragged // 1 2 3 4 if (!isDraggingTheCurrent && ((!isFirst && !previousWasExpandedFolder) || draggingSortable != null)) { - widgets.add(DragTargetDivider(dependingFolder: null, previousSortable: sortables.last, nextSortable: sortables[i])); + widgets.add(DragTargetDivider(dependingFolder: null, previousSortable: i == 0 ? null : sortables.elementAtOrNull(i - 1), nextSortable: sortables[i])); } if (introductionAdded == false && sortables[i] is Token) { widgets.add( @@ -71,7 +71,7 @@ class MainViewTokensList extends ConsumerStatefulWidget { ); introductionAdded = true; } else { - widgets.add(SortableWidgetBuilder.fromSortable(sortables[i], key: Key('mainview_${sortables[i].runtimeType}${sortables[i].sortIndex}'))); + widgets.add(SortableWidgetBuilder.fromSortable(sortables[i], key: Key('mainview_${sortables[i].runtimeType}${sortables[i].hashCode}'))); } } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart index 4e4bb6c90..67b3bc896 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action_dialog.dart @@ -285,7 +285,7 @@ class _EditActionExpansionTileState extends State with borderRadius: BorderRadius.circular(12.0), boxShadow: [ BoxShadow( - color: Theme.of(context).shadowColor.withOpacity(0.15), + color: Theme.of(context).shadowColor, blurRadius: 5.0, offset: const Offset(0, 3.0), ), diff --git a/lib/views/qr_scanner_view/qr_scanner_view.dart b/lib/views/qr_scanner_view/qr_scanner_view.dart index ab67c5975..6dc232abb 100644 --- a/lib/views/qr_scanner_view/qr_scanner_view.dart +++ b/lib/views/qr_scanner_view/qr_scanner_view.dart @@ -58,58 +58,49 @@ class _QRScannerViewState extends State { PermissionStatus? _cameraPermission; @override - Widget build(BuildContext context) { - return FutureBuilder( - future: Future(() async => _cameraPermission ?? await _requestCameraPermission()), - builder: (context, isGranted) { - if (isGranted.connectionState != ConnectionState.done) return const Center(child: CircularProgressIndicator()); - if (isGranted.data == PermissionStatus.permanentlyDenied) { - return DefaultDialog( - title: Text(AppLocalizations.of(context)!.grantCameraPermissionDialogTitle), - content: Text(AppLocalizations.of(context)!.grantCameraPermissionDialogPermanentlyDenied), - ); - } - if (isGranted.data != PermissionStatus.granted) { - return DefaultDialog( - title: Text(AppLocalizations.of(context)!.grantCameraPermissionDialogTitle), - content: Text(AppLocalizations.of(context)!.grantCameraPermissionDialogContent), - actions: [ - DefaultDialogButton( - child: Text(AppLocalizations.of(context)!.grantCameraPermissionDialogButton), - onPressed: () async { - //Trigger the permission to request it - final cameraPermission = await _requestCameraPermission(); - setState(() => _cameraPermission = cameraPermission); - }, + Widget build(BuildContext context) => FutureBuilder( + future: Future(() async => _cameraPermission ?? await _requestCameraPermission()), + builder: (context, isGranted) { + if (isGranted.connectionState != ConnectionState.done) return const Center(child: CircularProgressIndicator()); + return switch (isGranted.data) { + PermissionStatus.permanentlyDenied => DefaultDialog( + title: Text(AppLocalizations.of(context)!.grantCameraPermissionDialogTitle), + content: Text(AppLocalizations.of(context)!.grantCameraPermissionDialogPermanentlyDenied), ), - DefaultDialogButton( - child: Text(AppLocalizations.of(context)!.cancel), - onPressed: () { - Navigator.pop(context, null); - }, - ), - ], - ); - } - return SafeArea( - child: Stack( - children: [ - Scaffold( - resizeToAvoidBottomInset: false, - backgroundColor: Colors.transparent, - appBar: AppBar( + PermissionStatus.granted => SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, backgroundColor: Colors.transparent, - foregroundColor: Colors.white, - elevation: 0, + appBar: AppBar( + backgroundColor: Colors.transparent, + foregroundColor: Colors.white, + elevation: 0, + ), + extendBodyBehindAppBar: true, + body: const QRScannerWidget(), ), - extendBodyBehindAppBar: true, - body: const SizedBox(), ), - const QRScannerWidget(), - ], - ), - ); - }, - ); - } + _ => DefaultDialog( + title: Text(AppLocalizations.of(context)!.grantCameraPermissionDialogTitle), + content: Text(AppLocalizations.of(context)!.grantCameraPermissionDialogContent), + actions: [ + DefaultDialogButton( + child: Text(AppLocalizations.of(context)!.grantCameraPermissionDialogButton), + onPressed: () async { + //Trigger the permission to request it + final cameraPermission = await _requestCameraPermission(); + setState(() => _cameraPermission = cameraPermission); + }, + ), + DefaultDialogButton( + child: Text(AppLocalizations.of(context)!.cancel), + onPressed: () { + Navigator.pop(context, null); + }, + ), + ], + ), + }; + }, + ); } diff --git a/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart b/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart index 0b7361f29..3ba80dc04 100644 --- a/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart +++ b/lib/views/settings_view/settings_groups/settings_group_import_export_tokens.dart @@ -46,7 +46,7 @@ class _SettingsGroupImportExportTokensState extends ConsumerState Navigator.pushNamed(context, ImportTokensView.routeName) + .then((isImported) => (isImported == true && mounted) ? Navigator.of(context).pop(isImported) : null); } diff --git a/lib/views/settings_view/settings_groups/settings_group_theme.dart b/lib/views/settings_view/settings_groups/settings_group_theme.dart index 995165de7..5ab68a180 100644 --- a/lib/views/settings_view/settings_groups/settings_group_theme.dart +++ b/lib/views/settings_view/settings_groups/settings_group_theme.dart @@ -31,7 +31,7 @@ class SettingsGroupTheme extends StatelessWidget { Widget build(BuildContext context) { final current = EasyDynamicTheme.of(context).themeMode; return SettingsGroup( - title: AppLocalizations.of(context)!.themeMode, + title: AppLocalizations.of(context)!.theme, onPressed: () { switch (current) { case ThemeMode.light: diff --git a/lib/views/settings_view/settings_view_widgets/settings_group.dart b/lib/views/settings_view/settings_view_widgets/settings_group.dart index d7055e291..bb5a44eea 100644 --- a/lib/views/settings_view/settings_view_widgets/settings_group.dart +++ b/lib/views/settings_view/settings_view_widgets/settings_group.dart @@ -42,48 +42,62 @@ class SettingsGroup extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Divider(), - Column( + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + child: Container( + decoration: BoxDecoration( + color: theme.cardColor, + borderRadius: BorderRadius.circular(8.0), + boxShadow: [ + BoxShadow( + color: theme.shadowColor, + blurRadius: 4.0, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - onPressed != null - ? GestureDetector( - onTap: isActive ? onPressed : null, - child: ListTile( - dense: true, - leading: Text( - title, - style: theme.textTheme.titleLarge?.copyWith(color: isActive ? null : Colors.grey), - overflow: TextOverflow.fade, - softWrap: false, - ), - trailing: DefaultIconButton( - tooltip: title, - onPressed: isActive ? onPressed! : null, - icon: trailingIcon ?? Icons.arrow_forward_ios, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + onPressed != null + ? GestureDetector( + onTap: isActive ? onPressed : null, + child: ListTile( + dense: true, + leading: Text( + title, + style: theme.textTheme.titleLarge?.copyWith(color: isActive ? null : Colors.grey), + overflow: TextOverflow.fade, + softWrap: false, + ), + trailing: DefaultIconButton( + tooltip: title, + onPressed: isActive ? onPressed! : null, + icon: trailingIcon ?? Icons.arrow_forward_ios, + ), + ), + ) + : ListTile( + dense: true, + leading: Text( + title, + style: theme.textTheme.titleLarge?.copyWith(color: isActive ? null : Colors.grey), + overflow: TextOverflow.fade, + softWrap: false, + ), ), - ), - ) - : ListTile( - dense: true, - leading: Text( - title, - style: theme.textTheme.titleLarge?.copyWith(color: isActive ? null : Colors.grey), - overflow: TextOverflow.fade, - softWrap: false, - ), - ), - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Column(children: children), + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Column(children: children), + ), + ], ), ], ), - const Divider(), - ], + ), ); } } diff --git a/pubspec.lock b/pubspec.lock index cda0228f6..3400eef73 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: ddc6f775260b89176d329dee26f88b9469ef46aa3228ff6a0b91caf2b2989692 + sha256: "5fdcea390499dd26c808a3c662df5f4208d6bbc0643072eee94f1476249e2818" url: "https://pub.dev" source: hosted - version: "1.3.42" + version: "1.3.43" _macros: dependency: transitive description: dart @@ -98,10 +98,10 @@ packages: dependency: "direct main" description: name: asn1lib - sha256: "2ca377ad4d677bbadca278e0ba4da4e057b80a10b927bfc8f7d8bda8fe2ceb75" + sha256: "6b151826fcc95ff246cd219a0bf4c753ea14f4081ad71c61939becf3aba27f70" url: "https://pub.dev" source: hosted - version: "1.5.4" + version: "1.5.5" async: dependency: "direct main" description: @@ -506,10 +506,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + sha256: cb284e267f8e2a45a904b5c094d2ba51d0aabfc20b1538ab786d9ef7dc2bf75c url: "https://pub.dev" source: hosted - version: "0.9.4" + version: "0.9.4+1" file_selector_platform_interface: dependency: transitive description: @@ -538,50 +538,50 @@ packages: dependency: "direct main" description: name: firebase_core - sha256: "40921de9795fbf5887ed5c0adfdf4972d5a8d7ae7e1b2bb98dea39bc02626a88" + sha256: c7de9354eb2cd8bfe8059e1112174c9a58beda7051807207306bc48283277cfb url: "https://pub.dev" source: hosted - version: "3.4.1" + version: "3.5.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: f7d7180c7f99babd4b4c517754d41a09a4943a0f7a69b65c894ca5c68ba66315 + sha256: e30da58198a6d4b49d5bce4e852f985c32cb10db329ebef9473db2b9f09ce810 url: "https://pub.dev" source: hosted - version: "5.2.1" + version: "5.3.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: f4ee170441ca141c5f9ee5ad8737daba3ee9c8e7efb6902aee90b4fbd178ce25 + sha256: f967a7138f5d2ffb1ce15950e2a382924239eaa521150a8f144af34e68b3b3e5 url: "https://pub.dev" source: hosted - version: "2.18.0" + version: "2.18.1" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: cc02c4afd6510cd84586020670140c4a23fbe52af16cd260ccf8ede101bb8d1b + sha256: "32ce60b747e755b48d7112d728d4f736ba82acd98ec825626558d444d385fa3a" url: "https://pub.dev" source: hosted - version: "15.1.1" + version: "15.1.2" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: d8a4984635f09213302243ea670fe5c42f3261d7d8c7c0a5f7dcd5d6c84be459 + sha256: "69671a0f1a40c7b7c46ad0283e6f34ca2a59a0362ca14a240a4ea01c46e8a521" url: "https://pub.dev" source: hosted - version: "4.5.44" + version: "4.5.45" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: "258b9d637965db7855299b123533609ed95e52350746a723dfd1d8d6f3fac678" + sha256: "6890111a9d01d7b13d0f6fe74850812c334e903d2c80a2d9356a3abb8c3a9e9a" url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "3.9.1" fixnum: dependency: transitive description: @@ -620,10 +620,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: c500d5d9e7e553f06b61877ca6b9c8b92c570a4c8db371038702e8ce57f8a50f + sha256: "49eeef364fddb71515bc78d5a8c51435a68bccd6e4d68e25a942c5e47761ae71" url: "https://pub.dev" source: hosted - version: "17.2.2" + version: "17.2.3" flutter_local_notifications_linux: dependency: transitive description: @@ -893,7 +893,7 @@ packages: source: hosted version: "7.0.0" image_picker: - dependency: transitive + dependency: "direct main" description: name: image_picker sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" @@ -1753,10 +1753,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_platform_interface: dependency: transitive description: @@ -1889,10 +1889,10 @@ packages: dependency: transitive description: name: win32_registry - sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.1.5" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 01c644c8f..d7404826f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,8 +41,6 @@ dependencies: pi_authenticator_legacy: path: local_plugins/pi-authenticator-legacy uuid: ^4.0.0 - flutter_zxing: ^1.7.0 - zxing2: ^0.2.3 flutter_svg: ^2.0.10+1 freezed: ^2.5.7 json_annotation: ^4.9.0 @@ -96,7 +94,9 @@ dependencies: riverpod_annotation: ^2.3.5 freezed_annotation: ^2.4.4 async: ^2.11.0 - + image_picker: ^1.1.2 + flutter_zxing: ^1.7.0 + zxing2: ^0.2.3 dev_dependencies: From 965937b29a85f382e79f8953625689b540577cd4 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Thu, 26 Sep 2024 09:47:40 +0200 Subject: [PATCH 050/285] drag down to sync container --- lib/api/token_container_api_endpoint.dart | 259 +---------------- .../pi_server_results/pi_server_result.dart | 28 ++ .../pi_server_result_error.dart | 55 ++++ .../pi_server_result_value.dart | 112 +++++++ lib/model/encryption/encryption_params.dart | 53 ++++ lib/model/pi_server_response.dart | 129 +++++++++ .../pi_server_response.freezed.dart} | 2 +- lib/model/processor_result.dart | 2 +- .../token_container_state.dart | 4 +- .../token_container_state.freezed.dart | 56 ++-- .../token_container_state.g.dart | 14 +- lib/model/token_container.dart | 2 +- lib/model/token_template.dart | 1 + lib/model/tokens/day_password_token.dart | 2 +- lib/model/tokens/hotp_token.dart | 2 +- lib/model/tokens/push_token.dart | 2 +- lib/model/tokens/steam_token.dart | 2 +- lib/model/tokens/token.dart | 1 + lib/model/tokens/totp_token.dart | 2 +- .../mixins/token_import_processor.dart | 2 +- .../home_widget_navigate_processor.dart | 2 +- .../token_container_processor.dart | 2 +- .../otp_auth_processor.dart | 2 +- .../aegis_import_file_processor.dart | 2 +- ...thenticator_pro_import_file_processor.dart | 2 +- .../free_otp_plus_import_file_processor.dart | 2 +- .../two_fas_import_file_processor.dart | 2 +- .../secure_token_container_repository.dart | 2 +- lib/utils/ecc_utils.dart | 8 +- lib/utils/identifiers.dart | 197 ------------- lib/utils/object_validator.dart | 274 ++++++++++++++++++ lib/utils/object_validators.dart | 82 ------ .../token_container_notifier.dart | 51 ++-- .../token_container_notifier.g.dart | 2 +- .../generated_providers/token_notifier.g.dart | 2 +- lib/utils/utils.dart | 2 +- lib/views/container_view/container_view.dart | 2 +- .../pages/import_start_page.dart | 2 +- .../drag_target_divider.dart | 21 +- .../token_folder_expandable.dart | 76 +++-- .../token_folder_expandable_body.dart | 56 ++-- .../token_folder_expandable_header.dart | 168 ++++++----- .../main_view_tokens_list.dart | 80 ++--- .../token_widgets/token_widget_base.dart | 19 +- .../widgets/push_tokens_view_list.dart | 3 +- .../settings_group_container.dart | 2 +- lib/views/splash_screen/splash_screen.dart | 3 + lib/widgets/app_wrapper.dart | 2 +- lib/widgets/default_refresh_indicator.dart | 57 ++-- 49 files changed, 1012 insertions(+), 843 deletions(-) create mode 100644 lib/model/api_results/pi_server_results/pi_server_result.dart create mode 100644 lib/model/api_results/pi_server_results/pi_server_result_error.dart create mode 100644 lib/model/api_results/pi_server_results/pi_server_result_value.dart create mode 100644 lib/model/encryption/encryption_params.dart create mode 100644 lib/model/pi_server_response.dart rename lib/{api/token_container_api_endpoint.freezed.dart => model/pi_server_response.freezed.dart} (99%) create mode 100644 lib/utils/object_validator.dart delete mode 100644 lib/utils/object_validators.dart diff --git a/lib/api/token_container_api_endpoint.dart b/lib/api/token_container_api_endpoint.dart index a1185496c..f1cf5e7a9 100644 --- a/lib/api/token_container_api_endpoint.dart +++ b/lib/api/token_container_api_endpoint.dart @@ -20,17 +20,17 @@ * limitations under the License. */ import 'dart:convert'; -import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:cryptography/cryptography.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:http/http.dart'; import 'package:privacyidea_authenticator/processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart'; import 'package:privacyidea_authenticator/utils/ecc_utils.dart'; import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; import '../l10n/app_localizations.dart'; +import '../model/api_results/pi_server_results/pi_server_result_value.dart'; +import '../model/pi_server_response.dart'; import '../model/riverpod_states/token_state.dart'; import '../model/token_template.dart'; import '../model/token_container.dart'; @@ -41,8 +41,6 @@ import '../utils/logger.dart'; import '../utils/view_utils.dart'; import '../widgets/dialog_widgets/enter_passphrase_dialog.dart'; -part 'token_container_api_endpoint.freezed.dart'; - class PrivacyideaContainerApi { final PrivacyideaIOClient _ioClient; const PrivacyideaContainerApi({required PrivacyideaIOClient ioClient}) : _ioClient = ioClient; @@ -297,256 +295,3 @@ class PrivacyideaContainerApi { ); } } - -abstract class PiServerResult { - bool get status; - PiServerResultError? get asError => this is PiServerResultError ? this as PiServerResultError : null; - PiServerResultValue? get asValue => this is PiServerResultValue ? this as PiServerResultValue : null; - const PiServerResult(); -} - -@freezed -class PiServerResponse with _$PiServerResponse { - static const RESULT = 'result'; - static const DETAIL = 'detail'; - static const ID = 'id'; - static const JSONRPC = 'jsonrpc'; - static const TIME = 'time'; - static const VERSION = 'version'; - static const SIGNATURE = 'signature'; - - static const RESULT_STATUS = 'status'; - static const RESULT_VALUE = 'value'; - static const RESULT_ERROR = 'error'; - - const PiServerResponse._(); - factory PiServerResponse.success({ - required dynamic detail, - required int id, - required String jsonrpc, - required T resultValue, - required double time, - required String version, - required String signature, - }) = PiServerResponseSuccess; - bool get isSuccess => this is PiServerResponseSuccess; - PiServerResponseSuccess get asSuccess => this as PiServerResponseSuccess; - - factory PiServerResponse.error({ - required dynamic detail, - required int id, - required String jsonrpc, - required PiServerResultError resultError, - required double time, - required String version, - required String signature, - }) = PiServerResponseError; - bool get isError => this is PiServerResponseError; - PiServerResponseError get asError => this as PiServerResponseError; - - factory PiServerResponse.fromJson(Map json) { - Logger.debug('Received container sync response: $json'); - final map = validateMap( - map: json, - validators: { - RESULT: const ObjectValidator>(), - ID: const ObjectValidator(), - JSONRPC: const ObjectValidator(), - DETAIL: const ObjectValidatorNullable(), - TIME: const ObjectValidator(), - VERSION: const ObjectValidator(), - SIGNATURE: const ObjectValidator(), - }, - name: 'PiServerResponse#fromJson', - ); - final result = validateMap( - map: map[RESULT], - validators: { - RESULT_STATUS: const ObjectValidator(), - RESULT_VALUE: const ObjectValidatorNullable>(), - RESULT_ERROR: const ObjectValidatorNullable>(), - }, - name: 'PiServerResponse#fromJson#result', - ); - if (result[RESULT_STATUS] == true && result.containsKey(RESULT_VALUE)) { - return PiServerResponse.success( - detail: map[DETAIL], - id: map[ID], - jsonrpc: map[JSONRPC], - resultValue: PiServerResultValue.fromJsonOfType(result[RESULT_VALUE]), - time: map[TIME], - version: map[VERSION], - signature: map[SIGNATURE], - ); - } - if (result[RESULT_STATUS] == false && result.containsKey(RESULT_ERROR)) { - return PiServerResponse.error( - detail: map[DETAIL], - id: json[ID], - jsonrpc: map[JSONRPC], - resultError: PiServerResultError.fromJson(result[RESULT_ERROR]), - time: map[TIME], - version: map[VERSION], - signature: map[SIGNATURE], - ); - } - Logger.info('Status: ${result[RESULT_STATUS]}' - '\nContains error: ${result.containsKey(RESULT_ERROR)}' - '\nContains value: ${result.containsKey(RESULT_VALUE)}'); - - throw UnimplementedError('Unknown PiServerResponse type'); - } - - factory PiServerResponse.fromResponse(Response response) { - return PiServerResponse.fromJson(jsonDecode(response.body)); - } -} - -class EncryptionParams { - final String algorithm; - final String initVector; - final String mode; - final String tag; - - const EncryptionParams({ - required this.algorithm, - required this.mode, - required this.initVector, - required this.tag, - }); - - static EncryptionParams fromJson(Map json) { - final map = validateMap( - map: json, - validators: { - 'algorithm': const ObjectValidator(), - 'init_vector': const ObjectValidator(), - 'mode': const ObjectValidator(), - 'tag': const ObjectValidator(), - }, - name: 'EncryptionParams#fromJson', - ); - return EncryptionParams( - algorithm: map['algorithm'] as String, - initVector: map['init_vector'] as String, - mode: map['mode'] as String, - tag: map['tag'] as String, - ); - } -} -/* //////////////////////////// -////// PI SERVER RESULTS ////// -//////////////////////////// */ - -class PiServerResultError extends PiServerResult { - @override - bool get status => false; - final int code; - final String message; - - const PiServerResultError({ - required this.code, - required this.message, - }); - - factory PiServerResultError.fromJson(Map json) { - final map = validateMap( - map: json, - validators: { - PI_SERVER_ERROR_CODE: const ObjectValidator(), - PI_SERVER_ERROR_MESSAGE: const ObjectValidator(), - }, - name: 'PiServerResultError#fromJson', - ); - return PiServerResultError( - code: map[PI_SERVER_ERROR_CODE] as int, - message: map[PI_SERVER_ERROR_MESSAGE] as String, - ); - } - @override - String toString() => 'PiError(code: $code, message: $message)'; -} - -sealed class PiServerResultValue extends PiServerResult { - @override - bool get status => true; - - static T fromJsonOfType(Map json) { - return switch (T) { - const (ContainerChallenge) => ContainerChallenge.fromJson(json) as T, - const (ContainerSyncResult) => ContainerSyncResult.fromJson(json) as T, - _ => throw UnimplementedError('Unknown PiServerResultValue type'), - }; - } - - const PiServerResultValue(); -} - -class ContainerChallenge extends PiServerResultValue { - final String finalizeSyncUrl; - final String keyAlgorithm; - final String nonce; - final String timeStamp; - - get timeAsDatetime => DateTime.parse(timeStamp); - - const ContainerChallenge({ - required this.finalizeSyncUrl, - required this.keyAlgorithm, - required this.nonce, - required this.timeStamp, - }); - - factory ContainerChallenge.fromJson(Map json) { - final map = validateMap( - map: json, - validators: { - CONTAINER_SYNC_URL: const ObjectValidator(), - CONTAINER_SYNC_KEY_ALGORITHM: const ObjectValidator(), - CONTAINER_SYNC_NONCE: const ObjectValidator(), - CONTAINER_SYNC_TIMESTAMP: const ObjectValidator(), - }, - name: 'ContainerChallenge#fromJson', - ); - return ContainerChallenge( - finalizeSyncUrl: map[CONTAINER_SYNC_URL] as String, - keyAlgorithm: map[CONTAINER_SYNC_KEY_ALGORITHM] as String, - nonce: map[CONTAINER_SYNC_NONCE] as String, - timeStamp: map[CONTAINER_SYNC_TIMESTAMP] as String, - ); - } -} - -class ContainerSyncResult extends PiServerResultValue { - final String publicServerKey; - Uint8List get publicServerKeyBytes => base64Decode(publicServerKey); - final String encryptionAlgorithm; - final EncryptionParams encryptionParams; - final String containerDictEncrypted; - - const ContainerSyncResult({ - required this.publicServerKey, - required this.encryptionAlgorithm, - required this.encryptionParams, - required this.containerDictEncrypted, - }); - - static ContainerSyncResult fromJson(Map json) { - final map = validateMap( - map: json, - validators: { - CONTAINER_SYNC_PUBLIC_SERVER_KEY: const ObjectValidator(), - CONTAINER_SYNC_ENC_ALGORITHM: const ObjectValidator(), - CONTAINER_SYNC_ENC_PARAMS: ObjectValidator(transformer: (v) => EncryptionParams.fromJson(v)), - CONTAINER_SYNC_DICT_SERVER: const ObjectValidator(), - }, - name: 'ContainerSyncResult#fromJson', - ); - return ContainerSyncResult( - publicServerKey: map[CONTAINER_SYNC_PUBLIC_SERVER_KEY] as String, - encryptionAlgorithm: map[CONTAINER_SYNC_ENC_ALGORITHM] as String, - encryptionParams: map[CONTAINER_SYNC_ENC_PARAMS] as EncryptionParams, - containerDictEncrypted: map[CONTAINER_SYNC_DICT_SERVER] as String, - ); - } -} diff --git a/lib/model/api_results/pi_server_results/pi_server_result.dart b/lib/model/api_results/pi_server_results/pi_server_result.dart new file mode 100644 index 000000000..89764620b --- /dev/null +++ b/lib/model/api_results/pi_server_results/pi_server_result.dart @@ -0,0 +1,28 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 'pi_server_result_error.dart'; +import 'pi_server_result_value.dart'; + +abstract class PiServerResult { + bool get status; + PiServerResultError? get asError => this is PiServerResultError ? this as PiServerResultError : null; + PiServerResultValue? get asValue => this is PiServerResultValue ? this as PiServerResultValue : null; + const PiServerResult(); +} diff --git a/lib/model/api_results/pi_server_results/pi_server_result_error.dart b/lib/model/api_results/pi_server_results/pi_server_result_error.dart new file mode 100644 index 000000000..fa5041211 --- /dev/null +++ b/lib/model/api_results/pi_server_results/pi_server_result_error.dart @@ -0,0 +1,55 @@ +// ignore_for_file: constant_identifier_names + +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../../utils/object_validator.dart'; +import 'pi_server_result.dart'; + +class PiServerResultError extends PiServerResult { + static const CODE = 'code'; + static const MESSAGE = 'message'; + + @override + bool get status => false; + final int code; + final String message; + + const PiServerResultError({ + required this.code, + required this.message, + }); + + factory PiServerResultError.fromResult(Map json) { + final map = validateMap( + map: json, + validators: { + CODE: const ObjectValidator(), + MESSAGE: const ObjectValidator(), + }, + name: 'PiServerResultError#fromJson', + ); + return PiServerResultError( + code: map[CODE] as int, + message: map[MESSAGE] as String, + ); + } + @override + String toString() => 'PiError(code: $code, message: $message)'; +} diff --git a/lib/model/api_results/pi_server_results/pi_server_result_value.dart b/lib/model/api_results/pi_server_results/pi_server_result_value.dart new file mode 100644 index 000000000..632f3cb2b --- /dev/null +++ b/lib/model/api_results/pi_server_results/pi_server_result_value.dart @@ -0,0 +1,112 @@ +// ignore_for_file: constant_identifier_names + +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; +import 'dart:typed_data'; + +import '../../../utils/identifiers.dart'; +import '../../../utils/object_validator.dart'; +import '../../encryption/encryption_params.dart'; +import 'pi_server_result.dart'; + +sealed class PiServerResultValue extends PiServerResult { + @override + bool get status => true; + + static T fromJsonOfType(Map json) { + return switch (T) { + const (ContainerChallenge) => ContainerChallenge.fromJson(json) as T, + const (ContainerSyncResult) => ContainerSyncResult.fromJson(json) as T, + _ => throw UnimplementedError('Unknown PiServerResultValue type'), + }; + } + + const PiServerResultValue(); +} + +class ContainerChallenge extends PiServerResultValue { + final String finalizeSyncUrl; + final String keyAlgorithm; + final String nonce; + final String timeStamp; + + get timeAsDatetime => DateTime.parse(timeStamp); + + const ContainerChallenge({ + required this.finalizeSyncUrl, + required this.keyAlgorithm, + required this.nonce, + required this.timeStamp, + }); + + factory ContainerChallenge.fromJson(Map json) { + final map = validateMap( + map: json, + validators: { + CONTAINER_SYNC_URL: const ObjectValidator(), + CONTAINER_SYNC_KEY_ALGORITHM: const ObjectValidator(), + CONTAINER_SYNC_NONCE: const ObjectValidator(), + CONTAINER_SYNC_TIMESTAMP: const ObjectValidator(), + }, + name: 'ContainerChallenge#fromJson', + ); + return ContainerChallenge( + finalizeSyncUrl: map[CONTAINER_SYNC_URL] as String, + keyAlgorithm: map[CONTAINER_SYNC_KEY_ALGORITHM] as String, + nonce: map[CONTAINER_SYNC_NONCE] as String, + timeStamp: map[CONTAINER_SYNC_TIMESTAMP] as String, + ); + } +} + +class ContainerSyncResult extends PiServerResultValue { + final String publicServerKey; + Uint8List get publicServerKeyBytes => base64Decode(publicServerKey); + final String encryptionAlgorithm; + final EncryptionParams encryptionParams; + final String containerDictEncrypted; + + const ContainerSyncResult({ + required this.publicServerKey, + required this.encryptionAlgorithm, + required this.encryptionParams, + required this.containerDictEncrypted, + }); + + static ContainerSyncResult fromJson(Map json) { + final map = validateMap( + map: json, + validators: { + CONTAINER_SYNC_PUBLIC_SERVER_KEY: const ObjectValidator(), + CONTAINER_SYNC_ENC_ALGORITHM: const ObjectValidator(), + CONTAINER_SYNC_ENC_PARAMS: ObjectValidator(transformer: (v) => EncryptionParams.fromParams(v)), + CONTAINER_SYNC_DICT_SERVER: const ObjectValidator(), + }, + name: 'ContainerSyncResult#fromJson', + ); + return ContainerSyncResult( + publicServerKey: map[CONTAINER_SYNC_PUBLIC_SERVER_KEY] as String, + encryptionAlgorithm: map[CONTAINER_SYNC_ENC_ALGORITHM] as String, + encryptionParams: map[CONTAINER_SYNC_ENC_PARAMS] as EncryptionParams, + containerDictEncrypted: map[CONTAINER_SYNC_DICT_SERVER] as String, + ); + } +} diff --git a/lib/model/encryption/encryption_params.dart b/lib/model/encryption/encryption_params.dart new file mode 100644 index 000000000..72573e227 --- /dev/null +++ b/lib/model/encryption/encryption_params.dart @@ -0,0 +1,53 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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 '../../utils/object_validator.dart'; + +class EncryptionParams { + final String algorithm; + final String initVector; + final String mode; + final String tag; + + const EncryptionParams({ + required this.algorithm, + required this.mode, + required this.initVector, + required this.tag, + }); + + static EncryptionParams fromParams(Map json) { + final map = validateMap( + map: json, + validators: { + 'algorithm': const ObjectValidator(), + 'init_vector': const ObjectValidator(), + 'mode': const ObjectValidator(), + 'tag': const ObjectValidator(), + }, + name: 'EncryptionParams#fromJson', + ); + return EncryptionParams( + algorithm: map['algorithm'] as String, + initVector: map['init_vector'] as String, + mode: map['mode'] as String, + tag: map['tag'] as String, + ); + } +} diff --git a/lib/model/pi_server_response.dart b/lib/model/pi_server_response.dart new file mode 100644 index 000000000..80f405f70 --- /dev/null +++ b/lib/model/pi_server_response.dart @@ -0,0 +1,129 @@ +// ignore_for_file: constant_identifier_names + +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:convert'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:http/http.dart'; + +import '../utils/logger.dart'; +import '../utils/object_validator.dart'; +import 'api_results/pi_server_results/pi_server_result_error.dart'; +import 'api_results/pi_server_results/pi_server_result_value.dart'; + +part 'pi_server_response.freezed.dart'; + +@freezed +class PiServerResponse with _$PiServerResponse { + static const RESULT = 'result'; + static const DETAIL = 'detail'; + static const ID = 'id'; + static const JSONRPC = 'jsonrpc'; + static const TIME = 'time'; + static const VERSION = 'version'; + static const SIGNATURE = 'signature'; + + static const RESULT_STATUS = 'status'; + static const RESULT_VALUE = 'value'; + static const RESULT_ERROR = 'error'; + + const PiServerResponse._(); + factory PiServerResponse.success({ + required dynamic detail, + required int id, + required String jsonrpc, + required T resultValue, + required double time, + required String version, + required String signature, + }) = PiServerResponseSuccess; + bool get isSuccess => this is PiServerResponseSuccess; + PiServerResponseSuccess get asSuccess => this as PiServerResponseSuccess; + + factory PiServerResponse.error({ + required dynamic detail, + required int id, + required String jsonrpc, + required PiServerResultError resultError, + required double time, + required String version, + required String signature, + }) = PiServerResponseError; + bool get isError => this is PiServerResponseError; + PiServerResponseError get asError => this as PiServerResponseError; + + factory PiServerResponse.fromJson(Map json) { + Logger.debug('Received container sync response: $json'); + final map = validateMap( + map: json, + validators: { + RESULT: const ObjectValidator>(), + ID: const ObjectValidator(), + JSONRPC: const ObjectValidator(), + DETAIL: const ObjectValidatorNullable(), + TIME: const ObjectValidator(), + VERSION: const ObjectValidator(), + SIGNATURE: const ObjectValidator(), + }, + name: 'PiServerResponse#fromJson', + ); + final result = validateMap( + map: map[RESULT], + validators: { + RESULT_STATUS: const ObjectValidator(), + RESULT_VALUE: const ObjectValidatorNullable>(), + RESULT_ERROR: const ObjectValidatorNullable>(), + }, + name: 'PiServerResponse#fromJson#result', + ); + if (result[RESULT_STATUS] == true && result.containsKey(RESULT_VALUE)) { + return PiServerResponse.success( + detail: map[DETAIL], + id: map[ID], + jsonrpc: map[JSONRPC], + resultValue: PiServerResultValue.fromJsonOfType(result[RESULT_VALUE]), + time: map[TIME], + version: map[VERSION], + signature: map[SIGNATURE], + ); + } + if (result[RESULT_STATUS] == false && result.containsKey(RESULT_ERROR)) { + return PiServerResponse.error( + detail: map[DETAIL], + id: json[ID], + jsonrpc: map[JSONRPC], + resultError: PiServerResultError.fromResult(result[RESULT_ERROR]), + time: map[TIME], + version: map[VERSION], + signature: map[SIGNATURE], + ); + } + Logger.info('Status: ${result[RESULT_STATUS]}' + '\nContains error: ${result.containsKey(RESULT_ERROR)}' + '\nContains value: ${result.containsKey(RESULT_VALUE)}'); + + throw UnimplementedError('Unknown PiServerResponse type'); + } + + factory PiServerResponse.fromResponse(Response response) { + return PiServerResponse.fromJson(jsonDecode(response.body)); + } +} diff --git a/lib/api/token_container_api_endpoint.freezed.dart b/lib/model/pi_server_response.freezed.dart similarity index 99% rename from lib/api/token_container_api_endpoint.freezed.dart rename to lib/model/pi_server_response.freezed.dart index 27181ca41..59a117563 100644 --- a/lib/api/token_container_api_endpoint.freezed.dart +++ b/lib/model/pi_server_response.freezed.dart @@ -3,7 +3,7 @@ // ignore_for_file: type=lint // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark -part of 'token_container_api_endpoint.dart'; +part of 'pi_server_response.dart'; // ************************************************************************** // FreezedGenerator diff --git a/lib/model/processor_result.dart b/lib/model/processor_result.dart index 63974ad0c..382d9466e 100644 --- a/lib/model/processor_result.dart +++ b/lib/model/processor_result.dart @@ -19,11 +19,11 @@ */ import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:privacyidea_authenticator/utils/identifiers.dart'; import '../l10n/app_localizations.dart'; import '../utils/globals.dart'; import '../utils/logger.dart'; +import '../utils/object_validator.dart'; import '../utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import '../utils/view_utils.dart'; diff --git a/lib/model/riverpod_states/token_container_state.dart b/lib/model/riverpod_states/token_container_state.dart index a7985093e..e459027c2 100644 --- a/lib/model/riverpod_states/token_container_state.dart +++ b/lib/model/riverpod_states/token_container_state.dart @@ -34,7 +34,9 @@ class TokenContainerState with _$TokenContainerState { required List container, }) = _CredentialsState; - TokenContainer? containerOf(String containerSerial) => container.firstWhereOrNull((container) => container.serial == containerSerial); + bool get hasFinalizedContainers => containerList.every((container) => container is TokenContainerFinalized); + + TokenContainer? containerOf(String containerSerial) => containerList.firstWhereOrNull((container) => container.serial == containerSerial); static TokenContainerState fromJsonStringList(List jsonStrings) { final container = jsonStrings.map((jsonString) => TokenContainer.fromJson(jsonDecode(jsonString))).toList(); return TokenContainerState(container: container); diff --git a/lib/model/riverpod_states/token_container_state.freezed.dart b/lib/model/riverpod_states/token_container_state.freezed.dart index a0be8ab7f..6197d4ad4 100644 --- a/lib/model/riverpod_states/token_container_state.freezed.dart +++ b/lib/model/riverpod_states/token_container_state.freezed.dart @@ -20,7 +20,7 @@ TokenContainerState _$TokenContainerStateFromJson(Map json) { /// @nodoc mixin _$TokenContainerState { - List get container => throw _privateConstructorUsedError; + List get containerList => throw _privateConstructorUsedError; /// Serializes this TokenContainerState to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -28,22 +28,19 @@ mixin _$TokenContainerState { /// Create a copy of TokenContainerState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $TokenContainerStateCopyWith get copyWith => - throw _privateConstructorUsedError; + $TokenContainerStateCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc abstract class $TokenContainerStateCopyWith<$Res> { - factory $TokenContainerStateCopyWith( - TokenContainerState value, $Res Function(TokenContainerState) then) = + factory $TokenContainerStateCopyWith(TokenContainerState value, $Res Function(TokenContainerState) then) = _$TokenContainerStateCopyWithImpl<$Res, TokenContainerState>; @useResult $Res call({List container}); } /// @nodoc -class _$TokenContainerStateCopyWithImpl<$Res, $Val extends TokenContainerState> - implements $TokenContainerStateCopyWith<$Res> { +class _$TokenContainerStateCopyWithImpl<$Res, $Val extends TokenContainerState> implements $TokenContainerStateCopyWith<$Res> { _$TokenContainerStateCopyWithImpl(this._value, this._then); // ignore: unused_field @@ -60,7 +57,7 @@ class _$TokenContainerStateCopyWithImpl<$Res, $Val extends TokenContainerState> }) { return _then(_value.copyWith( container: null == container - ? _value.container + ? _value.containerList : container // ignore: cast_nullable_to_non_nullable as List, ) as $Val); @@ -68,10 +65,8 @@ class _$TokenContainerStateCopyWithImpl<$Res, $Val extends TokenContainerState> } /// @nodoc -abstract class _$$CredentialsStateImplCopyWith<$Res> - implements $TokenContainerStateCopyWith<$Res> { - factory _$$CredentialsStateImplCopyWith(_$CredentialsStateImpl value, - $Res Function(_$CredentialsStateImpl) then) = +abstract class _$$CredentialsStateImplCopyWith<$Res> implements $TokenContainerStateCopyWith<$Res> { + factory _$$CredentialsStateImplCopyWith(_$CredentialsStateImpl value, $Res Function(_$CredentialsStateImpl) then) = __$$CredentialsStateImplCopyWithImpl<$Res>; @override @useResult @@ -79,12 +74,9 @@ abstract class _$$CredentialsStateImplCopyWith<$Res> } /// @nodoc -class __$$CredentialsStateImplCopyWithImpl<$Res> - extends _$TokenContainerStateCopyWithImpl<$Res, _$CredentialsStateImpl> +class __$$CredentialsStateImplCopyWithImpl<$Res> extends _$TokenContainerStateCopyWithImpl<$Res, _$CredentialsStateImpl> implements _$$CredentialsStateImplCopyWith<$Res> { - __$$CredentialsStateImplCopyWithImpl(_$CredentialsStateImpl _value, - $Res Function(_$CredentialsStateImpl) _then) - : super(_value, _then); + __$$CredentialsStateImplCopyWithImpl(_$CredentialsStateImpl _value, $Res Function(_$CredentialsStateImpl) _then) : super(_value, _then); /// Create a copy of TokenContainerState /// with the given fields replaced by the non-null parameter values. @@ -109,12 +101,11 @@ class _$CredentialsStateImpl extends _CredentialsState { : _container = container, super._(); - factory _$CredentialsStateImpl.fromJson(Map json) => - _$$CredentialsStateImplFromJson(json); + factory _$CredentialsStateImpl.fromJson(Map json) => _$$CredentialsStateImplFromJson(json); final List _container; @override - List get container { + List get containerList { if (_container is EqualUnmodifiableListView) return _container; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_container); @@ -122,31 +113,25 @@ class _$CredentialsStateImpl extends _CredentialsState { @override String toString() { - return 'TokenContainerState(container: $container)'; + return 'TokenContainerState(container: $containerList)'; } @override bool operator ==(Object other) { return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$CredentialsStateImpl && - const DeepCollectionEquality() - .equals(other._container, _container)); + (other.runtimeType == runtimeType && other is _$CredentialsStateImpl && const DeepCollectionEquality().equals(other._container, _container)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => - Object.hash(runtimeType, const DeepCollectionEquality().hash(_container)); + int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(_container)); /// Create a copy of TokenContainerState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$CredentialsStateImplCopyWith<_$CredentialsStateImpl> get copyWith => - __$$CredentialsStateImplCopyWithImpl<_$CredentialsStateImpl>( - this, _$identity); + _$$CredentialsStateImplCopyWith<_$CredentialsStateImpl> get copyWith => __$$CredentialsStateImplCopyWithImpl<_$CredentialsStateImpl>(this, _$identity); @override Map toJson() { @@ -157,20 +142,17 @@ class _$CredentialsStateImpl extends _CredentialsState { } abstract class _CredentialsState extends TokenContainerState { - const factory _CredentialsState( - {required final List container}) = _$CredentialsStateImpl; + const factory _CredentialsState({required final List container}) = _$CredentialsStateImpl; const _CredentialsState._() : super._(); - factory _CredentialsState.fromJson(Map json) = - _$CredentialsStateImpl.fromJson; + factory _CredentialsState.fromJson(Map json) = _$CredentialsStateImpl.fromJson; @override - List get container; + List get containerList; /// Create a copy of TokenContainerState /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$CredentialsStateImplCopyWith<_$CredentialsStateImpl> get copyWith => - throw _privateConstructorUsedError; + _$$CredentialsStateImplCopyWith<_$CredentialsStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/model/riverpod_states/token_container_state.g.dart b/lib/model/riverpod_states/token_container_state.g.dart index 48ed1eff6..bae74199f 100644 --- a/lib/model/riverpod_states/token_container_state.g.dart +++ b/lib/model/riverpod_states/token_container_state.g.dart @@ -6,16 +6,10 @@ part of 'token_container_state.dart'; // JsonSerializableGenerator // ************************************************************************** -_$CredentialsStateImpl _$$CredentialsStateImplFromJson( - Map json) => - _$CredentialsStateImpl( - container: (json['container'] as List) - .map((e) => TokenContainer.fromJson(e as Map)) - .toList(), +_$CredentialsStateImpl _$$CredentialsStateImplFromJson(Map json) => _$CredentialsStateImpl( + container: (json['container'] as List).map((e) => TokenContainer.fromJson(e as Map)).toList(), ); -Map _$$CredentialsStateImplToJson( - _$CredentialsStateImpl instance) => - { - 'container': instance.container, +Map _$$CredentialsStateImplToJson(_$CredentialsStateImpl instance) => { + 'container': instance.containerList, }; diff --git a/lib/model/token_container.dart b/lib/model/token_container.dart index 3803b9db8..1ea88fc56 100644 --- a/lib/model/token_container.dart +++ b/lib/model/token_container.dart @@ -26,10 +26,10 @@ import 'package:privacyidea_authenticator/model/enums/algorithms.dart'; import 'package:privacyidea_authenticator/model/extensions/enums/ec_key_algorithm_extension.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; -import 'package:privacyidea_authenticator/utils/object_validators.dart'; import '../utils/ecc_utils.dart'; import '../utils/logger.dart'; +import '../utils/object_validator.dart'; import 'enums/container_finalization_state.dart'; import 'enums/ec_key_algorithm.dart'; import 'enums/token_origin_source_type.dart'; diff --git a/lib/model/token_template.dart b/lib/model/token_template.dart index 75bbb9fc5..ec33e72da 100644 --- a/lib/model/token_template.dart +++ b/lib/model/token_template.dart @@ -28,6 +28,7 @@ import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:privacyidea_authenticator/model/tokens/otp_token.dart'; import 'package:privacyidea_authenticator/utils/identifiers.dart'; +import '../utils/object_validator.dart'; import 'token_import/token_origin_data.dart'; import 'tokens/token.dart'; diff --git a/lib/model/tokens/day_password_token.dart b/lib/model/tokens/day_password_token.dart index b7a9f69d1..18ee45ea2 100644 --- a/lib/model/tokens/day_password_token.dart +++ b/lib/model/tokens/day_password_token.dart @@ -21,7 +21,7 @@ import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; import '../../utils/identifiers.dart'; -import '../../utils/object_validators.dart'; +import '../../utils/object_validator.dart'; import '../token_template.dart'; import 'package:uuid/uuid.dart'; diff --git a/lib/model/tokens/hotp_token.dart b/lib/model/tokens/hotp_token.dart index 475d11e5d..e5a5b43ef 100644 --- a/lib/model/tokens/hotp_token.dart +++ b/lib/model/tokens/hotp_token.dart @@ -19,10 +19,10 @@ */ import 'package:json_annotation/json_annotation.dart'; -import 'package:privacyidea_authenticator/utils/object_validators.dart'; import 'package:uuid/uuid.dart'; import '../../utils/identifiers.dart'; +import '../../utils/object_validator.dart'; import '../enums/algorithms.dart'; import '../enums/token_types.dart'; import '../extensions/enums/algorithms_extension.dart'; diff --git a/lib/model/tokens/push_token.dart b/lib/model/tokens/push_token.dart index df2985aff..330eb4666 100644 --- a/lib/model/tokens/push_token.dart +++ b/lib/model/tokens/push_token.dart @@ -25,8 +25,8 @@ import 'package:uuid/uuid.dart'; import '../../utils/custom_int_buffer.dart'; import '../../utils/errors.dart'; import '../../utils/identifiers.dart'; +import '../../utils/object_validator.dart'; import '../../utils/rsa_utils.dart'; -import '../../utils/object_validators.dart'; import '../enums/push_token_rollout_state.dart'; import '../enums/token_types.dart'; import '../token_import/token_origin_data.dart'; diff --git a/lib/model/tokens/steam_token.dart b/lib/model/tokens/steam_token.dart index f0fd6ad86..6f856ee4d 100644 --- a/lib/model/tokens/steam_token.dart +++ b/lib/model/tokens/steam_token.dart @@ -24,7 +24,7 @@ import 'package:privacyidea_authenticator/model/token_template.dart'; import 'package:uuid/uuid.dart'; import '../../utils/identifiers.dart'; -import '../../utils/object_validators.dart'; +import '../../utils/object_validator.dart'; import '../enums/algorithms.dart'; import '../enums/token_types.dart'; import '../extensions/int_extension.dart'; diff --git a/lib/model/tokens/token.dart b/lib/model/tokens/token.dart index ef3615b10..495fca65f 100644 --- a/lib/model/tokens/token.dart +++ b/lib/model/tokens/token.dart @@ -21,6 +21,7 @@ */ import 'package:flutter/material.dart'; import 'package:privacyidea_authenticator/model/token_container.dart'; +import '../../utils/object_validator.dart'; import '../token_template.dart'; import '../../utils/identifiers.dart'; diff --git a/lib/model/tokens/totp_token.dart b/lib/model/tokens/totp_token.dart index ee3a46476..797cf3126 100644 --- a/lib/model/tokens/totp_token.dart +++ b/lib/model/tokens/totp_token.dart @@ -23,7 +23,7 @@ import 'package:uuid/uuid.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; -import '../../utils/object_validators.dart'; +import '../../utils/object_validator.dart'; import '../enums/algorithms.dart'; import '../enums/token_types.dart'; import '../extensions/enums/algorithms_extension.dart'; diff --git a/lib/processors/mixins/token_import_processor.dart b/lib/processors/mixins/token_import_processor.dart index ca85f238b..b7e6acdfe 100644 --- a/lib/processors/mixins/token_import_processor.dart +++ b/lib/processors/mixins/token_import_processor.dart @@ -19,7 +19,7 @@ */ import '../../model/processor_result.dart'; import '../../model/tokens/token.dart'; -import '../../utils/identifiers.dart'; +import '../../utils/object_validator.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../scheme_processors/token_import_scheme_processors/google_authenticator_qr_processor.dart'; import '../token_import_file_processor/token_import_file_processor_interface.dart'; diff --git a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart index 561d42740..4a26c880b 100644 --- a/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart +++ b/lib/processors/scheme_processors/navigation_scheme_processors/home_widget_navigate_processor.dart @@ -22,8 +22,8 @@ import 'package:flutter/material.dart'; import '../../../model/processor_result.dart'; import '../../../utils/globals.dart'; import '../../../utils/home_widget_utils.dart'; -import '../../../utils/identifiers.dart'; import '../../../utils/logger.dart'; +import '../../../utils/object_validator.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../views/link_home_widget_view/link_home_widget_view.dart'; diff --git a/lib/processors/scheme_processors/token_container_processor.dart b/lib/processors/scheme_processors/token_container_processor.dart index f2ab7ebb6..23441c7a1 100644 --- a/lib/processors/scheme_processors/token_container_processor.dart +++ b/lib/processors/scheme_processors/token_container_processor.dart @@ -23,8 +23,8 @@ import 'package:privacyidea_authenticator/utils/globals.dart'; import '../../model/processor_result.dart'; import '../../model/token_container.dart'; -import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; +import '../../utils/object_validator.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import 'scheme_processor_interface.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 d2ea66c6e..2f7653df0 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 @@ -29,7 +29,7 @@ import '../../../model/token_import/token_origin_data.dart'; import '../../../model/tokens/token.dart'; import '../../../utils/identifiers.dart'; import '../../../utils/logger.dart'; -import '../../../utils/object_validators.dart'; +import '../../../utils/object_validator.dart'; import '../../../utils/utils.dart'; import '../../../utils/view_utils.dart'; import '../../../widgets/dialog_widgets/two_step_dialog.dart'; 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 38d13e9fb..1d1ca3b95 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 @@ -27,7 +27,6 @@ import 'package:cryptography/cryptography.dart' as crypto; import 'package:encrypt/encrypt.dart'; import 'package:file_selector/file_selector.dart'; import 'package:pointycastle/export.dart'; -import 'package:privacyidea_authenticator/utils/object_validators.dart'; import '../../l10n/app_localizations.dart'; import '../../model/enums/encodings.dart'; @@ -40,6 +39,7 @@ import '../../utils/errors.dart'; import '../../utils/globals.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; +import '../../utils/object_validator.dart'; import '../../utils/token_import_origins.dart'; import 'token_import_file_processor_interface.dart'; import 'two_fas_import_file_processor.dart'; 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 703fe7a1d..bcdec348a 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 @@ -23,7 +23,6 @@ import 'dart:convert'; import 'package:cryptography/cryptography.dart'; import 'package:file_selector/file_selector.dart'; -import 'package:privacyidea_authenticator/utils/object_validators.dart'; import '../../l10n/app_localizations.dart'; import '../../model/encryption/uint_8_buffer.dart'; @@ -40,6 +39,7 @@ import '../../utils/errors.dart'; import '../../utils/globals.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; +import '../../utils/object_validator.dart'; import '../../utils/token_import_origins.dart'; import 'token_import_file_processor_interface.dart'; diff --git a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart index 289122d3a..ff514debf 100644 --- a/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart +++ b/lib/processors/token_import_file_processor/free_otp_plus_import_file_processor.dart @@ -25,7 +25,6 @@ import 'dart:typed_data'; import 'package:file_selector/file_selector.dart'; import 'package:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; import 'package:privacyidea_authenticator/model/enums/encodings.dart'; -import 'package:privacyidea_authenticator/utils/object_validators.dart'; import '../../l10n/app_localizations.dart'; import '../../model/enums/token_origin_source_type.dart'; @@ -36,6 +35,7 @@ import '../../utils/errors.dart'; import '../../utils/globals.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; +import '../../utils/object_validator.dart'; 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'; 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 fdcd960ac..f61104a8a 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 @@ -23,7 +23,6 @@ import 'dart:convert'; import 'package:cryptography/cryptography.dart'; import 'package:file_selector/file_selector.dart'; -import 'package:privacyidea_authenticator/utils/object_validators.dart'; import '../../l10n/app_localizations.dart'; import '../../model/enums/token_origin_source_type.dart'; @@ -35,6 +34,7 @@ import '../../utils/errors.dart'; import '../../utils/globals.dart'; import '../../utils/identifiers.dart'; import '../../utils/logger.dart'; +import '../../utils/object_validator.dart'; import '../../utils/token_import_origins.dart'; import 'token_import_file_processor_interface.dart'; diff --git a/lib/repo/secure_token_container_repository.dart b/lib/repo/secure_token_container_repository.dart index 795510cdc..18923afd4 100644 --- a/lib/repo/secure_token_container_repository.dart +++ b/lib/repo/secure_token_container_repository.dart @@ -32,7 +32,7 @@ class SecureTokenContainerRepository extends TokenContainerRepository { Future saveContainerState(TokenContainerState containerState) async { Logger.warning('Saving container: $containerState'); final futures = []; - for (var container in containerState.container) { + for (var container in containerState.containerList) { futures.add(saveContainer(container)); } await Future.wait(futures); diff --git a/lib/utils/ecc_utils.dart b/lib/utils/ecc_utils.dart index e42470db2..ed1340fcc 100644 --- a/lib/utils/ecc_utils.dart +++ b/lib/utils/ecc_utils.dart @@ -37,6 +37,10 @@ class EccUtils { return CryptoUtils.ecSignatureToBase64(ecSignature); } - AsymmetricKeyPair generateKeyPair(EcKeyAlgorithm keyAlgorithm) => - CryptoUtils.generateEcKeyPair(curve: keyAlgorithm.curveName) as AsymmetricKeyPair; + AsymmetricKeyPair generateKeyPair(EcKeyAlgorithm keyAlgorithm) { + final keyPair = CryptoUtils.generateEcKeyPair(curve: keyAlgorithm.curveName); + final public = keyPair.publicKey; + final private = keyPair.privateKey; + return AsymmetricKeyPair(public as ECPublicKey, private as ECPrivateKey); + } } diff --git a/lib/utils/identifiers.dart b/lib/utils/identifiers.dart index 5ebd3b384..390d80ea1 100644 --- a/lib/utils/identifiers.dart +++ b/lib/utils/identifiers.dart @@ -22,9 +22,6 @@ // default email address for crash reports -import 'package:privacyidea_authenticator/utils/errors.dart'; -import 'package:privacyidea_authenticator/utils/logger.dart'; - const defaultCrashReportRecipient = 'app-crash@netknights.it'; // otp auth @@ -103,10 +100,6 @@ const String SIGNING_ALGORITHM = 'SHA-256/RSA'; // Custom error identifiers const String FIREBASE_TOKEN_ERROR_CODE = 'FIREBASE_TOKEN_ERROR_CODE'; -// Pi Server Error -const String PI_SERVER_ERROR_CODE = 'code'; -const String PI_SERVER_ERROR_MESSAGE = 'message'; - // Push request: const String PUSH_REQUEST_NONCE = 'nonce'; // 1. const String PUSH_REQUEST_URL = 'url'; // 2. @@ -153,193 +146,3 @@ const String CONTAINER_SYNC_ENC_PARAMS_TAG = 'tag'; const String CONTAINER_SYNC_DICT_ENCRYPTED = 'container_dict_encrypted'; const String GLOBAL_SECURE_REPO_PREFIX = 'app_v3_'; - -T? validateOptional({required dynamic value, required ObjectValidatorNullable validator, required String name}) { - if (validator.isTypeOf(value)) { - return validator.transform(value); - } else { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, name) => localizations.invalidValue(value.runtimeType.toString(), value, name), - unlocalizedMessage: 'The ${value.runtimeType} "$value" is not valid for "$name"', - invalidValue: value, - name: name, - ); - } -} - -T validate({required dynamic value, required ObjectValidator validator, required String name}) { - if (validator.isTypeOf(value)) { - return validator.transform(value); - } else { - throw LocalizedArgumentError( - localizedMessage: (localizations, value, name) => localizations.invalidValue(value.runtimeType.toString(), value, name), - unlocalizedMessage: 'The ${value.runtimeType} "$value" is not valid for "$name"', - invalidValue: value, - name: name, - ); - } -} - -/// Validates a map by checking if it contains all required keys and if the values are of the correct type. -/// -/// Throws a [LocalizedArgumentError] if the map is invalid. -///
If the validator provides a transformer function, the value will be transformed before checking the type. -///
The returned map will contain the transformed values. -Map validateMap({required Map map, required Map> validators, required String? name}) { - Map validatedMap = {}; - for (String key in validators.keys) { - final validator = validators[key]!; - final mapEntry = map[key]; - if (validator.isTypeOf(mapEntry)) { - if (validator.valueIsAllowed(mapEntry)) { - final newValue = validator.transform(mapEntry); - if (newValue != null) validatedMap[key] = newValue; - } else { - throw LocalizedArgumentError( - localizedMessage: name != null - ? (localizations, value, key) => localizations.valueNotAllowedIn(value.runtimeType.toString(), value.toString(), key, name) - : (localizations, value, key) => localizations.valueNotAllowed(value.runtimeType.toString(), value.toString(), key), - unlocalizedMessage: 'The ${mapEntry.runtimeType} "$mapEntry" is not an allowed value for "$key"', - invalidValue: mapEntry.toString(), - name: key, - ); - } - } else { - if (mapEntry == null) { - throw LocalizedArgumentError( - localizedMessage: name != null - ? (localizations, value, key) => localizations.missingRequiredParameterIn(key, name) - : (localizations, value, key) => localizations.missingRequiredParameter(key), - unlocalizedMessage: 'Map does not contain required key "$key"', - invalidValue: mapEntry.toString(), - name: key, - ); - } - throw LocalizedArgumentError( - localizedMessage: name != null - ? (localizations, value, key) => localizations.invalidValueIn(value.runtimeType.toString(), value.toString(), key, name) - : (localizations, value, key) => localizations.invalidValue(value.runtimeType.toString(), value.toString(), key), - unlocalizedMessage: 'The ${mapEntry.runtimeType} "$mapEntry" is not valid for "$key"${name != null ? ' in $name' : ''}', - invalidValue: mapEntry.toString(), - name: key, - ); - } - } - return validatedMap; -} - -class ObjectValidatorNullable { - final T Function(dynamic value)? transformer; - final T? defaultValue; - final bool Function(T)? allowedValues; - - const ObjectValidatorNullable({ - this.transformer, - this.defaultValue, - this.allowedValues, - }); - - /// Checks if the value is of the correct type, or sub-type. - /// If the transformer is provided, the value will be transformed before checking the type. - bool isTypeOf(dynamic value) { - Logger.debug('Checking type (${T.runtimeType}) of nullable value "$value" and default value "$defaultValue" with transformer "$transformer".'); - if (value == null) return true; - if (transformer == null) return value is T?; - try { - transformer!(value); - return true; - } catch (e) { - return false; - } - } - - bool valueIsAllowed(dynamic value) { - if (!isTypeOf(value)) return false; - if (allowedValues == null) return true; - if (value == null) return true; - final transformedValue = transform(value); - if (transformedValue == null) return true; - if (allowedValues!(transformedValue)) return true; - return false; - } - - /// Transforms the value if a transformer is provided, otherwise returns the value as is. - /// May throw an error if the value is not of the correct type. - /// To prevent an error, use isTypeOf before calling transform. - T? transform(dynamic value) { - if (value == null) return defaultValue; - if (transformer == null) return value as T; - try { - return transformer!.call(value); - } catch (e) { - return defaultValue; - } - } - - ObjectValidatorNullable nullable() => ObjectValidatorNullable(transformer: transformer, defaultValue: defaultValue, allowedValues: allowedValues); - ObjectValidatorNullable withDefault(T? defaultValue) => - ObjectValidatorNullable(transformer: transformer, defaultValue: defaultValue, allowedValues: allowedValues); - - String get type => RegExp('(?<=<).+(?=>)').firstMatch(toString())!.group(0)!; - - @override - String toString() => runtimeType.toString(); - - @override - bool operator ==(Object other) => other is ObjectValidatorNullable; - - @override - int get hashCode => toString().hashCode; -} - -class ObjectValidator extends ObjectValidatorNullable { - const ObjectValidator({ - super.transformer, - super.defaultValue, - super.allowedValues, - }); - - /// Transforms the value if a transformer is provided, otherwise returns the value as is. - /// May throw an error if the value is not of the correct type. - /// To prevent an error, use isTypeOf before calling transform. - @override - T transform(dynamic value) { - try { - if (value == null) return defaultValue!; - if (transformer == null) return value is T ? value : defaultValue!; - return transformer!.call(value); - } catch (e) { - if (defaultValue != null) return defaultValue!; - throw LocalizedArgumentError( - localizedMessage: (localizations, value, name) => localizations.invalidValue(value.runtimeType.toString(), value, name), - unlocalizedMessage: 'The type ${value.runtimeType} for value "$value" is not valid.', - invalidValue: value, - name: type, - ); - } - } - - /// Checks if the value is of the correct type, or sub-type. - /// If the transformer is provided, the value will be transformed before checking the type. - @override - bool isTypeOf(dynamic value) { - Logger.debug('Checking type (${T.runtimeType}) of value "$value" and default value "$defaultValue" with transformer "$transformer".'); - if (value == null) return defaultValue != null; - if (transformer == null) return value is T; - try { - transformer!(value); - return true; - } catch (e) { - return false; - } - } - - @override - bool valueIsAllowed(dynamic value) { - if (!isTypeOf(value)) return false; - value ??= defaultValue; - if (value == null) return false; - if (allowedValues == null) return true; - return allowedValues!(transform(value)); - } -} diff --git a/lib/utils/object_validator.dart b/lib/utils/object_validator.dart new file mode 100644 index 000000000..2ef41e337 --- /dev/null +++ b/lib/utils/object_validator.dart @@ -0,0 +1,274 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; + +import '../model/enums/algorithms.dart'; +import '../model/enums/encodings.dart'; +import 'errors.dart'; +import 'logger.dart'; + +final otpAutjPeriodSecondsValidatorNullable = otpAutjPeriodSecondsValidator.nullable(); +final otpAutjPeriodSecondsValidator = ObjectValidator( + transformer: (v) => int.parse(v), + allowedValues: (v) => v > 0, +); + +final otpAuthDigitsValidatorNullable = otpAuthDigitsValidator.nullable(); +final otpAuthDigitsValidator = ObjectValidator( + transformer: (v) => int.parse(v), + defaultValue: 6, + allowedValues: (p0) => p0 > 0, +); +final otpAuthCounterValidator = ObjectValidator( + transformer: (v) => int.parse(v), + allowedValues: (v) => v >= 0, +); + +final stringToIntValidatorNullable = stringToIntvalidator.nullable(); +final stringToIntvalidator = ObjectValidator(transformer: (v) => int.parse(v)); + +final intToStringValidator = ObjectValidator(transformer: (v) => (v as int).toString()); +final intToStringValidatorNullable = intToStringValidator.nullable(); + +final stringSecondsToDurationValidatorNullable = stringSecondsToDurationValidator.nullable(); +final stringSecondsToDurationValidator = ObjectValidator( + transformer: (v) => Duration(seconds: int.parse(v)), + allowedValues: (v) => v.inSeconds > 0, +); + +final stringToUriValidatorNullable = stringToUrivalidator.nullable(); +final stringToUrivalidator = ObjectValidator(transformer: (v) => Uri.parse(v)); + +final stringToBoolValidatorNullable = stringToBoolValidator.nullable(); +final stringToBoolValidator = ObjectValidator( + transformer: (v) => switch ((v as String).toLowerCase()) { + 'true' => true, + '1' => true, + 'false' => false, + '0' => false, + _ => throw ArgumentError('Invalid boolean value: $v'), + }); + +final stringToAlgorithmsValidator = ObjectValidator( + transformer: (v) { + return Algorithms.values.byName((v as String).toUpperCase()); + }, +); +final stringToAlgorithmsValidatorNullable = stringToAlgorithmsValidator.nullable(); + +/// When value is given, it checks if the value is a base32 encoded string. +final base32SecretValidatorNullable = base32Secretvalidator.nullable(); + +/// Checks if the value is a base32 encoded string. +final base32Secretvalidator = ObjectValidator(transformer: (v) { + if (v is String) v = Encodings.base32.decode(v); + return Encodings.base32.encode(v); +}); + +T? validateOptional({required dynamic value, required ObjectValidatorNullable validator, required String name}) { + if (validator.isTypeOf(value)) { + return validator.transform(value); + } else { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, name) => localizations.invalidValue(value.runtimeType.toString(), value, name), + unlocalizedMessage: 'The ${value.runtimeType} "$value" is not valid for "$name"', + invalidValue: value, + name: name, + ); + } +} + +T validate({required dynamic value, required ObjectValidator validator, required String name}) { + if (validator.isTypeOf(value)) { + return validator.transform(value); + } else { + throw LocalizedArgumentError( + localizedMessage: (localizations, value, name) => localizations.invalidValue(value.runtimeType.toString(), value, name), + unlocalizedMessage: 'The ${value.runtimeType} "$value" is not valid for "$name"', + invalidValue: value, + name: name, + ); + } +} + +/// Validates a map by checking if it contains all required keys and if the values are of the correct type. +/// +/// Throws a [LocalizedArgumentError] if the map is invalid. +///
If the validator provides a transformer function, the value will be transformed before checking the type. +///
The returned map will contain the transformed values. +Map validateMap({required Map map, required Map> validators, required String? name}) { + Map validatedMap = {}; + for (String key in validators.keys) { + final validator = validators[key]!; + final mapEntry = map[key]; + if (validator.isTypeOf(mapEntry)) { + if (validator.valueIsAllowed(mapEntry)) { + final newValue = validator.transform(mapEntry); + if (newValue != null) validatedMap[key] = newValue; + } else { + throw LocalizedArgumentError( + localizedMessage: name != null + ? (localizations, value, key) => localizations.valueNotAllowedIn(value.runtimeType.toString(), value.toString(), key, name) + : (localizations, value, key) => localizations.valueNotAllowed(value.runtimeType.toString(), value.toString(), key), + unlocalizedMessage: 'The ${mapEntry.runtimeType} "$mapEntry" is not an allowed value for "$key"', + invalidValue: mapEntry.toString(), + name: key, + ); + } + } else { + if (mapEntry == null) { + throw LocalizedArgumentError( + localizedMessage: name != null + ? (localizations, value, key) => localizations.missingRequiredParameterIn(key, name) + : (localizations, value, key) => localizations.missingRequiredParameter(key), + unlocalizedMessage: 'Map does not contain required key "$key"', + invalidValue: mapEntry.toString(), + name: key, + ); + } + throw LocalizedArgumentError( + localizedMessage: name != null + ? (localizations, value, key) => localizations.invalidValueIn(value.runtimeType.toString(), value.toString(), key, name) + : (localizations, value, key) => localizations.invalidValue(value.runtimeType.toString(), value.toString(), key), + unlocalizedMessage: 'The ${mapEntry.runtimeType} "$mapEntry" is not valid for "$key"${name != null ? ' in $name' : ''}', + invalidValue: mapEntry.toString(), + name: key, + ); + } + } + return validatedMap; +} + +class ObjectValidatorNullable { + final T Function(dynamic value)? transformer; + final T? defaultValue; + final bool Function(T)? allowedValues; + + const ObjectValidatorNullable({ + this.transformer, + this.defaultValue, + this.allowedValues, + }); + + /// Checks if the value is of the correct type, or sub-type. + /// If the transformer is provided, the value will be transformed before checking the type. + bool isTypeOf(dynamic value) { + Logger.debug('Checking type (${T.runtimeType}) of nullable value "$value" and default value "$defaultValue" with transformer "$transformer".'); + if (value == null) return true; + if (transformer == null) return value is T?; + try { + transformer!(value); + return true; + } catch (e) { + return false; + } + } + + bool valueIsAllowed(dynamic value) { + if (!isTypeOf(value)) return false; + if (allowedValues == null) return true; + if (value == null) return true; + final transformedValue = transform(value); + if (transformedValue == null) return true; + if (allowedValues!(transformedValue)) return true; + return false; + } + + /// Transforms the value if a transformer is provided, otherwise returns the value as is. + /// May throw an error if the value is not of the correct type. + /// To prevent an error, use isTypeOf before calling transform. + T? transform(dynamic value) { + if (value == null) return defaultValue; + if (transformer == null) return value as T; + try { + return transformer!.call(value); + } catch (e) { + return defaultValue; + } + } + + ObjectValidatorNullable nullable() => ObjectValidatorNullable(transformer: transformer, defaultValue: defaultValue, allowedValues: allowedValues); + ObjectValidatorNullable withDefault(T? defaultValue) => + ObjectValidatorNullable(transformer: transformer, defaultValue: defaultValue, allowedValues: allowedValues); + + String get type => RegExp('(?<=<).+(?=>)').firstMatch(toString())!.group(0)!; + + @override + String toString() => runtimeType.toString(); + + @override + bool operator ==(Object other) => other is ObjectValidatorNullable; + + @override + int get hashCode => toString().hashCode; +} + +class ObjectValidator extends ObjectValidatorNullable { + const ObjectValidator({ + super.transformer, + super.defaultValue, + super.allowedValues, + }); + + /// Transforms the value if a transformer is provided, otherwise returns the value as is. + /// May throw an error if the value is not of the correct type. + /// To prevent an error, use isTypeOf before calling transform. + @override + T transform(dynamic value) { + try { + if (value == null) return defaultValue!; + if (transformer == null) return value is T ? value : defaultValue!; + return transformer!.call(value); + } catch (e) { + if (defaultValue != null) return defaultValue!; + throw LocalizedArgumentError( + localizedMessage: (localizations, value, name) => localizations.invalidValue(value.runtimeType.toString(), value, name), + unlocalizedMessage: 'The type ${value.runtimeType} for value "$value" is not valid.', + invalidValue: value, + name: type, + ); + } + } + + /// Checks if the value is of the correct type, or sub-type. + /// If the transformer is provided, the value will be transformed before checking the type. + @override + bool isTypeOf(dynamic value) { + Logger.debug('Checking type (${T.runtimeType}) of value "$value" and default value "$defaultValue" with transformer "$transformer".'); + if (value == null) return defaultValue != null; + if (transformer == null) return value is T; + try { + transformer!(value); + return true; + } catch (e) { + return false; + } + } + + @override + bool valueIsAllowed(dynamic value) { + if (!isTypeOf(value)) return false; + value ??= defaultValue; + if (value == null) return false; + if (allowedValues == null) return true; + return allowedValues!(transform(value)); + } +} diff --git a/lib/utils/object_validators.dart b/lib/utils/object_validators.dart deleted file mode 100644 index 0ed27eeef..000000000 --- a/lib/utils/object_validators.dart +++ /dev/null @@ -1,82 +0,0 @@ -/* - * privacyIDEA Authenticator - * - * Author: Frank Merkel - * - * Copyright (c) 2024 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:privacyidea_authenticator/model/extensions/enums/encodings_extension.dart'; - -import '../model/enums/algorithms.dart'; -import '../model/enums/encodings.dart'; -import 'identifiers.dart'; - -final otpAutjPeriodSecondsValidatorNullable = otpAutjPeriodSecondsValidator.nullable(); -final otpAutjPeriodSecondsValidator = ObjectValidator( - transformer: (v) => int.parse(v), - allowedValues: (v) => v > 0, -); - -final otpAuthDigitsValidatorNullable = otpAuthDigitsValidator.nullable(); -final otpAuthDigitsValidator = ObjectValidator( - transformer: (v) => int.parse(v), - defaultValue: 6, - allowedValues: (p0) => p0 > 0, -); -final otpAuthCounterValidator = ObjectValidator( - transformer: (v) => int.parse(v), - allowedValues: (v) => v >= 0, -); - -final stringToIntValidatorNullable = stringToIntvalidator.nullable(); -final stringToIntvalidator = ObjectValidator(transformer: (v) => int.parse(v)); - -final intToStringValidator = ObjectValidator(transformer: (v) => (v as int).toString()); -final intToStringValidatorNullable = intToStringValidator.nullable(); - -final stringSecondsToDurationValidatorNullable = stringSecondsToDurationValidator.nullable(); -final stringSecondsToDurationValidator = ObjectValidator( - transformer: (v) => Duration(seconds: int.parse(v)), - allowedValues: (v) => v.inSeconds > 0, -); - -final stringToUriValidatorNullable = stringToUrivalidator.nullable(); -final stringToUrivalidator = ObjectValidator(transformer: (v) => Uri.parse(v)); - -final stringToBoolValidatorNullable = stringToBoolValidator.nullable(); -final stringToBoolValidator = ObjectValidator( - transformer: (v) => switch ((v as String).toLowerCase()) { - 'true' => true, - '1' => true, - 'false' => false, - '0' => false, - _ => throw ArgumentError('Invalid boolean value: $v'), - }); - -final stringToAlgorithmsValidator = ObjectValidator( - transformer: (v) { - return Algorithms.values.byName((v as String).toUpperCase()); - }, -); -final stringToAlgorithmsValidatorNullable = stringToAlgorithmsValidator.nullable(); - -/// When value is given, it checks if the value is a base32 encoded string. -final base32SecretValidatorNullable = base32Secretvalidator.nullable(); - -/// Checks if the value is a base32 encoded string. -final base32Secretvalidator = ObjectValidator(transformer: (v) { - if (v is String) v = Encodings.base32.decode(v); - return Encodings.base32.encode(v); -}); diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart index caedcaefe..6799a058f 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart @@ -24,11 +24,9 @@ import 'package:basic_utils/basic_utils.dart'; import 'package:collection/collection.dart'; import 'package:http/http.dart'; import 'package:mutex/mutex.dart'; -import 'package:privacyidea_authenticator/model/extensions/enums/ec_key_algorithm_extension.dart'; import 'package:privacyidea_authenticator/model/processor_result.dart'; import 'package:privacyidea_authenticator/model/tokens/token.dart'; import 'package:privacyidea_authenticator/utils/globals.dart'; -import 'package:privacyidea_authenticator/utils/identifiers.dart'; import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; @@ -45,6 +43,7 @@ import '../../../../repo/secure_token_container_repository.dart'; import '../../../ecc_utils.dart'; import '../../../errors.dart'; import '../../../logger.dart'; +import '../../../object_validator.dart'; part 'token_container_notifier.g.dart'; @@ -95,7 +94,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler Logger.warning('Building containerProvider'); final initState = await _repo.loadContainerState(); - for (var container in initState.container.whereType()) { + for (var container in initState.containerList.whereType()) { finalize(container); } _stateMutex.release(); @@ -139,7 +138,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler Future addContainerList(List container) async { await _stateMutex.acquire(); final newCredentials = container.toList(); - final oldCredentials = (await future).container; + final oldCredentials = (await future).containerList; Logger.debug('Loaded container: $oldCredentials'); final combinedCredentials = []; for (var oldCredential in oldCredentials) { @@ -205,7 +204,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler Future deleteContainerList(List container) async { await _stateMutex.acquire(); final newCredentials = container.toList(); - final oldCredentials = (await future).container; + final oldCredentials = (await future).containerList; final combinedCredentials = []; for (var oldCredential in oldCredentials) { final newCredential = newCredentials.firstWhereOrNull((newCredential) => newCredential.serial == oldCredential.serial); @@ -235,7 +234,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler final containerCredentials = results.getData().whereType().toList(); if (containerCredentials.isEmpty) return null; final currentState = await future; - final stateCredentials = currentState.container; + final stateCredentials = currentState.containerList; final stateCredentialsSerials = stateCredentials.map((e) => e.serial); final newCredentials = containerCredentials.where((element) => !stateCredentialsSerials.contains(element.serial)).toList(); Logger.info('Handling processor results: adding Credential'); @@ -245,7 +244,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler for (var container in newCredentials) { Logger.info('Handling processor results: finalize check ()'); - if (!stateAfterAdding.container.contains(container)) { + if (!stateAfterAdding.containerList.contains(container)) { failedToAdd.add(container); continue; } @@ -308,7 +307,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler TokenContainerUnfinalized? container = containerCredential; container = await updateContainer(container, (c) => c.copyWith(finalizationState: ContainerFinalizationState.generatingKeyPair)); if (container == null) throw StateError('Credential was removed'); - final keyPair = CryptoUtils.generateEcKeyPair(curve: container.ecKeyAlgorithm.curveName) as AsymmetricKeyPair; + final keyPair = eccUtils.generateKeyPair(container.ecKeyAlgorithm); container = await updateContainer(container, (c) => c.withClientKeyPair(keyPair) as TokenContainerUnfinalized); if (container == null) throw StateError('Credential was removed'); return container; @@ -383,20 +382,30 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler } Future syncTokens(TokenState tokenState) async { - final containerCredentials = (await future).container; - final containerCredential = containerCredentials.whereType().first; - - List syncedTokens; - List deletedTokens; - final tuple = await _containerApi.sync( - containerCredential, - tokenState, - ); - if (tuple == null) { - return; + final containerList = (await future).containerList; + final finalizedList = containerList.whereType(); + final syncFutures = , List)?>>[]; + + List syncedTokens = []; + List deletedTokens = []; + + for (var finalized in finalizedList) { + syncFutures.add(_containerApi.sync( + finalized, + tokenState, + )); } - syncedTokens = tuple.$1; - deletedTokens = tuple.$2; + + await Future.wait(syncFutures).then((tuples) { + for (var tuple in tuples) { + if (tuple == null) continue; + syncedTokens.addAll(tuple.$1); + deletedTokens.addAll(tuple.$2); + } + }); + + // Do not remove tokens that are synced in any other container + deletedTokens.removeWhere((serial) => syncedTokens.any((token) => token.serial == serial)); await ref.read(tokenProvider.notifier).addOrReplaceTokens(syncedTokens); await ref.read(tokenProvider.notifier).removeTokensBySerials(deletedTokens); diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart index c985f1ab7..3d9990252 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.g.dart @@ -7,7 +7,7 @@ part of 'token_container_notifier.dart'; // ************************************************************************** String _$tokenContainerNotifierHash() => - r'9997634617c8165dd0df6aaba13e7fa1f80149ce'; + r'7b019c6efaf5726ef905f530d36138606357b9d6'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart index 4c6db03e8..6766eae3f 100644 --- a/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart +++ b/lib/utils/riverpod/riverpod_providers/generated_providers/token_notifier.g.dart @@ -6,7 +6,7 @@ part of 'token_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$tokenNotifierHash() => r'572081984e3a8431279f47717fdf5e0f07aaa652'; +String _$tokenNotifierHash() => r'5b96504fb46555718bb87fe90b58ecc6041d84b9'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 412ab243d..7cda8a77d 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -30,7 +30,6 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:privacyidea_authenticator/mains/main_netknights.dart'; import 'package:privacyidea_authenticator/model/extensions/sortable_list.dart'; -import 'package:privacyidea_authenticator/utils/identifiers.dart'; import 'package:privacyidea_authenticator/utils/logger.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/sortable_notifier.dart'; @@ -41,6 +40,7 @@ import '../model/token_folder.dart'; import '../model/tokens/token.dart'; import '../processors/scheme_processors/scheme_processor_interface.dart'; import 'customization/application_customization.dart' show ApplicationCustomization; +import 'object_validator.dart'; import 'riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart'; import 'riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import 'riverpod/riverpod_providers/state_providers/dragging_sortable_provider.dart'; diff --git a/lib/views/container_view/container_view.dart b/lib/views/container_view/container_view.dart index b4781d5b8..f338f7ff3 100644 --- a/lib/views/container_view/container_view.dart +++ b/lib/views/container_view/container_view.dart @@ -45,7 +45,7 @@ class ContainerView extends ConsumerView { @override Widget build(BuildContext context, WidgetRef ref) { - final container = ref.watch(tokenContainerProvider).whenOrNull(data: (data) => data.container) ?? []; + final container = ref.watch(tokenContainerProvider).whenOrNull(data: (data) => data.containerList) ?? []; return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.container), 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 23972133c..5bb60819c 100644 --- a/lib/views/import_tokens_view/pages/import_start_page.dart +++ b/lib/views/import_tokens_view/pages/import_start_page.dart @@ -24,7 +24,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_zxing/flutter_zxing.dart'; import 'package:flutter_zxing/flutter_zxing.dart' as zxing; import 'package:image_picker/image_picker.dart'; -import 'package:privacyidea_authenticator/utils/identifiers.dart'; import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../l10n/app_localizations.dart'; @@ -39,6 +38,7 @@ import '../../../processors/mixins/token_import_processor.dart'; import '../../../processors/scheme_processors/token_import_scheme_processors/token_import_scheme_processor_interface.dart'; import '../../../processors/token_import_file_processor/token_import_file_processor_interface.dart'; import '../../../utils/logger.dart'; +import '../../../utils/object_validator.dart'; import '../../qr_scanner_view/qr_scanner_view.dart'; import '../import_tokens_view.dart'; import 'import_encrypted_data_page.dart'; 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 442e5026a..8ae51ce70 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 @@ -36,6 +36,7 @@ class DragTargetDivider extends ConsumerStatefulWidget final double dividerBaseHeight; final double dividerExpandedHeight; final double bottomPaddingIfLast; + final double opacity; final bool isExpandalbe; final bool isLastDivider; @@ -47,6 +48,7 @@ class DragTargetDivider extends ConsumerStatefulWidget this.bottomPaddingIfLast = 0, this.dividerBaseHeight = 1.5, this.dividerExpandedHeight = 40, + this.opacity = 1, this.isExpandalbe = true, this.isLastDivider = false, }); @@ -100,15 +102,18 @@ class _DragTargetDividerState extends ConsumerState w } @override - ExpandablePanel build(BuildContext context) { + Widget build(BuildContext context) { final hidePushTokens = ref.watch(settingsProvider).whenOrNull(data: (data) => data.hidePushTokens) ?? SettingsState.hidePushTokensDefault; final tokens = ref.watch(tokenProvider).tokensInFolder(widget.folder, exclude: hidePushTokens ? [PushToken] : []); @@ -96,30 +96,60 @@ class _TokenFolderExpandableState extends ConsumerState w } else { expandableController.value = widget.expandOverride!; } - return ExpandablePanel( - theme: const ExpandableThemeData( - useInkWell: false, - hasIcon: false, - tapBodyToCollapse: false, - tapBodyToExpand: false, + final isExpanded = expandableController.value; + return Container( + padding: const EdgeInsets.all(2), + margin: const EdgeInsets.only(bottom: 8, left: 14), + decoration: BoxDecoration( + color: isExpanded ? Theme.of(context).scaffoldBackgroundColor : Colors.transparent, + borderRadius: isExpanded + ? const BorderRadius.only( + topLeft: Radius.circular(6), + bottomLeft: Radius.circular(6), + ) + : const BorderRadius.all(Radius.circular(6)), + boxShadow: [ + if (isExpanded) + BoxShadow( + color: Theme.of(context).shadowColor, + offset: const Offset(0, 2), + blurRadius: 4, + ), + ], ), - controller: expandableController, - header: TokenFolderExpandableHeader( - tokens: tokens, - expandableController: expandableController, - animationController: animationController, - expandOverride: widget.expandOverride, - folder: widget.folder, + child: ExpandablePanel( + theme: const ExpandableThemeData( + useInkWell: false, + hasIcon: false, + fadeCurve: InstantCurve(), + tapBodyToCollapse: false, + tapBodyToExpand: false, + ), + controller: expandableController, + header: TokenFolderExpandableHeader( + tokens: tokens, + expandableController: expandableController, + animationController: animationController, + expandOverride: widget.expandOverride, + folder: widget.folder, + ), + collapsed: const SizedBox(), + expanded: tokens.isEmpty || (tokens.length == 1 && tokens.first == draggingSortable) + ? const SizedBox() + : TokenFolderExpandableBody( + tokens: tokens, + draggingSortable: draggingSortable, + folder: widget.folder, + filter: widget.filter, + ), ), - collapsed: const SizedBox(), - expanded: tokens.isEmpty || (tokens.length == 1 && tokens.first == draggingSortable) - ? const SizedBox() - : TokenFolderExpandableBody( - tokens: tokens, - draggingSortable: draggingSortable, - folder: widget.folder, - filter: widget.filter, - ), ); } } + +class InstantCurve extends Curve { + const InstantCurve(); + + @override + double transformInternal(double t) => t < 1 ? 1 : 0; +} diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_body.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_body.dart index 0ecdfde6e..e9058afbb 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_body.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_body.dart @@ -43,45 +43,25 @@ class TokenFolderExpandableBody extends StatelessWidget { @override Widget build(BuildContext context) => Container( - margin: const EdgeInsets.only(left: 15, bottom: 10), - decoration: BoxDecoration( - color: Theme.of(context).focusColor, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(4), - ), - ), - child: Card( - color: Theme.of(context).scaffoldBackgroundColor, - //Only bottom left corner round the other corners sharp - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(4), - ), - ), - margin: const EdgeInsets.only( - left: 4, - bottom: 2, - ), - semanticContainer: false, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - for (var i = 0; i < tokens.length; i++) ...[ - if (draggingSortable != tokens[i] && (i != 0 || draggingSortable is Token)) - filter == null - ? DragTargetDivider( - dependingFolder: folder, - previousSortable: (i - 1) < 0 ? null : tokens[i - 1], - nextSortable: tokens[i], - ) - : const Divider(), - TokenWidgetBuilder.fromToken(tokens[i]), - ], - if (tokens.isNotEmpty && draggingSortable is Token) - filter == null ? DragTargetDivider(dependingFolder: folder, previousSortable: tokens.last, nextSortable: null) : const Divider(), - if (tokens.isNotEmpty && draggingSortable is! Token) const SizedBox(height: 8), + color: Theme.of(context).scaffoldBackgroundColor, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + for (var i = 0; i < tokens.length; i++) ...[ + if (draggingSortable != tokens[i] && (i != 0 || draggingSortable is Token)) + filter == null + ? DragTargetDivider( + dependingFolder: folder, + previousSortable: (i - 1) < 0 ? null : tokens[i - 1], + nextSortable: tokens[i], + ) + : const Divider(), + TokenWidgetBuilder.fromToken(tokens[i]), ], - ), + if (tokens.isNotEmpty && draggingSortable is Token) + filter == null ? DragTargetDivider(dependingFolder: folder, previousSortable: tokens.last, nextSortable: null) : const Divider(), + if (tokens.isNotEmpty && draggingSortable is! Token) const SizedBox(height: 8), + ], ), ); } diff --git a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header.dart b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header.dart index 89b428822..8b706d321 100644 --- a/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header.dart +++ b/lib/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header.dart @@ -68,20 +68,20 @@ class _TokenFolderExpandableHeaderState extends ConsumerState( onWillAcceptWithDetails: (details) { if (details.data.folderId != widget.folder.folderId) { @@ -105,68 +105,51 @@ class _TokenFolderExpandableHeaderState extends ConsumerState Center( - child: Container( - margin: widget.folder.isExpanded ? null : const EdgeInsets.only(right: 8), - padding: widget.folder.isExpanded ? const EdgeInsets.only(right: 8) : null, + child: SizedBox( height: 50, - decoration: BoxDecoration( - color: willAccept.isNotEmpty - ? Theme.of(context).dividerColor - : isExpanded - ? Theme.of(context).scaffoldBackgroundColor - : null, - borderRadius: BorderRadius.only( - topRight: widget.folder.isExpanded ? const Radius.circular(0) : const Radius.circular(8), - topLeft: const Radius.circular(8), - bottomRight: widget.folder.isExpanded ? const Radius.circular(0) : const Radius.circular(8), - bottomLeft: widget.folder.isExpanded ? const Radius.circular(0) : const Radius.circular(8), - ), - ), - child: Material( - // Material to draw on for the InkWell - color: Colors.transparent, - child: InkWell( - onTap: () async { - if (widget.expandOverride != null) return; - if (isExpanded) { - widget.expandableController.value = false; - return; - } - if (widget.tokens.isEmpty || (widget.tokens.length == 1 && widget.tokens.first == draggingSortable)) return; - if (widget.folder.isLocked && await lockAuth(localizedReason: AppLocalizations.of(context)!.expandLockedFolder) == false) { - return; - } - if (!mounted) return; - widget.expandableController.value = true; - }, - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - RotationTransition( - turns: Tween(begin: 0.25, end: 0.0).animate(widget.animationController), - child: SizedBox.square( - dimension: 25, - child: (widget.tokens.isEmpty || (widget.tokens.length == 1 && widget.tokens.first == draggingSortable)) - ? null - : const Icon(Icons.arrow_forward_ios_sharp), - )), - const SizedBox(width: 8), - Expanded( - flex: 2, - child: Text( - widget.folder.label, - style: Theme.of(context).textTheme.titleLarge, - overflow: TextOverflow.fade, - softWrap: false, - ), - ), - TokenFolderExpandableHeaderIcon( - showEmptyFolderIcon: (widget.tokens.isEmpty || (widget.tokens.length == 1 && widget.tokens.first == draggingSortable)), - isLocked: widget.folder.isLocked, - isExpanded: isExpanded, + child: DefaultInkWell( + highlight: willAccept.isNotEmpty, + onTap: () async { + if (widget.expandOverride != null) return; + if (isExpanded) { + widget.expandableController.value = false; + return; + } + if (widget.tokens.isEmpty || (widget.tokens.length == 1 && widget.tokens.first == draggingSortable)) return; + if (widget.folder.isLocked && await lockAuth(localizedReason: AppLocalizations.of(context)!.expandLockedFolder) == false) { + return; + } + if (!mounted) return; + widget.expandableController.value = true; + }, + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + const SizedBox(width: 8), + RotationTransition( + turns: Tween(begin: 0.25, end: 0.0).animate(widget.animationController), + child: SizedBox.square( + dimension: 25, + child: (widget.tokens.isEmpty || (widget.tokens.length == 1 && widget.tokens.first == draggingSortable)) + ? null + : const Icon(Icons.arrow_forward_ios_sharp), + )), + const SizedBox(width: 8), + Expanded( + flex: 2, + child: Text( + widget.folder.label, + style: Theme.of(context).textTheme.titleLarge, + overflow: TextOverflow.fade, + softWrap: false, ), - ], - ), + ), + TokenFolderExpandableHeaderIcon( + showEmptyFolderIcon: (widget.tokens.isEmpty || (widget.tokens.length == 1 && widget.tokens.first == draggingSortable)), + isLocked: widget.folder.isLocked, + isExpanded: isExpanded, + ), + ], ), ), ), @@ -176,3 +159,40 @@ class _TokenFolderExpandableHeaderState extends ConsumerState asd = [5]; + asd.cast(); + + return Material( + // Material to draw on for the InkWell + color: Colors.transparent, + child: Container( + decoration: BoxDecoration( + color: highlight ? Theme.of(context).dividerColor : null, + borderRadius: const BorderRadius.all(Radius.circular(4)), + ), + child: InkWell( + customBorder: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + onTap: onTap, + child: child, + ), + ), + ); + } +} 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 887bb3444..b767b91a3 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 @@ -54,13 +54,23 @@ class MainViewTokensList extends ConsumerStatefulWidget { final isFirst = i == 0; final isDraggingTheCurrent = draggingSortable == sortables[i]; final previousWasExpandedFolder = i > 0 && sortables[i - 1] is TokenFolder && (sortables[i - 1] as TokenFolder).isExpanded; + final currentIsExpandedFolder = sortables[i] is TokenFolder && (sortables[i] as TokenFolder).isExpanded; // 1. Add a divider if the current sortable is not the one which is dragged - // 2. Dont add a divider if the current sortable is the first - // 3. Dont add a divider if the previous sortable was an expanded folder + // 2. Don't add a divider if the current sortable is the first + // 3. Don't add a divider after an expanded folder // 4. Ignore 2. and 3. if there is a sortable that is dragged - // 1 2 3 4 + // 5. Do not add a divider before a folder + // 1 2 3 4 if (!isDraggingTheCurrent && ((!isFirst && !previousWasExpandedFolder) || draggingSortable != null)) { - widgets.add(DragTargetDivider(dependingFolder: null, previousSortable: i == 0 ? null : sortables.elementAtOrNull(i - 1), nextSortable: sortables[i])); + widgets.add( + DragTargetDivider( + // The divider should be invisible if the upcoming folder is expanded + opacity: currentIsExpandedFolder ? 0 : 1, + dependingFolder: null, + previousSortable: i == 0 ? null : sortables.elementAtOrNull(i - 1), + nextSortable: sortables[i], + ), + ); } if (introductionAdded == false && sortables[i] is Token) { widgets.add( @@ -111,22 +121,16 @@ class _MainViewTokensListState extends ConsumerState { children: [ Flexible( child: DefaultRefreshIndicator( - allowToRefresh: allowToRefresh, - child: LayoutBuilder( - builder: (context, constraints) => SingleChildScrollView( - physics: _getScrollPhysics(allowToRefresh), - child: SizedBox( - height: constraints.maxHeight, - child: Opacity( - opacity: 0, - child: DragTargetDivider( - dependingFolder: null, - previousSortable: showSortables.last, - nextSortable: null, - isLastDivider: true, - bottomPaddingIfLast: 0, - ), - ), + child: SizedBox( + height: 9999, + child: Opacity( + opacity: 0, + child: DragTargetDivider( + dependingFolder: null, + previousSortable: showSortables.last, + nextSortable: null, + isLastDivider: true, + bottomPaddingIfLast: 0, ), ), ), @@ -135,31 +139,27 @@ class _MainViewTokensListState extends ConsumerState { ], ), DefaultRefreshIndicator( - allowToRefresh: allowToRefresh, + listViewKey: listViewKey, + scrollController: scrollController, child: SlidableAutoCloseBehavior( child: DragItemScroller( listViewKey: listViewKey, itemIsDragged: draggingSortable != null, scrollController: scrollController, - child: SingleChildScrollView( - key: listViewKey, - physics: _getScrollPhysics(allowToRefresh), - controller: scrollController, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ...MainViewTokensList.buildSortableWidgets(showSortables, draggingSortable), - (draggingSortable != null) - ? DragTargetDivider( - dependingFolder: null, - previousSortable: showSortables.last, - nextSortable: null, - isLastDivider: true, - bottomPaddingIfLast: 80, - ) - : const SizedBox(height: 80), - ], - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ...MainViewTokensList.buildSortableWidgets(showSortables, draggingSortable), + (draggingSortable != null) + ? DragTargetDivider( + dependingFolder: null, + previousSortable: showSortables.last, + nextSortable: null, + isLastDivider: true, + bottomPaddingIfLast: 80, + ) + : const SizedBox(height: 80), + ], ), ), ), diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart index 572fdf56b..49488a8bf 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart @@ -21,6 +21,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/folder_widgets/token_folder_expandable_widgets/token_folder_expandable_header.dart'; import '../../../../model/mixins/sortable_mixin.dart'; import '../../../../model/tokens/token.dart'; @@ -104,12 +105,18 @@ class TokenWidgetBase extends ConsumerWidget { ), data: token, child: ClipRRect( - child: PiSlideable( - groupTag: TokenWidget.groupTag, - identifier: token.id, - actions: actions, - stack: stack, - tile: tile, + child: Material( + color: Colors.transparent, + child: DefaultInkWell( + onTap: () {}, + child: PiSlideable( + groupTag: TokenWidget.groupTag, + identifier: token.id, + actions: actions, + stack: stack, + tile: tile, + ), + ), ), ), ) diff --git a/lib/views/push_token_view/widgets/push_tokens_view_list.dart b/lib/views/push_token_view/widgets/push_tokens_view_list.dart index 28283625f..4a68558c9 100644 --- a/lib/views/push_token_view/widgets/push_tokens_view_list.dart +++ b/lib/views/push_token_view/widgets/push_tokens_view_list.dart @@ -52,7 +52,8 @@ class _PushTokensViwListState extends ConsumerState { return Stack( children: [ DefaultRefreshIndicator( - allowToRefresh: allowToRefresh, + listViewKey: listViewKey, + scrollController: scrollController, child: SlidableAutoCloseBehavior( child: DragItemScroller( listViewKey: listViewKey, diff --git a/lib/views/settings_view/settings_groups/settings_group_container.dart b/lib/views/settings_view/settings_groups/settings_group_container.dart index 17cf7234c..5ee7254d5 100644 --- a/lib/views/settings_view/settings_groups/settings_group_container.dart +++ b/lib/views/settings_view/settings_groups/settings_group_container.dart @@ -33,7 +33,7 @@ class SettingsGroupContainer extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) => SettingsGroup( title: AppLocalizations.of(context)!.container, onPressed: () => Navigator.of(context).pushNamed(ContainerView.routeName), - isActive: ref.watch(tokenContainerProvider).whenOrNull(data: (data) => data)?.container.isNotEmpty ?? false, + isActive: ref.watch(tokenContainerProvider).whenOrNull(data: (data) => data)?.containerList.isNotEmpty ?? false, trailingIcon: Icons.arrow_forward_ios, // TODO: Change to container icon when we have one ); } diff --git a/lib/views/splash_screen/splash_screen.dart b/lib/views/splash_screen/splash_screen.dart index 8a644bfbc..48df91728 100644 --- a/lib/views/splash_screen/splash_screen.dart +++ b/lib/views/splash_screen/splash_screen.dart @@ -19,6 +19,7 @@ */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import '../../model/enums/app_feature.dart'; import '../../utils/app_info_utils.dart'; @@ -82,6 +83,8 @@ class _SplashScreenState extends ConsumerState { return []; }).then((values) async { if (!mounted) return; + final tokenState = ref.read(tokenProvider); + ref.read(tokenContainerProvider.notifier).syncTokens(tokenState); return _navigate(); }); }); diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index ca9c53bc5..c840ee695 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -82,7 +82,7 @@ class _AppWrapperState extends ConsumerState<_AppWrapper> { @override Widget build(BuildContext context) { - final container = ref.watch(tokenContainerProvider).value?.container ?? []; + final container = ref.watch(tokenContainerProvider).value?.containerList ?? []; Logger.debug('Credentials: $container'); return SingleTouchRecognizer( child: StateObserver( diff --git a/lib/widgets/default_refresh_indicator.dart b/lib/widgets/default_refresh_indicator.dart index 7a3cc929c..c5ec26027 100644 --- a/lib/widgets/default_refresh_indicator.dart +++ b/lib/widgets/default_refresh_indicator.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; -import '../utils/logger.dart'; import '../utils/push_provider.dart'; import '../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import '../views/main_view/main_view_widgets/loading_indicator.dart'; @@ -9,12 +9,14 @@ import 'deactivateable_refresh_indicator.dart'; class DefaultRefreshIndicator extends ConsumerStatefulWidget { final Widget child; - final bool allowToRefresh; + final GlobalKey? listViewKey; + final ScrollController? scrollController; const DefaultRefreshIndicator({ super.key, + this.listViewKey, + this.scrollController, required this.child, - required this.allowToRefresh, }); @override @@ -24,24 +26,35 @@ class DefaultRefreshIndicator extends ConsumerStatefulWidget { class _DefaultRefreshIndicatorState extends ConsumerState { bool isRefreshing = false; @override - Widget build(BuildContext context) => DeactivateableRefreshIndicator( - onRefresh: () async { - setState(() { - isRefreshing = true; - }); - final future = LoadingIndicator.show(context, () async { - final pushProviderInstance = PushProvider.instance; - final container = (await ref.read(tokenContainerProvider.future)).container; - Logger.debug('Refreshing container with ${container.length} container'); - await Future.wait([ - if (pushProviderInstance != null) pushProviderInstance.pollForChallenges(isManually: true), - // for (var container in container) (ref.read(tokenContainerNotifierProviderOf(container: container).notifier).sync()), - ]); - }); - await future; - if (mounted) setState(() => isRefreshing = false); - }, - allowToRefresh: widget.allowToRefresh && !isRefreshing, + Widget build(BuildContext context) { + final allowToRefresh = ref.watch(tokenProvider).hasRolledOutPushTokens || ref.watch(tokenContainerProvider).value?.hasFinalizedContainers == true; + return DeactivateableRefreshIndicator( + onRefresh: () async { + setState(() { + isRefreshing = true; + }); + final future = LoadingIndicator.show(context, () async { + final pushProviderInstance = PushProvider.instance; + final tokenState = ref.read(tokenProvider); + await Future.wait([ + if (pushProviderInstance != null) pushProviderInstance.pollForChallenges(isManually: true), + ref.read(tokenContainerProvider.notifier).syncTokens(tokenState), + // for (var container in container) (ref.read(tokenContainerNotifierProviderOf(container: container).notifier).sync()), + ]); + }); + await future; + if (mounted) setState(() => isRefreshing = false); + }, + allowToRefresh: allowToRefresh && !isRefreshing, + child: SingleChildScrollView( + key: widget.listViewKey, + physics: _getScrollPhysics(allowToRefresh), + controller: widget.scrollController, child: widget.child, - ); + ), + ); + } + + ScrollPhysics _getScrollPhysics(bool allowToRefresh) => + allowToRefresh ? const AlwaysScrollableScrollPhysics(parent: ClampingScrollPhysics()) : const BouncingScrollPhysics(); } From 7cec7a8a77e933c8c335fd76419a2d8d1dec6a68 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:25:49 +0200 Subject: [PATCH 051/285] container --- lib/views/container_view/container_view.dart | 108 +----------------- .../delete_container_action.dart | 57 +++++++++ .../edit_container_action.dart | 55 +++++++++ .../container_widgets/container_widget.dart | 76 ++++++++++++ 4 files changed, 189 insertions(+), 107 deletions(-) create mode 100644 lib/views/container_view/container_widgets/container_actions/delete_container_action.dart create mode 100644 lib/views/container_view/container_widgets/container_actions/edit_container_action.dart create mode 100644 lib/views/container_view/container_widgets/container_widget.dart diff --git a/lib/views/container_view/container_view.dart b/lib/views/container_view/container_view.dart index f338f7ff3..d61b47c73 100644 --- a/lib/views/container_view/container_view.dart +++ b/lib/views/container_view/container_view.dart @@ -20,18 +20,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/main_view_navigation_buttons/qr_scanner_button.dart'; -import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/token_widget_tile.dart'; import '../../l10n/app_localizations.dart'; -import '../../model/token_container.dart'; -import '../../utils/customization/theme_extentions/action_theme.dart'; import '../../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; -import '../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; -import '../../widgets/pi_slideable.dart'; -import '../main_view/main_view_widgets/token_widgets/slideable_action.dart'; import '../view_interface.dart'; +import 'container_widgets/container_widget.dart'; const String groupTag = 'container-actions'; @@ -63,103 +57,3 @@ class ContainerView extends ConsumerView { ); } } - -class ContainerWidget extends ConsumerWidget { - final TokenContainer containerCredential; - - final List stack; - - const ContainerWidget({ - required this.containerCredential, - this.stack = const [], - super.key, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) => ClipRRect( - child: PiSlideable( - groupTag: groupTag, - identifier: containerCredential.serial, - actions: [ - DeleteContainerAction(container: containerCredential, key: Key('${containerCredential.serial}-DeleteContainerAction')), - EditContainerAction(container: containerCredential, key: Key('${containerCredential.serial}-EditContainerAction')), - ], - stack: stack, - tile: TokenWidgetTile( - title: Text(containerCredential.serial), - subtitles: [ - 'issuer: ${containerCredential.issuer}', - 'finalizationState: ${containerCredential.finalizationState.name}', - ], - trailing: containerCredential is TokenContainerFinalized - ? IconButton( - icon: const Icon(Icons.sync), - onPressed: () { - final tokenState = ref.read(tokenProvider); - ref.read(tokenContainerProvider.notifier).syncTokens(tokenState); - }, - ) - : IconButton( - icon: const Icon(Icons.delete), - onPressed: () { - ref.read(tokenContainerProvider.notifier).deleteContainer(containerCredential); - }, - ), - ), - ), - ); -} - -class DeleteContainerAction extends PiSlideableAction { - final TokenContainer container; - - const DeleteContainerAction({ - required this.container, - super.key, - }); - - @override - CustomSlidableAction build(BuildContext context, WidgetRef ref) => CustomSlidableAction( - onPressed: (BuildContext context) => ref.read(tokenContainerProvider.notifier).deleteContainer(container), - backgroundColor: Theme.of(context).extension()!.deleteColor, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon(Icons.delete_forever), - Text( - AppLocalizations.of(context)!.delete, - overflow: TextOverflow.fade, - softWrap: false, - ), - ], - ), - ); -} - -class EditContainerAction extends PiSlideableAction { - final TokenContainer container; - - const EditContainerAction({ - required this.container, - super.key, - }); - - @override - CustomSlidableAction build(BuildContext context, WidgetRef ref) => CustomSlidableAction( - onPressed: (BuildContext context) {}, - backgroundColor: Theme.of(context).extension()!.editColor, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon(Icons.edit), - Text( - AppLocalizations.of(context)!.edit, - overflow: TextOverflow.fade, - softWrap: false, - ), - ], - ), - ); -} diff --git a/lib/views/container_view/container_widgets/container_actions/delete_container_action.dart b/lib/views/container_view/container_widgets/container_actions/delete_container_action.dart new file mode 100644 index 000000000..8db6bd86f --- /dev/null +++ b/lib/views/container_view/container_widgets/container_actions/delete_container_action.dart @@ -0,0 +1,57 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; + +import '../../../../l10n/app_localizations.dart'; +import '../../../../model/token_container.dart'; +import '../../../../utils/customization/theme_extentions/action_theme.dart'; +import '../../../../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; +import '../../../main_view/main_view_widgets/token_widgets/slideable_action.dart'; +import '../../../view_interface.dart'; + +class DeleteContainerAction extends PiSlideableAction { + final TokenContainer container; + + const DeleteContainerAction({ + required this.container, + super.key, + }); + + @override + CustomSlidableAction build(BuildContext context, WidgetRef ref) => CustomSlidableAction( + onPressed: (BuildContext context) => ref.read(tokenContainerProvider.notifier).deleteContainer(container), + backgroundColor: Theme.of(context).extension()!.deleteColor, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon(Icons.delete_forever), + Text( + AppLocalizations.of(context)!.delete, + overflow: TextOverflow.fade, + softWrap: false, + ), + ], + ), + ); + void _showDeleteDialog(BuildContext context, WidgetRef ref) {} +} diff --git a/lib/views/container_view/container_widgets/container_actions/edit_container_action.dart b/lib/views/container_view/container_widgets/container_actions/edit_container_action.dart new file mode 100644 index 000000000..bd6c1818e --- /dev/null +++ b/lib/views/container_view/container_widgets/container_actions/edit_container_action.dart @@ -0,0 +1,55 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; + +import '../../../../l10n/app_localizations.dart'; +import '../../../../model/token_container.dart'; +import '../../../../utils/customization/theme_extentions/action_theme.dart'; +import '../../../main_view/main_view_widgets/token_widgets/slideable_action.dart'; +import '../../../view_interface.dart'; + +class EditContainerAction extends PiSlideableAction { + final TokenContainer container; + + const EditContainerAction({ + required this.container, + super.key, + }); + + @override + CustomSlidableAction build(BuildContext context, WidgetRef ref) => CustomSlidableAction( + onPressed: (BuildContext context) {}, + backgroundColor: Theme.of(context).extension()!.editColor, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon(Icons.edit), + Text( + AppLocalizations.of(context)!.edit, + overflow: TextOverflow.fade, + softWrap: false, + ), + ], + ), + ); +} diff --git a/lib/views/container_view/container_widgets/container_widget.dart b/lib/views/container_view/container_widgets/container_widget.dart new file mode 100644 index 000000000..002840870 --- /dev/null +++ b/lib/views/container_view/container_widgets/container_widget.dart @@ -0,0 +1,76 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 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/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../model/token_container.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; +import '../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; +import '../../../widgets/pi_slideable.dart'; +import '../../main_view/main_view_widgets/token_widgets/token_widget_tile.dart'; +import '../container_view.dart'; +import 'container_actions/delete_container_action.dart'; +import 'container_actions/edit_container_action.dart'; + +class ContainerWidget extends ConsumerWidget { + final TokenContainer containerCredential; + + final List stack; + + const ContainerWidget({ + required this.containerCredential, + this.stack = const [], + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) => ClipRRect( + child: PiSlideable( + groupTag: groupTag, + identifier: containerCredential.serial, + actions: [ + DeleteContainerAction(container: containerCredential, key: Key('${containerCredential.serial}-DeleteContainerAction')), + EditContainerAction(container: containerCredential, key: Key('${containerCredential.serial}-EditContainerAction')), + ], + stack: stack, + tile: TokenWidgetTile( + title: Text(containerCredential.serial), + subtitles: [ + 'issuer: ${containerCredential.issuer}', + 'finalizationState: ${containerCredential.finalizationState.name}', + ], + trailing: containerCredential is TokenContainerFinalized + ? IconButton( + icon: const Icon(Icons.sync), + onPressed: () { + final tokenState = ref.read(tokenProvider); + ref.read(tokenContainerProvider.notifier).syncTokens(tokenState); + }, + ) + : IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + ref.read(tokenContainerProvider.notifier).deleteContainer(containerCredential); + }, + ), + ), + ), + ); +} From eb44153fd9500ba4f78e576b46391139a2a5a7c2 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:07:18 +0200 Subject: [PATCH 052/285] monochrome icon --- .../res/drawable-hdpi/ic_launcher_monochrome.png | Bin 0 -> 544 bytes .../res/drawable-mdpi/ic_launcher_monochrome.png | Bin 0 -> 343 bytes .../drawable-xhdpi/ic_launcher_monochrome.png | Bin 0 -> 807 bytes .../drawable-xxhdpi/ic_launcher_monochrome.png | Bin 0 -> 1353 bytes .../drawable-xxxhdpi/ic_launcher_monochrome.png | Bin 0 -> 2141 bytes .../res/mipmap-anydpi-v26/ic_launcher.xml | 11 ++++++++++- .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 +++++- flutter_launcher_icons-netknights.yaml | 3 ++- ios/Runner.xcodeproj/project.pbxproj | 4 ++-- .../AppIcon-netknights.appiconset/Contents.json | 2 +- .../Contents.json | 2 +- .../netknights/ic_launcher_monochrome.png | Bin 0 -> 3394 bytes pubspec.lock | 8 ++++++++ pubspec.yaml | 3 +-- 14 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 android/app/src/netknights/res/drawable-hdpi/ic_launcher_monochrome.png create mode 100644 android/app/src/netknights/res/drawable-mdpi/ic_launcher_monochrome.png create mode 100644 android/app/src/netknights/res/drawable-xhdpi/ic_launcher_monochrome.png create mode 100644 android/app/src/netknights/res/drawable-xxhdpi/ic_launcher_monochrome.png create mode 100644 android/app/src/netknights/res/drawable-xxxhdpi/ic_launcher_monochrome.png create mode 100644 launcher_icons/netknights/ic_launcher_monochrome.png diff --git a/android/app/src/netknights/res/drawable-hdpi/ic_launcher_monochrome.png b/android/app/src/netknights/res/drawable-hdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000000000000000000000000000000000000..c606eb0f9294303d4b1ea1c75bbbc9d71fa3f4fa GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^i$Iuz8AyKBd8P@Z*aCb)T!AzYEKT2e9!N2j1o;L3 zXL!2Zz>9%_@wlgpV@L(#+gk_y4hIN0U;Omno>!=8Q^S#O6-+MIU)%j&F)?cC*~d@M zKmT~U;j=>yOOmpHbmI+ds4z;UZ0Wsa z?lRt!H+D!^KAt;gg>yT1@FT@7mt!-3=B&PD=QTUk8tR5cf~Vz=#kKf;-+klEoE#*1Uc1=&w5cvG_`%V$?m(zgeBAu2k5Q%oin zo^5%d!7h60#Kgc#@wDSxkKgih+b1i0UL$;?htHxl+DBZIdv~Nw*=G7^Qs8W{PHnfs zNL4+FtBErs%unsM+SqLpCYyBeMNiZbu}iZ}Iz^XQW(Y6$sMk~An{qMCt!K*103Va7 t%~Lr8OZ?c69Z3%j|Mn(V^erpa2w2bR>zB)-#W)8P(Vnh;F6*2UngC_Z-e~{; literal 0 HcmV?d00001 diff --git a/android/app/src/netknights/res/drawable-mdpi/ic_launcher_monochrome.png b/android/app/src/netknights/res/drawable-mdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000000000000000000000000000000000000..6e0408967803e729c36bb03e6508a487b3c17e14 GIT binary patch literal 343 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz3?z5#SpFYKu?6^qxB_V)Sem}`Jdk243Gxg6 z&+v4+fftbX&(p;*q=ND7^@Ci^4g$;pfB(;aw{Ut>+N3i&-8Y_Om{*EurS=ym_az)` zV&xXoiP*6JNMHW0$s1x5%}!RyO!ip2Fsb5zWp<*y^QoDpGbSY*oR*L*kgK#NeUf3* z%SD?OCmgxL&hy6UR;aYJ%8hP=&$-j$*gn*_IP%%dbw4H+!T-0LQ|!&e8+VVpZc9qv zAbO*^UIA)T=hv-sC0>5~eKYLLg$VDR&&t-AEh}_#-g}^WO8ebAx-wj~tULMLt`zh7 z@Z55_;nF-=KstkW&Dw_tnOB zpW*3t11})woTrOpNCo5D+XsV`9eJ29Jo#T=wc5zJVQre^=}mWMO+AK-zt_CojS@JZZB>1i*E-HcL?vHT_qLS zY+XE*E@n|LmVRr#pFVYAM} zwp^jZJhxTa{=VN3n=$=*T)Xe<2 z2L5SuYKE zdK0^s&*VFN;g0CEmnLTnl6a!`zSVF{zVRy8om1*?SiZH_1&M7{w*#AwZMb^7PspTg z?Y*)o3k-9F=hYba01}n}5c|{V6t5%Ujr! zFM5djNE|$7EkFCyZXM^va?1}VNCAWGm}=yY_S5!~ZAbqJx76gz-{|jNpuKg&!TNig z?|Kd$OSw2xYr}^#203kF@|*|MQfxPrNg4?@m7dPyIxLp7!B1)jf9uwh7b*?bbh7n( zA9$5HGgC9dP}fw4Q+tVO`qD(#EnhU4)-<@ym||Zjcgypu%At8G(z!gZFI497U!BZX zJ$H_oto99d`*(b^`WRKi_lIvu+_!a_IS-*!m&g5^S@3grPRixrR#1BPboFyt=akR{ E0Hh;r#Q*>R literal 0 HcmV?d00001 diff --git a/android/app/src/netknights/res/drawable-xxhdpi/ic_launcher_monochrome.png b/android/app/src/netknights/res/drawable-xxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000000000000000000000000000000000000..1d82b1f10b0b17f17903206b11dcb3387ff50303 GIT binary patch literal 1353 zcmeAS@N?(olHy`uVBq!ia0y~yU~~at4rZW8hROCXK#DEEC&U#<1Hsbto#%lRQ%R6t z@PCG<+YP)J7+5}dx;TbZFuuM0+Dkc5fZ^ep-}}Ej@)0_$wEAPn{c~Y|xBGu*S8A!S z&e<9LmC0H~X+npC5>qF~AYgcRY_Gmzq(_B_{g=I+fClye8ZG;NoZZFMYdM zEi>icqtmw!zqR9ES^cZ3e$Ruz{vR2O?rv7gj`3yrv*M!G`C|`kvvyos%vkmPSDu-M zV519*kf4W)0(QnDf%cjPDZ#IMSIjg#uH11|BwLW-zvaV7%Oq2VAj71uKLeDQLY2bq zpHo$u*x``cakYJMCr3z2M6KEE4i}Q5+E-9K?eX1xvE`}X4xQ&S`h&|!G5UG2g03~{ z*xifAD}<%el+1!fAYoCZ#P|EO(xa;Ty4sKUxnj&jHcnmC;gBq7t=YwKMYQnJ`{F07 z4tC^3{SE7EeFppcCE`SNQb+XV*jNXA0A_ zj=i7W60u^&hI^k%?}{34^LAcScqTG8|HzWF*YvBM4QCqWu8%nqa%|7qhSm3ieUf}) zHRTNt8dl3bTJT-eS@QFA$8$ehlcpq}R4eUJ>@m$YnEoe7aEeas0-1>xi@%j}6$XC% zeq6-%_jj=)6PN@4T+IAn9j(0Vs7$|)YuU-u36@Ly}0;fL-B*28?!HFK0m^EQT_1e zb2INA=3mA>&$;^e{ceAo49>y@bDdlz|Ttzz){i@e;sOw!&YSFL_@ zj%RjFZ+^&wsfCNz|BU6bH7fe?)u}tlpJ(pBij?$&%A6a+3?J_Kp)RMNt5uPrB=@r~ zvpm(dWbd}6iAImUKJlx&?z;2*UHQ9R8#q4JR0tpVJgv(;exCff^8Jh6y?o9!`R_XJ vuKV?wbM9_7>)vB>^j+lsUN$wF6gBk>tEw#3BhB<#K_#N6tDnm{r-UW|Zz>YW literal 0 HcmV?d00001 diff --git a/android/app/src/netknights/res/drawable-xxxhdpi/ic_launcher_monochrome.png b/android/app/src/netknights/res/drawable-xxxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000000000000000000000000000000000000..7fc4f99046078871b4cbd09f78d8e9539a127ad3 GIT binary patch literal 2141 zcmdT`X;2af6s9sXQ#-K4E5_|$E6tV) z?p#~>$mOtFmVyT_2GPdnp&qIQoB-5$mB#fVw|Xr6&R~q~8y2?<3s=0dsG6fKNTma;s1}wVJM_}j0a_JL7Wi>OZ~9zWlxmlk9$Ux zR=6&kDq^G6Nn)=xbjDmc=WJbrpGj~*j|c`}2rQt7S~IETq(D_S-(E`;Fz;ZTAyXS8@cW5*im_J+J&FP+d!V#>#o`b+ZJXC@fu zaNk>Y7S-j^zNkga+xWnnlFCu$#@&l|If?1z!{hv-vL`O;`AMiGc(qx5n_`9_r9E7| z&n8_h<-$*lxOnh!kjILm(yA@p`XT%tfAI!-Tk#!G_H2aO8s29b$nk zDItGXWBKKk=XEbW0%~l&Z2Z>}#MK_$T=;iH$+pW1qk$LVH9=Y0AW?WKaJSiEU4o&P z;dRd72AvOPnEG1rnnya)hqSWHjg$@II-^Sp0rz@aB;q*&T!U7$o6AJ0 zo0->MOp-7*;AiNiD!XmDVKhxguK{m=?`qVJs)0tdhHNgwEOV;ILlOJ^jx%5R(?LXS zOkmon_jybsCeMs+N}P$wNka;LQXXW!o3h-JmaPpI(}`tq=<0SvcyN) zrfAj~=f2OE5O(r8VB56!qw0d5%secE!cBSWDj`L%#jFWaP)G!4lr^~(bV!HHDTBi% zu2JnN{7scsNxZwF%AsJlv0`W8>7t_KHdjJo6B8!=;hu6ZKY=)4jJIPQ67{~e?82mo z8Y-UZ7dOgSm%UL6mn;3$iP(KulAI-d*V4%z9;YtRMTHrxX-g7;?6{WH;{xBQcsQ15 z><+0GKd9+tDA`u@=~zz!MASGC(p#@$mp)TOClq0kt??~qrskUDnhezZ3yKht?60bm zd(*e)hSD@aQa$fIQn-K==rQ`p{S7t+GL@J|_DCVC!jk(IoNf5FiEff{3QhVhWONY;WGaMtAyc9 literal 0 HcmV?d00001 diff --git a/android/app/src/netknights/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/netknights/res/mipmap-anydpi-v26/ic_launcher.xml index 5f349f7f4..d5063661b 100644 --- a/android/app/src/netknights/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/android/app/src/netknights/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,14 @@ - + + + + + + diff --git a/android/app/src/netknights_debug/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/netknights_debug/res/mipmap-anydpi-v26/ic_launcher.xml index 5f349f7f4..c79c58a32 100644 --- a/android/app/src/netknights_debug/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/android/app/src/netknights_debug/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,9 @@ - + + + diff --git a/flutter_launcher_icons-netknights.yaml b/flutter_launcher_icons-netknights.yaml index 67737b14a..38ec2d0e6 100644 --- a/flutter_launcher_icons-netknights.yaml +++ b/flutter_launcher_icons-netknights.yaml @@ -1,9 +1,10 @@ flutter_launcher_icons: android: true - image_path_android: "launcher_icons/netknights/ic_launcher_android.png" min_sdk_android: 21 # android min sdk min:16, default 21 + image_path_android: "launcher_icons/netknights/ic_launcher_android.png" adaptive_icon_background: "#FFFFFF" adaptive_icon_foreground: "launcher_icons/netknights/ic_launcher_adaptive.png" + adaptive_icon_monochrome: "launcher_icons/netknights/ic_launcher_monochrome.png" ios: true image_path_ios: "launcher_icons/netknights/ic_launcher_ios.png" remove_alpha_ios: false # default false x \ No newline at end of file diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 2636604a2..d55a03e7b 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -626,7 +626,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DD06BE5D70E55B7EDE1DFEAC /* Pods-Runner.debug-netknights_debug.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-netknights_debug"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-netknights_debug; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -717,7 +717,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 31A644BB097DC1A31854E0BC /* Pods-Runner.release-netknights_debug.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-netknights_debug"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-netknights_debug; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; diff --git a/ios/Runner/Assets.xcassets/AppIcon-netknights.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon-netknights.appiconset/Contents.json index af6456ba1..86292dfe4 100644 --- a/ios/Runner/Assets.xcassets/AppIcon-netknights.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon-netknights.appiconset/Contents.json @@ -1 +1 @@ -{"images":[{"size":"20x20","idiom":"iphone","filename":"AppIcon-netknights-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"AppIcon-netknights-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-netknights-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-netknights-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-netknights-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-netknights-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-netknights-40x40@3x.png","scale":"3x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-netknights-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-netknights-50x50@2x.png","scale":"2x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-netknights-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-netknights-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-netknights-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-netknights-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-netknights-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-netknights-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-netknights-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-netknights-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-netknights-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-netknights-40x40@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-netknights-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-netknights-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-netknights-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-netknights-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"AppIcon-netknights-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"AppIcon-netknights-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file +{"images":[{"size":"20x20","idiom":"iphone","filename":"AppIcon-netknights-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"AppIcon-netknights-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-netknights-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-netknights-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-netknights-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-netknights-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-netknights-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-netknights-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-netknights-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-netknights-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-netknights-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-netknights-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-netknights-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-netknights-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-netknights-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-netknights-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-netknights-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-netknights-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-netknights-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-netknights-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-netknights-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-netknights-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-netknights-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"AppIcon-netknights-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"AppIcon-netknights-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon-netknights_debug.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon-netknights_debug.appiconset/Contents.json index 54b0f95bd..0aa0cd86c 100644 --- a/ios/Runner/Assets.xcassets/AppIcon-netknights_debug.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon-netknights_debug.appiconset/Contents.json @@ -1 +1 @@ -{"images":[{"size":"20x20","idiom":"iphone","filename":"AppIcon-netknights_debug-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"AppIcon-netknights_debug-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-netknights_debug-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-netknights_debug-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-netknights_debug-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-netknights_debug-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-netknights_debug-40x40@3x.png","scale":"3x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-netknights_debug-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-netknights_debug-50x50@2x.png","scale":"2x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-netknights_debug-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-netknights_debug-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-netknights_debug-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-netknights_debug-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-netknights_debug-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-netknights_debug-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-netknights_debug-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-netknights_debug-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-netknights_debug-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-netknights_debug-40x40@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-netknights_debug-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-netknights_debug-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-netknights_debug-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-netknights_debug-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"AppIcon-netknights_debug-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"AppIcon-netknights_debug-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file +{"images":[{"size":"20x20","idiom":"iphone","filename":"AppIcon-netknights_debug-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"AppIcon-netknights_debug-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-netknights_debug-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-netknights_debug-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-netknights_debug-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-netknights_debug-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-netknights_debug-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-netknights_debug-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-netknights_debug-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-netknights_debug-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-netknights_debug-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-netknights_debug-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-netknights_debug-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-netknights_debug-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-netknights_debug-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-netknights_debug-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-netknights_debug-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-netknights_debug-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-netknights_debug-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-netknights_debug-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-netknights_debug-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-netknights_debug-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-netknights_debug-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"AppIcon-netknights_debug-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"AppIcon-netknights_debug-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/launcher_icons/netknights/ic_launcher_monochrome.png b/launcher_icons/netknights/ic_launcher_monochrome.png new file mode 100644 index 0000000000000000000000000000000000000000..d4ccec582140dcebf7f219bc0db7a76fd2f88d1a GIT binary patch literal 3394 zcmeHK=~ok39(`3L?8G)oSVBONW|1f$`;N*cB7w*fQ6Y*5h(Z{UphPH9(I5nEM?j>- z77<$zNvkZ%QXxuYWYGo%Sp#Vo+AUE!q6FyT5121==EL-yu5;eG_rChQ`tUpVyx%Qu zaFCyty1qI9Kx>n~Hyr>NqcBia!a#V<SB!UQgE{g(9r@C!8*yYQG+ z4h{_U!4P{YXGr2;@`UtEy3c>n@{X2T}$^`RM2?}wkJ zlV50ghs-WyyZrOuo*}(X<5KF5k?nV*e|et&WE9Xo6&ZO7BVp@e(Dgk?RYQ0DO|d>q zf-HsqF4|e6U?Y^WKy4L09Fz6hxhQ3b+PJiot8LO-cHb67q3hbu(TdE1FbMIy-C@z% zF`hsrh;!TQ9xk2ddOl-dr)>#?Gd5lEf$HL1eg*$`NlBkB38gG?QpwzjD%o`?vxK%m zsPBMOv!D}FbO`0bFP_|!3Hz6#Duymbd>JQ_kM|HXcY&9ud za6qZ#JSe{^aJ6$Fh%hvuzB= zcms)LdZ=sbgm%WHKId3RCxp3yzNj1i|qGG@~le-yYPY!zsj5LL!1Se(b z(PWO$^8z=ypG9EmjL=s>U7|JiM3nPCM>iUHsb?4#FAq^+2@O;5j{OO$VqWE>>qzy+ zMBunzH+nL7=e=>Zs8z6}sQK zqZyzrJ%Xsdd@XVAV+wM@v{Z3`J(A+cnTyZFNpmYcZ+*fFvbg>3hkxm6rr-d@w^9Y+dx0 z&7!ydQuG!6^8B^Rv3kpx@R&%Tc`1L3k*LfUju%}kj0su=-HGWF*;JhcL;NZ13h)Q- z2d7iI5=@Zv{okBk*%7^59gY~J>Z@I*T=d{M7vP@o{wv4dbX>t}?lVD>sFCPpU-)nN zf7OkSJD|r3ao5o8_Xnecw~_UjZwmgs)RGCd{gl%g!`}-X!<;d*5B5WX&Asc@azWvxtY8)`+H?dg7-Y|ZDZcQf{~ zz)5YC9v1jGkRZMeGk(rbx$qP!3kBpbo13<-NUA!sd|k&u{h+SZ1Je9a{uat-mp&z@ z3XtA}sJuxtDA~Doa;0m`IgJeDi2}&6w$+N&Hjr_3nJ_g4PYd4Xy!P#mju`m^La|h} zat%D_1hp<08(!&8jg%i!rVVKtWoy$yhD;HPhWN3S#D+eLtBZy?@>nZuzw(+wO1CDOVZXRDO+yW9CfP)26c!bdV>+de+t}QUF?DR(tiMA}!)ognUuCR=o z%rWd;HoOiYUveRoe}UbCZ+`sY-5)M)Y(nc>pN#8~61{EjPGm?mIepdA(59P8jT0}b zW%gDNj=fA@!t{nYotCmaEj_dRi*WKA(QI@Gg-C0Dzg?rN&&U07Y-S(ZA`ix3YzZulk&iz)Sx&qJPD%nR8 zv0eD})E!e~xzvK!w=V=(h?kXJ8@K`JXm-klsK>BBv`~284x7K~GvtV=dTXrEEa}&z zSgeqDdV0G)wvW1#7-Vdii|oT0i$h(wrielDQfJJy2^6n4j0m(N7_nYp49_|iYl;)^ z06L5~Z&0sRHy5xGmQs!5I?xa zrM(eo5}Cbv%F3(^i0b_C0pJB8TA~s?l#j0)Td@)A`5v=fw?kejvv$Dy?Yn*hNsXiP zgK?K8AOkvh9BAi`ka>+(@{)vO*qh&S8O&ODc+dfA&8pi8H%?re72l5=Hb=UN;)_+o zs}aXCwnOVdm0>(fc)n`V(vcqnlAYvsoKfF&EMQ#h6|mpOO0W+paGa;uBsu z{uau2C^_qNdUICHNgT)`xsS1t+i-@Ec2$^3NQuINLS~EV689bfq&QK|&*>C4;iS$N n%fBYdRak$KXkRm@`09D6m*6=*9x#ag*MLnvLEg0+m^ps}+n09V literal 0 HcmV?d00001 diff --git a/pubspec.lock b/pubspec.lock index 2c5331705..86c4a46ce 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -608,6 +608,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "619817c4b65b322b5104b6bb6dfe6cda62d9729bd7ad4303ecc8b4e690a67a77" + url: "https://pub.dev" + source: hosted + version: "0.14.1" flutter_lints: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 2c5e1f0e9..0b07537aa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -109,13 +109,12 @@ dev_dependencies: flutter_lints: ^5.0.0 mockito: ^5.4.2 test: ^1.24.1 - - # dependencies to serialize objects to json build_runner: ^2.4.11 json_serializable: ^6.8.0 custom_lint: ^0.6.4 riverpod_lint: ^2.3.10 + flutter_launcher_icons: ^0.14.1 # For information on the generic Dart part of this file, see the From 6706d4623a1778f4e149a2d2c2a6d61d20b11175 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:24:50 +0200 Subject: [PATCH 053/285] monochrome icon --- .../drawable-hdpi/ic_launcher_monochrome.png | Bin 544 -> 537 bytes .../drawable-mdpi/ic_launcher_monochrome.png | Bin 343 -> 343 bytes .../drawable-xhdpi/ic_launcher_monochrome.png | Bin 807 -> 798 bytes .../ic_launcher_monochrome.png | Bin 1353 -> 1353 bytes .../ic_launcher_monochrome.png | Bin 2141 -> 2129 bytes .../netknights/ic_launcher_monochrome.png | Bin 3394 -> 3383 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/android/app/src/netknights/res/drawable-hdpi/ic_launcher_monochrome.png b/android/app/src/netknights/res/drawable-hdpi/ic_launcher_monochrome.png index c606eb0f9294303d4b1ea1c75bbbc9d71fa3f4fa..0cb72facc787a890d30990f6101d7e5f16e29ddb 100644 GIT binary patch delta 479 zcmZ3$GLvP3OZ@>)7srqa#<#Z)`Y8trIA8qm-`>`vLy-H#=Gb*@Ti(SkKkcsLz*)KH z?@zh?ycX;m1&_E1NIT}R;9|&j^nc&Rx}3A|N1$M;-1N@a<;B;P&n-Q8utUQ0vA6J| zH*;hqh3Bv7k&9EPi|m%G-!(ykRZz*LL!kOd_5}8WojWu|m%Ao~zsjvTve87cyJMp9 zy_20*B|8jM?OMz7!q@%TAGWntE?4e0XOeP9uY%d7j;Ha*;tnnQz5PaxUkn@9326a+ z0lxbZF-m*>T03+IC_1%p6wd$g=W3R|L(JcL6PFHi<*0O!DA?9|2~4GxH(n-Gi_tR4Ru9!vYx&H2~3j{yigUHx3vIVCg!0OI}7KmY&$ delta 486 zcmbQqvVdiROZ{?k=+VfXd0c>*14u6}vt+VPKV*c7oDIJ+DxHbx8VGD<#QawZg$H)Cb z)9a*mTNWOFQF?D;gu$$-c7-`5(*Eqfk7;)pFOC%#WIwUsP0gAtpIt#o-yYq=ObeatcU)^1Pk^^?0@d#Vzy#uuoc*Qe;~nv z1q&7|Sc&zuofz5U_p_~-p|LIHn%lA3n8pbk>IL4TJnZIf;ZW!+$on9oo@-#@peDj5 z8cafLpf#|zhJyvx)2d#Db-1!tV=hNp40AYI;+V<_9R(9SEh1tCXao#wJi&qm3l{7; ZA2v=Rz^F$@!VCZa002ovPDHLkV1i(Ge6j!l delta 257 zcmV+c0sj8i0@nhNO@F}(gFp}iQ2qa(?xB~0W|GV-i`e97H+d8)Wsh@Xj$pxp1q&7| zSg^muj&Hh?uw7v`$$A`GAv7R?N^? zOS$HDtTv``LPNd4dz6RW+$|gmeFeD>M$~f+ObpsY*hGVq5F2O>tgYd}0_$njUWIkI za;?T(j%+c^;ph^_R8H7YFu~JCM63{vzJpB@ELgB$!LIoLJQyOu_4W*B00000NkvXX Hu0mjfMTK|j diff --git a/android/app/src/netknights/res/drawable-xhdpi/ic_launcher_monochrome.png b/android/app/src/netknights/res/drawable-xhdpi/ic_launcher_monochrome.png index 8fb28798218047fe23a8caa68bdcf64b1cbfa94e..47e70354dee0c16a1b211df13dd2c01ca89067f9 100644 GIT binary patch delta 608 zcmV-m0-yb-2A&3xL@mWhL_t(|0qxsCQe#031JLCDm!>NDWT>eI+Oll7g0xOsdMqK_ z?r)mg^nj|7krtEl0vvw~Q;T7@M=GamZrJ^7=;$lpGAvLv2;D^qTBRrEpHf#VQMity0TX}bU~l`a0mGoADzo3r0XHWqcOUoEKpBM<%5r^%ef#w{ zXf)op{XAP#CYy)V$g*XDRlTw!V0>A=DYB_gMkpe|xH3jhXwgqH9*tANgRy9&3MPzT zu$2lm4t)*Tvb@SlHof;*$IFCk@<(!rEl$JQ&tQgU2Oc^|Z{i6s(w* z8kU3=(vq{%up(M|6aiL1BTUMlxDK79x)Wh{l8rWob?7>GhU!j-Jx)%Wo-5b~ud}15 u$AXHZC&Vzd7^W7()MAtP0v8wzQ_BZ7Y>R@qQg0vt0000dV#2+ zs1;-w=qtEg1G~PU=xizMmmTzj&9J`~kKu-krXz%VGO%Ir6Dz|D!utB-qXpp3!_Ww}1Xe*F3y zG#c-_{5)G!CYy)V$g*XDRlTw;V0>A=DYB_gMkpe|xH3jhXwko9JQ}Bj2V>Dl6-*ex z#ycOI^1CcUqaLTjU>f;yaKev9|^!FWIeEU88Q)qb<1uF!xmz&P8zrmg|*4hc`&Sr29Ha?>S>v2DOfQr zH7p4$q$OviVMVm`C<3g2Mwpa8aUR-9^(4ZcBpYoE>(F^_57m;3V9M?K=BiFVJLtZ_9F@S{enJR5`e zsNqVF2I$@EyV;;Y*{O5#c<%rb)NX3ZN=R=7I zREb!9JC}$=A_A2mRv(TgB2W=x_1SD95|P0z5j90zn2mS)aov^K3qL!^@FSDa0v8Bz zy|{Hn6!D#t!2(f8L@SOm>;1H>h%XelY$#$YMG=9EH? N002ovPDHLkV1gAdt3&_* delta 365 zcmV-z0h0d73dstPTLZ%)tC3!{2wyGU32^}9*OMCoJ_z4m6ZGn4h+mUo0bxYTOZ(wH zPfL2|d}~X=qvqez_R)JBl>Xw&+r#ZX52<_ldVjp&QIGg&qTRD5Yg|nP{HW0?&&J?A zYPiy)0ebiPZZ;^B;Q}8$;t+>?fEp3?y{MTn$0LZ<6l)Ox|2g4VIbt;cQZZun`A{MP zRU%g3&Ltv|h(KkC)rX^r2vmeveKwnjM3K=g5KR#mX5-zyU3X>n!p{ye{FCqk7zkW1 zZe0;Yd?%CP0x^;U5|RTDMrI;5Qxp+ML?97?dLj<{%Oc`>-z_WR4+>m16tR_}h(Kcz zJ1L3?)D>~k?{nm;!H8`XLl+fs0Ad%#*@!(9W7ib19Pvk!SObU%{LyLfJ6j{BlkEf5 zAzz*!&Ue3}?&;?Wll`s>ir;@}oZYiFi@Y?&?peQ$1}2f9RTh5$s(LvmSvD^P00000 LNkvXXu0mjf?O>yG diff --git a/android/app/src/netknights/res/drawable-xxxhdpi/ic_launcher_monochrome.png b/android/app/src/netknights/res/drawable-xxxhdpi/ic_launcher_monochrome.png index 7fc4f99046078871b4cbd09f78d8e9539a127ad3..0e8a517513b86509275c821af32462594a96840a 100644 GIT binary patch literal 2129 zcmdT`X)qfI6iyFO_w05xlrn0t-MZBoiEc~lXhW+=g_WhM#L+s7kRe;fS!3F|g&I^M z#FcIxiFAdkBdbW0RGL+^A|X~0X@pIG?C+h8ijmOD!x- zOLZ7&3B+1y71EGjUug&{+qn3L->$9#P488M$ID3{?PzM^QD`QNG#w%REdYzHv198~ zwND6dEICnnPO+3qWjqatm*uU#Z3e4{bXd{c+4>@ViBauc#2qW6AL6zQ3!WozHJg)gg@=VqoSC(jLm(deR4i!-b@Bm9g~ z+TI&{d)}NqIJW30(Fn~GI$(>QizkY0Sl|Eu$ghG+kj48txE&`n;+{QGC!>&eW!25A znJQ!tR-cEl*) z9bp^bmTPvL@Ho`XcMCBZ>6NpBe@9j@+gvO3U|`6*RKyG>7X znMB5c5u`2}efh_}{^K+(w^J^I0^MdM{4{krARBn#6jv=WHme0Z^9#8+^k?C@B~q4gmPhFrR* zP9N)g!D*LDM(=f<#>!@Y#^dN}{+LR!c{eKo+IwqW7`B%ar4i^P>kRMUq(6eW*a@3< zs(TjhYo@nQZvMGjKzsh$v=+lQgG+1)AYHHRE10njwUI}`mmI=n`C${r#zCE#^d!cy zo4WDOi^CY|O}g>JrGloes>ESORI#6lJwHEz8I)4PX*Gm#5`vRMr(+#ZQys!?TD&MUrV8gh7&+koVWVnIN^OzA2Lz&C z!`(P;ZlIA?BV1g;RLXH@W<2ISx;qq{QsS}#ot*eVba#1eqhYO~AI>+k=O$g?f>L{C z!t{Do29}}plrhM18+gV~ta(8N%BR6y;G5UI$)BQ@oyZ99j|{tIqq?hXI| literal 2141 zcmdT`X;2af6s9sXQ#-K4E5_|$E6tV) z?p#~>$mOtFmVyT_2GPdnp&qIQoB-5$mB#fVw|Xr6&R~q~8y2?<3s=0dsG6fKNTma;s1}wVJM_}j0a_JL7Wi>OZ~9zWlxmlk9$Ux zR=6&kDq^G6Nn)=xbjDmc=WJbrpGj~*j|c`}2rQt7S~IETq(D_S-(E`;Fz;ZTAyXS8@cW5*im_J+J&FP+d!V#>#o`b+ZJXC@fu zaNk>Y7S-j^zNkga+xWnnlFCu$#@&l|If?1z!{hv-vL`O;`AMiGc(qx5n_`9_r9E7| z&n8_h<-$*lxOnh!kjILm(yA@p`XT%tfAI!-Tk#!G_H2aO8s29b$nk zDItGXWBKKk=XEbW0%~l&Z2Z>}#MK_$T=;iH$+pW1qk$LVH9=Y0AW?WKaJSiEU4o&P z;dRd72AvOPnEG1rnnya)hqSWHjg$@II-^Sp0rz@aB;q*&T!U7$o6AJ0 zo0->MOp-7*;AiNiD!XmDVKhxguK{m=?`qVJs)0tdhHNgwEOV;ILlOJ^jx%5R(?LXS zOkmon_jybsCeMs+N}P$wNka;LQXXW!o3h-JmaPpI(}`tq=<0SvcyN) zrfAj~=f2OE5O(r8VB56!qw0d5%secE!cBSWDj`L%#jFWaP)G!4lr^~(bV!HHDTBi% zu2JnN{7scsNxZwF%AsJlv0`W8>7t_KHdjJo6B8!=;hu6ZKY=)4jJIPQ67{~e?82mo z8Y-UZ7dOgSm%UL6mn;3$iP(KulAI-d*V4%z9;YtRMTHrxX-g7;?6{WH;{xBQcsQ15 z><+0GKd9+tDA`u@=~zz!MASGC(p#@$mp)TOClq0kt??~qrskUDnhezZ3yKht?60bm zd(*e)hSD@aQa$fIQn-K==rQ`p{S7t+GL@J|_DCVC!jk(IoNf5FiEff{3QhVhWONY;WGaMtAyc9 diff --git a/launcher_icons/netknights/ic_launcher_monochrome.png b/launcher_icons/netknights/ic_launcher_monochrome.png index d4ccec582140dcebf7f219bc0db7a76fd2f88d1a..be004fa1af65ad69aa438b9514cc9d491db851cd 100644 GIT binary patch literal 3383 zcmeHK`BPI_7QQd5A%wI636KQ3QHio>5HJYH(hUfr1W^%OAdnth5Cp*?Eg?Kbq6u5O zvB=_r0wMyjWwF^FpwKh|DuNlYB@%bG$GBk=WM2OP^UM4&H9gh0>elytbeRV) z&XouFucPUh=l}p{zCNCT0N^l+0}JpNbd)an1p_EPaNW0{s*n8!6NKF!8$AF}W>dA{ zL`;(cHf-_25L;Jv-|EKzmk{W`c^Qh?c{gy?uUf0tuD3%ELx|=G(*ez9(ZMo4ev&n6|mI0w8Wb zbuO1?r<5rjVe*$N%d^mR+Rd3CAQxK=oLL?2EeZtm3{K|OYLUMv7zkX{?D0c*`aL5S zg70^PSig1H*;|lNJbjoxGH-5Ls`LV3;@1+%Q~?^0c^x-{q;`$kjmWuC`S@p2`vCh7M(aN}pbpxfrvi&Z19AO#zxb(1J!7hYra3QEVoX*fiHG z7CVPl$3=qkGqOarJPM_maLK^+g`ug^aO6_>S4g5n0Hxwl50e#;Hsio7@k<*|QYaIF zu$fM}wtIu5L>3Pk)j1{03Ol8)-dsH9hoA;~jdnP_-nBoRHbxJY5s zmB$1PkJhN7&M6_6*jxCTd!DCTC*;nPvTC#FY~ULEa2GnLnb1e*JH-|)ab_&+lG?Io zV|6!n1NG$C^jhb~POw0`zz?OcoP}uut$DI|YBGzWmYeycCKhTCkU&9aO4zp^GNtv8 zrknIR90cF0wh}+M=ucOZrM*2y&c(f#1lso2+itcI4F|Q>P@W%n$$)PWge(3t8si1D zt+CSd6hqFr>Rti6wWH|wgqD*W&4Gq(=`Fd+ojcolO%j?FcSbEIEgro$uT$XsxkK+v zf)tFbvcZ2E8Y;~DxP#M-A^9r3@`tEij-2Iq@y9QVDjD^$o}ACD;3bpK6iyVOW8bn8 zDHL^k*ON^a(1xyWwla_ecA%G5l5>I?yZ$7BXIf_93p8=-7fxtGz6ou!@nRqn_U6UV z-7Z^)KO22Xyzr&;iff&fC`hy&=oV*e#NPU1=%#33EN{REOsL*Fy|ibzJFjleT9Zk* zm-SybP7vM4ksKKAd_x;yz+&hx`G1uStjb<7kz%6mz9n$XcnEUA8~3{N-%Htyx-y{c zxia`C*aNG-Ob4pbLm$wqx^dJOlNmNtBZGV<@{CyXJ(OxrxnS&P$Xj*_99D{mr!mQN z4&p6e#e++z!aF(^UJUn@L6yQl&*bQw^6-!D*1_%NKDa%1jxNnmWaz4&;8fMeHl56e z3b%vdv%HE^z)87_QesKDu&`&4cWsqDY)2K=n?!DXo*S9Ds#DM&>7{!ugQjl6sji&} z&nMiIH7ksA?vLbqz0GqYMJ=+mwsV9#bc9zeOaEp;j@<%PZs}~e`x^LL!kHC$L-`zJ z4Y5XGtZ|}oBhMy@J1148Sw9rLfeJT)#wQ3q!AbA#3jXwuMapiPi~Alam}MfSi74SB z4X0WQfuO%r+h6M$58W)614c>myl2 z^hN!&(bZ&}Y9-`4yV|s5Fkx%Z;f3^Jf?2Xz|NY07Fu9m-dVZE+dZl&;C@?^}T4cNT z9^#xSdU}N0cPFt$Q6{e~I(_}=(RF>d`7K$mH7c-XAinLP5r z!ez_#=?v?S-Y#NN%bQ)|iYG&m3pzeHbB;gScCeAmGlyE97m06#zM4~QJi>pknVaey zVBIXvn(!3+vO=xetW4MjSkL#($n8eY={3(chr2mEwP>@tDc*McDfn4OtWKM{efonq zEM0`3X-}WhtSJ)5r@sV2h3}|gu?xSmLWyfLa4KT%z6Tt4of+2T!z*iH1C&{K)h`7s zS7$p077(G!u$$U5nSodrmiH^L&U+y4g0~;e2WoBm!Rb2XYoeCXwYPQ|=0+_neN=%N zq{QWi_Fx9{*^Tx)m{?qX)r078EZ*)Xmw)>eGtg(gskg@*(vqxeR%0lSs;qIu@W>8p zr4wvW^Lt9_=l-;YRI93*Ut!QhRg-!)EI@976*YY)z{%H>9ReCxIxL0BRS~JR zOqKy00$Dddz6>gLgfCur6NP8MY!%-cWD|l99gBmEmLSD;y1Hr<7kp45huB4cm1S!O zi>Sh84;j|ZyZzfEOYYLt4FuJ`yl!j?Xq;2_yL8|kFN2)?J%<6E9TzrkJ3zutx@k&e zXA+?%8Yh_(HWE-|(ryDouy|?o8ptWL=>AL zj(=j0HUXsigSX2JuZ#56qa}Z$F}~*G#PB@04(r9sxCIV>d~tle{5`9_6(0Q;K}a)h literal 3394 zcmeHK=~ok39(`3L?8G)oSVBONW|1f$`;N*cB7w*fQ6Y*5h(Z{UphPH9(I5nEM?j>- z77<$zNvkZ%QXxuYWYGo%Sp#Vo+AUE!q6FyT5121==EL-yu5;eG_rChQ`tUpVyx%Qu zaFCyty1qI9Kx>n~Hyr>NqcBia!a#V<SB!UQgE{g(9r@C!8*yYQG+ z4h{_U!4P{YXGr2;@`UtEy3c>n@{X2T}$^`RM2?}wkJ zlV50ghs-WyyZrOuo*}(X<5KF5k?nV*e|et&WE9Xo6&ZO7BVp@e(Dgk?RYQ0DO|d>q zf-HsqF4|e6U?Y^WKy4L09Fz6hxhQ3b+PJiot8LO-cHb67q3hbu(TdE1FbMIy-C@z% zF`hsrh;!TQ9xk2ddOl-dr)>#?Gd5lEf$HL1eg*$`NlBkB38gG?QpwzjD%o`?vxK%m zsPBMOv!D}FbO`0bFP_|!3Hz6#Duymbd>JQ_kM|HXcY&9ud za6qZ#JSe{^aJ6$Fh%hvuzB= zcms)LdZ=sbgm%WHKId3RCxp3yzNj1i|qGG@~le-yYPY!zsj5LL!1Se(b z(PWO$^8z=ypG9EmjL=s>U7|JiM3nPCM>iUHsb?4#FAq^+2@O;5j{OO$VqWE>>qzy+ zMBunzH+nL7=e=>Zs8z6}sQK zqZyzrJ%Xsdd@XVAV+wM@v{Z3`J(A+cnTyZFNpmYcZ+*fFvbg>3hkxm6rr-d@w^9Y+dx0 z&7!ydQuG!6^8B^Rv3kpx@R&%Tc`1L3k*LfUju%}kj0su=-HGWF*;JhcL;NZ13h)Q- z2d7iI5=@Zv{okBk*%7^59gY~J>Z@I*T=d{M7vP@o{wv4dbX>t}?lVD>sFCPpU-)nN zf7OkSJD|r3ao5o8_Xnecw~_UjZwmgs)RGCd{gl%g!`}-X!<;d*5B5WX&Asc@azWvxtY8)`+H?dg7-Y|ZDZcQf{~ zz)5YC9v1jGkRZMeGk(rbx$qP!3kBpbo13<-NUA!sd|k&u{h+SZ1Je9a{uat-mp&z@ z3XtA}sJuxtDA~Doa;0m`IgJeDi2}&6w$+N&Hjr_3nJ_g4PYd4Xy!P#mju`m^La|h} zat%D_1hp<08(!&8jg%i!rVVKtWoy$yhD;HPhWN3S#D+eLtBZy?@>nZuzw(+wO1CDOVZXRDO+yW9CfP)26c!bdV>+de+t}QUF?DR(tiMA}!)ognUuCR=o z%rWd;HoOiYUveRoe}UbCZ+`sY-5)M)Y(nc>pN#8~61{EjPGm?mIepdA(59P8jT0}b zW%gDNj=fA@!t{nYotCmaEj_dRi*WKA(QI@Gg-C0Dzg?rN&&U07Y-S(ZA`ix3YzZulk&iz)Sx&qJPD%nR8 zv0eD})E!e~xzvK!w=V=(h?kXJ8@K`JXm-klsK>BBv`~284x7K~GvtV=dTXrEEa}&z zSgeqDdV0G)wvW1#7-Vdii|oT0i$h(wrielDQfJJy2^6n4j0m(N7_nYp49_|iYl;)^ z06L5~Z&0sRHy5xGmQs!5I?xa zrM(eo5}Cbv%F3(^i0b_C0pJB8TA~s?l#j0)Td@)A`5v=fw?kejvv$Dy?Yn*hNsXiP zgK?K8AOkvh9BAi`ka>+(@{)vO*qh&S8O&ODc+dfA&8pi8H%?re72l5=Hb=UN;)_+o zs}aXCwnOVdm0>(fc)n`V(vcqnlAYvsoKfF&EMQ#h6|mpOO0W+paGa;uBsu z{uau2C^_qNdUICHNgT)`xsS1t+i-@Ec2$^3NQuINLS~EV689bfq&QK|&*>C4;iS$N n%fBYdRak$KXkRm@`09D6m*6=*9x#ag*MLnvLEg0+m^ps}+n09V From 99e2202e29def9539d8f34fa12b012c7a7c1d22e Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:14:24 +0200 Subject: [PATCH 054/285] Appearance --- .../drawable-hdpi/ic_launcher_foreground.png | Bin 5574 -> 7111 bytes .../drawable-hdpi/ic_launcher_monochrome.png | Bin 537 -> 519 bytes .../drawable-mdpi/ic_launcher_foreground.png | Bin 3267 -> 4060 bytes .../drawable-mdpi/ic_launcher_monochrome.png | Bin 343 -> 340 bytes .../drawable-xhdpi/ic_launcher_foreground.png | Bin 8211 -> 10979 bytes .../drawable-xhdpi/ic_launcher_monochrome.png | Bin 798 -> 802 bytes .../ic_launcher_foreground.png | Bin 15030 -> 20237 bytes .../ic_launcher_monochrome.png | Bin 1353 -> 1338 bytes .../ic_launcher_foreground.png | Bin 23017 -> 31436 bytes .../ic_launcher_monochrome.png | Bin 2129 -> 2119 bytes flutter_launcher_icons-netknights.yaml | 2 +- .../netknights/ic_launcher_adaptive.png | Bin 57628 -> 0 bytes .../netknights/ic_launcher_monochrome.png | Bin 3383 -> 3359 bytes .../customization/theme_customization.dart | 32 +++++++++--------- lib/utils/lock_auth.dart | 2 +- .../rows/add_token_button.dart | 2 +- lib/views/feedback_view/feedback_view.dart | 2 +- .../pages/import_encrypted_data_page.dart | 2 +- .../widgets/failed_imports_list.dart | 2 +- lib/views/main_view/main_view.dart | 2 +- .../token_folder_expandable_header.dart | 2 +- .../folder_widgets/token_folder_widget.dart | 4 +-- .../default_delete_action.dart | 2 +- .../default_edit_action_dialog.dart | 2 +- .../actions/edit_push_token_action.dart | 4 +-- .../token_widgets/token_widget_base.dart | 4 +-- .../dialogs/export_tokens_to_file_dialog.dart | 4 +-- .../settings_group_push_token.dart | 2 +- .../settings_view_widgets/settings_group.dart | 4 +-- .../dialog_widgets/patch_notes_dialog.dart | 2 +- .../push_request_dialog.dart | 2 +- .../push_decline_confirm_dialog.dart | 10 +++--- 32 files changed, 44 insertions(+), 44 deletions(-) delete mode 100644 launcher_icons/netknights/ic_launcher_adaptive.png diff --git a/android/app/src/netknights/res/drawable-hdpi/ic_launcher_foreground.png b/android/app/src/netknights/res/drawable-hdpi/ic_launcher_foreground.png index eb0e9cc6a6b3191604f72046264dc5ccd257851c..5226149fcdf238cce45803a727dd88d7968be030 100644 GIT binary patch literal 7111 zcmcI}S2P?9)UD{f_vi-EqPGxz^xk_n(QC92y&GjPdh{-OuNk5nBnBZ`B1*JjkT3}U zcOU-yaNqC4*=Oy2p3ci&YoC*1pr=kk$ViBRfkC3Fp=$JBulzrSkNF?!X2s}WU_8&z zR8=wweRlRCEXzjy{nUNv6qD>Ba)HHa>n4HB#cM8_y(4|}nUgc64bX5hh8oZy&HGqh zuf+vuFwlIlo5e>(r3}|ij!n-IrQeud%M-Nj*jMSufOc7FHVq$?-hd8lw>}`H20Mte z>mE{jI z{y);B0t$@f!4UhmyDJl$`E-|rV)1Q4t588xfer459egeF3;S(obvpJN1&3&vTy|WS zYSSVQ%9?V5ZW)m%z^ zZQA~q?o3&d%;^BvDPnt-C;&h}JD?ISBgqMHj5mZD)HAetPO@{4Po1<0;3Vd$AuWdq z;%aJ{TZ&YAhAZ7vrveUAP!B(6B5ItZ$;kn!bE-)N?mq>A!+*W7g?-XJ{+WUll|tDV z*4N5x)>%)|vZG7PppYypRcYT>p9jJ&t3ePa93`I;*KgtN=nV~eXF%XUhF!UZTE+BO zwhy>&esgT?VTOHA0TS0r%YUT2FD}IP1W?1!{x@S0T5cV7UMs;jGA4{0Fd%MdR4Xy_ z;%n!`u{Hu7&_5;&)*4$D`?ie0ja45T4MjXyNycP%y=1U1O9uSXYD*&l&X3TiCy1w# zGA=wfm35|?tHt>|doa&vsBMC=r(2eK@T{cQ>?=6Aluj^QNmD9Pd4D;TC+muAGuD9H zP}0ZProBX9V-co;&tVCgyO(OV9}r-hpc&EwZoCz|p0tQOx9}17q~BbYT^9CsJPu>l zd;t>#NMU$6!kljiWDMgBbjA&0O}W^#9SKx9mR3#n255#=cyV@(xwo9N+&>@C-YSkR zydfOYzkC3 zctwrw@Hj(WTE{A;W?|G|;oAU9LTj=0Kqc)%P@$r_ZjS*E<=u)@na;ct4jn2%qagA4%`ONA5RACgO`% zuRRuEfXu{`1Yq&!X!*jkmk9)z)hs$2%tSKtnyVP%Q2taZZ?~hh*s4P(KfcQ6o01!! zgV!6wXH%%pSM#4Do5AQ<4&)rR&!?%%$clXnnM3m-x&Y590?|nVJ}j)!ApX_|RliQ` zUm0Q+5-Oww1;Nu57T}0YoXJ`~DAx>WLk{tWW(((uVI|E`wfML;H9-}&TD&-cA-lzOjjT^7XYHMuSA5ilAZ@gZ|l;!oQu zTG$n5R{Wd2{Nc#zrtR!&rR0Tv4I=MIA?xKAX`>3O?`7V9fYy4e;)&dUV{1!O zCOMQEe_z=-S$nwa%Ix=6{e!vp{uOql z#K_^`{apbR8>I-KN%Lj|??8W#K`dk#=G6wo1vh@I zg^&nExg;~l=3i0aDjEW)jS`h-J&2w17~r%6Vm0x0V~C>Hrd{*dwo72K{nxID>|;wP zr6ZNL#kmC;YvW0nsJ`%^KY?RPfWb^&)53M1E-xE#iiUz2jZDVp7{8DX-?_Yzjc;V} zR0PI^IylSzAIXzEicm%~(wZ++zEI2nIl%By>&{zi^^zKnV*`PdqTEyyNI4}Kw->Kk zY1(BDTy#+0z_X|WsX~1JI#UT*neF+|8@N__fW;`~DXO*x)TYlmzHtc+6m1HQw89=f zVLNLk;$DqYG2Fg0;N@x4MlL#Ms3qHpE*@OnlTvyZV(e?k4T&un*b;5)U;?$EEpFF|U>q3s>f^DN8Yqi43;4GcflZn7%4WUk~Y@g2F-sj(a zZY&~09=J*p+x@SDu|D?<60vW&1()q2R*Gr2qS$QP2d9Og*Ks+LU_n{sC#?ONjpQow z7&D_5ZYlzy-Nd*E89z5TuM5OX`4`ufYHh*%>5>i>zY!hYYzijrj~&KBRO;FH*xzF* z`T1r+Fm<%w!_wpk#TDpLTzU=X7XRa+keObu;bL26Jz*?NlwW-@H?x#7B~!;GaTj~? zCvw_UTWZ*Ie*PaXblRCV9GI;m52t$9
TY&E~^Y*B1k6nKPO>xPLs(>gMdo4L}P zne&DUI`}(jgIfrrNz}OB{?G`%nciDlb8nuFVu94VnT|8t0SB$-^EYIts@7(5EUAt!*PCUM zx+PvIGVp>ND#>_{Cq}CM3nus~m|(U9mHP*%GwfxZ*Eg2J_(Zo^7Dw68eQ&n8MjoE1 z%Vo#$FH0k({CD|6C*es&@msdg(qxKsa^P3qAFWKTz_Cqn8W=Mx%@_+Y2Y7szhO0>v zVPzo1JMmFO?nBi6bpM)a>Lo)~UKOBM<64OgE?G=GB&y zY5o(RRU6xVY6PC!V|fUCm?RHr;DB-(zmS70R;eHr)3f+$n>gh0F}2u)>GSd%EY|Rm zXlqj5B1thXklrgW;r&9L5}a9~q77!t9>g7$5KDV?Aq3dyxXLm5coQ22OwVvvu{(+( z&o<>zm|&j3(XlHjbXEBc`nPISTgY|bs1v=!tc!QFu0bVxz8FY3kG=5VGv}=oe<6a3 zt@v^vV5|ds-;Lml!av zWa}qqTgAKWmV8o!Jze6M?{-dVzmK2$7UgpII5dnLo)@$bZ|ht-zwts3WJW~1jR^5CR^1Vm==R4@s7QeIfZI?P6WHvO4W^j!YM(g*JDq(c$5M19_@=OiY3%3d< zxy}ro+V(R8@I@u@6RNmfm9XiCWW$3Wilfg$_GFTUqlJ8_uDF&T9O_zer>8b4E=5A( zBjC*JtbywPON%lsnI>^9h}WXKT~|+Q_SqU#cOs=c1xHf8Bi}yAB)2E4E+QWewyB)m z{Bca&CWcOo%LH0N4{ONYaS)d@V`z**iK6W8z*ZQYO{wmlwKXqa!i!uf0%AJp3!ZaT z6X$ySO=38kW(C3|9LdC1t#LZnaQAG@OghKca7Ix9wBCLuSXGH@Y5S#$N6l7vR~+We zc(T+w&wp6p=oIc=)u%p&wsiAWiA!7w$|NIr<*Ue57Z zI(*5yk7(EVufb-R9Y@SP0){bP5w0f>Y6Xbi(HoS9KPh*fS7S7_c;-X$^UFgM)@uVb25X50^ttL)Vs_U@;y}sI z)b=qx{(7tRd%H-5dDl?vhOl$Aa3!gbPKW2BXkP`!kw(zD_MFy8oLA$7z6>3uPTuC_ zA68Ch`@Lxni-Fztk8M6KyTB_Qy=$c)lrZkggO!vEal(~A1aE6QIW7(&cbH*zSmrUE zOpVl*un|w`+WS*Mf3MQ;4}j!fwMFW=Y8<9K910u<&nOQ{;PJp_PKtIu-wI#i)v)$O z_T_<%@Qsy3a~2u*eaWlf&dXG3lI*#B;tzbpy?W6Xq7$|`YaOmU%-thbW`(hCK!o90 zeRDz{Pl1eO>;4neBem>A>pV^nAk6v$|w24vSY)IwsACfXf5d)co$ zEJFo08!ccb%}wkKPvyfHvB2Cgv;WliwEwk1puw|oy)v6kvE%JYM)6}q^)&vgpa`O= zO;1|xv=zS#DPKbC0`<#X-Iz3VdPxsf;hSW>m4g1_clP`-BE~S#h2S9OM5z+~h%tC4 zR=`f07(J%Z5WL{6OvOI8-S#mz*vcnUuTwHb|jn0OsG z$NuD-wu1RB?hQNm>tNI|Uu52;%K8o;#U!!pH&-za`)rTob5$1m44s|VWtrL=WQOcF zJNEeQ)Kgv%CMAt7w1LT(Wc@)aOU{f1U0aKG*cUQ}_@w5rS?KxTeco;+>rVPcdKb1M zfc~LsG2)=IB@&JdnG8XaynrT@uqXJM)_k5jW=!qM4(8T)hY z`@4e^M=y6%1ChI=i?J7g$qVWT|Cg8|7pBqoMhhOh>qQlo=s#bCrB?b)ztV`gr#ak zL;C#Xr-S;ZkHf{dQ3YlnW%MASP}9%}!wba6>!E;^?q2T)E5EyIh38ae zY)p0>xUoLTYUTB&FB>sxJv;W!36g2sjhglBQScH(n;RDvVAG@e5hXG+x$k{a(9@Z$ zNi009p&5mlQb|0WQYW16XA}LO!++VC8xIjN1;VOnLrv_1uZ~@NeIi&(rS0U!9v&c6 zg(*XN%ym5iWEF4iOc$$m7w~|aec~>=al%-m(eEZ2mu1af=HQvdETz6gM*W?lON6to!pA_|RqYlIV5|N+f zwUD; zo66Du!a|S=Pxt=2?BV)M=%JQhe{!PGsTd{KM-Na(djJU zK&gK$}B07H%b&guIj{FN@O{dd>-B0_(2?vTV zGAR_NJ{%|hVGSJs6cRaBS=WA^BbnPp{nL!uHTP+7g?~X9*CqRfwZhJy30aP){1YJx={od^jcHtO z+wjF@O|mBSJwo+v)c36Gmb%g~O^jq7aqzmT+&t=TFDEK+!~01XTF{k1hnU85YcY|E z_?Ln_ew=!IBWmAxL%W$^pdymyq!bz;ca_fixVKHVdHZRU!__=wFIpP16K;dT1C-f0~h z=~z#w`47f8%%N|YZzfi-;a`|VMVxmck$=v3)Sq@Itw(%?n42+Dk2P(4#b$OH$u1g3 zw03U;?Zfx-;}>^}A@qo@*Cr7qq)yj&xsk`}JNKQk^0&(8GzxQ02$w-56>yx(qr(*AZHs$z>Wz%{}K2kVkk?ncv4 zBt2t2_m?~*ec0Q`%+PZ&VUQ3xYrqLs)e;4BvuxZ?mSTMl8F{nu@7r0QLcX*GFh1qv z_Zh8q*id}AEHT#X0H94mQNDQk_05U>_RXpGCS42?3fJ}OpPv#)^w5M18bgf^cFICu zq8=WtTepI5{uW2os*`%WI8?3;;@K1jX3HAy;vA{{Nc6e%d2_u&7aVm2m1?X_c-@LoPZGYpttjF=<4M#@nady(bUpoP=v~QuR>@16o5tQe->-ZHp32!A1WU$4YUniqrD)gNH1o{%Z;6~s z&Yt-Z$9XOHOU6DuQKmWzLq@Kp^D}JZSO_AVxg#7fA zaCBXpH_Ov7`U0yUPLVPu7ZA*gE3D1oi6d!d^nsRJpZU3L(7RJN?vSWgz6`-^FF0Ys zYOpw9298OZV{pQl2*$-1RIGFp5b!~8pOx% zE0lb>jZn(j!@4WhhlO}5p}CHfRBzb%-s#^mzJt|6eWB%TKp!m{{|Q=c5HZGeb)kA9|Z4$#ZwHtKVK zElaaz8?TYD1#0b=|AYLwD%$RGxz=EH`+}k z!f}j8ZoN6hm$KIn%Pa-`<&-cKi_t6!z2GE!^)5R4+z5ZO3|ru{YEaA+aG z8)K5@b%|ICG%9waRq~aXe~72aL*UF+NLTSe`u6C@~ys z9MB(N>E;6cVD#n9_DlYis2j25TNOM(?2&V%T4t;x_6w89a%92$xijvea)nE(Oqx(m z6#L3{LCj!Qg3Qr_I9tz7oltGh6g>aY literal 5574 zcmd6r^;Z*)_y0lSrKE!q(u~>wCn=+(HW-3*m%;#LG)jjwW5ASBLg_B)ZbpYpKtQBX zhk&Hg@ZtUaE51M6d(S=3d(QKm=TDDwWA$`2XsOt!NJvO%HIZh| zNl2IyG~p^nexy4eH{Gpf{bOGHW~TPt^J=!B7SO8YUx?j-EoDeiXUsucbDgY(z;E5a zcXN~vb6+DZ>TgfdQzQvGp{Cu~2PYicaP4@_4YNi8OLnLG!2V#^xldE)Zi`yRS5>-F z^FN$f8qEb_AAWpq`5gQ?1lM`K+qtR=+LQ=rV&q8_Gl0iFcnf+Q6b95^qb|TOJmo>8 zoZN!s;aQLXflg&Cc%S0``;+i)EU^{JamHx?+d(K65qEQZ_QBf^MpYZsEQIQqys@4R zU!IC$Td;=Sr$P@~NbF`RkQp5A7zf277)`Op(@&CXRa4>rM)KT}A^L1FTGzi*KGuR! z!TL0KHR6_`ScaZh(#X1=+#CBmq(l3AeRp_sYpO~j2-SiVwW4p`wsOdY{dK zF=!7f2VZQ%PDmnW@gTe%P@!Wzv}vsoGh!h)^0#SoMZ<;#AeCNEoYb6ngjeIvuXug8 zDp-c95H|53s`{5d#S6kGgqm*Iz~U%0-u4G?qVA%j=5g zQ}a)%q8x{*WOs%>4yJN=$woJ9Vr(@%LaET=lGsb{&Oh+&3}PvW7!V+q@`BM~_TNcC zZsb}P)iTIQr>UN#pDQIP3P{vH!mM zKOeNJ`FqZ<6$&Eflto|+%_o67=@h(= z_%6|ifh{?>dh$DyxmDh3ONn9LJ#L6Mz;lKgTIFKnsO~43bwmVdA?8V!p_d%OY^YkHVo{dGj8Ha`GI zmL#eol}HxidmE1p3hi%EQ#-Z$IwfYcs4Qu@+c~g2D;Ufn=Hfo8NRpetM?Lr4KaKa= zKzd8lF)_Pv(E?kSlb0~LN0^hGC<30TfPsm@n^_%9y)d3{rl|R;q4c41azW6U)hf{= z;h)@zBLNX8$Q^WuS7p4-&d8rhTGzDZ7X2{p@w_U|4cYWiBg`|) zO`T${LUm0}%oI5grk_M04$VueJww=^ggcQr<&@+ZjpZ7dR$Eo9jP)b1@zAm&+F6sg zzm-DO$nI<}%7g~e7m1~fsp&7nVYRoWKEhiXNv2BmL?JR&e53Od3?+hegM|{ANy<_{ z4I?G0x-9v4t&7w`V0;&(@r`Y&IdW#14 z&@28A;|st&C<{86>_Q1v_@NnzYerHbfO3kho6diQmoh)CZG69#^?E$4+!+)Y$L