diff --git a/NXOAuth2Account+Private.h b/NXOAuth2Account+Private.h index f4dd6e74..99c4f9bb 100644 --- a/NXOAuth2Account+Private.h +++ b/NXOAuth2Account+Private.h @@ -15,7 +15,12 @@ @interface NXOAuth2Account (Private) -- (id)initAccountWithOAuthClient:(NXOAuth2Client *)oauthClient accountType:(NSString *)accountType; -- (id)initAccountWithAccessToken:(NXOAuth2AccessToken *)accessToken accountType:(NSString *)accountType; +- (instancetype)initAccountWithOAuthClient:(NXOAuth2Client *)oauthClient + accountType:(NSString *)accountType; + +- (instancetype)initAccountWithAccessToken:(NXOAuth2AccessToken *)accessToken + accountType:(NSString *)accountType; + +@property (nonatomic, strong) NXOAuth2AccessToken *accessToken; @end diff --git a/OAuth2Client.xcodeproj/project.pbxproj b/OAuth2Client.xcodeproj/project.pbxproj index 9b1d5d82..7a94c0f2 100644 --- a/OAuth2Client.xcodeproj/project.pbxproj +++ b/OAuth2Client.xcodeproj/project.pbxproj @@ -85,7 +85,6 @@ /* Begin PBXFileReference section */ 824D5A6D123F68A8001177D5 /* NXOAuth2ClientDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NXOAuth2ClientDelegate.h; sourceTree = ""; }; - 83DD991B6049465CB61E135B /* Pods-OAuth2ClientTests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OAuth2ClientTests.xcconfig"; path = "Pods/Pods-OAuth2ClientTests.xcconfig"; sourceTree = ""; }; 9404FAC1123E3A6900397DD1 /* NXOAuth2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NXOAuth2.h; sourceTree = ""; }; 9429B3A812267A3100D31807 /* NXOAuth2Client.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NXOAuth2Client.h; sourceTree = ""; }; 9429B3A912267A3100D31807 /* NXOAuth2Client.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NXOAuth2Client.m; sourceTree = ""; }; @@ -116,9 +115,11 @@ 99D8A7F713852C6E00E3073C /* NSData+NXOAuth2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+NXOAuth2.h"; sourceTree = ""; }; 99D8A7FE13852D3600E3073C /* NSData+NXOAuth2.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+NXOAuth2.m"; sourceTree = ""; }; 99F08DE9138BE8CE002A5401 /* NXOAuth2TrustDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NXOAuth2TrustDelegate.h; sourceTree = ""; }; + A584AA53216FADEE87202521 /* Pods-OAuth2ClientTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OAuth2ClientTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-OAuth2ClientTests/Pods-OAuth2ClientTests.release.xcconfig"; sourceTree = ""; }; AA747D9E0F9514B9006C5449 /* OAuth2Client_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OAuth2Client_Prefix.pch; sourceTree = ""; }; AACBBE490F95108600F1A2B1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; B9E2089E9E7941B7AF3D27AA /* libPods-OAuth2ClientTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OAuth2ClientTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + CCCB476CC96A52415E9D44D3 /* Pods-OAuth2ClientTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OAuth2ClientTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-OAuth2ClientTests/Pods-OAuth2ClientTests.debug.xcconfig"; sourceTree = ""; }; D2AAC07E0554694100DB518D /* libOAuth2Client.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libOAuth2Client.a; sourceTree = BUILT_PRODUCTS_DIR; }; F6525B4313D593C900ACAE8F /* NXOAuth2Account+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NXOAuth2Account+Private.h"; sourceTree = SOURCE_ROOT; }; F65713CC13CC87FD00C8A33A /* NXOAuth2AccountStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NXOAuth2AccountStore.h; sourceTree = ""; }; @@ -182,7 +183,7 @@ 0867D69AFE84028FC02AAC07 /* Frameworks */, 034768DFFF38A50411DB9C8B /* Products */, 942FFCDE12315E2E00E6C65E /* Resources */, - 83DD991B6049465CB61E135B /* Pods-OAuth2ClientTests.xcconfig */, + B061851D30896FF145B69FB5 /* Pods */, ); name = OAuth2Client; sourceTree = ""; @@ -276,6 +277,15 @@ path = Tests; sourceTree = ""; }; + B061851D30896FF145B69FB5 /* Pods */ = { + isa = PBXGroup; + children = ( + CCCB476CC96A52415E9D44D3 /* Pods-OAuth2ClientTests.debug.xcconfig */, + A584AA53216FADEE87202521 /* Pods-OAuth2ClientTests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; F6E1A11313D7128100B6DAC3 /* Private */ = { isa = PBXGroup; children = ( @@ -490,7 +500,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Pods-OAuth2ClientTests-resources.sh\"\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-OAuth2ClientTests/Pods-OAuth2ClientTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -682,7 +692,7 @@ }; 94B6CE6C19C1D0A300AA859B /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 83DD991B6049465CB61E135B /* Pods-OAuth2ClientTests.xcconfig */; + baseConfigurationReference = CCCB476CC96A52415E9D44D3 /* Pods-OAuth2ClientTests.debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; @@ -728,7 +738,7 @@ }; 94B6CE6D19C1D0A300AA859B /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 83DD991B6049465CB61E135B /* Pods-OAuth2ClientTests.xcconfig */; + baseConfigurationReference = A584AA53216FADEE87202521 /* Pods-OAuth2ClientTests.release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; diff --git a/Podfile.lock b/Podfile.lock index 7423955f..ddef80ec 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -2,7 +2,7 @@ PODS: - Expecta (0.3.1) - OCMock (3.1.1) - OHHTTPStubs (3.1.5): - - OHHTTPStubs/Core + - OHHTTPStubs/Core (= 3.1.5) - OHHTTPStubs/Core (3.1.5) - Specta (0.2.1) @@ -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.33.1 +COCOAPODS: 0.36.3 diff --git a/README.md b/README.md old mode 100755 new mode 100644 index 77afd698..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 @@ -80,6 +80,19 @@ The best place to configure your client is `+[UIApplicationDelegate initialize]` Take a look at the [Wiki](https://github.com/nxtbgthng/OAuth2Client/wiki) for some examples. +Unless otherwise specficied, the token request will be called with a HTTP Header 'Content-Type' set to 'multipart/form-data'. If you wish that header to be set to 'application/x-www-form-urlencoded', the custom header fields must be modified. + +
+NSMutableDictionary *configuration = [NSMutableDictionary dictionaryWithDictionary:[[NXOAuth2AccountStore sharedStore] configurationForAccountType:kOAuth2AccountType]];
+NSDictionary *customHeaderFields = [NSDictionary dictionaryWithObject:@"application/x-www-form-urlencoded" forKey:@"Content-Type"];
+[configuration setObject:customHeaderFields forKey:kNXOAuth2AccountStoreConfigurationCustomHeaderFields];
+[[NXOAuth2AccountStore sharedStore] setConfiguration:configuration forAccountType:kOAuth2AccountType];
+
+ +Consult the documentation of the OAuth2 provider to determine acceptable Content-Type header values. + + + ### Requesting Access to a Service Once you have configured your client you are ready to request access to one of those services. The NXOAuth2AccountStore provides three different methods for this: @@ -96,7 +109,12 @@ Once you have configured your client you are ready to request access to one of t [[NXOAuth2AccountStore sharedStore] requestAccessToAccountWithType:@"myFancyService"]; - If you are using an external browser, your application needs to handle the URL you have registered as an redirect URL for the account type. The service will redirect to that URL after the authentication process. + If you are using an external browser, your application needs to handle the URL you have registered as an redirect URL for the account type (e.g. a custom URL scheme like `myfancyscheme://oauth`). The service will redirect to that URL after the authentication process. + + In `-[AppDelegate application: openURL: sourceApplication: annotation:]` pass the redirect URL to +
+     [[NXOAuth2AccountStore sharedStore] handleRedirectURL: url];
+ 
- Provide an Authorization URL Handler
@@ -106,6 +124,24 @@ Once you have configured your client you are ready to request access to one of t
  	                            }];
  
Using an authorization URL handler gives you the ability to open the URL in an own web view or do some fancy stuff for authentication. Therefore you pass a block to the NXOAuth2AccountStore while requesting access. + + One method for receiving a code and exchanging it for an auth token requires the following: + + 1) Load the preparedURL into an existing UIWebView as part of the block code above. Be certain to set the delegate for the UIWebView. + +
+ [_webView loadRequest:[NSURLRequest requestWithURL:preparedURL]];
+ 
+ + 2) In the `webViewDidFinishLoad:` delegate method, you will need to parse the URL for your callback URL. If there is a match, pass that URL to `-[NXOAuth2AccountStore handleRedirectURL:]` + +
+ if ([webView.request.URL.absoluteString rangeOfString:kOAuth2RedirectURL options:NSCaseInsensitiveSearch].location != NSNotFound) {
+        [[NXOAuth2AccountStore sharedStore] handleRedirectURL:[NSURL URLWithString:webView.request.URL.absoluteString]];        
+    }
+
+ +This is a very basic example. In the above, it is assumed that the `code` being returned from the OAuth2 provider is in the query parameter of the webView.request.URL (i.e. `http://myredirecturl.com?code=`). This is not always the case and you may have to look elsewhere for the code (e.g. in the page content, web page title). You must prepare a URL in the format described above to pass to `-[NXOAuth2AccountStore handleRedirectURL:]`. #### On Success @@ -159,6 +195,29 @@ account.userData = userData; This payload will be stored together with the accounts in the Keychain. Thus it shouldn't be too big. +### Removing Accounts + +To remove an account and its tokens from the store: + +
+[[NXOAuth2AccountStore sharedStore]  removeAccount:account];
+
+ +Note that if you used a UIWebView to request access to a service as described above, it's likely that the token has been cached in `[NSHTTPCookieStorage sharedHTTPCookieStorage]` + +You can remove the auth token from the cookie cache using: + +
+for(NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
+    if([[cookie domain] isEqualToString:@"myapp.com"]) {
+        [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
+    }
+}
+[[NSUserDefaults standardUserDefaults] synchronize];
+
+ +Where `myapp.com` is the domain from which you received the auth token -- likley the `authorizationURL` assigned to the store at the time of request. As a convenience, you may want to store the domain of the token in the `account.userData` so that it is readily available if you need to delete the cookies. + ### Invoking a Request A request using the authentication for a service can be invoked via `NXOAuth2Request`. The most convenient method (see below) is a class method which you pass the method, a resource and some parameters (or nil) for the request and to handlers (both optional). One for a progress and the other for the response. The account is used for authentication and can be nil. Then a normal request without authentication will be invoked. diff --git a/Sources/NSURL+NXOAuth2.h b/Sources/NSURL+NXOAuth2.h index 62b8dab7..ff22c301 100644 --- a/Sources/NSURL+NXOAuth2.h +++ b/Sources/NSURL+NXOAuth2.h @@ -26,4 +26,6 @@ - (NSURL *)nxoauth2_URLWithoutQueryString; - (NSString *)nxoauth2_URLStringWithoutQueryString; +- (NSError*) nxoauth2_redirectURLError; + @end diff --git a/Sources/NSURL+NXOAuth2.m b/Sources/NSURL+NXOAuth2.m index 27e14af8..3585b257 100644 --- a/Sources/NSURL+NXOAuth2.m +++ b/Sources/NSURL+NXOAuth2.m @@ -14,7 +14,7 @@ #import "NSString+NXOAuth2.h" #import "NSURL+NXOAuth2.h" - +#import "NXOAuth2Constants.h" @implementation NSURL (NXOAuth2) @@ -62,4 +62,51 @@ - (NSString *)nxoauth2_URLStringWithoutQueryString; return [parts objectAtIndex:0]; } +- (NSError*) nxoauth2_redirectURLError +{ + NSURL* redirectURL = self; + NSInteger errorCode = 0; + NSDictionary *userInfo = nil; + NSString *errorString = [redirectURL nxoauth2_valueForQueryParameterKey:@"error"]; + if (errorString) { + NSString *localizedError = nil; + + if ([errorString caseInsensitiveCompare:@"invalid_request"] == NSOrderedSame) { + errorCode = NXOAuth2InvalidRequestErrorCode; + localizedError = NSLocalizedString(@"Invalid request to OAuth2 Server", @"NXOAuth2InvalidRequestErrorCode description"); + + } else if ([errorString caseInsensitiveCompare:@"invalid_client"] == NSOrderedSame) { + errorCode = NXOAuth2InvalidClientErrorCode; + localizedError = NSLocalizedString(@"Invalid OAuth2 Client", @"NXOAuth2InvalidClientErrorCode description"); + + } else if ([errorString caseInsensitiveCompare:@"unauthorized_client"] == NSOrderedSame) { + errorCode = NXOAuth2UnauthorizedClientErrorCode; + localizedError = NSLocalizedString(@"Unauthorized Client", @"NXOAuth2UnauthorizedClientErrorCode description"); + + } else if ([errorString caseInsensitiveCompare:@"redirect_uri_mismatch"] == NSOrderedSame) { + errorCode = NXOAuth2RedirectURIMismatchErrorCode; + localizedError = NSLocalizedString(@"Redirect URI mismatch", @"NXOAuth2RedirectURIMismatchErrorCode description"); + + } else if ([errorString caseInsensitiveCompare:@"access_denied"] == NSOrderedSame) { + errorCode = NXOAuth2AccessDeniedErrorCode; + localizedError = NSLocalizedString(@"Access denied", @"NXOAuth2AccessDeniedErrorCode description"); + + } else if ([errorString caseInsensitiveCompare:@"unsupported_response_type"] == NSOrderedSame) { + errorCode = NXOAuth2UnsupportedResponseTypeErrorCode; + localizedError = NSLocalizedString(@"Unsupported response type", @"NXOAuth2UnsupportedResponseTypeErrorCode description"); + + } else if ([errorString caseInsensitiveCompare:@"invalid_scope"] == NSOrderedSame) { + errorCode = NXOAuth2InvalidScopeErrorCode; + localizedError = NSLocalizedString(@"Invalid scope", @"NXOAuth2InvalidScopeErrorCode description"); + } + + if (localizedError) { + userInfo = [NSDictionary dictionaryWithObject:localizedError forKey:NSLocalizedDescriptionKey]; + } + } + return [NSError errorWithDomain:NXOAuth2ErrorDomain + code:errorCode + userInfo:userInfo]; +} + @end diff --git a/Sources/OAuth2Client/NXOAuth2AccessToken.h b/Sources/OAuth2Client/NXOAuth2AccessToken.h index 72e4c6c8..0953fcfb 100644 --- a/Sources/OAuth2Client/NXOAuth2AccessToken.h +++ b/Sources/OAuth2Client/NXOAuth2AccessToken.h @@ -33,23 +33,47 @@ @property (nonatomic, readonly) NSSet *scope; @property (nonatomic, readonly) NSString *responseBody; -+ (id)tokenWithResponseBody:(NSString *)responseBody; -+ (id)tokenWithResponseBody:(NSString *)responseBody tokenType:(NSString *)tokenType; ++ (instancetype)tokenWithResponseBody:(NSString *)responseBody; -- (id)initWithAccessToken:(NSString *)accessToken; -- (id)initWithAccessToken:(NSString *)accessToken refreshToken:(NSString *)refreshToken expiresAt:(NSDate *)expiryDate; -- (id)initWithAccessToken:(NSString *)accessToken refreshToken:(NSString *)refreshToken expiresAt:(NSDate *)expiryDate scope:(NSSet *)scope; -- (id)initWithAccessToken:(NSString *)accessToken refreshToken:(NSString *)refreshToken expiresAt:(NSDate *)expiryDate scope:(NSSet *)scope responseBody:(NSString *)responseBody; -- (id)initWithAccessToken:(NSString *)accessToken refreshToken:(NSString *)refreshToken expiresAt:(NSDate *)expiryDate scope:(NSSet *)scope responseBody:(NSString *)responseBody tokenType:(NSString*)tokenType; // designated ++ (instancetype)tokenWithResponseBody:(NSString *)responseBody + tokenType:(NSString *)tokenType; + +- (instancetype)initWithAccessToken:(NSString *)accessToken; + +- (instancetype)initWithAccessToken:(NSString *)accessToken + refreshToken:(NSString *)refreshToken + expiresAt:(NSDate *)expiryDate; + +- (instancetype)initWithAccessToken:(NSString *)accessToken + refreshToken:(NSString *)refreshToken + expiresAt:(NSDate *)expiryDate + scope:(NSSet *)scope; + +- (instancetype)initWithAccessToken:(NSString *)accessToken + refreshToken:(NSString *)refreshToken + expiresAt:(NSDate *)expiryDate + scope:(NSSet *)scope + responseBody:(NSString *)responseBody; + +- (instancetype)initWithAccessToken:(NSString *)accessToken + refreshToken:(NSString *)refreshToken + expiresAt:(NSDate *)expiryDate + scope:(NSSet *)scope + responseBody:(NSString *)responseBody + tokenType:(NSString*)tokenType; // designated - (void)restoreWithOldToken:(NXOAuth2AccessToken *)oldToken; #pragma mark Keychain Support //TODO: Support alternate KeyChain Locations ++ (instancetype)tokenFromDefaultKeychainWithServiceProviderName:(NSString *)provider + withAccessGroup:(NSString *)accessGroup; + +- (void)storeInDefaultKeychainWithServiceProviderName:(NSString *)provider + withAccessGroup:(NSString *)accessGroup; -+ (id)tokenFromDefaultKeychainWithServiceProviderName:(NSString *)provider; -- (void)storeInDefaultKeychainWithServiceProviderName:(NSString *)provider; -- (void)removeFromDefaultKeychainWithServiceProviderName:(NSString *)provider; +- (void)removeFromDefaultKeychainWithServiceProviderName:(NSString *)provider + withAccessGroup:(NSString *)accessGroup; @end diff --git a/Sources/OAuth2Client/NXOAuth2AccessToken.m b/Sources/OAuth2Client/NXOAuth2AccessToken.m index bedef7b8..4ff590cd 100644 --- a/Sources/OAuth2Client/NXOAuth2AccessToken.m +++ b/Sources/OAuth2Client/NXOAuth2AccessToken.m @@ -20,12 +20,12 @@ @implementation NXOAuth2AccessToken #pragma mark Lifecycle -+ (id)tokenWithResponseBody:(NSString *)theResponseBody; ++ (instancetype)tokenWithResponseBody:(NSString *)theResponseBody; { return [self tokenWithResponseBody:theResponseBody tokenType:nil]; } -+ (id)tokenWithResponseBody:(NSString *)theResponseBody tokenType:(NSString *)tokenType; ++ (instancetype)tokenWithResponseBody:(NSString *)theResponseBody tokenType:(NSString *)tokenType; { NSDictionary *jsonDict = nil; Class jsonSerializationClass = NSClassFromString(@"NSJSONSerialization"); @@ -54,7 +54,7 @@ + (id)tokenWithResponseBody:(NSString *)theResponseBody tokenType:(NSString *)to 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 @@ + (id)tokenWithResponseBody:(NSString *)theResponseBody tokenType:(NSString *)to } 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; @@ -78,12 +83,12 @@ + (id)tokenWithResponseBody:(NSString *)theResponseBody tokenType:(NSString *)to tokenType:tokenType]; } -- (id)initWithAccessToken:(NSString *)anAccessToken; +- (instancetype)initWithAccessToken:(NSString *)anAccessToken; { return [self initWithAccessToken:anAccessToken refreshToken:nil expiresAt:nil]; } -- (id)initWithAccessToken:(NSString *)anAccessToken refreshToken:(NSString *)aRefreshToken expiresAt:(NSDate *)anExpiryDate; +- (instancetype)initWithAccessToken:(NSString *)anAccessToken refreshToken:(NSString *)aRefreshToken expiresAt:(NSDate *)anExpiryDate; { return [[[self class] alloc] initWithAccessToken:anAccessToken refreshToken:aRefreshToken @@ -91,7 +96,7 @@ - (id)initWithAccessToken:(NSString *)anAccessToken refreshToken:(NSString *)aRe scope:nil]; } -- (id)initWithAccessToken:(NSString *)anAccessToken refreshToken:(NSString *)aRefreshToken expiresAt:(NSDate *)anExpiryDate scope:(NSSet *)aScope; +- (instancetype)initWithAccessToken:(NSString *)anAccessToken refreshToken:(NSString *)aRefreshToken expiresAt:(NSDate *)anExpiryDate scope:(NSSet *)aScope; { return [[[self class] alloc] initWithAccessToken:anAccessToken refreshToken:aRefreshToken @@ -100,7 +105,7 @@ - (id)initWithAccessToken:(NSString *)anAccessToken refreshToken:(NSString *)aRe responseBody:nil]; } -- (id)initWithAccessToken:(NSString *)anAccessToken refreshToken:(NSString *)aRefreshToken expiresAt:(NSDate *)anExpiryDate scope:(NSSet *)aScope responseBody:(NSString *)aResponseBody; +- (instancetype)initWithAccessToken:(NSString *)anAccessToken refreshToken:(NSString *)aRefreshToken expiresAt:(NSDate *)anExpiryDate scope:(NSSet *)aScope responseBody:(NSString *)aResponseBody; { return [[[self class] alloc] initWithAccessToken:anAccessToken refreshToken:aRefreshToken @@ -110,7 +115,7 @@ - (id)initWithAccessToken:(NSString *)anAccessToken refreshToken:(NSString *)aRe tokenType:nil]; } -- (id)initWithAccessToken:(NSString *)anAccessToken refreshToken:(NSString *)aRefreshToken expiresAt:(NSDate *)anExpiryDate scope:(NSSet *)aScope responseBody:(NSString *)aResponseBody tokenType:(NSString *)aTokenType +- (instancetype)initWithAccessToken:(NSString *)anAccessToken refreshToken:(NSString *)aRefreshToken expiresAt:(NSDate *)anExpiryDate scope:(NSSet *)aScope responseBody:(NSString *)aResponseBody tokenType:(NSString *)aTokenType { // a token object without an actual token is not what we want! NSAssert1(anAccessToken, @"No token from token response: %@", aResponseBody); @@ -191,7 +196,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder } } -- (id)initWithCoder:(NSCoder *)aDecoder +- (instancetype)initWithCoder:(NSCoder *)aDecoder { NSString *decodedAccessToken = [aDecoder decodeObjectForKey:@"accessToken"]; @@ -224,15 +229,22 @@ + (NSString *)serviceNameWithProvider:(NSString *)provider; #if TARGET_OS_IPHONE -+ (id)tokenFromDefaultKeychainWithServiceProviderName:(NSString *)provider; ++ (instancetype)tokenFromDefaultKeychainWithServiceProviderName:(NSString *)provider withAccessGroup:(NSString *)accessGroup; { NSString *serviceName = [[self class] serviceNameWithProvider:provider]; NSDictionary *result = nil; - NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: - (__bridge NSString *)kSecClassGenericPassword, kSecClass, - serviceName, kSecAttrService, - kCFBooleanTrue, kSecReturnAttributes, - nil]; + NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys: + (__bridge NSString *)kSecClassGenericPassword, kSecClass, + serviceName, kSecAttrService, + kCFBooleanTrue, kSecReturnAttributes, + nil]; + +#ifndef TARGET_IPHONE_SIMULATOR + if (accessGroup != nil) { + [query setObject:accessGroup forKey:(__bridge NSString *)kSecAttrAccessGroup]; + } +#endif + CFTypeRef cfResult = nil; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &cfResult); result = (__bridge_transfer NSDictionary *)cfResult; @@ -245,35 +257,49 @@ + (id)tokenFromDefaultKeychainWithServiceProviderName:(NSString *)provider; return [NSKeyedUnarchiver unarchiveObjectWithData:[result objectForKey:(__bridge NSString *)kSecAttrGeneric]]; } -- (void)storeInDefaultKeychainWithServiceProviderName:(NSString *)provider; +- (void)storeInDefaultKeychainWithServiceProviderName:(NSString *)provider withAccessGroup:(NSString *)accessGroup; { NSString *serviceName = [[self class] serviceNameWithProvider:provider]; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self]; - NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: - (__bridge NSString *)kSecClassGenericPassword, kSecClass, - serviceName, kSecAttrService, - @"OAuth 2 Access Token", kSecAttrLabel, - data, kSecAttrGeneric, - nil]; - [self removeFromDefaultKeychainWithServiceProviderName:provider]; + NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys: + (__bridge NSString *)kSecClassGenericPassword, kSecClass, + serviceName, kSecAttrService, + @"OAuth 2 Access Token", kSecAttrLabel, + data, kSecAttrGeneric, + nil]; + +#ifndef TARGET_IPHONE_SIMULATOR + if (accessGroup != nil) { + [query setObject:accessGroup forKey:(__bridge NSString *)kSecAttrAccessGroup]; + } +#endif + + [self removeFromDefaultKeychainWithServiceProviderName:provider withAccessGroup:accessGroup]; OSStatus __attribute__((unused)) err = SecItemAdd((__bridge CFDictionaryRef)query, NULL); NSAssert1(err == noErr, @"error while adding token to keychain: %d", (int)err); } -- (void)removeFromDefaultKeychainWithServiceProviderName:(NSString *)provider; +- (void)removeFromDefaultKeychainWithServiceProviderName:(NSString *)provider withAccessGroup:(NSString *)accessGroup; { NSString *serviceName = [[self class] serviceNameWithProvider:provider]; - NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: + NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys: (__bridge NSString *)kSecClassGenericPassword, kSecClass, serviceName, kSecAttrService, nil]; + +#ifndef TARGET_IPHONE_SIMULATOR + if (accessGroup != nil) { + [query setObject:accessGroup forKey:(__bridge NSString *)kSecAttrAccessGroup]; + } +#endif + OSStatus __attribute__((unused)) err = SecItemDelete((__bridge CFDictionaryRef)query); NSAssert1((err == noErr || err == errSecItemNotFound), @"error while deleting token from keychain: %d", (int)err); } #else -+ (id)tokenFromDefaultKeychainWithServiceProviderName:(NSString *)provider; ++ (instancetype)tokenFromDefaultKeychainWithServiceProviderName:(NSString *)provider withAccessGroup:(NSString *)accessGroup; { NSString *serviceName = [[self class] serviceNameWithProvider:provider]; @@ -321,9 +347,9 @@ + (id)tokenFromDefaultKeychainWithServiceProviderName:(NSString *)provider; return [NSKeyedUnarchiver unarchiveObjectWithData:result]; } -- (void)storeInDefaultKeychainWithServiceProviderName:(NSString *)provider; +- (void)storeInDefaultKeychainWithServiceProviderName:(NSString *)provider withAccessGroup:(NSString *)accessGroup; { - [self removeFromDefaultKeychainWithServiceProviderName:provider]; + [self removeFromDefaultKeychainWithServiceProviderName:provider withAccessGroup:accessGroup]; NSString *serviceName = [[self class] serviceNameWithProvider:provider]; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self]; @@ -339,7 +365,7 @@ OSStatus __attribute__((unused))err = SecKeychainAddGenericPassword(NULL, NSAssert1(err == noErr, @"error while adding token to keychain: %d", err); } -- (void)removeFromDefaultKeychainWithServiceProviderName:(NSString *)provider; +- (void)removeFromDefaultKeychainWithServiceProviderName:(NSString *)provider withAccessGroup:(NSString *)accessGroup; { NSString *serviceName = [[self class] serviceNameWithProvider:provider]; SecKeychainItemRef item = nil; diff --git a/Sources/OAuth2Client/NXOAuth2Account.m b/Sources/OAuth2Client/NXOAuth2Account.m index 2355a477..be2ff895 100644 --- a/Sources/OAuth2Client/NXOAuth2Account.m +++ b/Sources/OAuth2Client/NXOAuth2Account.m @@ -33,6 +33,8 @@ #pragma mark - @interface NXOAuth2Account () + +@property (nonatomic, strong) NXOAuth2AccessToken *accessToken; @end #pragma mark - @@ -41,17 +43,18 @@ @implementation NXOAuth2Account (Private) #pragma mark Lifecycle -- (id)initAccountWithOAuthClient:(NXOAuth2Client *)anOAuthClient accountType:(NSString *)anAccountType; +- (instancetype)initAccountWithOAuthClient:(NXOAuth2Client *)anOAuthClient accountType:(NSString *)anAccountType; { self = [self initAccountWithAccessToken:anOAuthClient.accessToken accountType:anAccountType]; if (self) { oauthClient = anOAuthClient; + oauthClient.delegate = self; } return self; } -- (id)initAccountWithAccessToken:(NXOAuth2AccessToken *)anAccessToken accountType:(NSString *)anAccountType; +- (instancetype)initAccountWithAccessToken:(NXOAuth2AccessToken *)anAccessToken accountType:(NSString *)anAccountType; { self = [super init]; if (self) { @@ -84,13 +87,14 @@ - (NXOAuth2Client *)oauthClient; @synchronized (oauthClient) { if (oauthClient == nil) { NSDictionary *configuration = [[NXOAuth2AccountStore sharedStore] configurationForAccountType:self.accountType]; - + NSString *clientID = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationClientID]; NSString *clientSecret = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationSecret]; NSURL *authorizeURL = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationAuthorizeURL]; NSURL *tokenURL = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationTokenURL]; NSString *tokenType = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationTokenType]; - NSString *keychainGroup = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationTokenType]; + NSString *keychainGroup = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationKeyChainGroup]; + NSString *keychainAccessGroup = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationKeyChainAccessGroup]; NSDictionary *additionalQueryParams = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationAdditionalAuthenticationParameters]; NSDictionary *customHeaderFields = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationCustomHeaderFields]; @@ -101,16 +105,17 @@ - (NXOAuth2Client *)oauthClient; accessToken:self.accessToken tokenType:tokenType keyChainGroup:keychainGroup + keyChainAccessGroup:keychainAccessGroup persistent:NO delegate:self]; if (additionalQueryParams) { oauthClient.additionalAuthenticationParameters = additionalQueryParams; } - + if (customHeaderFields) { oauthClient.customHeaderFields = customHeaderFields; } - + } } return oauthClient; @@ -202,7 +207,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder [aCoder encodeObject:userData forKey:@"userData"]; } -- (id)initWithCoder:(NSCoder *)aDecoder +- (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { userData = [aDecoder decodeObjectForKey:@"userData"]; diff --git a/Sources/OAuth2Client/NXOAuth2AccountStore.h b/Sources/OAuth2Client/NXOAuth2AccountStore.h index 05945884..3180cfc0 100644 --- a/Sources/OAuth2Client/NXOAuth2AccountStore.h +++ b/Sources/OAuth2Client/NXOAuth2AccountStore.h @@ -35,6 +35,8 @@ extern NSString * const kNXOAuth2AccountStoreConfigurationRedirectURL; extern NSString * const kNXOAuth2AccountStoreConfigurationScope; extern NSString * const kNXOAuth2AccountStoreConfigurationTokenType; extern NSString * const kNXOAuth2AccountStoreConfigurationTokenRequestHTTPMethod; +extern NSString * const kNXOAuth2AccountStoreConfigurationKeyChainGroup; +extern NSString * const kNXOAuth2AccountStoreConfigurationKeyChainAccessGroup; /* * Requires a NSDictionary as a value. @@ -75,10 +77,12 @@ typedef void(^NXOAuth2PreparedAuthorizationURLHandler)(NSURL *preparedURL); NSMutableDictionary *trustedCertificatesHandler; } -+ (id)sharedStore; ++ (instancetype)sharedStore; #pragma mark Accessors +@property(nonatomic, strong) NSString *keychainAccessGroup; +@property(nonatomic, strong) NSString *keychainServiceName; @property(nonatomic, strong, readonly) NSArray *accounts; - (NSArray *)accountsWithAccountType:(NSString *)accountType; - (NXOAuth2Account *)accountWithIdentifier:(NSString *)identifier; @@ -117,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; @@ -140,5 +144,6 @@ typedef void(^NXOAuth2PreparedAuthorizationURLHandler)(NSURL *preparedURL); #pragma mark Handle OAuth Redirects - (BOOL)handleRedirectURL:(NSURL *)URL; +- (BOOL)handleRedirectURL:(NSURL *)aURL error: (NSError**) error; @end diff --git a/Sources/OAuth2Client/NXOAuth2AccountStore.m b/Sources/OAuth2Client/NXOAuth2AccountStore.m index 45815ec4..dde5b927 100644 --- a/Sources/OAuth2Client/NXOAuth2AccountStore.m +++ b/Sources/OAuth2Client/NXOAuth2AccountStore.m @@ -44,6 +44,7 @@ NSString * const kNXOAuth2AccountStoreConfigurationTokenType = @"kNXOAuth2AccountStoreConfigurationTokenType"; NSString * const kNXOAuth2AccountStoreConfigurationTokenRequestHTTPMethod = @"kNXOAuth2AccountStoreConfigurationTokenRequestHTTPMethod"; NSString * const kNXOAuth2AccountStoreConfigurationKeyChainGroup = @"kNXOAuth2AccountStoreConfigurationKeyChainGroup"; +NSString * const kNXOAuth2AccountStoreConfigurationKeyChainAccessGroup = @"kNXOAuth2AccountStoreConfigurationKeyChainAccessGroup"; NSString * const kNXOAuth2AccountStoreConfigurationAdditionalAuthenticationParameters = @"kNXOAuth2AccountStoreConfigurationAdditionalAuthenticationParameters"; NSString * const kNXOAuth2AccountStoreConfigurationCustomHeaderFields = @"kNXOAuth2AccountStoreConfigurationCustomHeaderFields"; @@ -75,10 +76,10 @@ - (void)accountDidLoseAccessToken:(NSNotification *)aNotification; #pragma mark Keychain Support -+ (NSString *)keychainServiceName; -+ (NSDictionary *)accountsFromDefaultKeychain; -+ (void)storeAccountsInDefaultKeychain:(NSDictionary *)accounts; -+ (void)removeFromDefaultKeychain; +- (NSString *)accountsKeychainServiceName; +- (NSDictionary *)accountsFromDefaultKeychainWithAccessGroup:(NSString *)keyChainAccessGroup; +- (void)storeAccountsInDefaultKeychain:(NSDictionary *)accounts withAccessGroup:(NSString*)keyChainAccessGroup; +- (void)removeFromDefaultKeychainWithAccessGroup:(NSString*)keyChainAccessGroup; @end @@ -87,7 +88,7 @@ @implementation NXOAuth2AccountStore #pragma mark Lifecycle -+ (id)sharedStore; ++ (instancetype)sharedStore; { static NXOAuth2AccountStore *shared; static dispatch_once_t onceToken; @@ -97,26 +98,25 @@ + (id)sharedStore; return shared; } -- (id)init; +- (instancetype)init; { self = [super init]; if (self) { self.pendingOAuthClients = [NSMutableDictionary dictionary]; - self.accountsDict = [NSMutableDictionary dictionaryWithDictionary:[NXOAuth2AccountStore accountsFromDefaultKeychain]]; self.configurations = [NSMutableDictionary dictionary]; self.trustModeHandler = [NSMutableDictionary dictionary]; self.trustedCertificatesHandler = [NSMutableDictionary dictionary]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(accountDidChangeUserData:) name:NXOAuth2AccountDidChangeUserDataNotification object:nil]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(accountDidChangeAccessToken:) name:NXOAuth2AccountDidChangeAccessTokenNotification object:nil]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(accountDidLoseAccessToken:) name:NXOAuth2AccountDidLoseAccessTokenNotification @@ -140,6 +140,16 @@ - (void)dealloc; @synthesize trustModeHandler; @synthesize trustedCertificatesHandler; +- (NSMutableDictionary *)accountsDict; +{ + if (accountsDict == nil) { + accountsDict = [NSMutableDictionary dictionaryWithDictionary: + [self accountsFromDefaultKeychainWithAccessGroup:self.keychainAccessGroup]]; + } + + return accountsDict; +} + - (NSArray *)accounts; { NSArray *result = nil; @@ -219,7 +229,7 @@ - (void)removeAccount:(NXOAuth2Account *)account; if (account) { @synchronized (self.accountsDict) { [self.accountsDict removeObjectForKey:account.identifier]; - [NXOAuth2AccountStore storeAccountsInDefaultKeychain:self.accountsDict]; + [self storeAccountsInDefaultKeychain:self.accountsDict withAccessGroup:self.keychainAccessGroup]; } [[NSNotificationCenter defaultCenter] postNotificationName:NXOAuth2AccountStoreAccountsDidChangeNotification object:self]; } @@ -234,13 +244,19 @@ - (void)setClientID:(NSString *)aClientID redirectURL:(NSURL *)aRedirectURL forAccountType:(NSString *)anAccountType; { - [self setConfiguration:[NSDictionary dictionaryWithObjectsAndKeys: - aClientID, kNXOAuth2AccountStoreConfigurationClientID, - aSecret, kNXOAuth2AccountStoreConfigurationSecret, - anAuthorizationURL, kNXOAuth2AccountStoreConfigurationAuthorizeURL, - aTokenURL, kNXOAuth2AccountStoreConfigurationTokenURL, - aRedirectURL, kNXOAuth2AccountStoreConfigurationRedirectURL, nil] - forAccountType:anAccountType]; + NSMutableDictionary* config = [NSMutableDictionary dictionaryWithObjectsAndKeys: + aClientID, kNXOAuth2AccountStoreConfigurationClientID, + aSecret, kNXOAuth2AccountStoreConfigurationSecret, + anAuthorizationURL, kNXOAuth2AccountStoreConfigurationAuthorizeURL, + aTokenURL, kNXOAuth2AccountStoreConfigurationTokenURL, + aRedirectURL, kNXOAuth2AccountStoreConfigurationRedirectURL, nil]; + + if (self.keychainAccessGroup) { + [config setObject:self.keychainAccessGroup + forKey:kNXOAuth2AccountStoreConfigurationKeyChainAccessGroup]; + } + + [self setConfiguration:config forAccountType:anAccountType]; } - (void)setClientID:(NSString *)aClientID @@ -252,15 +268,23 @@ - (void)setClientID:(NSString *)aClientID keyChainGroup:(NSString *)aKeyChainGroup forAccountType:(NSString *)anAccountType; { - [self setConfiguration:[NSDictionary dictionaryWithObjectsAndKeys: - aClientID, kNXOAuth2AccountStoreConfigurationClientID, - aSecret, kNXOAuth2AccountStoreConfigurationSecret, - theScope, kNXOAuth2AccountStoreConfigurationScope, - anAuthorizationURL, kNXOAuth2AccountStoreConfigurationAuthorizeURL, - aTokenURL, kNXOAuth2AccountStoreConfigurationTokenURL, - aKeyChainGroup, kNXOAuth2AccountStoreConfigurationKeyChainGroup, - aRedirectURL, kNXOAuth2AccountStoreConfigurationRedirectURL, nil] - forAccountType:anAccountType]; + NSAssert(aKeyChainGroup, @"keyChainGroup must be non-nil"); + + NSMutableDictionary* config = [NSMutableDictionary dictionaryWithObjectsAndKeys: + aClientID, kNXOAuth2AccountStoreConfigurationClientID, + aSecret, kNXOAuth2AccountStoreConfigurationSecret, + theScope, kNXOAuth2AccountStoreConfigurationScope, + anAuthorizationURL, kNXOAuth2AccountStoreConfigurationAuthorizeURL, + aTokenURL, kNXOAuth2AccountStoreConfigurationTokenURL, + aKeyChainGroup, kNXOAuth2AccountStoreConfigurationKeyChainGroup, + aRedirectURL, kNXOAuth2AccountStoreConfigurationRedirectURL, nil]; + + if (self.keychainAccessGroup) { + [config setObject:self.keychainAccessGroup + forKey:kNXOAuth2AccountStoreConfigurationKeyChainAccessGroup]; + } + + [self setConfiguration:config forAccountType:anAccountType]; } - (void)setClientID:(NSString *)aClientID @@ -273,16 +297,25 @@ - (void)setClientID:(NSString *)aClientID tokenType:(NSString *)aTokenType forAccountType:(NSString *)anAccountType; { - [self setConfiguration:[NSDictionary dictionaryWithObjectsAndKeys: - aClientID, kNXOAuth2AccountStoreConfigurationClientID, - aSecret, kNXOAuth2AccountStoreConfigurationSecret, - theScope, kNXOAuth2AccountStoreConfigurationScope, - anAuthorizationURL, kNXOAuth2AccountStoreConfigurationAuthorizeURL, - aTokenURL, kNXOAuth2AccountStoreConfigurationTokenURL, - aTokenType, kNXOAuth2AccountStoreConfigurationTokenType, - aKeyChainGroup, kNXOAuth2AccountStoreConfigurationKeyChainGroup, - aRedirectURL, kNXOAuth2AccountStoreConfigurationRedirectURL, nil] - forAccountType:anAccountType]; + NSAssert(aKeyChainGroup, @"keyChainGroup must be non-nil"); + NSAssert(aTokenType, @"tokenType must be non-nil"); + + NSMutableDictionary* config = [NSMutableDictionary dictionaryWithObjectsAndKeys: + aClientID, kNXOAuth2AccountStoreConfigurationClientID, + aSecret, kNXOAuth2AccountStoreConfigurationSecret, + theScope, kNXOAuth2AccountStoreConfigurationScope, + anAuthorizationURL, kNXOAuth2AccountStoreConfigurationAuthorizeURL, + aTokenURL, kNXOAuth2AccountStoreConfigurationTokenURL, + aTokenType, kNXOAuth2AccountStoreConfigurationTokenType, + aKeyChainGroup, kNXOAuth2AccountStoreConfigurationKeyChainGroup, + aRedirectURL, kNXOAuth2AccountStoreConfigurationRedirectURL, nil]; + + if (self.keychainAccessGroup) { + [config setObject:self.keychainAccessGroup + forKey:kNXOAuth2AccountStoreConfigurationKeyChainAccessGroup]; + } + + [self setConfiguration:config forAccountType:anAccountType]; } - (void)setConfiguration:(NSDictionary *)configuration @@ -340,8 +373,12 @@ - (NXOAuth2TrustedCertificatesHandler)trustedCertificatesHandlerForAccountType:( #pragma mark Handle OAuth Redirects +- (BOOL)handleRedirectURL:(NSURL *)aURL +{ + return [self handleRedirectURL:aURL error:nil]; +} -- (BOOL)handleRedirectURL:(NSURL *)aURL; +- (BOOL)handleRedirectURL:(NSURL *)aURL error: (NSError**) error { __block NSURL *fixedRedirectURL = nil; NSSet *accountTypes; @@ -354,15 +391,7 @@ - (BOOL)handleRedirectURL:(NSURL *)aURL; // WORKAROUND: The URL which is passed to this method may be lower case also the scheme is registered in camel case. Therefor replace the prefix with the stored redirectURL. if (fixedRedirectURL == nil) { - if ([aURL.scheme isEqualToString:redirectURL.scheme]) { - fixedRedirectURL = aURL; - } else { - NSRange prefixRange; - prefixRange.location = 0; - prefixRange.length = [redirectURL.absoluteString length]; - fixedRedirectURL = [NSURL URLWithString:[aURL.absoluteString stringByReplacingCharactersInRange:prefixRange - withString:redirectURL.absoluteString]]; - } + fixedRedirectURL = [self fixRedirectURL: aURL storedURL:redirectURL]; } return YES; @@ -374,13 +403,28 @@ - (BOOL)handleRedirectURL:(NSURL *)aURL; for (NSString *accountType in accountTypes) { NXOAuth2Client *client = [self pendingOAuthClientForAccountType:accountType]; - if ([client openRedirectURL:fixedRedirectURL]) { + if ([client openRedirectURL:fixedRedirectURL error:error]) { return YES; } } return NO; } +-(NSURL*) fixRedirectURL: (NSURL*) incomingURL storedURL: (NSURL*) redirectURL +{ + NSURL *fixedRedirectURL; + if ([incomingURL.scheme isEqualToString:redirectURL.scheme]) { + fixedRedirectURL = incomingURL; + } else { + NSRange prefixRange; + prefixRange.location = 0; + prefixRange.length = [redirectURL.absoluteString length]; + fixedRedirectURL = [NSURL URLWithString:[incomingURL.absoluteString + stringByReplacingCharactersInRange:prefixRange + withString:redirectURL.absoluteString]]; + } + return fixedRedirectURL; +} #pragma mark OAuthClient to AccountType Relation @@ -404,6 +448,7 @@ - (NXOAuth2Client *)pendingOAuthClientForAccountType:(NSString *)accountType; NSString *tokenType = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationTokenType]; NSString *tokenRequestHTTPMethod = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationTokenRequestHTTPMethod]; NSString *keychainGroup = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationKeyChainGroup]; + NSString *keychainAccessGroup = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationKeyChainAccessGroup]; NSDictionary *additionalAuthenticationParameters = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationAdditionalAuthenticationParameters]; NSDictionary *customHeaderFields = [configuration objectForKey:kNXOAuth2AccountStoreConfigurationCustomHeaderFields]; @@ -414,6 +459,7 @@ - (NXOAuth2Client *)pendingOAuthClientForAccountType:(NSString *)accountType; accessToken:nil tokenType:tokenType keyChainGroup:keychainGroup + keyChainAccessGroup:keychainAccessGroup persistent:YES delegate:self]; @@ -461,6 +507,7 @@ - (NSString *)accountTypeOfPendingOAuthClient:(NXOAuth2Client *)oauthClient; - (void)oauthClientNeedsAuthentication:(NXOAuth2Client *)client; { +#if !defined(NX_APP_EXTENSIONS) NSString *accountType = [self accountTypeOfPendingOAuthClient:client]; NSDictionary *configuration; @@ -476,14 +523,17 @@ - (void)oauthClientNeedsAuthentication:(NXOAuth2Client *)client; #else [[NSWorkspace sharedWorkspace] openURL:preparedURL]; #endif +#endif } -- (void)oauthClientDidGetAccessToken:(NXOAuth2Client *)client; +- (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]; @@ -491,11 +541,38 @@ - (void)oauthClientDidGetAccessToken:(NXOAuth2Client *)client; [self addAccount:account]; } +- (void)oauthClientDidRefreshAccessToken:(NXOAuth2Client *)client +{ + NXOAuth2Account* foundAccount = nil; + + @synchronized (self.accountsDict) + { + for (NXOAuth2Account* account in self.accounts) + { + if (account.oauthClient == client) + { + foundAccount = account; + break; + } + } + } + + if (foundAccount) { + foundAccount.accessToken = client.accessToken; + NSDictionary *userInfo = [NSDictionary dictionaryWithObject: foundAccount + forKey: NXOAuth2AccountStoreNewAccountUserInfoKey]; + + [[NSNotificationCenter defaultCenter] postNotificationName:NXOAuth2AccountStoreAccountsDidChangeNotification + object:self + userInfo:userInfo]; + } +} + - (void)addAccount:(NXOAuth2Account *)account; { @synchronized (self.accountsDict) { [self.accountsDict setValue:account forKey:account.identifier]; - [NXOAuth2AccountStore storeAccountsInDefaultKeychain:self.accountsDict]; + [self storeAccountsInDefaultKeychain:self.accountsDict withAccessGroup:self.keychainAccessGroup]; } NSDictionary *userInfo = [NSDictionary dictionaryWithObject:account @@ -527,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: @@ -567,7 +646,7 @@ - (void)accountDidChangeUserData:(NSNotification *)aNotification; @synchronized (self.accountsDict) { // The user data of an account has been changed. // Save all accounts in the default keychain. - [NXOAuth2AccountStore storeAccountsInDefaultKeychain:self.accountsDict]; + [self storeAccountsInDefaultKeychain:self.accountsDict withAccessGroup:self.keychainAccessGroup]; } } @@ -576,7 +655,7 @@ - (void)accountDidChangeAccessToken:(NSNotification *)aNotification; @synchronized (self.accountsDict) { // An access token of an account has been changed. // Save all accounts in the default keychain. - [NXOAuth2AccountStore storeAccountsInDefaultKeychain:self.accountsDict]; + [self storeAccountsInDefaultKeychain:self.accountsDict withAccessGroup:self.keychainAccessGroup]; } } @@ -588,70 +667,97 @@ - (void)accountDidLoseAccessToken:(NSNotification *)aNotification; #pragma mark Keychain Support -+ (NSString *)keychainServiceName; +- (NSString *)accountsKeychainServiceName; { - NSString *appName = [[NSBundle mainBundle] bundleIdentifier]; - return [NSString stringWithFormat:@"%@::NXOAuth2AccountStore", appName]; + NSString *serviceName = self.keychainServiceName; + + if (!serviceName) { + NSString *appName = [[NSBundle mainBundle] bundleIdentifier]; + serviceName = [NSString stringWithFormat:@"%@::NXOAuth2AccountStore", appName]; + } + + return serviceName; } #if TARGET_OS_IPHONE -+ (NSDictionary *)accountsFromDefaultKeychain; +- (NSDictionary *)accountsFromDefaultKeychainWithAccessGroup:(NSString *)keyChainAccessGroup; { - NSString *serviceName = [self keychainServiceName]; + NSString *serviceName = [self accountsKeychainServiceName]; NSDictionary *result = nil; - NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: - (__bridge NSString *)kSecClassGenericPassword, kSecClass, - serviceName, kSecAttrService, - kCFBooleanTrue, kSecReturnAttributes, - nil]; + NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys: + (__bridge NSString *)kSecClassGenericPassword, kSecClass, + serviceName, kSecAttrService, + kCFBooleanTrue, kSecReturnAttributes, + nil]; + +#ifndef TARGET_IPHONE_SIMULATOR + if (keyChainAccessGroup) { + [query setObject:keyChainAccessGroup forKey:(__bridge NSString *)kSecAttrAccessGroup]; + } +#endif + CFTypeRef cfResult = nil; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &cfResult); 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; } return [NSKeyedUnarchiver unarchiveObjectWithData:[result objectForKey:(__bridge NSString *)kSecAttrGeneric]]; } -+ (void)storeAccountsInDefaultKeychain:(NSDictionary *)accounts; +- (void)storeAccountsInDefaultKeychain:(NSDictionary *)accounts withAccessGroup:(NSString *)keyChainAccessGroup; { - [self removeFromDefaultKeychain]; + [self removeFromDefaultKeychainWithAccessGroup:keyChainAccessGroup]; - NSString *serviceName = [self keychainServiceName]; + NSString *serviceName = [self accountsKeychainServiceName]; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:accounts]; - NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: - (__bridge NSString *)kSecClassGenericPassword, kSecClass, - serviceName, kSecAttrService, - @"OAuth 2 Account Store", kSecAttrLabel, - data, kSecAttrGeneric, - nil]; + NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys: + (__bridge NSString *)kSecClassGenericPassword, kSecClass, + serviceName, kSecAttrService, + @"OAuth 2 Account Store", kSecAttrLabel, + data, kSecAttrGeneric, + nil]; + +#ifndef TARGET_IPHONE_SIMULATOR + if (keyChainAccessGroup) { + [query setObject:keyChainAccessGroup forKey:(__bridge NSString *)kSecAttrAccessGroup]; + } +#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)removeFromDefaultKeychain; +- (void)removeFromDefaultKeychainWithAccessGroup:(NSString *)keyChainAccessGroup; { - NSString *serviceName = [self keychainServiceName]; - NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: - (__bridge NSString *)kSecClassGenericPassword, kSecClass, - serviceName, kSecAttrService, - nil]; + NSString *serviceName = [self accountsKeychainServiceName]; + NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys: + (__bridge NSString *)kSecClassGenericPassword, kSecClass, + serviceName, kSecAttrService, + nil]; + +#ifndef TARGET_IPHONE_SIMULATOR + if (keyChainAccessGroup) { + [query setObject:keyChainAccessGroup forKey:(__bridge NSString *)kSecAttrAccessGroup]; + } +#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); } #else -+ (NSDictionary *)accountsFromDefaultKeychain; +- (NSDictionary *)accountsFromDefaultKeychainWithAccessGroup:(NSString *)keyChainAccessGroup; { - NSString *serviceName = [self keychainServiceName]; + NSString *serviceName = [self accountsKeychainServiceName]; SecKeychainItemRef item = nil; OSStatus err = SecKeychainFindGenericPassword(NULL, @@ -697,11 +803,11 @@ + (NSDictionary *)accountsFromDefaultKeychain; return [NSKeyedUnarchiver unarchiveObjectWithData:result]; } -+ (void)storeAccountsInDefaultKeychain:(NSDictionary *)accounts; +- (void)storeAccountsInDefaultKeychain:(NSDictionary *)accounts withAccessGroup:(NSString *)keyChainAccessGroup; { - [self removeFromDefaultKeychain]; + [self removeFromDefaultKeychainWithAccessGroup:keyChainAccessGroup]; - NSString *serviceName = [self keychainServiceName]; + NSString *serviceName = [self accountsKeychainServiceName]; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:accounts]; @@ -717,9 +823,9 @@ OSStatus __attribute__((unused))err = SecKeychainAddGenericPassword(NULL, NSAssert1(err == noErr, @"Error while storing accounts in keychain: %d", err); } -+ (void)removeFromDefaultKeychain; +- (void)removeFromDefaultKeychainWithAccessGroup:(NSString *)keyChainAccessGroup; { - NSString *serviceName = [self keychainServiceName]; + NSString *serviceName = [self accountsKeychainServiceName]; SecKeychainItemRef item = nil; OSStatus err = SecKeychainFindGenericPassword(NULL, diff --git a/Sources/OAuth2Client/NXOAuth2Client.h b/Sources/OAuth2Client/NXOAuth2Client.h index f5295c28..6ee73d29 100644 --- a/Sources/OAuth2Client/NXOAuth2Client.h +++ b/Sources/OAuth2Client/NXOAuth2Client.h @@ -43,6 +43,7 @@ extern NSString * const NXOAuth2ClientConnectionContextTokenRefresh; NSString *userAgent; NSString *assertion; NSString *keyChainGroup; + NSString *keyChainAccessGroup; // server information NSURL *authorizeURL; @@ -56,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; @@ -73,8 +78,14 @@ 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; /*! * If set to NO, the access token is not stored any keychain, will be removed if it was. @@ -85,32 +96,35 @@ extern NSString * const NXOAuth2ClientConnectionContextTokenRefresh; /*! * Initializes the Client */ -- (id)initWithClientID:(NSString *)clientId - clientSecret:(NSString *)clientSecret - authorizeURL:(NSURL *)authorizeURL - tokenURL:(NSURL *)tokenURL - delegate:(NSObject *)delegate; - -- (id)initWithClientID:(NSString *)clientId - clientSecret:(NSString *)clientSecret - authorizeURL:(NSURL *)authorizeURL - tokenURL:(NSURL *)tokenURL - accessToken:(NXOAuth2AccessToken *)accessToken - keyChainGroup:(NSString *)keyChainGroup - persistent:(BOOL)shouldPersist - delegate:(NSObject *)delegate; - -- (id)initWithClientID:(NSString *)clientId - clientSecret:(NSString *)clientSecret - authorizeURL:(NSURL *)authorizeURL - tokenURL:(NSURL *)tokenURL - accessToken:(NXOAuth2AccessToken *)accessToken - tokenType:(NSString *)tokenType - keyChainGroup:(NSString *)keyChainGroup - persistent:(BOOL)shouldPersist - delegate:(NSObject *)delegate; +- (instancetype)initWithClientID:(NSString *)clientId + clientSecret:(NSString *)clientSecret + authorizeURL:(NSURL *)authorizeURL + tokenURL:(NSURL *)tokenURL + delegate:(NSObject *)delegate; + +- (instancetype)initWithClientID:(NSString *)clientId + clientSecret:(NSString *)clientSecret + authorizeURL:(NSURL *)authorizeURL + tokenURL:(NSURL *)tokenURL + accessToken:(NXOAuth2AccessToken *)accessToken + keyChainGroup:(NSString *)keyChainGroup + keyChainAccessGroup:(NSString *)keyChainAccessGroup + persistent:(BOOL)shouldPersist + delegate:(NSObject *)delegate; + +- (instancetype)initWithClientID:(NSString *)clientId + clientSecret:(NSString *)clientSecret + authorizeURL:(NSURL *)authorizeURL + tokenURL:(NSURL *)tokenURL + accessToken:(NXOAuth2AccessToken *)accessToken + tokenType:(NSString *)tokenType + keyChainGroup:(NSString *)keyChainGroup + keyChainAccessGroup:(NSString *)keyChainAccessGroup + persistent:(BOOL)shouldPersist + delegate:(NSObject *)delegate; - (BOOL)openRedirectURL:(NSURL *)URL; +- (BOOL)openRedirectURL:(NSURL *)URL error: (NSError**) error; #pragma mark Authorisation Methods @@ -140,6 +154,7 @@ extern NSString * const NXOAuth2ClientConnectionContextTokenRefresh; #pragma mark Public - (void)requestAccess; +- (void)requestAccessAndRetryConnection:(NXOAuth2Connection *)retryConnection; - (void)refreshAccessToken; - (void)refreshAccessTokenAndRetryConnection:(NXOAuth2Connection *)retryConnection; diff --git a/Sources/OAuth2Client/NXOAuth2Client.m b/Sources/OAuth2Client/NXOAuth2Client.m index 2dfa0f6e..7e017539 100644 --- a/Sources/OAuth2Client/NXOAuth2Client.m +++ b/Sources/OAuth2Client/NXOAuth2Client.m @@ -37,11 +37,11 @@ @implementation NXOAuth2Client #pragma mark Lifecycle -- (id)initWithClientID:(NSString *)aClientId - clientSecret:(NSString *)aClientSecret - authorizeURL:(NSURL *)anAuthorizeURL - tokenURL:(NSURL *)aTokenURL - delegate:(NSObject *)aDelegate; +- (instancetype)initWithClientID:(NSString *)aClientId + clientSecret:(NSString *)aClientSecret + authorizeURL:(NSURL *)anAuthorizeURL + tokenURL:(NSURL *)aTokenURL + delegate:(NSObject *)aDelegate; { return [self initWithClientID:aClientId clientSecret:aClientSecret @@ -49,18 +49,20 @@ - (id)initWithClientID:(NSString *)aClientId tokenURL:aTokenURL accessToken:nil keyChainGroup:nil + keyChainAccessGroup:nil persistent:YES delegate:aDelegate]; } -- (id)initWithClientID:(NSString *)aClientId - clientSecret:(NSString *)aClientSecret - authorizeURL:(NSURL *)anAuthorizeURL - tokenURL:(NSURL *)aTokenURL - accessToken:(NXOAuth2AccessToken *)anAccessToken - keyChainGroup:(NSString *)aKeyChainGroup - persistent:(BOOL)shouldPersist - delegate:(NSObject *)aDelegate; +- (instancetype)initWithClientID:(NSString *)aClientId + clientSecret:(NSString *)aClientSecret + authorizeURL:(NSURL *)anAuthorizeURL + tokenURL:(NSURL *)aTokenURL + accessToken:(NXOAuth2AccessToken *)anAccessToken + keyChainGroup:(NSString *)aKeyChainGroup + keyChainAccessGroup:(NSString *)aKeyChainAccessGroup + persistent:(BOOL)shouldPersist + delegate:(NSObject *)aDelegate; { return [self initWithClientID:aClientId clientSecret:aClientSecret @@ -69,19 +71,21 @@ - (id)initWithClientID:(NSString *)aClientId accessToken:anAccessToken tokenType:nil keyChainGroup:aKeyChainGroup + keyChainAccessGroup:aKeyChainAccessGroup persistent:shouldPersist delegate:aDelegate]; } -- (id)initWithClientID:(NSString *)aClientId - clientSecret:(NSString *)aClientSecret - authorizeURL:(NSURL *)anAuthorizeURL - tokenURL:(NSURL *)aTokenURL - accessToken:(NXOAuth2AccessToken *)anAccessToken - tokenType:(NSString *)aTokenType - keyChainGroup:(NSString *)aKeyChainGroup - persistent:(BOOL)shouldPersist - delegate:(NSObject *)aDelegate; +- (instancetype)initWithClientID:(NSString *)aClientId + clientSecret:(NSString *)aClientSecret + authorizeURL:(NSURL *)anAuthorizeURL + tokenURL:(NSURL *)aTokenURL + accessToken:(NXOAuth2AccessToken *)anAccessToken + tokenType:(NSString *)aTokenType + keyChainGroup:(NSString *)aKeyChainGroup + keyChainAccessGroup:(NSString *)aKeyChainAccessGroup + persistent:(BOOL)shouldPersist + delegate:(NSObject *)aDelegate; { NSAssert(aTokenURL != nil && anAuthorizeURL != nil, @"No token or no authorize URL"); self = [super init]; @@ -98,6 +102,7 @@ - (id)initWithClientID:(NSString *)aClientId self.tokenRequestHTTPMethod = @"POST"; self.acceptType = @"application/json"; keyChainGroup = aKeyChainGroup; + keyChainAccessGroup = aKeyChainAccessGroup; self.persistent = shouldPersist; self.delegate = aDelegate; @@ -117,6 +122,7 @@ - (void)dealloc; @synthesize desiredScope, userAgent; @synthesize delegate, persistent, accessToken, authenticating; @synthesize additionalAuthenticationParameters; +@synthesize authConnection = authConnection; - (void)setAdditionalAuthenticationParameters:(NSDictionary *)value; { @@ -146,11 +152,13 @@ - (void)setPersistent:(BOOL)shouldPersist; if (persistent == shouldPersist) return; if (shouldPersist && accessToken) { - [self.accessToken storeInDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host]]; + [self.accessToken storeInDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host] + withAccessGroup:keyChainAccessGroup]; } if (persistent && !shouldPersist) { - [accessToken removeFromDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host]]; + [accessToken removeFromDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host] + withAccessGroup:keyChainAccessGroup]; } [self willChangeValueForKey:@"persistent"]; @@ -163,7 +171,8 @@ - (NXOAuth2AccessToken *)accessToken; if (accessToken) return accessToken; if (persistent) { - accessToken = [NXOAuth2AccessToken tokenFromDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host]]; + accessToken = [NXOAuth2AccessToken tokenFromDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host] + withAccessGroup:keyChainAccessGroup]; if (accessToken) { if ([delegate respondsToSelector:@selector(oauthClientDidGetAccessToken:)]) { [delegate oauthClientDidGetAccessToken:self]; @@ -181,7 +190,8 @@ - (void)setAccessToken:(NXOAuth2AccessToken *)value; BOOL authorisationStatusChanged = ((accessToken == nil) || (value == nil)); //They can't both be nil, see one line above. So they have to have changed from or to nil. if (!value) { - [self.accessToken removeFromDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host]]; + [self.accessToken removeFromDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host] + withAccessGroup:keyChainAccessGroup]; } [self willChangeValueForKey:@"accessToken"]; @@ -189,7 +199,8 @@ - (void)setAccessToken:(NXOAuth2AccessToken *)value; [self didChangeValueForKey:@"accessToken"]; if (persistent) { - [accessToken storeInDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host]]; + [accessToken storeInDefaultKeychainWithServiceProviderName:keyChainGroup ? keyChainGroup : [tokenURL host] + withAccessGroup:keyChainAccessGroup]; } if (authorisationStatusChanged) { @@ -222,8 +233,19 @@ - (void)setDesiredScope:(NSSet *)aDesiredScope; #pragma mark Flow - (void)requestAccess; +{ + [self requestAccessAndRetryConnection:nil]; +} + +- (void)requestAccessAndRetryConnection:(NXOAuth2Connection *)retryConnection { if (!self.accessToken) { + + if (retryConnection) { + if (!waitingConnections) waitingConnections = [[NSMutableArray alloc] init]; + [waitingConnections addObject:retryConnection]; + } + [delegate oauthClientNeedsAuthentication:self]; } } @@ -250,60 +272,27 @@ - (NSURL *)authorizationURLWithRedirectURL:(NSURL *)redirectURL; // Web Server Flow only - (BOOL)openRedirectURL:(NSURL *)URL; +{ + return [self openRedirectURL:URL error:nil]; +} + +- (BOOL)openRedirectURL:(NSURL *)URL error: (NSError**) error; { NSString *accessGrant = [URL nxoauth2_valueForQueryParameterKey:@"code"]; if (accessGrant) { [self requestTokenWithAuthGrant:accessGrant redirectURL:[URL nxoauth2_URLWithoutQueryString]]; return YES; } - - NSString *errorString = [URL nxoauth2_valueForQueryParameterKey:@"error"]; - if (errorString) { - NSInteger errorCode = 0; - NSString *localizedError = nil; - - if ([errorString caseInsensitiveCompare:@"invalid_request"] == NSOrderedSame) { - errorCode = NXOAuth2InvalidRequestErrorCode; - localizedError = NSLocalizedString(@"Invalid request to OAuth2 Server", @"NXOAuth2InvalidRequestErrorCode description"); - - } else if ([errorString caseInsensitiveCompare:@"invalid_client"] == NSOrderedSame) { - errorCode = NXOAuth2InvalidClientErrorCode; - localizedError = NSLocalizedString(@"Invalid OAuth2 Client", @"NXOAuth2InvalidClientErrorCode description"); - - } else if ([errorString caseInsensitiveCompare:@"unauthorized_client"] == NSOrderedSame) { - errorCode = NXOAuth2UnauthorizedClientErrorCode; - localizedError = NSLocalizedString(@"Unauthorized Client", @"NXOAuth2UnauthorizedClientErrorCode description"); - - } else if ([errorString caseInsensitiveCompare:@"redirect_uri_mismatch"] == NSOrderedSame) { - errorCode = NXOAuth2RedirectURIMismatchErrorCode; - localizedError = NSLocalizedString(@"Redirect URI mismatch", @"NXOAuth2RedirectURIMismatchErrorCode description"); - - } else if ([errorString caseInsensitiveCompare:@"access_denied"] == NSOrderedSame) { - errorCode = NXOAuth2AccessDeniedErrorCode; - localizedError = NSLocalizedString(@"Access denied", @"NXOAuth2AccessDeniedErrorCode description"); - - } else if ([errorString caseInsensitiveCompare:@"unsupported_response_type"] == NSOrderedSame) { - errorCode = NXOAuth2UnsupportedResponseTypeErrorCode; - localizedError = NSLocalizedString(@"Unsupported response type", @"NXOAuth2UnsupportedResponseTypeErrorCode description"); - - } else if ([errorString caseInsensitiveCompare:@"invalid_scope"] == NSOrderedSame) { - errorCode = NXOAuth2InvalidScopeErrorCode; - localizedError = NSLocalizedString(@"Invalid scope", @"NXOAuth2InvalidScopeErrorCode description"); + else{ + NSError* oauthError = [URL nxoauth2_redirectURLError]; + if (oauthError && error) { + *error = oauthError; } - - if (errorCode != 0) { - NSDictionary *userInfo = nil; - if (localizedError) { - userInfo = [NSDictionary dictionaryWithObject:localizedError forKey:NSLocalizedDescriptionKey]; - } - if ([delegate respondsToSelector:@selector(oauthClient:didFailToGetAccessTokenWithError:)]) { - [delegate oauthClient:self didFailToGetAccessTokenWithError:[NSError errorWithDomain:NXOAuth2ErrorDomain - code:errorCode - userInfo:userInfo]]; - } + if ([delegate respondsToSelector:@selector(oauthClient:didFailToGetAccessTokenWithError:)]) { + [delegate oauthClient:self didFailToGetAccessTokenWithError: oauthError]; } + return NO; } - return NO; } #pragma mark Request Token @@ -474,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 ef41164c..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; @@ -87,16 +96,16 @@ typedef void(^NXOAuth2ConnectionSendingProgressHandler)(unsigned long long bytes @property (nonatomic, strong) NSDictionary *userInfo; @property (nonatomic, strong, readonly) NXOAuth2Client *client; -- (id) initWithRequest:(NSMutableURLRequest *)request - requestParameters:(NSDictionary *)requestParameters - oauthClient:(NXOAuth2Client *)client -sendingProgressHandler:(NXOAuth2ConnectionSendingProgressHandler)sendingProgressHandler - responseHandler:(NXOAuth2ConnectionResponseHandler)responseHandler; +- (instancetype) initWithRequest:(NSMutableURLRequest *)request + requestParameters:(NSDictionary *)requestParameters + oauthClient:(NXOAuth2Client *)client + sendingProgressHandler:(NXOAuth2ConnectionSendingProgressHandler)sendingProgressHandler + responseHandler:(NXOAuth2ConnectionResponseHandler)responseHandler; -- (id)initWithRequest:(NSMutableURLRequest *)request - requestParameters:(NSDictionary *)requestParameters - oauthClient:(NXOAuth2Client *)client - delegate:(NSObject *)delegate; +- (instancetype)initWithRequest:(NSMutableURLRequest *)request + requestParameters:(NSDictionary *)requestParameters + oauthClient:(NXOAuth2Client *)client + delegate:(NSObject *)delegate; - (void)cancel; diff --git a/Sources/OAuth2Client/NXOAuth2Connection.m b/Sources/OAuth2Client/NXOAuth2Connection.m index a324850b..b095572c 100644 --- a/Sources/OAuth2Client/NXOAuth2Connection.m +++ b/Sources/OAuth2Client/NXOAuth2Connection.m @@ -64,10 +64,10 @@ - (id)initWithRequest:(NSMutableURLRequest *)aRequest return self; } -- (id)initWithRequest:(NSMutableURLRequest *)aRequest - requestParameters:(NSDictionary *)someRequestParameters - oauthClient:(NXOAuth2Client *)aClient - delegate:(NSObject *)aDelegate; +- (instancetype)initWithRequest:(NSMutableURLRequest *)aRequest + requestParameters:(NSDictionary *)someRequestParameters + oauthClient:(NXOAuth2Client *)aClient + delegate:(NSObject *)aDelegate; { self = [super init]; if (self) { @@ -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,12 +416,18 @@ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLRespon } } } - if (/*self.statusCode == 401 // TODO: check for status code once the bug returning 500 is fixed - &&*/ client.accessToken.refreshToken != nil + + if (self.statusCode == 401 + && client.accessToken.refreshToken != nil && authenticateHeader - && [authenticateHeader rangeOfString:@"expired_token"].location != NSNotFound) { + && ([authenticateHeader rangeOfString:@"invalid_token"].location != NSNotFound || + [authenticateHeader rangeOfString:@"expired_token"].location != NSNotFound )) + { [self cancel]; [client refreshAccessTokenAndRetryConnection:self]; + } else if (client.authConnection != self && authenticateHeader && client) { + [self cancel]; + [client requestAccessAndRetryConnection:self]; } else { if ([delegate respondsToSelector:@selector(oauthConnection:didReceiveData:)]) { [delegate oauthConnection:self didReceiveData:data]; // inform the delegate that we start with empty data @@ -468,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 @@ -489,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/NXOAuth2FileStreamWrapper.h b/Sources/OAuth2Client/NXOAuth2FileStreamWrapper.h index 590c8d43..f4542cdd 100755 --- a/Sources/OAuth2Client/NXOAuth2FileStreamWrapper.h +++ b/Sources/OAuth2Client/NXOAuth2FileStreamWrapper.h @@ -25,11 +25,18 @@ @property (nonatomic, copy, readonly) NSString *fileName; @property (nonatomic, copy) NSString *contentType; // optional DEFAULT: "application/octettstream" -+ (id)wrapperWithStream:(NSInputStream *)stream contentLength:(unsigned long long)contentLength DEPRECATED_ATTRIBUTE; -- (id)initWithStream:(NSInputStream *)stream contentLength:(unsigned long long)contentLength DEPRECATED_ATTRIBUTE; ++ (instancetype)wrapperWithStream:(NSInputStream *)stream + contentLength:(unsigned long long)contentLength DEPRECATED_ATTRIBUTE; -+ (id)wrapperWithStream:(NSInputStream *)stream contentLength:(unsigned long long)contentLength fileName:(NSString *)fileName; -- (id)initWithStream:(NSInputStream *)stream contentLength:(unsigned long long)contentLength fileName:(NSString *)fileName; +- (instancetype)initWithStream:(NSInputStream *)stream + contentLength:(unsigned long long)contentLength DEPRECATED_ATTRIBUTE; ++ (instancetype)wrapperWithStream:(NSInputStream *)stream + contentLength:(unsigned long long)contentLength + fileName:(NSString *)fileName; + +- (instancetype)initWithStream:(NSInputStream *)stream + contentLength:(unsigned long long)contentLength + fileName:(NSString *)fileName; @end diff --git a/Sources/OAuth2Client/NXOAuth2FileStreamWrapper.m b/Sources/OAuth2Client/NXOAuth2FileStreamWrapper.m index 11b7678a..c7fb6ec9 100755 --- a/Sources/OAuth2Client/NXOAuth2FileStreamWrapper.m +++ b/Sources/OAuth2Client/NXOAuth2FileStreamWrapper.m @@ -18,12 +18,12 @@ @implementation NXOAuth2FileStreamWrapper #pragma mark Class Methods -+ (id)wrapperWithStream:(NSInputStream *)aStream contentLength:(unsigned long long)aContentLength; ++ (instancetype)wrapperWithStream:(NSInputStream *)aStream contentLength:(unsigned long long)aContentLength; { return [self wrapperWithStream:aStream contentLength:aContentLength fileName:nil]; } -+ (id)wrapperWithStream:(NSInputStream *)aStream contentLength:(unsigned long long)aContentLength fileName:(NSString *)aFileName; ++ (instancetype)wrapperWithStream:(NSInputStream *)aStream contentLength:(unsigned long long)aContentLength fileName:(NSString *)aFileName; { return [[self alloc] initWithStream:aStream contentLength:aContentLength fileName:aFileName]; } @@ -31,18 +31,18 @@ + (id)wrapperWithStream:(NSInputStream *)aStream contentLength:(unsigned long lo #pragma mark Lifecycle -- (id)init; +- (instancetype)init; { NSAssert(NO, @"-init should not be used in the NXOAuth2FileStreamWrapper"); return nil; } -- (id)initWithStream:(NSInputStream *)theStream contentLength:(unsigned long long)theContentLength; +- (instancetype)initWithStream:(NSInputStream *)theStream contentLength:(unsigned long long)theContentLength; { return [self initWithStream:theStream contentLength:theContentLength fileName:nil]; } -- (id)initWithStream:(NSInputStream *)aStream contentLength:(unsigned long long)aContentLength fileName:(NSString *)aFileName; +- (instancetype)initWithStream:(NSInputStream *)aStream contentLength:(unsigned long long)aContentLength fileName:(NSString *)aFileName; { if (!aFileName) aFileName = @"unknown"; diff --git a/Sources/OAuth2Client/NXOAuth2PostBodyPart.h b/Sources/OAuth2Client/NXOAuth2PostBodyPart.h index d83cf6d2..f0376bab 100644 --- a/Sources/OAuth2Client/NXOAuth2PostBodyPart.h +++ b/Sources/OAuth2Client/NXOAuth2PostBodyPart.h @@ -34,12 +34,22 @@ * - NSData * - NXOAuth2FileStreamWrapper */ -+ (id)partWithName:(NSString *)name content:(id)content; -- (id)initWithName:(NSString *)name content:(id)content; ++ (instancetype)partWithName:(NSString *)name + content:(id)content; -- (id)initWithHeaders:(NSString *)headers dataContent:(NSData *)data; -- (id)initWithName:(NSString *)name streamContent:(NSInputStream *)stream streamLength:(unsigned long long)streamLength fileName:(NSString *)fileName; +- (instancetype)initWithName:(NSString *)name + content:(id)content; -- (id)initWithHeaders:(NSString *)headers streamContent:(NSInputStream *)stream length:(unsigned long long)length; //designated initializer +- (instancetype)initWithHeaders:(NSString *)headers + dataContent:(NSData *)data; + +- (instancetype)initWithName:(NSString *)name + streamContent:(NSInputStream *)stream + streamLength:(unsigned long long)streamLength + fileName:(NSString *)fileName; + +- (instancetype)initWithHeaders:(NSString *)headers + streamContent:(NSInputStream *)stream + length:(unsigned long long)length; //designated initializer @end diff --git a/Sources/OAuth2Client/NXOAuth2PostBodyPart.m b/Sources/OAuth2Client/NXOAuth2PostBodyPart.m index 74d030d6..6c8345a5 100644 --- a/Sources/OAuth2Client/NXOAuth2PostBodyPart.m +++ b/Sources/OAuth2Client/NXOAuth2PostBodyPart.m @@ -17,9 +17,9 @@ @interface NXOAuth2PostBodyPart(Private) -- (id)initWithName:(NSString *)name dataContent:(NSData *)data; -- (id)initWithName:(NSString *)name fileContent:(NSString *)path; -- (id)initWithName:(NSString *)name stringContent:(NSString *)string; +- (instancetype)initWithName:(NSString *)name dataContent:(NSData *)data; +- (instancetype)initWithName:(NSString *)name fileContent:(NSString *)path; +- (instancetype)initWithName:(NSString *)name stringContent:(NSString *)string; @end @@ -27,12 +27,12 @@ @implementation NXOAuth2PostBodyPart #pragma mark Lifecycle -+ (id)partWithName:(NSString *)name content:(id)content; ++ (instancetype)partWithName:(NSString *)name content:(id)content; { return [[self alloc] initWithName:name content:content]; } -- (id)initWithName:(NSString *)name content:(id)content; +- (instancetype)initWithName:(NSString *)name content:(id)content; { if ([content isKindOfClass:[NSString class]]) { return [self initWithName:name stringContent:content]; @@ -50,7 +50,7 @@ - (id)initWithName:(NSString *)name content:(id)content; } } -- (id)initWithName:(NSString *)name streamContent:(NSInputStream *)stream streamLength:(unsigned long long)streamLength fileName:(NSString *)fileName; +- (instancetype)initWithName:(NSString *)name streamContent:(NSInputStream *)stream streamLength:(unsigned long long)streamLength fileName:(NSString *)fileName; { NSMutableString *headers = [NSMutableString string]; [headers appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, fileName]; @@ -60,7 +60,7 @@ - (id)initWithName:(NSString *)name streamContent:(NSInputStream *)stream stream return [self initWithHeaders:headers streamContent:stream length:streamLength]; } -- (id)initWithName:(NSString *)name streamContent:(NSInputStream *)stream streamLength:(unsigned long long)streamLength fileName:(NSString *)fileName contentType:(NSString *)contentType; +- (instancetype)initWithName:(NSString *)name streamContent:(NSInputStream *)stream streamLength:(unsigned long long)streamLength fileName:(NSString *)fileName contentType:(NSString *)contentType; { NSMutableString *headers = [NSMutableString string]; [headers appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, fileName]; @@ -70,7 +70,7 @@ - (id)initWithName:(NSString *)name streamContent:(NSInputStream *)stream stream return [self initWithHeaders:headers streamContent:stream length:streamLength]; } -- (id)initWithName:(NSString *)name dataContent:(NSData *)data; +- (instancetype)initWithName:(NSString *)name dataContent:(NSData *)data; { NSMutableString *headers = [NSMutableString string]; [headers appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"unknown\"\r\n", name]; @@ -80,7 +80,7 @@ - (id)initWithName:(NSString *)name dataContent:(NSData *)data; return [self initWithHeaders:headers dataContent:data]; } -- (id)initWithName:(NSString *)name fileContent:(NSString *)path; +- (instancetype)initWithName:(NSString *)name fileContent:(NSString *)path; { NSMutableString *headers = [NSMutableString string]; [headers appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, [path lastPathComponent]]; @@ -100,7 +100,7 @@ - (id)initWithName:(NSString *)name fileContent:(NSString *)path; length:[fileSize unsignedLongLongValue]]; } -- (id)initWithName:(NSString *)name stringContent:(NSString *)string; +- (instancetype)initWithName:(NSString *)name stringContent:(NSString *)string; { NSMutableString *headers = [NSMutableString string]; [headers appendFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", name]; @@ -108,14 +108,14 @@ - (id)initWithName:(NSString *)name stringContent:(NSString *)string; return [self initWithHeaders:headers dataContent:[string dataUsingEncoding:NSUTF8StringEncoding]]; } -- (id)initWithHeaders:(NSString *)headers dataContent:(NSData *)data; +- (instancetype)initWithHeaders:(NSString *)headers dataContent:(NSData *)data; { return [self initWithHeaders: headers streamContent: [NSInputStream inputStreamWithData:data] length: [data length]]; } -- (id)initWithHeaders:(NSString *)headers streamContent:(NSInputStream *)stream length:(unsigned long long)length; +- (instancetype)initWithHeaders:(NSString *)headers streamContent:(NSInputStream *)stream length:(unsigned long long)length; { self = [super init]; if(self) { diff --git a/Sources/OAuth2Client/NXOAuth2PostBodyStream.h b/Sources/OAuth2Client/NXOAuth2PostBodyStream.h index 9745db28..c08ea013 100644 --- a/Sources/OAuth2Client/NXOAuth2PostBodyStream.h +++ b/Sources/OAuth2Client/NXOAuth2PostBodyStream.h @@ -25,7 +25,7 @@ unsigned long long numBytesTotal; } -- (id)initWithParameters:(NSDictionary *)postParameters; +- (instancetype)initWithParameters:(NSDictionary *)postParameters; @property (readonly) NSString *boundary; @property (readonly) unsigned long long length; diff --git a/Sources/OAuth2Client/NXOAuth2PostBodyStream.m b/Sources/OAuth2Client/NXOAuth2PostBodyStream.m index 8b156d84..0d133ec9 100644 --- a/Sources/OAuth2Client/NXOAuth2PostBodyStream.m +++ b/Sources/OAuth2Client/NXOAuth2PostBodyStream.m @@ -26,11 +26,15 @@ @implementation NXOAuth2PostBodyStream #pragma mark Lifecycle -- (id)initWithParameters:(NSDictionary *)postParameters; +- (instancetype)initWithParameters:(NSDictionary *)postParameters; { self = [self init]; if (self) { - srandom(time(NULL)); +#if __DARWIN_UNIX03 + srandom((unsigned) time(NULL)); +#else + srandom((unsigned long) time(NULL)); +#endif boundary = [[NSString alloc] initWithFormat:@"------------nx-oauth2%d", rand()]; numBytesTotal = 0; streamIndex = 0; @@ -88,7 +92,7 @@ - (NSArray *)streamsForParameters:(NSDictionary *)parameters contentLength:(unsi NSData *delimiterData = [delimiter dataUsingEncoding:NSUTF8StringEncoding]; NSData *contentHeaderData = [[part contentHeaders] dataUsingEncoding:NSUTF8StringEncoding]; - int dataLength = delimiterData.length + contentHeaderData.length; + NSUInteger dataLength = delimiterData.length + contentHeaderData.length; NSMutableData *headerData = [NSMutableData dataWithCapacity: dataLength]; [headerData appendData:delimiterData]; [headerData appendData:contentHeaderData]; @@ -146,7 +150,7 @@ - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len; if (currentStream == nil) return 0; - int result = [currentStream read:buffer maxLength:len]; + NSInteger result = [currentStream read:buffer maxLength:len]; if (result == 0) { if (streamIndex < contentStreams.count - 1) { diff --git a/Sources/OAuth2Client/NXOAuth2Request.h b/Sources/OAuth2Client/NXOAuth2Request.h index 3bc2c270..09c9799f 100644 --- a/Sources/OAuth2Client/NXOAuth2Request.h +++ b/Sources/OAuth2Client/NXOAuth2Request.h @@ -37,10 +37,20 @@ 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 -- (id)initWithResource:(NSURL *)url method:(NSString *)method parameters:(NSDictionary *)parameter; +- (instancetype)initWithResource:(NSURL *)url + method:(NSString *)method + parameters:(NSDictionary *)parameter; #pragma mark Accessors @@ -51,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 680546c3..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,18 +41,36 @@ + (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]; } #pragma mark Lifecycle -- (id)initWithResource:(NSURL *)aResource method:(NSString *)aMethod parameters:(NSDictionary *)someParameters; +- (instancetype)initWithResource:(NSURL *)aResource method:(NSString *)aMethod parameters:(NSDictionary *)someParameters; { self = [super init]; if (self) { @@ -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