diff --git a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Parameters.json b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Parameters.json index 79b2c026..7b0df365 100644 --- a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Parameters.json +++ b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Parameters.json @@ -18,6 +18,7 @@ "Schema_Discoverability_Test", "SCIM_Null_Update_Test", "Validate_Credentials_Test", + "Federated_Identity_Test", "SCIM_User_Create_Test", "SCIM_Group_Create_Test", "SCIM_User_Update_Test", @@ -249,6 +250,55 @@ "type": "String", "value": "https://scimserver2.yellowsmoke-af7a3fff.eastus.azurecontainerapps.io/scim/oauth/token" }, + "federatedEntraTenantId": { + "metadata": { + "description": "Entra tenant ID used to construct the token endpoint URL for federated identity token acquisition." + }, + "type": "String", + "value": "changeme-tenant-id" + }, + "federatedApplicationId": { + "metadata": { + "description": "OAuth2 Application ID for the Entra app registration used in federated identity token acquisition." + }, + "type": "String", + "value": "changeme-client-id" + }, + "federatedApplicationClientSecret": { + "metadata": { + "description": "OAuth2 Client Secret for the Entra app registration used in federated identity token acquisition." + }, + "type": "String", + "value": "changeme-client-secret" + }, + "federatedTokenEndpoint": { + "metadata": { + "description": "ISV token endpoint URL for federated identity token exchange." + }, + "type": "String", + "value": "changeme-federated-endpoint" + }, + "federatedClientId": { + "metadata": { + "description": "ISV client ID used in federated identity token exchange." + }, + "type": "String", + "value": "changeme-federated-app-id" + }, + "federatedBaseAddress": { + "metadata": { + "description": "ISV base address sent in the federated identity token exchange request." + }, + "type": "String", + "value": "changeme-base-address" + }, + "federatedAudience": { + "metadata": { + "description": "Audience value for federated token exchange." + }, + "type": "String", + "value": "changeme-audience" + }, "servicePrincipalId": { "type": "String", "value": "3197053d-46d9-4a22-b843-6a9d98ec267e" diff --git a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Workflow.json b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Workflow.json index a1522f50..bcec0268 100644 --- a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Workflow.json +++ b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Workflow.json @@ -460,6 +460,7 @@ "scimUserUpdateTestEnabled": "@or(equals(parameters('EnabledTests'), 'All'), equals(parameters('EnabledTests'), 'SCIMTests'), equals(parameters('EnabledTests'), 'SCIM_User_Update_Test'))", "scimGroupUpdateTestEnabled": "@and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'], false), or(equals(parameters('EnabledTests'), 'All'), equals(parameters('EnabledTests'), 'SCIMTests'), equals(parameters('EnabledTests'), 'SCIM_Group_Update_Test')))", "validateCredentialsTestEnabled": "@or(equals(parameters('EnabledTests'), 'All'), equals(parameters('EnabledTests'), 'SCIMTests'), equals(parameters('EnabledTests'), 'Validate_Credentials_Test'))", + "federatedIdentityTestEnabled": "@or(equals(parameters('EnabledTests'), 'All'), equals(parameters('EnabledTests'), 'SCIMTests'), equals(parameters('EnabledTests'), 'Federated_Identity_Test'))", "scimUserPaginationTestEnabled": "@or(equals(parameters('EnabledTests'), 'All'), equals(parameters('EnabledTests'), 'SCIMTests'), equals(parameters('EnabledTests'), 'SCIM_User_Pagination_Test'))", "scimGroupPaginationTestEnabled": "@and(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'], false), or(equals(parameters('EnabledTests'), 'All'), equals(parameters('EnabledTests'), 'SCIMTests'), equals(parameters('EnabledTests'), 'SCIM_Group_Pagination_Test')))" }, @@ -485,6 +486,7 @@ "scimUserUpdateTestResult": "@coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserUpdateTestOutputs']?['overallResult'], 'SKIPPED')", "scimGroupUpdateTestResult": "@coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupUpdateTestOutputs']?['overallResult'], 'SKIPPED')", "validateCredentialsTestResult": "@coalesce(body('Get_SCIMTests_Output_Content')?['ValidateCredentialsTestOutputs']?['overallResult'], 'SKIPPED')", + "federatedIdentityTestResult": "@coalesce(body('Get_SCIMTests_Output_Content')?['FederatedIdentityTestOutputs']?['overallResult'], 'SKIPPED')", "scimUserPaginationTestResult": "@coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserPaginationTestOutputs']?['overallResult'], 'SKIPPED')", "scimGroupPaginationTestResult": "@coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupPaginationTestOutputs']?['overallResult'], 'SKIPPED')" }, @@ -500,7 +502,7 @@ "GroupTests_Workflow": "@outputs('Compose_GroupTests_RunLink')", "SCIMTests_Workflow": "@outputs('Compose_SCIMTests_RunLink')" }, - "overallLogicAppResult": "@if(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(equals(coalesce(body('Get_UserTests_Output_Content')?['CreateUserTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED'), equals(coalesce(body('Get_UserTests_Output_Content')?['UpdateUserTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_UserTests_Output_Content')?['DisableUserTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_UserTests_Output_Content')?['DeleteUserTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_UserTests_Output_Content')?['ManagerTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), or(equals(coalesce(body('Get_UserTests_Output_Content')?['RestoreUserTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED'), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMNullUpdateTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED'))), equals(coalesce(body('Get_SCIMTests_Output_Content')?['ValidateCredentialsTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['CreateGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['UpdateGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['DeleteGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['AddMemberTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['RemoveMemberTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SchemaDiscoverabilityTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserCreateTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupCreateTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserUpdateTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupUpdateTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserPaginationTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupPaginationTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_UserTests_Output_Content')?['PODUserTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['PODGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['RestoreGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(actions('Call_Initialization_Workflow')?['status'], 'Failed')), equals(coalesce(body('Poll_UserTests_Status')?['properties']?['status'], ''), 'Failed')), equals(coalesce(body('Poll_GroupTests_Status')?['properties']?['status'], ''), 'Failed')), 'Failed', if(equals(coalesce(body('Poll_SCIMTests_Status')?['properties']?['status'], ''), 'Failed'), 'Failed', 'Succeeded'))" + "overallLogicAppResult": "@if(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(or(equals(coalesce(body('Get_UserTests_Output_Content')?['CreateUserTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED'), equals(coalesce(body('Get_UserTests_Output_Content')?['UpdateUserTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_UserTests_Output_Content')?['DisableUserTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_UserTests_Output_Content')?['DeleteUserTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_UserTests_Output_Content')?['ManagerTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), or(equals(coalesce(body('Get_UserTests_Output_Content')?['RestoreUserTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED'), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMNullUpdateTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED'))), equals(coalesce(body('Get_SCIMTests_Output_Content')?['ValidateCredentialsTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['FederatedIdentityTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['CreateGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['UpdateGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['DeleteGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['AddMemberTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['RemoveMemberTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SchemaDiscoverabilityTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserCreateTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupCreateTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserUpdateTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupUpdateTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserPaginationTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupPaginationTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_UserTests_Output_Content')?['PODUserTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['PODGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['RestoreGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(actions('Call_Initialization_Workflow')?['status'], 'Failed')), equals(coalesce(body('Poll_UserTests_Status')?['properties']?['status'], ''), 'Failed')), equals(coalesce(body('Poll_GroupTests_Status')?['properties']?['status'], ''), 'Failed')), 'Failed', if(equals(coalesce(body('Poll_SCIMTests_Status')?['properties']?['status'], ''), 'Failed'), 'Failed', 'Succeeded'))" } }, "Final_TestResults": { @@ -656,6 +658,14 @@ "runLink": "@if(equals(coalesce(body('Get_SCIMTests_Output_Content')?['ValidateCredentialsTestOutputs']?['overallResult'], 'SKIPPED'), 'SKIPPED'), '', outputs('Compose_SCIMTests_RunLink'))", "message": "@if(equals(coalesce(body('Get_SCIMTests_Output_Content')?['ValidateCredentialsTestOutputs']?['overallResult'], 'SKIPPED'), 'SKIPPED'), '', 'Click the runLink and search for the action Compose_Final_Results for more info.')" }, + { + "testName": "Federated_Identity_Test", + "testResult": "@coalesce(body('Get_SCIMTests_Output_Content')?['FederatedIdentityTestOutputs']?['result'], 'SKIPPED')", + "provisioningErrorDetails": "@if(equals(coalesce(body('Get_SCIMTests_Output_Content')?['FederatedIdentityTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED'), body('Get_SCIMTests_Output_Content')?['FederatedIdentityTestOutputs']?['errorDetails'], '')", + "recommendationUrl": "@if(equals(coalesce(body('Get_SCIMTests_Output_Content')?['FederatedIdentityTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED'), 'https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/known-issues', '')", + "runLink": "@if(equals(coalesce(body('Get_SCIMTests_Output_Content')?['FederatedIdentityTestOutputs']?['overallResult'], 'SKIPPED'), 'SKIPPED'), '', outputs('Compose_SCIMTests_RunLink'))", + "message": "@if(equals(coalesce(body('Get_SCIMTests_Output_Content')?['FederatedIdentityTestOutputs']?['overallResult'], 'SKIPPED'), 'SKIPPED'), '', 'Click the runLink and search for the action Compose_Final_Results for more info.')" + }, { "testName": "SCIM_User_Pagination_Test", "testResult": "@coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUserPaginationTestOutputs']?['result'], 'SKIPPED')", diff --git a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/SCIMTests_Workflow.json b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/SCIMTests_Workflow.json index 13f5b197..9d56edc5 100644 --- a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/SCIMTests_Workflow.json +++ b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/SCIMTests_Workflow.json @@ -129,6 +129,19 @@ } } }, + { + "name": "FederatedIdentityTestOutputs", + "type": "Object", + "value": { + "overallResult": "SKIPPED", + "result": "SKIPPED", + "summary": null, + "detailedResults": [], + "errorDetails": { + "summary": null + } + } + }, { "name": "flattenedUserAttrNames", "type": "Array", @@ -2977,6 +2990,532 @@ }, "type": "If" }, + "Federated_Identity_Test": { + "runAfter": {}, + "actions": { + "Acquire_Entra_Token": { + "type": "Http", + "inputs": { + "uri": "@{concat('https://login.microsoftonline.com/', parameters('federatedEntraTenantId'), '/oauth2/v2.0/token')}", + "method": "POST", + "headers": { + "Content-Type": "application/x-www-form-urlencoded" + }, + "body": "@{concat('grant_type=client_credentials&client_id=', encodeUriComponent(parameters('federatedApplicationId')), '&client_secret=', encodeUriComponent(parameters('federatedApplicationClientSecret')), '&scope=', encodeUriComponent(concat('api://', parameters('federatedApplicationId'), '/.default')))}" + } + }, + "Check_Entra_Token_Acquired": { + "runAfter": { + "Acquire_Entra_Token": [ + "Succeeded", + "Failed", + "TimedOut" + ] + }, + "type": "If", + "expression": { + "and": [ + { + "greaterOrEquals": [ + "@coalesce(outputs('Acquire_Entra_Token')?['statusCode'], 0)", + 200 + ] + }, + { + "less": [ + "@coalesce(outputs('Acquire_Entra_Token')?['statusCode'], 0)", + 300 + ] + }, + { + "not": { + "equals": [ + "@coalesce(body('Acquire_Entra_Token')?['access_token'], '')", + "" + ] + } + } + ] + }, + "actions": { + "Check_Is_Google_SA_Flow": { + "type": "If", + "expression": { + "and": [ + { + "contains": [ + "@parameters('federatedTokenEndpoint')", + "googleapis.com" + ] + }, + { + "contains": [ + "@parameters('federatedTokenEndpoint')", + "serviceAccounts" + ] + } + ] + }, + "actions": { + "Acquire_Google_STS_Token": { + "type": "Http", + "inputs": { + "uri": "https://sts.googleapis.com/v1/token", + "method": "POST", + "headers": { + "Content-Type": "application/json" + }, + "body": { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": "@{parameters('federatedAudience')}", + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "subject_token": "@{body('Acquire_Entra_Token')?['access_token']}", + "subject_token_type": "urn:ietf:params:oauth:token-type:id_token", + "scope": "https://www.googleapis.com/auth/cloud-platform" + } + } + }, + "Check_Google_STS_Token": { + "runAfter": { + "Acquire_Google_STS_Token": [ + "Succeeded", + "Failed", + "TimedOut" + ] + }, + "type": "If", + "expression": { + "and": [ + { + "greaterOrEquals": [ + "@coalesce(outputs('Acquire_Google_STS_Token')?['statusCode'], 0)", + 200 + ] + }, + { + "less": [ + "@coalesce(outputs('Acquire_Google_STS_Token')?['statusCode'], 0)", + 300 + ] + }, + { + "not": { + "equals": [ + "@coalesce(body('Acquire_Google_STS_Token')?['access_token'], '')", + "" + ] + } + } + ] + }, + "actions": { + "Acquire_Google_SA_Token": { + "type": "Http", + "inputs": { + "uri": "@{parameters('federatedTokenEndpoint')}", + "method": "POST", + "headers": { + "Content-Type": "application/json", + "Authorization": "@{concat('Bearer ', body('Acquire_Google_STS_Token')?['access_token'])}" + }, + "body": { + "scope": [ + "https://www.googleapis.com/auth/cloud-platform" + ] + } + } + }, + "Check_Google_SA_Token": { + "runAfter": { + "Acquire_Google_SA_Token": [ + "Succeeded", + "Failed", + "TimedOut" + ] + }, + "type": "If", + "expression": { + "and": [ + { + "greaterOrEquals": [ + "@coalesce(outputs('Acquire_Google_SA_Token')?['statusCode'], 0)", + 200 + ] + }, + { + "less": [ + "@coalesce(outputs('Acquire_Google_SA_Token')?['statusCode'], 0)", + 300 + ] + }, + { + "not": { + "equals": [ + "@coalesce(body('Acquire_Google_SA_Token')?['accessToken'], '')", + "" + ] + } + } + ] + }, + "actions": { + "Compose_Federated_Validation_Summary_GoogleSA": { + "type": "Compose", + "inputs": { + "testName": "Federated_Identity_Test", + "timestamp": "@utcNow()", + "authMode": "FederatedIdentity_Google_SA", + "entraTokenAcquisition": { + "endpoint": "@concat('https://login.microsoftonline.com/', parameters('federatedEntraTenantId'), '/oauth2/v2.0/token')", + "statusCode": "@outputs('Acquire_Entra_Token')['statusCode']", + "passed": true + }, + "googleStsTokenExchange": { + "endpoint": "https://sts.googleapis.com/v1/token", + "statusCode": "@outputs('Acquire_Google_STS_Token')['statusCode']", + "passed": true + }, + "googleSaImpersonation": { + "endpoint": "@parameters('federatedTokenEndpoint')", + "statusCode": "@outputs('Acquire_Google_SA_Token')['statusCode']", + "passed": true + }, + "overallResult": "PASSED" + } + } + }, + "else": { + "actions": { + "Compose_Federated_Validation_Summary_GoogleSA_Fail": { + "type": "Compose", + "inputs": { + "testName": "Federated_Identity_Test", + "timestamp": "@utcNow()", + "authMode": "FederatedIdentity_Google_SA", + "entraTokenAcquisition": { + "passed": true + }, + "googleStsTokenExchange": { + "passed": true + }, + "googleSaImpersonation": { + "endpoint": "@parameters('federatedTokenEndpoint')", + "statusCode": "@coalesce(outputs('Acquire_Google_SA_Token')?['statusCode'], 0)", + "hasAccessToken": "@not(equals(coalesce(body('Acquire_Google_SA_Token')?['accessToken'], ''), ''))", + "passed": false + }, + "overallResult": "FAILED" + } + } + } + } + } + }, + "else": { + "actions": { + "Compose_Federated_Validation_Summary_GoogleSTS_Fail": { + "type": "Compose", + "inputs": { + "testName": "Federated_Identity_Test", + "timestamp": "@utcNow()", + "authMode": "FederatedIdentity_Google_SA", + "entraTokenAcquisition": { + "passed": true + }, + "googleStsTokenExchange": { + "endpoint": "https://sts.googleapis.com/v1/token", + "statusCode": "@coalesce(outputs('Acquire_Google_STS_Token')?['statusCode'], 0)", + "hasAccessToken": "@not(equals(coalesce(body('Acquire_Google_STS_Token')?['access_token'], ''), ''))", + "passed": false + }, + "overallResult": "FAILED" + } + } + } + } + } + }, + "else": { + "actions": { + "Check_Is_Google_STS_Flow": { + "type": "If", + "expression": { + "and": [ + { + "contains": [ + "@parameters('federatedTokenEndpoint')", + "googleapis.com" + ] + } + ] + }, + "actions": { + "Acquire_Google_STS_Token_Only": { + "type": "Http", + "inputs": { + "uri": "@{parameters('federatedTokenEndpoint')}", + "method": "POST", + "headers": { + "Content-Type": "application/json" + }, + "body": { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": "@{parameters('federatedAudience')}", + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "subject_token": "@{body('Acquire_Entra_Token')?['access_token']}", + "subject_token_type": "urn:ietf:params:oauth:token-type:id_token", + "scope": "https://www.googleapis.com/auth/cloud-platform" + } + } + }, + "Check_Google_STS_Only_Token": { + "runAfter": { + "Acquire_Google_STS_Token_Only": [ + "Succeeded", + "Failed", + "TimedOut" + ] + }, + "type": "If", + "expression": { + "and": [ + { + "greaterOrEquals": [ + "@coalesce(outputs('Acquire_Google_STS_Token_Only')?['statusCode'], 0)", + 200 + ] + }, + { + "less": [ + "@coalesce(outputs('Acquire_Google_STS_Token_Only')?['statusCode'], 0)", + 300 + ] + }, + { + "not": { + "equals": [ + "@coalesce(body('Acquire_Google_STS_Token_Only')?['access_token'], '')", + "" + ] + } + } + ] + }, + "actions": { + "Compose_Federated_Validation_Summary_GoogleSTSOnly": { + "type": "Compose", + "inputs": { + "testName": "Federated_Identity_Test", + "timestamp": "@utcNow()", + "authMode": "FederatedIdentity_Google_STS", + "entraTokenAcquisition": { + "endpoint": "@concat('https://login.microsoftonline.com/', parameters('federatedEntraTenantId'), '/oauth2/v2.0/token')", + "statusCode": "@outputs('Acquire_Entra_Token')['statusCode']", + "passed": true + }, + "googleStsTokenExchange": { + "endpoint": "@parameters('federatedTokenEndpoint')", + "statusCode": "@outputs('Acquire_Google_STS_Token_Only')['statusCode']", + "passed": true + }, + "overallResult": "PASSED" + } + } + }, + "else": { + "actions": { + "Compose_Federated_Validation_Summary_GoogleSTSOnly_Fail": { + "type": "Compose", + "inputs": { + "testName": "Federated_Identity_Test", + "timestamp": "@utcNow()", + "authMode": "FederatedIdentity_Google_STS", + "entraTokenAcquisition": { + "passed": true + }, + "googleStsTokenExchange": { + "endpoint": "@parameters('federatedTokenEndpoint')", + "statusCode": "@coalesce(outputs('Acquire_Google_STS_Token_Only')?['statusCode'], 0)", + "hasAccessToken": "@not(equals(coalesce(body('Acquire_Google_STS_Token_Only')?['access_token'], ''), ''))", + "passed": false + }, + "overallResult": "FAILED" + } + } + } + } + } + }, + "else": { + "actions": { + "Acquire_Federated_Token": { + "type": "Http", + "inputs": { + "uri": "@{parameters('federatedTokenEndpoint')}", + "method": "POST", + "headers": { + "Content-Type": "application/x-www-form-urlencoded" + }, + "body": "@{concat('grant_type=client_credentials&client_id=', encodeUriComponent(parameters('federatedClientId')), '&client_assertion=', encodeUriComponent(body('Acquire_Entra_Token')?['access_token']), '&client_assertion_type=', encodeUriComponent('urn:ietf:params:oauth:client-assertion-type:jwt-bearer'), '&baseAddress=', encodeUriComponent(parameters('federatedBaseAddress')))}" + } + }, + "Check_Federated_Token_Acquired": { + "runAfter": { + "Acquire_Federated_Token": [ + "Succeeded", + "Failed", + "TimedOut" + ] + }, + "type": "If", + "expression": { + "and": [ + { + "greaterOrEquals": [ + "@coalesce(outputs('Acquire_Federated_Token')?['statusCode'], 0)", + 200 + ] + }, + { + "less": [ + "@coalesce(outputs('Acquire_Federated_Token')?['statusCode'], 0)", + 300 + ] + }, + { + "not": { + "equals": [ + "@coalesce(body('Acquire_Federated_Token')?['access_token'], '')", + "" + ] + } + } + ] + }, + "actions": { + "Compose_Federated_Validation_Summary": { + "type": "Compose", + "inputs": { + "testName": "Federated_Identity_Test", + "timestamp": "@utcNow()", + "authMode": "FederatedIdentity", + "entraTokenAcquisition": { + "endpoint": "@concat('https://login.microsoftonline.com/', parameters('federatedEntraTenantId'), '/oauth2/v2.0/token')", + "statusCode": "@outputs('Acquire_Entra_Token')['statusCode']", + "passed": true + }, + "federatedTokenExchange": { + "endpoint": "@parameters('federatedTokenEndpoint')", + "statusCode": "@outputs('Acquire_Federated_Token')['statusCode']", + "passed": true + }, + "overallResult": "PASSED" + } + } + }, + "else": { + "actions": { + "Compose_Federated_Validation_Summary_TokenFail": { + "type": "Compose", + "inputs": { + "testName": "Federated_Identity_Test", + "timestamp": "@utcNow()", + "authMode": "FederatedIdentity", + "entraTokenAcquisition": { + "passed": true + }, + "federatedTokenExchange": { + "endpoint": "@parameters('federatedTokenEndpoint')", + "statusCode": "@coalesce(outputs('Acquire_Federated_Token')?['statusCode'], 0)", + "hasAccessToken": "@not(equals(coalesce(body('Acquire_Federated_Token')?['access_token'], ''), ''))", + "passed": false + }, + "overallResult": "FAILED" + } + } + } + } + } + } + } + } + } + } + } + }, + "else": { + "actions": { + "Compose_Federated_Validation_Summary_EntraFail": { + "type": "Compose", + "inputs": { + "testName": "Federated_Identity_Test", + "timestamp": "@utcNow()", + "authMode": "FederatedIdentity", + "entraTokenAcquisition": { + "endpoint": "@concat('https://login.microsoftonline.com/', parameters('federatedEntraTenantId'), '/oauth2/v2.0/token')", + "statusCode": "@coalesce(outputs('Acquire_Entra_Token')?['statusCode'], 0)", + "hasAccessToken": "@not(equals(coalesce(body('Acquire_Entra_Token')?['access_token'], ''), ''))", + "passed": false + }, + "federatedTokenExchange": null, + "scimEndpointValidation": null, + "overallResult": "FAILED" + } + } + } + } + }, + "Set_FederatedIdentityTestOutputs": { + "runAfter": { + "Check_Entra_Token_Acquired": [ + "Succeeded", + "Failed", + "TimedOut", + "Skipped" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "FederatedIdentityTestOutputs", + "value": { + "overallResult": "@coalesce(outputs('Compose_Federated_Validation_Summary')?['overallResult'], outputs('Compose_Federated_Validation_Summary_TokenFail')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSA')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSA_Fail')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSTS_Fail')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSTSOnly')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSTSOnly_Fail')?['overallResult'], outputs('Compose_Federated_Validation_Summary_EntraFail')?['overallResult'], 'FAILED')", + "result": "@if(equals(coalesce(outputs('Compose_Federated_Validation_Summary')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSA')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSTSOnly')?['overallResult'], outputs('Compose_Federated_Validation_Summary_TokenFail')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSA_Fail')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSTS_Fail')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSTSOnly_Fail')?['overallResult'], outputs('Compose_Federated_Validation_Summary_EntraFail')?['overallResult'], 'FAILED'), 'PASSED'), 'success', if(not(empty(coalesce(outputs('Compose_Federated_Validation_Summary_EntraFail'), json('null')))), 'Entra token acquisition failed', 'Federated token exchange failed'))", + "summary": "@coalesce(outputs('Compose_Federated_Validation_Summary'), outputs('Compose_Federated_Validation_Summary_TokenFail'), outputs('Compose_Federated_Validation_Summary_GoogleSA'), outputs('Compose_Federated_Validation_Summary_GoogleSA_Fail'), outputs('Compose_Federated_Validation_Summary_GoogleSTS_Fail'), outputs('Compose_Federated_Validation_Summary_GoogleSTSOnly'), outputs('Compose_Federated_Validation_Summary_GoogleSTSOnly_Fail'), outputs('Compose_Federated_Validation_Summary_EntraFail'))", + "detailedResults": [], + "errorDetails": { + "summary": "@if(equals(coalesce(outputs('Compose_Federated_Validation_Summary')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSA')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSTSOnly')?['overallResult'], outputs('Compose_Federated_Validation_Summary_TokenFail')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSA_Fail')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSTS_Fail')?['overallResult'], outputs('Compose_Federated_Validation_Summary_GoogleSTSOnly_Fail')?['overallResult'], outputs('Compose_Federated_Validation_Summary_EntraFail')?['overallResult'], 'FAILED'), 'PASSED'), null, if(not(empty(coalesce(outputs('Compose_Federated_Validation_Summary_EntraFail'), json('null')))), concat('Entra token acquisition failed. Status: ', string(coalesce(outputs('Acquire_Entra_Token')?['statusCode'], 0))), if(not(empty(coalesce(outputs('Compose_Federated_Validation_Summary_GoogleSTS_Fail'), json('null')))), concat('Google STS token exchange failed. Status: ', string(coalesce(outputs('Acquire_Google_STS_Token')?['statusCode'], 0))), if(not(empty(coalesce(outputs('Compose_Federated_Validation_Summary_GoogleSA_Fail'), json('null')))), concat('Google SA impersonation failed. Status: ', string(coalesce(outputs('Acquire_Google_SA_Token')?['statusCode'], 0))), if(not(empty(coalesce(outputs('Compose_Federated_Validation_Summary_GoogleSTSOnly_Fail'), json('null')))), concat('Google STS token exchange failed. Status: ', string(coalesce(outputs('Acquire_Google_STS_Token_Only')?['statusCode'], 0))), concat('Federated token exchange failed. Status: ', string(coalesce(outputs('Acquire_Federated_Token')?['statusCode'], 0))))))))" + } + } + } + } + }, + "else": { + "actions": {} + }, + "expression": { + "or": [ + { + "equals": [ + "@triggerBody()?['EnabledTests']", + "All" + ] + }, + { + "equals": [ + "@triggerBody()?['EnabledTests']", + "SCIMTests" + ] + }, + { + "equals": [ + "@triggerBody()?['EnabledTests']", + "Federated_Identity_Test" + ] + } + ] + }, + "type": "If" + }, "SCIM_User_Pagination_Test": { "runAfter": {}, "type": "If", @@ -3603,12 +4142,13 @@ "SchemaDiscoverabilityTestOutputs": "@variables('SchemaDiscoverabilityTestOutputs')", "SCIMNullUpdateTestOutputs": "@variables('SCIMNullUpdateTestOutputs')", "ValidateCredentialsTestOutputs": "@variables('ValidateCredentialsTestOutputs')", + "FederatedIdentityTestOutputs": "@variables('FederatedIdentityTestOutputs')", "SCIMUserCreateTestOutputs": "@variables('SCIMUserCreateTestOutputs')", "SCIMGroupCreateTestOutputs": "@variables('SCIMGroupCreateTestOutputs')", "SCIMUserUpdateTestOutputs": "@variables('SCIMUserUpdateTestOutputs')", "SCIMGroupUpdateTestOutputs": "@variables('SCIMGroupUpdateTestOutputs')", "testSuiteName": "SCIMTests", - "status": "@if(or(or(or(equals(variables('SchemaDiscoverabilityTestOutputs')['overallResult'], 'FAILED'), equals(variables('SCIMNullUpdateTestOutputs')['overallResult'], 'FAILED')), or(equals(variables('ValidateCredentialsTestOutputs')['overallResult'], 'FAILED'), equals(variables('SCIMUserCreateTestOutputs')['overallResult'], 'FAILED'))), or(or(equals(variables('SCIMGroupCreateTestOutputs')['overallResult'], 'FAILED'), equals(variables('SCIMUserUpdateTestOutputs')['overallResult'], 'FAILED')), equals(variables('SCIMGroupUpdateTestOutputs')['overallResult'], 'FAILED'))), 'Failed', 'Passed')", + "status": "@if(or(or(or(equals(variables('SchemaDiscoverabilityTestOutputs')['overallResult'], 'FAILED'), equals(variables('SCIMNullUpdateTestOutputs')['overallResult'], 'FAILED')), or(or(equals(variables('ValidateCredentialsTestOutputs')['overallResult'], 'FAILED'), equals(variables('FederatedIdentityTestOutputs')['overallResult'], 'FAILED')), equals(variables('SCIMUserCreateTestOutputs')['overallResult'], 'FAILED'))), or(or(equals(variables('SCIMGroupCreateTestOutputs')['overallResult'], 'FAILED'), equals(variables('SCIMUserUpdateTestOutputs')['overallResult'], 'FAILED')), equals(variables('SCIMGroupUpdateTestOutputs')['overallResult'], 'FAILED'))), 'Failed', 'Passed')", "workflowRunId": "@workflow().run.name", "SCIMUserPaginationTestOutputs": "@variables('SCIMUserPaginationTestOutputs')", "SCIMGroupPaginationTestOutputs": "@variables('SCIMGroupPaginationTestOutputs')" diff --git a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/VERSION b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/VERSION index 819e07a2..e0ea36fe 100644 --- a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/VERSION +++ b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/VERSION @@ -1 +1 @@ -5.0 +6.0