Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom browser support #201

Merged
merged 1 commit into from
Feb 10, 2018
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
10 changes: 10 additions & 0 deletions AppAuth.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,9 @@
343AAB9B1E834A8800F9D36E /* AppAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = 343AAA4D1E8345B600F9D36E /* AppAuth.h */; settings = {ATTRIBUTES = (Public, ); }; };
343AAB9C1E834A8900F9D36E /* AppAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = 343AAA4D1E8345B600F9D36E /* AppAuth.h */; settings = {ATTRIBUTES = (Public, ); }; };
343AAB9D1E834A8A00F9D36E /* AppAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = 343AAA4D1E8345B600F9D36E /* AppAuth.h */; settings = {ATTRIBUTES = (Public, ); }; };
345AE747202D526900738D22 /* OIDAuthorizationUICoordinatorCustomBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = 345AE745202D526800738D22 /* OIDAuthorizationUICoordinatorCustomBrowser.m */; };
345AE748202D526900738D22 /* OIDAuthorizationUICoordinatorCustomBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = 345AE745202D526800738D22 /* OIDAuthorizationUICoordinatorCustomBrowser.m */; };
345AE749202D526900738D22 /* OIDAuthorizationUICoordinatorCustomBrowser.h in Headers */ = {isa = PBXBuildFile; fileRef = 345AE746202D526800738D22 /* OIDAuthorizationUICoordinatorCustomBrowser.h */; settings = {ATTRIBUTES = (Public, ); }; };
347423E41E7F3C4000D3E6D6 /* OIDAuthorizationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B71C5D8243000EF209 /* OIDAuthorizationResponse.m */; };
347423FF1E7F4BA000D3E6D6 /* OIDAuthorizationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B51C5D8243000EF209 /* OIDAuthorizationRequest.m */; };
347424001E7F4BA000D3E6D6 /* OIDAuthorizationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 341741B71C5D8243000EF209 /* OIDAuthorizationResponse.m */; };
Expand Down Expand Up @@ -542,6 +545,8 @@
343AAAAE1E83489A00F9D36E /* AppAuth_tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppAuth_tvOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
343AAAC21E8348A900F9D36E /* AppAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; };
343AAACA1E8348AA00F9D36E /* AppAuth_macOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppAuth_macOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
345AE745202D526800738D22 /* OIDAuthorizationUICoordinatorCustomBrowser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OIDAuthorizationUICoordinatorCustomBrowser.m; path = iOS/OIDAuthorizationUICoordinatorCustomBrowser.m; sourceTree = "<group>"; };
345AE746202D526800738D22 /* OIDAuthorizationUICoordinatorCustomBrowser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OIDAuthorizationUICoordinatorCustomBrowser.h; path = iOS/OIDAuthorizationUICoordinatorCustomBrowser.h; sourceTree = "<group>"; };
347423F61E7F4B5600D3E6D6 /* libAppAuth-watchOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libAppAuth-watchOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
34D5EC431E6D1AD900814354 /* OIDAppAuthTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OIDAppAuthTests-Bridging-Header.h"; sourceTree = "<group>"; };
34D5EC441E6D1AD900814354 /* OIDSwiftTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OIDSwiftTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -847,6 +852,8 @@
children = (
F6F60FB31D2BFEFE00325CB3 /* OIDAuthorizationService+IOS.h */,
F6F60FB11D2BFEFE00325CB3 /* OIDAuthorizationService+IOS.m */,
345AE746202D526800738D22 /* OIDAuthorizationUICoordinatorCustomBrowser.h */,
345AE745202D526800738D22 /* OIDAuthorizationUICoordinatorCustomBrowser.m */,
F6F60FB51D2BFEFE00325CB3 /* OIDAuthState+IOS.h */,
F6F60FB01D2BFEFE00325CB3 /* OIDAuthState+IOS.m */,
F6F60FB41D2BFEFE00325CB3 /* OIDAuthorizationUICoordinatorIOS.h */,
Expand Down Expand Up @@ -874,6 +881,7 @@
343AAAE41E83499000F9D36E /* OIDAuthorizationResponse.h in Headers */,
343AAAF31E83499000F9D36E /* OIDScopes.h in Headers */,
343AAAE81E83499000F9D36E /* OIDAuthStateChangeDelegate.h in Headers */,
345AE749202D526900738D22 /* OIDAuthorizationUICoordinatorCustomBrowser.h in Headers */,
343AAA6B1E83465500F9D36E /* AppAuth.h in Headers */,
343AAA6E1E83466B00F9D36E /* OIDAuthorizationUICoordinatorIOS.h in Headers */,
343AAAF21E83499000F9D36E /* OIDResponseTypes.h in Headers */,
Expand Down Expand Up @@ -1457,6 +1465,7 @@
60140F7A1DE4276800DA0DC3 /* OIDClientMetadataParameters.m in Sources */,
341741DE1C5D8243000EF209 /* OIDAuthState.m in Sources */,
341741DD1C5D8243000EF209 /* OIDAuthorizationService.m in Sources */,
345AE747202D526900738D22 /* OIDAuthorizationUICoordinatorCustomBrowser.m in Sources */,
340DAECD1D582DE100EC285B /* OIDAuthorizationUICoordinatorIOS.m in Sources */,
341741EB1C5D8243000EF209 /* OIDURLQueryComponent.m in Sources */,
341741E11C5D8243000EF209 /* OIDFieldMapping.m in Sources */,
Expand Down Expand Up @@ -1579,6 +1588,7 @@
343AAA931E83478900F9D36E /* OIDTokenUtilities.m in Sources */,
343AAA901E83478900F9D36E /* OIDServiceDiscovery.m in Sources */,
343AAA911E83478900F9D36E /* OIDTokenRequest.m in Sources */,
345AE748202D526900738D22 /* OIDAuthorizationUICoordinatorCustomBrowser.m in Sources */,
343AAA6F1E83467D00F9D36E /* OIDAuthorizationService+IOS.m in Sources */,
343AAA8F1E83478900F9D36E /* OIDServiceConfiguration.m in Sources */,
343AAA891E83478900F9D36E /* OIDRegistrationResponse.m in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Source/AppAuth.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#elif TARGET_OS_IOS
#import "OIDAuthState+IOS.h"
#import "OIDAuthorizationService+IOS.h"
#import "OIDAuthorizationUICoordinatorCustomBrowser.h"
#import "OIDAuthorizationUICoordinatorIOS.h"
#elif TARGET_OS_MAC
#import "OIDAuthState+Mac.h"
Expand Down
1 change: 1 addition & 0 deletions Source/Framework/AppAuth.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ FOUNDATION_EXPORT const unsigned char AppAuthVersionString[];
#elif TARGET_OS_IOS
#import <AppAuth/OIDAuthState+IOS.h>
#import <AppAuth/OIDAuthorizationService+IOS.h>
#import <AppAuth/OIDAuthorizationUICoordinatorCustomBrowser.h>
#import <AppAuth/OIDAuthorizationUICoordinatorIOS.h>
#elif TARGET_OS_MAC
#import <AppAuth/OIDAuthState+Mac.h>
Expand Down
6 changes: 6 additions & 0 deletions Source/OIDURLQueryComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ extern BOOL gOIDURLQueryComponentForceIOS7Handling;
*/
- (NSString *)URLEncodedParameters;

