diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Package.swift b/Package.swift index ae72258ac..c62ca8a5b 100644 --- a/Package.swift +++ b/Package.swift @@ -50,11 +50,12 @@ let package = Package( name: "AppAuth", dependencies: ["AppAuthCore"], path: "Source/AppAuth", - sources: ["iOS", "macOS"], + sources: ["iOS", "macOS", "visionOS"], publicHeadersPath: "", cSettings: [ .headerSearchPath("iOS"), .headerSearchPath("macOS"), + .headerSearchPath("visionOS"), .headerSearchPath("macOS/LoopbackHTTPServer"), ] ), diff --git a/Source/AppAuth.h b/Source/AppAuth.h index 19abc55e1..924b21b17 100644 --- a/Source/AppAuth.h +++ b/Source/AppAuth.h @@ -56,6 +56,11 @@ #import "OIDAuthorizationService+Mac.h" #import "OIDExternalUserAgentMac.h" #import "OIDRedirectHTTPHandler.h" +#elif TARGET_OS_VISION +#import "OIDAuthState+Vision.h" +#import "OIDAuthorizationService+Vision.h" +#import "OIDExternalUserAgentVision.h" +#import "OIDExternalUserAgentVisionCustomBrowser.h" #else #error "Platform Undefined" #endif diff --git a/Source/AppAuth/iOS/OIDAuthState+IOS.h b/Source/AppAuth/iOS/OIDAuthState+IOS.h index 1a1ee63a0..82f09e77f 100644 --- a/Source/AppAuth/iOS/OIDAuthState+IOS.h +++ b/Source/AppAuth/iOS/OIDAuthState+IOS.h @@ -46,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN + (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest presentingViewController:(UIViewController *)presentingViewController - callback:(OIDAuthStateAuthorizationCallback)callback; + completion:(OIDAuthStateAuthorizationCallback)callback; /*! @brief Convenience method to create a @c OIDAuthState by presenting an authorization request (optionally using an emphemeral browser session that shares no cookies or data with the @@ -68,12 +68,12 @@ NS_ASSUME_NONNULL_BEGIN authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest presentingViewController:(UIViewController *)presentingViewController prefersEphemeralSession:(BOOL)prefersEphemeralSession - callback:(OIDAuthStateAuthorizationCallback)callback + completion:(OIDAuthStateAuthorizationCallback)callback API_AVAILABLE(ios(13)); + (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest - callback:(OIDAuthStateAuthorizationCallback)callback API_AVAILABLE(ios(11)) API_UNAVAILABLE(macCatalyst) + completion:(OIDAuthStateAuthorizationCallback)callback API_AVAILABLE(ios(11)) API_UNAVAILABLE(macCatalyst) __deprecated_msg("This method will not work on iOS 13. Use " "authStateByPresentingAuthorizationRequest:presentingViewController:callback:"); diff --git a/Source/AppAuth/iOS/OIDAuthState+IOS.m b/Source/AppAuth/iOS/OIDAuthState+IOS.m index c474a77d1..1f644bfb6 100644 --- a/Source/AppAuth/iOS/OIDAuthState+IOS.m +++ b/Source/AppAuth/iOS/OIDAuthState+IOS.m @@ -29,7 +29,7 @@ @implementation OIDAuthState (IOS) + (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest presentingViewController:(UIViewController *)presentingViewController - callback:(OIDAuthStateAuthorizationCallback)callback { + completion:(OIDAuthStateAuthorizationCallback)callback { id externalUserAgent; #if TARGET_OS_MACCATALYST externalUserAgent = [[OIDExternalUserAgentCatalyst alloc] @@ -39,14 +39,14 @@ @implementation OIDAuthState (IOS) #endif // TARGET_OS_MACCATALYST return [self authStateByPresentingAuthorizationRequest:authorizationRequest externalUserAgent:externalUserAgent - callback:callback]; + completion:callback]; } + (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest presentingViewController:(UIViewController *)presentingViewController prefersEphemeralSession:(BOOL)prefersEphemeralSession - callback:(OIDAuthStateAuthorizationCallback)callback { + completion:(OIDAuthStateAuthorizationCallback)callback { id externalUserAgent; #if TARGET_OS_MACCATALYST externalUserAgent = [[OIDExternalUserAgentCatalyst alloc] @@ -59,17 +59,17 @@ @implementation OIDAuthState (IOS) #endif // TARGET_OS_MACCATALYST return [self authStateByPresentingAuthorizationRequest:authorizationRequest externalUserAgent:externalUserAgent - callback:callback]; + completion:callback]; } #if !TARGET_OS_MACCATALYST + (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest - callback:(OIDAuthStateAuthorizationCallback)callback { + completion:(OIDAuthStateAuthorizationCallback)callback { OIDExternalUserAgentIOS *externalUserAgent = [[OIDExternalUserAgentIOS alloc] init]; return [self authStateByPresentingAuthorizationRequest:authorizationRequest externalUserAgent:externalUserAgent - callback:callback]; + completion:callback]; } #endif // !TARGET_OS_MACCATALYST diff --git a/Source/AppAuth/iOS/OIDExternalUserAgentIOS.m b/Source/AppAuth/iOS/OIDExternalUserAgentIOS.m index 4a8cda0a3..62d5574c2 100644 --- a/Source/AppAuth/iOS/OIDExternalUserAgentIOS.m +++ b/Source/AppAuth/iOS/OIDExternalUserAgentIOS.m @@ -104,26 +104,51 @@ - (BOOL)presentExternalUserAgentRequest:(id)request if (!UIAccessibilityIsGuidedAccessEnabled()) { __weak OIDExternalUserAgentIOS *weakSelf = self; NSString *redirectScheme = request.redirectScheme; - ASWebAuthenticationSession *authenticationVC = - [[ASWebAuthenticationSession alloc] initWithURL:requestURL - callbackURLScheme:redirectScheme - completionHandler:^(NSURL * _Nullable callbackURL, - NSError * _Nullable error) { - __strong OIDExternalUserAgentIOS *strongSelf = weakSelf; - if (!strongSelf) { + ASWebAuthenticationSession *authenticationVC; + if (@available(iOS 17.4, *)) { + authenticationVC = + [[ASWebAuthenticationSession alloc] initWithURL:requestURL + callback: [ASWebAuthenticationSessionCallback callbackWithCustomScheme: redirectScheme] + completionHandler:^(NSURL * _Nullable callbackURL, + NSError * _Nullable error) { + __strong OIDExternalUserAgentIOS *strongSelf = weakSelf; + if (!strongSelf) { return; - } - strongSelf->_webAuthenticationVC = nil; - if (callbackURL) { - [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL]; - } else { - NSError *safariError = - [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow - underlyingError:error - description:nil]; - [strongSelf->_session failExternalUserAgentFlowWithError:safariError]; - } - }]; + } + strongSelf->_webAuthenticationVC = nil; + if (callbackURL) { + [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL]; + } else { + NSError *safariError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:error + description:nil]; + [strongSelf->_session failExternalUserAgentFlowWithError:safariError]; + } + }]; + + } else { + authenticationVC = [[ASWebAuthenticationSession alloc] initWithURL:requestURL + callbackURLScheme:redirectScheme + completionHandler:^(NSURL * _Nullable callbackURL, + NSError * _Nullable error) { + __strong OIDExternalUserAgentIOS *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_webAuthenticationVC = nil; + if (callbackURL) { + [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL]; + } else { + NSError *safariError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:error + description:nil]; + [strongSelf->_session failExternalUserAgentFlowWithError:safariError]; + } + }]; + } + #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 if (@available(iOS 13.0, *)) { authenticationVC.presentationContextProvider = self; diff --git a/Source/AppAuth/macOS/OIDAuthState+Mac.h b/Source/AppAuth/macOS/OIDAuthState+Mac.h index 71e56f22a..315aac709 100644 --- a/Source/AppAuth/macOS/OIDAuthState+Mac.h +++ b/Source/AppAuth/macOS/OIDAuthState+Mac.h @@ -47,7 +47,7 @@ NS_ASSUME_NONNULL_BEGIN + (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest presentingWindow:(NSWindow *)presentingWindow - callback:(OIDAuthStateAuthorizationCallback)callback; + completion:(OIDAuthStateAuthorizationCallback)callback; /*! @brief Convenience method to create a @c OIDAuthState by presenting an authorization request (optionally using an emphemeral browser session that shares no cookies or data with the @@ -69,7 +69,7 @@ NS_ASSUME_NONNULL_BEGIN authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest presentingWindow:(NSWindow *)presentingWindow prefersEphemeralSession:(BOOL)prefersEphemeralSession - callback:(OIDAuthStateAuthorizationCallback)callback + completion:(OIDAuthStateAuthorizationCallback)callback API_AVAILABLE(macos(10.15)); /*! @param authorizationRequest The authorization request to present. @@ -81,7 +81,7 @@ NS_ASSUME_NONNULL_BEGIN */ + (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest - callback:(OIDAuthStateAuthorizationCallback)callback + completion:(OIDAuthStateAuthorizationCallback)callback __deprecated_msg("For macOS 10.15 and above please use " "authStateByPresentingAuthorizationRequest:presentingWindow:callback:"); diff --git a/Source/AppAuth/macOS/OIDAuthState+Mac.m b/Source/AppAuth/macOS/OIDAuthState+Mac.m index f2894daaf..2fb6f39fe 100644 --- a/Source/AppAuth/macOS/OIDAuthState+Mac.m +++ b/Source/AppAuth/macOS/OIDAuthState+Mac.m @@ -29,32 +29,32 @@ @implementation OIDAuthState (Mac) + (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest presentingWindow:(NSWindow *)presentingWindow - callback:(OIDAuthStateAuthorizationCallback)callback { + completion:(OIDAuthStateAuthorizationCallback)callback { OIDExternalUserAgentMac *externalUserAgent = [[OIDExternalUserAgentMac alloc] initWithPresentingWindow:presentingWindow]; return [self authStateByPresentingAuthorizationRequest:authorizationRequest externalUserAgent:externalUserAgent - callback:callback]; + completion:callback]; } + (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest presentingWindow:(NSWindow *)presentingWindow prefersEphemeralSession:(BOOL)prefersEphemeralSession - callback:(OIDAuthStateAuthorizationCallback)callback { + completion:(OIDAuthStateAuthorizationCallback)callback { OIDExternalUserAgentMac *externalUserAgent = [[OIDExternalUserAgentMac alloc] initWithPresentingWindow:presentingWindow prefersEphemeralSession:prefersEphemeralSession]; return [self authStateByPresentingAuthorizationRequest:authorizationRequest externalUserAgent:externalUserAgent - callback:callback]; + completion:callback]; } + (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest - callback:(OIDAuthStateAuthorizationCallback)callback { + completion:(OIDAuthStateAuthorizationCallback)callback { OIDExternalUserAgentMac *externalUserAgent = [[OIDExternalUserAgentMac alloc] init]; return [self authStateByPresentingAuthorizationRequest:authorizationRequest externalUserAgent:externalUserAgent - callback:callback]; + completion:callback]; } @end diff --git a/Source/AppAuth/visionOS/OIDAuthState+Vision.h b/Source/AppAuth/visionOS/OIDAuthState+Vision.h new file mode 100644 index 000000000..d0e310286 --- /dev/null +++ b/Source/AppAuth/visionOS/OIDAuthState+Vision.h @@ -0,0 +1,90 @@ +/*! @file OIDAuthState+Vision.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 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 + +#if TARGET_OS_VISION + +#import + +#import "OIDAuthState.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief visionOS specific convenience methods for @c OIDAuthState. + */ +@interface OIDAuthState (Vision) + +/*! @brief Convenience method to create a @c OIDAuthState by presenting an authorization request + and performing the authorization code exchange in the case of code flow requests. For + the hybrid flow, the caller should validate the id_token and c_hash, then perform the token + request (@c OIDAuthorizationService.performTokenRequest:callback:) + 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. + */ ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingWindow:(UIWindow *)presentingWindow + completion:(OIDAuthStateAuthorizationCallback)callback; + +/*! @brief Convenience method to create a @c OIDAuthState by presenting an authorization request + (optionally using an emphemeral browser session that shares no cookies or data with the + normal browser session) and performing the authorization code exchange in the case of code + flow requests. For the hybrid flow, the caller should validate the id_token and c_hash, then + perform the token request (@c OIDAuthorizationService.performTokenRequest:callback:) + and update the OIDAuthState with the results using + @c OIDAuthState.updateWithTokenResponse:error:. + @param authorizationRequest The authorization request to present. + @param presentingWindow The window to present the @c ASWebAuthenticationSession UI. + @param prefersEphemeralSession Whether the caller prefers to use a private authentication + session. See @c ASWebAuthenticationSession.prefersEphemeralWebBrowserSession for more. + @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. + */ ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingWindow:(UIWindow *)presentingWindow + prefersEphemeralSession:(BOOL)prefersEphemeralSession + completion:(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) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + completion:(OIDAuthStateAuthorizationCallback)callback + __deprecated_msg("For visionOS 1.0 and above please use " + "authStateByPresentingAuthorizationRequest:presentingWindow:callback:"); + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/Source/AppAuth/visionOS/OIDAuthState+Vision.m b/Source/AppAuth/visionOS/OIDAuthState+Vision.m new file mode 100644 index 000000000..ec7ecf1cd --- /dev/null +++ b/Source/AppAuth/visionOS/OIDAuthState+Vision.m @@ -0,0 +1,61 @@ +/*! @file OIDAuthState+Vision.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 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 + +#if TARGET_OS_VISION + +#import "OIDAuthState+Vision.h" +#import "OIDExternalUserAgentVision.h" + +@implementation OIDAuthState (Vision) + ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingWindow:(UIWindow *)presentingWindow + completion:(OIDAuthStateAuthorizationCallback)callback { + OIDExternalUserAgentVision *externalUserAgent = [[OIDExternalUserAgentVision alloc] initWithPresentingWindow:presentingWindow]; + return [self authStateByPresentingAuthorizationRequest:authorizationRequest + externalUserAgent:externalUserAgent + completion:callback]; +} ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingWindow:(UIWindow *)presentingWindow + prefersEphemeralSession:(BOOL)prefersEphemeralSession + completion:(OIDAuthStateAuthorizationCallback)callback { + OIDExternalUserAgentVision *externalUserAgent = + [[OIDExternalUserAgentVision alloc] initWithPresentingWindow:presentingWindow + prefersEphemeralSession:prefersEphemeralSession]; + return [self authStateByPresentingAuthorizationRequest:authorizationRequest + externalUserAgent:externalUserAgent + completion:callback]; +} + ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + completion:(OIDAuthStateAuthorizationCallback)callback { + OIDExternalUserAgentVision *externalUserAgent = [[OIDExternalUserAgentVision alloc] init]; + return [self authStateByPresentingAuthorizationRequest:authorizationRequest + externalUserAgent:externalUserAgent + completion:callback]; +} + +@end + +#endif // TARGET_OS_OSX diff --git a/Source/AppAuth/visionOS/OIDAuthorizationService+Vision.h b/Source/AppAuth/visionOS/OIDAuthorizationService+Vision.h new file mode 100644 index 000000000..4f404f9b1 --- /dev/null +++ b/Source/AppAuth/visionOS/OIDAuthorizationService+Vision.h @@ -0,0 +1,78 @@ +/*! @file OIDAuthorizationService+Vision.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 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 + +#if TARGET_OS_VISION + +#import + +#import "OIDAuthorizationService.h" +#import "OIDExternalUserAgentSession.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Provides visionOS specific authorization request handling. + */ +@interface OIDAuthorizationService (Vision) + +/*! @brief Perform an authorization flow, presenting an appropriate browser for the user to + authenticate. + @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. + */ ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingWindow:(UIWindow *)presentingWindow + callback:(OIDAuthorizationCallback)callback; + +/*! @brief Perform an authorization flow using the @c ASWebAuthenticationSession optionally using an + emphemeral browser session that shares no cookies or data with the normal browser session. + @param request The authorization request. + @param presentingWindow The window to present the authentication flow. + @param prefersEphemeralSession Whether the caller prefers to use a private authentication + session. See @c ASWebAuthenticationSession.prefersEphemeralWebBrowserSession for more. + @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. + */ ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingWindow:(UIWindow *)presentingWindow + prefersEphemeralSession:(BOOL)prefersEphemeralSession + 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. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + */ ++ (id)presentAuthorizationRequest:(OIDAuthorizationRequest *)request + callback:(OIDAuthorizationCallback)callback + __deprecated_msg("For visionOS 1.15 and above please use presentAuthorizationRequest:presentingWindow:callback:"); + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_VISION diff --git a/Source/AppAuth/visionOS/OIDAuthorizationService+Vision.m b/Source/AppAuth/visionOS/OIDAuthorizationService+Vision.m new file mode 100644 index 000000000..ead07edc6 --- /dev/null +++ b/Source/AppAuth/visionOS/OIDAuthorizationService+Vision.m @@ -0,0 +1,58 @@ +/*! @file OIDAuthorizationService+Vision.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 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 + +#if TARGET_OS_VISION + +#import "OIDAuthorizationService+Vision.h" +#import "OIDExternalUserAgentVision.h" + + +NS_ASSUME_NONNULL_BEGIN + +@implementation OIDAuthorizationService (Vision) + ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingWindow:(UIWindow *)presentingWindow + callback:(OIDAuthorizationCallback)callback { + OIDExternalUserAgentVision *externalUserAgent = [[OIDExternalUserAgentVision alloc] initWithPresentingWindow:presentingWindow]; + return [self presentAuthorizationRequest:request externalUserAgent:externalUserAgent callback:callback]; +} + ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingWindow:(UIWindow *)presentingWindow + prefersEphemeralSession:(BOOL)prefersEphemeralSession + callback:(OIDAuthorizationCallback)callback { + OIDExternalUserAgentVision *externalUserAgent = + [[OIDExternalUserAgentVision alloc] initWithPresentingWindow:presentingWindow + prefersEphemeralSession:prefersEphemeralSession]; + return [self presentAuthorizationRequest:request externalUserAgent:externalUserAgent callback:callback]; +} + ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + callback:(OIDAuthorizationCallback)callback { + OIDExternalUserAgentVision *externalUserAgent = [[OIDExternalUserAgentVision alloc] init]; + return [self presentAuthorizationRequest:request externalUserAgent:externalUserAgent callback:callback]; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/Source/AppAuth/visionOS/OIDExternalUserAgentVision.h b/Source/AppAuth/visionOS/OIDExternalUserAgentVision.h new file mode 100644 index 000000000..945edf6db --- /dev/null +++ b/Source/AppAuth/visionOS/OIDExternalUserAgentVision.h @@ -0,0 +1,54 @@ +/*! @file OIDExternalUserAgentVision.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 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 + +#if TARGET_OS_VISION + +#import + +#import "OIDExternalUserAgent.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief An Apple-Vision-specific external user-agent UI Coordinator that uses the default browser to + present an external user-agent request. + */ +@interface OIDExternalUserAgentVision : NSObject + +/*! @brief The designated initializer. + @param presentingWindow The window from which to present the @c ASWebAuthenticationSession on + macOS 10.15 and above. Older macOS versions use the system browser. + */ +- (instancetype)initWithPresentingWindow:(UIWindow *)presentingWindow NS_DESIGNATED_INITIALIZER; + +/*! @brief Create an external user-agent which optionally uses a private authentication session. + @param presentingWindow The window from which to present the @c ASWebAuthenticationSession. + @param prefersEphemeralSession Whether the caller prefers to use a private authentication + session. See @c ASWebAuthenticationSession.prefersEphemeralWebBrowserSession for more. + */ +- (nullable instancetype)initWithPresentingWindow:(UIWindow *)presentingWindow + prefersEphemeralSession:(BOOL)prefersEphemeralSession; + +- (instancetype)init __deprecated_msg("Use initWithPresentingWindow for visionOS 1.0 and above."); + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/Source/AppAuth/visionOS/OIDExternalUserAgentVision.m b/Source/AppAuth/visionOS/OIDExternalUserAgentVision.m new file mode 100644 index 000000000..f43385725 --- /dev/null +++ b/Source/AppAuth/visionOS/OIDExternalUserAgentVision.m @@ -0,0 +1,194 @@ +/*! @file OIDExternalUserAgentVision.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 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 + +#if TARGET_OS_VISION + +#import "OIDExternalUserAgentVision.h" + +#import + +#import "OIDErrorUtilities.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDExternalUserAgentRequest.h" + + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDExternalUserAgentVision () +@end + +@implementation OIDExternalUserAgentVision { + BOOL _externalUserAgentFlowInProgress; + __weak id _session; + BOOL _prefersEphemeralSession; + + UIWindow *_presentingWindow; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" + ASWebAuthenticationSession *_webAuthenticationSession; +#pragma clang diagnostic pop +} + +- (instancetype)initWithPresentingWindow:(UIWindow *)presentingWindow { + self = [super init]; + if (self) { + _presentingWindow = presentingWindow; + } + return self; +} + +- (nullable instancetype)initWithPresentingWindow:(UIWindow *)presentingWindow + prefersEphemeralSession:(BOOL)prefersEphemeralSession { + self = [self initWithPresentingWindow:presentingWindow]; + if (self) { + _prefersEphemeralSession = prefersEphemeralSession; + } + return self; +} + +- (instancetype)init { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + return [self initWithPresentingWindow:nil]; +#pragma clang diagnostic pop +} + +- (BOOL)presentExternalUserAgentRequest:(id)request + session:(id)session { + if (_externalUserAgentFlowInProgress) { + // TODO: Handle errors as authorization is already in progress. + return NO; + } + + _externalUserAgentFlowInProgress = YES; + _session = session; + NSURL *requestURL = [request externalUserAgentRequestURL]; + + if (_presentingWindow) { + __weak OIDExternalUserAgentVision *weakSelf = self; + NSString *redirectScheme = request.redirectScheme; + ASWebAuthenticationSession *authenticationSession; + if (@available(visionOS 1.1, *)) { + authenticationSession = [[ASWebAuthenticationSession alloc] initWithURL:requestURL + callback: [ASWebAuthenticationSessionCallback callbackWithCustomScheme: redirectScheme] + completionHandler:^(NSURL * _Nullable callbackURL, + NSError * _Nullable error) { + __strong OIDExternalUserAgentVision *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]; + } + }]; + + } else { + authenticationSession = [[ASWebAuthenticationSession alloc] initWithURL:requestURL + callbackURLScheme:redirectScheme + completionHandler:^(NSURL * _Nullable callbackURL, + NSError * _Nullable error) { + __strong OIDExternalUserAgentVision *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; + _webAuthenticationSession.prefersEphemeralWebBrowserSession = _prefersEphemeralSession; + if (authenticationSession.canStart) { + return [authenticationSession start]; + } else { + return NO; + } + } + + + [[UIApplication sharedApplication] openURL:requestURL options: [NSDictionary new] completionHandler:nil]; + /*if (!openedBrowser) { + [self cleanUp]; + NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeBrowserOpenError + underlyingError:nil + description:@"Unable to open the browser."]; + [session failExternalUserAgentFlowWithError:safariError]; + }*/ + return true; +} + +- (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(void))completion { + if (!_externalUserAgentFlowInProgress) { + // Ignore this call if there is no authorization flow in progress. + 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 (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 { + return _presentingWindow; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_OSX diff --git a/Source/AppAuth/visionOS/OIDExternalUserAgentVisionCustomBrowser.h b/Source/AppAuth/visionOS/OIDExternalUserAgentVisionCustomBrowser.h new file mode 100644 index 000000000..793f78f00 --- /dev/null +++ b/Source/AppAuth/visionOS/OIDExternalUserAgentVisionCustomBrowser.h @@ -0,0 +1,113 @@ +/*! @file OIDExternalUserAgentVisionCustomBrowser.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 + +#if TARGET_OS_VISION + +#import + +#import "OIDExternalUserAgent.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 OIDExternalUserAgent protocol for visionOS that uses + a custom browser (i.e. not Safari) for external requests. 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. + */ +API_UNAVAILABLE(macCatalyst) +@interface OIDExternalUserAgentVisionCustomBrowser : NSObject + +/*! @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 OIDExternalUserAgentVisionCustomBrowser for Chrome. + */ ++ (instancetype)CustomBrowserChrome; + +/*! @brief An instance of @c OIDExternalUserAgentVisionCustomBrowser for Firefox. + */ ++ (instancetype)CustomBrowserFirefox; + +/*! @brief An instance of @c OIDExternalUserAgentVisionCustomBrowser for Opera. + */ ++ (instancetype)CustomBrowserOpera; + +/*! @brief An instance of @c OIDExternalUserAgentVisionCustomBrowser for Safari. + */ ++ (instancetype)CustomBrowserSafari; + +/*! @brief Creates a @c OIDCustomBrowserURLTransformation using the scheme substitution method used + visionOS 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 + visionOS browsers like Firefox. + */ ++ (OIDCustomBrowserURLTransformation) URLTransformationSchemeConcatPrefix:(NSString*)URLprefix; + +/*! @internal + @brief Unavailable. Please use @c initWithURLTransformation:canOpenURLScheme:appStoreURL: + */ +- (nonnull instancetype)init NS_UNAVAILABLE; + +/*! @brief OIDExternalUserAgent for a custom browser. @c presentExternalUserAgentRequest: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 presentExternalUserAgentRequest: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 + +#endif // TARGET_OS_VISION diff --git a/Source/AppAuth/visionOS/OIDExternalUserAgentVisionCustomBrowser.m b/Source/AppAuth/visionOS/OIDExternalUserAgentVisionCustomBrowser.m new file mode 100644 index 000000000..4dab4faca --- /dev/null +++ b/Source/AppAuth/visionOS/OIDExternalUserAgentVisionCustomBrowser.m @@ -0,0 +1,172 @@ +/*! @file OIDExternalUserAgentVisionCustomBrowser.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 + +#if TARGET_OS_VISION + +#import "OIDExternalUserAgentVisionCustomBrowser.h" + +#import + +#import "OIDAuthorizationRequest.h" +#import "OIDAuthorizationService.h" +#import "OIDErrorUtilities.h" +#import "OIDURLQueryComponent.h" + +#if !TARGET_OS_MACCATALYST + +NS_ASSUME_NONNULL_BEGIN + +@implementation OIDExternalUserAgentVisionCustomBrowser + ++ (instancetype)CustomBrowserChrome { + // Chrome iOS documentation: https://developer.chrome.com/multidevice/ios/links + OIDCustomBrowserURLTransformation transform = [[self class] URLTransformationSchemeSubstitutionHTTPS:@"googlechromes" HTTP:@"googlechrome"]; + NSURL *appStoreURL = + [NSURL URLWithString:@"https://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:@"https://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:@"https://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; + }; + OIDExternalUserAgentVisionCustomBrowser *transform = + [[[self class] alloc] initWithURLTransformation:transformNOP]; + return transform; +} + ++ (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)presentExternalUserAgentRequest:(nonnull id)request + session:(nonnull id)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 options:[NSDictionary new] completionHandler:nil]; + return NO; + } + } + + // Transforms the request URL and opens it. + NSURL *requestURL = [request externalUserAgentRequestURL]; + requestURL = _URLTransformation(requestURL); + [[UIApplication sharedApplication] openURL:requestURL options:[NSDictionary new] completionHandler:nil]; + + return YES; +} + +- (void)dismissExternalUserAgentAnimated:(BOOL)animated + completion:(nonnull void (^)(void))completion { + completion(); +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // !TARGET_OS_MACCATALYST + +#endif // TARGET_OS_Vision diff --git a/Source/AppAuthCore/OIDAuthState.h b/Source/AppAuthCore/OIDAuthState.h index 46c78a831..837babebb 100644 --- a/Source/AppAuthCore/OIDAuthState.h +++ b/Source/AppAuthCore/OIDAuthState.h @@ -138,7 +138,7 @@ static NSString *const kRefreshTokenRequestException = + (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest externalUserAgent:(id)externalUserAgent - callback:(OIDAuthStateAuthorizationCallback)callback; + completion:(OIDAuthStateAuthorizationCallback)callback; /*! @internal @brief Unavailable. Please use @c initWithAuthorizationResponse:. diff --git a/Source/AppAuthCore/OIDAuthState.m b/Source/AppAuthCore/OIDAuthState.m index cb5a22a1e..02d869e0e 100644 --- a/Source/AppAuthCore/OIDAuthState.m +++ b/Source/AppAuthCore/OIDAuthState.m @@ -122,7 +122,7 @@ @implementation OIDAuthState { + (id) authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest externalUserAgent:(id)externalUserAgent - callback:(OIDAuthStateAuthorizationCallback)callback { + completion:(OIDAuthStateAuthorizationCallback)callback { // presents the authorization request id authFlowSession = [OIDAuthorizationService presentAuthorizationRequest:authorizationRequest diff --git a/Source/Framework/AppAuth.h b/Source/Framework/AppAuth.h index f1916de77..4d9eaaf3f 100644 --- a/Source/Framework/AppAuth.h +++ b/Source/Framework/AppAuth.h @@ -64,6 +64,11 @@ FOUNDATION_EXPORT const unsigned char AppAuthVersionString[]; #import #import #import +#elif TARGET_OS_VISION +#import +#import +#import +#import #else #error "Platform Undefined" #endif