| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- // Copyright 2023 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- import Foundation
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- class AuthStoredUserManager {
- /// Key of user access group stored in user defaults. Used for retrieve the
- /// user access group at launch.
- private static let storedUserAccessGroupKey = "firebase_auth_stored_user_access_group"
- /// Default value for kSecAttrAccount of shared keychain items.
- private static let sharedKeychainAccountValue = "firebase_auth_firebase_user"
- /// The key to encode and decode the stored user.
- private static let storedUserCoderKey = "firebase_auth_stored_user_coder_key"
- /// Mediator object used to access the keychain.
- private let keychainServices: AuthKeychainServices
- /// Mediator object used to access user defaults.
- private let userDefaults: AuthUserDefaults
- /// Designated initializer.
- /// - Parameter serviceName: The service name to initialize with.
- /// - Parameter keychainServices: The keychain manager (or a fake in unit tests)
- init(serviceName: String, keychainServices: AuthKeychainServices) {
- userDefaults = AuthUserDefaults(service: serviceName)
- self.keychainServices = keychainServices
- }
- /// Get the user access group stored locally.
- /// - Returns: The stored user access group; otherwise, `nil`.
- func getStoredUserAccessGroup() -> String? {
- if let data = try? userDefaults.data(forKey: Self.storedUserAccessGroupKey) {
- let userAccessGroup = String(data: data, encoding: .utf8)
- return userAccessGroup
- } else {
- return nil
- }
- }
- /// The setter of the user access group stored locally.
- /// - Parameter accessGroup: The access group to be store.
- func setStoredUserAccessGroup(accessGroup: String?) {
- if let data = accessGroup?.data(using: .utf8) {
- try? userDefaults.setData(data, forKey: Self.storedUserAccessGroupKey)
- } else {
- try? userDefaults.removeData(forKey: Self.storedUserAccessGroupKey)
- }
- }
- // MARK: - User for Access Group
- /// The getter of the user stored locally.
- /// - Parameters:
- /// - accessGroup: The access group to retrieve the user from.
- /// - shareAuthStateAcrossDevices: If `true`, the keychain will be synced
- /// across the end-user's iCloud.
- /// - projectIdentifier: An identifier of the project that the user
- /// associates with.
- /// - Returns: The stored user for the given attributes.
- /// - Throws: An error if the operation failed.
- func getStoredUser(accessGroup: String,
- shareAuthStateAcrossDevices: Bool,
- projectIdentifier: String) throws -> User? {
- let query = keychainQuery(
- accessGroup: accessGroup,
- shareAuthStateAcrossDevices: shareAuthStateAcrossDevices,
- projectIdentifier: projectIdentifier
- )
- guard let data = try keychainServices.getItem(query: query) else {
- return nil
- }
- let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
- return unarchiver.decodeObject(of: User.self, forKey: Self.storedUserCoderKey)
- }
- /// The setter of the user stored locally.
- /// - Parameters:
- /// - user: The user to be stored.
- /// - accessGroup: The access group to store the user in.
- /// - shareAuthStateAcrossDevices: If `true`, the keychain will be
- /// synced across the end-user's iCloud.
- /// - projectIdentifier: An identifier of the project that the user
- /// associates with.
- /// - Throws: An error if the operation failed.
- func setStoredUser(user: User,
- accessGroup: String,
- shareAuthStateAcrossDevices: Bool,
- projectIdentifier: String) throws {
- var query = keychainQuery(
- accessGroup: accessGroup,
- shareAuthStateAcrossDevices: shareAuthStateAcrossDevices,
- projectIdentifier: projectIdentifier
- )
- if shareAuthStateAcrossDevices {
- query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
- } else {
- query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
- }
- // TODO(ncooke3): The Objective-C code has an #if for watchOS here.
- // Does this work for watchOS?
- let archiver = NSKeyedArchiver(requiringSecureCoding: false)
- archiver.encode(user, forKey: Self.storedUserCoderKey)
- archiver.finishEncoding()
- try keychainServices.setItem(archiver.encodedData, withQuery: query)
- }
- /// Remove the user that stored locally.
- /// - Parameters:
- /// - accessGroup: The access group to remove the user from.
- /// - shareAuthStateAcrossDevices: If `true`, the keychain will be
- /// synced across the end-user's iCloud.
- /// - projectIdentifier: An identifier of the project that the user
- /// associates with.
- /// - Throws: An error if the operation failed.
- func removeStoredUser(accessGroup: String,
- shareAuthStateAcrossDevices: Bool,
- projectIdentifier: String) throws {
- var query = keychainQuery(
- accessGroup: accessGroup,
- shareAuthStateAcrossDevices: shareAuthStateAcrossDevices,
- projectIdentifier: projectIdentifier
- )
- if shareAuthStateAcrossDevices {
- query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
- } else {
- query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
- }
- try keychainServices.removeItem(query: query)
- }
- // MARK: - Private Helpers
- private func keychainQuery(accessGroup: String,
- shareAuthStateAcrossDevices: Bool,
- projectIdentifier: String) -> [String: Any] {
- var query: [String: Any] = [
- kSecClass as String: kSecClassGenericPassword,
- kSecAttrAccessGroup as String: accessGroup,
- kSecAttrService as String: projectIdentifier,
- kSecAttrAccount as String: Self.sharedKeychainAccountValue,
- ]
- query[kSecUseDataProtectionKeychain as String] = true
- return query
- }
- }
|