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 ASWebAuthenticationSession support in OIDExternalUserAgentMac. #675

Merged
merged 6 commits into from
Feb 14, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 2 additions & 1 deletion AppAuth.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ It follows the OAuth 2.0 for Native Apps best current practice
externalUserAgent.ios.weak_frameworks = "AuthenticationServices"

# macOS
externalUserAgent.osx.source_files = "Source/AppAuth/macOS/**/*.{h,m}"
externalUserAgent.osx.source_files = "Source/AppAuth/macOS/**/*.{h,m}"
externalUserAgent.osx.deployment_target = '10.9'
externalUserAgent.osx.weak_frameworks = "AuthenticationServices"
end

s.subspec 'EnterpriseUserAgent' do |enterpriseUserAgent|
Expand Down
91 changes: 47 additions & 44 deletions Examples/Example-macOS/Source/AppAuthExampleViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -213,17 +213,18 @@ - (IBAction)authWithAutoCodeExchange:(nullable id)sender {
additionalParameters:nil];
// performs authentication request
self.appDelegate.currentAuthorizationFlow =
[OIDAuthState authStateByPresentingAuthorizationRequest:request
callback:^(OIDAuthState *_Nullable authState,
NSError *_Nullable error) {
if (authState) {
[self setAuthState:authState];
[self logMessage:@"Got authorization tokens. Access token: %@",
authState.lastTokenResponse.accessToken];
} else {
[self logMessage:@"Authorization error: %@", [error localizedDescription]];
[self setAuthState:nil];
}
[OIDAuthState authStateByPresentingAuthorizationRequest:request
Alex-4-Git marked this conversation as resolved.
Show resolved Hide resolved
presentingWindow:self.view.window
callback:^(OIDAuthState *_Nullable authState,
NSError *_Nullable error) {
if (authState) {
Alex-4-Git marked this conversation as resolved.
Show resolved Hide resolved
Alex-4-Git marked this conversation as resolved.
Show resolved Hide resolved
[self setAuthState:authState];
[self logMessage:@"Got authorization tokens. Access token: %@",
authState.lastTokenResponse.accessToken];
} else {
[self logMessage:@"Authorization error: %@", [error localizedDescription]];
[self setAuthState:nil];
}
}];
}];
}
Expand Down Expand Up @@ -281,23 +282,23 @@ - (IBAction)authWithAutoCodeExchangeHTTP:(nullable id)sender {
// performs authentication request
__weak __typeof(self) weakSelf = self;
_redirectHTTPHandler.currentAuthorizationFlow =
[OIDAuthState authStateByPresentingAuthorizationRequest:request
callback:^(OIDAuthState *_Nullable authState,
NSError *_Nullable error) {
// Brings this app to the foreground.
[[NSRunningApplication currentApplication]
activateWithOptions:(NSApplicationActivateAllWindows |
NSApplicationActivateIgnoringOtherApps)];

// Processes the authorization response.
if (authState) {
[weakSelf logMessage:@"Got authorization tokens. Access token: %@",
authState.lastTokenResponse.accessToken];
} else {
[weakSelf logMessage:@"Authorization error: %@", error.localizedDescription];
}
[weakSelf setAuthState:authState];
}];
[OIDAuthState authStateByPresentingAuthorizationRequest:request
Alex-4-Git marked this conversation as resolved.
Show resolved Hide resolved
presentingWindow:self.view.window
callback:^(OIDAuthState *_Nullable authState, NSError *_Nullable error) {
// Brings this app to the foreground.
Alex-4-Git marked this conversation as resolved.
Show resolved Hide resolved
[[NSRunningApplication currentApplication]
activateWithOptions:(NSApplicationActivateAllWindows |
NSApplicationActivateIgnoringOtherApps)];

// Processes the authorization response.
if (authState) {
[weakSelf logMessage:@"Got authorization tokens. Access token: %@",
authState.lastTokenResponse.accessToken];
} else {
[weakSelf logMessage:@"Authorization error: %@", error.localizedDescription];
}
[weakSelf setAuthState:authState];
}];
}];
}

