diff --git a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Initialization_Workflow.json b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Initialization_Workflow.json
index e4661de9..5735f313 100644
--- a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Initialization_Workflow.json
+++ b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Initialization_Workflow.json
@@ -952,7 +952,7 @@
"type": "Select",
"inputs": {
"from": "@body('Compose_SCIM_User_Extension_URNs')",
- "select": "@join(take(split(item()?['name'], ':'), sub(length(split(item()?['name'], ':')), 1)), ':')"
+ "select": "@substring(item()?['name'], 0, lastIndexOf(item()?['name'], ':'))"
}
},
"Compose_SCIM_User_Schemas": {
@@ -1025,7 +1025,7 @@
]
},
"type": "Compose",
- "inputs": "@if(or(or(or(or(equals(items('ForEach_Build_SCIM_Body')?['name'], 'id'), equals(toLower(coalesce(items('ForEach_Build_SCIM_Body')?['type'], 'string')), 'reference')), equals(items('ForEach_Build_SCIM_Body')?['name'], 'externalId')), or(equals(items('ForEach_Build_SCIM_Body')?['name'], 'userName'), equals(items('ForEach_Build_SCIM_Body')?['name'], 'active'))), equals(items('ForEach_Build_SCIM_Body')?['name'], 'roles')), variables('scimDirectUserCreateBody'), if(startsWith(items('ForEach_Build_SCIM_Body')?['name'], 'urn:'), union(variables('scimDirectUserCreateBody'), json(concat('{\"', join(take(split(items('ForEach_Build_SCIM_Body')?['name'], ':'), sub(length(split(items('ForEach_Build_SCIM_Body')?['name'], ':')), 1)), ':'), '\":', string(setProperty(coalesce(variables('scimDirectUserCreateBody')?[join(take(split(items('ForEach_Build_SCIM_Body')?['name'], ':'), sub(length(split(items('ForEach_Build_SCIM_Body')?['name'], ':')), 1)), ':')], json('{}')), last(split(items('ForEach_Build_SCIM_Body')?['name'], ':')), outputs('Compose_SCIM_Source_Value'))), '}'))), if(contains(items('ForEach_Build_SCIM_Body')?['name'], '['), setProperty(variables('scimDirectUserCreateBody'), first(split(items('ForEach_Build_SCIM_Body')?['name'], '[')), setProperty(coalesce(variables('scimDirectUserCreateBody')?[first(split(items('ForEach_Build_SCIM_Body')?['name'], '['))], json('{}')), first(skip(split(items('ForEach_Build_SCIM_Body')?['name'], '\"'), 1)), setProperty(coalesce(variables('scimDirectUserCreateBody')?[first(split(items('ForEach_Build_SCIM_Body')?['name'], '['))]?[first(skip(split(items('ForEach_Build_SCIM_Body')?['name'], '\"'), 1))], json(concat('{\"type\":\"', first(skip(split(items('ForEach_Build_SCIM_Body')?['name'], '\"'), 1)), '\",\"primary\":', if(not(contains(string(coalesce(variables('scimDirectUserCreateBody')?[first(split(items('ForEach_Build_SCIM_Body')?['name'], '['))], json('{}'))), '\"primary\":true')), 'true', 'false'), '}'))), last(split(items('ForEach_Build_SCIM_Body')?['name'], '.')), outputs('Compose_SCIM_Source_Value')))), if(contains(items('ForEach_Build_SCIM_Body')?['name'], '.'), setProperty(variables('scimDirectUserCreateBody'), first(split(items('ForEach_Build_SCIM_Body')?['name'], '.')), setProperty(coalesce(variables('scimDirectUserCreateBody')?[first(split(items('ForEach_Build_SCIM_Body')?['name'], '.'))], json('{}')), last(split(items('ForEach_Build_SCIM_Body')?['name'], '.')), outputs('Compose_SCIM_Source_Value'))), setProperty(variables('scimDirectUserCreateBody'), items('ForEach_Build_SCIM_Body')?['name'], outputs('Compose_SCIM_Source_Value'))))))"
+ "inputs": "@if(or(or(or(or(equals(items('ForEach_Build_SCIM_Body')?['name'], 'id'), equals(toLower(coalesce(items('ForEach_Build_SCIM_Body')?['type'], 'string')), 'reference')), equals(items('ForEach_Build_SCIM_Body')?['name'], 'externalId')), or(equals(items('ForEach_Build_SCIM_Body')?['name'], 'userName'), equals(items('ForEach_Build_SCIM_Body')?['name'], 'active'))), equals(items('ForEach_Build_SCIM_Body')?['name'], 'roles')), variables('scimDirectUserCreateBody'), if(startsWith(items('ForEach_Build_SCIM_Body')?['name'], 'urn:'), union(variables('scimDirectUserCreateBody'), json(concat('{\"', substring(items('ForEach_Build_SCIM_Body')?['name'], 0, lastIndexOf(items('ForEach_Build_SCIM_Body')?['name'], ':')), '\":', string(setProperty(coalesce(variables('scimDirectUserCreateBody')?[substring(items('ForEach_Build_SCIM_Body')?['name'], 0, lastIndexOf(items('ForEach_Build_SCIM_Body')?['name'], ':'))], json('{}')), last(split(items('ForEach_Build_SCIM_Body')?['name'], ':')), outputs('Compose_SCIM_Source_Value'))), '}'))), if(contains(items('ForEach_Build_SCIM_Body')?['name'], '['), setProperty(variables('scimDirectUserCreateBody'), first(split(items('ForEach_Build_SCIM_Body')?['name'], '[')), setProperty(coalesce(variables('scimDirectUserCreateBody')?[first(split(items('ForEach_Build_SCIM_Body')?['name'], '['))], json('{}')), first(skip(split(items('ForEach_Build_SCIM_Body')?['name'], '\"'), 1)), setProperty(coalesce(variables('scimDirectUserCreateBody')?[first(split(items('ForEach_Build_SCIM_Body')?['name'], '['))]?[first(skip(split(items('ForEach_Build_SCIM_Body')?['name'], '\"'), 1))], json(concat('{\"type\":\"', first(skip(split(items('ForEach_Build_SCIM_Body')?['name'], '\"'), 1)), '\",\"primary\":', if(not(contains(string(coalesce(variables('scimDirectUserCreateBody')?[first(split(items('ForEach_Build_SCIM_Body')?['name'], '['))], json('{}'))), '\"primary\":true')), 'true', 'false'), '}'))), last(split(items('ForEach_Build_SCIM_Body')?['name'], '.')), outputs('Compose_SCIM_Source_Value')))), if(contains(items('ForEach_Build_SCIM_Body')?['name'], '.'), setProperty(variables('scimDirectUserCreateBody'), first(split(items('ForEach_Build_SCIM_Body')?['name'], '.')), setProperty(coalesce(variables('scimDirectUserCreateBody')?[first(split(items('ForEach_Build_SCIM_Body')?['name'], '.'))], json('{}')), last(split(items('ForEach_Build_SCIM_Body')?['name'], '.')), outputs('Compose_SCIM_Source_Value'))), setProperty(variables('scimDirectUserCreateBody'), items('ForEach_Build_SCIM_Body')?['name'], outputs('Compose_SCIM_Source_Value'))))))"
},
"Set_SCIM_Body_Property": {
"runAfter": {
diff --git a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Parameters.json b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Parameters.json
index 97556e9a..bd852a36 100644
--- a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Parameters.json
+++ b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Parameters.json
@@ -22,6 +22,7 @@
"SCIM_User_Create_Test",
"SCIM_Group_Create_Test",
"SCIM_User_Update_Test",
+ "SCIM_Update_Manager_Test",
"SCIM_Group_Update_Test",
"SCIM_User_Pagination_Test",
"SCIM_Group_Pagination_Test",
@@ -38,7 +39,7 @@
"description": "Version of the Logic App for tracking and deployment purposes"
},
"type": "String",
- "value": "4.0"
+ "value": "8.0"
},
"attributeTransforms": {
"metadata": {
@@ -226,7 +227,7 @@
},
"scimEndpoint": {
"type": "String",
- "value": "https://scimserver2.yellowsmoke-af7a3fff.eastus.azurecontainerapps.io/scim/endpoints/8f3c4c4a-7b5d-4e91-9a62-2d7f1b8e5c73"
+ "value": "https://scimserver2.yellowsmoke-af7a3fff.eastus.azurecontainerapps.io/scim/endpoints/f4c95b63-db4c-4f0a-91b0-78fdf88007be"
},
"scimOAuthScope": {
"metadata": {
@@ -243,61 +244,61 @@
"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."
+ "metadata": {
+ "description": "Entra tenant ID used to construct the token endpoint URL for federated identity token acquisition."
+ },
+ "type": "String",
+ "value": "changeme-tenant-id"
},
- "type": "String",
- "value": "changeme-tenant-id"
- },
- "federatedApplicationId": {
- "metadata": {
- "description": "OAuth2 Application ID for the Entra app registration used in federated identity token acquisition."
+ "federatedApplicationId": {
+ "metadata": {
+ "description": "OAuth2 Application ID for the Entra app registration used in federated identity token acquisition."
+ },
+ "type": "String",
+ "value": "changeme-client-id"
},
- "type": "String",
- "value": "changeme-client-id"
- },
- "federatedApplicationClientSecret": {
- "metadata": {
- "description": "OAuth2 Client Secret for the Entra app registration used in federated identity token acquisition."
+ "federatedApplicationClientSecret": {
+ "metadata": {
+ "description": "OAuth2 Client Secret for the Entra app registration used in federated identity token acquisition."
+ },
+ "type": "String",
+ "value": "changeme-client-secret"
},
- "type": "String",
- "value": "changeme-client-secret"
- },
- "federatedTokenEndpoint": {
- "metadata": {
- "description": "ISV token endpoint URL for federated identity token exchange."
+ "federatedTokenEndpoint": {
+ "metadata": {
+ "description": "ISV token endpoint URL for federated identity token exchange."
+ },
+ "type": "String",
+ "value": "changeme-federated-endpoint"
},
- "type": "String",
- "value": "changeme-federated-endpoint"
- },
- "federatedClientId": {
- "metadata": {
- "description": "ISV client ID used in federated identity token exchange."
+ "federatedClientId": {
+ "metadata": {
+ "description": "ISV client ID used in federated identity token exchange."
+ },
+ "type": "String",
+ "value": "changeme-federated-app-id"
},
- "type": "String",
- "value": "changeme-federated-app-id"
- },
- "federatedBaseAddress": {
- "metadata": {
- "description": "ISV base address sent in the federated identity token exchange request."
+ "federatedBaseAddress": {
+ "metadata": {
+ "description": "ISV base address sent in the federated identity token exchange request."
+ },
+ "type": "String",
+ "value": "changeme-base-address"
},
- "type": "String",
- "value": "changeme-base-address"
- },
- "federatedAudience": {
- "metadata": {
- "description": "Audience value for federated token exchange."
+ "federatedAudience": {
+ "metadata": {
+ "description": "Audience value for federated token exchange."
+ },
+ "type": "String",
+ "value": "changeme-audience"
},
- "type": "String",
- "value": "changeme-audience"
- },
"servicePrincipalId": {
"type": "String",
- "value": "ed1a4e07-8807-4362-8b9b-c8edc2b51747"
+ "value": "3197053d-46d9-4a22-b843-6a9d98ec267e"
},
"testUserDomain": {
"type": "String",
- "value": "proviamla01.onmicrosoft.com"
+ "value": "proviamtest07.onmicrosoft.com"
},
"scimTargetUserValues": {
"type": "Array",
diff --git a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Workflow.json b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Workflow.json
index d7745e7b..bd8a91d8 100644
--- a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Workflow.json
+++ b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/Orchestrator_Workflow.json
@@ -462,7 +462,8 @@
"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": "@or(equals(parameters('EnabledTests'), 'All'), equals(parameters('EnabledTests'), 'SCIMTests'), equals(parameters('EnabledTests'), 'SCIM_Group_Pagination_Test'))"
+ "scimGroupPaginationTestEnabled": "@or(equals(parameters('EnabledTests'), 'All'), equals(parameters('EnabledTests'), 'SCIMTests'), equals(parameters('EnabledTests'), 'SCIM_Group_Pagination_Test'))",
+ "scimUpdateManagerTestEnabled": "@and(contains(string(coalesce(body('Call_Initialization_Workflow')?['targetDirectoryUserAttributes'], json('[]'))), 'manager'), or(equals(parameters('EnabledTests'), 'All'), equals(parameters('EnabledTests'), 'SCIMTests'), equals(parameters('EnabledTests'), 'SCIM_Update_Manager_Test')))"
},
"testResults": {
"createUserTestResult": "@coalesce(body('Get_UserTests_Output_Content')?['CreateUserTestOutputs']?['overallResult'], 'SKIPPED')",
@@ -488,7 +489,8 @@
"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')"
+ "scimGroupPaginationTestResult": "@coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupPaginationTestOutputs']?['overallResult'], 'SKIPPED')",
+ "scimUpdateManagerTestResult": "@coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUpdateManagerTestOutputs']?['overallResult'], 'SKIPPED')"
},
"childWorkflowStatuses": {
"initializationStatus": "@coalesce(actions('Call_Initialization_Workflow')?['status'], 'Unknown')",
@@ -502,7 +504,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(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')?['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')?['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_UserTests_Output_Content')?['PODUserTestOutputs']?['overallResult'],'SKIPPED'),'FAILED')),equals(coalesce(body('Get_GroupTests_Output_Content')?['PODGroupTestOutputs']?['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')),and(not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['ValidateCredentialsTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['FederatedIdentityTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')))),not(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false))),'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')?['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')?['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_UserTests_Output_Content')?['PODUserTestOutputs']?['overallResult'],'SKIPPED'),'FAILED')),equals(coalesce(body('Get_GroupTests_Output_Content')?['PODGroupTestOutputs']?['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')),and(not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['ValidateCredentialsTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')),not(equals(coalesce(body('Get_SCIMTests_Output_Content')?['FederatedIdentityTestOutputs']?['overallResult'],'SKIPPED'),'PASSED')))),not(coalesce(body('Call_Initialization_Workflow')?['isGroupSupported'],false))),and(contains(string(coalesce(body('Call_Initialization_Workflow')?['targetDirectoryUserAttributes'],json('[]'))),'manager'),equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUpdateManagerTestOutputs']?['overallResult'],'SKIPPED'),'FAILED'))),'Failed',if(equals(coalesce(body('Poll_SCIMTests_Status')?['properties']?['status'],''),'Failed'),'Failed','Succeeded'))",
"optionalTestsResult": "@if(or(or(or(equals(coalesce(body('Get_UserTests_Output_Content')?['DeleteUserTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED'), equals(coalesce(body('Get_GroupTests_Output_Content')?['DeleteGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_GroupTests_Output_Content')?['RestoreGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMGroupPaginationTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED')), 'WARNING', 'PASSED')",
"authenticationModelResult": "@if(or(equals(coalesce(body('Get_SCIMTests_Output_Content')?['ValidateCredentialsTestOutputs']?['overallResult'], 'SKIPPED'), 'PASSED'), equals(coalesce(body('Get_SCIMTests_Output_Content')?['FederatedIdentityTestOutputs']?['overallResult'], 'SKIPPED'), 'PASSED')), 'PASSED', 'FAILED')"
}
@@ -723,6 +725,15 @@
"runLink": "@if(equals(coalesce(body('Get_GroupTests_Output_Content')?['PODGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'SKIPPED'), '', outputs('Compose_GroupTests_RunLink'))",
"message": "@if(equals(coalesce(body('Get_GroupTests_Output_Content')?['PODGroupTestOutputs']?['overallResult'], 'SKIPPED'), 'SKIPPED'), '', 'Click the runLink and search for the action Compose_Final_Results for more info.')"
},
+ {
+ "testName": "SCIM_Update_Manager_Test",
+ "testCategory": "mandatory",
+ "testResult": "@if(not(contains(string(coalesce(body('Call_Initialization_Workflow')?['targetDirectoryUserAttributes'], json('[]'))), 'manager')), 'SKIPPED - manager attribute not in target directory', coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUpdateManagerTestOutputs']?['result'], 'SKIPPED'))",
+ "provisioningErrorDetails": "@if(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUpdateManagerTestOutputs']?['overallResult'], 'SKIPPED'), 'FAILED'), body('Get_SCIMTests_Output_Content')?['SCIMUpdateManagerTestOutputs']?['errorDetails'], '')",
+ "recommendationUrl": "@if(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUpdateManagerTestOutputs']?['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')?['SCIMUpdateManagerTestOutputs']?['overallResult'], 'SKIPPED'), 'SKIPPED'), '', outputs('Compose_SCIMTests_RunLink'))",
+ "message": "@if(equals(coalesce(body('Get_SCIMTests_Output_Content')?['SCIMUpdateManagerTestOutputs']?['overallResult'], 'SKIPPED'), 'SKIPPED'), '', 'Click the runLink and search for the action Compose_Final_Results for more info.')"
+ },
{
"testName": "Restore_Group_Test",
"testCategory": "optional",
diff --git a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/SCIMTests_Workflow.json b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/SCIMTests_Workflow.json
index 756988dd..c4c9fbbd 100644
--- a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/SCIMTests_Workflow.json
+++ b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/SCIMTests_Workflow.json
@@ -42,12 +42,34 @@
}
},
"actions": {
- "Response_Immediate": {
+ "Initialize_SCIM_Test_Variables_3": {
"runAfter": {
"Initialize_SCIM_Test_Variables_2": [
"Succeeded"
]
},
+ "type": "InitializeVariable",
+ "inputs": {
+ "variables": [
+ {
+ "name": "SCIMUpdateManagerTestOutputs",
+ "type": "Object",
+ "value": {
+ "overallResult": "SKIPPED",
+ "result": "SKIPPED",
+ "summary": null,
+ "errorDetails": null
+ }
+ }
+ ]
+ }
+ },
+ "Response_Immediate": {
+ "runAfter": {
+ "Initialize_SCIM_Test_Variables_3": [
+ "Succeeded"
+ ]
+ },
"type": "Response",
"kind": "Http",
"inputs": {
@@ -331,6 +353,24 @@
}
}
},
+ "Set_SchemaTestOutputs_On_Failure": {
+ "runAfter": {
+ "SCIM_Get_Schemas": ["Failed", "TimedOut"]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "SchemaDiscoverabilityTestOutputs",
+ "value": {
+ "overallResult": "FAILED",
+ "result": "@concat('FAILED - Action: SCIM_Get_Schemas - /Schemas endpoint returned HTTP ', string(coalesce(outputs('SCIM_Get_Schemas')?['statusCode'], 0)))",
+ "summary": null,
+ "detailedResults": [],
+ "errorDetails": {
+ "summary": "@concat('/Schemas endpoint not accessible. HTTP ', string(coalesce(outputs('SCIM_Get_Schemas')?['statusCode'], 0)), '. Response: ', substring(string(coalesce(body('SCIM_Get_Schemas'), json('{}'))), 0, min(500, length(string(coalesce(body('SCIM_Get_Schemas'), json('{}')))))))"
+ }
+ }
+ }
+ },
"Filter_User_Schema": {
"runAfter": {
"SCIM_Get_Schemas": [
@@ -343,7 +383,7 @@
"where": "@equals(item()?['id'], 'urn:ietf:params:scim:schemas:core:2.0:User')"
}
},
- "Filter_EnterpriseUser_Schema": {
+ "Filter_All_User_Extension_Schemas": {
"runAfter": {
"SCIM_Get_Schemas": [
"Succeeded"
@@ -352,7 +392,7 @@
"type": "Query",
"inputs": {
"from": "@coalesce(body('SCIM_Get_Schemas')?['Resources'], json('[]'))",
- "where": "@equals(item()?['id'], 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User')"
+ "where": "@and(not(equals(item()?['id'], 'urn:ietf:params:scim:schemas:core:2.0:User')), not(equals(item()?['id'], 'urn:ietf:params:scim:schemas:core:2.0:Group')))"
}
},
"Filter_Group_Schema": {
@@ -931,7 +971,7 @@
},
"Compose_User_Lookup_String": {
"runAfter": {
- "Flatten_Enterprise_User_Attrs_Loop": [
+ "ForEach_User_Extension_Schema": [
"Succeeded"
]
},
@@ -1011,7 +1051,7 @@
"schemasEndpointStatusCode": "@outputs('SCIM_Get_Schemas')['statusCode']",
"discoveredSchemasCount": "@length(coalesce(body('SCIM_Get_Schemas')?['Resources'], json('[]')))",
"userSchemaFound": "@greater(length(body('Filter_User_Schema')), 0)",
- "enterpriseUserSchemaFound": "@greater(length(body('Filter_EnterpriseUser_Schema')), 0)",
+ "extensionSchemasCount": "@length(body('Filter_All_User_Extension_Schemas'))",
"groupSchemaFound": "@greater(length(body('Filter_Group_Schema')), 0)",
"flattenedUserSchemaAttrs": "@variables('flattenedUserAttrNames')",
"flattenedGroupSchemaAttrs": "@variables('flattenedGroupAttrNames')",
@@ -1046,9 +1086,9 @@
}
}
},
- "Flatten_Enterprise_User_Attrs_Loop": {
+ "ForEach_User_Extension_Schema": {
"runAfter": {
- "Filter_EnterpriseUser_Schema": [
+ "Filter_All_User_Extension_Schemas": [
"Succeeded"
],
"Flatten_User_Attrs_Loop": [
@@ -1056,40 +1096,23 @@
]
},
"type": "Foreach",
- "foreach": "@coalesce(first(body('Filter_EnterpriseUser_Schema'))?['attributes'], json('[]'))",
+ "foreach": "@body('Filter_All_User_Extension_Schemas')",
"actions": {
- "Check_Enterprise_Attr_Type": {
- "type": "If",
- "expression": {
- "and": [
- {
- "not": {
- "equals": [
- "@items('Flatten_Enterprise_User_Attrs_Loop')?['type']",
- "complex"
- ]
- }
- }
- ]
- },
+ "ForEach_Extension_Schema_Attr": {
+ "type": "Foreach",
+ "foreach": "@coalesce(items('ForEach_User_Extension_Schema')?['attributes'], json('[]'))",
"actions": {
- "Append_Enterprise_Simple_Attr": {
+ "Append_Extension_Attr": {
"type": "AppendToArrayVariable",
"inputs": {
"name": "flattenedUserAttrNames",
- "value": "@concat(first(body('Filter_EnterpriseUser_Schema'))?['id'], ':', items('Flatten_Enterprise_User_Attrs_Loop')?['name'])"
+ "value": "@concat(items('ForEach_User_Extension_Schema')?['id'], ':', items('ForEach_Extension_Schema_Attr')?['name'])"
}
}
},
- "else": {
- "actions": {
- "Append_Enterprise_Complex_Attr": {
- "type": "AppendToArrayVariable",
- "inputs": {
- "name": "flattenedUserAttrNames",
- "value": "@concat(first(body('Filter_EnterpriseUser_Schema'))?['id'], ':', items('Flatten_Enterprise_User_Attrs_Loop')?['name'])"
- }
- }
+ "runtimeConfiguration": {
+ "concurrency": {
+ "repetitions": 1
}
}
}
@@ -1147,13 +1170,10 @@
"inputs": {
"name": "scimNullCreateBody",
"value": {
- "schemas": [
- "urn:ietf:params:scim:schemas:core:2.0:User",
- "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
- ],
+ "schemas": "@coalesce(triggerBody()?['initializationData']?['scimUserBody']?['schemas'], createArray('urn:ietf:params:scim:schemas:core:2.0:User'))",
"userName": "@{concat(outputs('Generate_SCIM_Null_Test_UserName'), '@', coalesce(last(split(coalesce(triggerBody()?['initializationData']?['userBody']?['userPrincipalName'], 'x@test.com'), '@')), 'test.com'))}",
"active": true,
- "externalId": "@outputs('Generate_SCIM_Null_Test_UserName')"
+ "externalId": "@{if(contains(string(coalesce(triggerBody()?['initializationData']?['targetDirectoryUserAttributes'], json('[]'))), 'externalId'), guid(), '')}"
}
}
},
@@ -1171,7 +1191,7 @@
]
},
"type": "Compose",
- "inputs": "@if(or(or(equals(items('ForEach_Build_Null_Create_Body')?['source'], 'manager'), equals(items('ForEach_Build_Null_Create_Body')?['target'], 'userName')), or(equals(items('ForEach_Build_Null_Create_Body')?['target'], 'active'), equals(items('ForEach_Build_Null_Create_Body')?['target'], 'externalId')), equals(items('ForEach_Build_Null_Create_Body')?['target'], 'roles')), variables('scimNullCreateBody'), if(startsWith(items('ForEach_Build_Null_Create_Body')?['target'], 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:'), union(variables('scimNullCreateBody'), json(concat('{\"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User\":', string(setProperty(coalesce(variables('scimNullCreateBody')?['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'], json('{}')), last(split(items('ForEach_Build_Null_Create_Body')?['target'], ':')), outputs('Compose_Null_Create_Source_Value'))), '}'))), if(contains(items('ForEach_Build_Null_Create_Body')?['target'], '['), setProperty(variables('scimNullCreateBody'), first(split(items('ForEach_Build_Null_Create_Body')?['target'], '[')), setProperty(coalesce(variables('scimNullCreateBody')?[first(split(items('ForEach_Build_Null_Create_Body')?['target'], '['))], json('{}')), first(skip(split(items('ForEach_Build_Null_Create_Body')?['target'], '\"'), 1)), setProperty(coalesce(variables('scimNullCreateBody')?[first(split(items('ForEach_Build_Null_Create_Body')?['target'], '['))]?[first(skip(split(items('ForEach_Build_Null_Create_Body')?['target'], '\"'), 1))], json(concat('{\"type\":\"', first(skip(split(items('ForEach_Build_Null_Create_Body')?['target'], '\"'), 1)), '\",\"primary\":', if(not(contains(string(coalesce(variables('scimNullCreateBody')?[first(split(items('ForEach_Build_Null_Create_Body')?['target'], '['))], json('{}'))), '\"primary\":true')), 'true', 'false'), '}'))), last(split(items('ForEach_Build_Null_Create_Body')?['target'], '.')), outputs('Compose_Null_Create_Source_Value')))), if(contains(items('ForEach_Build_Null_Create_Body')?['target'], '.'), setProperty(variables('scimNullCreateBody'), first(split(items('ForEach_Build_Null_Create_Body')?['target'], '.')), setProperty(coalesce(variables('scimNullCreateBody')?[first(split(items('ForEach_Build_Null_Create_Body')?['target'], '.'))], json('{}')), last(split(items('ForEach_Build_Null_Create_Body')?['target'], '.')), outputs('Compose_Null_Create_Source_Value'))), setProperty(variables('scimNullCreateBody'), items('ForEach_Build_Null_Create_Body')?['target'], outputs('Compose_Null_Create_Source_Value'))))))"
+ "inputs": "@if(or(or(equals(items('ForEach_Build_Null_Create_Body')?['source'], 'manager'), equals(items('ForEach_Build_Null_Create_Body')?['target'], 'userName')), or(equals(items('ForEach_Build_Null_Create_Body')?['target'], 'active'), equals(items('ForEach_Build_Null_Create_Body')?['target'], 'externalId')), equals(items('ForEach_Build_Null_Create_Body')?['target'], 'roles')), variables('scimNullCreateBody'), if(startsWith(items('ForEach_Build_Null_Create_Body')?['target'], 'urn:'), union(variables('scimNullCreateBody'), json(concat('{\"', substring(items('ForEach_Build_Null_Create_Body')?['target'], 0, lastIndexOf(items('ForEach_Build_Null_Create_Body')?['target'], ':')), '\":', string(setProperty(coalesce(variables('scimNullCreateBody')?[substring(items('ForEach_Build_Null_Create_Body')?['target'], 0, lastIndexOf(items('ForEach_Build_Null_Create_Body')?['target'], ':'))], json('{}')), last(split(items('ForEach_Build_Null_Create_Body')?['target'], ':')), outputs('Compose_Null_Create_Source_Value'))), '}'))), if(contains(items('ForEach_Build_Null_Create_Body')?['target'], '['), setProperty(variables('scimNullCreateBody'), first(split(items('ForEach_Build_Null_Create_Body')?['target'], '[')), setProperty(coalesce(variables('scimNullCreateBody')?[first(split(items('ForEach_Build_Null_Create_Body')?['target'], '['))], json('{}')), first(skip(split(items('ForEach_Build_Null_Create_Body')?['target'], '\"'), 1)), setProperty(coalesce(variables('scimNullCreateBody')?[first(split(items('ForEach_Build_Null_Create_Body')?['target'], '['))]?[first(skip(split(items('ForEach_Build_Null_Create_Body')?['target'], '\"'), 1))], json(concat('{\"type\":\"', first(skip(split(items('ForEach_Build_Null_Create_Body')?['target'], '\"'), 1)), '\",\"primary\":', if(not(contains(string(coalesce(variables('scimNullCreateBody')?[first(split(items('ForEach_Build_Null_Create_Body')?['target'], '['))], json('{}'))), '\"primary\":true')), 'true', 'false'), '}'))), last(split(items('ForEach_Build_Null_Create_Body')?['target'], '.')), outputs('Compose_Null_Create_Source_Value')))), if(contains(items('ForEach_Build_Null_Create_Body')?['target'], '.'), setProperty(variables('scimNullCreateBody'), first(split(items('ForEach_Build_Null_Create_Body')?['target'], '.')), setProperty(coalesce(variables('scimNullCreateBody')?[first(split(items('ForEach_Build_Null_Create_Body')?['target'], '.'))], json('{}')), last(split(items('ForEach_Build_Null_Create_Body')?['target'], '.')), outputs('Compose_Null_Create_Source_Value'))), setProperty(variables('scimNullCreateBody'), items('ForEach_Build_Null_Create_Body')?['target'], outputs('Compose_Null_Create_Source_Value'))))))"
},
"Set_scimNullCreateBody": {
"runAfter": {
@@ -1320,7 +1340,7 @@
"type": "Query",
"inputs": {
"from": "@coalesce(triggerBody()?['initializationData']?['sourceToTargetLookup'], json('[]'))",
- "where": "@and(equals(coalesce(item()?['matchingPrecedence'], 0), 0), not(contains(toLower(coalesce(item()?['targetSchema']?['type'], '')), 'reference')), not(equals(coalesce(item()?['target'], ''), 'userName')), not(equals(coalesce(item()?['target'], ''), 'active')), not(equals(coalesce(item()?['target'], ''), 'externalId')), not(equals(coalesce(item()?['target'], ''), 'roles')), if(startsWith(coalesce(item()?['target'], ''), 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:'), not(contains(string(body('Query_Required_Enterprise_Attrs')), concat('\"name\":\"', last(split(item()?['target'], ':')), '\"'))), not(contains(string(body('Query_Required_Core_Attrs')), concat('\"name\":\"', if(contains(coalesce(item()?['target'], ''), '['), first(split(item()?['target'], '[')), if(contains(coalesce(item()?['target'], ''), '.'), first(split(item()?['target'], '.')), coalesce(item()?['target'], ''))), '\"')))))"
+ "where": "@and(equals(coalesce(item()?['matchingPrecedence'], 0), 0), not(contains(toLower(coalesce(item()?['targetSchema']?['type'], '')), 'reference')), not(equals(coalesce(item()?['target'], ''), 'userName')), not(equals(coalesce(item()?['target'], ''), 'active')), not(equals(coalesce(item()?['target'], ''), 'externalId')), not(equals(coalesce(item()?['target'], ''), 'roles')), if(startsWith(coalesce(item()?['target'], ''), 'urn:'), not(contains(string(body('Query_Required_Enterprise_Attrs')), concat('\"name\":\"', last(split(item()?['target'], ':')), '\"'))), not(contains(string(body('Query_Required_Core_Attrs')), concat('\"name\":\"', if(contains(coalesce(item()?['target'], ''), '['), first(split(item()?['target'], '[')), if(contains(coalesce(item()?['target'], ''), '.'), first(split(item()?['target'], '.')), coalesce(item()?['target'], ''))), '\"')))))"
}
},
"ForEach_Build_Null_Patch_Operations": {
@@ -1476,7 +1496,7 @@
]
},
"type": "Compose",
- "inputs": "@if(contains(items('ForEach_Verify_Null_Attributes')?['target'], '[type eq'), if(contains(items('ForEach_Verify_Null_Attributes')?['target'], '].'), coalesce(first(body('NullVerify_Filter_Array_By_Type'))?[last(split(items('ForEach_Verify_Null_Attributes')?['target'], '.'))], ''), coalesce(first(body('NullVerify_Filter_Array_By_Type'))?['value'], '')), if(and(contains(items('ForEach_Verify_Null_Attributes')?['target'], '.'), not(contains(items('ForEach_Verify_Null_Attributes')?['target'], ':'))), coalesce(body('SCIM_Get_User_After_Null_Update')?[first(split(items('ForEach_Verify_Null_Attributes')?['target'], '.'))]?[last(split(items('ForEach_Verify_Null_Attributes')?['target'], '.'))], ''), if(contains(items('ForEach_Verify_Null_Attributes')?['target'], 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:'), coalesce(body('SCIM_Get_User_After_Null_Update')?['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']?[last(split(items('ForEach_Verify_Null_Attributes')?['target'], ':'))], ''), coalesce(body('SCIM_Get_User_After_Null_Update')?[items('ForEach_Verify_Null_Attributes')?['target']], ''))))"
+ "inputs": "@if(contains(items('ForEach_Verify_Null_Attributes')?['target'], '[type eq'), if(contains(items('ForEach_Verify_Null_Attributes')?['target'], '].'), coalesce(first(body('NullVerify_Filter_Array_By_Type'))?[last(split(items('ForEach_Verify_Null_Attributes')?['target'], '.'))], ''), coalesce(first(body('NullVerify_Filter_Array_By_Type'))?['value'], '')), if(and(contains(items('ForEach_Verify_Null_Attributes')?['target'], '.'), not(contains(items('ForEach_Verify_Null_Attributes')?['target'], ':'))), coalesce(body('SCIM_Get_User_After_Null_Update')?[first(split(items('ForEach_Verify_Null_Attributes')?['target'], '.'))]?[last(split(items('ForEach_Verify_Null_Attributes')?['target'], '.'))], ''), if(startsWith(items('ForEach_Verify_Null_Attributes')?['target'], 'urn:'), coalesce(body('SCIM_Get_User_After_Null_Update')?[substring(items('ForEach_Verify_Null_Attributes')?['target'], 0, lastIndexOf(items('ForEach_Verify_Null_Attributes')?['target'], ':'))]?[last(split(items('ForEach_Verify_Null_Attributes')?['target'], ':'))], ''), coalesce(body('SCIM_Get_User_After_Null_Update')?[items('ForEach_Verify_Null_Attributes')?['target']], ''))))"
},
"Compose_NullVerify_IsNullOrEmpty": {
"runAfter": {
@@ -2130,7 +2150,7 @@
]
},
"type": "Compose",
- "inputs": "@setProperty(coalesce(triggerBody()?['initializationData']?['scimUserBody'], json('{}')), 'userName', outputs('Generate_SCIM_Direct_UserName'))"
+ "inputs": "@setProperty(if(contains(string(coalesce(triggerBody()?['initializationData']?['targetDirectoryUserAttributes'], json('[]'))), 'externalId'), setProperty(coalesce(triggerBody()?['initializationData']?['scimUserBody'], json('{}')), 'externalId', guid()), coalesce(triggerBody()?['initializationData']?['scimUserBody'], json('{}'))), 'userName', outputs('Generate_SCIM_Direct_UserName'))"
}
},
"runAfter": {},
@@ -2290,7 +2310,7 @@
]
},
"type": "Compose",
- "inputs": "@setProperty(setProperty(coalesce(triggerBody()?['initializationData']?['scimGroupBody'], json('{}')), 'displayName', outputs('Generate_SCIM_Direct_GroupName')), 'externalId', substring(guid(),0,8))"
+ "inputs": "@setProperty(if(contains(string(coalesce(triggerBody()?['initializationData']?['targetDirectoryGroupAttributes'], json('[]'))), 'externalId'), setProperty(coalesce(triggerBody()?['initializationData']?['scimGroupBody'], json('{}')), 'externalId', guid()), coalesce(triggerBody()?['initializationData']?['scimGroupBody'], json('{}'))), 'displayName', outputs('Generate_SCIM_Direct_GroupName'))"
}
},
"runAfter": {},
@@ -2373,7 +2393,7 @@
"type": "Query",
"inputs": {
"from": "@coalesce(triggerBody()?['initializationData']?['targetDirectoryUserAttributes'], json('[]'))",
- "where": "@and(and(and(and(equals(coalesce(item()?['multivalued'], item()?['multiValued'], false), false), or(equals(toLower(coalesce(item()?['mutability'], 'readwrite')), 'readwrite'), equals(toLower(coalesce(item()?['mutability'], 'readwrite')), 'writeonly'))), not(contains(toLower(coalesce(item()?['type'], 'string')), 'reference'))), not(equals(toLower(coalesce(item()?['type'], 'string')), 'boolean'))), and(and(and(not(equals(coalesce(item()?['name'], ''), 'id')), not(equals(coalesce(item()?['name'], ''), 'schemas'))), not(equals(coalesce(item()?['name'], ''), 'meta'))), and(not(equals(coalesce(item()?['name'], ''), 'active')), not(contains(createArray('groups','roles'), coalesce(item()?['name'], ''))))))"
+ "where": "@and(and(and(equals(coalesce(item()?['multivalued'], item()?['multiValued'], false), false), or(equals(toLower(coalesce(item()?['mutability'], 'readwrite')), 'readwrite'), equals(toLower(coalesce(item()?['mutability'], 'readwrite')), 'writeonly'))), not(contains(toLower(coalesce(item()?['type'], 'string')), 'reference'))), and(and(and(not(equals(coalesce(item()?['name'], ''), 'id')), not(equals(coalesce(item()?['name'], ''), 'schemas'))), not(equals(coalesce(item()?['name'], ''), 'meta'))), and(not(equals(coalesce(item()?['name'], ''), 'active')), not(contains(createArray('groups','roles'), coalesce(item()?['name'], ''))))))"
}
},
"Filter_Update_Typed_Attributes": {
@@ -2385,7 +2405,7 @@
"type": "Query",
"inputs": {
"from": "@body('Filter_Update_Eligible_Attributes')",
- "where": "@contains(json('[\"string\",\"integer\",\"datetime\"]'), toLower(coalesce(item()?['type'], 'string')))"
+ "where": "@contains(json('[\"string\",\"integer\",\"datetime\",\"boolean\"]'), toLower(coalesce(item()?['type'], 'string')))"
}
},
"Select_Update_Patch_Ops": {
@@ -2400,7 +2420,7 @@
"select": {
"op": "replace",
"path": "@coalesce(item()?['name'], '')",
- "value": "@if(and(greater(length(coalesce(triggerBody()?['scimTargetUserValues'], json('[]'))), 1), contains(triggerBody()?['scimTargetUserValues'][1], coalesce(item()?['name'], ''))), triggerBody()?['scimTargetUserValues'][1][coalesce(item()?['name'], '')], if(equals(toLower(coalesce(item()?['type'], 'string')), 'integer'), rand(1, 99999), if(equals(toLower(coalesce(item()?['type'], 'string')), 'datetime'), utcNow(), concat('upd-', substring(guid(),0,8)))))"
+ "value": "@if(and(greater(length(coalesce(triggerBody()?['scimTargetUserValues'], json('[]'))), 1), contains(triggerBody()?['scimTargetUserValues'][1], coalesce(item()?['name'], ''))), triggerBody()?['scimTargetUserValues'][1][coalesce(item()?['name'], '')], if(equals(toLower(coalesce(item()?['type'], 'string')), 'integer'), rand(1, 99999), if(equals(toLower(coalesce(item()?['type'], 'string')), 'boolean'), true, if(equals(toLower(coalesce(item()?['type'], 'string')), 'datetime'), utcNow(), concat('upd-', substring(guid(),0,8))))))"
}
}
},
@@ -2413,7 +2433,7 @@
"type": "Select",
"inputs": {
"from": "@body('Select_Update_Patch_Ops')",
- "select": "@concat('\"', replace(item()?['path'], '\"', '\\\"'), '\":', if(or(equals(item()?['value'], null), equals(string(item()?['value']), '')), 'null', if(isInt(item()?['value']), string(item()?['value']), concat('\"', replace(string(item()?['value']), '\"', '\\\"'), '\"'))))"
+ "select": "@concat('\"', replace(item()?['path'], '\"', '\\\"'), '\":', if(or(equals(item()?['value'], null), equals(string(item()?['value']), '')), 'null', if(or(isInt(item()?['value']), equals(item()?['value'], true), equals(item()?['value'], false)), string(item()?['value']), concat('\"', replace(string(item()?['value']), '\"', '\\\"'), '\"'))))"
}
},
"Filter_Update_MultiValued_Ops": {
@@ -2449,7 +2469,7 @@
"type": "Select",
"inputs": {
"from": "@body('Filter_Update_Batch_Ops')",
- "select": "@concat('\"', replace(item()?['path'], '\"', '\\\"'), '\":', if(or(equals(item()?['value'], null), equals(string(item()?['value']), '')), 'null', if(isInt(item()?['value']), string(item()?['value']), concat('\"', replace(string(item()?['value']), '\"', '\\\"'), '\"'))))"
+ "select": "@concat('\"', replace(item()?['path'], '\"', '\\\"'), '\":', if(or(equals(item()?['value'], null), equals(string(item()?['value']), '')), 'null', if(or(isInt(item()?['value']), equals(item()?['value'], true), equals(item()?['value'], false)), string(item()?['value']), concat('\"', replace(string(item()?['value']), '\"', '\\\"'), '\"'))))"
}
},
"Compose_Update_Patch_Body": {
@@ -2659,7 +2679,7 @@
]
},
"type": "Compose",
- "inputs": "@setProperty(coalesce(triggerBody()?['initializationData']?['scimUserBody'], json('{}')), 'userName', outputs('Generate_SCIM_Update_UserName'))"
+ "inputs": "@setProperty(if(contains(string(coalesce(triggerBody()?['initializationData']?['targetDirectoryUserAttributes'], json('[]'))), 'externalId'), setProperty(coalesce(triggerBody()?['initializationData']?['scimUserBody'], json('{}')), 'externalId', guid()), coalesce(triggerBody()?['initializationData']?['scimUserBody'], json('{}'))), 'userName', outputs('Generate_SCIM_Update_UserName'))"
}
},
"runAfter": {},
@@ -2824,7 +2844,7 @@
]
},
"type": "Compose",
- "inputs": "@setProperty(setProperty(coalesce(triggerBody()?['initializationData']?['scimGroupBody'], json('{}')), 'displayName', outputs('Generate_SCIM_GroupUpdate_GroupName')), 'externalId', substring(guid(),0,8))"
+ "inputs": "@setProperty(if(contains(string(coalesce(triggerBody()?['initializationData']?['targetDirectoryGroupAttributes'], json('[]'))), 'externalId'), setProperty(coalesce(triggerBody()?['initializationData']?['scimGroupBody'], json('{}')), 'externalId', guid()), coalesce(triggerBody()?['initializationData']?['scimGroupBody'], json('{}'))), 'displayName', outputs('Generate_SCIM_GroupUpdate_GroupName'))"
},
"Filter_GroupUpdate_Eligible": {
"runAfter": {
@@ -2835,7 +2855,7 @@
"type": "Query",
"inputs": {
"from": "@coalesce(triggerBody()?['initializationData']?['targetDirectoryGroupAttributes'], json('[]'))",
- "where": "@and(and(and(equals(coalesce(item()?['multivalued'], item()?['multiValued'], false), false), or(equals(toLower(coalesce(item()?['mutability'], 'readwrite')), 'readwrite'), equals(toLower(coalesce(item()?['mutability'], 'readwrite')), 'writeonly'))), not(contains(toLower(coalesce(item()?['type'], 'string')), 'reference'))), and(not(equals(coalesce(item()?['name'], ''), 'id')), not(equals(toLower(coalesce(item()?['type'], 'string')), 'boolean'))))"
+ "where": "@and(and(and(equals(coalesce(item()?['multivalued'], item()?['multiValued'], false), false), or(equals(toLower(coalesce(item()?['mutability'], 'readwrite')), 'readwrite'), equals(toLower(coalesce(item()?['mutability'], 'readwrite')), 'writeonly'))), not(contains(toLower(coalesce(item()?['type'], 'string')), 'reference'))), and(not(equals(coalesce(item()?['name'], ''), 'id')), not(contains(createArray('members','schemas','meta'), coalesce(item()?['name'], '')))))"
}
},
"Select_GroupUpdate_Patch_Ops": {
@@ -2850,7 +2870,7 @@
"select": {
"op": "replace",
"path": "@coalesce(item()?['name'], '')",
- "value": "@concat('upd-', substring(guid(),0,8))"
+ "value": "@if(equals(toLower(coalesce(item()?['type'], 'string')), 'integer'), rand(1, 99999), if(equals(toLower(coalesce(item()?['type'], 'string')), 'boolean'), true, if(equals(toLower(coalesce(item()?['type'], 'string')), 'datetime'), utcNow(), concat('upd-', substring(guid(),0,8)))))"
}
}
},
@@ -3186,7 +3206,7 @@
},
"type": "If"
},
- "Federated_Identity_Test": {
+ "Federated_Identity_Test": {
"runAfter": {},
"actions": {
"Acquire_Entra_Token": {
@@ -3202,7 +3222,7 @@
},
"Check_Entra_Token_Acquired": {
"runAfter": {
- "Acquire_Entra_Token": ["Succeeded", "Failed", "TimedOut"]
+ "Acquire_Entra_Token": [ "Succeeded", "Failed", "TimedOut" ]
},
"type": "If",
"expression": {
@@ -3687,10 +3707,10 @@
"expression": {
"or": [
{
- "equals": ["@triggerBody()?['EnabledTests']", "All"]
+ "equals": [ "@triggerBody()?['EnabledTests']", "All" ]
},
{
- "equals": ["@triggerBody()?['EnabledTests']", "SCIMTests"]
+ "equals": [ "@triggerBody()?['EnabledTests']", "SCIMTests" ]
},
{
"equals": [
@@ -3761,7 +3781,7 @@
"Content-Type": "@{parameters('scimContentType')}",
"Accept": "@{parameters('scimContentType')}"
},
- "body": "@setProperty(setProperty(triggerBody()?['initializationData']?['scimUserBody'], 'userName', concat('paguser', substring(guid(),0,8), '@', coalesce(parameters('testUserDomain'), 'test.com'))), 'displayName', concat('PagUser ', substring(guid(),0,8)))"
+ "body": "@setProperty(setProperty(if(contains(string(coalesce(triggerBody()?['initializationData']?['targetDirectoryUserAttributes'], json('[]'))), 'externalId'), setProperty(triggerBody()?['initializationData']?['scimUserBody'], 'externalId', guid()), triggerBody()?['initializationData']?['scimUserBody']), 'userName', concat('paguser', substring(guid(),0,8), '@', coalesce(parameters('testUserDomain'), 'test.com'))), 'displayName', concat('PagUser ', substring(guid(),0,8)))"
}
},
"Append_User_Pagination_UserId": {
@@ -4057,7 +4077,7 @@
"Content-Type": "@{parameters('scimContentType')}",
"Accept": "@{parameters('scimContentType')}"
},
- "body": "@setProperty(setProperty(coalesce(triggerBody()?['initializationData']?['scimGroupBody'], json('{}')), 'displayName', concat('PagGroup ', substring(guid(),0,8))), 'externalId', substring(guid(),0,8))"
+ "body": "@setProperty(if(contains(string(coalesce(triggerBody()?['initializationData']?['targetDirectoryGroupAttributes'], json('[]'))), 'externalId'), setProperty(coalesce(triggerBody()?['initializationData']?['scimGroupBody'], json('{}')), 'externalId', guid()), coalesce(triggerBody()?['initializationData']?['scimGroupBody'], json('{}'))), 'displayName', concat('PagGroup ', substring(guid(),0,8)))"
}
},
"Append_Group_Pagination_GroupId": {
@@ -4295,6 +4315,363 @@
]
},
"type": "If"
+ },
+ "SCIM_Update_Manager_Test": {
+ "runAfter": {},
+ "actions": {
+ "SCIM_Manager_Test_Actions": {
+ "actions": {
+ "Generate_Manager_TestUser_Name": {
+ "type": "Compose",
+ "inputs": "@concat('scmgr-user-', substring(guid(),0,8), '@', coalesce(last(split(coalesce(triggerBody()?['initializationData']?['userBody']?['userPrincipalName'], 'x@test.com'), '@')), 'test.com'))"
+ },
+ "Compose_Manager_TestUser_Body": {
+ "runAfter": {
+ "Generate_Manager_TestUser_Name": ["Succeeded"]
+ },
+ "type": "Compose",
+ "inputs": "@setProperty(if(contains(string(coalesce(triggerBody()?['initializationData']?['targetDirectoryUserAttributes'], json('[]'))), 'externalId'), setProperty(coalesce(triggerBody()?['initializationData']?['scimUserBody'], json('{}')), 'externalId', guid()), coalesce(triggerBody()?['initializationData']?['scimUserBody'], json('{}'))), 'userName', outputs('Generate_Manager_TestUser_Name'))"
+ },
+ "SCIM_Manager_Create_TestUser": {
+ "runAfter": {
+ "Compose_Manager_TestUser_Body": ["Succeeded"]
+ },
+ "type": "Http",
+ "inputs": {
+ "uri": "@{concat(parameters('scimEndpoint'), '/Users')}",
+ "method": "POST",
+ "headers": {
+ "Authorization": "@{concat('Bearer ', parameters('scimBearerToken'))}",
+ "Content-Type": "@{parameters('scimContentType')}",
+ "Accept": "@{parameters('scimContentType')}"
+ },
+ "body": "@outputs('Compose_Manager_TestUser_Body')"
+ }
+ },
+ "Generate_Manager_ManagerUser_Name": {
+ "runAfter": {
+ "SCIM_Manager_Create_TestUser": ["Succeeded"]
+ },
+ "type": "Compose",
+ "inputs": "@concat('scmgr-mgr-', substring(guid(),0,8), '@', coalesce(last(split(coalesce(triggerBody()?['initializationData']?['userBody']?['userPrincipalName'], 'x@test.com'), '@')), 'test.com'))"
+ },
+ "Compose_Manager_ManagerUser_Body": {
+ "runAfter": {
+ "Generate_Manager_ManagerUser_Name": ["Succeeded"]
+ },
+ "type": "Compose",
+ "inputs": "@setProperty(if(contains(string(coalesce(triggerBody()?['initializationData']?['targetDirectoryUserAttributes'], json('[]'))), 'externalId'), setProperty(coalesce(triggerBody()?['initializationData']?['scimUserBody'], json('{}')), 'externalId', guid()), coalesce(triggerBody()?['initializationData']?['scimUserBody'], json('{}'))), 'userName', outputs('Generate_Manager_ManagerUser_Name'))"
+ },
+ "SCIM_Manager_Create_ManagerUser": {
+ "runAfter": {
+ "Compose_Manager_ManagerUser_Body": ["Succeeded"]
+ },
+ "type": "Http",
+ "inputs": {
+ "uri": "@{concat(parameters('scimEndpoint'), '/Users')}",
+ "method": "POST",
+ "headers": {
+ "Authorization": "@{concat('Bearer ', parameters('scimBearerToken'))}",
+ "Content-Type": "@{parameters('scimContentType')}",
+ "Accept": "@{parameters('scimContentType')}"
+ },
+ "body": "@outputs('Compose_Manager_ManagerUser_Body')"
+ }
+ },
+ "Compose_Manager_Set_Patch_Body": {
+ "runAfter": {
+ "SCIM_Manager_Create_ManagerUser": [ "Succeeded" ]
+ },
+ "type": "Compose",
+ "inputs": {
+ "schemas": [
+ "urn:ietf:params:scim:api:messages:2.0:PatchOp"
+ ],
+ "Operations": [
+ {
+ "op": "Add",
+ "path": "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:manager",
+ "value": "@body('SCIM_Manager_Create_ManagerUser')?['id']"
+ }
+ ]
+ }
+ },
+ "SCIM_Manager_Patch_Set_Manager": {
+ "runAfter": {
+ "Compose_Manager_Set_Patch_Body": [ "Succeeded" ]
+ },
+ "type": "Http",
+ "inputs": {
+ "uri": "@{concat(parameters('scimEndpoint'), '/Users/', body('SCIM_Manager_Create_TestUser')?['id'])}",
+ "method": "PATCH",
+ "headers": {
+ "Authorization": "@{concat('Bearer ', parameters('scimBearerToken'))}",
+ "Content-Type": "@{parameters('scimContentType')}",
+ "Accept": "@{parameters('scimContentType')}"
+ },
+ "body": "@outputs('Compose_Manager_Set_Patch_Body')"
+ }
+ },
+ "SCIM_Manager_Get_After_Set": {
+ "runAfter": {
+ "SCIM_Manager_Patch_Set_Manager": [ "Succeeded" ]
+ },
+ "type": "Http",
+ "inputs": {
+ "uri": "@{concat(parameters('scimEndpoint'), '/Users/', body('SCIM_Manager_Create_TestUser')?['id'])}",
+ "method": "GET",
+ "headers": {
+ "Authorization": "@{concat('Bearer ', parameters('scimBearerToken'))}",
+ "Accept": "@{parameters('scimContentType')}"
+ }
+ }
+ },
+ "Generate_Manager_SecondManager_Name": {
+ "runAfter": {
+ "SCIM_Manager_Get_After_Set": ["Succeeded"]
+ },
+ "type": "Compose",
+ "inputs": "@concat('scmgr-mgr2-', substring(guid(),0,8), '@', coalesce(last(split(coalesce(triggerBody()?['initializationData']?['userBody']?['userPrincipalName'], 'x@test.com'), '@')), 'test.com'))"
+ },
+ "Compose_Manager_SecondManager_Body": {
+ "runAfter": {
+ "Generate_Manager_SecondManager_Name": ["Succeeded"]
+ },
+ "type": "Compose",
+ "inputs": "@setProperty(if(contains(string(coalesce(triggerBody()?['initializationData']?['targetDirectoryUserAttributes'], json('[]'))), 'externalId'), setProperty(coalesce(triggerBody()?['initializationData']?['scimUserBody'], json('{}')), 'externalId', guid()), coalesce(triggerBody()?['initializationData']?['scimUserBody'], json('{}'))), 'userName', outputs('Generate_Manager_SecondManager_Name'))"
+ },
+ "SCIM_Manager_Create_SecondManager": {
+ "runAfter": {
+ "Compose_Manager_SecondManager_Body": ["Succeeded"]
+ },
+ "type": "Http",
+ "inputs": {
+ "uri": "@{concat(parameters('scimEndpoint'), '/Users')}",
+ "method": "POST",
+ "headers": {
+ "Authorization": "@{concat('Bearer ', parameters('scimBearerToken'))}",
+ "Content-Type": "@{parameters('scimContentType')}",
+ "Accept": "@{parameters('scimContentType')}"
+ },
+ "body": "@outputs('Compose_Manager_SecondManager_Body')"
+ }
+ },
+ "Compose_Manager_Change_Patch_Body": {
+ "runAfter": {
+ "SCIM_Manager_Create_SecondManager": ["Succeeded"]
+ },
+ "type": "Compose",
+ "inputs": {
+ "schemas": [
+ "urn:ietf:params:scim:api:messages:2.0:PatchOp"
+ ],
+ "Operations": [
+ {
+ "op": "replace",
+ "path": "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:manager",
+ "value": "@body('SCIM_Manager_Create_SecondManager')?['id']"
+ }
+ ]
+ }
+ },
+ "SCIM_Manager_Patch_Change_Manager": {
+ "runAfter": {
+ "Compose_Manager_Change_Patch_Body": ["Succeeded"]
+ },
+ "type": "Http",
+ "inputs": {
+ "uri": "@{concat(parameters('scimEndpoint'), '/Users/', body('SCIM_Manager_Create_TestUser')?['id'])}",
+ "method": "PATCH",
+ "headers": {
+ "Authorization": "@{concat('Bearer ', parameters('scimBearerToken'))}",
+ "Content-Type": "@{parameters('scimContentType')}",
+ "Accept": "@{parameters('scimContentType')}"
+ },
+ "body": "@outputs('Compose_Manager_Change_Patch_Body')"
+ }
+ },
+ "SCIM_Manager_Get_After_Change": {
+ "runAfter": {
+ "SCIM_Manager_Patch_Change_Manager": ["Succeeded"]
+ },
+ "type": "Http",
+ "inputs": {
+ "uri": "@{concat(parameters('scimEndpoint'), '/Users/', body('SCIM_Manager_Create_TestUser')?['id'])}",
+ "method": "GET",
+ "headers": {
+ "Authorization": "@{concat('Bearer ', parameters('scimBearerToken'))}",
+ "Accept": "@{parameters('scimContentType')}"
+ }
+ }
+ },
+ "Compose_Manager_Remove_Patch_Body": {
+ "runAfter": {
+ "SCIM_Manager_Get_After_Change": [ "Succeeded" ]
+ },
+ "type": "Compose",
+ "inputs": {
+ "schemas": [
+ "urn:ietf:params:scim:api:messages:2.0:PatchOp"
+ ],
+ "Operations": [
+ {
+ "op": "replace",
+ "path": "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:manager",
+ "value": ""
+ }
+ ]
+ }
+ },
+ "SCIM_Manager_Patch_Remove_Manager": {
+ "runAfter": {
+ "Compose_Manager_Remove_Patch_Body": [ "Succeeded" ]
+ },
+ "type": "Http",
+ "inputs": {
+ "uri": "@{concat(parameters('scimEndpoint'), '/Users/', body('SCIM_Manager_Create_TestUser')?['id'])}",
+ "method": "PATCH",
+ "headers": {
+ "Authorization": "@{concat('Bearer ', parameters('scimBearerToken'))}",
+ "Content-Type": "@{parameters('scimContentType')}",
+ "Accept": "@{parameters('scimContentType')}"
+ },
+ "body": "@outputs('Compose_Manager_Remove_Patch_Body')"
+ }
+ },
+ "SCIM_Manager_Get_After_Remove": {
+ "runAfter": {
+ "SCIM_Manager_Patch_Remove_Manager": [ "Succeeded" ]
+ },
+ "type": "Http",
+ "inputs": {
+ "uri": "@{concat(parameters('scimEndpoint'), '/Users/', body('SCIM_Manager_Create_TestUser')?['id'])}",
+ "method": "GET",
+ "headers": {
+ "Authorization": "@{concat('Bearer ', parameters('scimBearerToken'))}",
+ "Accept": "@{parameters('scimContentType')}"
+ }
+ }
+ },
+ "Compose_SCIM_Manager_Test_Summary": {
+ "runAfter": {
+ "SCIM_Manager_Get_After_Remove": [ "Succeeded", "Failed" ]
+ },
+ "type": "Compose",
+ "inputs": {
+ "createTestUserStatus": "@coalesce(outputs('SCIM_Manager_Create_TestUser')?['statusCode'], 0)",
+ "createManagerUserStatus": "@coalesce(outputs('SCIM_Manager_Create_ManagerUser')?['statusCode'], 0)",
+ "createSecondManagerStatus": "@coalesce(outputs('SCIM_Manager_Create_SecondManager')?['statusCode'], 0)",
+ "setManagerPatchStatus": "@coalesce(outputs('SCIM_Manager_Patch_Set_Manager')?['statusCode'], 0)",
+ "setManagerVerifyResult": "@if(or(equals(coalesce(outputs('SCIM_Manager_Patch_Set_Manager')?['statusCode'], 0), 200), equals(coalesce(outputs('SCIM_Manager_Patch_Set_Manager')?['statusCode'], 0), 204)), if(equals(string(coalesce(body('SCIM_Manager_Get_After_Set')?['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']?['manager']?['value'], body('SCIM_Manager_Get_After_Set')?['manager']?['value'], '')), string(body('SCIM_Manager_Create_ManagerUser')?['id'])), 'PASSED', 'FAILED_VERIFY'), 'FAILED_PATCH')",
+ "changeManagerPatchStatus": "@coalesce(outputs('SCIM_Manager_Patch_Change_Manager')?['statusCode'], 0)",
+ "changeManagerVerifyResult": "@if(or(equals(coalesce(outputs('SCIM_Manager_Patch_Change_Manager')?['statusCode'], 0), 200), equals(coalesce(outputs('SCIM_Manager_Patch_Change_Manager')?['statusCode'], 0), 204)), if(equals(string(coalesce(body('SCIM_Manager_Get_After_Change')?['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']?['manager']?['value'], body('SCIM_Manager_Get_After_Change')?['manager']?['value'], '')), string(body('SCIM_Manager_Create_SecondManager')?['id'])), 'PASSED', 'FAILED_VERIFY'), 'FAILED_PATCH')",
+ "removeManagerPatchStatus": "@coalesce(outputs('SCIM_Manager_Patch_Remove_Manager')?['statusCode'], 0)",
+ "removeManagerVerifyResult": "@if(or(equals(coalesce(outputs('SCIM_Manager_Patch_Remove_Manager')?['statusCode'], 0), 200), equals(coalesce(outputs('SCIM_Manager_Patch_Remove_Manager')?['statusCode'], 0), 204)), if(or(or(equals(coalesce(body('SCIM_Manager_Get_After_Remove')?['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']?['manager']?['value'], body('SCIM_Manager_Get_After_Remove')?['manager']?['value'], 'NOT_NULL'), ''), equals(coalesce(body('SCIM_Manager_Get_After_Remove')?['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']?['manager']?['value'], body('SCIM_Manager_Get_After_Remove')?['manager']?['value'], 'NOT_NULL'), null)), equals(coalesce(body('SCIM_Manager_Get_After_Remove')?['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']?['manager'], body('SCIM_Manager_Get_After_Remove')?['manager'], json('null')), null)), 'PASSED', 'FAILED_VERIFY'), 'FAILED_PATCH')",
+ "overallResult": "@if(and(and(or(equals(coalesce(outputs('SCIM_Manager_Patch_Set_Manager')?['statusCode'], 0), 200), equals(coalesce(outputs('SCIM_Manager_Patch_Set_Manager')?['statusCode'], 0), 204)), equals(string(coalesce(body('SCIM_Manager_Get_After_Set')?['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']?['manager']?['value'], body('SCIM_Manager_Get_After_Set')?['manager']?['value'], '')), string(body('SCIM_Manager_Create_ManagerUser')?['id']))), and(and(or(equals(coalesce(outputs('SCIM_Manager_Patch_Change_Manager')?['statusCode'], 0), 200), equals(coalesce(outputs('SCIM_Manager_Patch_Change_Manager')?['statusCode'], 0), 204)), equals(string(coalesce(body('SCIM_Manager_Get_After_Change')?['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']?['manager']?['value'], body('SCIM_Manager_Get_After_Change')?['manager']?['value'], '')), string(body('SCIM_Manager_Create_SecondManager')?['id']))), and(or(equals(coalesce(outputs('SCIM_Manager_Patch_Remove_Manager')?['statusCode'], 0), 200), equals(coalesce(outputs('SCIM_Manager_Patch_Remove_Manager')?['statusCode'], 0), 204)), or(or(equals(coalesce(body('SCIM_Manager_Get_After_Remove')?['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']?['manager']?['value'], body('SCIM_Manager_Get_After_Remove')?['manager']?['value'], 'NOT_NULL'), ''), equals(coalesce(body('SCIM_Manager_Get_After_Remove')?['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']?['manager']?['value'], body('SCIM_Manager_Get_After_Remove')?['manager']?['value'], 'NOT_NULL'), null)), equals(coalesce(body('SCIM_Manager_Get_After_Remove')?['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']?['manager'], body('SCIM_Manager_Get_After_Remove')?['manager'], json('null')), null))))), 'PASSED', 'FAILED')"
+ }
+ }
+ },
+ "type": "Scope"
+ },
+ "Set_SCIMUpdateManagerTestOutputs": {
+ "runAfter": {
+ "SCIM_Manager_Test_Actions": [ "Succeeded", "Failed", "TimedOut" ]
+ },
+ "type": "SetVariable",
+ "inputs": {
+ "name": "SCIMUpdateManagerTestOutputs",
+ "value": {
+ "overallResult": "@if(equals(coalesce(outputs('Compose_SCIM_Manager_Test_Summary')?['overallResult'], 'FAILED'), 'PASSED'), 'PASSED', 'FAILED')",
+ "result": "@if(equals(coalesce(outputs('Compose_SCIM_Manager_Test_Summary')?['overallResult'], 'FAILED'), 'PASSED'), 'success', concat('FAILED - Action: ', if(not(or(equals(coalesce(outputs('SCIM_Manager_Patch_Set_Manager')?['statusCode'], 0), 200), equals(coalesce(outputs('SCIM_Manager_Patch_Set_Manager')?['statusCode'], 0), 204))), 'SCIM_Manager_Patch_Set_Manager', if(not(equals(coalesce(outputs('Compose_SCIM_Manager_Test_Summary')?['setManagerVerifyResult'], ''), 'PASSED')), 'SCIM_Manager_Get_After_Set', if(not(or(equals(coalesce(outputs('SCIM_Manager_Patch_Remove_Manager')?['statusCode'], 0), 200), equals(coalesce(outputs('SCIM_Manager_Patch_Remove_Manager')?['statusCode'], 0), 204))), 'SCIM_Manager_Patch_Remove_Manager', 'SCIM_Manager_Get_After_Remove'))), ' - ', if(not(or(equals(coalesce(outputs('SCIM_Manager_Patch_Set_Manager')?['statusCode'], 0), 200), equals(coalesce(outputs('SCIM_Manager_Patch_Set_Manager')?['statusCode'], 0), 204))), concat('Set manager PATCH returned HTTP ', string(coalesce(outputs('SCIM_Manager_Patch_Set_Manager')?['statusCode'], 0))), if(not(equals(coalesce(outputs('Compose_SCIM_Manager_Test_Summary')?['setManagerVerifyResult'], ''), 'PASSED')), 'Manager value not set correctly after PATCH', if(not(or(equals(coalesce(outputs('SCIM_Manager_Patch_Remove_Manager')?['statusCode'], 0), 200), equals(coalesce(outputs('SCIM_Manager_Patch_Remove_Manager')?['statusCode'], 0), 204))), concat('Remove manager PATCH returned HTTP ', string(coalesce(outputs('SCIM_Manager_Patch_Remove_Manager')?['statusCode'], 0))), 'Manager value not removed after null PATCH')))))",
+ "summary": "@outputs('Compose_SCIM_Manager_Test_Summary')",
+ "errorDetails": "@if(equals(coalesce(outputs('Compose_SCIM_Manager_Test_Summary')?['overallResult'], 'FAILED'), 'PASSED'), null, string(outputs('Compose_SCIM_Manager_Test_Summary')))"
+ }
+ }
+ },
+ "SCIM_Manager_Test_Cleanup": {
+ "runAfter": {
+ "Set_SCIMUpdateManagerTestOutputs": [ "Succeeded", "Failed", "Skipped" ]
+ },
+ "type": "Scope",
+ "actions": {
+ "SCIM_Manager_Delete_TestUser": {
+ "type": "Http",
+ "inputs": {
+ "uri": "@{concat(parameters('scimEndpoint'), '/Users/', coalesce(body('SCIM_Manager_Create_TestUser')?['id'], 'UNKNOWN'))}",
+ "method": "DELETE",
+ "headers": {
+ "Authorization": "@{concat('Bearer ', parameters('scimBearerToken'))}",
+ "Accept": "@{parameters('scimContentType')}"
+ }
+ }
+ },
+ "SCIM_Manager_Delete_ManagerUser": {
+ "runAfter": {
+ "SCIM_Manager_Delete_TestUser": [ "Succeeded", "Failed", "Skipped" ]
+ },
+ "type": "Http",
+ "inputs": {
+ "uri": "@{concat(parameters('scimEndpoint'), '/Users/', coalesce(body('SCIM_Manager_Create_ManagerUser')?['id'], 'UNKNOWN'))}",
+ "method": "DELETE",
+ "headers": {
+ "Authorization": "@{concat('Bearer ', parameters('scimBearerToken'))}",
+ "Accept": "@{parameters('scimContentType')}"
+ }
+ }
+ },
+ "SCIM_Manager_Delete_SecondManager": {
+ "runAfter": {
+ "SCIM_Manager_Delete_ManagerUser": [ "Succeeded", "Failed", "Skipped" ]
+ },
+ "type": "Http",
+ "inputs": {
+ "uri": "@{concat(parameters('scimEndpoint'), '/Users/', coalesce(body('SCIM_Manager_Create_SecondManager')?['id'], 'UNKNOWN'))}",
+ "method": "DELETE",
+ "headers": {
+ "Authorization": "@{concat('Bearer ', parameters('scimBearerToken'))}",
+ "Accept": "@{parameters('scimContentType')}"
+ }
+ }
+ }
+ }
+ }
+ },
+ "else": {
+ "actions": {}
+ },
+ "expression": {
+ "and": [
+ {
+ "equals": [
+ "@contains(string(coalesce(triggerBody()?['initializationData']?['targetDirectoryUserAttributes'], json('[]'))), 'manager')",
+ true
+ ]
+ },
+ {
+ "or": [
+ {
+ "equals": [
+ "@triggerBody()?['EnabledTests']",
+ "All"
+ ]
+ },
+ {
+ "equals": [
+ "@triggerBody()?['EnabledTests']",
+ "SCIMTests"
+ ]
+ },
+ {
+ "equals": [
+ "@triggerBody()?['EnabledTests']",
+ "SCIM_Update_Manager_Test"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "type": "If"
}
},
"runAfter": {
@@ -4306,7 +4683,7 @@
},
"Compose_Final_Results": {
"runAfter": {
- "SCIMTests_Scope": ["Succeeded", "Failed", "Skipped", "TimedOut"]
+ "SCIMTests_Scope": [ "Succeeded", "Failed", "Skipped", "TimedOut" ]
},
"type": "Compose",
"inputs": {
@@ -4318,11 +4695,12 @@
"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(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')"
+ "SCIMGroupPaginationTestOutputs": "@variables('SCIMGroupPaginationTestOutputs')",
+ "SCIMUpdateManagerTestOutputs": "@variables('SCIMUpdateManagerTestOutputs')",
+ "testSuiteName": "SCIMTests",
+ "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(or(equals(variables('SCIMGroupCreateTestOutputs')['overallResult'], 'FAILED'), equals(variables('SCIMUserUpdateTestOutputs')['overallResult'], 'FAILED')), equals(variables('SCIMGroupUpdateTestOutputs')['overallResult'], 'FAILED')), and(contains(string(coalesce(triggerBody()?['initializationData']?['targetDirectoryUserAttributes'], json('[]'))), 'manager'), equals(variables('SCIMUpdateManagerTestOutputs')['overallResult'], 'FAILED')))), 'Failed', 'Passed')",
+ "workflowRunId": "@workflow().run.name"
}
}
},
diff --git a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/SetupLogicApp-Standard-Agent.md b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/SetupLogicApp-Standard-Agent.md
index cd1cddb4..7270737a 100644
--- a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/SetupLogicApp-Standard-Agent.md
+++ b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/SetupLogicApp-Standard-Agent.md
@@ -933,16 +933,22 @@ will be skipped
"GroupTests",
+ "SCIMTests",
+
"Create_User_Test",
"Update_User_Test",
"Delete_User_Test",
- "User_Disable_Test",
+ "Disable_User_Test",
"User_Update_Manager_Test",
+ "Restore_User_Test",
+
+ "POD_User_Test",
+
"Create_Group_Test",
"Update_Group_Test",
@@ -953,49 +959,58 @@ will be skipped
"Group_Update_Remove_Member_Test",
-“Schema_Discoverability_Test”,
+ "POD_Group_Test",
-“SCIM_Null_Update_Test”,
+ "Restore_Group_Test",
-“Validate_Credentials_Test”
+"Schema_Discoverability_Test",
-
+"SCIM_Null_Update_Test",
+
+"SCIM_User_Create_Test",
+
+"SCIM_Group_Create_Test",
+
+"SCIM_User_Update_Test",
+
+"SCIM_Group_Update_Test",
+
+"SCIM_Update_Manager_Test",
-31. IsSoftDeleted can be ‘true’ or ‘false’. Set to true only if soft
- deletion is supported and defined in your SCIM schema. This property
- indicates that the user resource is marked for soft deletion—meaning
- it is flagged for removal but not permanently deleted.
- “Disable_User_Test’ and “Delete_User_Test” are dependent on the
- correct value of this parameter. If ‘IsSoftDeleted’ is false, then
- “Disable_User_Test” will be skipped.
+"SCIM_User_Pagination_Test",
-
+"SCIM_Group_Pagination_Test",
-32. Update scimClientId with client id.
+"Validate_Credentials_Test",
+
+"Federated_Identity_Test"
+
+
+
+31. Update scimClientId with client id.
-33. Update client secret
+32. Update client secret
-34. Update ISV token endpoint
+33. Update ISV token endpoint
## Run the Logic App
-35. You’re now ready to run the Logic app! Navigate to **WorkFlows\>**
+34. You’re now ready to run the Logic app! Navigate to **WorkFlows\>**
Select **Orchestrtor_workflow**,
-36. From the Orchestartor_workflow’s designer, select “**Run**”
+35. From the Orchestartor_workflow’s designer, select “**Run**”
@@ -1004,7 +1019,7 @@ style="width:6.5in;height:2.47153in" />
## Verify the Runs
-37. You can view logs of your runs in the **Runs history** blade. When
+36. You can view logs of your runs in the **Runs history** blade. When
clicking on an entry in **Runs history**, you check the final
results of that entry, including the list of tests that were run,
alongside status and any errors that may have come up.
@@ -1014,7 +1029,7 @@ style="width:6.5in;height:1.42639in" />
## Debugging
-38. Debugging Logic App:
+37. Debugging Logic App:
Check the Final_TestResults action of the Orchestrator_workflow’s run to
learn about the tests and their results.
@@ -1045,18 +1060,29 @@ correct, no action is needed. The Logic App automatically detects these
capabilities during initialization and skips tests that do not apply.
\(1\) User_Update_Manager_Test: Skipped when the manager attribute is
-not mapped in the target directory schema. Only applies if your app
+not present in the target directory schema. Only applies if your app
supports manager provisioning.
-\(2\) All Group tests (Create_Group_Test, Update_Group_Test,
-Delete_Group_Test, Group_Update_Add_Member_Test,
-Group_Update_Remove_Member_Test, POD_Group_Test, Restore_Group_Test) and
-SCIM Group tests (SCIM_Group_Create_Test, SCIM_Group_Update_Test,
-SCIM_Group_Pagination_Test): Skipped when the application does not
-support group provisioning.
+\(2\) SCIM_Update_Manager_Test: Skipped when the manager attribute is
+not present in the target directory schema. This test validates direct
+SCIM PATCH calls to set, change, and remove the manager attribute.
-\(3\) Disable_User_Test: Skipped when IsSoftDeleted is set to false or
-soft-delete is not supported by the target application.
+\(3\) Federated_Identity_Test: Skipped when OAuth client credentials
+are not configured. Only applies if your app uses Workload Identity
+Federation.
+
+Note on WARNING results: The following tests produce a WARNING (not
+FAILED) if they fail — they do not block overall validation:
+
+- Delete_User_Test (hard delete is optional)
+- Delete_Group_Test (group delete is optional)
+- Restore_Group_Test (group restore is optional)
+- SCIM_Group_Pagination_Test (group pagination is optional)
+
+Note on Group tests: Group provisioning is mandatory. If your application
+does not support groups (no Group schema in /Schemas endpoint), all group
+tests will FAIL (not skip). Your application must support both Users and
+Groups provisioning.
“provisioningErrorDetails” gives the glimpse of Error information in
case of failure.
@@ -1076,7 +1102,7 @@ style="width:6.5in;height:3.65625in" />
# Understanding the Test Results
-### The Logic App runs 23 tests across three workflows: 7 User tests, 7 Group tests, and 9 SCIM compliance tests. "For detailed descriptions of each test and what they validate, see the SCIM Validation Test Overview: [SCIMReferenceCode/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp at master · AzureAD/SCIMReferenceCode · GitHub](https://github.com/AzureAD/SCIMReferenceCode/tree/master/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp)"
+### The Logic App runs 25 tests across three workflows: 7 User tests, 7 Group tests, and 11 SCIM compliance tests. "For detailed descriptions of each test and what they validate, see the SCIM Validation Test Overview: [SCIMReferenceCode/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp at master · AzureAD/SCIMReferenceCode · GitHub](https://github.com/AzureAD/SCIMReferenceCode/tree/master/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp)"
### Not all tests will run for every application — the Logic App automatically detects what your app supports and skips tests that do not apply.
@@ -1123,7 +1149,7 @@ Here’s what each field means:
|----|----|----|
| `"success"` | Test passed — your SCIM endpoint handled the operation correctly | ✅ None |
| `"FAILED - [Phase] Failed Action: "` | Test failed at a specific action. The phase (e.g., Create Phase, Update Phase, Delete Phase) and action name tell you exactly where it broke. | ❌ Yes — see Debugging below |
-| `"skipped"` | Test was skipped because a prerequisite was not met (e.g., `Disable_User_Test` skipped when `IsSoftDeleted` is `false`, or group tests skipped when groups are not supported) | ✅ None (if intentional) |
+| `"skipped"` | Test was skipped because a prerequisite was not met (e.g., `User_Update_Manager_Test` skipped when manager attribute is not in target schema, or `Federated_Identity_Test` skipped when OAuth is not configured) | ✅ None (if intentional) |
| `"Token acquisition failed"` | Only for `Validate_Credentials_Test` — the OAuth token request failed. Expected when using a static bearer token. | ✅ None (if using static token) |
| `"The SCIM schema does not support all attributes..."` | `Schema_Discoverability_Test` found that your SCIM schema doesn’t advertise all the attributes in the provisioning mappings. The details show how many are supported vs mapped. | ⚠️ Prune your attribute mappings to match |
@@ -1136,22 +1162,34 @@ Here’s what each field means:
{ "testName": "Disable_User_Test", "testResult": "success" },
{ "testName": "Delete_User_Test", "testResult": "success" },
{ "testName": "User_Update_Manager_Test", "testResult": "success" },
+ { "testName": "Restore_User_Test", "testResult": "success" },
+ { "testName": "POD_User_Test", "testResult": "success" },
{ "testName": "Create_Group_Test", "testResult": "success" },
{ "testName": "Update_Group_Test", "testResult": "success" },
{ "testName": "Delete_Group_Test", "testResult": "success" },
{ "testName": "Group_Update_Add_Member_Test", "testResult": "success" },
{ "testName": "Group_Update_Remove_Member_Test", "testResult": "success" },
+ { "testName": "POD_Group_Test", "testResult": "success" },
+ { "testName": "Restore_Group_Test", "testResult": "success" },
{ "testName": "Schema_Discoverability_Test", "testResult": "success" },
{ "testName": "SCIM_Null_Update_Test", "testResult": "success" },
- { "testName": "Validate_Credentials_Test", "testResult": "Token acquisition failed" }
+ { "testName": "SCIM_User_Create_Test", "testResult": "success" },
+ { "testName": "SCIM_Group_Create_Test", "testResult": "success" },
+ { "testName": "SCIM_User_Update_Test", "testResult": "success" },
+ { "testName": "SCIM_Group_Update_Test", "testResult": "success" },
+ { "testName": "SCIM_Update_Manager_Test", "testResult": "success" },
+ { "testName": "SCIM_User_Pagination_Test", "testResult": "success" },
+ { "testName": "SCIM_Group_Pagination_Test", "testResult": "success" },
+ { "testName": "Validate_Credentials_Test", "testResult": "Token acquisition failed" },
+ { "testName": "Federated_Identity_Test", "testResult": "skipped" }
],
"overallResult": "Failed"
}
-> **Note:** Even with 12/13 tests passing, the `overallResult` shows
+> **Note:** Even with 23/25 tests passing, the `overallResult` shows
> `"Failed"` because `Validate_Credentials_Test` did not pass. This is
> expected when using a static bearer token — it does not block
-> onboarding.
+> onboarding. `Federated_Identity_Test` is skipped when OAuth is not configured.
### Sample Results — With a Failure
@@ -1199,24 +1237,18 @@ automatically and tells you the root cause and fix.
To proceed with gallery onboarding, all applicable tests must pass. The
following are acceptable exceptions: -
-- Validate_Credentials_Test failing when using a static bearer token
- (OAuth will be required for production).
-
-- Group tests being SKIPPED if your application does not support group
- provisioning. This includes all 7 Group tests (Create_Group_Test,
- Update_Group_Test, Delete_Group_Test, Group_Update_Add_Member_Test,
- Group_Update_Remove_Member_Test, POD_Group_Test, Restore_Group_Test)
- and 3 SCIM Group tests (SCIM_Group_Create_Test,
- SCIM_Group_Update_Test, SCIM_Group_Pagination_Test).
+- Validate_Credentials_Test or Federated_Identity_Test failing when
+ using a static bearer token (OAuth or WIF will be required for
+ production). At least one of these two must PASS.
-- User_Update_Manager_Test being SKIPPED when the manager attribute is
- not present in the target directory schema. The Logic App checks
- whether manager is mapped and automatically skips this test if it is
- not.
+- User_Update_Manager_Test and SCIM_Update_Manager_Test being SKIPPED
+ when the manager attribute is not present in the target directory
+ schema. The Logic App checks whether manager is in the target
+ directory attributes and automatically skips these tests if it is not.
-- Disable_User_Test being SKIPPED when IsSoftDeleted is set to false or
- soft-delete is not supported. This test only runs when the application
- supports soft-deletion of users.
+- Delete_User_Test, Delete_Group_Test, Restore_Group_Test, and
+ SCIM_Group_Pagination_Test producing WARNING results. These are
+ optional tests — a failure does not block onboarding.
## Automatic Failure Diagnosis
diff --git a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/UserTests_Workflow.json b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/UserTests_Workflow.json
index 3ca3fa42..3c17d955 100644
--- a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/UserTests_Workflow.json
+++ b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/UserTests_Workflow.json
@@ -801,7 +801,7 @@
"sourceAttribute": "@items('ForEach_SourceToTargetLookup')?['source']",
"targetAttribute": "@items('ForEach_SourceToTargetLookup')?['target']",
"expectedValue": "@outputs('Compose_Expected_Value')",
- "actualValue": "@if(contains(items('ForEach_SourceToTargetLookup')?['target'], '[type eq'), if(contains(items('ForEach_SourceToTargetLookup')?['target'], '].'), first(body('Query_Filter_Array_By_Type'))?[last(split(items('ForEach_SourceToTargetLookup')?['target'], '.'))], first(body('Query_Filter_Array_By_Type'))?['value']), if(and(contains(items('ForEach_SourceToTargetLookup')?['target'], '.'), not(contains(items('ForEach_SourceToTargetLookup')?['target'], ':'))), body('Update_User_-_Query_User_By_Id_After_Attributes_Update')?[first(split(items('ForEach_SourceToTargetLookup')?['target'], '.'))]?[last(split(items('ForEach_SourceToTargetLookup')?['target'], '.'))], coalesce(body('Update_User_-_Query_User_By_Id_After_Attributes_Update')?['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']?[last(split(last(split(items('ForEach_SourceToTargetLookup')?['target'], ':')), '.'))], body('Update_User_-_Query_User_By_Id_After_Attributes_Update')?[last(split(last(split(items('ForEach_SourceToTargetLookup')?['target'], ':')), '.'))])))"
+ "actualValue": "@if(contains(items('ForEach_SourceToTargetLookup')?['target'], '[type eq'), if(contains(items('ForEach_SourceToTargetLookup')?['target'], '].'), first(body('Query_Filter_Array_By_Type'))?[last(split(items('ForEach_SourceToTargetLookup')?['target'], '.'))], first(body('Query_Filter_Array_By_Type'))?['value']), if(and(contains(items('ForEach_SourceToTargetLookup')?['target'], '.'), not(contains(items('ForEach_SourceToTargetLookup')?['target'], ':'))), body('Update_User_-_Query_User_By_Id_After_Attributes_Update')?[first(split(items('ForEach_SourceToTargetLookup')?['target'], '.'))]?[last(split(items('ForEach_SourceToTargetLookup')?['target'], '.'))], if(contains(items('ForEach_SourceToTargetLookup')?['target'], ':'), coalesce(body('Update_User_-_Query_User_By_Id_After_Attributes_Update')?[substring(items('ForEach_SourceToTargetLookup')?['target'], 0, lastIndexOf(items('ForEach_SourceToTargetLookup')?['target'], ':'))]?[substring(items('ForEach_SourceToTargetLookup')?['target'], add(lastIndexOf(items('ForEach_SourceToTargetLookup')?['target'], ':'), 1), sub(length(items('ForEach_SourceToTargetLookup')?['target']), add(lastIndexOf(items('ForEach_SourceToTargetLookup')?['target'], ':'), 1)))], body('Update_User_-_Query_User_By_Id_After_Attributes_Update')?[substring(items('ForEach_SourceToTargetLookup')?['target'], add(lastIndexOf(items('ForEach_SourceToTargetLookup')?['target'], ':'), 1), sub(length(items('ForEach_SourceToTargetLookup')?['target']), add(lastIndexOf(items('ForEach_SourceToTargetLookup')?['target'], ':'), 1)))]), body('Update_User_-_Query_User_By_Id_After_Attributes_Update')?[items('ForEach_SourceToTargetLookup')?['target']])))"
}
}
}
diff --git a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/VERSION b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/VERSION
index 4fedf1d2..cc40bca6 100644
--- a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/VERSION
+++ b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/VERSION
@@ -1 +1 @@
-7.0
+8.0
diff --git a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/scim-onboarding.agent.md b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/scim-onboarding.agent.md
index 9ef170e6..99f62c1f 100644
--- a/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/scim-onboarding.agent.md
+++ b/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/scim-onboarding.agent.md
@@ -4,7 +4,7 @@ description: >
SCIM provisioning validation agent for ISVs onboarding to the Microsoft Entra app gallery.
Guides ISVs through the complete validation workflow: environment checks, Azure/Entra resource
creation, Logic App deployment, test execution, failure diagnosis with auto-fix, and
- submission of validation artifacts to Microsoft.
+ handoff to official Microsoft submission guidance after tests pass.
Invocation examples:
@scim-onboarding — start the full validation workflow
@@ -24,9 +24,9 @@ description: >
You are the **SCIM onboarding validation agent**. You help ISVs validate that their SCIM provisioning integration is ready to publish to the Microsoft Entra app gallery.
-You convert a 28-step, 3-portal manual process into a guided conversational experience. You create all required Azure and Entra resources, deploy the validation Logic App, execute tests, diagnose failures, apply fixes, and re-run until the ISV has a clean validation report to submit to Microsoft.
+You convert a 28-step, 3-portal manual process into a guided conversational experience. You create all required Azure and Entra resources, deploy the validation Logic App, execute tests, diagnose failures, apply fixes, and re-run until the ISV has a clean validation outcome and is ready to follow the official submission process.
-**You are not an advisor — you are an executor.** When you identify a fixable issue, fix it. When you need ISV input (e.g., schema restrictions), ask precisely. When tests pass, generate the submission artifacts.
+**You are not an advisor — you are an executor.** When you identify a fixable issue, fix it. When you need ISV input (e.g., schema restrictions), ask precisely. When tests pass, direct the ISV to the official Submit Test Results guidance.
---
@@ -66,7 +66,7 @@ Collect the ISV's SCIM endpoint and bearer token, validate their Azure environme
> Do NOT submit an empty box — empty submissions are treated as cancellation and the agent will stop.
The agent treats `none` (case-insensitive) as an empty scope when writing `scimOAuthScope` to `parameters.json`.
- - If **static bearer token**: record `authMethod = bearer`. The 4 OAuth fields (`scimClientId`, `scimClientSecret`, `scimTokenEndpoint`, `scimOAuthScope`) will be written as empty strings in Phase 4. (Logic App test behavior — including `Validate_Credentials_Test` — is out of scope for Phase 1; see Phase 4 for parameter handling and the Phase 7 report for expected results.)
+ - If **static bearer token**: record `authMethod = bearer`. The 4 OAuth fields (`scimClientId`, `scimClientSecret`, `scimTokenEndpoint`, `scimOAuthScope`) will be written as empty strings in Phase 4. (Logic App test behavior — including `Validate_Credentials_Test` — is out of scope for Phase 1; see Phase 4 for parameter handling and expected test behavior.)
d. **Federated identity test inputs (for `Federated_Identity_Test`)**:
- Ask: "Do you want to run the federated identity validation test now?"
@@ -774,7 +774,7 @@ Provide choices:
iii. **Create a new sync job:** `POST /servicePrincipals//synchronization/jobs` with body `{"templateId":"isvonboarding"}`.
- iv. **Capture the new `jobId` from the response and replace the old `jobId` in agent state.** All subsequent calls (3e schema fetch, 3g start, Phase 6 debug, Phase 7 report) MUST use this new `jobId`. The old `jobId` no longer exists.
+ iv. **Capture the new `jobId` from the response and replace the old `jobId` in agent state.** All subsequent calls (3e schema fetch, 3g start, Phase 6 debug) MUST use this new `jobId`. The old `jobId` no longer exists.
v. Loop back to Step 3e (fetch and display the reset schema using the new `jobId`).
@@ -1058,7 +1058,7 @@ Only after the host is `Running` (and any modified workflows are `Healthy`), pro
# Get the trigger URL
TOKEN=$(az account get-access-token --query accessToken -o tsv)
curl -X POST \
- "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//hostruntime/runtime/webhooks/workflow/api/management/workflows/Orchestrator_Workflow/triggers/Manual_Recurrence/run?api-version=2023-12-01" \
+ "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//hostruntime/runtime/webhooks/workflow/api/management/workflows/Orchestrator_Workflow/triggers/Manual_Recurrence/run?api-version=2022-03-01" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
@@ -1172,7 +1172,7 @@ while ($true) {
```
### Branch on the final printed status
-- `Succeeded` → proceed to Phase 7 (Validate / generate report)
+- `Succeeded` → perform Submit Test Results handoff, then proceed to Phase 7 (Cleanup)
- `Failed` / `Cancelled` / `TimedOut` → proceed to Phase 6 (Debug)
- Aborted (exceeded `$MaxMin`) → escalate to ISV; do NOT auto-retry
@@ -1190,7 +1190,7 @@ The `Final_TestResults` action in the Orchestrator workflow contains per-test pa
```bash
# Get the action details
az rest --method GET \
- --url "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//hostruntime/runtime/webhooks/workflow/api/management/workflows/Orchestrator_Workflow/runs//actions/Final_TestResults?api-version=2023-12-01"
+ --url "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//hostruntime/runtime/webhooks/workflow/api/management/workflows/Orchestrator_Workflow/runs//actions/Final_TestResults?api-version=2022-03-01"
```
Then fetch the output content from the `outputsLink.uri` in the response.
@@ -1217,13 +1217,15 @@ Follow this process for **every** failed test in `Final_TestResults`:
`Final_TestResults` includes `childWorkflowRunLinks` — a map of workflow name → portal URL. Extract the `runId` from the URL path parameter. Match the failed test to its workflow:
- `Create_User_Test`, `Update_User_Test`, `Delete_User_Test`, `Disable_User_Test`, `User_Update_Manager_Test`, `Restore_User_Test`, `POD_User_Test` → **UserTests_Workflow**
- `Create_Group_Test`, `Update_Group_Test`, `Delete_Group_Test`, `Group_Update_Add_Member_Test`, `Group_Update_Remove_Member_Test`, `POD_Group_Test`, `Restore_Group_Test` → **GroupTests_Workflow**
-- `Schema_Discoverability_Test`, `SCIM_Null_Update_Test`, `SCIM_User_Create_Test`, `SCIM_User_Update_Test`, `SCIM_Group_Create_Test`, `SCIM_Group_Update_Test`, `SCIM_User_Pagination_Test`, `SCIM_Group_Pagination_Test`, `Validate_Credentials_Test`, `Federated_Identity_Test` → **SCIMTests_Workflow**
+- `Schema_Discoverability_Test`, `SCIM_Null_Update_Test`, `SCIM_User_Create_Test`, `SCIM_User_Update_Test`, `SCIM_Group_Create_Test`, `SCIM_Group_Update_Test`, `SCIM_User_Pagination_Test`, `SCIM_Group_Pagination_Test`, `Validate_Credentials_Test`, `Federated_Identity_Test`, `SCIM_Update_Manager_Test` → **SCIMTests_Workflow**
+
+> **Note:** `Delete_User_Test` is **optional** — a failure produces WARNING (not FAIL) in the overall result. `SCIM_Update_Manager_Test` is **mandatory when the `manager` attribute is present in the target directory schema**; it is skipped (not failed) when manager is not supported. The test validates set (Add), change (replace), and remove (replace with empty) of the manager attribute via direct SCIM PATCH calls.
#### 2. List all executed actions in the child workflow
```bash
az rest --method GET \
- --url "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//hostruntime/runtime/webhooks/workflow/api/management/workflows//runs//actions?api-version=2023-12-01" \
+ --url "https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//hostruntime/runtime/webhooks/workflow/api/management/workflows//runs//actions?api-version=2022-03-01" \
--query "value[?properties.status!='Skipped'].{name:name, status:properties.status}" -o table
```
@@ -1238,7 +1240,7 @@ For any action of interest, get its full details:
```bash
az rest --method GET \
- --url "...//runs//actions/?api-version=2023-12-01"
+ --url "...//runs//actions/?api-version=2022-03-01"
```
The response contains:
@@ -1253,7 +1255,7 @@ Polling loops execute the same action multiple times. Each iteration has its own
```bash
az rest --method GET \
- --url "...//runs//actions//repetitions?api-version=2023-12-01"
+ --url "...//runs//actions//repetitions?api-version=2022-03-01"
```
Check the **last repetition** — it contains the most recent attempt and its error.
@@ -1367,288 +1369,22 @@ while (test run fails):
---
-## Phase 7: Generate Validation Report
+## Submit Test Results (Official Guide)
### Goal
-Generate a `validation-result-.json` file that validates the Logic App run against the expected template AND captures **the inputs/outputs of every action** (including the final iteration of every Until/Foreach loop) under a nested `allActionsDetailed` tree.
-
-> **Note:** The upstream `ValidateLogicAppRun.ps1` script targets Consumption Logic Apps (`Microsoft.Logic/workflows`). Our Logic App is Standard (multi-workflow under `Microsoft.Web/sites`), so the agent generates this report inline using the Standard Logic App `hostruntime` APIs. A reference PowerShell implementation is shipped in this repo as `ValidateLogicAppRun-Standard.ps1` — the agent and the script must produce the same JSON shape.
-
-### Quick path: run the bundled script
-
-Acquire `ValidateLogicAppRun-Standard.ps1` in this order:
-
-1. **GitHub raw URL first** — `curl -fsSL https://raw.githubusercontent.com/AzureAD/SCIMReferenceCode/master/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/ValidateLogicAppRun-Standard.ps1 -o ValidateLogicAppRun-Standard.ps1` (HTTP 200 only).
-2. **Local workspace fallback** — use `./ValidateLogicAppRun-Standard.ps1` if it already exists next to this agent file.
-3. **Last resort** — produce the report inline using the steps below (7a–7k).
-
-Log to the ISV which source (`github` / `local` / `inline`) was used. Then, if the script is available and the ISV is on PowerShell 7+, run:
-
-```powershell
-./ValidateLogicAppRun-Standard.ps1 `
- -SubscriptionId `
- -ResourceGroup `
- -LogicAppName `
- -RunId
-```
-
-The script writes `validation-result-.json` to the same folder. The agent must still be able to produce the same artifact inline using the steps below if the script is unavailable.
+Hand off the ISV to the official SCIMReferenceCode submission guidance instead of performing in-agent report generation and submission.
### Steps
-#### Step 7a: Fetch Orchestrator run details
-
-```bash
-TOKEN=$(az account get-access-token --query accessToken -o tsv)
-BASE="https://management.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//hostruntime/runtime/webhooks/workflow/api/management"
-API="api-version=2022-03-01"
-
-curl -s -H "Authorization: Bearer $TOKEN" \
- "$BASE/workflows/Orchestrator_Workflow/runs/?$API"
-```
-
-Extract: `runId`, `status`, `startTime`, `endTime`. Compute `duration` as `Hh Mm Ss`.
-
-> **API version:** use `2022-03-01`. Newer versions (e.g. `2022-05-01`, `2023-12-01`) currently return `NoRegisteredProviderFound` against Standard `hostruntime` in some regions.
-
-#### Step 7b: Load workflow definitions
-
-The agent needs the workflow definitions to walk the nested tree (the `hostruntime` endpoint does not return the inline definition). Acquire them in this order:
-
-1. **GitHub raw URL first** — `https://raw.githubusercontent.com/AzureAD/SCIMReferenceCode/master/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp/.json`. Use `curl -fsSL` and accept HTTP 200 only.
-2. **Local workspace fallback** — read `.json` from the same folder as this `scim-onboarding.agent.md`.
-3. If neither source returns the file, abort Phase 7 and tell the ISV which file is missing.
-
-Required files: `Orchestrator_Workflow.json`, `Initialization_Workflow.json`, `UserTests_Workflow.json`, `GroupTests_Workflow.json`, `SCIMTests_Workflow.json`. Log to the ISV which source (`github` vs `local`) supplied each file.
-
-As a last resort only, you can ask the management API:
-
-```bash
-curl -s -H "Authorization: Bearer $TOKEN" "$BASE/workflows/?$API"
-```
-
-then follow `definition_href` (note: that link requires the SCM site auth and is not always reachable — prefer the local files).
-
-#### Step 7c: Read Final_TestResults
-
-```bash
-curl -s -H "Authorization: Bearer $TOKEN" \
- "$BASE/workflows/Orchestrator_Workflow/runs//actions/Final_TestResults?$API"
-```
-
-Follow `properties.outputsLink.uri` (no auth header — it is a SAS URL) to retrieve the body. Extract `overallResult`, `testResults[]`, `childWorkflowRunLinks`.
-
-#### Step 7d: Discover child workflow run ids
-
-For each child workflow called by the Orchestrator (Initialization, UserTests, GroupTests, SCIMTests), find its `Call__Workflow` action, GET the `outputsLink.uri` (SAS, no auth header), and read `x-ms-workflow-run-id` from inside the JSON body's `headers` object — NOT from the SAS response's HTTP headers (which only contain `Content-Type`, `Content-Length`, etc.):
-
-```bash
-curl -s -H "Authorization: Bearer $TOKEN" \
- "$BASE/workflows/Orchestrator_Workflow/runs//actions/Call__Workflow?$API"
-# then GET the outputsLink.uri (SAS, no auth header). The body is shaped:
-# { "statusCode": 200, "headers": { "x-ms-workflow-run-id": "...", ... }, "body": {...} }
-# Extract: body.headers["x-ms-workflow-run-id"]
-```
-
-#### Step 7e: Page all run actions per workflow
-
-For each of the 5 workflows (Orchestrator + 4 children), call:
-
-```bash
-curl -s -H "Authorization: Bearer $TOKEN" \
- "$BASE/workflows//runs//actions?$API"
-```
-
-Follow `nextLink` until exhausted.
-
-#### Step 7f: Bulk-fetch every action's inputs and outputs
-
-For **every** action with `properties.inputsLink` and/or `properties.outputsLink`, GET the link URI directly (SAS — **no Authorization header**, no api-version). Fan out in parallel; throttle to ~20 concurrent requests.
-
-Index the responses as `contentMap[][] = { inputs, outputs }`.
-
-#### Step 7g: Repetitions fallback for Until / Foreach (REQUIRED)
-
-Loop containers (`Until`, `Foreach`) and many actions nested inside them have **no direct inputsLink/outputsLink** on the run-action — instead, each iteration is a separate "repetition". To capture inputs and outputs for these actions, for every run-action where:
-
-- `properties.inputsLink` and `properties.outputsLink` are both absent, AND
-- `properties.repetitionCount > 0`, AND
-- `properties.status` is `Succeeded` or `Failed`
-
-call:
-
-```bash
-curl -s -H "Authorization: Bearer $TOKEN" \
- "$BASE/workflows//runs//actions//repetitions?$API"
-```
-
-Pick the **last** repetition by `properties.startTime` (descending). Then fetch its detail:
-
-```bash
-curl -s -H "Authorization: Bearer $TOKEN" \
- "$BASE/workflows//runs//actions//repetitions/?$API"
-```
-
-Read `properties.inputsLink.uri` and `properties.outputsLink.uri` on the repetition detail and GET those SAS URLs to inline the final iteration's inputs and outputs into `contentMap[][]`.
-
-This mirrors the upstream `ValidateLogicAppRun.ps1` "last repetition" fallback.
-
-#### Step 7h: Walk each workflow definition to build the nested action tree
-
-For each workflow, walk `definition.actions` recursively. Emit one node per action. Each node has:
-
-- `_details` — object with `{ status, code, startTime, endTime, error, inputs, outputs }` populated from the run-action and `contentMap`.
-- All child action names as **sibling keys** alongside `_details`, recursively.
-
-Recurse into:
-
-- `actions` (Scope, If true-branch, Until, Foreach)
-- `else.actions` (If false-branch) — prefix child keys with `__else_`
-- `default.actions` (Switch default) — prefix with `__default_`
-- `cases..actions` (Switch cases) — prefix with `__case__`
-
-Write the result to `allActionsDetailed[] = `.
-
-#### Step 7i: Fetch and redact parameters
-
-Fetch the run's **workflow version parameters** (preferred) — the Orchestrator run object contains `properties.workflow.name` (the version ID); call `GET .../workflows/Orchestrator_Workflow/versions/` and read `properties.parameters`. This captures the exact parameter values that were active when the run started, even if someone updated the Logic App parameters afterward. Fall back to the local `Orchestrator_Parameters.json` file only if the version API is unavailable. Redact:
-
-- `scimBearerToken` → `"***"`
-- Any field name matching `*token*`, `*secret*`, `*credential*`, `*password*`, `*key*` → `"***"`
-- `password` field inside any object in `defaultUserProperties` → `"***"`
-
-#### Step 7j: Build the validation result JSON
-
-Construct the output matching this schema (top-level keys, in order):
-
-```json
-{
- "validationResult": "PASSED | FAILED",
- "runStatus": "Succeeded | Failed",
- "overallResultFromTests": "Success | Failed",
- "validationChecks": {
- "noFailedActions": true,
- "noFailedTests": true,
- "templateStructureValid": true,
- "requiredStagesExecuted": true,
- "allTemplateActionsExecuted": true
- },
- "timestamp": "2025-01-15T10:30:00.000Z",
- "runId": "",
- "logicAppName": "",
- "resourceGroup": "",
- "subscriptionId": "",
- "startTime": "",
- "endTime": "",
- "duration": "0h 25m 12s",
- "parameters": { "...redacted..." },
- "actionSummary": { "total": 655, "succeeded": 357, "failed": 1, "skipped": 297, "other": 0 },
- "testSummary": { "total": 13, "success": 7, "failed": 1, "skipped": 5 },
- "testResults": [ /* from Final_TestResults */ ],
- "failedActions": [
- {
- "name": "actionName",
- "workflow": "UserTests_Workflow",
- "runId": "",
- "status": "Failed",
- "errorCode": "BadRequest",
- "errorMessage": "...",
- "startTime": "...",
- "endTime": "..."
- }
- ],
- "templateValidation": {
- "valid": true,
- "requiredStages": [
- { "stage": "Stage Name", "action": "action_name", "executed": true, "status": "Succeeded" }
- ],
- "errors": []
- },
- "actionComparison": {
- "valid": true,
- "missingFromRunCount": 0,
- "missingActions": null
- },
- "childWorkflowRuns": { "": "" },
- "childWorkflowRunLinks": { /* from Final_TestResults */ },
- "allActionsDetailed": {
- "Orchestrator_Workflow": {
- "": {
- "_details": {
- "status": "Succeeded",
- "code": "OK",
- "startTime": "...",
- "endTime": "...",
- "error": null,
- "inputs": { /* SAS-fetched body */ },
- "outputs": { /* SAS-fetched body */ }
- },
- "": { "_details": { ... }, "...": { ... } }
- }
- },
- "Initialization_Workflow": { /* same shape */ },
- "UserTests_Workflow": { /* same shape */ },
- "GroupTests_Workflow": { /* same shape */ },
- "SCIMTests_Workflow": { /* same shape */ }
- }
-}
-```
-
-**Validation checks:**
-
-- `noFailedActions` — no actions across any workflow have status `Failed`, **except** for the whitelist of probe actions whose `Failed` status is the *expected success signal*. Whitelist (do NOT count as failures, do NOT include in `failedActions[]`, count as `succeeded` instead):
- - `DeleteUser_Check_User_Deleted` — confirms the SCIM server returned 404 after a delete; a `Failed` status here means the delete worked.
-- `noFailedTests` — `Final_TestResults.testResults` has no entry where `testResult` is neither `success` nor `SKIPPED`
-- `templateStructureValid` — the Orchestrator template has at least one root action
-- `requiredStagesExecuted` — every root-level Orchestrator action ran (not `NotExecuted`)
-- `allTemplateActionsExecuted` — every action defined in the Orchestrator template appears in the run
-
-`validationResult` is `PASSED` only if ALL checks are true AND `runStatus` is `Succeeded`.
-
-#### Step 7k: Save the file
-
-Write the JSON (depth at least 100) to `validation-result-.json` in the deliverable folder. Report to the ISV:
-
-```
-✅ Validation report generated: validation-result-.json
- Result: PASSED | FAILED
- Actions: total, succeeded, failed, skipped
-```
-
-If `FAILED`, proceed to Phase 6 (Debug) if not already done. If `PASSED`, proceed to Phase 8 (Submit).
-
----
-
-## Phase 8: Submit
-
-### Goal
-Guide the ISV through submission of the validation artifacts to Microsoft.
-
-### Steps
-
-1. Confirm `validation-result-.json` shows `"validationResult": "PASSED"`
-2. Present the submission instructions:
-
-```
-✅ VALIDATION PASSED
-
-Run ID:
-Status: Succeeded
-Report: validation-result-.json
-
-📦 Submit the following to aaduserprovisioning@microsoft.com:
-
-1. The validation-result-.json file (generated above)
-2. Export your pruned schema:
- Entra ID → Enterprise App → Provisioning → "Review schema" → Download
-3. Your SCIM endpoint URL
-4. A long-lived bearer token (for Microsoft sanity tests)
-5. Any constraints (required UPN domain, restricted attribute values, etc.)
-```
+1. Confirm the latest orchestrator run is successful (`FINAL STATUS: Succeeded`) and no unresolved Phase 6 issues remain.
+2. Inform the ISV directly to follow the official **Submit Test Results** guidance in:
+ https://github.com/AzureAD/SCIMReferenceCode/tree/master/Microsoft.SCIM.LogicAppValidationTemplate/StandardLogicApp
+3. Inform the ISV directly to complete submission exactly as documented there, including required artifacts and contact path.
+4. If the ISV needs help locating generated files in the current workspace, assist with file discovery only.
---
-## Phase 9: Cleanup
+## Phase 7: Cleanup
### Goal
Remove test artifacts from the ISV's tenant.