-
Notifications
You must be signed in to change notification settings - Fork 773
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- An UICoordinator for iOS to support auth with a custom iOS browser like Chrome or Firefox. - Implements #200.
- Loading branch information
1 parent
8d6aabe
commit b0e4a30
Showing
7 changed files
with
302 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
Source/iOS/OIDAuthorizationUICoordinatorCustomBrowser.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/*! @file OIDAuthorizationUICoordinatorCustomBrowser.h | ||
@brief AppAuth iOS SDK | ||
@copyright | ||
Copyright 2018 Google LLC | ||
@copydetails | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
#import <Foundation/Foundation.h> | ||
|
||
#import "OIDAuthorizationUICoordinator.h" | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
/*! @brief A block that transforms a regular http/https URL into one that will open in an | ||
alternative browser. | ||
@param requestURL the http/https request URL to be transformed. | ||
@return transformed URL. | ||
*/ | ||
typedef NSURL *_Nullable (^OIDCustomBrowserURLTransformation)(NSURL *_Nullable requestURL); | ||
|
||
/*! @brief An implementation of the OIDAuthorizationUICoordinator protocol for iOS that uses | ||
a custom browser (i.e. not Safari) for authorization. It is suitable for browsers that | ||
offer a custom url scheme that simply replaces the "https" scheme. It is not designed | ||
for browsers that require other modifications to the URL. If the browser is not installed | ||
the user will be prompted to install it. | ||
*/ | ||
@interface OIDAuthorizationUICoordinatorCustomBrowser : NSObject<OIDAuthorizationUICoordinator> | ||
|
||
/*! @internal | ||
@brief Unavailable. Please use @c initWithURLTransformation:canOpenURLScheme:appStoreURL: | ||
*/ | ||
- (nonnull instancetype)init NS_UNAVAILABLE; | ||
|
||
/*! @brief UICoordinator for a custom browser. @c presentAuthorizationRequest:session method | ||
will return NO if the browser isn't installed. | ||
*/ | ||
- (nullable instancetype)initWithURLTransformation:(OIDCustomBrowserURLTransformation)URLTransformation; | ||
|
||
/*! @brief The designated initializer. | ||
@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 presentAuthorizationRequest: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 | ||
NS_DESIGNATED_INITIALIZER; | ||
|
||
/*! @brief URL transformation block for the browser. | ||
*/ | ||
@property(nonatomic, readonly) OIDCustomBrowserURLTransformation URLTransformation; | ||
|
||
/*! @brief URL Scheme used to test for whether the browser is installed. | ||
*/ | ||
@property(nonatomic, readonly, nullable) NSString *canOpenURLScheme; | ||
|
||
/*! @brief URL of the browser's App Store listing. | ||
*/ | ||
@property(nonatomic, readonly, nullable) NSURL *appStoreURL; | ||
|
||
/*! @brief Creates a @c OIDCustomBrowserURLTransformation with the URL prefix method used by | ||
iOS browsers like Firefox. | ||
*/ | ||
+ (OIDCustomBrowserURLTransformation) URLTransformationSchemeConcatPrefix:(NSString*)URLprefix; | ||
|
||
/*! @brief Creates a @c OIDCustomBrowserURLTransformation using the scheme substitution method used | ||
iOS browsers like Chrome and Firefox. | ||
*/ | ||
+ (OIDCustomBrowserURLTransformation) | ||
URLTransformationSchemeSubstitutionHTTPS:(NSString *)browserSchemeHTTPS | ||
HTTP:(nullable NSString *)browserSchemeHTTP; | ||
|
||
/*! @brief An instance of @c OIDAuthorizationUICoordinatorCustomBrowser for Chrome. | ||
*/ | ||
+ (OIDAuthorizationUICoordinatorCustomBrowser*)CustomBrowserChrome; | ||
|
||
/*! @brief An instance of @c OIDAuthorizationUICoordinatorCustomBrowser for Firefox. | ||
*/ | ||
+ (OIDAuthorizationUICoordinatorCustomBrowser*)CustomBrowserFirefox; | ||
|
||
/*! @brief An instance of @c OIDAuthorizationUICoordinatorCustomBrowser for Opera. | ||
*/ | ||
+ (OIDAuthorizationUICoordinatorCustomBrowser*)CustomBrowserOpera; | ||
|
||
/*! @brief An instance of @c OIDAuthorizationUICoordinatorCustomBrowser for Safari. | ||
*/ | ||
+ (OIDAuthorizationUICoordinatorCustomBrowser*)CustomBrowserSafari; | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END |
168 changes: 168 additions & 0 deletions
168
Source/iOS/OIDAuthorizationUICoordinatorCustomBrowser.m
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
/*! @file OIDAuthorizationUICoordinatorCustomBrowser.m | ||
@brief AppAuth iOS SDK | ||
@copyright | ||
Copyright 2018 Google LLC | ||
@copydetails | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
#import "OIDAuthorizationUICoordinatorCustomBrowser.h" | ||
|
||
#import <UIKit/UIKit.h> | ||
|
||
#import "OIDAuthorizationRequest.h" | ||
#import "OIDAuthorizationService.h" | ||
#import "OIDErrorUtilities.h" | ||
#import "OIDURLQueryComponent.h" | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
@implementation OIDAuthorizationUICoordinatorCustomBrowser { | ||
OIDCustomBrowserURLTransformation _URLTransformation; | ||
NSString *_canOpenURLScheme; | ||
NSURL *_appStoreURL; | ||
} | ||
|
||
@synthesize URLTransformation = _URLTransformation; | ||
@synthesize canOpenURLScheme = _canOpenURLScheme; | ||
@synthesize appStoreURL = _appStoreURL; | ||
|
||
+ (OIDCustomBrowserURLTransformation) | ||
URLTransformationSchemeSubstitutionHTTPS:(NSString *)browserSchemeHTTPS | ||
HTTP:(nullable NSString *)browserSchemeHTTP { | ||
OIDCustomBrowserURLTransformation transform = ^NSURL *(NSURL *requestURL) { | ||
// Replace the URL Scheme with the Chrome equivalent. | ||
NSString *newScheme = nil; | ||
if ([requestURL.scheme isEqualToString:@"https"]) { | ||
newScheme = browserSchemeHTTPS; | ||
} else if ([requestURL.scheme isEqualToString:@"http"]) { | ||
if (!browserSchemeHTTP) { | ||
NSAssert(false, @"No HTTP scheme registered for browser"); | ||
return nil; | ||
} | ||
newScheme = browserSchemeHTTP; | ||
} | ||
|
||
// Replaces the URI scheme with the custom scheme | ||
NSURLComponents *components = [NSURLComponents componentsWithURL:requestURL | ||
resolvingAgainstBaseURL:YES]; | ||
components.scheme = newScheme; | ||
return components.URL; | ||
}; | ||
return transform; | ||
} | ||
|
||
+ (OIDCustomBrowserURLTransformation)URLTransformationSchemeConcatPrefix:(NSString *)URLprefix { | ||
OIDCustomBrowserURLTransformation transform = ^NSURL *(NSURL *requestURL) { | ||
NSString *requestURLString = [requestURL absoluteString]; | ||
NSMutableCharacterSet *allowedParamCharacters = | ||
[OIDURLQueryComponent URLParamValueAllowedCharacters]; | ||
NSString *encodedUrl = [requestURLString stringByAddingPercentEncodingWithAllowedCharacters:allowedParamCharacters]; | ||
NSString *newURL = [NSString stringWithFormat:@"%@%@", URLprefix, encodedUrl]; | ||
return [NSURL URLWithString:newURL]; | ||
}; | ||
return transform; | ||
} | ||
|
||
+ (OIDAuthorizationUICoordinatorCustomBrowser *)CustomBrowserChrome { | ||
// Chrome iOS documentation: https://developer.chrome.com/multidevice/ios/links | ||
OIDCustomBrowserURLTransformation transform = [[self class] URLTransformationSchemeSubstitutionHTTPS:@"googlechromes" HTTP:@"googlechrome"]; | ||
NSURL *appStoreURL = | ||
[NSURL URLWithString:@"itms-apps://itunes.apple.com/us/app/chrome/id535886823"]; | ||
return [[[self class] alloc] initWithURLTransformation:transform | ||
canOpenURLScheme:@"googlechromes" | ||
appStoreURL:appStoreURL]; | ||
} | ||
|
||
+ (OIDAuthorizationUICoordinatorCustomBrowser *)CustomBrowserFirefox { | ||
// Firefox iOS documentation: https://github.com/mozilla-mobile/firefox-ios-open-in-client | ||
OIDCustomBrowserURLTransformation transform = | ||
[[self class] URLTransformationSchemeConcatPrefix:@"firefox://open-url?url="]; | ||
NSURL *appStoreURL = | ||
[NSURL URLWithString:@"itms-apps://itunes.apple.com/us/app/firefox-web-browser/id989804926"]; | ||
return [[[self class] alloc] initWithURLTransformation:transform | ||
canOpenURLScheme:@"firefox" | ||
appStoreURL:appStoreURL]; | ||
} | ||
|
||
+ (OIDAuthorizationUICoordinatorCustomBrowser *)CustomBrowserOpera { | ||
OIDCustomBrowserURLTransformation transform = | ||
[[self class] URLTransformationSchemeSubstitutionHTTPS:@"opera-https" HTTP:@"opera-http"]; | ||
NSURL *appStoreURL = | ||
[NSURL URLWithString:@"itms-apps://itunes.apple.com/us/app/opera-mini-web-browser/id363729560"]; | ||
return [[[self class] alloc] initWithURLTransformation:transform | ||
canOpenURLScheme:@"opera-https" | ||
appStoreURL:appStoreURL]; | ||
} | ||
|
||
+ (OIDAuthorizationUICoordinatorCustomBrowser *)CustomBrowserSafari { | ||
OIDCustomBrowserURLTransformation transformNOP = ^NSURL *(NSURL *requestURL) { | ||
return requestURL; | ||
}; | ||
OIDAuthorizationUICoordinatorCustomBrowser *coordinator = | ||
[[[self class] alloc] initWithURLTransformation:transformNOP]; | ||
return coordinator; | ||
} | ||
|
||
- (nullable instancetype)initWithURLTransformation: | ||
(OIDCustomBrowserURLTransformation)URLTransformation { | ||
return [self initWithURLTransformation:URLTransformation canOpenURLScheme:nil appStoreURL:nil]; | ||
} | ||
|
||
- (nullable instancetype) | ||
initWithURLTransformation:(OIDCustomBrowserURLTransformation)URLTransformation | ||
canOpenURLScheme:(nullable NSString *)canOpenURLScheme | ||
appStoreURL:(nullable NSURL *)appStoreURL { | ||
self = [super init]; | ||
if (self) { | ||
_URLTransformation = URLTransformation; | ||
_canOpenURLScheme = canOpenURLScheme; | ||
_appStoreURL = appStoreURL; | ||
} | ||
return self; | ||
} | ||
|
||
- (BOOL)presentAuthorizationRequest:(OIDAuthorizationRequest *)request | ||
session:(id<OIDAuthorizationFlowSession>)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 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]; | ||
return NO; | ||
} | ||
} | ||
|
||
NSURL *requestURL = [request authorizationRequestURL]; | ||
requestURL = _URLTransformation(requestURL); | ||
BOOL openedInBrowser = [[UIApplication sharedApplication] openURL:requestURL]; | ||
return openedInBrowser; | ||
} | ||
|
||
- (void)dismissAuthorizationAnimated:(BOOL)animated completion:(nonnull void (^)(void))completion { | ||
completion(); | ||
} | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END |