Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions AppAuth.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
341742201C5D82D3000EF209 /* OIDTokenResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742101C5D82D3000EF209 /* OIDTokenResponseTests.m */; };
341742211C5D82D3000EF209 /* OIDURLQueryComponentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742121C5D82D3000EF209 /* OIDURLQueryComponentTests.m */; };
341742221C5D82D3000EF209 /* OIDURLQueryComponentTestsIOS7.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742131C5D82D3000EF209 /* OIDURLQueryComponentTestsIOS7.m */; };
0A981C0100000003000A0981 /* OIDExternalUserAgentIOSCustomBrowserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A981C0100000001000A0981 /* OIDExternalUserAgentIOSCustomBrowserTests.m */; };
3417422B1C5D8502000EF209 /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3417422A1C5D8502000EF209 /* SafariServices.framework */; };
3417422D1C5D850C000EF209 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3417422C1C5D850C000EF209 /* Security.framework */; };
341AA4D31E7F392C00FCA5C6 /* libAppAuth-macOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 340DAE4E1D58216A00EC285B /* libAppAuth-macOS.a */; };
Expand Down Expand Up @@ -269,6 +270,7 @@
343AAA7C1E8346B400F9D36E /* OIDTokenResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742101C5D82D3000EF209 /* OIDTokenResponseTests.m */; };
343AAA7D1E8346B400F9D36E /* OIDURLQueryComponentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742121C5D82D3000EF209 /* OIDURLQueryComponentTests.m */; };
343AAA7E1E8346B400F9D36E /* OIDURLQueryComponentTestsIOS7.m in Sources */ = {isa = PBXBuildFile; fileRef = 341742131C5D82D3000EF209 /* OIDURLQueryComponentTestsIOS7.m */; };
0A981C0100000004000A0981 /* OIDExternalUserAgentIOSCustomBrowserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A981C0100000001000A0981 /* OIDExternalUserAgentIOSCustomBrowserTests.m */; };
343AAA7F1E8346B400F9D36E /* OIDRegistrationRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F821DE43BAF00DA0DC3 /* OIDRegistrationRequestTests.m */; };
343AAA801E8346B400F9D36E /* OIDRegistrationResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 60140F851DE43CC700DA0DC3 /* OIDRegistrationResponseTests.m */; };
343AAA811E83477100F9D36E /* OIDURLQueryComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741D81C5D8243000EF209 /* OIDURLQueryComponent.m */; };
Expand Down Expand Up @@ -795,6 +797,7 @@
341742111C5D82D3000EF209 /* OIDURLQueryComponentTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OIDURLQueryComponentTests.h; sourceTree = "<group>"; };
341742121C5D82D3000EF209 /* OIDURLQueryComponentTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDURLQueryComponentTests.m; sourceTree = "<group>"; };
341742131C5D82D3000EF209 /* OIDURLQueryComponentTestsIOS7.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDURLQueryComponentTestsIOS7.m; sourceTree = "<group>"; };
0A981C0100000001000A0981 /* OIDExternalUserAgentIOSCustomBrowserTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OIDExternalUserAgentIOSCustomBrowserTests.m; sourceTree = "<group>"; };
341742231C5D8317000EF209 /* UnitTestsInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = UnitTestsInfo.plist; sourceTree = "<group>"; };
3417422A1C5D8502000EF209 /* SafariServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SafariServices.framework; path = System/Library/Frameworks/SafariServices.framework; sourceTree = SDKROOT; };
3417422C1C5D850C000EF209 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -1139,6 +1142,7 @@
341742111C5D82D3000EF209 /* OIDURLQueryComponentTests.h */,
341742121C5D82D3000EF209 /* OIDURLQueryComponentTests.m */,
341742131C5D82D3000EF209 /* OIDURLQueryComponentTestsIOS7.m */,
0A981C0100000001000A0981 /* OIDExternalUserAgentIOSCustomBrowserTests.m */,
60140F821DE43BAF00DA0DC3 /* OIDRegistrationRequestTests.m */,
60140F841DE43C8C00DA0DC3 /* OIDRegistrationResponseTests.h */,
60140F851DE43CC700DA0DC3 /* OIDRegistrationResponseTests.m */,
Expand Down Expand Up @@ -2210,6 +2214,7 @@
341742211C5D82D3000EF209 /* OIDURLQueryComponentTests.m in Sources */,
341742201C5D82D3000EF209 /* OIDTokenResponseTests.m in Sources */,
341742221C5D82D3000EF209 /* OIDURLQueryComponentTestsIOS7.m in Sources */,
0A981C0100000003000A0981 /* OIDExternalUserAgentIOSCustomBrowserTests.m in Sources */,
E56963F03064815800AC4DFE /* OIDExternalUserAgentIOSTests.m in Sources */,
3417421E1C5D82D3000EF209 /* OIDServiceDiscoveryTests.m in Sources */,
3417421F1C5D82D3000EF209 /* OIDTokenRequestTests.m in Sources */,
Expand Down Expand Up @@ -2385,6 +2390,7 @@
343AAA7D1E8346B400F9D36E /* OIDURLQueryComponentTests.m in Sources */,
343AAA791E8346B400F9D36E /* OIDServiceConfigurationTests.m in Sources */,
343AAA7E1E8346B400F9D36E /* OIDURLQueryComponentTestsIOS7.m in Sources */,
0A981C0100000004000A0981 /* OIDExternalUserAgentIOSCustomBrowserTests.m in Sources */,
343AAA801E8346B400F9D36E /* OIDRegistrationResponseTests.m in Sources */,
343AAA7A1E8346B400F9D36E /* OIDServiceDiscoveryTests.m in Sources */,
343AAA751E8346B400F9D36E /* OIDAuthStateTests.m in Sources */,
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# UNRELEASED
- Improved the state validation error messages to distinguish a missing `state` parameter from a mismatched one. The end session message now notes that a provider's `end_session_endpoint` may point at a legacy or SAML single-logout endpoint, which does not echo `state`. Addresses issue #956.
- Fixed `OIDExternalUserAgentIOSCustomBrowser` failing the flow with "Unable to open Safari." when `canOpenURL:` returns `NO` for a URL that the system can open, for example an https URL when Safari is restricted and another browser is the default. The flow now fails only if the URL really cannot be opened. Addresses issue #980.
- Added `OIDExternalUserAgentIOSCustomBrowserURLOpener` and `-[OIDExternalUserAgentIOSCustomBrowser initWithURLTransformation:canOpenURLScheme:appStoreURL:URLOpener:]` so an app can supply the object that opens URLs instead of `UIApplication`. The existing initializers keep using `UIApplication`.
- Fixed a crash when the user cancels sign-in on iOS, caused by `ASWebAuthenticationSession` being deallocated during its completion handler or dismiss animation. Based on [#917](https://github.com/openid/AppAuth-iOS/pull/917) by @Prash88.

# 3.0.0
Expand Down
41 changes: 41 additions & 0 deletions Sources/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ NS_ASSUME_NONNULL_BEGIN
*/
typedef NSURL *_Nullable (^OIDCustomBrowserURLTransformation)(NSURL *_Nullable requestURL);

/*! @brief Opens URLs on behalf of @c OIDExternalUserAgentIOSCustomBrowser.
@discussion The default opener forwards to @c UIApplication. Supply your own to open URLs
where @c UIApplication is unavailable, or to substitute the opener in tests.
*/
@protocol OIDExternalUserAgentIOSCustomBrowserURLOpener <NSObject>

/*! @brief Whether @c URL can be opened. Mirrors @c -[UIApplication canOpenURL:] and is subject
to the same @c LSApplicationQueriesSchemes restrictions.
*/
- (BOOL)canOpenURL:(NSURL *)URL;

/*! @brief Opens @c URL and reports the outcome on the main queue. Mirrors
@c -[UIApplication openURL:options:completionHandler:].
@param URL The URL to open.
@param completionHandler Called with YES if the URL was opened, NO otherwise. May be nil.
*/
- (void)openURL:(NSURL *)URL completionHandler:(nullable void (^)(BOOL success))completionHandler;

@end

/*! @brief An implementation of the OIDExternalUserAgent protocol for iOS that uses
a custom browser (i.e. not Safari) for external requests. It is suitable for browsers that
offer a custom url scheme that simply replaces the "https" scheme. It is not designed
Expand All @@ -54,6 +74,10 @@ API_UNAVAILABLE(macCatalyst)
*/
@property(nonatomic, readonly, nullable) NSURL *appStoreURL;

/*! @brief The object used to open URLs. Defaults to an opener backed by @c UIApplication.
*/
@property(nonatomic, readonly) id<OIDExternalUserAgentIOSCustomBrowserURLOpener> URLOpener;

/*! @brief An instance of @c OIDExternalUserAgentIOSCustomBrowser for Chrome.
*/
+ (instancetype)CustomBrowserChrome;
Expand Down Expand Up @@ -92,6 +116,19 @@ API_UNAVAILABLE(macCatalyst)
*/
- (nullable instancetype)initWithURLTransformation:(OIDCustomBrowserURLTransformation)URLTransformation;

/*! @brief Convenience initializer. Uses an opener backed by @c UIApplication.
@param URLTransformation the transformation block to translate the URL into one that will open
in the desired custom browser.
@param canOpenURLScheme any scheme supported by the browser used to check if the browser is
installed.
@param appStoreURL URL of the browser in the app store. When this and @c canOpenURLScheme
are non-nil, @c presentExternalUserAgentRequest:session will redirect the user to the app store
if the browser is not installed.
*/
- (nullable instancetype)initWithURLTransformation:(OIDCustomBrowserURLTransformation)URLTransformation
canOpenURLScheme:(nullable NSString *)canOpenURLScheme
appStoreURL:(nullable NSURL *)appStoreURL;

/*! @brief The designated initializer.
@param URLTransformation the transformation block to translate the URL into one that will open
in the desired custom browser.
Expand All @@ -100,10 +137,14 @@ API_UNAVAILABLE(macCatalyst)
@param appStoreURL URL of the browser in the app store. When this and @c canOpenURLScheme
are non-nil, @c presentExternalUserAgentRequest:session will redirect the user to the app store
if the browser is not installed.
@param URLOpener the object used to open URLs. Pass nil to use an opener backed by
@c UIApplication.
*/
- (nullable instancetype)initWithURLTransformation:(OIDCustomBrowserURLTransformation)URLTransformation
canOpenURLScheme:(nullable NSString *)canOpenURLScheme
appStoreURL:(nullable NSURL *)appStoreURL
URLOpener:
(nullable id<OIDExternalUserAgentIOSCustomBrowserURLOpener>)URLOpener
NS_DESIGNATED_INITIALIZER;

@end
Expand Down
70 changes: 51 additions & 19 deletions Sources/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,32 @@

NS_ASSUME_NONNULL_BEGIN

/*! @brief The default @c OIDExternalUserAgentIOSCustomBrowserURLOpener, backed by
@c UIApplication.
*/
@interface OIDExternalUserAgentIOSCustomBrowserApplicationURLOpener
: NSObject <OIDExternalUserAgentIOSCustomBrowserURLOpener>
@end

@implementation OIDExternalUserAgentIOSCustomBrowserApplicationURLOpener

- (BOOL)canOpenURL:(NSURL *)URL {
// canOpenURL: answers NO for any scheme missing from LSApplicationQueriesSchemes, which would
// send every user to the App Store. Asserting here catches the missing entry in development.
NSArray<NSString *> __unused *canOpenURLs =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"LSApplicationQueriesSchemes"];
NSAssert(canOpenURLs, @"plist missing LSApplicationQueriesSchemes key");
NSAssert1([canOpenURLs containsObject:URL.scheme],
@"plist missing LSApplicationQueriesSchemes entry for '%@'", URL.scheme);
return [[UIApplication sharedApplication] canOpenURL:URL];
}

