adding pods method of package managing

This commit is contained in:
talksik
2021-12-13 12:34:20 -08:00
parent dad674aca7
commit 705203d7bd
5871 changed files with 1259393 additions and 3 deletions
@@ -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
@@ -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
@@ -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
@@ -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
+293
View File
@@ -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
+202
View File
@@ -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.
+391
View File
@@ -0,0 +1,391 @@
[![Version](https://img.shields.io/cocoapods/v/GTMAppAuth.svg?style=flat)](https://cocoapods.org/pods/GTMAppAuth)
[![Platform](https://img.shields.io/cocoapods/p/GTMAppAuth.svg?style=flat)](https://cocoapods.org/pods/GTMAppAuth)
[![License](https://img.shields.io/cocoapods/l/GTMAppAuth.svg?style=flat)](https://cocoapods.org/pods/GTMAppAuth)
[![tests](https://github.com/google/GTMAppAuth/actions/workflows/tests.yml/badge.svg?event=push)](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];
}
}
```