adding pods method of package managing
This commit is contained in:
+46
@@ -0,0 +1,46 @@
|
||||
/*! @file GTMAppAuthFetcherAuthorization+Keychain.m
|
||||
@brief GTMAppAuth SDK
|
||||
@copyright
|
||||
Copyright 2016 Google Inc.
|
||||
@copydetails
|
||||
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 "GTMAppAuth/Sources/Public/GTMAppAuth/GTMAppAuthFetcherAuthorization+Keychain.h"
|
||||
|
||||
#import "GTMAppAuth/Sources/Public/GTMAppAuth/GTMKeychain.h"
|
||||
|
||||
@implementation GTMAppAuthFetcherAuthorization (Keychain)
|
||||
|
||||
+ (GTMAppAuthFetcherAuthorization *)authorizationFromKeychainForName:(NSString *)keychainItemName {
|
||||
NSData *passwordData = [GTMKeychain passwordDataFromKeychainForName:keychainItemName];
|
||||
if (!passwordData) {
|
||||
return nil;
|
||||
}
|
||||
GTMAppAuthFetcherAuthorization *authorization = (GTMAppAuthFetcherAuthorization *)
|
||||
[NSKeyedUnarchiver unarchiveObjectWithData:passwordData];
|
||||
return authorization;
|
||||
}
|
||||
|
||||
+ (BOOL)removeAuthorizationFromKeychainForName:(NSString *)keychainItemName {
|
||||
return [GTMKeychain removePasswordFromKeychainForName:keychainItemName];
|
||||
}
|
||||
|
||||
+ (BOOL)saveAuthorization:(GTMAppAuthFetcherAuthorization *)auth
|
||||
toKeychainForName:(NSString *)keychainItemName {
|
||||
NSData *authorizationData = [NSKeyedArchiver archivedDataWithRootObject:auth];
|
||||
return [GTMKeychain savePasswordDataToKeychainForName:keychainItemName
|
||||
passwordData:authorizationData];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,494 @@
|
||||
/*! @file GTMAppAuthFetcherAuthorization.m
|
||||
@brief GTMAppAuth SDK
|
||||
@copyright
|
||||
Copyright 2016 Google Inc.
|
||||
@copydetails
|
||||
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 "GTMAppAuth/Sources/Public/GTMAppAuth/GTMAppAuthFetcherAuthorization.h"
|
||||
|
||||
#if SWIFT_PACKAGE || GTMAPPAUTH_USE_MODULAR_IMPORT
|
||||
@import AppAuthCore;
|
||||
#elif GTMAPPAUTH_USER_IMPORTS
|
||||
#import "AppAuthCore.h"
|
||||
#else
|
||||
#import <AppAuth/AppAuthCore.h>
|
||||
#endif
|
||||
|
||||
#define GTMOAuth2AssertValidSelector GTMBridgeAssertValidSelector
|
||||
|
||||
/*! @brief Provides a template implementation for init-family methods which have been marked as
|
||||
NS_UNAVILABLE. Stops the compiler from giving a warning when it's the super class'
|
||||
designated initializer, and gives callers useful feedback telling them what the
|
||||
new designated initializer is.
|
||||
@remarks Takes a SEL as a parameter instead of a string so that we get compiler warnings if the
|
||||
designated initializer's signature changes.
|
||||
@param designatedInitializer A SEL referencing the designated initializer.
|
||||
*/
|
||||
#define GTM_UNAVAILABLE_USE_INITIALIZER(designatedInitializer) { \
|
||||
NSString *reason = [NSString stringWithFormat:@"Called: %@\nDesignated Initializer:%@", \
|
||||
NSStringFromSelector(_cmd), \
|
||||
NSStringFromSelector(designatedInitializer)]; \
|
||||
@throw [NSException exceptionWithName:@"Attempt to call unavailable initializer." \
|
||||
reason:reason \
|
||||
userInfo:nil]; \
|
||||
}
|
||||
|
||||
/*! @brief Key used to encode the @c authState property for @c NSSecureCoding.
|
||||
*/
|
||||
static NSString *const kAuthStateKey = @"authState";
|
||||
|
||||
/*! @brief Key used to encode the @c serviceProvider property for @c NSSecureCoding.
|
||||
*/
|
||||
static NSString *const kServiceProviderKey = @"serviceProvider";
|
||||
|
||||
/*! @brief Key used to encode the @c userID property for @c NSSecureCoding.
|
||||
*/
|
||||
static NSString *const kUserIDKey = @"userID";
|
||||
|
||||
/*! @brief Key used to encode the @c userEmail property for @c NSSecureCoding.
|
||||
*/
|
||||
static NSString *const kUserEmailKey = @"userEmail";
|
||||
|
||||
/*! @brief Key used to encode the @c userEmailIsVerified property for @c NSSecureCoding.
|
||||
*/
|
||||
static NSString *const kUserEmailIsVerifiedKey = @"userEmailIsVerified";
|
||||
|
||||
NSString *const GTMAppAuthFetcherAuthorizationErrorDomain =
|
||||
@"kGTMAppAuthFetcherAuthorizationErrorDomain";
|
||||
NSString *const GTMAppAuthFetcherAuthorizationErrorRequestKey = @"request";
|
||||
|
||||
/*! @brief Internal wrapper class for requests needing authorization and their callbacks.
|
||||
@discusssion Used to abstract away the detail of whether a callback or block is used.
|
||||
*/
|
||||
@interface GTMAppAuthFetcherAuthorizationArgs : NSObject
|
||||
|
||||
/*! @brief The request to authorize.
|
||||
* @discussion Not copied, as we are mutating the request.
|
||||
*/
|
||||
@property (nonatomic, strong) NSMutableURLRequest *request;
|
||||
|
||||
/*! @brief The delegate on which @c selector is called on completion.
|
||||
*/
|
||||
@property (nonatomic, weak) id delegate;
|
||||
|
||||
/*! @brief The selector called on the @c delegate object on completion.
|
||||
*/
|
||||
@property (nonatomic) SEL selector;
|
||||
|
||||
/*! @brief The completion block when the block option was used.
|
||||
*/
|
||||
@property (nonatomic, strong) GTMAppAuthFetcherAuthorizationCompletion completionHandler;
|
||||
|
||||
/*! @brief The error that happened during token refresh (if any).
|
||||
*/
|
||||
@property (nonatomic, strong) NSError *error;
|
||||
|
||||
+ (GTMAppAuthFetcherAuthorizationArgs *)argsWithRequest:(NSMutableURLRequest *)req
|
||||
delegate:(id)delegate
|
||||
selector:(SEL)selector
|
||||
completionHandler:(GTMAppAuthFetcherAuthorizationCompletion)completionHandler;
|
||||
|
||||
@end
|
||||
|
||||
@implementation GTMAppAuthFetcherAuthorizationArgs
|
||||
|
||||
@synthesize request = _request;
|
||||
@synthesize delegate = _delegate;
|
||||
@synthesize selector = _selector;
|
||||
@synthesize completionHandler = _completionHandler;
|
||||
@synthesize error = _error;
|
||||
|
||||
+ (GTMAppAuthFetcherAuthorizationArgs *)argsWithRequest:(NSMutableURLRequest *)req
|
||||
delegate:(id)delegate
|
||||
selector:(SEL)selector
|
||||
completionHandler:(GTMAppAuthFetcherAuthorizationCompletion)completionHandler {
|
||||
GTMAppAuthFetcherAuthorizationArgs *obj;
|
||||
obj = [[GTMAppAuthFetcherAuthorizationArgs alloc] init];
|
||||
obj.request = req;
|
||||
obj.delegate = delegate;
|
||||
obj.selector = selector;
|
||||
obj.completionHandler = completionHandler;
|
||||
return obj;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GTMAppAuthFetcherAuthorization {
|
||||
/*! @brief Array of requests pending authorization headers.
|
||||
*/
|
||||
NSMutableArray<GTMAppAuthFetcherAuthorizationArgs *> *_authorizationQueue;
|
||||
}
|
||||
|
||||
@synthesize authState = _authState;
|
||||
@synthesize serviceProvider = _serviceProvider;
|
||||
@synthesize userID = _userID;
|
||||
@synthesize userEmailIsVerified = _userEmailIsVerified;
|
||||
|
||||
// GTMFetcherAuthorizationProtocol doesn't specify atomic/nonatomic for these properties.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wimplicit-atomic-properties"
|
||||
@synthesize userEmail = _userEmail;
|
||||
@synthesize shouldAuthorizeAllRequests = _shouldAuthorizeAllRequests;
|
||||
@synthesize fetcherService = _fetcherService;
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#pragma mark - Initializers
|
||||
|
||||
// Ignore warning about not calling the designated initializer.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
- (instancetype)init
|
||||
GTM_UNAVAILABLE_USE_INITIALIZER(@selector(initWithAuthState:));
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (instancetype)initWithAuthState:(OIDAuthState *)authState {
|
||||
return [self initWithAuthState:authState
|
||||
serviceProvider:nil
|
||||
userID:nil
|
||||
userEmail:nil
|
||||
userEmailIsVerified:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithAuthState:(OIDAuthState *)authState
|
||||
serviceProvider:(nullable NSString *)serviceProvider
|
||||
userID:(nullable NSString *)userID
|
||||
userEmail:(nullable NSString *)userEmail
|
||||
userEmailIsVerified:(nullable NSString *)userEmailIsVerified {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_authState = authState;
|
||||
_authorizationQueue = [[NSMutableArray alloc] init];
|
||||
|
||||
_serviceProvider = [serviceProvider copy];
|
||||
_userID = [userID copy];
|
||||
_userEmail = [userEmail copy];
|
||||
_userEmailIsVerified = [userEmailIsVerified copy];
|
||||
|
||||
// Decodes the ID Token locally to extract the email address.
|
||||
NSString *idToken = _authState.lastTokenResponse.idToken
|
||||
? : _authState.lastAuthorizationResponse.idToken;
|
||||
if (idToken) {
|
||||
NSDictionary *claimsDictionary = [[OIDIDToken alloc] initWithIDTokenString:idToken].claims;
|
||||
if (claimsDictionary) {
|
||||
_userEmail = (NSString *)[claimsDictionary[@"email"] copy];
|
||||
_userEmailIsVerified = [(NSNumber *)claimsDictionary[@"email_verified"] stringValue];
|
||||
_userID = [claimsDictionary[@"sub"] copy];
|
||||
}
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
# pragma mark - Convenience
|
||||
|
||||
#if !GTM_APPAUTH_SKIP_GOOGLE_SUPPORT
|
||||
+ (OIDServiceConfiguration *)configurationForGoogle {
|
||||
NSURL *authorizationEndpoint =
|
||||
[NSURL URLWithString:@"https://accounts.google.com/o/oauth2/v2/auth"];
|
||||
NSURL *tokenEndpoint =
|
||||
[NSURL URLWithString:@"https://www.googleapis.com/oauth2/v4/token"];
|
||||
|
||||
OIDServiceConfiguration *configuration =
|
||||
[[OIDServiceConfiguration alloc] initWithAuthorizationEndpoint:authorizationEndpoint
|
||||
tokenEndpoint:tokenEndpoint];
|
||||
return configuration;
|
||||
}
|
||||
#endif // !GTM_APPAUTH_SKIP_GOOGLE_SUPPORT
|
||||
|
||||
#pragma mark - Authorizing Requests
|
||||
|
||||
/*! @brief Internal routine common to delegate and block invocations to queue requests while
|
||||
fresh tokens are obtained.
|
||||
*/
|
||||
- (void)authorizeRequestArgs:(GTMAppAuthFetcherAuthorizationArgs *)args {
|
||||
// Adds requests to queue.
|
||||
@synchronized(_authorizationQueue) {
|
||||
[_authorizationQueue addObject:args];
|
||||
}
|
||||
|
||||
NSDictionary<NSString *, NSString *> *additionalRefreshParameters = _tokenRefreshDelegate ?
|
||||
[_tokenRefreshDelegate additionalRefreshParameters:self] : nil;
|
||||
|
||||
// Obtains fresh tokens from AppAuth.
|
||||
[_authState performActionWithFreshTokens:^(NSString *_Nullable accessToken,
|
||||
NSString *_Nullable idToken,
|
||||
NSError *_Nullable error) {
|
||||
// Processes queue.
|
||||
@synchronized(self->_authorizationQueue) {
|
||||
for (GTMAppAuthFetcherAuthorizationArgs *fetcherArgs in self->_authorizationQueue) {
|
||||
[self authorizeRequestImmediateArgs:fetcherArgs accessToken:accessToken error:error];
|
||||
}
|
||||
[self->_authorizationQueue removeAllObjects];
|
||||
}
|
||||
}
|
||||
additionalRefreshParameters:additionalRefreshParameters];
|
||||
}
|
||||
|
||||
/*! @brief Adds authorization headers to the given request, using the supplied access token, or
|
||||
handles the error.
|
||||
@param args The request argument group to authorize.
|
||||
@param accessToken A currently valid access token.
|
||||
@param error If accessToken is nil, the error which caused the token to be unavailable.
|
||||
@return YES if the request was authorized with a valid access token.
|
||||
*/
|
||||
- (BOOL)authorizeRequestImmediateArgs:(GTMAppAuthFetcherAuthorizationArgs *)args
|
||||
accessToken:(NSString *)accessToken
|
||||
error:(NSError *)error {
|
||||
// This authorization entry point never attempts to refresh the access token,
|
||||
// but does call the completion routine
|
||||
|
||||
NSMutableURLRequest *request = args.request;
|
||||
|
||||
NSURL *requestURL = [request URL];
|
||||
NSString *scheme = [requestURL scheme];
|
||||
BOOL isAuthorizableRequest =
|
||||
!requestURL
|
||||
|| (scheme && [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame)
|
||||
|| [requestURL isFileURL]
|
||||
|| self.shouldAuthorizeAllRequests;
|
||||
if (!isAuthorizableRequest) {
|
||||
// Request is not https, a local file, or nil, so may be insecure
|
||||
//
|
||||
// The NSError will be created below
|
||||
#if DEBUG
|
||||
NSLog(@"Cannot authorize request with scheme %@ (%@)", scheme, request);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Get the access token.
|
||||
if (isAuthorizableRequest && accessToken && accessToken.length > 0) {
|
||||
if (request) {
|
||||
// Adds the authorization header to the request.
|
||||
NSString *value = [NSString stringWithFormat:@"%@ %@", @"Bearer", accessToken];
|
||||
[request setValue:value forHTTPHeaderField:@"Authorization"];
|
||||
}
|
||||
|
||||
// We've authorized the request, even if the previous refresh
|
||||
// failed with an error
|
||||
args.error = nil;
|
||||
} else {
|
||||
NSMutableDictionary *userInfo = [error.userInfo mutableCopy];
|
||||
if (!userInfo) {
|
||||
userInfo = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
if (request) {
|
||||
userInfo[GTMAppAuthFetcherAuthorizationErrorRequestKey] = request;
|
||||
}
|
||||
|
||||
if (!isAuthorizableRequest || !error) {
|
||||
args.error = [NSError errorWithDomain:GTMAppAuthFetcherAuthorizationErrorDomain
|
||||
code:GTMAppAuthFetcherAuthorizationErrorUnauthorizableRequest
|
||||
userInfo:userInfo];
|
||||
} else {
|
||||
// Passes through error domain & code from AppAuth, with additional userInfo args.
|
||||
args.error = [NSError errorWithDomain:error.domain
|
||||
code:error.code
|
||||
userInfo:userInfo];
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke any callbacks on the proper thread
|
||||
if (args.delegate || args.completionHandler) {
|
||||
// If the fetcher service provides a callback queue, we'll use that
|
||||
// (or if it's nil, we'll use the main thread) for callbacks.
|
||||
dispatch_queue_t callbackQueue = self.fetcherService.callbackQueue;
|
||||
if (!callbackQueue) {
|
||||
callbackQueue = dispatch_get_main_queue();
|
||||
}
|
||||
dispatch_async(callbackQueue, ^{
|
||||
[self invokeCallbackArgs:args];
|
||||
});
|
||||
}
|
||||
|
||||
BOOL didAuth = (args.error == nil);
|
||||
return didAuth;
|
||||
}
|
||||
|
||||
/*! @brief Invokes the callback for the given authorization argument group.
|
||||
@param args The request argument group to invoke following authorization or error.
|
||||
*/
|
||||
- (void)invokeCallbackArgs:(GTMAppAuthFetcherAuthorizationArgs *)args {
|
||||
NSError *error = args.error;
|
||||
id delegate = args.delegate;
|
||||
SEL sel = args.selector;
|
||||
|
||||
// If the selector callback method exists, invokes the selector.
|
||||
if (delegate && sel) {
|
||||
NSMutableURLRequest *request = args.request;
|
||||
|
||||
NSMethodSignature *sig = [delegate methodSignatureForSelector:sel];
|
||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
|
||||
[invocation setSelector:sel];
|
||||
[invocation setTarget:delegate];
|
||||
GTMAppAuthFetcherAuthorization *authorization = self;
|
||||
[invocation setArgument:&authorization atIndex:2];
|
||||
[invocation setArgument:&request atIndex:3];
|
||||
[invocation setArgument:&error atIndex:4];
|
||||
[invocation invoke];
|
||||
}
|
||||
|
||||
// If a callback block exists, executes the block.
|
||||
id handler = args.completionHandler;
|
||||
if (handler) {
|
||||
void (^authCompletionBlock)(NSError *) = handler;
|
||||
authCompletionBlock(error);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - GTMFetcherAuthorizationProtocol
|
||||
|
||||
/*! @brief Authorizing with a callback selector.
|
||||
@discussion Selector has the signature:
|
||||
- (void)authentication:(GTMAppAuthFetcherAuthorization *)auth
|
||||
request:(NSMutableURLRequest *)request
|
||||
finishedWithError:(NSError *)error;
|
||||
*/
|
||||
- (void)authorizeRequest:(NSMutableURLRequest *)request
|
||||
delegate:(id)delegate
|
||||
didFinishSelector:(SEL)sel {
|
||||
GTMOAuth2AssertValidSelector(delegate, sel,
|
||||
@encode(GTMAppAuthFetcherAuthorization *),
|
||||
@encode(NSMutableURLRequest *),
|
||||
@encode(NSError *), 0);
|
||||
|
||||
GTMAppAuthFetcherAuthorizationArgs *args;
|
||||
args = [GTMAppAuthFetcherAuthorizationArgs argsWithRequest:request
|
||||
delegate:delegate
|
||||
selector:sel
|
||||
completionHandler:nil];
|
||||
[self authorizeRequestArgs:args];
|
||||
}
|
||||
|
||||
/*! @brief Removes all pending requests from the authorization queue.
|
||||
*/
|
||||
- (void)stopAuthorization {
|
||||
@synchronized(_authorizationQueue) {
|
||||
[_authorizationQueue removeAllObjects];
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Attempts to remove a specific pending requests from the authorization queue.
|
||||
@discussion Has no effect if the authorization already occurred.
|
||||
*/
|
||||
- (void)stopAuthorizationForRequest:(NSURLRequest *)request {
|
||||
@synchronized(_authorizationQueue) {
|
||||
NSUInteger argIndex = 0;
|
||||
BOOL found = NO;
|
||||
for (GTMAppAuthFetcherAuthorizationArgs *args in _authorizationQueue) {
|
||||
// Checks pointer equality with given request, don't want to match equivalent requests.
|
||||
if ([args request] == request) {
|
||||
found = YES;
|
||||
break;
|
||||
}
|
||||
argIndex++;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
[_authorizationQueue removeObjectAtIndex:argIndex];
|
||||
|
||||
// If the queue is now empty, go ahead and stop the fetcher.
|
||||
if (_authorizationQueue.count == 0) {
|
||||
[self stopAuthorization];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Returns YES if the given requests is in the pending authorization queue.
|
||||
*/
|
||||
- (BOOL)isAuthorizingRequest:(NSURLRequest *)request {
|
||||
BOOL wasFound = NO;
|
||||
@synchronized(_authorizationQueue) {
|
||||
for (GTMAppAuthFetcherAuthorizationArgs *args in _authorizationQueue) {
|
||||
// Checks pointer equality with given request, don't want to match equivalent requests.
|
||||
if ([args request] == request) {
|
||||
wasFound = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return wasFound;
|
||||
}
|
||||
|
||||
/*! @brief Returns YES if given request has an Authorization header.
|
||||
*/
|
||||
- (BOOL)isAuthorizedRequest:(NSURLRequest *)request {
|
||||
NSString *authStr = [request valueForHTTPHeaderField:@"Authorization"];
|
||||
return (authStr.length > 0);
|
||||
}
|
||||
|
||||
/*! @brief Returns YES if the authorization state is currently valid.
|
||||
@discussion Note that the state can become invalid immediately due to an error on token refresh.
|
||||
*/
|
||||
- (BOOL)canAuthorize {
|
||||
return [_authState isAuthorized];
|
||||
}
|
||||
|
||||
/*! @brief Authorizing with a completion block.
|
||||
*/
|
||||
- (void)authorizeRequest:(NSMutableURLRequest *)request
|
||||
completionHandler:(GTMAppAuthFetcherAuthorizationCompletion)handler {
|
||||
GTMAppAuthFetcherAuthorizationArgs *args =
|
||||
[GTMAppAuthFetcherAuthorizationArgs argsWithRequest:request
|
||||
delegate:nil
|
||||
selector:NULL
|
||||
completionHandler:handler];
|
||||
[self authorizeRequestArgs:args];
|
||||
}
|
||||
|
||||
/*! @brief Forces a token refresh the next time a request is queued for authorization.
|
||||
*/
|
||||
- (BOOL)primeForRefresh {
|
||||
if (_authState.refreshToken == nil) {
|
||||
// Cannot refresh without a refresh token
|
||||
return NO;
|
||||
}
|
||||
[_authState setNeedsTokenRefresh];
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - NSSecureCoding
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
OIDAuthState *authState =
|
||||
[aDecoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthStateKey];
|
||||
NSString *serviceProvider =
|
||||
[aDecoder decodeObjectOfClass:[NSString class] forKey:kServiceProviderKey];
|
||||
NSString *userID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kUserIDKey];
|
||||
NSString *userEmail = [aDecoder decodeObjectOfClass:[NSString class] forKey:kUserEmailKey];
|
||||
NSString *userEmailIsVerified =
|
||||
[aDecoder decodeObjectOfClass:[NSString class] forKey:kUserEmailIsVerifiedKey];
|
||||
|
||||
self = [self initWithAuthState:authState
|
||||
serviceProvider:serviceProvider
|
||||
userID:userID
|
||||
userEmail:userEmail
|
||||
userEmailIsVerified:userEmailIsVerified];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:_authState forKey:kAuthStateKey];
|
||||
[aCoder encodeObject:_serviceProvider forKey:kServiceProviderKey];
|
||||
[aCoder encodeObject:_userID forKey:kUserIDKey];
|
||||
[aCoder encodeObject:_userEmail forKey:kUserEmailKey];
|
||||
[aCoder encodeObject:_userEmailIsVerified forKey:kUserEmailIsVerifiedKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,331 @@
|
||||
/*! @file GTMOAuth2Compatibility.m
|
||||
@brief GTMAppAuth SDK
|
||||
@copyright
|
||||
Copyright 2016 Google Inc.
|
||||
@copydetails
|
||||
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 "GTMAppAuth/Sources/Public/GTMAppAuth/GTMOAuth2KeychainCompatibility.h"
|
||||
|
||||
#import "GTMAppAuth/Sources/Public/GTMAppAuth/GTMAppAuthFetcherAuthorization.h"
|
||||
#import "GTMAppAuth/Sources/Public/GTMAppAuth/GTMKeychain.h"
|
||||
|
||||
#if SWIFT_PACKAGE || GTMAPPAUTH_USE_MODULAR_IMPORT
|
||||
@import AppAuthCore;
|
||||
@import GTMSessionFetcherCore;
|
||||
#elif GTMAPPAUTH_USER_IMPORTS
|
||||
#import "AppAuthCore.h"
|
||||
#import "GTMSessionFetcher.h"
|
||||
#else
|
||||
#import <AppAuth/AppAuthCore.h>
|
||||
#import <GTMSessionFetcher/GTMSessionFetcher.h>
|
||||
#endif
|
||||
|
||||
// standard OAuth keys
|
||||
static NSString *const kOAuth2AccessTokenKey = @"access_token";
|
||||
static NSString *const kOAuth2RefreshTokenKey = @"refresh_token";
|
||||
static NSString *const kOAuth2ScopeKey = @"scope";
|
||||
static NSString *const kOAuth2ErrorKey = @"error";
|
||||
static NSString *const kOAuth2TokenTypeKey = @"token_type";
|
||||
static NSString *const kOAuth2ExpiresInKey = @"expires_in";
|
||||
static NSString *const kOAuth2CodeKey = @"code";
|
||||
static NSString *const kOAuth2AssertionKey = @"assertion";
|
||||
static NSString *const kOAuth2RefreshScopeKey = @"refreshScope";
|
||||
|
||||
// additional persistent keys
|
||||
static NSString *const kServiceProviderKey = @"serviceProvider";
|
||||
static NSString *const kUserIDKey = @"userID";
|
||||
static NSString *const kUserEmailKey = @"email";
|
||||
static NSString *const kUserEmailIsVerifiedKey = @"isVerified";
|
||||
|
||||
// URI indicating an installed app is signing in. This is described at
|
||||
//
|
||||
// https://developers.google.com/identity/protocols/OAuth2InstalledApp#formingtheurl
|
||||
//
|
||||
static NSString *const kOOBString = @"urn:ietf:wg:oauth:2.0:oob";
|
||||
|
||||
@implementation GTMOAuth2KeychainCompatibility
|
||||
|
||||
// This returns a "response string" that can be passed later to
|
||||
// setKeysForResponseString: to reuse an old access token in a new auth object
|
||||
+ (NSString *)persistenceResponseStringForAuthorization:
|
||||
(GTMAppAuthFetcherAuthorization *)authorization {
|
||||
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
NSString *refreshToken = authorization.authState.refreshToken;
|
||||
NSString *accessToken = authorization.authState.lastTokenResponse.accessToken;
|
||||
|
||||
// Any nil values will not set a dictionary entry
|
||||
[dict setValue:refreshToken forKey:kOAuth2RefreshTokenKey];
|
||||
[dict setValue:accessToken forKey:kOAuth2AccessTokenKey];
|
||||
[dict setValue:authorization.serviceProvider forKey:kServiceProviderKey];
|
||||
[dict setValue:authorization.userID forKey:kUserIDKey];
|
||||
[dict setValue:authorization.userEmail forKey:kUserEmailKey];
|
||||
[dict setValue:authorization.userEmailIsVerified forKey:kUserEmailIsVerifiedKey];
|
||||
[dict setValue:authorization.authState.scope forKey:kOAuth2ScopeKey];
|
||||
|
||||
NSString *result = [self encodedQueryParametersForDictionary:dict];
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (GTMAppAuthFetcherAuthorization *)authorizeFromKeychainForName:(NSString *)keychainItemName
|
||||
tokenURL:(NSURL *)tokenURL
|
||||
redirectURI:(NSString *)redirectURI
|
||||
clientID:(NSString *)clientID
|
||||
clientSecret:(nullable NSString *)clientSecret {
|
||||
// Loads password string from keychain.
|
||||
NSString *password = [GTMKeychain passwordFromKeychainForName:keychainItemName];
|
||||
|
||||
if (!password) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
GTMAppAuthFetcherAuthorization *authorization =
|
||||
[self authorizeFromPersistenceString:password
|
||||
tokenURL:tokenURL
|
||||
redirectURI:redirectURI
|
||||
clientID:clientID
|
||||
clientSecret:clientSecret];
|
||||
return authorization;
|
||||
}
|
||||
|
||||
+ (GTMAppAuthFetcherAuthorization *)authorizeFromPersistenceString:(NSString *)persistenceString
|
||||
tokenURL:(NSURL *)tokenURL
|
||||
redirectURI:(NSString *)redirectURIString
|
||||
clientID:(NSString *)clientID
|
||||
clientSecret:(NSString *)clientSecret {
|
||||
// Parses persistence data into NSDictionary.
|
||||
NSDictionary *dict = [self dictionaryWithResponseString:persistenceString];
|
||||
|
||||
NSURL *redirectURI = (NSURL *)[NSURL URLWithString:redirectURIString];
|
||||
|
||||
// OIDAuthState is based on the request/response history.
|
||||
// Creates history based on the data from the keychain, and client details passed in.
|
||||
OIDServiceConfiguration *authConfig =
|
||||
[[OIDServiceConfiguration alloc] initWithAuthorizationEndpoint:tokenURL tokenEndpoint:tokenURL];
|
||||
OIDAuthorizationRequest *authRequest =
|
||||
[[OIDAuthorizationRequest alloc] initWithConfiguration:authConfig
|
||||
clientId:clientID
|
||||
clientSecret:clientSecret
|
||||
scope:dict[kOAuth2ScopeKey]
|
||||
redirectURL:redirectURI
|
||||
responseType:OIDResponseTypeCode
|
||||
state:nil
|
||||
nonce:nil
|
||||
codeVerifier:nil
|
||||
codeChallenge:nil
|
||||
codeChallengeMethod:nil
|
||||
additionalParameters:nil];
|
||||
OIDAuthorizationResponse *authResponse =
|
||||
[[OIDAuthorizationResponse alloc] initWithRequest:authRequest parameters:dict];
|
||||
// Exclude scope and refresh token parameters from additionalParameters.
|
||||
NSMutableDictionary *additionalParameters = [dict mutableCopy];
|
||||
[additionalParameters removeObjectForKey:kOAuth2ScopeKey];
|
||||
[additionalParameters removeObjectForKey:kOAuth2RefreshTokenKey];
|
||||
OIDTokenRequest *tokenRequest =
|
||||
[[OIDTokenRequest alloc] initWithConfiguration:authConfig
|
||||
grantType:@"token"
|
||||
authorizationCode:nil
|
||||
redirectURL:redirectURI
|
||||
clientID:clientID
|
||||
clientSecret:clientSecret
|
||||
scope:dict[kOAuth2ScopeKey]
|
||||
refreshToken:dict[kOAuth2RefreshTokenKey]
|
||||
codeVerifier:nil
|
||||
additionalParameters:additionalParameters];
|
||||
OIDTokenResponse *tokenResponse =
|
||||
[[OIDTokenResponse alloc] initWithRequest:tokenRequest parameters:dict];
|
||||
OIDAuthState *authState = [[OIDAuthState alloc] initWithAuthorizationResponse:authResponse
|
||||
tokenResponse:tokenResponse];
|
||||
// We're not serializing the token expiry date, so the first refresh needs to be forced.
|
||||
[authState setNeedsTokenRefresh];
|
||||
|
||||
GTMAppAuthFetcherAuthorization *authorizer =
|
||||
[[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState
|
||||
serviceProvider:dict[kServiceProviderKey]
|
||||
userID:dict[kUserIDKey]
|
||||
userEmail:dict[kUserEmailKey]
|
||||
userEmailIsVerified:dict[kUserEmailIsVerifiedKey]];
|
||||
return authorizer;
|
||||
}
|
||||
|
||||
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
|
||||
|
||||
+ (GTMAppAuthFetcherAuthorization *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
|
||||
clientID:(NSString *)clientID
|
||||
clientSecret:(NSString *)clientSecret {
|
||||
Class signInClass = self;
|
||||
NSURL *tokenURL = [signInClass googleTokenURL];
|
||||
NSString *redirectURI = [signInClass nativeClientRedirectURI];
|
||||
|
||||
GTMAppAuthFetcherAuthorization *auth;
|
||||
auth = [self authorizeFromKeychainForName:keychainItemName
|
||||
tokenURL:tokenURL
|
||||
redirectURI:redirectURI
|
||||
clientID:clientID
|
||||
clientSecret:clientSecret];
|
||||
return auth;
|
||||
}
|
||||
|
||||
#endif // !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
|
||||
|
||||
/*! @brief Removes stored tokens, such as when the user signs out.
|
||||
@return YES the tokens were removed successfully (or didn't exist).
|
||||
*/
|
||||
+ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName {
|
||||
return [GTMKeychain removePasswordFromKeychainForName:keychainItemName];
|
||||
}
|
||||
|
||||
/*! @brief Saves the authorization state to the keychain, in a GTMOAuth2 compatible manner.
|
||||
@return YES when the state was saved successfully.
|
||||
*/
|
||||
+ (BOOL)saveAuthToKeychainForName:(NSString *)keychainItemName
|
||||
authentication:(GTMAppAuthFetcherAuthorization *)auth {
|
||||
[self removeAuthFromKeychainForName:keychainItemName];
|
||||
NSString *password = [self persistenceResponseStringForAuthorization:auth];
|
||||
|
||||
return [GTMKeychain savePasswordToKeychainForName:keychainItemName password:password];
|
||||
}
|
||||
|
||||
#pragma mark Utility Routines
|
||||
|
||||
+ (NSString *)encodedQueryParametersForDictionary:(NSDictionary *)dict {
|
||||
// Make a string like "cat=fluffy&dog=spot"
|
||||
NSMutableString *result = [NSMutableString string];
|
||||
NSArray *sortedKeys =
|
||||
[[dict allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
|
||||
NSString *joiner = @"";
|
||||
for (NSString *key in sortedKeys) {
|
||||
NSString *value = [dict objectForKey:key];
|
||||
NSString *encodedValue = [self encodedOAuthValueForString:value];
|
||||
NSString *encodedKey = [self encodedOAuthValueForString:key];
|
||||
[result appendFormat:@"%@%@=%@", joiner, encodedKey, encodedValue];
|
||||
joiner = @"&";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (NSString *)encodedOAuthValueForString:(NSString *)originalString {
|
||||
// For parameters, we'll explicitly leave spaces unescaped now, and replace
|
||||
// them with +'s
|
||||
NSString *const kForceEscape = @"!*'();:@&=+$,/?%#[]";
|
||||
|
||||
#if (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9) \
|
||||
|| (TARGET_OS_IPHONE && defined(__IPHONE_7_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0)
|
||||
// Builds targeting iOS 7/OS X 10.9 and higher only.
|
||||
NSMutableCharacterSet *cs = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
|
||||
[cs removeCharactersInString:kForceEscape];
|
||||
|
||||
return [originalString stringByAddingPercentEncodingWithAllowedCharacters:cs];
|
||||
#else
|
||||
// Builds targeting iOS 6/OS X 10.8.
|
||||
CFStringRef escapedStr = NULL;
|
||||
if (originalString) {
|
||||
escapedStr = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
|
||||
(CFStringRef)originalString,
|
||||
NULL,
|
||||
(CFStringRef)kForceEscape,
|
||||
kCFStringEncodingUTF8);
|
||||
}
|
||||
|
||||
return (__bridge NSString *)escapedStr;
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (NSDictionary *)dictionaryWithResponseString:(NSString *)responseStr {
|
||||
// Build a dictionary from a response string of the form
|
||||
// "cat=fluffy&dog=spot". Missing or empty values are considered
|
||||
// empty strings; keys and values are percent-decoded.
|
||||
if (responseStr == nil) return nil;
|
||||
|
||||
NSArray *items = [responseStr componentsSeparatedByString:@"&"];
|
||||
|
||||
NSMutableDictionary *responseDict = [NSMutableDictionary dictionaryWithCapacity:items.count];
|
||||
|
||||
for (NSString *item in items) {
|
||||
NSString *key;
|
||||
NSString *value = @"";
|
||||
|
||||
NSRange equalsRange = [item rangeOfString:@"="];
|
||||
if (equalsRange.location != NSNotFound) {
|
||||
// The parameter has at least one '='
|
||||
key = [item substringToIndex:equalsRange.location];
|
||||
|
||||
// There are characters after the '='
|
||||
if (equalsRange.location + 1 < item.length) {
|
||||
value = [item substringFromIndex:(equalsRange.location + 1)];
|
||||
}
|
||||
} else {
|
||||
// The parameter has no '='
|
||||
key = item;
|
||||
}
|
||||
|
||||
NSString *plainKey = [self unencodedOAuthParameterForString:key];
|
||||
NSString *plainValue = [self unencodedOAuthParameterForString:value];
|
||||
|
||||
[responseDict setObject:plainValue forKey:plainKey];
|
||||
}
|
||||
|
||||
return responseDict;
|
||||
}
|
||||
|
||||
+ (NSString *)unencodedOAuthParameterForString:(NSString *)str {
|
||||
#if (!TARGET_OS_IPHONE \
|
||||
&& defined(MAC_OS_X_VERSION_10_9) \
|
||||
&& MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9) \
|
||||
|| (TARGET_OS_IPHONE \
|
||||
&& defined(__IPHONE_7_0) \
|
||||
&& __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0)
|
||||
// On iOS 7, -stringByRemovingPercentEncoding incorrectly returns nil for an empty string.
|
||||
if (str != nil && [str length] == 0) return @"";
|
||||
|
||||
NSString *plainStr = [str stringByRemovingPercentEncoding];
|
||||
return plainStr;
|
||||
#else
|
||||
NSString *plainStr = [str stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
return plainStr;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
|
||||
|
||||
// Endpoint URLs are available at https://accounts.google.com/.well-known/openid-configuration
|
||||
|
||||
+ (NSURL *)googleAuthorizationURL {
|
||||
NSString *str = @"https://accounts.google.com/o/oauth2/v2/auth";
|
||||
return (NSURL *)[NSURL URLWithString:str];
|
||||
}
|
||||
|
||||
+ (NSURL *)googleTokenURL {
|
||||
NSString *str = @"https://www.googleapis.com/oauth2/v4/token";
|
||||
return (NSURL *)[NSURL URLWithString:str];
|
||||
}
|
||||
|
||||
+ (NSURL *)googleRevocationURL {
|
||||
NSString *urlStr = @"https://accounts.google.com/o/oauth2/revoke";
|
||||
return (NSURL *)[NSURL URLWithString:urlStr];
|
||||
}
|
||||
|
||||
+ (NSURL *)googleUserInfoURL {
|
||||
NSString *urlStr = @"https://www.googleapis.com/oauth2/v3/userinfo";
|
||||
return (NSURL *)[NSURL URLWithString:urlStr];
|
||||
}
|
||||
|
||||
+ (NSString *)nativeClientRedirectURI {
|
||||
return kOOBString;
|
||||
}
|
||||
|
||||
#endif // !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,29 @@
|
||||
/*! @file GTMAppAuth.h
|
||||
@brief GTMAppAuth SDK
|
||||
@copyright
|
||||
Copyright 2016 Google Inc.
|
||||
@copydetails
|
||||
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 "GTMAppAuthFetcherAuthorization.h"
|
||||
#import "GTMAppAuthFetcherAuthorization+Keychain.h"
|
||||
|
||||
#if TARGET_OS_TV
|
||||
#elif TARGET_OS_WATCH
|
||||
#elif TARGET_OS_IOS || TARGET_OS_MAC
|
||||
#import "GTMKeychain.h"
|
||||
#import "GTMOAuth2KeychainCompatibility.h"
|
||||
#else
|
||||
#warn "Platform Undefined"
|
||||
#endif
|
||||
Generated
+52
@@ -0,0 +1,52 @@
|
||||
/*! @file GTMAppAuthFetcherAuthorization+Keychain.h
|
||||
@brief GTMAppAuth SDK
|
||||
@copyright
|
||||
Copyright 2016 Google Inc.
|
||||
@copydetails
|
||||
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 "GTMAppAuthFetcherAuthorization.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*! @brief Category to support serialization and deserialization of
|
||||
@c GTMAppAuthFetcherAuthorization in the format used by GTMAppAuth.
|
||||
*/
|
||||
@interface GTMAppAuthFetcherAuthorization (Keychain)
|
||||
|
||||
/*! @brief Attempts to create a @c GTMAppAuthFetcherAuthorization from data stored in the keychain
|
||||
in GTMAppAuth format.
|
||||
@param keychainItemName The keychain name.
|
||||
@return A @c GTMAppAuthFetcherAuthorization object, or nil.
|
||||
*/
|
||||
+ (nullable GTMAppAuthFetcherAuthorization *)
|
||||
authorizationFromKeychainForName:(NSString *)keychainItemName;
|
||||
|
||||
/*! @brief Removes a stored authorization state.
|
||||
@param keychainItemName The keychain name.
|
||||
@return YES the tokens were removed successfully (or didn't exist).
|
||||
*/
|
||||
+ (BOOL)removeAuthorizationFromKeychainForName:(NSString *)keychainItemName;
|
||||
|
||||
/*! @brief Saves the authorization state to the keychain, in GTMAppAuth format.
|
||||
@param auth The authorization to save.
|
||||
@param keychainItemName The keychain name.
|
||||
@return YES when the state was saved successfully.
|
||||
*/
|
||||
+ (BOOL)saveAuthorization:(GTMAppAuthFetcherAuthorization *)auth
|
||||
toKeychainForName:(NSString *)keychainItemName;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
/*! @file GTMAppAuthFetcherAuthorization.h
|
||||
@brief GTMAppAuth SDK
|
||||
@copyright
|
||||
Copyright 2016 Google Inc.
|
||||
@copydetails
|
||||
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.
|
||||
*/
|
||||
|
||||
#if !defined(__has_include)
|
||||
#error "__has_include not available."
|
||||
#elif __has_include(<GTMSessionFetcher/GTMSessionFetcher.h>)
|
||||
#import <GTMSessionFetcher/GTMSessionFetcher.h>
|
||||
#elif __has_include("../GTMSessionFetcher.h")
|
||||
#import "../GTMSessionFetcher.h"
|
||||
#else
|
||||
# error "Failed to find GTMSessionFetcher"
|
||||
#endif
|
||||
|
||||
@class OIDAuthState;
|
||||
@class OIDServiceConfiguration;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*! @brief The userInfo key for the @c NSURLRequest.
|
||||
*/
|
||||
extern NSString *const GTMAppAuthFetcherAuthorizationErrorRequestKey;
|
||||
|
||||
/*! @brief The error domain for errors specific to the session fetcher authorization.
|
||||
*/
|
||||
extern NSString *const GTMAppAuthFetcherAuthorizationErrorDomain;
|
||||
|
||||
/*! @brief Enum of all possible error codes in the @c ::GTMAppAuthFetcherAuthorizationErrorDomain
|
||||
domain.
|
||||
@discussion Note that these are GTMAppAuth-specific errors. When AppAuth errors are encountered,
|
||||
those are returned instead.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, GTMAppAuthFetcherAuthorizationError) {
|
||||
GTMAppAuthFetcherAuthorizationErrorUnauthorizableRequest = -1004
|
||||
};
|
||||
|
||||
typedef void (^GTMAppAuthFetcherAuthorizationCompletion)(NSError *_Nullable error);
|
||||
|
||||
@class GTMAppAuthFetcherAuthorization;
|
||||
|
||||
/*! @protocol GTMAppAuthFetcherAuthorizationTokenRefreshDelegate
|
||||
@brief Delegate of the GTMAppAuthFetcherAuthorization used to supply additional parameters on
|
||||
token refresh.
|
||||
*/
|
||||
@protocol GTMAppAuthFetcherAuthorizationTokenRefreshDelegate <NSObject>
|
||||
|
||||
/*! @brief Called before a token refresh request is performed.
|
||||
@param authorization The @c GTMFetcherAuthorization performing the token refresh.
|
||||
@return A dictionary of parameters to be added to the token refresh request.
|
||||
*/
|
||||
- (nullable NSDictionary<NSString *, NSString *> *)additionalRefreshParameters:
|
||||
(GTMAppAuthFetcherAuthorization *)authorization;
|
||||
|
||||
@end
|
||||
|
||||
/*! @brief An implementation of the @c GTMFetcherAuthorizationProtocol protocol for the AppAuth
|
||||
library.
|
||||
@discussion Enables you to use AppAuth with the GTM Session Fetcher library.
|
||||
*/
|
||||
@interface GTMAppAuthFetcherAuthorization : NSObject <GTMFetcherAuthorizationProtocol,
|
||||
NSSecureCoding>
|
||||
|
||||
/*! @brief The AppAuth authentication state.
|
||||
*/
|
||||
@property(nonatomic, readonly) OIDAuthState *authState;
|
||||
|
||||
/*! @brief Service identifier, for example "Google"; not used for authentication.
|
||||
@discussion The provider name is just for allowing stored authorization to be associated
|
||||
with the authorizing service.
|
||||
*/
|
||||
@property(nullable, nonatomic, readonly) NSString *serviceProvider;
|
||||
|
||||
/*! @brief User ID from the ID Token.
|
||||
* @discussion Note: Never send this value to your backend as an authentication token, rather send
|
||||
* an ID Token and validate it.
|
||||
*/
|
||||
@property(nullable, nonatomic, readonly) NSString *userID;
|
||||
|
||||
/*! @brief Email verified status; not used for authentication.
|
||||
@discussion The verified string can be checked with -boolValue. If the result is false, then
|
||||
the email address is listed with the account on the server, but the address has not been
|
||||
confirmed as belonging to the owner of the account.
|
||||
*/
|
||||
@property(nullable, nonatomic, readonly) NSString *userEmailIsVerified;
|
||||
|
||||
@property(nullable, nonatomic, weak) id<GTMAppAuthFetcherAuthorizationTokenRefreshDelegate>
|
||||
tokenRefreshDelegate;
|
||||
|
||||
/*! @brief Creates a new @c GTMAppAuthFetcherAuthorization using the given @c OIDAuthState from
|
||||
AppAuth.
|
||||
@param authState The authorization state.
|
||||
*/
|
||||
- (instancetype)initWithAuthState:(OIDAuthState *)authState;
|
||||
|
||||
/*! @brief Creates a new @c GTMAppAuthFetcherAuthorization using the given @c OIDAuthState from
|
||||
AppAuth.
|
||||
@param authState The authorization state.
|
||||
@param serviceProvider An optional string to describe the service.
|
||||
@param userID An optional string of the user ID.
|
||||
@param userEmail An optional string of the user's email address.
|
||||
@param userEmailIsVerified An optional string representation of a boolean to indicate that the
|
||||
email address has been verified. Pass @"true" for @c YES, or @"false" for @c NO.
|
||||
@discussion Designated initializer.
|
||||
*/
|
||||
- (instancetype)initWithAuthState:(OIDAuthState *)authState
|
||||
serviceProvider:(nullable NSString *)serviceProvider
|
||||
userID:(nullable NSString *)userID
|
||||
userEmail:(nullable NSString *)userEmail
|
||||
userEmailIsVerified:(nullable NSString *)userEmailIsVerified
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
#if !GTM_APPAUTH_SKIP_GOOGLE_SUPPORT
|
||||
/*! @brief Convenience method to return an @c OIDServiceConfiguration for Google.
|
||||
@return A @c OIDServiceConfiguration object setup with Google OAuth endpoints.
|
||||
*/
|
||||
+ (OIDServiceConfiguration *)configurationForGoogle;
|
||||
#endif // !GTM_APPAUTH_SKIP_GOOGLE_SUPPORT
|
||||
|
||||
/*! @brief Adds an authorization header to the given request, using the authorization state.
|
||||
Refreshes the access token if needed.
|
||||
@param request The request to authorize.
|
||||
@param handler The block that is called after authorizing the request is attempted. If @c error
|
||||
is non-nil, the authorization failed. Errors in the domain @c ::OIDOAuthTokenErrorDomain
|
||||
indicate that the authorization itself is invalid, and will need to be re-obtained from the
|
||||
user. Errors in the @c GTMAppAuthFetcherAuthorizationErrorDomain indicate another
|
||||
unrecoverable errors. Errors in other domains may indicate a transitive error condition such
|
||||
as a network error, and typically you do not need to reauthenticate the user on such errors.
|
||||
@discussion The completion handler is scheduled on the main thread, unless the @c callbackQueue
|
||||
property is set on the @c fetcherService in which case the handler is scheduled on that
|
||||
queue.
|
||||
*/
|
||||
- (void)authorizeRequest:(nullable NSMutableURLRequest *)request
|
||||
completionHandler:(GTMAppAuthFetcherAuthorizationCompletion)handler;
|
||||
|
||||
/*! @brief Returns YES if the authorization state is currently valid.
|
||||
@discussion Note that this doesn't guarantee that a request will get a valid authorization, as
|
||||
the authorization state could become invalid on on the next token refresh.
|
||||
*/
|
||||
- (BOOL)canAuthorize;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,62 @@
|
||||
/*! @file GTMKeychain.h
|
||||
@brief GTMAppAuth SDK
|
||||
@copyright
|
||||
Copyright 2016 Google Inc.
|
||||
@copydetails
|
||||
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/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*! @brief Utility for saving and loading data to the keychain.
|
||||
*/
|
||||
@interface GTMKeychain : NSObject
|
||||
|
||||
/*! @brief Saves the password string to the keychain with the given identifier.
|
||||
@param keychainItemName Keychain name of the item.
|
||||
@param password Password string to save.
|
||||
@return YES when the password string was saved successfully.
|
||||
*/
|
||||
+ (BOOL)savePasswordToKeychainForName:(NSString *)keychainItemName password:(NSString *)password;
|
||||
|
||||
/*! @brief Loads the password string from the keychain with the given identifier.
|
||||
@param keychainItemName Keychain name of the item.
|
||||
@return The password string at the given identifier, or nil.
|
||||
*/
|
||||
+ (nullable NSString *)passwordFromKeychainForName:(NSString *)keychainItemName;
|
||||
|
||||
/*! @brief Saves the password data to the keychain with the given identifier.
|
||||
@param keychainItemName Keychain name of the item.
|
||||
@param passwordData Password data to save.
|
||||
@return YES when the password data was saved successfully.
|
||||
*/
|
||||
+ (BOOL)savePasswordDataToKeychainForName:(NSString *)keychainItemName
|
||||
passwordData:(NSData *)passwordData;
|
||||
|
||||
/*! @brief Loads the password data from the keychain with the given identifier.
|
||||
@param keychainItemName Keychain name of the item.
|
||||
@return The password data at the given identifier, or nil.
|
||||
*/
|
||||
+ (nullable NSData *)passwordDataFromKeychainForName:(NSString *)keychainItemName;
|
||||
|
||||
/*! @brief Removes stored password string, such as when the user signs out.
|
||||
@param keychainItemName Keychain name of the item.
|
||||
@return YES if the password string was removed successfully (or didn't exist).
|
||||
*/
|
||||
+ (BOOL)removePasswordFromKeychainForName:(NSString *)keychainItemName;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*! @file GTMOAuth2Compatibility.h
|
||||
@brief GTMAppAuth SDK
|
||||
@copyright
|
||||
Copyright 2016 Google Inc.
|
||||
@copydetails
|
||||
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/Foundation.h>
|
||||
|
||||
@class GTMAppAuthFetcherAuthorization;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*! @brief Class to support serialization and deserialization of @c GTMAppAuthFetcherAuthorization
|
||||
in the format used by GTMOAuth2.
|
||||
@discussion The methods of this class are capable of serializing and deserializing auth
|
||||
objects in a way compatible with the serialization in @c GTMOAuth2ViewControllerTouch and
|
||||
@c GTMOAuth2WindowController in GTMOAuth2.
|
||||
*/
|
||||
@interface GTMOAuth2KeychainCompatibility : NSObject
|
||||
|
||||
/*! @brief Encodes the given @c GTMAppAuthFetcherAuthorization in a GTMOAuth2 compatible persistence
|
||||
string using URL param key/value encoding.
|
||||
@param authorization The @c GTMAppAuthFetcherAuthorization to serialize in GTMOAuth2 format.
|
||||
@return The GTMOAuth2 persistence representation of this object.
|
||||
*/
|
||||
+ (NSString *)persistenceResponseStringForAuthorization:
|
||||
(GTMAppAuthFetcherAuthorization *)authorization;
|
||||
|
||||
/*! @brief Attempts to create a @c GTMAppAuthFetcherAuthorization from data stored in the keychain
|
||||
in GTMOAuth2 format, at the supplied keychain identifier.
|
||||
@param keychainItemName The keychain name.
|
||||
@param tokenURL The OAuth token endpoint URL.
|
||||
@param redirectURI The OAuth redirect URI used when obtaining the original authorization.
|
||||
@param clientID The OAuth client id.
|
||||
@param clientSecret The OAuth client secret.
|
||||
@return A @c GTMAppAuthFetcherAuthorization object, or nil.
|
||||
*/
|
||||
+ (nullable GTMAppAuthFetcherAuthorization *)
|
||||
authorizeFromKeychainForName:(NSString *)keychainItemName
|
||||
tokenURL:(NSURL *)tokenURL
|
||||
redirectURI:(NSString *)redirectURI
|
||||
clientID:(NSString *)clientID
|
||||
clientSecret:(nullable NSString *)clientSecret;
|
||||
|
||||
/*! @brief Attempts to create a @c GTMAppAuthFetcherAuthorization from a @c NSString
|
||||
representation of the GTMOAuth2 keychain data.
|
||||
@param persistenceString String representation of the GTMOAuth2 keychain data.
|
||||
@param tokenURL The OAuth token endpoint URL.
|
||||
@param redirectURI The OAuth redirect URI used when obtaining the original authorization.
|
||||
@param clientID The OAuth client id.
|
||||
@param clientSecret The OAuth client secret.
|
||||
@return A @c GTMAppAuthFetcherAuthorization object, or nil.
|
||||
*/
|
||||
+ (nullable GTMAppAuthFetcherAuthorization *)
|
||||
authorizeFromPersistenceString:(NSString *)persistenceString
|
||||
tokenURL:(NSURL *)tokenURL
|
||||
redirectURI:(NSString *)redirectURI
|
||||
clientID:(NSString *)clientID
|
||||
clientSecret:(nullable NSString *)clientSecret;
|
||||
|
||||
/*! @brief Removes stored tokens, such as when the user signs out.
|
||||
@param keychainItemName The keychain name.
|
||||
@return YES the tokens were removed successfully (or didn't exist).
|
||||
*/
|
||||
+ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName;
|
||||
|
||||
/*! @brief Saves the authorization state to the keychain, in a GTMOAuth2 compatible manner.
|
||||
@param keychainItemName The keychain name.
|
||||
@return YES when the state was saved successfully.
|
||||
*/
|
||||
+ (BOOL)saveAuthToKeychainForName:(NSString *)keychainItemName
|
||||
authentication:(GTMAppAuthFetcherAuthorization *)auth
|
||||
__attribute__((deprecated(
|
||||
"Use GTMAppAuthFetcherAuthorization::saveAuthorization:toKeychainForName:")));
|
||||
|
||||
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
|
||||
|
||||
/*! @brief Attempts to create a @c GTMAppAuthFetcherAuthorization from data stored in the keychain
|
||||
in GTMOAuth2 format, at the supplied keychain identifier. Uses Google OAuth provider
|
||||
information.
|
||||
@param keychainItemName The keychain name.
|
||||
@param clientID The OAuth client id.
|
||||
@param clientSecret The OAuth client secret.
|
||||
@return A @c GTMAppAuthFetcherAuthorization object, or nil.
|
||||
*/
|
||||
+ (nullable GTMAppAuthFetcherAuthorization *)
|
||||
authForGoogleFromKeychainForName:(NSString *)keychainItemName
|
||||
clientID:(NSString *)clientID
|
||||
clientSecret:(nullable NSString *)clientSecret;
|
||||
|
||||
/*! @brief Returns Google's OAuth 2.0 authorization endpoint.
|
||||
@return Returns Google's OAuth 2.0 authorization endpoint.
|
||||
*/
|
||||
+ (NSURL *)googleAuthorizationURL;
|
||||
|
||||
/*! @brief Returns Google's OAuth 2.0 token endpoint.
|
||||
@return Returns Google's OAuth 2.0 token endpoint.
|
||||
*/
|
||||
+ (NSURL *)googleTokenURL;
|
||||
|
||||
/*! @brief Returns Google's OAuth 2.0 revocation endpoint.
|
||||
@return Returns Google's OAuth 2.0 revocation endpoint.
|
||||
*/
|
||||
+ (NSURL *)googleRevocationURL;
|
||||
|
||||
/*! @brief Returns Google's OAuth 2.0 userinfo endpoint.
|
||||
@return Returns Google's OAuth 2.0 userinfo endpoint.
|
||||
*/
|
||||
+ (NSURL *)googleUserInfoURL;
|
||||
|
||||
/*! @brief Returns Google's native OOB redirect URI.
|
||||
@discussion This is a legacy redirect URI that was used with WebViews.
|
||||
@return Returns Google's native OOB redirect URI.
|
||||
*/
|
||||
+ (NSString *)nativeClientRedirectURI;
|
||||
|
||||
#endif // !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,293 @@
|
||||
/*! @file GTMKeychain_iOS.m
|
||||
@brief GTMAppAuth SDK
|
||||
@copyright
|
||||
Copyright 2016 Google Inc.
|
||||
@copydetails
|
||||
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 <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
|
||||
#import "GTMAppAuth/Sources/Public/GTMAppAuth/GTMKeychain.h"
|
||||
|
||||
#import <Security/Security.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, GTMAppAuthFetcherAuthorizationGTMAppAuthGTMOAuth2KeychainError) {
|
||||
GTMAppAuthGTMOAuth2KeychainErrorBadArguments = -1301,
|
||||
GTMAppAuthGTMOAuth2KeychainErrorNoPassword = -1302
|
||||
};
|
||||
|
||||
/*! @brief Keychain helper class.
|
||||
*/
|
||||
@interface GTMAppAuthGTMOAuth2Keychain : NSObject
|
||||
|
||||
+ (GTMAppAuthGTMOAuth2Keychain *)defaultKeychain;
|
||||
|
||||
// OK to pass nil for the error parameter.
|
||||
- (NSString *)passwordForService:(NSString *)service
|
||||
account:(NSString *)account
|
||||
error:(NSError **)error;
|
||||
|
||||
- (NSData *)passwordDataForService:(NSString *)service
|
||||
account:(NSString *)account
|
||||
error:(NSError **)error;
|
||||
|
||||
// OK to pass nil for the error parameter.
|
||||
- (BOOL)removePasswordForService:(NSString *)service
|
||||
account:(NSString *)account
|
||||
error:(NSError **)error;
|
||||
|
||||
// OK to pass nil for the error parameter.
|
||||
//
|
||||
// accessibility should be one of the constants for kSecAttrAccessible
|
||||
// such as kSecAttrAccessibleWhenUnlocked
|
||||
- (BOOL)setPassword:(NSString *)password
|
||||
forService:(NSString *)service
|
||||
accessibility:(CFTypeRef)accessibility
|
||||
account:(NSString *)account
|
||||
error:(NSError **)error;
|
||||
|
||||
- (BOOL)setPasswordData:(NSData *)passwordData
|
||||
forService:(NSString *)service
|
||||
accessibility:(CFTypeRef)accessibility
|
||||
account:(NSString *)account
|
||||
error:(NSError **)error;
|
||||
|
||||
// For unit tests: allow setting a mock object
|
||||
+ (void)setDefaultKeychain:(GTMAppAuthGTMOAuth2Keychain *)keychain;
|
||||
|
||||
@end
|
||||
|
||||
NSString *const kGTMAppAuthFetcherAuthorizationGTMOAuth2ErrorDomain = @"com.google.GTMOAuth2";
|
||||
NSString *const kGTMAppAuthFetcherAuthorizationGTMOAuth2KeychainErrorDomain =
|
||||
@"com.google.GTMOAuthKeychain";
|
||||
static NSString *const kGTMAppAuthFetcherAuthorizationGTMOAuth2AccountName = @"OAuth";
|
||||
static GTMAppAuthGTMOAuth2Keychain* gGTMAppAuthFetcherAuthorizationGTMOAuth2DefaultKeychain = nil;
|
||||
|
||||
@implementation GTMKeychain
|
||||
|
||||
+ (BOOL)removePasswordFromKeychainForName:(NSString *)keychainItemName {
|
||||
GTMAppAuthGTMOAuth2Keychain *keychain = [GTMAppAuthGTMOAuth2Keychain defaultKeychain];
|
||||
return [keychain removePasswordForService:keychainItemName
|
||||
account:kGTMAppAuthFetcherAuthorizationGTMOAuth2AccountName
|
||||
error:nil];
|
||||
}
|
||||
|
||||
+ (NSString *)passwordFromKeychainForName:(NSString *)keychainItemName {
|
||||
GTMAppAuthGTMOAuth2Keychain *keychain = [GTMAppAuthGTMOAuth2Keychain defaultKeychain];
|
||||
NSError *error;
|
||||
NSString *password =
|
||||
[keychain passwordForService:keychainItemName
|
||||
account:kGTMAppAuthFetcherAuthorizationGTMOAuth2AccountName
|
||||
error:&error];
|
||||
return password;
|
||||
}
|
||||
|
||||
+ (BOOL)savePasswordToKeychainForName:(NSString *)keychainItemName password:(NSString *)password {
|
||||
return [self savePasswordToKeychainForName:keychainItemName
|
||||
password:password
|
||||
accessibility:NULL
|
||||
error:NULL];
|
||||
}
|
||||
|
||||
+ (BOOL)savePasswordToKeychainForName:(NSString *)keychainItemName password:(NSString *)password
|
||||
accessibility:(CFTypeRef)accessibility
|
||||
error:(NSError **)error {
|
||||
if (accessibility == NULL) {
|
||||
accessibility = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
|
||||
}
|
||||
// make a response string containing the values we want to save
|
||||
GTMAppAuthGTMOAuth2Keychain *keychain = [GTMAppAuthGTMOAuth2Keychain defaultKeychain];
|
||||
return [keychain setPassword:password
|
||||
forService:keychainItemName
|
||||
accessibility:accessibility
|
||||
account:kGTMAppAuthFetcherAuthorizationGTMOAuth2AccountName
|
||||
error:error];
|
||||
}
|
||||
|
||||
/*! @brief Saves the password string to the keychain with the given identifier.
|
||||
@param keychainItemName Keychain name of the item.
|
||||
@param password Password string to save.
|
||||
@return YES when the password string was saved successfully.
|
||||
*/
|
||||
+ (BOOL)savePasswordDataToKeychainForName:(NSString *)keychainItemName
|
||||
passwordData:(NSData *)password {
|
||||
CFTypeRef accessibility = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
|
||||
// make a response string containing the values we want to save
|
||||
GTMAppAuthGTMOAuth2Keychain *keychain = [GTMAppAuthGTMOAuth2Keychain defaultKeychain];
|
||||
return [keychain setPasswordData:password
|
||||
forService:keychainItemName
|
||||
accessibility:accessibility
|
||||
account:kGTMAppAuthFetcherAuthorizationGTMOAuth2AccountName
|
||||
error:NULL];
|
||||
}
|
||||
|
||||
/*! @brief Loads the password string from the keychain with the given identifier.
|
||||
@param keychainItemName Keychain name of the item.
|
||||
@return The password string at the given identifier, or nil.
|
||||
*/
|
||||
+ (NSData *)passwordDataFromKeychainForName:(NSString *)keychainItemName {
|
||||
GTMAppAuthGTMOAuth2Keychain *keychain = [GTMAppAuthGTMOAuth2Keychain defaultKeychain];
|
||||
NSError *error;
|
||||
NSData *password =
|
||||
[keychain passwordDataForService:keychainItemName
|
||||
account:kGTMAppAuthFetcherAuthorizationGTMOAuth2AccountName
|
||||
error:&error];
|
||||
return password;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark GTMAppAuthGTMOAuth2Keychain
|
||||
|
||||
@implementation GTMAppAuthGTMOAuth2Keychain
|
||||
|
||||
+ (GTMAppAuthGTMOAuth2Keychain *)defaultKeychain {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once (&onceToken, ^{
|
||||
gGTMAppAuthFetcherAuthorizationGTMOAuth2DefaultKeychain = [[self alloc] init];
|
||||
});
|
||||
return gGTMAppAuthFetcherAuthorizationGTMOAuth2DefaultKeychain;
|
||||
}
|
||||
|
||||
|
||||
// For unit tests: allow setting a mock object
|
||||
+ (void)setDefaultKeychain:(GTMAppAuthGTMOAuth2Keychain *)keychain {
|
||||
if (gGTMAppAuthFetcherAuthorizationGTMOAuth2DefaultKeychain != keychain) {
|
||||
gGTMAppAuthFetcherAuthorizationGTMOAuth2DefaultKeychain = keychain;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)keyForService:(NSString *)service account:(NSString *)account {
|
||||
return [NSString stringWithFormat:@"com.google.GTMOAuth.%@%@", service, account];
|
||||
}
|
||||
|
||||
+ (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NSString *)account {
|
||||
NSMutableDictionary *query =
|
||||
[NSMutableDictionary dictionaryWithObjectsAndKeys:(id)kSecClassGenericPassword, (id)kSecClass,
|
||||
@"OAuth", (id)kSecAttrGeneric,
|
||||
account, (id)kSecAttrAccount,
|
||||
service, (id)kSecAttrService,
|
||||
nil];
|
||||
return query;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NSString *)account {
|
||||
return [[self class] keychainQueryForService:service account:account];
|
||||
}
|
||||
|
||||
// iPhone
|
||||
- (NSString *)passwordForService:(NSString *)service
|
||||
account:(NSString *)account
|
||||
error:(NSError **)error {
|
||||
NSData *passwordData = [self passwordDataForService:service account:account error:error];
|
||||
if (!passwordData) {
|
||||
return nil;
|
||||
}
|
||||
NSString *result = [[NSString alloc] initWithData:passwordData
|
||||
encoding:NSUTF8StringEncoding];
|
||||
return result;
|
||||
}
|
||||
|
||||
// iPhone
|
||||
- (NSData *)passwordDataForService:(NSString *)service
|
||||
account:(NSString *)account
|
||||
error:(NSError **)error {
|
||||
OSStatus status = GTMAppAuthGTMOAuth2KeychainErrorBadArguments;
|
||||
NSData *result = nil;
|
||||
if (service.length > 0 && account.length > 0) {
|
||||
CFDataRef passwordData = NULL;
|
||||
NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
|
||||
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
|
||||
[keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
|
||||
|
||||
status = SecItemCopyMatching((CFDictionaryRef)keychainQuery,
|
||||
(CFTypeRef *)&passwordData);
|
||||
if (status == noErr && 0 < [(__bridge NSData *)passwordData length]) {
|
||||
result = [(__bridge NSData *)passwordData copy];
|
||||
}
|
||||
if (passwordData != NULL) {
|
||||
CFRelease(passwordData);
|
||||
}
|
||||
}
|
||||
if (status != noErr && error != NULL) {
|
||||
*error = [NSError errorWithDomain:kGTMAppAuthFetcherAuthorizationGTMOAuth2KeychainErrorDomain
|
||||
code:status
|
||||
userInfo:nil];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// iPhone
|
||||
- (BOOL)removePasswordForService:(NSString *)service
|
||||
account:(NSString *)account
|
||||
error:(NSError **)error {
|
||||
OSStatus status = GTMAppAuthGTMOAuth2KeychainErrorBadArguments;
|
||||
if (0 < [service length] && 0 < [account length]) {
|
||||
NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
|
||||
status = SecItemDelete((CFDictionaryRef)keychainQuery);
|
||||
}
|
||||
if (status != noErr && error != NULL) {
|
||||
*error = [NSError errorWithDomain:kGTMAppAuthFetcherAuthorizationGTMOAuth2KeychainErrorDomain
|
||||
code:status
|
||||
userInfo:nil];
|
||||
}
|
||||
return status == noErr;
|
||||
}
|
||||
|
||||
// iPhone
|
||||
- (BOOL)setPassword:(NSString *)password
|
||||
forService:(NSString *)service
|
||||
accessibility:(CFTypeRef)accessibility
|
||||
account:(NSString *)account
|
||||
error:(NSError **)error {
|
||||
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [self setPasswordData:passwordData
|
||||
forService:service
|
||||
accessibility:accessibility
|
||||
account:account
|
||||
error:error];
|
||||
}
|
||||
|
||||
- (BOOL)setPasswordData:(NSData *)passwordData
|
||||
forService:(NSString *)service
|
||||
accessibility:(CFTypeRef)accessibility
|
||||
account:(NSString *)account
|
||||
error:(NSError **)error {
|
||||
OSStatus status = GTMAppAuthGTMOAuth2KeychainErrorBadArguments;
|
||||
if (0 < [service length] && 0 < [account length]) {
|
||||
[self removePasswordForService:service account:account error:nil];
|
||||
if (0 < [passwordData length]) {
|
||||
NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
|
||||
[keychainQuery setObject:passwordData forKey:(id)kSecValueData];
|
||||
|
||||
if (accessibility != NULL) {
|
||||
[keychainQuery setObject:(__bridge id)accessibility
|
||||
forKey:(id)kSecAttrAccessible];
|
||||
}
|
||||
status = SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
|
||||
}
|
||||
}
|
||||
if (status != noErr && error != NULL) {
|
||||
*error = [NSError errorWithDomain:kGTMAppAuthFetcherAuthorizationGTMOAuth2KeychainErrorDomain
|
||||
code:status
|
||||
userInfo:nil];
|
||||
}
|
||||
return status == noErr;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif // TARGET_OS_IPHONE
|
||||
Generated
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
Generated
+391
@@ -0,0 +1,391 @@
|
||||
[](https://cocoapods.org/pods/GTMAppAuth)
|
||||
[](https://cocoapods.org/pods/GTMAppAuth)
|
||||
[](https://cocoapods.org/pods/GTMAppAuth)
|
||||
[](https://github.com/google/GTMAppAuth/actions/workflows/tests.yml)
|
||||
|
||||
# GTMAppAuth for Apple Platforms
|
||||
|
||||
GTMAppAuth enables you to use [AppAuth](https://github.com/openid/AppAuth-iOS)
|
||||
with the
|
||||
[Google Toolbox for Mac - Session Fetcher](https://github.com/google/gtm-session-fetcher)
|
||||
and
|
||||
[Google APIs Client Library for Objective-C For REST](https://github.com/google/google-api-objectivec-client-for-rest)
|
||||
libraries on iOS, macOS, tvOS, and watchOS by providing an implementation of
|
||||
[`GTMFetcherAuthorizationProtocol`](https://github.com/google/gtm-session-fetcher/blob/2a3b5264108e80d62003b770ff02eb7364ff1365/Source/GTMSessionFetcher.h#L660)
|
||||
for authorizing requests with AppAuth.
|
||||
|
||||
GTMAppAuth is an alternative authorizer to GTMOAuth2. The key differentiator is
|
||||
the use of the user's default browser for the authorization, which is more
|
||||
secure, more usable (the user's session can be reused) and follows modern OAuth
|
||||
[best practices for native apps](https://tools.ietf.org/html/draft-ietf-oauth-native-apps).
|
||||
Compatibility methods for GTMOAuth2 are offered allowing you to migrate
|
||||
from GTMOAuth2 to GTMAppAuth preserving previously serialized authorizations
|
||||
(so users shouldn't need to re-authenticate).
|
||||
|
||||
## Setup
|
||||
|
||||
If you use [CocoaPods](https://guides.cocoapods.org/using/getting-started.html),
|
||||
simply add:
|
||||
|
||||
pod 'GTMAppAuth'
|
||||
|
||||
To your `Podfile` and run `pod install`.
|
||||
|
||||
## Usage
|
||||
|
||||
### Configuration
|
||||
|
||||
To configure GTMAppAuth with the OAuth endpoints for Google, you can use the
|
||||
convenience method:
|
||||
|
||||
```objc
|
||||
OIDServiceConfiguration *configuration =
|
||||
[GTMAppAuthFetcherAuthorization configurationForGoogle];
|
||||
```
|
||||
|
||||
Alternatively, you can configure GTMAppAuth by specifying the endpoints
|
||||
directly:
|
||||
|
||||
```objc
|
||||
NSURL *authorizationEndpoint =
|
||||
[NSURL URLWithString:@"https://accounts.google.com/o/oauth2/v2/auth"];
|
||||
NSURL *tokenEndpoint =
|
||||
[NSURL URLWithString:@"https://www.googleapis.com/oauth2/v4/token"];
|
||||
|
||||
OIDServiceConfiguration *configuration =
|
||||
[[OIDServiceConfiguration alloc]
|
||||
initWithAuthorizationEndpoint:authorizationEndpoint
|
||||
tokenEndpoint:tokenEndpoint];
|
||||
|
||||
// perform the auth request...
|
||||
```
|
||||
|
||||
Or through discovery:
|
||||
|
||||
```objc
|
||||
NSURL *issuer = [NSURL URLWithString:@"https://accounts.google.com"];
|
||||
|
||||
[OIDAuthorizationService discoverServiceConfigurationForIssuer:issuer
|
||||
completion:^(OIDServiceConfiguration *_Nullable configuration,
|
||||
NSError *_Nullable error) {
|
||||
if (!configuration) {
|
||||
NSLog(@"Error retrieving discovery document: %@",
|
||||
[error localizedDescription]);
|
||||
return;
|
||||
}
|
||||
|
||||
// perform the auth request...
|
||||
}];
|
||||
```
|
||||
|
||||
### Authorizing
|
||||
|
||||
First, you need to have a way for your UIApplicationDelegate to continue the
|
||||
authorization flow session from the incoming redirect URI. Typically you could
|
||||
store the in-progress OIDAuthorizationFlowSession instance in a property:
|
||||
|
||||
```objc
|
||||
// property of the app's UIApplicationDelegate
|
||||
@property(nonatomic, nullable)
|
||||
id<OIDExternalUserAgentSession> currentAuthorizationFlow;
|
||||
```
|
||||
|
||||
And in a location accessible by all controllers that need authorization, a
|
||||
property to store the authorization state:
|
||||
|
||||
```objc
|
||||
// property of the containing class
|
||||
@property(nonatomic, nullable) GTMAppAuthFetcherAuthorization *authorization;
|
||||
```
|
||||
|
||||
Then, initiate the authorization request. By using the
|
||||
`authStateByPresentingAuthorizationRequest` method, the OAuth token
|
||||
exchange will be performed automatically, and everything will be protected with
|
||||
PKCE (if the server supports it).
|
||||
|
||||
```objc
|
||||
// builds authentication request
|
||||
OIDAuthorizationRequest *request =
|
||||
[[OIDAuthorizationRequest alloc] initWithConfiguration:configuration
|
||||
clientId:kClientID
|
||||
clientSecret:kClientSecret
|
||||
scopes:@[OIDScopeOpenID, OIDScopeProfile]
|
||||
redirectURL:redirectURI
|
||||
responseType:OIDResponseTypeCode
|
||||
additionalParameters:nil];
|
||||
// performs authentication request
|
||||
self.appDelegate.currentAuthorizationFlow =
|
||||
[OIDAuthState authStateByPresentingAuthorizationRequest:request
|
||||
callback:^(OIDAuthState *_Nullable authState,
|
||||
NSError *_Nullable error) {
|
||||
if (authState) {
|
||||
// Creates the GTMAppAuthFetcherAuthorization from the OIDAuthState.
|
||||
GTMAppAuthFetcherAuthorization *authorization =
|
||||
[[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState];
|
||||
|
||||
self.authorization = authorization;
|
||||
NSLog(@"Got authorization tokens. Access token: %@",
|
||||
authState.lastTokenResponse.accessToken);
|
||||
} else {
|
||||
NSLog(@"Authorization error: %@", [error localizedDescription]);
|
||||
self.authorization = nil;
|
||||
}
|
||||
}];
|
||||
```
|
||||
|
||||
### Handling the Redirect
|
||||
|
||||
The authorization response URL is returned to the app via the platform-specific
|
||||
application delegate method, so you need to pipe this through to the current
|
||||
authorization session (created in the previous session).
|
||||
|
||||
#### macOS Custom URI Scheme Redirect Example
|
||||
|
||||
```objc
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
// Other app initialization code ...
|
||||
|
||||
// Register for GetURL events.
|
||||
NSAppleEventManager *appleEventManager =
|
||||
[NSAppleEventManager sharedAppleEventManager];
|
||||
[appleEventManager setEventHandler:self
|
||||
andSelector:@selector(handleGetURLEvent:withReplyEvent:)
|
||||
forEventClass:kInternetEventClass
|
||||
andEventID:kAEGetURL];
|
||||
}
|
||||
|
||||
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
|
||||
withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
|
||||
NSString *URLString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
|
||||
NSURL *URL = [NSURL URLWithString:URLString];
|
||||
[_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:URL];
|
||||
}
|
||||
```
|
||||
|
||||
#### iOS Custom URI Scheme Redirect Example
|
||||
|
||||
```objc
|
||||
- (BOOL)application:(UIApplication *)app
|
||||
openURL:(NSURL *)url
|
||||
options:(NSDictionary<NSString *, id> *)options {
|
||||
// Sends the URL to the current authorization flow (if any) which will
|
||||
// process it if it relates to an authorization response.
|
||||
if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) {
|
||||
_currentAuthorizationFlow = nil;
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Your additional URL handling (if any) goes here.
|
||||
|
||||
return NO;
|
||||
}
|
||||
```
|
||||
|
||||
### Making API Calls
|
||||
|
||||
The goal of GTMAppAuth is to enable you to authorize HTTP requests with fresh
|
||||
tokens following the Session Fetcher pattern, which you can do like so:
|
||||
|
||||
```objc
|
||||
// Creates a GTMSessionFetcherService with the authorization.
|
||||
// Normally you would save this service object and re-use it for all REST API calls.
|
||||
GTMSessionFetcherService *fetcherService = [[GTMSessionFetcherService alloc] init];
|
||||
fetcherService.authorizer = self.authorization;
|
||||
|
||||
// Creates a fetcher for the API call.
|
||||
NSURL *userinfoEndpoint = [NSURL URLWithString:@"https://www.googleapis.com/oauth2/v3/userinfo"];
|
||||
GTMSessionFetcher *fetcher = [fetcherService fetcherWithURL:userinfoEndpoint];
|
||||
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
|
||||
// Checks for an error.
|
||||
if (error) {
|
||||
// OIDOAuthTokenErrorDomain indicates an issue with the authorization.
|
||||
if ([error.domain isEqual:OIDOAuthTokenErrorDomain]) {
|
||||
self.authorization = nil;
|
||||
NSLog(@"Authorization error during token refresh, clearing state. %@",
|
||||
error);
|
||||
// Other errors are assumed transient.
|
||||
} else {
|
||||
NSLog(@"Transient error during token refresh. %@", error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Parses the JSON response.
|
||||
NSError *jsonError = nil;
|
||||
id jsonDictionaryOrArray =
|
||||
[NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
|
||||
|
||||
// JSON error.
|
||||
if (jsonError) {
|
||||
NSLog(@"JSON decoding error %@", jsonError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Success response!
|
||||
NSLog(@"Success: %@", jsonDictionaryOrArray);
|
||||
}];
|
||||
```
|
||||
|
||||
### Serialization
|
||||
|
||||
You can easily serialize `GTMAppAuthFetcherAuthorization` objects using the
|
||||
included Keychain category.
|
||||
|
||||
```objc
|
||||
// Serialize to Keychain
|
||||
[GTMAppAuthFetcherAuthorization saveAuthorization:_authorization
|
||||
toKeychainForName:kGTMAppAuthExampleAuthorizerKey];
|
||||
|
||||
// Deserialize from Keychain
|
||||
GTMAppAuthFetcherAuthorization* authorization =
|
||||
[GTMAppAuthFetcherAuthorization authorizationFromKeychainForName:kGTMAppAuthExampleAuthorizerKey];
|
||||
|
||||
// Remove from Keychain
|
||||
[GTMAppAuthFetcherAuthorization
|
||||
removeAuthorizationFromKeychainForName:kGTMAppAuthExampleAuthorizerKey];
|
||||
```
|
||||
|
||||
### GTMOAuth2-compatible Serialization
|
||||
|
||||
To assist the migration from GTMOAuth2 to GTMAppAuth, GTMOAuth2-compatible
|
||||
serialization methods are provided in `GTMOAuth2KeychainCompatibility`.
|
||||
|
||||
```objc
|
||||
// Deserialize from Keychain
|
||||
GTMAppAuthFetcherAuthorization *auth =
|
||||
[GTMOAuth2KeychainCompatibility authForGoogleFromKeychainForName:kKeychainItemName
|
||||
clientID:clientID
|
||||
clientSecret:clientSecret];
|
||||
|
||||
// Remove from Keychain
|
||||
[GTMOAuth2KeychainCompatibility removeAuthFromKeychainForName:kKeychainItemName];
|
||||
```
|
||||
|
||||
You can also serialize to GTMOAuth2 format, though this is discouraged (you
|
||||
should serialize in GTMAppAuth format as described above).
|
||||
|
||||
```objc
|
||||
// Serialize to Keychain
|
||||
[GTMOAuth2KeychainCompatibility saveAuthToKeychainForName:kKeychainItemName
|
||||
authentication:authorization];
|
||||
```
|
||||
|
||||
## Included Samples
|
||||
|
||||
Try out one of the included sample apps under [Examples](Examples). In the
|
||||
apps folder run `pod install`, then open the resulting `xcworkspace` file.
|
||||
|
||||
Be sure to follow the instructions in
|
||||
[Example-iOS/README.md](Examples/Example-iOS/README.md) or
|
||||
[Example-macOS/README.md](Examples/Example-macOS/README.md) to configure
|
||||
your own OAuth client ID for use with the example.
|
||||
|
||||
## Differences with GTMOAuth2
|
||||
|
||||
### Authorization Method
|
||||
|
||||
GTMAppAuth uses the browser to present the authorization request, while
|
||||
GTMOAuth2 uses an embedded web-view. Migrating to GTMAppAuth will require you
|
||||
to change how you authorize the user. Follow the instructions above to get the
|
||||
authorization. You can then create a `GTMAppAuthFetcherAuthorization` object
|
||||
with the `initWithAuthState:authState` initializer.
|
||||
|
||||
Once you have the `GTMAppAuthFetcherAuthorization` you can continue to make REST
|
||||
calls as before.
|
||||
|
||||
### Error Handling
|
||||
|
||||
GTMAppAuth's error handling is also different. There are no notifications,
|
||||
instead you need to inspect NSError in the callback. If the error domain is
|
||||
`OIDOAuthTokenErrorDomain`, it indicates an authorization error, you should
|
||||
clear your authorization state and consider prompting the user to authorize
|
||||
again. Other errors are generally considered transient, meaning that you should
|
||||
retry the request after a delay.
|
||||
|
||||
### Serialization
|
||||
|
||||
The serialization format is different between GTMOAuth2 and GTMAppAuth, though
|
||||
we have methods to help you migrate from one to the other without losing any
|
||||
data.
|
||||
|
||||
## Migrating from GTMOAuth2
|
||||
|
||||
### OAuth Client Registration
|
||||
|
||||
Typically, GTMOAuth2 clients are registered with Google as type "Other". This is
|
||||
correct for macOS, but on iOS clients should be registered with the type "iOS".
|
||||
|
||||
If you're migrating an iOS client, in the *same project as your existing client*,
|
||||
[register a new iOS client](https://console.developers.google.com/apis/credentials?project=_)
|
||||
to be used with GTMAppAuth.
|
||||
|
||||
### Changing your Authorization Flows
|
||||
|
||||
Both GTMOAuth2 and GTMAppAuth support the `GTMFetcherAuthorizationProtocol`
|
||||
allowing you to use the authorization with the session fetcher. Where you
|
||||
previously had a property like `GTMOAuth2Authentication *authorization` change the
|
||||
type to reference the protocol instead, i.e.:
|
||||
`id<GTMFetcherAuthorizationProtocol> authorization`. This allows you to switch
|
||||
the authorization implementation under the hood to GTMAppAuth.
|
||||
|
||||
Then, follow the instructions above to replace authorization request
|
||||
(where you ask the user to grant access) with the GTMAppAuth approach. If you
|
||||
created a new OAuth client, use that for these requests.
|
||||
|
||||
### Serialization & Migrating Existing Grants
|
||||
|
||||
GTMAppAuth has a new data format and APIs for serialization. Unlike
|
||||
GTMOAuth2, GTMAppAuth serializes the configuration and history of the
|
||||
authorization, including the client id, and a record of the authorization
|
||||
request that resulted in the authorization grant.
|
||||
|
||||
The client ID used for GTMAppAuth is [different](#oauth-client-registration) to
|
||||
the one used for GTMOAuth2. In order to keep track of the different client ids
|
||||
used for new and old grants, it's recommended to migrate to the new
|
||||
serialization format, which will store that for you.
|
||||
[GTMOAuth2-compatible serialization](#gtmoauth2-compatible-serialization) is
|
||||
also offered, but not fully supported.
|
||||
|
||||
Change how you serialize your `authorization` object using the new methods
|
||||
using the following example.
|
||||
|
||||
```objc
|
||||
// Serialize to Keychain
|
||||
[GTMAppAuthFetcherAuthorization saveAuthorization:(GTMAppAuthFetcherAuthorization *)authorization
|
||||
toKeychainForName:kNewKeychainName];
|
||||
```
|
||||
|
||||
Be sure to use a *new* name for the keychain. Don't reuse your old one!
|
||||
|
||||
For deserializing, we can preserve all existing grants (so users who authorized
|
||||
your app in GTMOAuth2 don't have to authorize it again). Remember that when
|
||||
deserializing the *old* data you need to use your *old* keychain name, and
|
||||
the old client id and client secret (if those changed), and that when
|
||||
serializing to the *new* format, use the *new* keychain name.
|
||||
Once again, pay particular care to use the old details when deserializing the
|
||||
GTMOAuth2 keychain, and the new details for all other GTMAppAuth calls.
|
||||
|
||||
Keychain migration example:
|
||||
|
||||
```objc
|
||||
// Attempt to deserialize from Keychain in GTMAppAuth format.
|
||||
id<GTMFetcherAuthorizationProtocol> authorization =
|
||||
[GTMAppAuthFetcherAuthorization authorizationFromKeychainForName:kNewKeychainName];
|
||||
|
||||
// If no data found in the new format, try to deserialize data from GTMOAuth2
|
||||
if (!authorization) {
|
||||
// Tries to load the data serialized by GTMOAuth2 using old keychain name.
|
||||
// If you created a new client id, be sure to use the *previous* client id and secret here.
|
||||
authorization =
|
||||
[GTMOAuth2KeychainCompatibility authForGoogleFromKeychainForName:kPreviousKeychainName
|
||||
clientID:kPreviousClientID
|
||||
clientSecret:kPreviousClientSecret];
|
||||
if (authorization) {
|
||||
// Remove previously stored GTMOAuth2-formatted data.
|
||||
[GTMOAuth2KeychainCompatibility removeAuthFromKeychainForName:kPreviousKeychainName];
|
||||
// Serialize to Keychain in GTMAppAuth format.
|
||||
[GTMAppAuthFetcherAuthorization saveAuthorization:(GTMAppAuthFetcherAuthorization *)authorization
|
||||
toKeychainForName:kNewKeychainName];
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user