Skip to content
This repository has been archived by the owner on Jan 17, 2023. It is now read-only.

Add support for Keychain sharing #115

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions AFOAuth2Manager/AFOAuth2Manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

#import <AFNetworking/AFHTTPRequestOperationManager.h>

NS_ASSUME_NONNULL_BEGIN

@class AFOAuthCredential;

/**
Expand Down Expand Up @@ -253,44 +255,48 @@
Stores the specified OAuth credential for a given web service identifier in the Keychain.
with the default Keychain Accessibilty of kSecAttrAccessibleWhenUnlocked.

@param credential The OAuth credential to be stored.
@param identifier The service identifier associated with the specified credential.
@param credential The OAuth credential to be stored.
@param identifier The service identifier associated with the specified credential.
@param accessGroup The optional access group for shared Keychains.

@return Whether or not the credential was stored in the keychain.
*/
+ (BOOL)storeCredential:(AFOAuthCredential *)credential
withIdentifier:(NSString *)identifier;
+ (BOOL)storeCredential:(AFOAuthCredential *)credential withIdentifier:(NSString *)identifier
accessGroup:(nullable NSString *)accessGroup;

/**
Stores the specified OAuth token for a given web service identifier in the Keychain.

@param credential The OAuth credential to be stored.
@param identifier The service identifier associated with the specified token.
@param securityAccessibility The Keychain security accessibility to store the credential with.
@param accessGroup The optional access group for shared Keychains.

@return Whether or not the credential was stored in the keychain.
*/
+ (BOOL)storeCredential:(AFOAuthCredential *)credential
withIdentifier:(NSString *)identifier
withAccessibility:(id)securityAccessibility;
+ (BOOL)storeCredential:(AFOAuthCredential *)credential withIdentifier:(NSString *)identifier
withAccessibility:(id)securityAccessibility accessGroup:(nullable NSString *)accessGroup;

/**
Retrieves the OAuth credential stored with the specified service identifier from the Keychain.

@param identifier The service identifier associated with the specified credential.
@param accessGroup The optional access group for shared Keychains.

@return The retrieved OAuth credential.
*/
+ (AFOAuthCredential *)retrieveCredentialWithIdentifier:(NSString *)identifier;
+ (nullable AFOAuthCredential *)retrieveCredentialWithIdentifier:(NSString *)identifier
accessGroup:(nullable NSString *)accessGroup;

/**
Deletes the OAuth credential stored with the specified service identifier from the Keychain.

@param identifier The service identifier associated with the specified credential.
@param accessGroup The optional access group for shared Keychains.

@return Whether or not the credential was deleted from the keychain.
*/
+ (BOOL)deleteCredentialWithIdentifier:(NSString *)identifier;
+ (BOOL)deleteCredentialWithIdentifier:(NSString *)identifier accessGroup:(nullable NSString *)accessGroup;

@end

Expand Down Expand Up @@ -323,3 +329,5 @@ extern NSString * const kAFOAuthRefreshGrantType;

@compatibility_alias AFOAuth2Client AFOAuth2Manager;
@compatibility_alias AFOAuth2RequestOperationManager AFOAuth2Manager;

NS_ASSUME_NONNULL_END
67 changes: 39 additions & 28 deletions AFOAuth2Manager/AFOAuth2Manager.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,20 @@

NSString * const kAFOAuth2CredentialServiceName = @"AFOAuthCredentialService";

static NSDictionary * AFKeychainQueryDictionaryWithIdentifier(NSString *identifier) {
static NSDictionary *AFKeychainQueryDictionaryWithIdentifier(NSString *identifier, NSString *_Nullable accessGroup) {
NSCParameterAssert(identifier);

return @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: kAFOAuth2CredentialServiceName,
(__bridge id)kSecAttrAccount: identifier
};
NSMutableDictionary *dictionary = [@{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: kAFOAuth2CredentialServiceName,
(__bridge id)kSecAttrAccount: identifier
} mutableCopy];

if (accessGroup) {
dictionary[(__bridge id)kSecAttrAccessGroup] = accessGroup;
}

return [dictionary copy];
}

// See: http://tools.ietf.org/html/rfc6749#section-5.2
Expand Down Expand Up @@ -106,15 +112,15 @@ - (id)initWithBaseURL:(NSURL *)url
if (!self) {
return nil;
}

self.serviceProviderIdentifier = [self.baseURL host];
self.clientID = clientID;
self.secret = secret;

self.useHTTPBasicAuthentication = YES;

[self.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Accept"];

return self;
}

Expand Down Expand Up @@ -269,7 +275,7 @@ - (AFHTTPRequestOperation *)authenticateUsingOAuthWithURLString:(NSString *)URLS
failure(error);
}
}];

return requestOperation;
}

