From 049049c8f856fd550376fe7c931cbe1d8713195f Mon Sep 17 00:00:00 2001 From: Cheng Date: Wed, 25 Sep 2024 13:08:12 -0400 Subject: [PATCH] appauth: support SFSafariController --- flutter_appauth/example/lib/main.dart | 32 +++- .../ios/Classes/AppAuthIOSAuthorization.h | 1 + .../ios/Classes/AppAuthIOSAuthorization.m | 20 +- flutter_appauth/ios/Classes/FlutterAppAuth.h | 4 +- flutter_appauth/ios/Classes/FlutterAppAuth.m | 2 +- .../ios/Classes/FlutterAppauthPlugin.m | 12 +- ...ExternalUserAgentIOSSafariViewController.h | 68 +++++++ ...ExternalUserAgentIOSSafariViewController.m | 173 ++++++++++++++++++ flutter_appauth/lib/flutter_appauth.dart | 1 + .../macos/Classes/AppAuthMacOSAuthorization.m | 12 +- 10 files changed, 293 insertions(+), 32 deletions(-) create mode 100644 flutter_appauth/ios/Classes/OIDExternalUserAgentIOSSafariViewController.h create mode 100644 flutter_appauth/ios/Classes/OIDExternalUserAgentIOSSafariViewController.m diff --git a/flutter_appauth/example/lib/main.dart b/flutter_appauth/example/lib/main.dart index f9765dc7..2b1e6efd 100644 --- a/flutter_appauth/example/lib/main.dart +++ b/flutter_appauth/example/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io' show Platform; import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_appauth/flutter_appauth.dart'; @@ -107,7 +108,22 @@ class _MyAppState extends State { textAlign: TextAlign.center, ), onPressed: () => _signInWithAutoCodeExchange( - preferEphemeralSession: true), + preferredExternalAgent: ExternalAgentType + .ephemeralAsWebAuthenticationSession), + ), + ), + if (Platform.isIOS) + Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + child: const Text( + 'Sign in with auto code exchange using ' + 'SFSafariViewController', + textAlign: TextAlign.center, + ), + onPressed: () => _signInWithAutoCodeExchange( + preferredExternalAgent: + ExternalAgentType.sfSafariViewController), ), ), ElevatedButton( @@ -285,7 +301,8 @@ class _MyAppState extends State { } Future _signInWithAutoCodeExchange( - {bool preferEphemeralSession = false}) async { + {ExternalAgentType preferredExternalAgent = + ExternalAgentType.asWebAuthenticationSession}) async { try { _setBusyState(); @@ -295,13 +312,10 @@ class _MyAppState extends State { */ final AuthorizationTokenResponse result = await _appAuth.authorizeAndExchangeCode( - AuthorizationTokenRequest( - _clientId, - _redirectUrl, - serviceConfiguration: _serviceConfiguration, - scopes: _scopes, - preferEphemeralSession: preferEphemeralSession, - ), + AuthorizationTokenRequest(_clientId, _redirectUrl, + serviceConfiguration: _serviceConfiguration, + scopes: _scopes, + preferredExternalAgent: preferredExternalAgent), ); /* diff --git a/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.h b/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.h index d41a9579..9c80bc5d 100644 --- a/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.h +++ b/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.h @@ -1,6 +1,7 @@ #import #import #import "OIDExternalUserAgentIOSNoSSO.h" +#import "OIDExternalUserAgentIOSSafariViewController.h" #import "FlutterAppAuth.h" NS_ASSUME_NONNULL_BEGIN diff --git a/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.m b/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.m index 47d8b755..187b4a7f 100644 --- a/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.m +++ b/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.m @@ -2,7 +2,7 @@ @implementation AppAuthIOSAuthorization -- (id) performAuthorization:(OIDServiceConfiguration *)serviceConfiguration clientId:(NSString*)clientId clientSecret:(NSString*)clientSecret scopes:(NSArray *)scopes redirectUrl:(NSString*)redirectUrl additionalParameters:(NSDictionary *)additionalParameters preferEphemeralSession:(BOOL)preferEphemeralSession result:(FlutterResult)result exchangeCode:(BOOL)exchangeCode nonce:(NSString*)nonce{ +- (id) performAuthorization:(OIDServiceConfiguration *)serviceConfiguration clientId:(NSString*)clientId clientSecret:(NSString*)clientSecret scopes:(NSArray *)scopes redirectUrl:(NSString*)redirectUrl additionalParameters:(NSDictionary *)additionalParameters preferredExternalAgent:(NSString*)preferredExternalAgent result:(FlutterResult)result exchangeCode:(BOOL)exchangeCode nonce:(NSString*)nonce{ NSString *codeVerifier = [OIDAuthorizationRequest generateCodeVerifier]; NSString *codeChallenge = [OIDAuthorizationRequest codeChallengeS256ForVerifier:codeVerifier]; @@ -21,7 +21,7 @@ @implementation AppAuthIOSAuthorization additionalParameters:additionalParameters]; UIViewController *rootViewController = [self rootViewController]; if(exchangeCode) { - id externalUserAgent = [self userAgentWithViewController:rootViewController useEphemeralSession:preferEphemeralSession]; + id externalUserAgent = [self userAgentWithViewController:rootViewController preferredExternalAgent:preferredExternalAgent]; return [OIDAuthState authStateByPresentingAuthorizationRequest:request externalUserAgent:externalUserAgent callback:^(OIDAuthState *_Nullable authState, NSError *_Nullable error) { if(authState) { @@ -32,7 +32,7 @@ @implementation AppAuthIOSAuthorization } }]; } else { - id externalUserAgent = [self userAgentWithViewController:rootViewController useEphemeralSession:preferEphemeralSession]; + id externalUserAgent = [self userAgentWithViewController:rootViewController preferredExternalAgent:preferredExternalAgent]; return [OIDAuthorizationService presentAuthorizationRequest:request externalUserAgent:externalUserAgent callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse, NSError *_Nullable error) { if(authorizationResponse) { NSMutableDictionary *processedResponse = [[NSMutableDictionary alloc] init]; @@ -56,7 +56,7 @@ @implementation AppAuthIOSAuthorization additionalParameters:requestParameters.additionalParameters]; UIViewController *rootViewController = [self rootViewController]; - id externalUserAgent = [self userAgentWithViewController:rootViewController useEphemeralSession:requestParameters.preferEphemeralSession]; + id externalUserAgent = [self userAgentWithViewController:rootViewController preferredExternalAgent:requestParameters.preferredExternalAgent]; return [OIDAuthorizationService presentEndSessionRequest:endSessionRequest externalUserAgent:externalUserAgent callback:^(OIDEndSessionResponse * _Nullable endSessionResponse, NSError * _Nullable error) { @@ -71,13 +71,17 @@ @implementation AppAuthIOSAuthorization }]; } -- (id)userAgentWithViewController:(UIViewController *)rootViewController useEphemeralSession:(BOOL)useEphemeralSession { - if (useEphemeralSession) { +- (id)userAgentWithViewController:(UIViewController *)rootViewController preferredExternalAgent:(NSString*)preferredExternalAgent { + if ([preferredExternalAgent isEqual:@"ExternalAgentType.ephemeralAsWebAuthenticationSession"]) { return [[OIDExternalUserAgentIOSNoSSO alloc] initWithPresentingViewController:rootViewController]; + } else if ([preferredExternalAgent isEqual:@"ExternalAgentType.sfSafariViewController"]) { + return [[OIDExternalUserAgentIOSSafariViewController alloc] + initWithPresentingViewController:rootViewController]; + } else { + return [[OIDExternalUserAgentIOS alloc] + initWithPresentingViewController:rootViewController]; } - return [[OIDExternalUserAgentIOS alloc] - initWithPresentingViewController:rootViewController]; } - (UIViewController *)rootViewController { diff --git a/flutter_appauth/ios/Classes/FlutterAppAuth.h b/flutter_appauth/ios/Classes/FlutterAppAuth.h index 7191dda4..78715c1b 100644 --- a/flutter_appauth/ios/Classes/FlutterAppAuth.h +++ b/flutter_appauth/ios/Classes/FlutterAppAuth.h @@ -39,12 +39,12 @@ static NSString *const END_SESSION_ERROR_MESSAGE_FORMAT = @"Failed to end sessio @property(nonatomic, strong) NSString *discoveryUrl; @property(nonatomic, strong) NSDictionary *serviceConfigurationParameters; @property(nonatomic, strong) NSDictionary *additionalParameters; -@property(nonatomic, readwrite) BOOL preferEphemeralSession; +@property(nonatomic, strong) NSString *preferredExternalAgent; @end @interface AppAuthAuthorization : NSObject -- (id)performAuthorization:(OIDServiceConfiguration *)serviceConfiguration clientId:(NSString*)clientId clientSecret:(NSString*)clientSecret scopes:(NSArray *)scopes redirectUrl:(NSString*)redirectUrl additionalParameters:(NSDictionary *)additionalParameters preferEphemeralSession:(BOOL)preferEphemeralSession result:(FlutterResult)result exchangeCode:(BOOL)exchangeCode nonce:(NSString*)nonce; +- (id)performAuthorization:(OIDServiceConfiguration *)serviceConfiguration clientId:(NSString*)clientId clientSecret:(NSString*)clientSecret scopes:(NSArray *)scopes redirectUrl:(NSString*)redirectUrl additionalParameters:(NSDictionary *)additionalParameters preferredExternalAgent:(NSString*)preferredExternalAgent result:(FlutterResult)result exchangeCode:(BOOL)exchangeCode nonce:(NSString*)nonce; - (id)performEndSessionRequest:(OIDServiceConfiguration *)serviceConfiguration requestParameters:(EndSessionRequestParameters *)requestParameters result:(FlutterResult)result; diff --git a/flutter_appauth/ios/Classes/FlutterAppAuth.m b/flutter_appauth/ios/Classes/FlutterAppAuth.m index 921ffbd3..94414c06 100644 --- a/flutter_appauth/ios/Classes/FlutterAppAuth.m +++ b/flutter_appauth/ios/Classes/FlutterAppAuth.m @@ -96,7 +96,7 @@ + (NSString *) formatMessageWithError:(NSString *)messageFormat error:(NSError * @implementation AppAuthAuthorization -- (id)performAuthorization:(OIDServiceConfiguration *)serviceConfiguration clientId:(NSString*)clientId clientSecret:(NSString*)clientSecret scopes:(NSArray *)scopes redirectUrl:(NSString*)redirectUrl additionalParameters:(NSDictionary *)additionalParameters preferEphemeralSession:(BOOL)preferEphemeralSession result:(FlutterResult)result exchangeCode:(BOOL)exchangeCode nonce:(NSString*)nonce { +- (id)performAuthorization:(OIDServiceConfiguration *)serviceConfiguration clientId:(NSString*)clientId clientSecret:(NSString*)clientSecret scopes:(NSArray *)scopes redirectUrl:(NSString*)redirectUrl additionalParameters:(NSDictionary *)additionalParameters preferredExternalAgent:(NSString*)preferredExternalAgent result:(FlutterResult)result exchangeCode:(BOOL)exchangeCode nonce:(NSString*)nonce { return nil; } diff --git a/flutter_appauth/ios/Classes/FlutterAppauthPlugin.m b/flutter_appauth/ios/Classes/FlutterAppauthPlugin.m index c78bc067..006f6f81 100644 --- a/flutter_appauth/ios/Classes/FlutterAppauthPlugin.m +++ b/flutter_appauth/ios/Classes/FlutterAppauthPlugin.m @@ -28,7 +28,7 @@ @interface TokenRequestParameters : NSObject @property(nonatomic, strong) NSArray *scopes; @property(nonatomic, strong) NSDictionary *serviceConfigurationParameters; @property(nonatomic, strong) NSDictionary *additionalParameters; -@property(nonatomic, readwrite) BOOL preferEphemeralSession; +@property(nonatomic, strong) NSString *preferredExternalAgent; @end @@ -47,7 +47,7 @@ - (void)processArguments:(NSDictionary *)arguments { _scopes = [ArgumentProcessor processArgumentValue:arguments withKey:@"scopes"]; _serviceConfigurationParameters = [ArgumentProcessor processArgumentValue:arguments withKey:@"serviceConfiguration"]; _additionalParameters = [ArgumentProcessor processArgumentValue:arguments withKey:@"additionalParameters"]; - _preferEphemeralSession = [[ArgumentProcessor processArgumentValue:arguments withKey:@"preferEphemeralSession"] isEqual:@YES]; + _preferredExternalAgent = [ArgumentProcessor processArgumentValue:arguments withKey:@"preferredExternalAgent"]; } - (id)initWithArguments:(NSDictionary *)arguments { @@ -82,7 +82,7 @@ - (id)initWithArguments:(NSDictionary *)arguments { _discoveryUrl = [ArgumentProcessor processArgumentValue:arguments withKey:@"discoveryUrl"]; _serviceConfigurationParameters = [ArgumentProcessor processArgumentValue:arguments withKey:@"serviceConfiguration"]; _additionalParameters = [ArgumentProcessor processArgumentValue:arguments withKey:@"additionalParameters"]; - _preferEphemeralSession = [[ArgumentProcessor processArgumentValue:arguments withKey:@"preferEphemeralSession"] isEqual:@YES]; + _preferredExternalAgent = [ArgumentProcessor processArgumentValue:arguments withKey:@"preferredExternalAgent"]; return self; } @end @@ -149,7 +149,7 @@ -(void)handleAuthorizeMethodCall:(NSDictionary*)arguments result:(FlutterResult) if(requestParameters.serviceConfigurationParameters != nil) { OIDServiceConfiguration *serviceConfiguration = [self processServiceConfigurationParameters:requestParameters.serviceConfigurationParameters]; - _currentAuthorizationFlow = [authorization performAuthorization:serviceConfiguration clientId:requestParameters.clientId clientSecret:requestParameters.clientSecret scopes:requestParameters.scopes redirectUrl:requestParameters.redirectUrl additionalParameters:requestParameters.additionalParameters preferEphemeralSession:requestParameters.preferEphemeralSession result:result exchangeCode:exchangeCode nonce:requestParameters.nonce]; + _currentAuthorizationFlow = [authorization performAuthorization:serviceConfiguration clientId:requestParameters.clientId clientSecret:requestParameters.clientSecret scopes:requestParameters.scopes redirectUrl:requestParameters.redirectUrl additionalParameters:requestParameters.additionalParameters preferredExternalAgent:requestParameters.preferredExternalAgent result:result exchangeCode:exchangeCode nonce:requestParameters.nonce]; } else if (requestParameters.discoveryUrl) { NSURL *discoveryUrl = [NSURL URLWithString:requestParameters.discoveryUrl]; [OIDAuthorizationService discoverServiceConfigurationForDiscoveryURL:discoveryUrl @@ -161,7 +161,7 @@ -(void)handleAuthorizeMethodCall:(NSDictionary*)arguments result:(FlutterResult) return; } - self->_currentAuthorizationFlow = [authorization performAuthorization:configuration clientId:requestParameters.clientId clientSecret:requestParameters.clientSecret scopes:requestParameters.scopes redirectUrl:requestParameters.redirectUrl additionalParameters:requestParameters.additionalParameters preferEphemeralSession:requestParameters.preferEphemeralSession result:result exchangeCode:exchangeCode nonce:requestParameters.nonce]; + self->_currentAuthorizationFlow = [authorization performAuthorization:configuration clientId:requestParameters.clientId clientSecret:requestParameters.clientSecret scopes:requestParameters.scopes redirectUrl:requestParameters.redirectUrl additionalParameters:requestParameters.additionalParameters preferredExternalAgent:requestParameters.preferredExternalAgent result:result exchangeCode:exchangeCode nonce:requestParameters.nonce]; }]; } else { NSURL *issuerUrl = [NSURL URLWithString:requestParameters.issuer]; @@ -174,7 +174,7 @@ -(void)handleAuthorizeMethodCall:(NSDictionary*)arguments result:(FlutterResult) return; } - self->_currentAuthorizationFlow = [authorization performAuthorization:configuration clientId:requestParameters.clientId clientSecret:requestParameters.clientSecret scopes:requestParameters.scopes redirectUrl:requestParameters.redirectUrl additionalParameters:requestParameters.additionalParameters preferEphemeralSession:requestParameters.preferEphemeralSession result:result exchangeCode:exchangeCode nonce:requestParameters.nonce]; + self->_currentAuthorizationFlow = [authorization performAuthorization:configuration clientId:requestParameters.clientId clientSecret:requestParameters.clientSecret scopes:requestParameters.scopes redirectUrl:requestParameters.redirectUrl additionalParameters:requestParameters.additionalParameters preferredExternalAgent:requestParameters.preferredExternalAgent result:result exchangeCode:exchangeCode nonce:requestParameters.nonce]; }]; } } diff --git a/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSSafariViewController.h b/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSSafariViewController.h new file mode 100644 index 00000000..cbf6dc87 --- /dev/null +++ b/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSSafariViewController.h @@ -0,0 +1,68 @@ +/*! @file OIDExternalUserAgentIOSSafariViewController.h + @brief AppAuth iOS SDK + @copyright + Copyright 2018 Google Inc. All Rights Reserved. + @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 + +#import "OIDExternalUserAgentIOSSafariViewController.h" +#import "OIDExternalUserAgent.h" +#import "OIDExternalUserAgentIOS.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 OIDSafariViewControllerFactory + +/*! @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 (on iOS 9+). 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 + +/*! @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)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 \ No newline at end of file diff --git a/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSSafariViewController.m b/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSSafariViewController.m new file mode 100644 index 00000000..42ad8537 --- /dev/null +++ b/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSSafariViewController.m @@ -0,0 +1,173 @@ +/*! @file OIDExternalUserAgentIOSSafariViewController.m + @brief AppAuth iOS SDK + @copyright + Copyright 2018 Google Inc. All Rights Reserved. + @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 "OIDExternalUserAgentIOSSafariViewController.h" + +#import + +#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 __nullable gSafariViewControllerFactory; + +/** @brief The default @c OIDSafariViewControllerFactory which creates new instances of + @c SFSafariViewController using known best practices. + */ +@interface OIDDefaultSafariViewControllerFactory : NSObject +@end + +@interface OIDExternalUserAgentIOSSafariViewController () +@end + +@implementation OIDExternalUserAgentIOSSafariViewController { + UIViewController *_presentingViewController; + + BOOL _externalUserAgentFlowInProgress; + __weak id _session; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" + __weak SFSafariViewController *_safariVC; +#pragma clang diagnostic pop +} + +/** @brief Obtains the current @c OIDSafariViewControllerFactory; creating a new default instance if + required. + */ ++ (id)safariViewControllerFactory { + if (!gSafariViewControllerFactory) { + gSafariViewControllerFactory = [[OIDDefaultSafariViewControllerFactory alloc] init]; + } + return gSafariViewControllerFactory; +} + ++ (void)setSafariViewControllerFactory:(id)factory { + NSAssert(factory, @"Parameter: |factory| must be non-nil."); + gSafariViewControllerFactory = factory; +} + +- (nullable instancetype)initWithPresentingViewController: + (UIViewController *)presentingViewController { + self = [super init]; + if (self) { + _presentingViewController = presentingViewController; + } + return self; +} + +- (BOOL)presentExternalUserAgentRequest:(id)request + session:(id)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]; + + if (@available(iOS 9.0, *)) { + SFSafariViewController *safariVC = + [[[self class] safariViewControllerFactory] safariViewControllerWithURL:requestURL]; + safariVC.delegate = self; + safariVC.modalPresentationStyle = UIModalPresentationFormSheet; + _safariVC = safariVC; + [_presentingViewController presentViewController:safariVC animated:YES completion:nil]; + openedSafari = YES; + } else { + openedSafari = [[UIApplication sharedApplication] openURL:requestURL]; + } + + 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; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" + SFSafariViewController *safariVC = _safariVC; +#pragma clang diagnostic pop + + [self cleanUp]; + + if (@available(iOS 9.0, *)) { + if (safariVC) { + [safariVC dismissViewControllerAnimated:YES completion:completion]; + } else { + if (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 NS_AVAILABLE_IOS(9.0) { + 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 session = _session; + [self cleanUp]; + NSError *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeProgramCanceledAuthorizationFlow + underlyingError:nil + description:nil]; + [session failExternalUserAgentFlowWithError:error]; +} + +@end + +@implementation OIDDefaultSafariViewControllerFactory + +- (SFSafariViewController *)safariViewControllerWithURL:(NSURL *)URL NS_AVAILABLE_IOS(9.0) { + SFSafariViewController *safariViewController = + [[SFSafariViewController alloc] initWithURL:URL entersReaderIfAvailable:NO]; + return safariViewController; +} + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/flutter_appauth/lib/flutter_appauth.dart b/flutter_appauth/lib/flutter_appauth.dart index 480400e2..de2f4d78 100644 --- a/flutter_appauth/lib/flutter_appauth.dart +++ b/flutter_appauth/lib/flutter_appauth.dart @@ -7,6 +7,7 @@ export 'package:flutter_appauth_platform_interface/flutter_appauth_platform_inte AuthorizationTokenResponse, EndSessionRequest, EndSessionResponse, + ExternalAgentType, FlutterAppAuthOAuthError, FlutterAppAuthPlatformErrorDetails, FlutterAppAuthUserCancelledException, diff --git a/flutter_appauth/macos/Classes/AppAuthMacOSAuthorization.m b/flutter_appauth/macos/Classes/AppAuthMacOSAuthorization.m index 0d90ab0a..c6b94a24 100644 --- a/flutter_appauth/macos/Classes/AppAuthMacOSAuthorization.m +++ b/flutter_appauth/macos/Classes/AppAuthMacOSAuthorization.m @@ -2,7 +2,7 @@ @implementation AppAuthMacOSAuthorization -- (id)performAuthorization:(OIDServiceConfiguration *)serviceConfiguration clientId:(NSString*)clientId clientSecret:(NSString*)clientSecret scopes:(NSArray *)scopes redirectUrl:(NSString*)redirectUrl additionalParameters:(NSDictionary *)additionalParameters preferEphemeralSession:(BOOL)preferEphemeralSession result:(FlutterResult)result exchangeCode:(BOOL)exchangeCode nonce:(NSString*)nonce { +- (id)performAuthorization:(OIDServiceConfiguration *)serviceConfiguration clientId:(NSString*)clientId clientSecret:(NSString*)clientSecret scopes:(NSArray *)scopes redirectUrl:(NSString*)redirectUrl additionalParameters:(NSDictionary *)additionalParameters preferredExternalAgent:(NSString*)preferredExternalAgent result:(FlutterResult)result exchangeCode:(BOOL)exchangeCode nonce:(NSString*)nonce { NSString *codeVerifier = [OIDAuthorizationRequest generateCodeVerifier]; NSString *codeChallenge = [OIDAuthorizationRequest codeChallengeS256ForVerifier:codeVerifier]; @@ -21,7 +21,7 @@ @implementation AppAuthMacOSAuthorization additionalParameters:additionalParameters]; NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; if(exchangeCode) { - NSObject *agent = [self userAgentWithPresentingWindow:keyWindow useEphemeralSession:preferEphemeralSession]; + NSObject *agent = [self userAgentWithPresentingWindow:keyWindow preferredExternalAgent:preferredExternalAgent]; return [OIDAuthState authStateByPresentingAuthorizationRequest:request externalUserAgent:agent callback:^(OIDAuthState *_Nullable authState, NSError *_Nullable error) { if(authState) { @@ -32,7 +32,7 @@ @implementation AppAuthMacOSAuthorization } }]; } else { - NSObject *agent = [self userAgentWithPresentingWindow:keyWindow useEphemeralSession:preferEphemeralSession]; + NSObject *agent = [self userAgentWithPresentingWindow:keyWindow preferredExternalAgent:preferredExternalAgent]; return [OIDAuthorizationService presentAuthorizationRequest:request externalUserAgent:agent callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse, NSError *_Nullable error) { if(authorizationResponse) { NSMutableDictionary *processedResponse = [[NSMutableDictionary alloc] init]; @@ -56,7 +56,7 @@ @implementation AppAuthMacOSAuthorization additionalParameters:requestParameters.additionalParameters]; NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; - id externalUserAgent = [self userAgentWithPresentingWindow:keyWindow useEphemeralSession:requestParameters.preferEphemeralSession]; + id externalUserAgent = [self userAgentWithPresentingWindow:keyWindow preferredExternalAgent:requestParameters.preferredExternalAgent]; return [OIDAuthorizationService presentEndSessionRequest:endSessionRequest externalUserAgent:externalUserAgent callback:^(OIDEndSessionResponse * _Nullable endSessionResponse, NSError * _Nullable error) { if(!endSessionResponse) { NSString *message = [NSString stringWithFormat:END_SESSION_ERROR_MESSAGE_FORMAT, [error localizedDescription]]; @@ -69,8 +69,8 @@ @implementation AppAuthMacOSAuthorization }]; } -- (id)userAgentWithPresentingWindow:(NSWindow *)presentingWindow useEphemeralSession:(BOOL)useEphemeralSession { - if (useEphemeralSession) { +- (id)userAgentWithPresentingWindow:(NSWindow *)presentingWindow preferredExternalAgent:(NSString*)preferredExternalAgent { + if ([preferredExternalAgent isEqual:@"ExternalAgentType.ephemeralAsWebAuthenticationSession"]) { return [[OIDExternalUserAgentMacNoSSO alloc] initWithPresentingWindow:presentingWindow]; } return [[OIDExternalUserAgentMac alloc] initWithPresentingWindow:presentingWindow];