Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save mattio/f2bf65e2cfbe385774a9bc49086366ba to your computer and use it in GitHub Desktop.
Save mattio/f2bf65e2cfbe385774a9bc49086366ba to your computer and use it in GitHub Desktop.
// Original source from AppAuth: https://gist.github.com/WilliamDenniss/18f3779b4a310361bb955cf4e534f29c
// NOTE: If you use this for LOGIN, you probably want to use it for LOGOUT, too.
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <SafariServices/SafariServices.h>
#import "OIDExternalUserAgent.h"
NS_ASSUME_NONNULL_BEGIN
/*! @brief Allows library consumers to bootstrap an @c SFSafariViewController as they see fit.
@remarks Useful for customizing tint colors and presentation styles.
*/
@protocol OIDSafariViewControllerFactoryExternal
/*! @brief Creates and returns a new @c SFSafariViewController.
@param URL The URL which the @c SFSafariViewController should load initially.
*/
- (SFSafariViewController *)safariViewControllerWithURL:(NSURL *)URL;
@end
/*! @brief A special-case iOS external user-agent that always uses \SFSafariViewController. Most applications should use the more generic @c OIDExternalUserAgentIOS to get the default AppAuth user-agent handling with the benefits of Single Sign-on (SSO) for all supported versions of iOS.
*/
@interface OIDExternalUserAgentIOSSafariViewController : NSObject<OIDExternalUserAgent>
/*! @brief Allows library consumers to change the @c OIDSafariViewControllerFactory used to create new instances of @c SFSafariViewController.
@remarks Useful for customizing tint colors and presentation styles.
@param factory The @c OIDSafariViewControllerFactory to use for creating new instances of @c SFSafariViewController.
*/
+ (void)setSafariViewControllerFactory:(id<OIDSafariViewControllerFactoryExternal>)factory;
/*! @internal
@brief Unavailable. Please use @c initWithPresentingViewController:
*/
- (nonnull instancetype)init NS_UNAVAILABLE;
/*! @brief The designated initializer.
@param presentingViewController The view controller from which to present the \SFSafariViewController.
*/
- (nullable instancetype)initWithPresentingViewController:(UIViewController *)presentingViewController NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END
// Original source from AppAuth: https://gist.github.com/WilliamDenniss/18f3779b4a310361bb955cf4e534f29c
// NOTE: If you use this for LOGIN, you probably want to use it for LOGOUT, too.
#import "OIDExternalUserAgentIOSSafariViewController.h"
#import "OIDErrorUtilities.h"
#import "OIDExternalUserAgentSession.h"
#import "OIDExternalUserAgentRequest.h"
NS_ASSUME_NONNULL_BEGIN
/** @brief The global/shared Safari view controller factory. Responsible for creating all new instances of @c SFSafariViewController.
*/
static id<OIDSafariViewControllerFactoryExternal> __nullable gSafariViewControllerFactoryExternal;
/** @brief The default @c OIDSafariViewControllerFactory which creates new instances of @c SFSafariViewController using known best practices.
*/
@interface MSKDefaultSafariViewControllerFactory : NSObject<OIDSafariViewControllerFactoryExternal>
@end
@interface OIDExternalUserAgentIOSSafariViewController() <SFSafariViewControllerDelegate>
@end
@implementation OIDExternalUserAgentIOSSafariViewController {
UIViewController *_presentingViewController;
BOOL _externalUserAgentFlowInProgress;
__weak id<OIDExternalUserAgentSession> _session;
__weak SFSafariViewController *_safariVC;
}
/** @brief Obtains the current @c OIDSafariViewControllerFactory; creating a new default instance if required.
*/
+ (id<OIDSafariViewControllerFactoryExternal>)safariViewControllerFactory {
if (!gSafariViewControllerFactoryExternal) {
gSafariViewControllerFactoryExternal = [[MSKDefaultSafariViewControllerFactory alloc] init];
}
return gSafariViewControllerFactoryExternal;
}
+ (void)setSafariViewControllerFactory:(id<OIDSafariViewControllerFactoryExternal>)factory {
NSAssert(factory, @"Parameter: |factory| must be non-nil.");
gSafariViewControllerFactoryExternal = factory;
}
- (nullable instancetype)initWithPresentingViewController: (UIViewController *)presentingViewController {
self = [super init];
if (self) {
_presentingViewController = presentingViewController;
}
return self;
}
- (BOOL)presentExternalUserAgentRequest:(id<OIDExternalUserAgentRequest>)request
session:(id<OIDExternalUserAgentSession>)session {
if (_externalUserAgentFlowInProgress) {
// TODO: Handle errors as authorization is already in progress.
return NO;
}
_externalUserAgentFlowInProgress = YES;
_session = session;
BOOL openedSafari = NO;
NSURL *requestURL = [request externalUserAgentRequestURL];
SFSafariViewController *safariVC = [[[self class] safariViewControllerFactory] safariViewControllerWithURL:requestURL];
safariVC.delegate = self;
_safariVC = safariVC;
[_presentingViewController presentViewController:safariVC animated:YES completion:nil];
openedSafari = YES;
if (!openedSafari) {
[self cleanUp];
NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError
underlyingError:nil
description:@"Unable to open Safari."];
[session failExternalUserAgentFlowWithError:safariError];
}
return openedSafari;
}
- (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(void))completion {
if (!_externalUserAgentFlowInProgress) {
// Ignore this call if there is no authorization flow in progress.
return;
}
SFSafariViewController *safariVC = _safariVC;
[self cleanUp];
if (safariVC) {
[safariVC dismissViewControllerAnimated:YES completion:completion];
} else {
if (completion) completion();
}
}
- (void)cleanUp {
// The weak references to |_safariVC| and |_session| are set to nil to avoid accidentally using them while not in an authorization flow.
_safariVC = nil;
_session = nil;
_externalUserAgentFlowInProgress = NO;
}
#pragma mark - SFSafariViewControllerDelegate
- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller {
if (controller != _safariVC) {
// Ignore this call if the safari view controller do not match.
return;
}
if (!_externalUserAgentFlowInProgress) {
// Ignore this call if there is no authorization flow in progress.
return;
}
id<OIDExternalUserAgentSession> session = _session;
[self cleanUp];
NSError *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeProgramCanceledAuthorizationFlow
underlyingError:nil
description:nil];
[session failExternalUserAgentFlowWithError:error];
}
@end
@implementation MSKDefaultSafariViewControllerFactory
- (SFSafariViewController *)safariViewControllerWithURL:(NSURL *)URL {
SFSafariViewController *safariViewController = [[SFSafariViewController alloc] initWithURL:URL
entersReaderIfAvailable:NO];
return safariViewController;
}
@end
NS_ASSUME_NONNULL_END
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment