diff --git a/ADAL.podspec b/ADAL.podspec index 38b050f98..6774eb193 100644 --- a/ADAL.podspec +++ b/ADAL.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "ADAL" s.module_name = "ADAL" - s.version = "2.1.0" + s.version = "2.1.1" s.summary = "The ADAL SDK for iOS gives you the ability to add Azure Identity authentication to your application" s.description = <<-DESC diff --git a/ADAL/src/ADAL_Internal.h b/ADAL/src/ADAL_Internal.h index 0756ce4a9..cedf04e87 100644 --- a/ADAL/src/ADAL_Internal.h +++ b/ADAL/src/ADAL_Internal.h @@ -25,11 +25,11 @@ //version in static define until we identify a better place: #define ADAL_VER_HIGH 2 #define ADAL_VER_LOW 1 -#define ADAL_VER_PATCH 0 +#define ADAL_VER_PATCH 1 #define STR_ADAL_VER_HIGH "2" #define STR_ADAL_VER_LOW "1" -#define STR_ADAL_VER_PATCH "0" +#define STR_ADAL_VER_PATCH "1" // Framework versions only support high and low for the double value, sadly. #define ADAL_VERSION_NUMBER 2.1 @@ -40,7 +40,7 @@ #define ADAL_VERSION_(high, low, patch) adalVersion_ ## high ## _ ## low ## _ ## patch // This is specially crafted so the name of the variable matches the full ADAL version -#define ADAL_VERSION_VAR ADAL_VERSION_(2, 1, 0) +#define ADAL_VERSION_VAR ADAL_VERSION_(2, 1, 1) #import "ADLogger+Internal.h" #import "ADErrorCodes.h" diff --git a/ADAL/src/ADInstanceDiscovery.m b/ADAL/src/ADInstanceDiscovery.m index 3c028a408..8417b600d 100644 --- a/ADAL/src/ADInstanceDiscovery.m +++ b/ADAL/src/ADInstanceDiscovery.m @@ -53,9 +53,10 @@ - (id)init //List of prevalidated authorities (Azure Active Directory cloud instances). //Only the sThrustedAuthority is used for validation of new authorities. [_validatedAuthorities addObject:sTrustedAuthority]; - [_validatedAuthorities addObject:@"https://login.chinacloudapi.cn"]; - [_validatedAuthorities addObject:@"https://login.cloudgovapi.us"]; - [_validatedAuthorities addObject:@"https://login.microsoftonline.com"]; + [_validatedAuthorities addObject:@"https://login.chinacloudapi.cn"]; // Microsoft Azure China + [_validatedAuthorities addObject:@"https://login.microsoftonline.de"]; // Microsoft Azure Germany + [_validatedAuthorities addObject:@"https://login.microsoftonline.com"]; // Microsoft Azure Worldwide + [_validatedAuthorities addObject:@"https://login-us.microsoftonline.com"]; // Microsoft Azure US Government return self; } diff --git a/ADAL/src/cache/ADAuthenticationRequest+TokenCaching.m b/ADAL/src/cache/ADAuthenticationRequest+TokenCaching.m index 6937ec963..0956c49de 100644 --- a/ADAL/src/cache/ADAuthenticationRequest+TokenCaching.m +++ b/ADAL/src/cache/ADAuthenticationRequest+TokenCaching.m @@ -148,14 +148,6 @@ - (void)updateCacheToResult:(ADAuthenticationResult *)result return; } - // If we succeeded, or weren't told it was a bad refresh token then just return right away as there are no changes - // to make to the cache. - if (!(result.status == AD_SUCCEEDED || result.error.code == AD_ERROR_SERVER_REFRESH_TOKEN_REJECTED)) - { - return; - } - - if (AD_SUCCEEDED == result.status) { ADTokenCacheItem* item = [result tokenCacheItem]; @@ -171,13 +163,23 @@ - (void)updateCacheToResult:(ADAuthenticationResult *)result [self updateCacheToItem:item MRRT:[result multiResourceRefreshToken]]; + return; } - else + + if (result.error.code != AD_ERROR_SERVER_REFRESH_TOKEN_REJECTED) { - [self removeItemFromCache:cacheItem - refreshToken:refreshToken - error:result.error]; + return; + } + + // Only remove tokens from the cache if we get an invalid_grant from the server + if (![result.error.protocolCode isEqualToString:@"invalid_grant"]) + { + return; } + + [self removeItemFromCache:cacheItem + refreshToken:refreshToken + error:result.error]; } - (void)updateCacheToItem:(ADTokenCacheItem *)cacheItem diff --git a/ADAL/src/request/ADAuthenticationRequest+AcquireToken.m b/ADAL/src/request/ADAuthenticationRequest+AcquireToken.m index 4159475a5..283089236 100644 --- a/ADAL/src/request/ADAuthenticationRequest+AcquireToken.m +++ b/ADAL/src/request/ADAuthenticationRequest+AcquireToken.m @@ -214,6 +214,8 @@ - (void)attemptToUseCacheItem:(ADTokenCacheItem*)item //The refresh token attempt failed and no other suitable refresh token found //call acquireToken + _underlyingError = result.error; + SAFE_ARC_RETAIN(_underlyingError); [self requestToken:completionBlock]; }];//End of the refreshing token completion block, executed asynchronously. } @@ -227,11 +229,14 @@ - (void)requestToken:(ADAuthenticationCallback)completionBlock //The cache lookup and refresh token attempt have been unsuccessful, //so credentials are needed to get an access token, but the developer, requested //no UI to be shown: + NSDictionary* underlyingError = _underlyingError ? @{NSUnderlyingErrorKey:_underlyingError} : nil; ADAuthenticationError* error = [ADAuthenticationError errorFromAuthenticationError:AD_ERROR_SERVER_USER_INPUT_NEEDED protocolCode:nil errorDetails:ADCredentialsNeeded + userInfo:underlyingError correlationId:_correlationId]; + ADAuthenticationResult* result = [ADAuthenticationResult resultFromError:error correlationId:_correlationId]; completionBlock(result); return; diff --git a/ADAL/src/request/ADAuthenticationRequest.h b/ADAL/src/request/ADAuthenticationRequest.h index c8000ebaf..3f521e30b 100644 --- a/ADAL/src/request/ADAuthenticationRequest.h +++ b/ADAL/src/request/ADAuthenticationRequest.h @@ -68,6 +68,8 @@ NSString* _logComponent; BOOL _requestStarted; + + ADAuthenticationError* _underlyingError; } @property (retain) NSString* logComponent; diff --git a/ADAL/src/request/ADAuthenticationRequest.m b/ADAL/src/request/ADAuthenticationRequest.m index 5d9dac627..3340f71cf 100644 --- a/ADAL/src/request/ADAuthenticationRequest.m +++ b/ADAL/src/request/ADAuthenticationRequest.m @@ -133,6 +133,7 @@ - (void)dealloc SAFE_ARC_RELEASE(_queryParams); SAFE_ARC_RELEASE(_refreshTokenCredential); SAFE_ARC_RELEASE(_correlationId); + SAFE_ARC_RELEASE(_underlyingError); SAFE_ARC_SUPER_DEALLOC(); } diff --git a/ADAL/src/ui/ADWebAuthController.m b/ADAL/src/ui/ADWebAuthController.m index 5f0178b2e..5d7c12bc8 100755 --- a/ADAL/src/ui/ADWebAuthController.m +++ b/ADAL/src/ui/ADWebAuthController.m @@ -380,7 +380,6 @@ - (BOOL)webAuthShouldStartLoadRequest:(NSURLRequest *)request // The user cancelled authentication - (void)webAuthDidCancel { - DebugLog(); AD_LOG_INFO(@"-webAuthDidCancel", _correlationId, nil); // Dispatch the completion block @@ -393,7 +392,7 @@ - (void)webAuthDidCancel - (void)webAuthDidCompleteWithURL:(NSURL *)endURL { AD_LOG_INFO_F(@"-webAuthDidCompleteWithURL:", _correlationId, @"%@", endURL); - DebugLog(); + [self endWebAuthenticationWithError:nil orURL:endURL]; [[NSNotificationCenter defaultCenter] postNotificationName:ADWebAuthDidCompleteNotification object:self userInfo:nil]; } @@ -401,9 +400,16 @@ - (void)webAuthDidCompleteWithURL:(NSURL *)endURL // Authentication failed somewhere - (void)webAuthDidFailWithError:(NSError *)error { - AD_LOG_ERROR_F(@"-webAuthDidFailWithError:", error.code, _correlationId, @"error: %@", error); + // Ignore WebKitError 102 for OAuth 2.0 flow. + if ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102) + { + return; + } + if (error) { + AD_LOG_ERROR_F(@"-webAuthDidFailWithError:", error.code, _correlationId, @"error: %@", error); + [[NSNotificationCenter defaultCenter] postNotificationName:ADWebAuthDidFailNotification object:self userInfo:@{ @"error" : error}]; diff --git a/ADAL/src/ui/mac/ADAuthenticationViewController.m b/ADAL/src/ui/mac/ADAuthenticationViewController.m index 9242db268..fe6a848e2 100644 --- a/ADAL/src/ui/mac/ADAuthenticationViewController.m +++ b/ADAL/src/ui/mac/ADAuthenticationViewController.m @@ -91,7 +91,8 @@ - (BOOL)loadView:(ADAuthenticationError * __autoreleasing *)error backing:NSBackingStoreBuffered defer:YES]; [window setDelegate:self]; - + [window setAccessibilityIdentifier:@"ADAL_SIGN_IN_WINDOW"]; + NSView* contentView = window.contentView; @@ -102,7 +103,8 @@ - (BOOL)loadView:(ADAuthenticationError * __autoreleasing *)error [webView setResourceLoadDelegate:self]; [webView setPolicyDelegate:self]; [webView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable]; - + [webView setAccessibilityIdentifier:@"ADAL_SIGN_IN_WEBVIEW"]; + [contentView addSubview:webView]; NSProgressIndicator* progressIndicator = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(DEFAULT_WINDOW_WIDTH / 2 - 16, DEFAULT_WINDOW_HEIGHT / 2 - 16, 32, 32)]; diff --git a/ADAL/tests/ADAcquireTokenTests.m b/ADAL/tests/ADAcquireTokenTests.m index cacee5318..853f8da7f 100644 --- a/ADAL/tests/ADAcquireTokenTests.m +++ b/ADAL/tests/ADAcquireTokenTests.m @@ -679,6 +679,41 @@ - (void)testMRRTNoNetworkConnection XCTAssertEqualObjects(allItems[0], mrrtItem); } +- (void)testMRRTUnauthorizedClient +{ + // Refresh tokens should only be deleted when the server returns a 'invalid_grant' error + ADAuthenticationError* error = nil; + ADAuthenticationContext* context = [self getTestAuthenticationContext]; + + // Add an MRRT to the cache as well + ADTokenCacheItem* mrrtItem = [self adCreateMRRTCacheItem]; + [[context tokenCacheStore] addOrUpdateItem:mrrtItem correlationId:nil error:&error]; + XCTAssertNil(error); + + // Set up the mock connection to simulate a no internet connection error + [ADTestURLConnection addResponse:[self adDefaultBadRefreshTokenResponseError:@"unauthorized_client"]]; + + [context acquireTokenSilentWithResource:TEST_RESOURCE + clientId:TEST_CLIENT_ID + redirectUri:TEST_REDIRECT_URL + completionBlock:^(ADAuthenticationResult *result) + { + XCTAssertNotNil(result); + XCTAssertEqual(result.status, AD_FAILED); + XCTAssertNotNil(result.error); + + TEST_SIGNAL; + }]; + + TEST_WAIT; + + // The MRRT should still be in the cache + NSArray* allItems = [[context tokenCacheStore] allItems:&error]; + XCTAssertNotNil(allItems); + XCTAssertEqual(allItems.count, 1); + XCTAssertEqualObjects(allItems[0], mrrtItem); +} + - (void)testRequestRetryOnUnusualHttpResponse { //Create a normal authority (not a test one): diff --git a/ADAL/tests/XCTestCase+TestHelperMethods.h b/ADAL/tests/XCTestCase+TestHelperMethods.h index 906fee5d2..733594cb7 100644 --- a/ADAL/tests/XCTestCase+TestHelperMethods.h +++ b/ADAL/tests/XCTestCase+TestHelperMethods.h @@ -71,6 +71,7 @@ typedef enum resource:(NSString *)resource clientId:(NSString *)clientId correlationId:(NSUUID *)correlationId; +- (ADTestURLResponse *)adDefaultBadRefreshTokenResponseError:(NSString*)oauthError; - (ADTestURLResponse *)adDefaultBadRefreshTokenResponse; - (ADTestURLResponse *)adDefaultRefreshResponse:(NSString *)newRefreshToken diff --git a/ADAL/tests/XCTestCase+TestHelperMethods.m b/ADAL/tests/XCTestCase+TestHelperMethods.m index cf1fa2d6c..899d12d5c 100644 --- a/ADAL/tests/XCTestCase+TestHelperMethods.m +++ b/ADAL/tests/XCTestCase+TestHelperMethods.m @@ -294,6 +294,7 @@ - (ADTestURLResponse *)adResponseBadRefreshToken:(NSString *)refreshToken authority:(NSString *)authority resource:(NSString *)resource clientId:(NSString *)clientId + oauthError:(NSString *)oauthError correlationId:(NSUUID *)correlationId { NSString* requestUrlString = [NSString stringWithFormat:@"%@/oauth2/token?x-client-Ver=" ADAL_VERSION_STRING, authority]; @@ -314,19 +315,26 @@ - (ADTestURLResponse *)adResponseBadRefreshToken:(NSString *)refreshToken responseURLString:@"https://contoso.com" responseCode:400 httpHeaderFields:@{} - dictionaryAsJSON:@{ OAUTH2_ERROR : @"bad_refresh_token", + dictionaryAsJSON:@{ OAUTH2_ERROR : oauthError, OAUTH2_ERROR_DESCRIPTION : @"oauth error description"}]; return response; } -- (ADTestURLResponse *)adDefaultBadRefreshTokenResponse +- (ADTestURLResponse *)adDefaultBadRefreshTokenResponseError:(NSString*)oauthError { return [self adResponseBadRefreshToken:TEST_REFRESH_TOKEN authority:TEST_AUTHORITY resource:TEST_RESOURCE clientId:TEST_CLIENT_ID + oauthError:oauthError correlationId:TEST_CORRELATION_ID]; + +} + +- (ADTestURLResponse *)adDefaultBadRefreshTokenResponse +{ + return [self adDefaultBadRefreshTokenResponseError:@"invalid_grant"]; } - (ADTestURLResponse *)adDefaultRefreshResponse:(NSString *)newRefreshToken diff --git a/changelog.txt b/changelog.txt index ecd5403eb..8643bc0df 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,38 @@ +Version 2.1.1 +_____________ +* Added underlying errors to ADAuthenticationErrors when returning user interaction required +* Fixed a crash in ADKeychainTokenCache +* Add Azure Germany, and login-us to the list of known good Azure AD Authorities +* Refresh Tokens will only be removed on 'invalid_grant' OAuth errors now. + +Version 2.1.0 +_____________ +(See the GitHub release page for a more detailed list) +* Mac OS X Support +* Brokered Authentication support with Azure Authenticator +* Support for Conditional Access in 3rd Party apps via Azure Authenticator +* Support for User Cert Based Authentication via Azure Authenticator +* Changed ADLogger callback to allow more data to be passed through for telemetry purposes +* Logging improvements +* Token Cache API changes +* Renamed ADKeychainTokenCacheStore to ADKeychainTokenCache +* Renamed ADTokenCacheStoreItem to ADTokenCacheItem +* Renamed ADAuthenticationBroker to ADWebAuthController +* Changed APIs in ADAuthenticationSettings +* Added ADUserIdentifier API. + + +Version 1.2.6 +_____________ +* Whitelisted "about:blank" on the ADAL webview redirect filter. + + Version 1.2.5 _____________ * Fix for a crash in ADClientMetrics when using ADAL directly against ADFS +* Fix a correlation ID synchronization issue that could cause crashes +* Added token removal logging +* Redirects and forwards to unsecure endpoints (HTTP) will be blocked in the ADAL webview Version 1.2.4 -------------