Expand Down Expand Up @@ -329,23 +330,23 @@ - (IBAction)authNoCodeExchange:(nullable id)sender {
additionalParameters:nil];
// performs authentication request
[self logMessage:@"Initiating authorization request %@", request];

self.appDelegate.currentAuthorizationFlow =
[OIDAuthorizationService presentAuthorizationRequest:request
callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse,
NSError *_Nullable error) {

if (authorizationResponse) {
OIDAuthState *authState =
[[OIDAuthState alloc] initWithAuthorizationResponse:authorizationResponse];
[self setAuthState:authState];

[self logMessage:@"Authorization response with code: %@",
authorizationResponse.authorizationCode];
// could just call [self tokenExchange:nil] directly, but will let the user initiate it.
} else {
[self logMessage:@"Authorization error: %@", [error localizedDescription]];
}
}];
presentingWindow:self.view.window
callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse, NSError *_Nullable error) {
if (authorizationResponse) {
Alex-4-Git marked this conversation as resolved.
Show resolved Hide resolved
OIDAuthState *authState =
[[OIDAuthState alloc] initWithAuthorizationResponse:authorizationResponse];
[self setAuthState:authState];

[self logMessage:@"Authorization response with code: %@",
authorizationResponse.authorizationCode];
// could just call [self tokenExchange:nil] directly, but will let the user initiate it.
} else {
[self logMessage:@"Authorization error: %@", [error localizedDescription]];
}
}];
}];
}

Expand Down Expand Up @@ -488,7 +489,9 @@ - (void)logMessage:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) {
NSString *dateString = [dateFormatter stringFromDate:[NSDate date]];
NSString *logLine = [NSString stringWithFormat:@"\n%@: %@", dateString, log];
NSAttributedString* logLineAttr = [[NSAttributedString alloc] initWithString:logLine];
[[_logTextView textStorage] appendAttributedString:logLineAttr];
dispatch_async(dispatch_get_main_queue(), ^{
[[_logTextView textStorage] appendAttributedString:logLineAttr];
});
}

@end
17 changes: 17 additions & 0 deletions Source/AppAuth/macOS/OIDAuthState+Mac.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#if TARGET_OS_OSX

#import <AppKit/AppKit.h>
#import "OIDAuthState.h"

NS_ASSUME_NONNULL_BEGIN
Expand All @@ -35,15 +36,31 @@ NS_ASSUME_NONNULL_BEGIN
and update the OIDAuthState with the results (@c
OIDAuthState.updateWithTokenResponse:error:).
@param authorizationRequest The authorization request to present.
@param presentingWindow The window to present the authentication flow.
@param callback The method called when the request has completed or failed.
@return A @c OIDExternalUserAgentSession instance which will terminate when it
receives a @c OIDExternalUserAgentSession.cancel message, or after processing a
@c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message.
@discussion This method adopts ASWebAuthenticationSession for macOS 10.15 and above or the default browser otherwise.
*/
+ (id<OIDExternalUserAgentSession>)
authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest
presentingWindow:(NSWindow *)presentingWindow
callback:(OIDAuthStateAuthorizationCallback)callback;

/*! @param authorizationRequest The authorization request to present.
@param callback The method called when the request has completed or failed.
@return A @c OIDExternalUserAgentSession instance which will terminate when it
receives a @c OIDExternalUserAgentSession.cancel message, or after processing a
@c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message.
@discussion This method uses the default browser to present the authentication flow.
*/
+ (id<OIDExternalUserAgentSession>)
Alex-4-Git marked this conversation as resolved.
Show resolved Hide resolved
authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest
callback:(OIDAuthStateAuthorizationCallback)callback
__deprecated_msg("For macOS 10.15 and above please use "
"authStateByPresentingAuthorizationRequest:presentingWindow:callback:");