Expand All @@ -289,6 +295,17 @@ @implementation AFOAuthCredential

#pragma mark -

- (NSString *)description {
NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: ", NSStringFromClass([self class])];
[description appendFormat:@"self.accessToken=%@", self.accessToken];
[description appendFormat:@", self.tokenType=%@", self.tokenType];
[description appendFormat:@", self.refreshToken=%@", self.refreshToken];
[description appendFormat:@", self.expired=%d", self.expired];
[description appendFormat:@", self.expiration=%@", self.expiration];
[description appendString:@">"];
return description;
}

+ (instancetype)credentialWithOAuthToken:(NSString *)token
tokenType:(NSString *)type
{
Expand All @@ -309,10 +326,6 @@ - (id)initWithOAuthToken:(NSString *)token
return self;
}

- (NSString *)description {
return [NSString stringWithFormat:@"<%@ accessToken:\"%@\" tokenType:\"%@\" refreshToken:\"%@\" expiration:\"%@\">", [self class], self.accessToken, self.tokenType, self.refreshToken, self.expiration];
}

- (void)setRefreshToken:(NSString *)refreshToken
{
_refreshToken = refreshToken;
Expand All @@ -339,9 +352,8 @@ - (BOOL)isExpired {

#pragma mark Keychain

+ (BOOL)storeCredential:(AFOAuthCredential *)credential
withIdentifier:(NSString *)identifier
{
+ (BOOL)storeCredential:(AFOAuthCredential *)credential withIdentifier:(NSString *)identifier
accessGroup:(NSString *)accessGroup {
id securityAccessibility = nil;
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 43000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)
#pragma clang diagnostic push
Expand All @@ -352,17 +364,16 @@ + (BOOL)storeCredential:(AFOAuthCredential *)credential
#pragma clang diagnostic pop
#endif

return [[self class] storeCredential:credential withIdentifier:identifier withAccessibility:securityAccessibility];
return [[self class] storeCredential:credential withIdentifier:identifier withAccessibility:securityAccessibility
accessGroup:accessGroup];
}

+ (BOOL)storeCredential:(AFOAuthCredential *)credential
withIdentifier:(NSString *)identifier
withAccessibility:(id)securityAccessibility
{
NSMutableDictionary *queryDictionary = [AFKeychainQueryDictionaryWithIdentifier(identifier) mutableCopy];
+ (BOOL)storeCredential:(AFOAuthCredential *)credential withIdentifier:(NSString *)identifier
withAccessibility:(id)securityAccessibility accessGroup:(NSString *)accessGroup {
NSMutableDictionary *queryDictionary = [AFKeychainQueryDictionaryWithIdentifier(identifier, accessGroup) mutableCopy];

if (!credential) {
return [self deleteCredentialWithIdentifier:identifier];
return [self deleteCredentialWithIdentifier:identifier accessGroup:accessGroup];
}

NSMutableDictionary *updateDictionary = [NSMutableDictionary dictionary];
Expand All @@ -373,7 +384,7 @@ + (BOOL)storeCredential:(AFOAuthCredential *)credential
}

OSStatus status;
BOOL exists = ([self retrieveCredentialWithIdentifier:identifier] != nil);
BOOL exists = ([self retrieveCredentialWithIdentifier:identifier accessGroup:accessGroup] != nil);

if (exists) {
status = SecItemUpdate((__bridge CFDictionaryRef)queryDictionary, (__bridge CFDictionaryRef)updateDictionary);
Expand All @@ -385,16 +396,16 @@ + (BOOL)storeCredential:(AFOAuthCredential *)credential
return (status == errSecSuccess);
}

+ (BOOL)deleteCredentialWithIdentifier:(NSString *)identifier {
NSMutableDictionary *queryDictionary = [AFKeychainQueryDictionaryWithIdentifier(identifier) mutableCopy];
+ (BOOL)deleteCredentialWithIdentifier:(NSString *)identifier accessGroup:(NSString *)accessGroup {
NSMutableDictionary *queryDictionary = [AFKeychainQueryDictionaryWithIdentifier(identifier, accessGroup) mutableCopy];

OSStatus status = SecItemDelete((__bridge CFDictionaryRef)queryDictionary);

return (status == errSecSuccess);
}

+ (AFOAuthCredential *)retrieveCredentialWithIdentifier:(NSString *)identifier {
NSMutableDictionary *queryDictionary = [AFKeychainQueryDictionaryWithIdentifier(identifier) mutableCopy];
+ (AFOAuthCredential *)retrieveCredentialWithIdentifier:(NSString *)identifier accessGroup:(NSString *)accessGroup {
NSMutableDictionary *queryDictionary = [AFKeychainQueryDictionaryWithIdentifier(identifier, accessGroup) mutableCopy];
queryDictionary[(__bridge id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
queryDictionary[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;

Expand Down