[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.
This commit is contained in:
Aleksey Belousov 2020-04-16 09:53:28 +03:00 committed by Alexander Boriskov
parent 485d506048
commit c97310b4ae
4 changed files with 96 additions and 16 deletions

View file

@ -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 = "<group>"; };
478F6FA723C5067C00054A53 /* MyReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyReviewView.swift; sourceTree = "<group>"; };
479603722446F17C00F3BDD0 /* User+AppleId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "User+AppleId.swift"; sourceTree = "<group>"; };
4796037424482E3800F3BDD0 /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = "<group>"; };
479D305522C627CB00D18278 /* PartnerBannerViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PartnerBannerViewController.xib; sourceTree = "<group>"; };
479D305922C62F4000D18278 /* MWMBookmarksBannerViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MWMBookmarksBannerViewController.xib; sourceTree = "<group>"; };
479D306322C664CE00D18278 /* MWMDownloadBannerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MWMDownloadBannerViewController.h; sourceTree = "<group>"; };
@ -3226,6 +3228,7 @@
34BBD6661F8273350070CA50 /* AuthorizationViewController.xib */,
47FA14D0230D52FC003DA979 /* PhoneNumberAuthorizationViewController.swift */,
479603722446F17C00F3BDD0 /* User+AppleId.swift */,
4796037424482E3800F3BDD0 /* KeychainStorage.swift */,
);
path = Authorization;
sourceTree = "<group>";
@ -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;
};

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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
}