diff --git a/lib/features/push_notification/data/keychain/keychain_sharing_manager.dart b/lib/features/push_notification/data/keychain/keychain_sharing_manager.dart new file mode 100644 index 000000000..4b267e8ca --- /dev/null +++ b/lib/features/push_notification/data/keychain/keychain_sharing_manager.dart @@ -0,0 +1,38 @@ + +import 'dart:convert'; + +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:model/extensions/account_id_extensions.dart'; +import 'package:tmail_ui_user/features/push_notification/data/keychain/keychain_sharing_session.dart'; + +class KeychainSharingManager { + final FlutterSecureStorage _secureStorage; + + KeychainSharingManager(this._secureStorage); + + Future save(KeychainSharingSession keychainSharingSession) => _secureStorage.write( + key: keychainSharingSession.accountId.asString, + value: jsonEncode(keychainSharingSession.toJson()), + ); + + Future isSessionExist(AccountId accountId) => + _secureStorage.containsKey(key: accountId.asString); + + Future getSharingSession(AccountId accountId) async { + final jsonData = await _secureStorage.read(key: accountId.asString); + if (jsonData?.isNotEmpty == true) { + return KeychainSharingSession.fromJson(jsonDecode(jsonData!)); + } else { + return null; + } + } + + Future delete({String? accountId}) { + if (accountId != null) { + return _secureStorage.delete(key: accountId); + } else { + return _secureStorage.deleteAll(); + } + } +} \ No newline at end of file diff --git a/lib/features/push_notification/data/keychain/keychain_sharing_session.dart b/lib/features/push_notification/data/keychain/keychain_sharing_session.dart new file mode 100644 index 000000000..90c7d7deb --- /dev/null +++ b/lib/features/push_notification/data/keychain/keychain_sharing_session.dart @@ -0,0 +1,48 @@ +import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/http/converter/account_id_converter.dart'; +import 'package:jmap_dart_client/http/converter/user_name_converter.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:model/account/authentication_type.dart'; +import 'package:model/oidc/token_oidc.dart'; + +part 'keychain_sharing_session.g.dart'; + +@UserNameConverter() +@AccountIdConverter() +@JsonSerializable(includeIfNull: false, explicitToJson: true) +class KeychainSharingSession with EquatableMixin { + AccountId accountId; + UserName userName; + AuthenticationType authenticationType; + String apiUrl; + String? emailState; + TokenOIDC? tokenOIDC; + String? basicAuth; + + KeychainSharingSession({ + required this.accountId, + required this.userName, + required this.authenticationType, + required this.apiUrl, + this.emailState, + this.tokenOIDC, + this.basicAuth, + }); + + factory KeychainSharingSession.fromJson(Map json) => _$KeychainSharingSessionFromJson(json); + + Map toJson() => _$KeychainSharingSessionToJson(this); + + @override + List get props => [ + accountId, + userName, + authenticationType, + apiUrl, + emailState, + tokenOIDC, + basicAuth, + ]; +} diff --git a/lib/main/utils/ios_sharing_manager.dart b/lib/main/utils/ios_sharing_manager.dart new file mode 100644 index 000000000..8b9d896e8 --- /dev/null +++ b/lib/main/utils/ios_sharing_manager.dart @@ -0,0 +1,127 @@ + +import 'dart:convert'; + +import 'package:core/utils/app_logger.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:model/account/personal_account.dart'; +import 'package:model/oidc/token_oidc.dart'; +import 'package:tmail_ui_user/features/login/data/local/authentication_info_cache_manager.dart'; +import 'package:tmail_ui_user/features/login/data/local/token_oidc_cache_manager.dart'; +import 'package:tmail_ui_user/features/mailbox/data/local/state_cache_manager.dart'; +import 'package:tmail_ui_user/features/mailbox/data/model/state_type.dart'; +import 'package:tmail_ui_user/features/push_notification/data/keychain/keychain_sharing_manager.dart'; +import 'package:tmail_ui_user/features/push_notification/data/keychain/keychain_sharing_session.dart'; + +class IOSSharingManager { + final KeychainSharingManager _keychainSharingManager; + final StateCacheManager _stateCacheManager; + final TokenOidcCacheManager _tokenOidcCacheManager; + final AuthenticationInfoCacheManager _authenticationInfoCacheManager; + + IOSSharingManager( + this._keychainSharingManager, + this._stateCacheManager, + this._tokenOidcCacheManager, + this._authenticationInfoCacheManager + ); + + bool _validateToSaveKeychain(PersonalAccount personalAccount) { + return personalAccount.accountId != null && + personalAccount.userName != null && + personalAccount.apiUrl != null; + } + + Future _getEmailDeliveryStateFromKeychain(AccountId accountId) async { + try { + if (await _keychainSharingManager.isSessionExist(accountId)) { + final keychainSharingStored = await _keychainSharingManager.getSharingSession(accountId); + return keychainSharingStored?.emailState; + } + return null; + } catch (e) { + logError('IOSSharingManager::_getEmailDeliveryStateFromKeychain: Exception: $e'); + return null; + } + } + + Future saveKeyChainSharingSession(PersonalAccount personalAccount) async { + try { + if (!_validateToSaveKeychain(personalAccount)) { + logError('IOSSharingManager::saveKeyChainSharingSession: account is null'); + return Future.value(null); + } + + Tuple2 authenticationInfo = await Future.wait( + [ + _getTokenOidc(tokeHashId: personalAccount.id), + _getCredentialAuthentication() + ], + eagerError: true + ).then((listValue) => Tuple2(listValue[0] as TokenOIDC?, listValue[1] as String?)); + + final emailDeliveryState = await _getEmailDeliveryState( + accountId: personalAccount.accountId!, + userName: personalAccount.userName! + ); + + final keychainSharingSession = KeychainSharingSession( + accountId: personalAccount.accountId!, + userName: personalAccount.userName!, + authenticationType: personalAccount.authenticationType, + apiUrl: personalAccount.apiUrl!, + emailState: emailDeliveryState, + tokenOIDC: authenticationInfo.value1, + basicAuth: authenticationInfo.value2 + ); + log('IOSSharingManager::_saveKeyChainSharingSession: $keychainSharingSession'); + await _keychainSharingManager.save(keychainSharingSession); + } catch (e) { + logError('IOSSharingManager::_saveKeyChainSharingSession: Exception: $e'); + } + } + + Future _getTokenOidc({required String tokeHashId}) async { + try { + final tokenOidc = await _tokenOidcCacheManager.getTokenOidc(tokeHashId); + log('IOSSharingManager::_getTokenOidc:tokenOidc: $tokenOidc'); + return tokenOidc; + } catch (e) { + logError('IOSSharingManager::_getTokenOidc:Exception: $e'); + return null; + } + } + + Future _getCredentialAuthentication() async { + try { + final credentialInfo = await _authenticationInfoCacheManager.getAuthenticationInfoStored(); + log('IOSSharingManager::_getCredentialAuthentication:credentialInfo: $credentialInfo'); + return base64Encode(utf8.encode('${credentialInfo.username}:${credentialInfo.password}')); + } catch (e) { + logError('IOSSharingManager::_getCredentialAuthentication:Exception: $e'); + return null; + } + } + + Future _getEmailDeliveryState({ + required AccountId accountId, + required UserName userName + }) async { + try { + String? emailDeliveryState = await _getEmailDeliveryStateFromKeychain(accountId); + if (emailDeliveryState == null || emailDeliveryState.isEmpty) { + final emailState = await _stateCacheManager.getState( + accountId, + userName, + StateType.email + ); + emailDeliveryState = emailState?.value; + } + return emailDeliveryState; + } catch (e) { + logError('IOSSharingManager::_getEmailDeliveryState:Exception: $e'); + return null; + } + } +} \ No newline at end of file