diff --git a/Podfile.lock b/Podfile.lock index 873a876b..ddef80ec 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -13,9 +13,9 @@ DEPENDENCIES: - Specta (~> 0.2) SPEC CHECKSUMS: - Expecta: 03aabd0a89d8dea843baecb19a7fd7466a69a31d - OCMock: f6cb8c162ab9d5620dddf411282c7b2c0ee78854 - OHHTTPStubs: c1e362552b71b81e1deb7a80f44c51585b946c43 - Specta: 9141310f46b1f68b676650ff2854e1ed0b74163a + Expecta: a354d4633409dd9fe8c4f5ff5130426adbe31628 + OCMock: a73f69963a8a542b0b343e2617650e4dca0cbbc2 + OHHTTPStubs: 5cb8c4e590851d42cfb02fb6269d5404cfa2e9c3 + Specta: 15a276a6343867b426d5ed135d5aa4d04123a573 -COCOAPODS: 0.36.1 +COCOAPODS: 0.36.3 diff --git a/README.md b/README.md old mode 100755 new mode 100644 index ab6f2ddb..c060631a --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ In order to install the library this way add the following line to your `Podfile and run the following command `pod install`. -*Note:* CocoaPods is now the preferred way to integrate NXOAuth2Client into XCode +*Note:* CocoaPods is now the preferred way to integrate NXOAuth2Client into Xcode ### Manually including the library in your Xcode project diff --git a/Sources/OAuth2Client/NXOAuth2AccessToken.m b/Sources/OAuth2Client/NXOAuth2AccessToken.m index 2b64be1e..4ff590cd 100644 --- a/Sources/OAuth2Client/NXOAuth2AccessToken.m +++ b/Sources/OAuth2Client/NXOAuth2AccessToken.m @@ -54,7 +54,7 @@ + (instancetype)tokenWithResponseBody:(NSString *)theResponseBody tokenType:(NSS NSString *expiresIn = [jsonDict objectForKey:@"expires_in"]; NSString *anAccessToken = [jsonDict objectForKey:@"access_token"]; NSString *aRefreshToken = [jsonDict objectForKey:@"refresh_token"]; - NSString *scopeString = [jsonDict objectForKey:@"scope"]; + NSObject *scopeObj = [jsonDict objectForKey:@"scope"]; // if the response overrides token_type we take it from the response if ([jsonDict objectForKey:@"token_type"]) { @@ -62,8 +62,13 @@ + (instancetype)tokenWithResponseBody:(NSString *)theResponseBody tokenType:(NSS } NSSet *scope = nil; - if (scopeString && ![scopeString isEqual:[NSNull null]]) { - scope = [NSSet setWithArray:[scopeString componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]]; + if (scopeObj && ![scopeObj isEqual:[NSNull null]]) { + if([scopeObj isKindOfClass:[NSArray class]]) { + scope = [NSSet setWithArray:(NSArray*)scopeObj]; + } + else if([scopeObj isKindOfClass:[NSString class]]) { + scope = [NSSet setWithArray:[(NSString *)scopeObj componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]]; + } } NSDate *expiryDate = nil; diff --git a/Sources/OAuth2Client/NXOAuth2Account.m b/Sources/OAuth2Client/NXOAuth2Account.m index 10b0e73e..be2ff895 100644 --- a/Sources/OAuth2Client/NXOAuth2Account.m +++ b/Sources/OAuth2Client/NXOAuth2Account.m @@ -49,6 +49,7 @@ - (instancetype)initAccountWithOAuthClient:(NXOAuth2Client *)anOAuthClient accou accountType:anAccountType]; if (self) { oauthClient = anOAuthClient; + oauthClient.delegate = self; } return self; } diff --git a/Sources/OAuth2Client/NXOAuth2AccountStore.h b/Sources/OAuth2Client/NXOAuth2AccountStore.h index 9325e8bf..3180cfc0 100644 --- a/Sources/OAuth2Client/NXOAuth2AccountStore.h +++ b/Sources/OAuth2Client/NXOAuth2AccountStore.h @@ -121,7 +121,7 @@ typedef void(^NXOAuth2PreparedAuthorizationURLHandler)(NSURL *preparedURL); - (NSDictionary *)configurationForAccountType:(NSString *)accountType; -#pragma Trust Mode Handler +#pragma mark Trust Mode Handler - (void)setTrustModeHandlerForAccountType:(NSString *)accountType block:(NXOAuth2TrustModeHandler)handler; - (NXOAuth2TrustModeHandler)trustModeHandlerForAccountType:(NSString *)accountType; diff --git a/Sources/OAuth2Client/NXOAuth2AccountStore.m b/Sources/OAuth2Client/NXOAuth2AccountStore.m index 0f2d80fd..dde5b927 100644 --- a/Sources/OAuth2Client/NXOAuth2AccountStore.m +++ b/Sources/OAuth2Client/NXOAuth2AccountStore.m @@ -531,7 +531,9 @@ - (void)oauthClientDidGetAccessToken:(NXOAuth2Client *)client NSString *accountType; @synchronized (self.pendingOAuthClients) { accountType = [self accountTypeOfPendingOAuthClient:client]; - [self.pendingOAuthClients removeObjectForKey:accountType]; + if (accountType) { + [self.pendingOAuthClients removeObjectForKey:accountType]; + } } NXOAuth2Account *account = [[NXOAuth2Account alloc] initAccountWithOAuthClient:client accountType:accountType]; @@ -602,7 +604,9 @@ - (void)oauthClient:(NXOAuth2Client *)client didFailToGetAccessTokenWithError:(N NSString *accountType; @synchronized (self.pendingOAuthClients) { accountType = [self accountTypeOfPendingOAuthClient:client]; - [self.pendingOAuthClients removeObjectForKey:accountType]; + if (accountType) { + [self.pendingOAuthClients removeObjectForKey:accountType]; + } } NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: @@ -699,7 +703,7 @@ - (NSDictionary *)accountsFromDefaultKeychainWithAccessGroup:(NSString *)keyChai result = (__bridge_transfer NSDictionary *)cfResult; if (status != noErr) { - NSAssert1(status == errSecItemNotFound, @"Unexpected error while fetching accounts from keychain: %zd", status); + NSAssert1(status == errSecItemNotFound, @"Unexpected error while fetching accounts from keychain: %d", status); return nil; } @@ -727,7 +731,7 @@ - (void)storeAccountsInDefaultKeychain:(NSDictionary *)accounts withAccessGroup: #endif OSStatus __attribute__((unused)) err = SecItemAdd((__bridge CFDictionaryRef)query, NULL); - NSAssert1(err == noErr, @"Error while adding token to keychain: %zd", err); + NSAssert1(err == noErr, @"Error while adding token to keychain: %d", err); } - (void)removeFromDefaultKeychainWithAccessGroup:(NSString *)keyChainAccessGroup; @@ -745,7 +749,7 @@ - (void)removeFromDefaultKeychainWithAccessGroup:(NSString *)keyChainAccessGroup #endif OSStatus __attribute__((unused)) err = SecItemDelete((__bridge CFDictionaryRef)query); - NSAssert1((err == noErr || err == errSecItemNotFound), @"Error while deleting token from keychain: %zd", err); + NSAssert1((err == noErr || err == errSecItemNotFound), @"Error while deleting token from keychain: %d", err); } diff --git a/Sources/OAuth2Client/NXOAuth2Client.h b/Sources/OAuth2Client/NXOAuth2Client.h index 501779cb..6ee73d29 100644 --- a/Sources/OAuth2Client/NXOAuth2Client.h +++ b/Sources/OAuth2Client/NXOAuth2Client.h @@ -57,7 +57,11 @@ extern NSString * const NXOAuth2ClientConnectionContextTokenRefresh; NSInteger refreshConnectionDidRetryCount; // delegates +#if __has_feature(objc_arc_weak) + NSObject* __weak delegate; +#else NSObject* __unsafe_unretained delegate; // assigned +#endif } @property (nonatomic, readonly, getter = isAuthenticating) BOOL authenticating; @@ -74,7 +78,12 @@ extern NSString * const NXOAuth2ClientConnectionContextTokenRefresh; @property (nonatomic, copy) NSString *acceptType; // defaults to application/json @property (nonatomic, strong) NXOAuth2AccessToken *accessToken; -@property (nonatomic, unsafe_unretained) NSObject* delegate; + +#if __has_feature(objc_arc_weak) + @property (nonatomic, weak) NSObject* delegate; +#else + @property (nonatomic, unsafe_unretained) NSObject* delegate; +#endif @property (nonatomic, readonly) NXOAuth2Connection *authConnection; diff --git a/Sources/OAuth2Client/NXOAuth2Client.m b/Sources/OAuth2Client/NXOAuth2Client.m index 0c0ee8b0..7e017539 100644 --- a/Sources/OAuth2Client/NXOAuth2Client.m +++ b/Sources/OAuth2Client/NXOAuth2Client.m @@ -463,6 +463,13 @@ - (void)refreshAccessTokenAndRetryConnection:(NXOAuth2Connection *)retryConnecti clientSecret, @"client_secret", accessToken.refreshToken, @"refresh_token", nil]; + + if (self.customHeaderFields) { + [self.customHeaderFields enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { + [tokenRequest addValue:obj forHTTPHeaderField:key]; + }]; + } + if (self.desiredScope) { [parameters setObject:[[self.desiredScope allObjects] componentsJoinedByString:@" "] forKey:@"scope"]; } diff --git a/Sources/OAuth2Client/NXOAuth2Connection.h b/Sources/OAuth2Client/NXOAuth2Connection.h index c29feb54..7b885586 100644 --- a/Sources/OAuth2Client/NXOAuth2Connection.h +++ b/Sources/OAuth2Client/NXOAuth2Connection.h @@ -65,7 +65,11 @@ typedef void(^NXOAuth2ConnectionSendingProgressHandler)(unsigned long long bytes NXOAuth2Client *client; +#if __has_feature(objc_arc_weak) + NSObject *__weak delegate; +#else NSObject *__unsafe_unretained delegate; // assigned +#endif NXOAuth2ConnectionResponseHandler responseHandler; NXOAuth2ConnectionSendingProgressHandler sendingProgressHandler; @@ -77,7 +81,12 @@ typedef void(^NXOAuth2ConnectionSendingProgressHandler)(unsigned long long bytes #endif } -@property (nonatomic, unsafe_unretained) NSObject *delegate; +#if __has_feature(objc_arc_weak) + @property (nonatomic, weak) NSObject *delegate; +#else + @property (nonatomic, unsafe_unretained) NSObject *delegate; +#endif + @property (nonatomic, strong, readonly) NSData *data; @property (nonatomic, assign) BOOL savesData; @property (nonatomic, assign, readonly) long long expectedContentLength; diff --git a/Sources/OAuth2Client/NXOAuth2Connection.m b/Sources/OAuth2Client/NXOAuth2Connection.m index fb2e2114..b095572c 100644 --- a/Sources/OAuth2Client/NXOAuth2Connection.m +++ b/Sources/OAuth2Client/NXOAuth2Connection.m @@ -158,7 +158,7 @@ - (void)retry; - (NSURLConnection *)createConnection; { // if the request is a token refresh request don't sign it and don't check for the expiration of the token (we know that already) - NSString *oauthAuthorizationHeader = nil; + NSString *oauthAuthorizationHeader = request.allHTTPHeaderFields[@"Authorization"]; if (client.accessToken && ![[requestParameters objectForKey:@"grant_type"] isEqualToString:@"refresh_token"]) { @@ -222,7 +222,8 @@ - (void)applyParameters:(NSDictionary *)parameters onRequest:(NSMutableURLReques NSString *httpMethod = [aRequest HTTPMethod]; if ([httpMethod caseInsensitiveCompare:@"POST"] != NSOrderedSame - && [httpMethod caseInsensitiveCompare:@"PUT"] != NSOrderedSame) { + && [httpMethod caseInsensitiveCompare:@"PUT"] != NSOrderedSame + && [httpMethod caseInsensitiveCompare:@"PATCH"] != NSOrderedSame) { aRequest.URL = [aRequest.URL nxoauth2_URLByAddingParameters:parameters]; @@ -232,7 +233,7 @@ - (void)applyParameters:(NSDictionary *)parameters onRequest:(NSMutableURLReques if (!contentType || [contentType isEqualToString:@"multipart/form-data"]) { - // sends the POST/PUT request as multipart/form-data as default + // sends the POST/PUT/PATCH request as multipart/form-data as default NSInputStream *postBodyStream = [[NXOAuth2PostBodyStream alloc] initWithParameters:parameters]; @@ -245,7 +246,7 @@ - (void)applyParameters:(NSDictionary *)parameters onRequest:(NSMutableURLReques } else if ([contentType isEqualToString:@"application/x-www-form-urlencoded"]) { - // sends the POST/PUT request as application/x-www-form-urlencoded + // sends the POST/PUT/PATCH request as application/x-www-form-urlencoded NSString *query = [[aRequest.URL nxoauth2_URLByAddingParameters:parameters] query]; [aRequest setHTTPBody:[query dataUsingEncoding:NSUTF8StringEncoding]]; @@ -415,8 +416,13 @@ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLRespon } } } - - if (client.authConnection != self && authenticateHeader && client.accessToken.refreshToken != nil && [authenticateHeader rangeOfString:@"expired_token"].location != NSNotFound) { + + if (self.statusCode == 401 + && client.accessToken.refreshToken != nil + && authenticateHeader + && ([authenticateHeader rangeOfString:@"invalid_token"].location != NSNotFound || + [authenticateHeader rangeOfString:@"expired_token"].location != NSNotFound )) + { [self cancel]; [client refreshAccessTokenAndRetryConnection:self]; } else if (client.authConnection != self && authenticateHeader && client) { @@ -469,13 +475,19 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)connection; } } } - if (authenticateHeader - && [authenticateHeader rangeOfString:@"invalid_token"].location != NSNotFound) { - client.accessToken = nil; + if (authenticateHeader && ([authenticateHeader rangeOfString:@"invalid_token"].location != NSNotFound || [authenticateHeader rangeOfString:@"invalid_grant"].location != NSNotFound)) { + // Try to refresh the token if possible, otherwise remove this account. + if (client.accessToken.refreshToken) { + [self cancel]; + [client refreshAccessTokenAndRetryConnection:self]; + return; + } else { + client.accessToken = nil; + } } } - NSString *localizedError = [NSString stringWithFormat:NSLocalizedString(@"HTTP Error: %d", @"NXOAuth2HTTPErrorDomain description"), self.statusCode]; + NSString *localizedError = [NSString stringWithFormat:NSLocalizedString(@"HTTP Error: %ld", @"NXOAuth2HTTPErrorDomain description"), (long)self.statusCode]; NSDictionary *errorUserInfo = [NSDictionary dictionaryWithObject:localizedError forKey:NSLocalizedDescriptionKey]; NSError *error = [NSError errorWithDomain:NXOAuth2HTTPErrorDomain code:self.statusCode @@ -490,7 +502,7 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)connection; - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error; { #if (NXOAuth2ConnectionDebug) - NSLog(@"%.0fms (FAIL) - %@ (%@ %i)", -[startDate timeIntervalSinceNow]*1000.0, [self descriptionForRequest:request], [error domain], [error code]); + NSLog(@"%.0fms (FAIL) - %@ (%@ %ld)", -[startDate timeIntervalSinceNow]*1000.0, [self descriptionForRequest:request], [error domain], (long)[error code]); #endif if (sendConnectionDidEndNotification) [[NSNotificationCenter defaultCenter] postNotificationName:NXOAuth2ConnectionDidEndNotification object:self]; diff --git a/Sources/OAuth2Client/NXOAuth2Request.h b/Sources/OAuth2Client/NXOAuth2Request.h index aa601af5..09c9799f 100644 --- a/Sources/OAuth2Client/NXOAuth2Request.h +++ b/Sources/OAuth2Client/NXOAuth2Request.h @@ -37,6 +37,14 @@ sendProgressHandler:(NXOAuth2ConnectionSendingProgressHandler)progressHandler responseHandler:(NXOAuth2ConnectionResponseHandler)responseHandler; ++ (void)performMethod:(NSString *)method + onResource:(NSURL *)resource + usingParameters:(NSDictionary *)parameters + usingHeaders:(NSDictionary *)headers + withAccount:(NXOAuth2Account *)account + sendProgressHandler:(NXOAuth2ConnectionSendingProgressHandler)progressHandler + responseHandler:(NXOAuth2ConnectionResponseHandler)responseHandler; + #pragma mark Lifecycle @@ -53,6 +61,7 @@ @property (nonatomic, strong, readwrite) NSURL *resource; @property (nonatomic, strong, readwrite) NSDictionary *parameters; +@property (nonatomic, strong, readwrite) NSDictionary *headerFields; #pragma mark Signed NSURLRequest diff --git a/Sources/OAuth2Client/NXOAuth2Request.m b/Sources/OAuth2Client/NXOAuth2Request.m index ad8564bd..7c3cc1d1 100644 --- a/Sources/OAuth2Client/NXOAuth2Request.m +++ b/Sources/OAuth2Client/NXOAuth2Request.m @@ -27,6 +27,7 @@ @interface NXOAuth2Request () @property (nonatomic, strong, readwrite) NXOAuth2Request *me; #pragma mark Apply Parameters - (void)applyParameters:(NSDictionary *)someParameters onRequest:(NSMutableURLRequest *)aRequest; +- (void)applyHeaders:(NSDictionary *)someHeaders onRequest:(NSMutableURLRequest *)aRequest; @end @@ -40,11 +41,29 @@ + (void)performMethod:(NSString *)aMethod withAccount:(NXOAuth2Account *)anAccount sendProgressHandler:(NXOAuth2ConnectionSendingProgressHandler)progressHandler responseHandler:(NXOAuth2ConnectionResponseHandler)responseHandler; +{ + [self performMethod:aMethod + onResource:aResource + usingParameters:someParameters + usingHeaders:nil + withAccount:anAccount + sendProgressHandler:progressHandler + responseHandler:responseHandler]; +} + ++ (void)performMethod:(NSString *)aMethod + onResource:(NSURL *)aResource + usingParameters:(NSDictionary *)someParameters + usingHeaders:(NSDictionary *)someHeaders + withAccount:(NXOAuth2Account *)anAccount + sendProgressHandler:(NXOAuth2ConnectionSendingProgressHandler)progressHandler + responseHandler:(NXOAuth2ConnectionResponseHandler)responseHandler { NXOAuth2Request *request = [[NXOAuth2Request alloc] initWithResource:aResource method:aMethod parameters:someParameters]; request.account = anAccount; + request.headerFields = someHeaders; [request performRequestWithSendingProgressHandler:progressHandler responseHandler:responseHandler]; } @@ -81,6 +100,7 @@ - (NSURLRequest *)signedURLRequest; [request setHTTPMethod:self.requestMethod]; + [self applyHeaders:self.headerFields onRequest:request]; [self applyParameters:self.parameters onRequest:request]; if (self.account.oauthClient.userAgent && ![request valueForHTTPHeaderField:@"User-Agent"]) { @@ -105,6 +125,7 @@ - (void)performRequestWithSendingProgressHandler:(NXOAuth2ConnectionSendingProgr NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.resource]; [request setHTTPMethod:self.requestMethod]; + [self applyHeaders:self.headerFields onRequest:request]; self.connection = [[NXOAuth2Connection alloc] initWithRequest:request requestParameters:self.parameters oauthClient:self.account.oauthClient @@ -171,4 +192,14 @@ - (void)applyParameters:(NSDictionary *)someParameters onRequest:(NSMutableURLRe } } +- (void)applyHeaders:(NSDictionary *)someHeaders onRequest:(NSMutableURLRequest *)aRequest; +{ + for (id key in someHeaders) { + id value = someHeaders[key]; + NSAssert1([key isKindOfClass:[NSString class]], @"Header name should be a string. name=%@", key); + NSAssert2([value isKindOfClass:[NSString class]], @"Header value should be a string. name=%@, value=%@", key, value); + [aRequest setValue:value forHTTPHeaderField:key]; + } +} + @end