mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
315 lines
12 KiB
Objective-C
315 lines
12 KiB
Objective-C
/*
|
|
* Copyright 2017 Google
|
|
*
|
|
* 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 <Security/Security.h>
|
|
#import <XCTest/XCTest.h>
|
|
|
|
#import "FIRAuthKeychainServices.h"
|
|
|
|
/** @var kAccountPrefix
|
|
@brief The keychain account prefix assumed by the tests.
|
|
*/
|
|
static NSString *const kAccountPrefix = @"firebase_auth_1_";
|
|
|
|
/** @var kKey
|
|
@brief The key used in tests.
|
|
*/
|
|
static NSString *const kKey = @"ACCOUNT";
|
|
|
|
/** @var kService
|
|
@brief The keychain service used in tests.
|
|
*/
|
|
static NSString *const kService = @"SERVICE";
|
|
|
|
/** @var kOtherService
|
|
@brief Another keychain service used in tests.
|
|
*/
|
|
static NSString *const kOtherService = @"OTHER_SERVICE";
|
|
|
|
/** @var kData
|
|
@brief A piece of keychain data used in tests.
|
|
*/
|
|
static NSString *const kData = @"DATA";
|
|
|
|
/** @var kOtherData
|
|
@brief Another piece of keychain data used in tests.
|
|
*/
|
|
static NSString *const kOtherData = @"OTHER_DATA";
|
|
|
|
/** @fn accountFromKey
|
|
@brief Converts a key string to an account string.
|
|
@param key The key string to be converted from.
|
|
@return The account string being the conversion result.
|
|
*/
|
|
static NSString *accountFromKey(NSString *key) {
|
|
return [kAccountPrefix stringByAppendingString:key];
|
|
}
|
|
|
|
/** @fn dataFromString
|
|
@brief Converts a NSString to NSData.
|
|
@param string The NSString to be converted from.
|
|
@return The NSData being the conversion result.
|
|
*/
|
|
static NSData *dataFromString(NSString *string) {
|
|
return [string dataUsingEncoding:NSUTF8StringEncoding];
|
|
}
|
|
|
|
/** @fn stringFromData
|
|
@brief Converts a NSData to NSString.
|
|
@param data The NSData to be converted from.
|
|
@return The NSString being the conversion result.
|
|
*/
|
|
static NSString *stringFromData(NSData *data) {
|
|
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
}
|
|
|
|
/** @fn fakeError
|
|
@brief Creates a fake error object.
|
|
@return a non-nil NSError instance.
|
|
*/
|
|
static NSError *fakeError() {
|
|
return [NSError errorWithDomain:@"ERROR" code:-1 userInfo:nil];
|
|
}
|
|
|
|
/** @class FIRAuthKeychainTests
|
|
@brief Tests for @c FIRAuthKeychainTests .
|
|
*/
|
|
@interface FIRAuthKeychainTests : XCTestCase
|
|
@end
|
|
|
|
@implementation FIRAuthKeychainTests
|
|
|
|
/** @fn testReadNonexisting
|
|
@brief Tests reading non-existing keychain item.
|
|
*/
|
|
- (void)testReadNonexisting {
|
|
[self setPassword:nil account:accountFromKey(kKey) service:kService];
|
|
[self setPassword:nil account:kKey service:nil]; // legacy form
|
|
FIRAuthKeychainServices *keychain = [[FIRAuthKeychainServices alloc] initWithService:kService];
|
|
NSError *error = fakeError();
|
|
XCTAssertNil([keychain dataForKey:kKey error:&error]);
|
|
XCTAssertNil(error);
|
|
}
|
|
|
|
/** @fn testReadExisting
|
|
@brief Tests reading existing keychain item.
|
|
*/
|
|
- (void)testReadExisting {
|
|
[self setPassword:kData account:accountFromKey(kKey) service:kService];
|
|
FIRAuthKeychainServices *keychain = [[FIRAuthKeychainServices alloc] initWithService:kService];
|
|
NSError *error = fakeError();
|
|
XCTAssertEqualObjects([keychain dataForKey:kKey error:&error], dataFromString(kData));
|
|
XCTAssertNil(error);
|
|
[self deletePasswordWithAccount:accountFromKey(kKey) service:kService];
|
|
}
|
|
|
|
/** @fn testNotReadOtherService
|
|
@brief Tests not reading keychain item belonging to other service.
|
|
*/
|
|
- (void)testNotReadOtherService {
|
|
[self setPassword:nil account:accountFromKey(kKey) service:kService];
|
|
[self setPassword:kData account:accountFromKey(kKey) service:kOtherService];
|
|
FIRAuthKeychainServices *keychain = [[FIRAuthKeychainServices alloc] initWithService:kService];
|
|
NSError *error = fakeError();
|
|
XCTAssertNil([keychain dataForKey:kKey error:&error]);
|
|
XCTAssertNil(error);
|
|
[self deletePasswordWithAccount:accountFromKey(kKey) service:kOtherService];
|
|
}
|
|
|
|
/** @fn testWriteNonexisting
|
|
@brief Tests writing new keychain item.
|
|
*/
|
|
- (void)testWriteNonexisting {
|
|
[self setPassword:nil account:accountFromKey(kKey) service:kService];
|
|
FIRAuthKeychainServices *keychain = [[FIRAuthKeychainServices alloc] initWithService:kService];
|
|
XCTAssertTrue([keychain setData:dataFromString(kData) forKey:kKey error:NULL]);
|
|
XCTAssertEqualObjects([self passwordWithAccount:accountFromKey(kKey) service:kService], kData);
|
|
[self deletePasswordWithAccount:accountFromKey(kKey) service:kService];
|
|
}
|
|
|
|
/** @fn testWriteExisting
|
|
@brief Tests overwriting existing keychain item.
|
|
*/
|
|
- (void)testWriteExisting {
|
|
[self setPassword:kData account:accountFromKey(kKey) service:kService];
|
|
FIRAuthKeychainServices *keychain = [[FIRAuthKeychainServices alloc] initWithService:kService];
|
|
XCTAssertTrue([keychain setData:dataFromString(kOtherData) forKey:kKey error:NULL]);
|
|
XCTAssertEqualObjects([self passwordWithAccount:accountFromKey(kKey) service:kService],
|
|
kOtherData);
|
|
[self deletePasswordWithAccount:accountFromKey(kKey) service:kService];
|
|
}
|
|
|
|
/** @fn testDeleteNonexisting
|
|
@brief Tests deleting non-existing keychain item.
|
|
*/
|
|
- (void)testDeleteNonexisting {
|
|
[self setPassword:nil account:accountFromKey(kKey) service:kService];
|
|
FIRAuthKeychainServices *keychain = [[FIRAuthKeychainServices alloc] initWithService:kService];
|
|
XCTAssertTrue([keychain removeDataForKey:kKey error:NULL]);
|
|
XCTAssertNil([self passwordWithAccount:accountFromKey(kKey) service:kService]);
|
|
}
|
|
|
|
/** @fn testDeleteExisting
|
|
@brief Tests deleting existing keychain item.
|
|
*/
|
|
- (void)testDeleteExisting {
|
|
[self setPassword:kData account:accountFromKey(kKey) service:kService];
|
|
FIRAuthKeychainServices *keychain = [[FIRAuthKeychainServices alloc] initWithService:kService];
|
|
XCTAssertTrue([keychain removeDataForKey:kKey error:NULL]);
|
|
XCTAssertNil([self passwordWithAccount:accountFromKey(kKey) service:kService]);
|
|
}
|
|
|
|
/** @fn testReadLegacy
|
|
@brief Tests reading legacy keychain item.
|
|
*/
|
|
- (void)testReadLegacy {
|
|
[self setPassword:nil account:accountFromKey(kKey) service:kService];
|
|
[self setPassword:kData account:kKey service:nil]; // legacy form
|
|
FIRAuthKeychainServices *keychain = [[FIRAuthKeychainServices alloc] initWithService:kService];
|
|
NSError *error = fakeError();
|
|
XCTAssertEqualObjects([keychain dataForKey:kKey error:&error], dataFromString(kData));
|
|
XCTAssertNil(error);
|
|
// Legacy item should have been moved to current form.
|
|
XCTAssertEqualObjects([self passwordWithAccount:accountFromKey(kKey) service:kService], kData);
|
|
XCTAssertNil([self passwordWithAccount:kKey service:nil]);
|
|
[self deletePasswordWithAccount:accountFromKey(kKey) service:kService];
|
|
}
|
|
|
|
/** @fn testNotReadLegacy
|
|
@brief Tests not reading legacy keychain item because current keychain item exists.
|
|
*/
|
|
- (void)testNotReadLegacy {
|
|
[self setPassword:kData account:accountFromKey(kKey) service:kService];
|
|
[self setPassword:kOtherData account:kKey service:nil]; // legacy form
|
|
FIRAuthKeychainServices *keychain = [[FIRAuthKeychainServices alloc] initWithService:kService];
|
|
NSError *error = fakeError();
|
|
XCTAssertEqualObjects([keychain dataForKey:kKey error:&error], dataFromString(kData));
|
|
XCTAssertNil(error);
|
|
// Legacy item should have leave untouched.
|
|
XCTAssertEqualObjects([self passwordWithAccount:accountFromKey(kKey) service:kService], kData);
|
|
XCTAssertEqualObjects([self passwordWithAccount:kKey service:nil], kOtherData);
|
|
[self deletePasswordWithAccount:accountFromKey(kKey) service:kService];
|
|
[self deletePasswordWithAccount:kKey service:nil];
|
|
}
|
|
|
|
/** @fn testRemoveLegacy
|
|
@brief Tests removing keychain item also removes legacy keychain item.
|
|
*/
|
|
- (void)testRemoveLegacy {
|
|
[self setPassword:kData account:accountFromKey(kKey) service:kService];
|
|
[self setPassword:kOtherData account:kKey service:nil]; // legacy form
|
|
FIRAuthKeychainServices *keychain = [[FIRAuthKeychainServices alloc] initWithService:kService];
|
|
XCTAssertTrue([keychain removeDataForKey:kKey error:NULL]);
|
|
XCTAssertNil([self passwordWithAccount:accountFromKey(kKey) service:kService]);
|
|
XCTAssertNil([self passwordWithAccount:kKey service:nil]);
|
|
}
|
|
|
|
/** @fn testNullErrorParameter
|
|
@brief Tests that 'NULL' can be safely passed in.
|
|
*/
|
|
- (void)testNullErrorParameter {
|
|
FIRAuthKeychainServices *keychain = [[FIRAuthKeychainServices alloc] initWithService:kService];
|
|
[keychain dataForKey:kKey error:NULL];
|
|
[keychain setData:dataFromString(kData) forKey:kKey error:NULL];
|
|
[keychain removeDataForKey:kKey error:NULL];
|
|
}
|
|
|
|
#pragma mark - Helpers
|
|
|
|
/** @fn passwordWithAccount:service:
|
|
@brief Reads a generic password string from the keychain.
|
|
@param account The account attribute of the keychain item.
|
|
@param service The service attribute of the keychain item, if provided.
|
|
@return The generic password string, if the keychain item exists.
|
|
*/
|
|
- (nullable NSString *)passwordWithAccount:(nonnull NSString *)account
|
|
service:(nullable NSString *)service {
|
|
NSMutableDictionary *query = [@{
|
|
(__bridge id)kSecReturnData : @YES,
|
|
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
|
|
(__bridge id)kSecAttrAccount : account,
|
|
} mutableCopy];
|
|
if (service) {
|
|
query[(__bridge id)kSecAttrService] = service;
|
|
}
|
|
CFDataRef result;
|
|
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
|
|
if (status == errSecItemNotFound) {
|
|
return nil;
|
|
}
|
|
XCTAssertEqual(status, errSecSuccess);
|
|
return stringFromData((__bridge NSData *)(result));
|
|
}
|
|
|
|
/** @fn addPassword:account:service:
|
|
@brief Adds a generic password string to the keychain.
|
|
@param password The value attribute for the password to write to the keychain item.
|
|
@param account The account attribute of the keychain item.
|
|
@param service The service attribute of the keychain item, if provided.
|
|
*/
|
|
- (void)addPassword:(nonnull NSString *)password
|
|
account:(nonnull NSString *)account
|
|
service:(nullable NSString *)service {
|
|
NSMutableDictionary *query = [@{
|
|
(__bridge id)kSecValueData : dataFromString(password),
|
|
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
|
|
(__bridge id)kSecAttrAccount : account,
|
|
} mutableCopy];
|
|
if (service) {
|
|
query[(__bridge id)kSecAttrService] = service;
|
|
}
|
|
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
|
|
XCTAssertEqual(status, errSecSuccess);
|
|
}
|
|
|
|
/** @fn deletePasswordWithAccount:service:
|
|
@brief Deletes a generic password string from the keychain.
|
|
@param account The account attribute of the keychain item.
|
|
@param service The service attribute of the keychain item, if provided.
|
|
*/
|
|
- (void)deletePasswordWithAccount:(nonnull NSString *)account
|
|
service:(nullable NSString *)service {
|
|
NSMutableDictionary *query = [@{
|
|
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
|
|
(__bridge id)kSecAttrAccount : account,
|
|
} mutableCopy];
|
|
if (service) {
|
|
query[(__bridge id)kSecAttrService] = service;
|
|
}
|
|
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
|
|
XCTAssertEqual(status, errSecSuccess);
|
|
}
|
|
|
|
/** @fn setPasswordWithString:account:service:
|
|
@brief Sets a generic password string to the keychain.
|
|
@param password The value attribute of the keychain item, if provided, or nil to delete the
|
|
existing password if any.
|
|
@param account The account attribute of the keychain item.
|
|
@param service The service attribute of the keychain item, if provided.
|
|
*/
|
|
- (void)setPassword:(nullable NSString *)password
|
|
account:(nonnull NSString *)account
|
|
service:(nullable NSString *)service {
|
|
if ([self passwordWithAccount:account service:service]) {
|
|
[self deletePasswordWithAccount:account service:service];
|
|
}
|
|
if (password) {
|
|
[self addPassword:password account:account service:service];
|
|
}
|
|
}
|
|
|
|
@end
|