From c97310b4aec302213d51cf6eb7f72b002510116b Mon Sep 17 00:00:00 2001 From: Aleksey Belousov Date: Thu, 16 Apr 2020 09:53:28 +0300 Subject: [PATCH] [iOS] keep Sign in with Apple data in keychain Sign in with Apple returns requested user data at first login only. If we were unable to process login to maps.me, we'll use saved data for subsequent login attempts. --- iphone/Maps/Maps.xcodeproj/project.pbxproj | 4 ++ .../AuthorizationViewController.swift | 20 +++---- .../UI/Authorization/KeychainStorage.swift | 57 +++++++++++++++++++ .../Maps/UI/Authorization/User+AppleId.swift | 31 ++++++++-- 4 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 iphone/Maps/UI/Authorization/KeychainStorage.swift diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index 72def3a42d..ac68d616b7 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -371,6 +371,7 @@ 478F6FA623C4521F00054A53 /* MoreReviewsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478F6FA523C4521F00054A53 /* MoreReviewsViewController.swift */; }; 478F6FA823C5067C00054A53 /* MyReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478F6FA723C5067C00054A53 /* MyReviewView.swift */; }; 479603732446F17C00F3BDD0 /* User+AppleId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 479603722446F17C00F3BDD0 /* User+AppleId.swift */; }; + 4796037524482E3900F3BDD0 /* KeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4796037424482E3800F3BDD0 /* KeychainStorage.swift */; }; 479D305722C627CB00D18278 /* PartnerBannerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 479D305522C627CB00D18278 /* PartnerBannerViewController.xib */; }; 479D305B22C62F4000D18278 /* MWMBookmarksBannerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 479D305922C62F4000D18278 /* MWMBookmarksBannerViewController.xib */; }; 479D306522C664CE00D18278 /* MWMDownloadBannerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 479D306422C664CE00D18278 /* MWMDownloadBannerViewController.m */; }; @@ -1474,6 +1475,7 @@ 478F6FA523C4521F00054A53 /* MoreReviewsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreReviewsViewController.swift; sourceTree = ""; }; 478F6FA723C5067C00054A53 /* MyReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyReviewView.swift; sourceTree = ""; }; 479603722446F17C00F3BDD0 /* User+AppleId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "User+AppleId.swift"; sourceTree = ""; }; + 4796037424482E3800F3BDD0 /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = ""; }; 479D305522C627CB00D18278 /* PartnerBannerViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PartnerBannerViewController.xib; sourceTree = ""; }; 479D305922C62F4000D18278 /* MWMBookmarksBannerViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MWMBookmarksBannerViewController.xib; sourceTree = ""; }; 479D306322C664CE00D18278 /* MWMDownloadBannerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMDownloadBannerViewController.h; sourceTree = ""; }; @@ -3226,6 +3228,7 @@ 34BBD6661F8273350070CA50 /* AuthorizationViewController.xib */, 47FA14D0230D52FC003DA979 /* PhoneNumberAuthorizationViewController.swift */, 479603722446F17C00F3BDD0 /* User+AppleId.swift */, + 4796037424482E3800F3BDD0 /* KeychainStorage.swift */, ); path = Authorization; sourceTree = ""; @@ -5835,6 +5838,7 @@ F5BD255A0838E70EC012748E /* DiscoverySearchCell.swift in Sources */, 47C7F9732191E15A00C2760C /* InAppBilling.swift in Sources */, F5BD2CA4DBEFACBC48195F39 /* DiscoveryCollectionHolderCell.swift in Sources */, + 4796037524482E3900F3BDD0 /* KeychainStorage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iphone/Maps/UI/Authorization/AuthorizationViewController.swift b/iphone/Maps/UI/Authorization/AuthorizationViewController.swift index cad1cce698..d901c4921c 100644 --- a/iphone/Maps/UI/Authorization/AuthorizationViewController.swift +++ b/iphone/Maps/UI/Authorization/AuthorizationViewController.swift @@ -242,8 +242,7 @@ final class AuthorizationViewController: MWMViewController { private func process(token: String, type: SocialTokenType, firstName: String = "", - lastName: String = "", - completion: ((_ success: Bool) -> Void)? = nil) { + lastName: String = "") { Statistics.logEvent(kStatUGCReviewAuthExternalRequestSuccess, withParameters: [kStatProvider: getProviderStatStr(type: type)]) User.authenticate(withToken: token, @@ -254,7 +253,6 @@ final class AuthorizationViewController: MWMViewController { firstName: firstName, lastName: lastName, source: sourceComponent) { success in - completion?(success) self.logStats(type: type, success: success) if success { self.successHandler?(type) @@ -325,14 +323,14 @@ extension AuthorizationViewController: ASAuthorizationControllerDelegate { switch authorization.credential { case let appleIDCredential as ASAuthorizationAppleIDCredential: guard let token = appleIDCredential.identityToken, - let tokenString = String(data: token, encoding: .utf8), - let fullName = appleIDCredential.fullName else { return } - let appleId = appleIDCredential.user - process(token: tokenString, type: .apple, firstName: fullName.givenName ?? "", lastName: fullName.familyName ?? "") { - if $0 { - User.setAppleId(appleId) - } - } + let tokenString = String(data: token, encoding: .utf8) else { return } + let fullName = appleIDCredential.fullName + let userId = appleIDCredential.user + let appleId = User.getAppleId() + let firstName = fullName?.givenName ?? appleId?.firstName ?? "" + let lastName = fullName?.familyName ?? appleId?.lastName ?? "" + User.setAppleId(AppleId(userId: userId, firstName: firstName, lastName: lastName)) + process(token: tokenString, type: .apple, firstName: firstName, lastName: lastName) default: break } diff --git a/iphone/Maps/UI/Authorization/KeychainStorage.swift b/iphone/Maps/UI/Authorization/KeychainStorage.swift new file mode 100644 index 0000000000..d67c4bf136 --- /dev/null +++ b/iphone/Maps/UI/Authorization/KeychainStorage.swift @@ -0,0 +1,57 @@ +import Foundation + +class KeychainStorage { + static var shared = { + KeychainStorage() + }() + + private init() { } + + func set(_ value: String, forKey key: String) { + var query = self.query(forKey: key) + + if let _ = string(forKey: key) { + var attributesToUpdate = [String: AnyObject]() + attributesToUpdate[kSecValueData as String] = value.data(using: .utf8) as AnyObject + SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary) + return + } + + query[kSecValueData as String] = value.data(using: .utf8) as AnyObject + SecItemAdd(query as CFDictionary, nil) + } + + func string(forKey key: String) -> String? { + var query = self.query(forKey: key) + query[kSecMatchLimit as String] = kSecMatchLimitOne + query[kSecReturnAttributes as String] = kCFBooleanTrue + query[kSecReturnData as String] = kCFBooleanTrue + + var keychainData: AnyObject? + let status = withUnsafeMutablePointer(to: &keychainData) { + SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) + } + + guard status == noErr, + let keychainItem = keychainData as? [String: AnyObject], + let stringData = keychainItem[kSecValueData as String] as? Data, + let string = String(data: stringData, encoding: String.Encoding.utf8) else { + return nil + } + + return string + } + + func deleteString(forKey key: String) { + let query = self.query(forKey: key) + SecItemDelete(query as CFDictionary) + } + + private func query(forKey key: String) -> [String: AnyObject] { + var query = [String: AnyObject]() + query[kSecClass as String] = kSecClassGenericPassword + query[kSecAttrService as String] = "com.mapswithme.full" as AnyObject + query[kSecAttrAccount as String] = key as AnyObject + return query + } +} diff --git a/iphone/Maps/UI/Authorization/User+AppleId.swift b/iphone/Maps/UI/Authorization/User+AppleId.swift index 4f93c8f138..ca38e75a2c 100644 --- a/iphone/Maps/UI/Authorization/User+AppleId.swift +++ b/iphone/Maps/UI/Authorization/User+AppleId.swift @@ -2,22 +2,43 @@ import Foundation fileprivate enum Const { static let kAppleIdKey = "kAppleIdKey" + static let kAppleIdFirstName = "kAppleIdFirstName" + static let kAppleIdLastName = "kAppleIdLastName" +} + +struct AppleId { + let userId: String + let firstName: String + let lastName: String } @available(iOS 13.0, *) extension User { - static func setAppleId(_ appleId: String) { - UserDefaults.standard.set(appleId, forKey: Const.kAppleIdKey) + static func setAppleId(_ appleId: AppleId) { + KeychainStorage.shared.set(appleId.userId, forKey: Const.kAppleIdKey) + KeychainStorage.shared.set(appleId.firstName, forKey: Const.kAppleIdFirstName) + KeychainStorage.shared.set(appleId.lastName, forKey: Const.kAppleIdLastName) + } + + static func getAppleId() -> AppleId? { + guard let userId = KeychainStorage.shared.string(forKey: Const.kAppleIdKey), + let firstName = KeychainStorage.shared.string(forKey: Const.kAppleIdFirstName), + let lastName = KeychainStorage.shared.string(forKey: Const.kAppleIdLastName) else { + return nil + } + return AppleId(userId: userId, firstName: firstName, lastName: lastName) } @objc static func verifyAppleId() { - guard let appleId = UserDefaults.standard.string(forKey: Const.kAppleIdKey) else { return } + guard let userId = KeychainStorage.shared.string(forKey: Const.kAppleIdKey) else { return } let appleIDProvider = ASAuthorizationAppleIDProvider() - appleIDProvider.getCredentialState(forUserID: appleId) { (state, error) in + appleIDProvider.getCredentialState(forUserID: userId) { (state, error) in switch state { case .revoked, .notFound: logOut() - UserDefaults.standard.set(nil, forKey: Const.kAppleIdKey) + KeychainStorage.shared.deleteString(forKey: Const.kAppleIdKey) + KeychainStorage.shared.deleteString(forKey: Const.kAppleIdFirstName) + KeychainStorage.shared.deleteString(forKey: Const.kAppleIdLastName) default: break }