From 034b7e3cf45faa38df2c06df6458428028a1d4cb Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:40:31 +0100 Subject: [PATCH] changes for customizer --- .../privacy_idea_container_api.dart | 44 +++---- lib/api/interfaces/container_api.dart | 31 +++++ lib/mains/main_netknights.dart | 8 +- lib/model/token_folder.g.dart | 3 +- lib/utils/firebase_utils.dart | 45 +++++++- .../token_container_notifier.dart | 5 +- test/tests_app_wrapper.dart | 4 +- test/tests_app_wrapper.mocks.dart | 108 ++++++++++-------- .../token_container_notifier_test.dart | 1 - 9 files changed, 171 insertions(+), 78 deletions(-) rename lib/api/{ => impl}/privacy_idea_container_api.dart (91%) create mode 100644 lib/api/interfaces/container_api.dart diff --git a/lib/api/privacy_idea_container_api.dart b/lib/api/impl/privacy_idea_container_api.dart similarity index 91% rename from lib/api/privacy_idea_container_api.dart rename to lib/api/impl/privacy_idea_container_api.dart index a75ba4d5c..6626dae2f 100644 --- a/lib/api/privacy_idea_container_api.dart +++ b/lib/api/impl/privacy_idea_container_api.dart @@ -25,30 +25,32 @@ import 'package:collection/collection.dart'; import 'package:cryptography/cryptography.dart'; import 'package:http/http.dart'; -import '../../../../../../../l10n/app_localizations_en.dart'; -import '../../../../../../../model/extensions/token_folder_extension.dart'; -import '../../../../../../../processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart'; -import '../../../../../../../utils/ecc_utils.dart'; -import '../../../../../../../utils/privacyidea_io_client.dart'; -import '../model/api_results/pi_server_results/pi_server_result_value.dart'; -import '../model/exception_errors/localized_exception.dart'; -import '../model/exception_errors/response_error.dart'; -import '../model/pi_server_response.dart'; -import '../model/riverpod_states/token_state.dart'; -import '../model/token_container.dart'; -import '../model/token_template.dart'; -import '../model/tokens/token.dart'; -import '../utils/app_info_utils.dart'; -import '../utils/globals.dart'; -import '../utils/identifiers.dart'; -import '../utils/logger.dart'; -import '../widgets/dialog_widgets/enter_passphrase_dialog.dart'; - -class PrivacyIdeaContainerApi { +import '../../../../../../../../l10n/app_localizations_en.dart'; +import '../../../../../../../../model/extensions/token_folder_extension.dart'; +import '../../../../../../../../processors/scheme_processors/token_import_scheme_processors/otp_auth_processor.dart'; +import '../../../../../../../../utils/ecc_utils.dart'; +import '../../../../../../../../utils/privacyidea_io_client.dart'; +import '../../model/api_results/pi_server_results/pi_server_result_value.dart'; +import '../../model/exception_errors/localized_exception.dart'; +import '../../model/exception_errors/response_error.dart'; +import '../../model/pi_server_response.dart'; +import '../../model/riverpod_states/token_state.dart'; +import '../../model/token_container.dart'; +import '../../model/token_template.dart'; +import '../../model/tokens/token.dart'; +import '../../utils/app_info_utils.dart'; +import '../../utils/globals.dart'; +import '../../utils/identifiers.dart'; +import '../../utils/logger.dart'; +import '../../widgets/dialog_widgets/enter_passphrase_dialog.dart'; +import '../interfaces/container_api.dart'; + +class PrivacyIdeaContainerApi implements ContainerApi { final PrivacyideaIOClient _ioClient; const PrivacyIdeaContainerApi({required PrivacyideaIOClient ioClient}) : _ioClient = ioClient; // Returns a tuple of updated/new tokens and serials of deleted tokens + @override Future<(List, List)?> sync(TokenContainerFinalized container, TokenState tokenState) async { final containerTokenTemplates = tokenState.containerTokens(container.serial).toTemplates(); final maybePiTokensTemplates = tokenState.maybePiTokens.toTemplates(); @@ -105,6 +107,7 @@ class PrivacyIdeaContainerApi { return ([...updatedTokens, ...newTokens], deleteSerials); } + @override Future finalizeContainer(TokenContainerUnfinalized container, EccUtils eccUtils) async { final ecPrivateClientKey = container.ecPrivateClientKey; if (ecPrivateClientKey == null) { @@ -281,6 +284,7 @@ class PrivacyIdeaContainerApi { return (mergedTemplatesWithSerial, deleteSerials); } + @override Future getTransferQrData(TokenContainerFinalized container) async { final requestUrl = container.transferUrl; final challenge = await _getChallenge(container, requestUrl); diff --git a/lib/api/interfaces/container_api.dart b/lib/api/interfaces/container_api.dart new file mode 100644 index 000000000..196979be3 --- /dev/null +++ b/lib/api/interfaces/container_api.dart @@ -0,0 +1,31 @@ +/* + * privacyIDEA Authenticator + * + * Author: Frank Merkel + * + * Copyright (c) 2024 NetKnights GmbH + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES 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/http.dart'; + +import '../../model/riverpod_states/token_state.dart'; +import '../../model/token_container.dart'; +import '../../model/tokens/token.dart'; +import '../../utils/ecc_utils.dart'; + +abstract class ContainerApi { + Future finalizeContainer(TokenContainerUnfinalized container, EccUtils eccUtils); + Future getTransferQrData(TokenContainerFinalized container); + Future<(List, List)?> sync(TokenContainerFinalized container, TokenState tokenState); +} diff --git a/lib/mains/main_netknights.dart b/lib/mains/main_netknights.dart index 672e784ba..94709bb8d 100644 --- a/lib/mains/main_netknights.dart +++ b/lib/mains/main_netknights.dart @@ -20,7 +20,6 @@ */ 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'; @@ -29,6 +28,7 @@ import '../firebase_options/default_firebase_options.dart'; import '../l10n/app_localizations.dart'; import '../model/enums/app_feature.dart'; import '../utils/customization/application_customization.dart'; +import '../utils/firebase_utils.dart'; import '../utils/globals.dart'; import '../utils/home_widget_utils.dart'; import '../utils/logger.dart'; @@ -53,12 +53,12 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); await HomeWidgetUtils().registerInteractivityCallback(homeWidgetBackgroundCallback); await HomeWidgetUtils().setAppGroupId(appGroupId); - final app = await Firebase.initializeApp( + final app = await FirebaseUtils().initializeApp( name: 'netknights', options: DefaultFirebaseOptions.currentPlatformOf('netknights'), ); - await app.setAutomaticDataCollectionEnabled(false); - if (app.isAutomaticDataCollectionEnabled) { + await app?.setAutomaticDataCollectionEnabled(false); + if (app?.isAutomaticDataCollectionEnabled == true) { Logger.error('Automatic data collection should not be enabled'); } runApp(AppWrapper(child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization))); diff --git a/lib/model/token_folder.g.dart b/lib/model/token_folder.g.dart index 14bcd7845..cb98515b6 100644 --- a/lib/model/token_folder.g.dart +++ b/lib/model/token_folder.g.dart @@ -14,7 +14,8 @@ TokenFolder _$TokenFolderFromJson(Map json) => TokenFolder( sortIndex: (json['sortIndex'] as num?)?.toInt(), ); -Map _$TokenFolderToJson(TokenFolder instance) => { +Map _$TokenFolderToJson(TokenFolder instance) => + { 'label': instance.label, 'folderId': instance.folderId, 'isExpanded': instance.isExpanded, diff --git a/lib/utils/firebase_utils.dart b/lib/utils/firebase_utils.dart index 0e77de324..118bf9d75 100644 --- a/lib/utils/firebase_utils.dart +++ b/lib/utils/firebase_utils.dart @@ -23,6 +23,7 @@ import 'dart:io'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:mutex/mutex.dart'; @@ -33,11 +34,12 @@ import 'logger.dart'; class FirebaseUtils { static FirebaseUtils? _instance; - bool _initialized = false; + bool _initializedHandler = false; FirebaseUtils._(); factory FirebaseUtils() { + if (kIsWeb) _instance ??= FirebaseUtilsWeb(); _instance ??= FirebaseUtils._(); return _instance!; } @@ -47,10 +49,10 @@ class FirebaseUtils { required Future Function(RemoteMessage) backgroundHandler, required dynamic Function(String?) updateFirebaseToken, }) async { - if (_initialized) { + if (_initializedHandler) { return; } - _initialized = true; + _initializedHandler = true; Logger.info('Initializing Firebase'); // try { @@ -209,4 +211,41 @@ class FirebaseUtils { return _storage.write(key: _NEW_APP_TOKEN_KEY, value: str); }); Future getNewFirebaseToken() => _protect(() => _storage.read(key: _NEW_APP_TOKEN_KEY)); + + Future initializeApp({required String name, required FirebaseOptions options}) => Firebase.initializeApp(name: name, options: options); +} + +/// This class just is used to disable Firebase for web builds. +class FirebaseUtilsWeb implements FirebaseUtils { + @override + bool _initializedHandler = false; + + @override + Future getFBToken() => Future.value(_currentFbToken); + + @override + Future initFirebase({ + required Future Function(RemoteMessage p1) foregroundHandler, + required Future Function(RemoteMessage p1) backgroundHandler, + required void Function(String? p1) updateFirebaseToken, + }) async {} + + @override + Future deleteFirebaseToken() => Future.value(true); + + static String _currentFbToken = 'currentFbToken'; + static String _newFbToken = 'newFbToken'; + + @override + Future setCurrentFirebaseToken(String str) async => _currentFbToken = str; + @override + Future getCurrentFirebaseToken() => Future.value(_currentFbToken); + + @override + Future setNewFirebaseToken(String str) async => _newFbToken = str; + @override + Future getNewFirebaseToken() => Future.value(_newFbToken); + + @override + Future initializeApp({required String name, required FirebaseOptions options}) async => null; } 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 664cd1aed..f96978288 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 @@ -34,7 +34,8 @@ import '../../../../../../../utils/privacyidea_io_client.dart'; import '../../../../../../../utils/riverpod/riverpod_providers/generated_providers/token_notifier.dart'; import '../../../../../../../utils/riverpod/riverpod_providers/state_providers/status_message_provider.dart'; import '../../../../../../../utils/view_utils.dart'; -import '../../../../api/privacy_idea_container_api.dart'; +import '../../../../api/impl/privacy_idea_container_api.dart'; +import '../../../../api/interfaces/container_api.dart'; import '../../../../interfaces/repo/token_container_repository.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../model/api_results/pi_server_results/pi_server_result_value.dart'; @@ -67,7 +68,7 @@ class TokenContainerNotifier extends _$TokenContainerNotifier with ResultHandler TokenContainerNotifier({ TokenContainerRepository? repoOverride, - PrivacyIdeaContainerApi? containerApiOverride, + ContainerApi? containerApiOverride, EccUtils? eccUtilsOverride, }) : _repoOverride = repoOverride, _containerApiOverride = containerApiOverride, diff --git a/test/tests_app_wrapper.dart b/test/tests_app_wrapper.dart index c6ac1f997..f1e6f2a16 100644 --- a/test/tests_app_wrapper.dart +++ b/test/tests_app_wrapper.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; -import 'package:privacyidea_authenticator/api/privacy_idea_container_api.dart'; +import 'package:privacyidea_authenticator/api/interfaces/container_api.dart'; import 'package:privacyidea_authenticator/interfaces/repo/introduction_repository.dart'; import 'package:privacyidea_authenticator/interfaces/repo/push_request_repository.dart'; import 'package:privacyidea_authenticator/interfaces/repo/settings_repository.dart'; @@ -23,7 +23,7 @@ import 'package:privacyidea_authenticator/utils/rsa_utils.dart'; MockSpec(), MockSpec(), MockSpec(), - MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), diff --git a/test/tests_app_wrapper.mocks.dart b/test/tests_app_wrapper.mocks.dart index 9ebfaab77..6a0ef84bf 100644 --- a/test/tests_app_wrapper.mocks.dart +++ b/test/tests_app_wrapper.mocks.dart @@ -6,12 +6,13 @@ import 'dart:async' as _i13; import 'dart:typed_data' as _i26; +import 'package:firebase_core/firebase_core.dart' as _i30; import 'package:firebase_messaging/firebase_messaging.dart' as _i29; import 'package:http/http.dart' as _i7; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i25; +import 'package:mockito/src/dummies.dart' as _i24; import 'package:pointycastle/export.dart' as _i8; -import 'package:privacyidea_authenticator/api/privacy_idea_container_api.dart' +import 'package:privacyidea_authenticator/api/interfaces/container_api.dart' as _i22; import 'package:privacyidea_authenticator/interfaces/repo/introduction_repository.dart' as _i17; @@ -39,15 +40,15 @@ import 'package:privacyidea_authenticator/model/riverpod_states/token_container_ import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart' as _i3; import 'package:privacyidea_authenticator/model/riverpod_states/token_state.dart' - as _i23; + as _i25; import 'package:privacyidea_authenticator/model/token_container.dart' as _i21; import 'package:privacyidea_authenticator/model/tokens/push_token.dart' as _i27; import 'package:privacyidea_authenticator/model/tokens/token.dart' as _i14; -import 'package:privacyidea_authenticator/utils/ecc_utils.dart' as _i24; +import 'package:privacyidea_authenticator/utils/ecc_utils.dart' as _i23; import 'package:privacyidea_authenticator/utils/firebase_utils.dart' as _i9; import 'package:privacyidea_authenticator/utils/privacyidea_io_client.dart' as _i10; -import 'package:privacyidea_authenticator/utils/push_provider.dart' as _i30; +import 'package:privacyidea_authenticator/utils/push_provider.dart' as _i31; import 'package:privacyidea_authenticator/utils/rsa_utils.dart' as _i11; // ignore_for_file: type=lint @@ -700,33 +701,14 @@ class MockTokenContainerRepository extends _i1.Mock ) as _i13.Future<_i6.TokenContainerState>); } -/// A class which mocks [PrivacyIdeaContainerApi]. +/// A class which mocks [ContainerApi]. /// /// See the documentation for Mockito's code generation for more information. -class MockPrivacyIdeaContainerApi extends _i1.Mock - implements _i22.PrivacyIdeaContainerApi { - @override - _i13.Future<(List<_i14.Token>, List)?> sync( - _i21.TokenContainerFinalized? container, - _i23.TokenState? tokenState, - ) => - (super.noSuchMethod( - Invocation.method( - #sync, - [ - container, - tokenState, - ], - ), - returnValue: _i13.Future<(List<_i14.Token>, List)?>.value(), - returnValueForMissingStub: - _i13.Future<(List<_i14.Token>, List)?>.value(), - ) as _i13.Future<(List<_i14.Token>, List)?>); - +class MockContainerApi extends _i1.Mock implements _i22.ContainerApi { @override _i13.Future<_i7.Response> finalizeContainer( _i21.TokenContainerUnfinalized? container, - _i24.EccUtils? eccUtils, + _i23.EccUtils? eccUtils, ) => (super.noSuchMethod( Invocation.method( @@ -767,7 +749,7 @@ class MockPrivacyIdeaContainerApi extends _i1.Mock #getTransferQrData, [container], ), - returnValue: _i13.Future.value(_i25.dummyValue( + returnValue: _i13.Future.value(_i24.dummyValue( this, Invocation.method( #getTransferQrData, @@ -775,7 +757,7 @@ class MockPrivacyIdeaContainerApi extends _i1.Mock ), )), returnValueForMissingStub: - _i13.Future.value(_i25.dummyValue( + _i13.Future.value(_i24.dummyValue( this, Invocation.method( #getTransferQrData, @@ -783,6 +765,24 @@ class MockPrivacyIdeaContainerApi extends _i1.Mock ), )), ) as _i13.Future); + + @override + _i13.Future<(List<_i14.Token>, List)?> sync( + _i21.TokenContainerFinalized? container, + _i25.TokenState? tokenState, + ) => + (super.noSuchMethod( + Invocation.method( + #sync, + [ + container, + tokenState, + ], + ), + returnValue: _i13.Future<(List<_i14.Token>, List)?>.value(), + returnValueForMissingStub: + _i13.Future<(List<_i14.Token>, List)?>.value(), + ) as _i13.Future<(List<_i14.Token>, List)?>); } /// A class which mocks [PrivacyideaIOClient]. @@ -931,14 +931,14 @@ class MockRsaUtils extends _i1.Mock implements _i11.RsaUtils { #serializeRSAPublicKeyPKCS1, [publicKey], ), - returnValue: _i25.dummyValue( + returnValue: _i24.dummyValue( this, Invocation.method( #serializeRSAPublicKeyPKCS1, [publicKey], ), ), - returnValueForMissingStub: _i25.dummyValue( + returnValueForMissingStub: _i24.dummyValue( this, Invocation.method( #serializeRSAPublicKeyPKCS1, @@ -977,14 +977,14 @@ class MockRsaUtils extends _i1.Mock implements _i11.RsaUtils { #serializeRSAPublicKeyPKCS8, [key], ), - returnValue: _i25.dummyValue( + returnValue: _i24.dummyValue( this, Invocation.method( #serializeRSAPublicKeyPKCS8, [key], ), ), - returnValueForMissingStub: _i25.dummyValue( + returnValueForMissingStub: _i24.dummyValue( this, Invocation.method( #serializeRSAPublicKeyPKCS8, @@ -1000,14 +1000,14 @@ class MockRsaUtils extends _i1.Mock implements _i11.RsaUtils { #serializeRSAPrivateKeyPKCS1, [key], ), - returnValue: _i25.dummyValue( + returnValue: _i24.dummyValue( this, Invocation.method( #serializeRSAPrivateKeyPKCS1, [key], ), ), - returnValueForMissingStub: _i25.dummyValue( + returnValueForMissingStub: _i24.dummyValue( this, Invocation.method( #serializeRSAPrivateKeyPKCS1, @@ -1118,7 +1118,7 @@ class MockRsaUtils extends _i1.Mock implements _i11.RsaUtils { dataToSign, ], ), - returnValue: _i25.dummyValue( + returnValue: _i24.dummyValue( this, Invocation.method( #createBase32Signature, @@ -1128,7 +1128,7 @@ class MockRsaUtils extends _i1.Mock implements _i11.RsaUtils { ], ), ), - returnValueForMissingStub: _i25.dummyValue( + returnValueForMissingStub: _i24.dummyValue( this, Invocation.method( #createBase32Signature, @@ -1161,7 +1161,7 @@ class MockRsaUtils extends _i1.Mock implements _i11.RsaUtils { /// A class which mocks [EccUtils]. /// /// See the documentation for Mockito's code generation for more information. -class MockEccUtils extends _i1.Mock implements _i24.EccUtils { +class MockEccUtils extends _i1.Mock implements _i23.EccUtils { @override String serializeECPublicKey(_i8.ECPublicKey? publicKey) => (super.noSuchMethod( @@ -1169,14 +1169,14 @@ class MockEccUtils extends _i1.Mock implements _i24.EccUtils { #serializeECPublicKey, [publicKey], ), - returnValue: _i25.dummyValue( + returnValue: _i24.dummyValue( this, Invocation.method( #serializeECPublicKey, [publicKey], ), ), - returnValueForMissingStub: _i25.dummyValue( + returnValueForMissingStub: _i24.dummyValue( this, Invocation.method( #serializeECPublicKey, @@ -1215,14 +1215,14 @@ class MockEccUtils extends _i1.Mock implements _i24.EccUtils { #serializeECPrivateKey, [ecPrivateKey], ), - returnValue: _i25.dummyValue( + returnValue: _i24.dummyValue( this, Invocation.method( #serializeECPrivateKey, [ecPrivateKey], ), ), - returnValueForMissingStub: _i25.dummyValue( + returnValueForMissingStub: _i24.dummyValue( this, Invocation.method( #serializeECPrivateKey, @@ -1267,7 +1267,7 @@ class MockEccUtils extends _i1.Mock implements _i24.EccUtils { message, ], ), - returnValue: _i25.dummyValue( + returnValue: _i24.dummyValue( this, Invocation.method( #signWithPrivateKey, @@ -1277,7 +1277,7 @@ class MockEccUtils extends _i1.Mock implements _i24.EccUtils { ], ), ), - returnValueForMissingStub: _i25.dummyValue( + returnValueForMissingStub: _i24.dummyValue( this, Invocation.method( #signWithPrivateKey, @@ -1399,12 +1399,30 @@ class MockFirebaseUtils extends _i1.Mock implements _i9.FirebaseUtils { returnValue: _i13.Future.value(), returnValueForMissingStub: _i13.Future.value(), ) as _i13.Future); + + @override + _i13.Future<_i30.FirebaseApp?> initializeApp({ + required String? name, + required _i30.FirebaseOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #initializeApp, + [], + { + #name: name, + #options: options, + }, + ), + returnValue: _i13.Future<_i30.FirebaseApp?>.value(), + returnValueForMissingStub: _i13.Future<_i30.FirebaseApp?>.value(), + ) as _i13.Future<_i30.FirebaseApp?>); } /// A class which mocks [PushProvider]. /// /// See the documentation for Mockito's code generation for more information. -class MockPushProvider extends _i1.Mock implements _i30.PushProvider { +class MockPushProvider extends _i1.Mock implements _i31.PushProvider { @override bool get pollingIsEnabled => (super.noSuchMethod( Invocation.getter(#pollingIsEnabled), 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 5585b8c95..c99f840a9 100644 --- a/test/unit_test/state_notifiers/token_container_notifier_test.dart +++ b/test/unit_test/state_notifiers/token_container_notifier_test.dart @@ -8,7 +8,6 @@ import 'package:privacyidea_authenticator/model/enums/ec_key_algorithm.dart'; import 'package:privacyidea_authenticator/model/riverpod_states/token_container_state.dart'; import 'package:privacyidea_authenticator/model/token_container.dart'; import 'package:privacyidea_authenticator/utils/ecc_utils.dart'; -import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_container_notifier.dart'; import '../../tests_app_wrapper.mocks.dart';