diff --git a/MASFoundation.xcodeproj/project.pbxproj b/MASFoundation.xcodeproj/project.pbxproj index cac3bf9e..237e62ba 100644 --- a/MASFoundation.xcodeproj/project.pbxproj +++ b/MASFoundation.xcodeproj/project.pbxproj @@ -293,6 +293,10 @@ A4F671331BAFC345000E2223 /* NSString+MASPrivate.m in Sources */ = {isa = PBXBuildFile; fileRef = A4F671311BAFC345000E2223 /* NSString+MASPrivate.m */; }; A858C6651D0978A6001FB9AD /* MASOTPService.h in Headers */ = {isa = PBXBuildFile; fileRef = A858C6631D0978A6001FB9AD /* MASOTPService.h */; }; A858C6661D0978A6001FB9AD /* MASOTPService.m in Sources */ = {isa = PBXBuildFile; fileRef = A858C6641D0978A6001FB9AD /* MASOTPService.m */; }; + C81CC3CC1FC2EA190058718E /* MASBrowserBasedAuthentication.h in Headers */ = {isa = PBXBuildFile; fileRef = C81CC3CA1FC2EA190058718E /* MASBrowserBasedAuthentication.h */; }; + C81CC3CD1FC2EA190058718E /* MASBrowserBasedAuthentication.m in Sources */ = {isa = PBXBuildFile; fileRef = C81CC3CB1FC2EA190058718E /* MASBrowserBasedAuthentication.m */; }; + C81CC3D01FC2EFBB0058718E /* UIAlertController+MAS.h in Headers */ = {isa = PBXBuildFile; fileRef = C81CC3CE1FC2EFBB0058718E /* UIAlertController+MAS.h */; }; + C81CC3D11FC2EFBB0058718E /* UIAlertController+MAS.m in Sources */ = {isa = PBXBuildFile; fileRef = C81CC3CF1FC2EFBB0058718E /* UIAlertController+MAS.m */; }; CB0A8ADF1D01F64F00B1DCCC /* MASProximityLogin.h in Headers */ = {isa = PBXBuildFile; fileRef = CB0A8ADD1D01F64F00B1DCCC /* MASProximityLogin.h */; settings = {ATTRIBUTES = (Public, ); }; }; CB0A8AE01D01F64F00B1DCCC /* MASProximityLogin.m in Sources */ = {isa = PBXBuildFile; fileRef = CB0A8ADE1D01F64F00B1DCCC /* MASProximityLogin.m */; }; CB0B58591E258C2A00BC0163 /* MASAuthorizationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = CB0B58571E258C2A00BC0163 /* MASAuthorizationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -720,6 +724,10 @@ A4F671311BAFC345000E2223 /* NSString+MASPrivate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MASPrivate.m"; sourceTree = ""; }; A858C6631D0978A6001FB9AD /* MASOTPService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MASOTPService.h; sourceTree = ""; }; A858C6641D0978A6001FB9AD /* MASOTPService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MASOTPService.m; sourceTree = ""; }; + C81CC3CA1FC2EA190058718E /* MASBrowserBasedAuthentication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MASBrowserBasedAuthentication.h; sourceTree = ""; }; + C81CC3CB1FC2EA190058718E /* MASBrowserBasedAuthentication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MASBrowserBasedAuthentication.m; sourceTree = ""; }; + C81CC3CE1FC2EFBB0058718E /* UIAlertController+MAS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIAlertController+MAS.h"; sourceTree = ""; }; + C81CC3CF1FC2EFBB0058718E /* UIAlertController+MAS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIAlertController+MAS.m"; sourceTree = ""; }; CB0A8ADD1D01F64F00B1DCCC /* MASProximityLogin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MASProximityLogin.h; sourceTree = ""; }; CB0A8ADE1D01F64F00B1DCCC /* MASProximityLogin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MASProximityLogin.m; sourceTree = ""; }; CB0B58571E258C2A00BC0163 /* MASAuthorizationResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MASAuthorizationResponse.h; sourceTree = ""; }; @@ -1348,6 +1356,8 @@ CBA3EB2D1E945F2400E64D9D /* MASClaims.m */, CB1FD1491FB23701000AFA25 /* MASSharedStorage.h */, CB1FD14A1FB23701000AFA25 /* MASSharedStorage.m */, + C81CC3CA1FC2EA190058718E /* MASBrowserBasedAuthentication.h */, + C81CC3CB1FC2EA190058718E /* MASBrowserBasedAuthentication.m */, ); path = models; sourceTree = ""; @@ -1402,6 +1412,8 @@ A4F6712F1BAFC321000E2223 /* categories */ = { isa = PBXGroup; children = ( + C81CC3CE1FC2EFBB0058718E /* UIAlertController+MAS.h */, + C81CC3CF1FC2EFBB0058718E /* UIAlertController+MAS.m */, CB1907ED1C1794F400A5EF16 /* MASIKeyChainStore+MASPrivate.h */, CB1907EE1C1794F400A5EF16 /* MASIKeyChainStore+MASPrivate.m */, A483C1FF1BE6D0C5007572CE /* CBCentralManager+MASPrivate.h */, @@ -1724,9 +1736,11 @@ 105B2F4B1CA6B3EA0005A2D0 /* ripemd.h in Headers */, A46F49F11C2F5FC500A4C370 /* MASINTULocationRequestDefines.h in Headers */, CB5E4C641C1D1B56001B3B8A /* MASGetURLRequest.h in Headers */, + C81CC3CC1FC2EA190058718E /* MASBrowserBasedAuthentication.h in Headers */, 105B2F471CA6B3EA0005A2D0 /* pqueue.h in Headers */, 105B2F371CA6B3EA0005A2D0 /* kssl.h in Headers */, 105B2F511CA6B3EA0005A2D0 /* srtp.h in Headers */, + C81CC3D01FC2EFBB0058718E /* UIAlertController+MAS.h in Headers */, CBFA70F41F1ED5D6006D025D /* MASSecurityPolicy.h in Headers */, CBD25AF51E78C47C00DFB47F /* JWTCryptoKeyExtractor.h in Headers */, 105B2F5F1CA6B3EA0005A2D0 /* x509_vfy.h in Headers */, @@ -2092,6 +2106,7 @@ A4150EFC1BF16EE200037E27 /* MASKeyChainService.m in Sources */, A47F12811C1D73530008E3F2 /* MASBluetoothPeripheral.m in Sources */, CBD25B061E78C47C00DFB47F /* JWTCoding+VersionThree.m in Sources */, + C81CC3D11FC2EFBB0058718E /* UIAlertController+MAS.m in Sources */, CBD25AED1E78C47C00DFB47F /* JWTAlgorithmDataHolderChain.m in Sources */, A46F49DA1C2F5FC500A4C370 /* MASINetworkActivityIndicatorManager.m in Sources */, A4831AAE1BD1A551007B4AE6 /* MASConfiguration.m in Sources */, @@ -2152,6 +2167,7 @@ A46F49C51C2F5FC500A4C370 /* MASIHTTPRequestOperation.m in Sources */, A4150E701BF1643900037E27 /* MASIJSONResponseSerializer+MASPrivate.m in Sources */, A417BA521BF033C300EC9BCB /* CLLocation+MASPrivate.m in Sources */, + C81CC3CD1FC2EA190058718E /* MASBrowserBasedAuthentication.m in Sources */, 69B7DF6D1F96756B0056DD3A /* MASRequest+MASPrivate.m in Sources */, A4831AB21BD1A551007B4AE6 /* MASFile.m in Sources */, CB0B585A1E258C2A00BC0163 /* MASAuthorizationResponse.m in Sources */, diff --git a/MASFoundation/Classes/MAS.h b/MASFoundation/Classes/MAS.h index 8d290576..65d8de1c 100644 --- a/MASFoundation/Classes/MAS.h +++ b/MASFoundation/Classes/MAS.h @@ -111,6 +111,16 @@ +/** + * Sets Bool indicator of Browser Based Authentication (templatized login) enabled or not for authorization process. + * By default, it is disabled. + + @param enable BOOL value indicating whether Browser Based Authentication is enabled or not. + */ ++ (void)enableBrowserBasedAuthentication:(BOOL)enable; + + + /** * Sets the gateway monitoring block defined by the GatewayMonitorStatusBlock type. * This block will be triggered when any change to the current monitoring status diff --git a/MASFoundation/Classes/MAS.m b/MASFoundation/Classes/MAS.m index 6f470ced..ddda9014 100644 --- a/MASFoundation/Classes/MAS.m +++ b/MASFoundation/Classes/MAS.m @@ -92,6 +92,12 @@ + (void)setOTPCredentialsBlock:(MASOTPCredentialsBlock)oneTimePassword } ++ (void)enableBrowserBasedAuthentication:(BOOL)enable +{ + [MASModelService setBrowserBasedAuthentication:enable]; +} + + + (void)setGatewayMonitor:(MASGatewayMonitorStatusBlock)monitor { [MASNetworkingService setGatewayMonitor:monitor]; diff --git a/MASFoundation/Classes/MASConstants.h b/MASFoundation/Classes/MASConstants.h index 2a4a86c6..7821afb7 100644 --- a/MASFoundation/Classes/MASConstants.h +++ b/MASFoundation/Classes/MASConstants.h @@ -465,6 +465,10 @@ typedef NS_ENUM(NSInteger, MASFoundationErrorCode) MASFoundationErrorCodeJWTUnexpectedClassType = 170002, MASFoundationErrorCodeJWTSerializationError = 170003, + // + // Browser Based Login + // + MASFoundationErrorCodeBBANotEnabled = 180000, // // SharedStorage // diff --git a/MASFoundation/Classes/_private_/categories/NSError+MASPrivate.h b/MASFoundation/Classes/_private_/categories/NSError+MASPrivate.h index b20b0bb4..a41079e6 100644 --- a/MASFoundation/Classes/_private_/categories/NSError+MASPrivate.h +++ b/MASFoundation/Classes/_private_/categories/NSError+MASPrivate.h @@ -644,9 +644,17 @@ /** * Create MASFoundationErrorDomainLocal NSError for MASFoundationErrorCodeProximityLoginInvalidAuthorizeURL. * - * @return REturns an NSError instance with the domain MASFoundationErrorDomainLocal and + * @return Returns an NSError instance with the domain MASFoundationErrorDomainLocal and * error MASFoundationErrorCodeProximityLoginInvalidAuthorizeURL */ + (NSError *)errorProximityLoginInvalidAuthroizeURL; + +/** + * Create MASFoundationErrorDomainLocal NSError for MASFoundationErrorCodeBBANotEnabled. + * + * @return Returns an NSError instance with the domain MASFoundationErrorDomainLocal and + * error MASFoundationErrorCodeBBANotEnabled + */ ++ (NSError *)errorBrowserBasedAuthenticaionNotEnabled; @end diff --git a/MASFoundation/Classes/_private_/categories/NSError+MASPrivate.m b/MASFoundation/Classes/_private_/categories/NSError+MASPrivate.m index 1a3c0c90..9362038c 100644 --- a/MASFoundation/Classes/_private_/categories/NSError+MASPrivate.m +++ b/MASFoundation/Classes/_private_/categories/NSError+MASPrivate.m @@ -835,6 +835,11 @@ + (NSError *)errorProximityLoginInvalidAuthroizeURL } ++ (NSError *)errorBrowserBasedAuthenticaionNotEnabled +{ + return [self errorForFoundationCode:MASFoundationErrorCodeBBANotEnabled errorDomain:MASFoundationErrorDomainLocal]; +} + # pragma mark - Foundation Errors Private + (MASFoundationErrorCode)foundationErrorCodeForApiCode:(MASApiErrorCode)apiCode @@ -1113,7 +1118,11 @@ + (NSString *)descriptionForFoundationErrorCode:(MASFoundationErrorCode)errorCod // Shared Storage // case MASFoundationErrorCodeSharedStorageNotNilKey: return @"Data key cannot be nil or empty string."; - + + // + // Browser Based Authentication + // + case MASFoundationErrorCodeBBANotEnabled : return @"MAS Browser Based Authentication is Not Enabled"; // // Default // diff --git a/MASFoundation/Classes/_private_/categories/UIAlertController+MAS.h b/MASFoundation/Classes/_private_/categories/UIAlertController+MAS.h new file mode 100644 index 00000000..ec8de498 --- /dev/null +++ b/MASFoundation/Classes/_private_/categories/UIAlertController+MAS.h @@ -0,0 +1,23 @@ +// +// UIAlertController+MAS.h +// MASFoundation +// +// Created by nimma01 on 11/10/17. +// Copyright © 2017 CA Technologies. All rights reserved. +// + +#import + +@interface UIAlertController (MAS) + +///-------------------------------------- +/// @name Public +///------------------------------------- + +# pragma mark - Public + +/** + * Retrieve the currently visible UIViewController. + */ ++ (UIViewController *)rootViewController; +@end diff --git a/MASFoundation/Classes/_private_/categories/UIAlertController+MAS.m b/MASFoundation/Classes/_private_/categories/UIAlertController+MAS.m new file mode 100644 index 00000000..cbd86f3e --- /dev/null +++ b/MASFoundation/Classes/_private_/categories/UIAlertController+MAS.m @@ -0,0 +1,59 @@ +// +// UIAlertController+MAS.m +// MASFoundation +// +// Created by nimma01 on 11/10/17. +// Copyright © 2017 CA Technologies. All rights reserved. +// + +#import "UIAlertController+MAS.h" +#import "MASUser.h" +@implementation UIAlertController (MAS) + +# pragma mark - Error Alert + ++ (UIViewController *) presentedViewController:(id)viewController +{ + if ([viewController isKindOfClass:[UINavigationController class]]) + { + UINavigationController *navigationController = (UINavigationController *)viewController; + return [self presentedViewController:navigationController.topViewController]; + } + if ([viewController isKindOfClass:[UIViewController class]]) + { + if ([viewController presentedViewController]) + { + if ([[viewController presentedViewController] isKindOfClass:[UINavigationController class]]) + { + UINavigationController *navigationController = (UINavigationController *)[viewController presentedViewController]; + + if (navigationController.isBeingDismissed) + { + return viewController; + } + } + + return [self presentedViewController:[viewController presentedViewController]]; + } + else { + return viewController; + } + } + else if ([viewController isKindOfClass:[UITabBarController class]]) + { + UITabBarController *tabBarController = (UITabBarController *)viewController; + return [self presentedViewController:tabBarController.presentedViewController]; + } + else { + return nil; + } +} + + +# pragma mark - Public + ++ (UIViewController *)rootViewController +{ + return [self presentedViewController:[UIApplication sharedApplication].keyWindow.rootViewController]; +} +@end diff --git a/MASFoundation/Classes/_private_/services/MASServiceRegistry.h b/MASFoundation/Classes/_private_/services/MASServiceRegistry.h index 694f9508..781e5f37 100644 --- a/MASFoundation/Classes/_private_/services/MASServiceRegistry.h +++ b/MASFoundation/Classes/_private_/services/MASServiceRegistry.h @@ -187,4 +187,15 @@ typedef NS_ENUM(NSInteger, MASRegistryState) - (BOOL)uiServiceWillHandleOTPChannelSelection:(NSArray *)supportedChannels otpGenerationBlock:(MASOTPGenerationBlock)generationBlock; + +/** + * Calling this method will attempt to launch a browser which handles the user authentication steps. + * For this method to handle authentication it requires browser based login to be enabled. + * @see enableBrowserBasedAuthentication + * + * @param bbaLoginBlock The MASAuthCredentialsBlock to receive login result. + * @return Return YES if handled, NO if not. + */ +-(BOOL)browserBasedLoginWillHandleAuthentication : (MASAuthCredentialsBlock)bbaLoginBlock; + @end diff --git a/MASFoundation/Classes/_private_/services/MASServiceRegistry.m b/MASFoundation/Classes/_private_/services/MASServiceRegistry.m index 9c375214..d7148fc4 100644 --- a/MASFoundation/Classes/_private_/services/MASServiceRegistry.m +++ b/MASFoundation/Classes/_private_/services/MASServiceRegistry.m @@ -15,6 +15,8 @@ #import "MASConfigurationService.h" #import "MASModelService.h" #import "MASService.h" +#import "MASServiceRegistry.h" +#import "MASBrowserBasedAuthentication.h" // @@ -932,4 +934,17 @@ - (BOOL)uiServiceWillHandleOTPChannelSelection:(NSArray *)supportedChannels return YES; } + +-(BOOL)browserBasedLoginWillHandleAuthentication : (MASAuthCredentialsBlock)bbaLoginBlock +{ + if(![MASModelService browserBasedAuthentication]) + { + return NO; + } + + [[MASBrowserBasedAuthentication sharedInstance] loadWebLoginTemplate:bbaLoginBlock]; + return YES; + +} + @end diff --git a/MASFoundation/Classes/_private_/services/model/MASModelService.h b/MASFoundation/Classes/_private_/services/model/MASModelService.h index 50b63377..d2c356cd 100644 --- a/MASFoundation/Classes/_private_/services/model/MASModelService.h +++ b/MASFoundation/Classes/_private_/services/model/MASModelService.h @@ -82,6 +82,25 @@ - (void)setUserObject:(MASUser *)user; + +/** + * Sets the browser based authentication property. Default is NO. + * + * @param browserBasedLogin The state of browser based authentication. + * If this is set to true, a URL which has a templatized login is launched in a browser and this would disable the Social Login, Proximity Login and also prevents the launch of MASUI. + */ ++ (void)setBrowserBasedAuthentication:(BOOL)browserBasedAuthentication; + + + +/** + * The current state of browser based login. + * + * @return BOOL value is returned. + */ ++ (BOOL)browserBasedAuthentication; + + ///-------------------------------------- /// @name Application ///-------------------------------------- diff --git a/MASFoundation/Classes/_private_/services/model/MASModelService.m b/MASFoundation/Classes/_private_/services/model/MASModelService.m index e89189f2..205ce5e3 100644 --- a/MASFoundation/Classes/_private_/services/model/MASModelService.m +++ b/MASFoundation/Classes/_private_/services/model/MASModelService.m @@ -30,6 +30,7 @@ @interface MASModelService () @property (nonatomic, strong, readwrite) MASAuthenticationProviders *currentProviders; + @end @@ -38,8 +39,7 @@ @implementation MASModelService static MASGrantFlow _grantFlow_ = MASGrantFlowClientCredentials; static MASUserLoginWithUserCredentialsBlock _userLoginBlock_ = nil; static MASUserAuthCredentialsBlock _userAuthCredentialsBlock_ = nil; - - +static BOOL _isBrowserBasedAuthentication_ = NO; # pragma mark - Properties @@ -71,6 +71,19 @@ - (void)setUserObject:(MASUser *)user _currentUser = user; } + ++ (void)setBrowserBasedAuthentication : (BOOL)browserBasedAuthentication +{ + _isBrowserBasedAuthentication_ = browserBasedAuthentication; +} + + ++ (BOOL)browserBasedAuthentication +{ + return _isBrowserBasedAuthentication_; +} + + # pragma mark - Shared Service + (instancetype)sharedService @@ -367,7 +380,7 @@ - (void)retrieveAuthenticationProviders:(MASObjectResponseErrorBlock)completion // // If the user was already authenticated, we don't have to retrieve the authentication provider // - if (([MASApplication currentApplication].isAuthenticated && [MASApplication currentApplication].authenticationStatus == MASAuthenticationStatusLoginWithUser) || [MASAccess currentAccess].isSessionLocked) + if (([MASApplication currentApplication].isAuthenticated && [MASApplication currentApplication].authenticationStatus == MASAuthenticationStatusLoginWithUser) || [MASAccess currentAccess].isSessionLocked || _isBrowserBasedAuthentication_) { // @@ -906,9 +919,15 @@ - (void)registerDeviceWithCompletion:(MASCompletionErrorBlock)completion @"\n\n********************************************************\n\n\n"); // - // If the UI handling framework is present and will handle this stop here + // If the UI handling framework or browser based login is present and will handle this stop here // MASServiceRegistry *serviceRegistry = [MASServiceRegistry sharedRegistry]; + + if([serviceRegistry browserBasedLoginWillHandleAuthentication:authCredentialsBlock]) + { + return; + } + if([serviceRegistry uiServiceWillHandleWithAuthCredentialsBlock:authCredentialsBlock]) { return; @@ -1712,6 +1731,11 @@ - (void)loginUsingUserCredentials:(MASCompletionErrorBlock)completion // If the UI handling framework is present and will handle this stop here // MASServiceRegistry *serviceRegistry = [MASServiceRegistry sharedRegistry]; + if([serviceRegistry browserBasedLoginWillHandleAuthentication:authCredentialsBlock]) + { + return; + } + if([serviceRegistry uiServiceWillHandleWithAuthCredentialsBlock:authCredentialsBlock]) { return; diff --git a/MASFoundation/Classes/_private_/services/network/MASNetworkingService.h b/MASFoundation/Classes/_private_/services/network/MASNetworkingService.h index 6653a47c..69fc55c1 100644 --- a/MASFoundation/Classes/_private_/services/network/MASNetworkingService.h +++ b/MASFoundation/Classes/_private_/services/network/MASNetworkingService.h @@ -9,10 +9,11 @@ // #import "MASService.h" - #import "MASConstantsPrivate.h" + #import "MASAuthValidationOperation.h" +typedef NSURLRequest* (^MASSessionDataTaskHTTPRedirectBlock)(NSURLSession *_Nonnull session, NSURLSessionTask *_Nonnull task, NSURLResponse * _Nonnull response, NSURLRequest *_Nonnull request); @interface MASNetworkingService : MASService @@ -26,6 +27,12 @@ @property (nonatomic, assign, readonly) MASGatewayMonitoringStatus monitoringStatus; +/** + Http redirection block. Set this block only if you want to handle the redirection coming from the original NSURLRequest. + */ +@property (nonatomic) MASSessionDataTaskHTTPRedirectBlock httpRedirectionBlock; + + /** Constructs, if not exist, the shared operation that validates current session's registration and authentication status diff --git a/MASFoundation/Classes/_private_/services/network/MASNetworkingService.m b/MASFoundation/Classes/_private_/services/network/MASNetworkingService.m index c4415cac..d016e392 100644 --- a/MASFoundation/Classes/_private_/services/network/MASNetworkingService.m +++ b/MASFoundation/Classes/_private_/services/network/MASNetworkingService.m @@ -11,7 +11,6 @@ #import "MASNetworkingService.h" #import "MASAccessService.h" -#import "MASConstantsPrivate.h" #import "MASConfigurationService.h" #import "MASLocationService.h" #import "MASModelService.h" @@ -657,6 +656,7 @@ - (MASSessionDataTaskCompletionBlock)sessionDataTaskCompletionBlockWithEndPoint: } + - (BOOL)isMAGEndpoint:(NSString *)endpoint { BOOL isMAGEndpoint = NO; @@ -1273,6 +1273,11 @@ - (void)httpRequest:(NSString *)httpMethod endPoint:(NSString *)endPoint paramet // // Construct MASSessionDataTaskOperation with request, and completion block to handle any responsive re-authentication or re-registration. // + if(self.httpRedirectionBlock) + { + [_sessionManager setSessionDidReceiveHTTPRedirectBlock:self.httpRedirectionBlock]; + } + MASSessionDataTaskOperation *operation = [_sessionManager dataOperationWithRequest:request completionHandler:[self sessionDataTaskCompletionBlockWithEndPoint:endPoint parameters:parameterInfo @@ -1283,6 +1288,7 @@ - (void)httpRequest:(NSString *)httpMethod endPoint:(NSString *)endPoint paramet isPublic:isPublic completionBlock:blockCompletion]]; + if (![self isMAGEndpoint:endPoint]) { // diff --git a/MASFoundation/Classes/_private_/services/network/internal/MASURLSessionManager.h b/MASFoundation/Classes/_private_/services/network/internal/MASURLSessionManager.h index ad42a64c..139d8155 100644 --- a/MASFoundation/Classes/_private_/services/network/internal/MASURLSessionManager.h +++ b/MASFoundation/Classes/_private_/services/network/internal/MASURLSessionManager.h @@ -150,4 +150,12 @@ typedef void (^MASNetworkSessionDidFinishEventsForBackgroundURLSessionBlock)(NSU - (void)setSessionDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential **credential))block; + +/** + Set code block of session level redirection for the current NSURLRequest object. + + @param block http redirection code block. + */ +- (void)setSessionDidReceiveHTTPRedirectBlock:(NSURLRequest* (^)(NSURLSession *session,NSURLSessionTask *task, NSURLResponse* response,NSURLRequest *request))block; + @end diff --git a/MASFoundation/Classes/_private_/services/network/internal/MASURLSessionManager.m b/MASFoundation/Classes/_private_/services/network/internal/MASURLSessionManager.m index 52f37c81..f35e4139 100644 --- a/MASFoundation/Classes/_private_/services/network/internal/MASURLSessionManager.m +++ b/MASFoundation/Classes/_private_/services/network/internal/MASURLSessionManager.m @@ -17,6 +17,7 @@ @interface MASURLSessionManager () +#import "MASConstants.h" + +/** + MASBrowserBasedAuthentication class is a helper class to utilize SFSafariViewController to launch a customized login template. + * This class will get the redirection to receive the authorization code to perform login. + */ +@interface MASBrowserBasedAuthentication : NSObject + +# pragma mark - Browser Based Authentication + +/** + * Retrieve the shared MASBrowserBasedAuthentication singleton. + * + * @return Returns the shared MASBrowserBasedAuthentication singleton. + */ ++ (instancetype)sharedInstance; + + + +/** + * Method to load the browser with a URL that loads a templatized login page. + * + * @param webLoginBlock completion MASCompletionErrorBlock that receives the results. + */ +- (void)loadWebLoginTemplate:(MASAuthCredentialsBlock)webLoginBlock; + +@end diff --git a/MASFoundation/Classes/models/MASBrowserBasedAuthentication.m b/MASFoundation/Classes/models/MASBrowserBasedAuthentication.m new file mode 100644 index 00000000..56efa02a --- /dev/null +++ b/MASFoundation/Classes/models/MASBrowserBasedAuthentication.m @@ -0,0 +1,288 @@ +// +// MASBrowserBasedAuthentication.m +// MASFoundation +// +// Copyright (c) 2017 CA. All rights reserved. +// +// This software may be modified and distributed under the terms +// of the MIT license. See the LICENSE file for details. +// + +#import "MASAccessService.h" +#import "MASAuthorizationResponse.h" +#import "MASBrowserBasedAuthentication.h" +#import "MASConfigurationService.h" +#import "MASGetURLRequest.h" +#import "MASModelService.h" +#import "UIAlertController+MAS.h" +#import + +@interface MASBrowserBasedAuthentication () +{ + +} + +@property (nonatomic) SFSafariViewController *safariViewController; +@property (nonatomic) MASAuthCredentialsBlock webLoginCallBack; + +@end + +@implementation MASBrowserBasedAuthentication + +# pragma mark - Shared Service + ++ (instancetype)sharedInstance +{ + static id sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + sharedInstance = [[MASBrowserBasedAuthentication alloc] init]; + }); + + return sharedInstance; +} + + +-(void)loadWebLoginTemplate:(MASAuthCredentialsBlock)webLoginBlock +{ + self.webLoginCallBack = webLoginBlock; + MASModelService* service = [MASModelService sharedService]; + [[MASAuthorizationResponse sharedInstance] setDelegate:self]; + __block MASBrowserBasedAuthentication *blockSelf = self; + + // + // Try to register so that all the essential things are set up in that API call and we get a valid URL. If Application is already registered the API returns without doing any work. + // + [service registerApplication:^(BOOL completed, NSError *error) { + [blockSelf getURLForWebLogin]; + }]; +} + + +- (void)getURLForWebLogin +{ + // + // Headers + // + MASIMutableOrderedDictionary *headerInfo = [MASIMutableOrderedDictionary new]; + + // + // Parameters + // + MASIMutableOrderedDictionary *parameterInfo = [MASIMutableOrderedDictionary new]; + + // ClientId + parameterInfo[MASClientKeyRequestResponseKey] = [[MASAccessService sharedService] getAccessValueStringWithStorageKey:MASKeychainStorageKeyClientId]; + + // RedirectUri + parameterInfo[MASRedirectUriRequestResponseKey] = [[MASApplication currentApplication].redirectUri absoluteString]; + + // Scope + NSString *scope = [MASApplication currentApplication].scopeAsString; + + // + // Workaround - msso_register scope should NOT be used to retrieve authenticationProviders when it is going to be used to "authenticate" + // msso_register scope should only contain for device registration with authorizationCode. + // When the authroizationCode was granted with msso_register scope and used to retrieve the tokens, it will FAIL with unknown error from the server. + // + if (scope && [[MASDevice currentDevice] isRegistered]) + { + scope = [scope replaceStringWithRegexPattern:@"\\bmsso_register\\b" withString:@""]; + } + + // + // If sso is disabled, manually remove msso scope, as it will create id_token with msso scope + // + if (scope && ![MASConfiguration currentConfiguration].ssoEnabled) + { + scope = [scope replaceStringWithRegexPattern:@"\\bmsso\\b" withString:@""]; + } + + parameterInfo[MASScopeRequestResponseKey] = scope; + + // ResponseType + parameterInfo[MASRequestResponseTypeRequestResponseKey] = @"code"; + + // Display + parameterInfo[MASDisplayRequestResponseKey] = @"template"; + + // PKCE Support - generate code verifier + [[MASAccessService sharedService].currentAccessObj generateCodeVerifier]; + + // PKCE Support - generate state + [[MASAccessService sharedService].currentAccessObj generatePKCEState]; + + // Retrieve code verifier + NSString *codeVerifier = [[MASAccessService sharedService].currentAccessObj retrieveCodeVerifier]; + + // Retrieve state + NSString *pkceState = [[MASAccessService sharedService].currentAccessObj retrievePKCEState]; + + if (codeVerifier) + { + // SHA256 the code verifier and encode it with base64url + NSString *codeChallenge = [NSString base64URLWithNSData:[codeVerifier sha256Data]]; + + if (codeChallenge) + { + parameterInfo[MASPKCECodeChallengeRequestResponseKey] = codeChallenge; + // + // code_challenge_method should be S256 if the code challenge is hashed; + // + // Otherwise, make code_challenge = code_verifier, and send code_challenge_method as plan, MASPKCECodeChallengeMethodPlainKey + // + parameterInfo[MASPKCECodeChallengeMethodRequestResponseKey] = MASPKCECodeChallengeMethodSHA256Key; + + parameterInfo[MASPKCEStateRequestResponseKey] = pkceState; + } + } + + //Put the mag-identifier in the url as query parameter for the device to be identified + NSString* magIdentifier = [[MASAccessService sharedService] getAccessValueStringWithStorageKey:MASKeychainStorageKeyMAGIdentifier]; + + if(magIdentifier && magIdentifier.length > 0) + { + parameterInfo[MASMagIdentifierRequestResponseKey] = magIdentifier; + } + + // + // Endpoint + // + NSString *endPoint = [MASConfiguration currentConfiguration].authorizationEndpointPath; + + // + // preserve the redirection block that was set earlier + // + MASSessionDataTaskHTTPRedirectBlock previousRedirectionBlock = [[MASNetworkingService sharedService] httpRedirectionBlock]; + [[MASNetworkingService sharedService] setHttpRedirectionBlock:[self getRedirectionBlock]]; + + // + // This get request would result in a redirection which contains the actual URL to be loaded into browser and hence this would be canceled after the redirection + // + [[MASNetworkingService sharedService] getFrom:endPoint withParameters:parameterInfo andHeaders:headerInfo requestType:MASRequestResponseTypeWwwFormUrlEncoded responseType:MASRequestResponseTypeWwwFormUrlEncoded completion:^(NSDictionary* response, NSError* error){ + + if(error) + { + // + // This error is expected as we cancel the Get request after the redirection + // + DLog(@"original request to get the url cancelled"); + } + + [[MASNetworkingService sharedService] setHttpRedirectionBlock:previousRedirectionBlock]; + }]; +} + +-(MASSessionDataTaskHTTPRedirectBlock)getRedirectionBlock +{ + MASSessionDataTaskHTTPRedirectBlock redirectionBlock = ^(NSURLSession *session, NSURLSessionTask *task, NSURLResponse * response, NSURLRequest *request){ + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if(httpResponse.statusCode == 302 && [self isBBARedirection:task.originalRequest]) + { + DLog(@"all headers %@",httpResponse.allHeaderFields); + NSString* locationURL = [httpResponse.allHeaderFields objectForKey:@"Location"]; + NSURL* redirectURL = [NSURL URLWithString:locationURL]; + [task cancel]; + [self launchBrowserWithURL:redirectURL]; + } + return request; + }; + + return redirectionBlock; +} + +-(BOOL)isBBARedirection:(NSURLRequest*)request +{ + if([request.URL.absoluteString containsString:[MASConfiguration currentConfiguration].authorizationEndpointPath] && [request.URL.absoluteString containsString:@"display=template"]) + { + return YES; + } + + return NO; +} + +-(void)launchBrowserWithURL:(NSURL*)templatizedURL +{ + __block MASBrowserBasedAuthentication *blockSelf = self; + __weak __typeof__(self) weakSelf = self; + blockSelf.safariViewController = [[SFSafariViewController alloc] initWithURL:templatizedURL]; + + if (@available(iOS 11.0, *)) { + blockSelf.safariViewController.dismissButtonStyle = SFSafariViewControllerDismissButtonStyleCancel; + } + else { + // Fallback on earlier versions + } + blockSelf.safariViewController.delegate = weakSelf; + + __block UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:blockSelf.safariViewController]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [UIAlertController rootViewController].modalTransitionStyle = UIModalTransitionStyleCoverVertical; + + [[UIAlertController rootViewController] presentViewController:navigationController animated:YES + completion:^{ + + navigationController = nil; + }]; + + return; + }); +} + + +#pragma mark - SafariViewController Delegates + +-(void)safariViewControllerDidFinish:(SFSafariViewController *)controller +{ + self.webLoginCallBack(nil, YES, ^(BOOL completed, NSError* error){ + if(error) + { + DLog(@"Browser cancel clicked"); + } + }); +} + + +#pragma mark - Authorization Response delegate + +-(void)didReceiveAuthorizationCode:(NSString *)code +{ + MASAuthCredentialsAuthorizationCode *authCredentials = [MASAuthCredentialsAuthorizationCode initWithAuthorizationCode:code]; + [[MASNetworkingService sharedService] setHttpRedirectionBlock:nil]; + + self.webLoginCallBack(authCredentials, NO, ^(BOOL completed, NSError* error){ + // + // In either success or error case dismiss the browser and just log the status. The caller would pass the error state/success back to user. + // + if(error) + { + DLog(@"successfully logged in"); + } + DLog(@"successfully logged in"); + [self dismissBrowser]; + }); +} + + +-(void)didReceiveError:(NSError *)error +{ + self.webLoginCallBack(nil, YES, ^(BOOL completed, NSError* error){ + if(error) + { + DLog(@"Did not receive Authorization code"); + } + }); +} + +#pragma mark - UI + +-(void)dismissBrowser +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIAlertController rootViewController] dismissViewControllerAnimated:YES completion:nil]; + }); +} + +@end diff --git a/MASFoundation/Classes/models/MASUser.h b/MASFoundation/Classes/models/MASUser.h index ff7c6507..78f5c497 100644 --- a/MASFoundation/Classes/models/MASUser.h +++ b/MASFoundation/Classes/models/MASUser.h @@ -261,6 +261,16 @@ +/** + Authenticate a user by launching a Browser which in turn loads a URL (templatized). + + @param completion The MASCompletionErrorBlock block that receives the results. On a successful completion, the user + available via [MASUser currentUser] has been updated with the new information. + */ ++(void)initializeBrowserBasedAuthenticationWithCompletion:(MASCompletionErrorBlock _Nullable)completion; + + + /** * Requesting userInfo for the MASUser object. * This method will retrieve additional information on the MASUser object. diff --git a/MASFoundation/Classes/models/MASUser.m b/MASFoundation/Classes/models/MASUser.m index 9cc8fc67..34eaded7 100644 --- a/MASFoundation/Classes/models/MASUser.m +++ b/MASFoundation/Classes/models/MASUser.m @@ -250,6 +250,24 @@ + (void)loginWithAuthCredentials:(MASAuthCredentials *_Nonnull)authCredentials c } ++(void)initializeBrowserBasedAuthenticationWithCompletion:(MASCompletionErrorBlock _Nullable)completion +{ + if(![MASModelService browserBasedAuthentication]) + { + if(completion) completion(NO, [NSError errorBrowserBasedAuthenticaionNotEnabled]); + return; + } + + if ([MASUser currentUser] && [MASUser currentUser].isAuthenticated) + { + if(completion) completion(NO, [NSError errorUserAlreadyAuthenticated]); + + return; + } + + [[MASModelService sharedService] validateCurrentUserSession:completion]; +} + - (void)requestUserInfoWithCompletion:(MASUserResponseErrorBlock)completion { [[MASModelService sharedService] requestUserInfoWithCompletion:completion];