/*! @brief A NSMutableCharacterSet containing allowed characters in URL parameter values (that is
the "value" part of "?key=value"). This has less allowed characters than
@c URLQueryAllowedCharacterSet, as the query component includes both the key & value.
*/
+ (NSMutableCharacterSet *)URLParamValueAllowedCharacters;

@end

NS_ASSUME_NONNULL_END
14 changes: 10 additions & 4 deletions Source/OIDURLQueryComponent.m
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ - (void)addParameters:(NSDictionary<NSString *, NSString *> *)parameters {
return queryParameters;
}

+ (NSMutableCharacterSet *)URLParamValueAllowedCharacters {
// Starts with the standard URL-allowed character set.
NSMutableCharacterSet *allowedParamCharacters =
[[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
// Removes additional characters we don't want to see in the query component.
[allowedParamCharacters removeCharactersInString:kQueryStringParamAdditionalDisallowedCharacters];
return allowedParamCharacters;
}

/*! @brief Builds a query string that can be set to @c NSURLComponents.percentEncodedQuery
@discussion This string is percent encoded, and shouldn't be used with
@c NSURLComponents.query.
Expand All @@ -133,10 +142,7 @@ - (NSString *)percentEncodedQueryString {
NSMutableArray<NSString *> *parameterizedValues = [NSMutableArray array];

// Starts with the standard URL-allowed character set.
NSMutableCharacterSet *allowedParamCharacters =
[[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
// Removes additional characters we don't want to see in the query component.
[allowedParamCharacters removeCharactersInString:kQueryStringParamAdditionalDisallowedCharacters];
NSMutableCharacterSet *allowedParamCharacters = [[self class] URLParamValueAllowedCharacters];

for (NSString *parameterName in _parameters.allKeys) {
NSString *encodedParameterName =
Expand Down
106 changes: 106 additions & 0 deletions Source/iOS/OIDAuthorizationUICoordinatorCustomBrowser.h
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>

/*! @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 An instance of @c OIDAuthorizationUICoordinatorCustomBrowser for Chrome.
*/
+ (instancetype)CustomBrowserChrome;

/*! @brief An instance of @c OIDAuthorizationUICoordinatorCustomBrowser for Firefox.
*/
+ (instancetype)CustomBrowserFirefox;

/*! @brief An instance of @c OIDAuthorizationUICoordinatorCustomBrowser for Opera.
*/
+ (instancetype)CustomBrowserOpera;

/*! @brief An instance of @c OIDAuthorizationUICoordinatorCustomBrowser for Safari.
*/
+ (instancetype)CustomBrowserSafari;

/*! @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 Creates a @c OIDCustomBrowserURLTransformation with the URL prefix method used by
iOS browsers like Firefox.
*/
+ (OIDCustomBrowserURLTransformation) URLTransformationSchemeConcatPrefix:(NSString*)URLprefix;

/*! @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;

@end

NS_ASSUME_NONNULL_END
168 changes: 168 additions & 0 deletions Source/iOS/OIDAuthorizationUICoordinatorCustomBrowser.m
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;

+ (instancetype)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];
}

+ (instancetype)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];
}

+ (instancetype)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];
}

+ (instancetype)CustomBrowserSafari {
OIDCustomBrowserURLTransformation transformNOP = ^NSURL *(NSURL *requestURL) {
return requestURL;
};
OIDAuthorizationUICoordinatorCustomBrowser *coordinator =
[[[self class] alloc] initWithURLTransformation:transformNOP];
return coordinator;
}

+ (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;
}

- (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 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];
return NO;
}
}

// Transforms the request URL and opens it.
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