@end

NS_ASSUME_NONNULL_END
Expand Down
10 changes: 10 additions & 0 deletions Source/AppAuth/macOS/OIDAuthState+Mac.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@

@implementation OIDAuthState (Mac)

+ (id<OIDExternalUserAgentSession>)
authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest
presentingWindow:(NSWindow *)presentingWindow
callback:(OIDAuthStateAuthorizationCallback)callback {
OIDExternalUserAgentMac *externalUserAgent = [[OIDExternalUserAgentMac alloc] initWithPresentingWindow:presentingWindow];
return [self authStateByPresentingAuthorizationRequest:authorizationRequest
externalUserAgent:externalUserAgent
callback:callback];
}

+ (id<OIDExternalUserAgentSession>)
authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest
callback:(OIDAuthStateAuthorizationCallback)callback {
Expand Down
17 changes: 16 additions & 1 deletion Source/AppAuth/macOS/OIDAuthorizationService+Mac.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#if TARGET_OS_OSX

#import <AppKit/AppKit.h>
#import "OIDAuthorizationService.h"

NS_ASSUME_NONNULL_BEGIN
Expand All @@ -28,6 +29,19 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface OIDAuthorizationService (Mac)

/*! @brief Perform an authorization flow.
@param request The authorization request.
@param presentingWindow The window to present the authentication flow.
@param callback The method called when the request has completed or failed.
@return A @c OIDExternalUserAgentSession instance which will terminate when it
receives a @c OIDExternalUserAgentSession.cancel message, or after processing a
@c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message.
@discussion This method adopts ASWebAuthenticationSession for macOS 10.15 and above or the default browser otherwise.
*/
+ (id<OIDExternalUserAgentSession>) presentAuthorizationRequest:(OIDAuthorizationRequest *)request
presentingWindow:(NSWindow *)presentingWindow
callback:(OIDAuthorizationCallback)callback;

/*! @brief Perform an authorization flow using the default browser.
@param request The authorization request.
@param callback The method called when the request has completed or failed.
Expand All @@ -36,7 +50,8 @@ NS_ASSUME_NONNULL_BEGIN
@c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message.
*/
+ (id<OIDExternalUserAgentSession>)presentAuthorizationRequest:(OIDAuthorizationRequest *)request
callback:(OIDAuthorizationCallback)callback;
callback:(OIDAuthorizationCallback)callback
__deprecated_msg("For macOS 10.15 and above please use presentAuthorizationRequest:presentingWindow:callback:");

@end

Expand Down
9 changes: 9 additions & 0 deletions Source/AppAuth/macOS/OIDAuthorizationService+Mac.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@

@implementation OIDAuthorizationService (Mac)

+ (id<OIDExternalUserAgentSession>) presentAuthorizationRequest:(OIDAuthorizationRequest *)request
presentingWindow:(NSWindow *)presentingWindow
callback:(OIDAuthorizationCallback)callback {
OIDExternalUserAgentMac *externalUserAgent = [[OIDExternalUserAgentMac alloc] initWithPresentingWindow:presentingWindow];
return [self presentAuthorizationRequest:request
externalUserAgent:externalUserAgent
callback:callback];
}

+ (id<OIDExternalUserAgentSession>) presentAuthorizationRequest:(OIDAuthorizationRequest *)request
callback:(OIDAuthorizationCallback)callback {
OIDExternalUserAgentMac *externalUserAgent = [[OIDExternalUserAgentMac alloc] init];
Expand Down
8 changes: 8 additions & 0 deletions Source/AppAuth/macOS/OIDExternalUserAgentMac.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#if TARGET_OS_OSX

#import <AppKit/AppKit.h>
#import "OIDExternalUserAgent.h"

NS_ASSUME_NONNULL_BEGIN
Expand All @@ -29,6 +30,13 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface OIDExternalUserAgentMac : NSObject <OIDExternalUserAgent>

/*! @brief The designated initializer.
@param presentingWindow The window from which to present the ASWebAuthenticationSession.
*/
- (instancetype)initWithPresentingWindow:(NSWindow *)presentingWindow NS_DESIGNATED_INITIALIZER;

- (instancetype)init __deprecated_msg("Use initWithPresentingWindow for macOS 10.15 and above.");

@end

NS_ASSUME_NONNULL_END
Expand Down
79 changes: 78 additions & 1 deletion Source/AppAuth/macOS/OIDExternalUserAgentMac.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,38 @@
#import "OIDErrorUtilities.h"
#import "OIDExternalUserAgentSession.h"
#import "OIDExternalUserAgentRequest.h"
#import <AuthenticationServices/AuthenticationServices.h>


NS_ASSUME_NONNULL_BEGIN

@interface OIDExternalUserAgentMac ()<ASWebAuthenticationPresentationContextProviding>
@end

@implementation OIDExternalUserAgentMac {
BOOL _externalUserAgentFlowInProgress;
__weak id<OIDExternalUserAgentSession> _session;

NSWindow *_presentingWindow;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
ASWebAuthenticationSession *_webAuthenticationSession;
#pragma clang diagnostic pop
}

- (instancetype)initWithPresentingWindow:(NSWindow *)presentingWindow {
self = [super init];
if (self) {
_presentingWindow = presentingWindow;
}
return self;
}

- (instancetype)init {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
return [self initWithPresentingWindow:nil];
#pragma clang diagnostic pop
}

- (BOOL)presentExternalUserAgentRequest:(id<OIDExternalUserAgentRequest>)request
Expand All @@ -46,6 +72,38 @@ - (BOOL)presentExternalUserAgentRequest:(id<OIDExternalUserAgentRequest>)request
_session = session;
NSURL *requestURL = [request externalUserAgentRequestURL];

if (@available(macOS 10.15, *)) {
if (_presentingWindow) {
__weak OIDExternalUserAgentMac *weakSelf = self;
NSString *redirectScheme = request.redirectScheme;
ASWebAuthenticationSession *authenticationSession =
[[ASWebAuthenticationSession alloc] initWithURL:requestURL
callbackURLScheme:redirectScheme
completionHandler:^(NSURL * _Nullable callbackURL,
NSError * _Nullable error) {
__strong OIDExternalUserAgentMac *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
strongSelf->_webAuthenticationSession = nil;
if (callbackURL) {
[strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL];
} else {
NSError *safariError =
[OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow
underlyingError:error
description:nil];
[strongSelf->_session failExternalUserAgentFlowWithError:safariError];
}
}];

authenticationSession.presentationContextProvider = self;

_webAuthenticationSession = authenticationSession;
return [authenticationSession start];
}
}

BOOL openedBrowser = [[NSWorkspace sharedWorkspace] openURL:requestURL];
if (!openedBrowser) {
[self cleanUp];
Expand All @@ -63,15 +121,34 @@ - (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(voi
if (completion) completion();
return;
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
ASWebAuthenticationSession *webAuthenticationSession = _webAuthenticationSession;
#pragma clang diagnostic pop

// Ideally the browser tab with the URL should be closed here, but the AppAuth library does not
// control the browser.
[self cleanUp];
if (completion) completion();
if (webAuthenticationSession) {
// dismiss the ASWebAuthenticationSession
[webAuthenticationSession cancel];
if (completion) completion();
} else if (completion) {
completion();
}
}

- (void)cleanUp {
_session = nil;
_externalUserAgentFlowInProgress = NO;
_webAuthenticationSession = nil;
}

#pragma mark - ASWebAuthenticationPresentationContextProviding

-(ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session API_AVAILABLE(macosx(10.15)){
Alex-4-Git marked this conversation as resolved.
Show resolved Hide resolved
return _presentingWindow;
}

@end
Expand Down