Files
firebase-ios-sdk/FirebaseAuth/Tests/Unit/AuthKeychainServicesTests.swift
T
2023-11-21 21:25:23 -08:00

198 lines
7.2 KiB
Swift

// 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.
// TODO(ncooke3): Add documentation for manually configuring tests on macOS.
import Foundation
import XCTest
@testable import FirebaseAuth
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
class AuthKeychainServicesTests: XCTestCase {
static let accountPrefix = "firebase_auth_1_"
static let key = "ACCOUNT"
static let service = "SERVICE"
static let otherService = "OTHER_SERVICE"
static let data = "DATA"
static let otherData = "OTHER_DATA"
static var account: String {
accountPrefix + key
}
var keychain: AuthKeychainServices!
override func setUp() {
super.setUp()
#if (os(macOS) && !FIREBASE_AUTH_TESTING_USE_MACOS_KEYCHAIN) || SWIFT_PACKAGE
keychain = AuthKeychainServices(service: Self.service, storage: FakeAuthKeychainStorage())
#else
keychain = AuthKeychainServices(service: Self.service)
#endif // (os(macOS) && !FIREBASE_AUTH_TESTING_USE_MACOS_KEYCHAIN) || SWIFT_PACKAGE
}
func testReadNonexisting() throws {
setPassword(nil, account: Self.account, service: Self.service)
setPassword(nil, account: Self.key, service: nil) // Legacy form.
XCTAssertNil(try keychain.data(forKey: Self.key))
}
func testReadExisting() throws {
setPassword(Self.data, account: Self.account, service: Self.service)
XCTAssertEqual(try keychain.data(forKey: Self.key), Self.data.data(using: .utf8))
deletePassword(account: Self.account, service: Self.service)
}
func testNotReadOtherService() throws {
setPassword(nil, account: Self.account, service: Self.service)
setPassword(Self.data, account: Self.account, service: Self.otherService)
XCTAssertNil(try keychain.data(forKey: Self.key))
deletePassword(account: Self.account, service: Self.otherService)
}
func testWriteNonexisting() throws {
setPassword(nil, account: Self.account, service: Self.service)
XCTAssertNoThrow(try keychain.setData(Self.data.data(using: .utf8)!, forKey: Self.key))
XCTAssertEqual(password(for: Self.account, service: Self.service), Self.data)
deletePassword(account: Self.account, service: Self.service)
}
func testWriteExisting() throws {
setPassword(Self.data, account: Self.account, service: Self.service)
XCTAssertNoThrow(try keychain.setData(Self.otherData.data(using: .utf8)!, forKey: Self.key))
XCTAssertEqual(password(for: Self.account, service: Self.service), Self.otherData)
deletePassword(account: Self.account, service: Self.service)
}
func testDeleteNonexisting() {
setPassword(nil, account: Self.account, service: Self.service)
XCTAssertNoThrow(try keychain.removeData(forKey: Self.key))
XCTAssertNil(password(for: Self.account, service: Self.service))
}
func testDeleteExisting() throws {
setPassword(Self.data, account: Self.account, service: Self.service)
XCTAssertNoThrow(try keychain.removeData(forKey: Self.key))
XCTAssertNil(password(for: Self.account, service: Self.service))
}
func testReadLegacy() throws {
setPassword(nil, account: Self.account, service: Self.service)
setPassword(Self.data, account: Self.key, service: nil) // Legacy form.
XCTAssertEqual(
try keychain.data(forKey: Self.key), Self.data.data(using: .utf8)
)
// Legacy item should have been moved to current form.
XCTAssertEqual(
password(for: Self.account, service: Self.service),
Self.data
)
XCTAssertNil(password(for: Self.key, service: nil), Self.data)
deletePassword(account: Self.account, service: Self.service)
}
func testNotReadLegacy() throws {
setPassword(Self.data, account: Self.account, service: Self.service)
setPassword(Self.otherData, account: Self.key, service: nil) // Legacy form.
XCTAssertEqual(try keychain.data(forKey: Self.key), Self.data.data(using: .utf8)!)
// Legacy item should have leave untouched.
XCTAssertEqual(password(for: Self.account, service: Self.service), Self.data)
XCTAssertEqual(password(for: Self.key, service: nil), Self.otherData)
deletePassword(account: Self.account, service: Self.service)
deletePassword(account: Self.key, service: nil)
}
func testRemoveLegacy() throws {
setPassword(Self.data, account: Self.account, service: Self.service)
setPassword(Self.otherData, account: Self.key, service: nil) // Legacy form.
XCTAssertNoThrow(try keychain.removeData(forKey: Self.key))
XCTAssertNil(password(for: Self.account, service: Self.service))
XCTAssertNil(password(for: Self.key, service: nil))
}
func testNullErrorParameter() throws {
_ = try keychain.data(forKey: Self.key)
try keychain.setData(Self.data.data(using: .utf8)!, forKey: Self.key)
try keychain.removeData(forKey: Self.key)
}
// MARK: - Test Helpers
private func password(for account: String, service: String?) -> String? {
var query: [CFString: Any] = [
kSecReturnData: true,
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
]
if let service {
query[kSecAttrService] = service
}
var result: CFTypeRef?
let status = keychain.keychainStorage.get(query: query as [String: Any], result: &result)
guard let result = result as? Data, status != errSecItemNotFound else {
if let resultArray = result as? [[String: Any]],
let data = resultArray[0]["v_Data"] as? Data {
XCTAssertEqual(status, errSecSuccess)
return String(data: data, encoding: .utf8)
}
return nil
}
XCTAssertEqual(status, errSecSuccess)
return String(data: result, encoding: .utf8)
}
private func addPassword(_ password: String,
account: String,
service: String?) {
var query: [CFString: Any] = [
kSecValueData: password.data(using: .utf8)!,
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
]
if let service {
query[kSecAttrService] = service
}
XCTAssertEqual(keychain.keychainStorage.add(query: query as [String: Any]), errSecSuccess)
}
private func setPassword(_ password: String?,
account: String,
service: String?) {
if self.password(for: account, service: service) != nil {
deletePassword(account: account, service: service)
}
if let password {
addPassword(password, account: account, service: service)
}
}
private func deletePassword(account: String,
service: String?) {
var query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
]
if let service {
query[kSecAttrService] = service
}
XCTAssertEqual(keychain.keychainStorage.delete(query: query as [String: Any]), errSecSuccess)
}
}