- (void)openURL:(NSURL *)URL completionHandler:(nullable void (^)(BOOL))completionHandler {
[[UIApplication sharedApplication] openURL:URL options:@{} completionHandler:completionHandler];
}

@end

@implementation OIDExternalUserAgentIOSCustomBrowser

+ (instancetype)CustomBrowserChrome {
Expand Down Expand Up @@ -122,11 +148,25 @@ - (nullable instancetype)initWithURLTransformation:
initWithURLTransformation:(OIDCustomBrowserURLTransformation)URLTransformation
canOpenURLScheme:(nullable NSString *)canOpenURLScheme
appStoreURL:(nullable NSURL *)appStoreURL {
return [self initWithURLTransformation:URLTransformation
canOpenURLScheme:canOpenURLScheme
appStoreURL:appStoreURL
URLOpener:nil];
}

- (nullable instancetype)
initWithURLTransformation:(OIDCustomBrowserURLTransformation)URLTransformation
canOpenURLScheme:(nullable NSString *)canOpenURLScheme
appStoreURL:(nullable NSURL *)appStoreURL
URLOpener:
(nullable id<OIDExternalUserAgentIOSCustomBrowserURLOpener>)URLOpener {
self = [super init];
if (self) {
_URLTransformation = URLTransformation;
_canOpenURLScheme = canOpenURLScheme;
_appStoreURL = appStoreURL;
_URLOpener =
URLOpener ?: [[OIDExternalUserAgentIOSCustomBrowserApplicationURLOpener alloc] init];
}
return self;
}
Expand All @@ -135,18 +175,11 @@ - (BOOL)presentExternalUserAgentRequest:(nonnull id<OIDExternalUserAgentRequest>
session:(nonnull id<OIDExternalUserAgentSession>)session {
// If the app store URL is set, checks if the app is installed and if not opens the app store.
if (_appStoreURL && _canOpenURLScheme) {
// Verifies existence of LSApplicationQueriesSchemes Info.plist key.
NSArray __unused* canOpenURLs =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"LSApplicationQueriesSchemes"];
NSAssert(canOpenURLs, @"plist missing LSApplicationQueriesSchemes key");
NSAssert1([canOpenURLs containsObject:_canOpenURLScheme],
@"plist missing LSApplicationQueriesSchemes entry for '%@'", _canOpenURLScheme);

// Opens AppStore if app isn't installed
NSString *testURLString = [NSString stringWithFormat:@"%@://example.com", _canOpenURLScheme];
NSURL *testURL = [NSURL URLWithString:testURLString];
if (![[UIApplication sharedApplication] canOpenURL:testURL]) {
[[UIApplication sharedApplication] openURL:_appStoreURL options:@{} completionHandler:nil];
if (![_URLOpener canOpenURL:testURL]) {
[_URLOpener openURL:_appStoreURL completionHandler:nil];
Comment thread
w-goog marked this conversation as resolved.
Resolved
return NO;
}
}
Expand All @@ -161,16 +194,15 @@ - (BOOL)presentExternalUserAgentRequest:(nonnull id<OIDExternalUserAgentRequest>
// LSApplicationQueriesSchemes and can return NO for a URL that
// -openURL:options:completionHandler: opens successfully (e.g. an https URL when Safari is
// unavailable and another browser is the default). The flow fails only if the open really fails.
[[UIApplication sharedApplication] openURL:requestURL
options:@{}
completionHandler:^(BOOL success) {
if (!success) {
NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError
underlyingError:nil
description:@"Unable to open Safari."];
[session failExternalUserAgentFlowWithError:safariError];
}
}];
[_URLOpener openURL:requestURL
completionHandler:^(BOOL success) {
if (!success) {
NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError
underlyingError:nil
description:@"Unable to open Safari."];
[session failExternalUserAgentFlowWithError:safariError];
}
}];
return YES;
}

Expand Down
Loading
Loading