diff --git a/IdentityCore/tests/automation/ui_tests_lib/MSIDClientCredentialHelper.m b/IdentityCore/tests/automation/ui_tests_lib/MSIDClientCredentialHelper.m index 3d4df5dc9..59f6b284a 100644 --- a/IdentityCore/tests/automation/ui_tests_lib/MSIDClientCredentialHelper.m +++ b/IdentityCore/tests/automation/ui_tests_lib/MSIDClientCredentialHelper.m @@ -54,6 +54,17 @@ + (void)getAccessTokenForAuthority:(NSString *)authority clientCredential:(NSString *)clientCredential completionHandler:(void (^)(NSString *, NSError *))completionHandler { + // Validate required parameters + if (!authority || !resource || !clientId || !clientCredential) + { + if (completionHandler) + { + NSError *error = MSIDCreateError(MSIDErrorDomain, MSIDErrorInternal, @"Missing required parameters for client credential flow.", nil, nil, nil, nil, nil, YES); + completionHandler(nil, error); + } + return; + } + MSIDLegacyTokenCacheKey *cacheKey = [[MSIDLegacyTokenCacheKey alloc] initWithAuthority:[NSURL URLWithString:authority] clientId:clientId resource:resource @@ -91,6 +102,17 @@ + (void)getAccessTokenForAuthority:(NSString *)authorityString certificatePassword:(NSString *)password completionHandler:(void (^)(NSString *accessToken, NSError *error))completionHandler { + // Validate required parameters + if (!authorityString || !resource || !clientId || !certificateData || !password) + { + if (completionHandler) + { + NSError *error = MSIDCreateError(MSIDErrorDomain, MSIDErrorInternal, @"Missing required parameters for certificate-based client credential flow.", nil, nil, nil, nil, nil, YES); + completionHandler(nil, error); + } + return; + } + MSIDLegacyTokenCacheKey *cacheKey = [[MSIDLegacyTokenCacheKey alloc] initWithAuthority:[NSURL URLWithString:authorityString] clientId:clientId resource:resource diff --git a/IdentityCore/tests/automation/ui_tests_lib/MSIDTestConfigurationProvider.m b/IdentityCore/tests/automation/ui_tests_lib/MSIDTestConfigurationProvider.m index f54911fb8..d5d9f0637 100644 --- a/IdentityCore/tests/automation/ui_tests_lib/MSIDTestConfigurationProvider.m +++ b/IdentityCore/tests/automation/ui_tests_lib/MSIDTestConfigurationProvider.m @@ -58,6 +58,7 @@ - (instancetype)initWithClientCertificateContents:(NSString *)certificate defaultScopes:(NSDictionary *)defaultScopes defaultResources:(NSDictionary *)defaultResources operationAPIConf:(NSDictionary *)operationAPIConfiguration + functionAppAPIConf:(NSDictionary *)functionAppAPIConfiguration jitConfig:(NSDictionary *)jitConfig { self = [super init]; @@ -76,10 +77,10 @@ - (instancetype)initWithClientCertificateContents:(NSString *)certificate MSIDAutomationOperationAPIInMemoryCacheHandler *cacheHandler = [[MSIDAutomationOperationAPIInMemoryCacheHandler alloc] initWithDictionary:additionalConfigurations]; _operationAPIRequestHandler = [[MSIDAutomationOperationAPIRequestHandler alloc] initWithAPIPath:operationAPIConfiguration[@"operation_api_path"] - encodedCertificate:certificate certificatePassword:password - operationAPIConfiguration:operationAPIConfiguration]; + operationAPIConfiguration:operationAPIConfiguration + functionAppAPIConfiguration:functionAppAPIConfiguration]; _operationAPIRequestHandler.apiCacheHandler = cacheHandler; _passwordRequestHandler = [MSIDAutomationPasswordRequestHandler new]; @@ -148,6 +149,7 @@ - (instancetype)initWithConfigurationPath:(NSString *)configurationPath defaultScopes:configurationDictionary[@"scopes"] defaultResources:configurationDictionary[@"resources"] operationAPIConf:configurationDictionary[@"operation_api_conf"] + functionAppAPIConf:configurationDictionary[@"function_app_api_url"] jitConfig:configurationDictionary[@"jit_intune_ids"]]; } diff --git a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationDeleteDeviceAPIRequest.m b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationDeleteDeviceAPIRequest.m index 62abeb1f7..4a8193f53 100644 --- a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationDeleteDeviceAPIRequest.m +++ b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationDeleteDeviceAPIRequest.m @@ -34,7 +34,7 @@ - (NSString *)requestOperationPath - (NSString *)httpMethod { - return @"DELETE"; + return @"POST"; } - (NSArray *)queryItems diff --git a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationOperationAPIRequestHandler.h b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationOperationAPIRequestHandler.h index 64f964350..0fe866f04 100644 --- a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationOperationAPIRequestHandler.h +++ b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationOperationAPIRequestHandler.h @@ -45,7 +45,8 @@ - (instancetype)initWithAPIPath:(NSString *)apiPath encodedCertificate:(NSString *)encodedCertificate certificatePassword:(NSString *)certificatePassword - operationAPIConfiguration:(NSDictionary *)operationAPIConfiguration; + operationAPIConfiguration:(NSDictionary *)operationAPIConfiguration + functionAppAPIConfiguration:(NSDictionary *)functionAppAPIConfiguration; - (void)executeAPIRequest:(MSIDAutomationBaseApiRequest *)apiRequest responseHandler:(id)responseHandler diff --git a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationOperationAPIRequestHandler.m b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationOperationAPIRequestHandler.m index 15f5c7e01..696fc821d 100644 --- a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationOperationAPIRequestHandler.m +++ b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationOperationAPIRequestHandler.m @@ -29,6 +29,7 @@ @interface MSIDAutomationOperationAPIRequestHandler() @property (nonatomic) NSString *labAPIPath; @property (nonatomic) NSDictionary *configurationParams; +@property (nonatomic) NSDictionary *functionAppURL; @property (nonatomic) NSString *encodedCertificate; @property (nonatomic) NSString *certificatePassword; @@ -42,6 +43,7 @@ - (instancetype)initWithAPIPath:(NSString *)apiPath encodedCertificate:(NSString *)encodedCertificate certificatePassword:(NSString *)certificatePassword operationAPIConfiguration:(NSDictionary *)operationAPIConfiguration + functionAppAPIConfiguration:(NSDictionary *)functionAppAPIConfiguration { self = [super init]; @@ -49,6 +51,7 @@ - (instancetype)initWithAPIPath:(NSString *)apiPath { _labAPIPath = apiPath; _configurationParams = operationAPIConfiguration; + _functionAppURL = functionAppAPIConfiguration; _encodedCertificate = encodedCertificate; _certificatePassword = certificatePassword; } @@ -56,6 +59,16 @@ - (instancetype)initWithAPIPath:(NSString *)apiPath return self; } +- (instancetype)initWithAPIPath:(NSString *)apiPath + operationAPIConfiguration:(NSDictionary *)operationAPIConfiguration +{ + return [self initWithAPIPath:apiPath + encodedCertificate:nil + certificatePassword:nil + operationAPIConfiguration:operationAPIConfiguration + functionAppAPIConfiguration:nil]; +} + #pragma mark - Public - (void)executeAPIRequest:(MSIDAutomationBaseApiRequest *)apiRequest @@ -76,16 +89,44 @@ - (void)executeAPIRequest:(MSIDAutomationBaseApiRequest *)apiRequest return; } - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self getAccessTokenAndCallLabAPI:apiRequest - responseHandler:responseHandler - completionHandler:^(id result, NSError *error) - { - dispatch_async(dispatch_get_main_queue(), ^{ - completionHandler(result, error); - }); - }]; - }); + // Check if we should use function app URLs (with bearer token authentication) + // Only use Function App URL for operation requests, not for app configuration queries + if (self.functionAppURL && self.functionAppURL[@"operation_api_path"] && [self shouldUseFunctionAppURLForRequest:apiRequest]) + { + // Use function app URL API with bearer token + [self getAccessTokenAndCallFunctionAppAPI:apiRequest + responseHandler:responseHandler + completionHandler:completionHandler]; + return; + } + + // Fall back to OAuth-based API (legacy) - used for app configuration and other queries + [self getAccessTokenAndCallLabAPI:apiRequest + responseHandler:responseHandler + completionHandler:completionHandler]; +} + +#pragma mark - Helper + +- (BOOL)shouldUseFunctionAppURLForRequest:(MSIDAutomationBaseApiRequest *)request +{ + // Get the class name of the request + NSString *className = NSStringFromClass([request class]); + + // Function App URL should only be used for operation requests: + // - MSIDAutomationResetAPIRequest (password reset) + // - MSIDAutomationTemporaryAccountRequest (create temp user) + // - MSIDAutomationDeleteDeviceAPIRequest (delete device) + // - MSIDAutomationPolicyToggleAPIRequest (enable/disable policy) + // + // NOT for: + // - MSIDTestAutomationAppConfigurationRequest (app configuration queries) + // - Any other query/read requests + + return [className isEqualToString:@"MSIDAutomationResetAPIRequest"] || + [className isEqualToString:@"MSIDAutomationTemporaryAccountRequest"] || + [className isEqualToString:@"MSIDAutomationDeleteDeviceAPIRequest"] || + [className isEqualToString:@"MSIDAutomationPolicyToggleAPIRequest"]; } #pragma mark - Get access token @@ -94,44 +135,138 @@ - (void)getAccessTokenAndCallLabAPI:(MSIDAutomationBaseApiRequest *)request responseHandler:(id)responseHandler completionHandler:(void (^)(id result, NSError *error))completionHandler { - NSData *base64EncodedCert = [[NSData alloc] initWithBase64EncodedString:self.encodedCertificate options:0]; - if (!base64EncodedCert || base64EncodedCert.length == 0) { - NSLog(@"Couldn't fetch certificate data, make sure certificate path is correct"); - return; - } - - [MSIDClientCredentialHelper getAccessTokenForAuthority:self.configurationParams[@"operation_api_authority"] - resource:self.configurationParams[@"operation_api_resource"] - clientId:self.configurationParams[@"operation_api_client_id"] - certificate:base64EncodedCert - certificatePassword:self.certificatePassword - completionHandler:^(NSString *accessToken, NSError *error) - { + // Use certificate-based authentication if certificate is provided + if (self.encodedCertificate && self.certificatePassword) + { + NSData *certificateData = [[NSData alloc] initWithBase64EncodedString:self.encodedCertificate options:0]; - if (!accessToken) - { - dispatch_async(dispatch_get_main_queue(), ^{ - completionHandler(nil, error); - }); - return; - } + [MSIDClientCredentialHelper getAccessTokenForAuthority:self.configurationParams[@"operation_api_authority"] + resource:self.configurationParams[@"operation_api_resource"] + clientId:self.configurationParams[@"operation_api_client_id"] + certificate:certificateData + certificatePassword:self.certificatePassword + completionHandler:^(NSString *accessToken, NSError *error) { + + if (!accessToken) + { + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(nil, error); + }); + return; + } + + [self executeAPIRequestWithAccessToken:request + responseHandler:responseHandler + accessToken:accessToken + apiPath:self.labAPIPath + completionHandler:completionHandler]; + }]; + } + else + { + // Fall back to client secret authentication + [MSIDClientCredentialHelper getAccessTokenForAuthority:self.configurationParams[@"operation_api_authority"] + resource:self.configurationParams[@"operation_api_resource"] + clientId:self.configurationParams[@"operation_api_client_id"] + clientCredential:self.configurationParams[@"operation_api_client_secret"] + completionHandler:^(NSString *accessToken, NSError *error) { + + if (!accessToken) + { + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(nil, error); + }); + return; + } + + [self executeAPIRequestWithAccessToken:request + responseHandler:responseHandler + accessToken:accessToken + apiPath:self.labAPIPath + completionHandler:completionHandler]; + }]; + } +} - [self executeAPIRequestImpl:request - responseHandler:responseHandler - accessToken:accessToken - completionHandler:completionHandler]; - }]; +- (void)getAccessTokenAndCallFunctionAppAPI:(MSIDAutomationBaseApiRequest *)request + responseHandler:(id)responseHandler + completionHandler:(void (^)(id result, NSError *error))completionHandler +{ + // Use certificate-based authentication to get bearer token + if (self.encodedCertificate && self.certificatePassword) + { + NSData *certificateData = [[NSData alloc] initWithBase64EncodedString:self.encodedCertificate options:0]; + + [MSIDClientCredentialHelper getAccessTokenForAuthority:self.configurationParams[@"operation_api_authority"] + resource:self.configurationParams[@"operation_api_resource"] + clientId:self.configurationParams[@"operation_api_client_id"] + certificate:certificateData + certificatePassword:self.certificatePassword + completionHandler:^(NSString *accessToken, NSError *error) { + + if (!accessToken) + { + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(nil, error); + }); + return; + } + + NSString *functionAppAPIPath = self.functionAppURL[@"operation_api_path"]; + [self executeAPIRequestWithAccessToken:request + responseHandler:responseHandler + accessToken:accessToken + apiPath:functionAppAPIPath + completionHandler:completionHandler]; + }]; + } + else + { + // Fall back to client secret authentication + [MSIDClientCredentialHelper getAccessTokenForAuthority:self.configurationParams[@"operation_api_authority"] + resource:self.configurationParams[@"operation_api_resource"] + clientId:self.configurationParams[@"operation_api_client_id"] + clientCredential:self.configurationParams[@"operation_api_client_secret"] + completionHandler:^(NSString *accessToken, NSError *error) { + + if (!accessToken) + { + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(nil, error); + }); + return; + } + + NSString *functionAppAPIPath = self.functionAppURL[@"operation_api_path"]; + [self executeAPIRequestWithAccessToken:request + responseHandler:responseHandler + accessToken:accessToken + apiPath:functionAppAPIPath + completionHandler:completionHandler]; + }]; + } } -#pragma mark - Execute request +#pragma mark - Execute API request with bearer token -- (void)executeAPIRequestImpl:(MSIDAutomationBaseApiRequest *)request - responseHandler:(id)responseHandler - accessToken:(NSString *)accessToken - completionHandler:(void (^)(id result, NSError *error))completionHandler +- (void)executeAPIRequestWithAccessToken:(MSIDAutomationBaseApiRequest *)request + responseHandler:(id)responseHandler + accessToken:(NSString *)accessToken + apiPath:(NSString *)apiPath + completionHandler:(void (^)(id result, NSError *error))completionHandler { + NSURL *resultURL = [request requestURLWithAPIPath:apiPath]; - NSURL *resultURL = [request requestURLWithAPIPath:self.labAPIPath]; + if (!resultURL) + { + NSError *error = [NSError errorWithDomain:@"MSIDAutomationOperationAPIRequestHandler" + code:-1 + userInfo:@{NSLocalizedDescriptionKey: @"Failed to build API URL"}]; + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(nil, error); + }); + return; + } NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:resultURL]; NSString *bearerHeader = [NSString stringWithFormat:@"Bearer %@", accessToken]; @@ -160,8 +295,22 @@ - (void)executeAPIRequestImpl:(MSIDAutomationBaseApiRequest *)request return; } + NSError *apiError = error; + if (!apiError && httpResponse) + { + NSString *errorMessage = [NSString stringWithFormat:@"API request failed with status code: %ld", (long)httpResponse.statusCode]; + if (data) + { + NSString *responseBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + errorMessage = [errorMessage stringByAppendingFormat:@"\nResponse: %@", responseBody]; + } + apiError = [NSError errorWithDomain:@"MSIDAutomationOperationAPIRequestHandler" + code:httpResponse.statusCode + userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; + } + dispatch_async(dispatch_get_main_queue(), ^{ - completionHandler(nil, error); + completionHandler(nil, apiError); }); }] resume]; diff --git a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationPolicyToggleAPIRequest.m b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationPolicyToggleAPIRequest.m index e2e08afff..a2c4db7ea 100644 --- a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationPolicyToggleAPIRequest.m +++ b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationPolicyToggleAPIRequest.m @@ -34,7 +34,7 @@ - (NSString *)requestOperationPath - (NSString *)httpMethod { - return @"PUT"; + return @"POST"; } - (NSArray *)queryItems diff --git a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationResetAPIRequest.m b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationResetAPIRequest.m index ea16a7f83..b8d64d2d2 100644 --- a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationResetAPIRequest.m +++ b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationResetAPIRequest.m @@ -34,7 +34,7 @@ - (NSString *)requestOperationPath - (NSString *)httpMethod { - return @"PUT"; + return @"POST"; } - (NSArray *)queryItems