From 05a8664adb3ce3120767af996254767920295512 Mon Sep 17 00:00:00 2001 From: Andrea Tomassilli Date: Wed, 14 Jan 2026 21:44:36 +0000 Subject: [PATCH 01/12] Fix Get-AzRoleDefinition flattening permissions and losing per-permission conditions --- .../Resources/DataActionsRoleDefinition.json | 18 +- .../Resources/InvalidRoleDefinition.json | 12 +- .../Resources/NewRoleDefinition.json | 12 +- .../Resources/RoleDefinition.json | 12 +- .../ScenarioTests/RoleDefinitionTests.ps1 | 92 ++-- .../AuthorizationClientExtensionsTests.cs | 460 ++++++++++++++++++ src/Resources/Resources/ChangeLog.md | 10 + .../AuthorizationClient.cs | 23 +- .../AuthorizationClientExtensions.cs | 15 +- .../Models.Authorization/PSPermission.cs | 4 + .../Models.Authorization/PSRoleDefinition.cs | 12 +- 11 files changed, 578 insertions(+), 92 deletions(-) create mode 100644 src/Resources/Resources.Test/UnitTests/Authorization/AuthorizationClientExtensionsTests.cs diff --git a/src/Resources/Resources.Test/Resources/DataActionsRoleDefinition.json b/src/Resources/Resources.Test/Resources/DataActionsRoleDefinition.json index 57531d5d8810..dde534455ba4 100644 --- a/src/Resources/Resources.Test/Resources/DataActionsRoleDefinition.json +++ b/src/Resources/Resources.Test/Resources/DataActionsRoleDefinition.json @@ -1,13 +1,17 @@ { "Name": "CustomRole Tests Role New", "Description": "Test role", - "Actions": [], - "NotActions": [], - "DataActions": [ - "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/*" - ], - "NotDataActions": [ - "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write" + "Permissions": [ + { + "Actions": [], + "NotActions": [], + "DataActions": [ + "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/*" + ], + "NotDataActions": [ + "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write" + ] + } ], "AssignableScopes": ["/subscriptions/0b1f6471-1bf0-4dda-aec3-cb9272f09590"] } \ No newline at end of file diff --git a/src/Resources/Resources.Test/Resources/InvalidRoleDefinition.json b/src/Resources/Resources.Test/Resources/InvalidRoleDefinition.json index 7243b1fc1b20..be9697410487 100644 --- a/src/Resources/Resources.Test/Resources/InvalidRoleDefinition.json +++ b/src/Resources/Resources.Test/Resources/InvalidRoleDefinition.json @@ -2,10 +2,14 @@ "Name": "Another Invalid test role", "Id": "85E460B3-89E9-48BA-9DCD-A8A99D64A674", "Description": "Test role", - "Actions": [ - "Microsoft.Authorization/*/read", - "Microsoft.Support/*" + "Permissions": [ + { + "Actions": [ + "Microsoft.Authorization/*/read", + "Microsoft.Support/*" + ], + "NotActions": [] + } ], - "NotActions": [], "AssignableScopes": ["/subscriptions/4004a9fd-d58e-48dc-aeb2-4a4aec58606f/ResourceGroups"] } \ No newline at end of file diff --git a/src/Resources/Resources.Test/Resources/NewRoleDefinition.json b/src/Resources/Resources.Test/Resources/NewRoleDefinition.json index 000751a01ca9..81e7e215078d 100644 --- a/src/Resources/Resources.Test/Resources/NewRoleDefinition.json +++ b/src/Resources/Resources.Test/Resources/NewRoleDefinition.json @@ -1,10 +1,14 @@ { "Name": "CustomRole Tests Role", "Description": "Test role", - "Actions": [ - "Microsoft.Authorization/*/read", - "Microsoft.Support/*" + "Permissions": [ + { + "Actions": [ + "Microsoft.Authorization/*/read", + "Microsoft.Support/*" + ], + "NotActions": [] + } ], - "NotActions": [], "AssignableScopes": ["/subscriptions/4004a9fd-d58e-48dc-aeb2-4a4aec58606f"] } \ No newline at end of file diff --git a/src/Resources/Resources.Test/Resources/RoleDefinition.json b/src/Resources/Resources.Test/Resources/RoleDefinition.json index 776697b09eb6..e2fc0ea7d1fc 100644 --- a/src/Resources/Resources.Test/Resources/RoleDefinition.json +++ b/src/Resources/Resources.Test/Resources/RoleDefinition.json @@ -2,11 +2,15 @@ "Name": "Another tests role", "Id": "85E460B3-89E9-48BA-9DCD-A8A99D64A674", "Description": "Test role", - "Actions": [ - "Microsoft.Authorization/*/read", - "Microsoft.Support/*" + "Permissions": [ + { + "Actions": [ + "Microsoft.Authorization/*/read", + "Microsoft.Support/*" + ], + "NotActions": [] + } ], - "NotActions": [], "AssignableScopes": ["/subscriptions/4004a9fd-d58e-48dc-aeb2-4a4aec58606f", "/subscriptions/4004a9fd-d58e-48dc-aeb2-4a4aec58606f/ResourceGroups/rbactest"] } \ No newline at end of file diff --git a/src/Resources/Resources.Test/ScenarioTests/RoleDefinitionTests.ps1 b/src/Resources/Resources.Test/ScenarioTests/RoleDefinitionTests.ps1 index 366b193ce68d..3fe1f49ea6a9 100644 --- a/src/Resources/Resources.Test/ScenarioTests/RoleDefinitionTests.ps1 +++ b/src/Resources/Resources.Test/ScenarioTests/RoleDefinitionTests.ps1 @@ -27,33 +27,31 @@ function Test-RDWithAbacConditionsGet { $rgScope = "/subscriptions/" + $subscription[0].SubscriptionId + "/resourceGroups/" + $resource.ResourceGroupName $resourceScope = $resource.ResourceId - # Task 1: Get Contributor role, verify condition doesn't exist + # Task 1: Get Reader role, verify permissions structure exists but no condition $roleDef1 = Get-AzRoleDefinition -Name "Reader" Assert-AreEqual $roleDef1.Name "Reader" Assert-AreEqual $false $roleDef1.IsCustom - # TODO: replace the active code with the commented Permissions check after the breaking change - # Assert-NotNull $roleDef1.Permissions - # Assert-True { $roleDef1.Permissions.Length -gt 0 } - # Assert-NotNull $roleDef1.Permissions[0].Actions - # Assert-True { $roleDef1.Permissions[0].Actions.Length -gt 0 } - Assert-NotNull $roleDef1.Actions - Assert-True { $roleDef1.Actions.Length -gt 0 } + # Verify Permissions property exists and has content + Assert-NotNull $roleDef1.Permissions + Assert-True { $roleDef1.Permissions.Count -gt 0 } + Assert-NotNull $roleDef1.Permissions[0].Actions + Assert-True { $roleDef1.Permissions[0].Actions.Count -gt 0 } - # Task 2: Get Key Vault role, verify condition exists + # Task 2: Get Key Vault Data Access Administrator role, verify condition exists in Permissions $roleDef2 = Get-AzRoleDefinition -Id 8b54135c-b56d-4d72-a534-26097cfdc8d8 - Assert-AreEqual $false $roleDef1.IsCustom - - # TODO: replace the active code with the commented Permissions check after the breaking change - # Assert-NotNull $roleDef2.Permissions - # Assert-True { $roleDef2.Permissions.Length -gt 0 } - # Assert-NotNull $roleDef2.Permissions[0].Actions - # Assert-NotNull $roleDef2.Permissions[0].Condition - # Assert-NotNull $roleDef2.Permissions[0].ConditionVersion - Assert-NotNull $roleDef2.Actions - Assert-True { $roleDef2.Actions.Length -gt 0 } - Assert-NotNull $roleDef2.ConditionVersion - Assert-NotNull $roleDef2.Condition + Assert-AreEqual $false $roleDef2.IsCustom + + # Verify Permissions structure with condition + Assert-NotNull $roleDef2.Permissions + Assert-True { $roleDef2.Permissions.Count -gt 0 } + Assert-NotNull $roleDef2.Permissions[0].Actions + + # Find the permission entry with condition + $conditionPermission = $roleDef2.Permissions | Where-Object { $_.Condition -ne $null } | Select-Object -First 1 + Assert-NotNull $conditionPermission "Expected at least one permission with a condition" + Assert-NotNull $conditionPermission.Condition + Assert-NotNull $conditionPermission.ConditionVersion } <# @@ -70,25 +68,25 @@ function Test-RoleDefinitionCreateTests { $rd = Get-AzRoleDefinition -Name $rdName Assert-AreEqual "Test role" $rd.Description Assert-AreEqual $true $rd.IsCustom - Assert-NotNull $rd.Actions - Assert-AreEqual "Microsoft.Authorization/*/read" $rd.Actions[0] - Assert-AreEqual "Microsoft.Support/*" $rd.Actions[1] + Assert-NotNull $rd.Permissions + Assert-NotNull $rd.Permissions[0].Actions + Assert-AreEqual "Microsoft.Authorization/*/read" $rd.Permissions[0].Actions[0] + Assert-AreEqual "Microsoft.Support/*" $rd.Permissions[0].Actions[1] Assert-NotNull $rd.AssignableScopes - Assert-Null $rd.DataActions - Assert-Null $rd.NotDataActions # Basic positive case - read from object $roleDef = Get-AzRoleDefinition -Name "Reader" $roleDef.Id = $null $roleDef.Name = "New Custom Reader" - $roleDef.Actions.Add("Microsoft.ClassicCompute/virtualMachines/restart/action") + $roleDef.Permissions[0].Actions.Add("Microsoft.ClassicCompute/virtualMachines/restart/action") $roleDef.Description = "Read, monitor and restart virtual machines" $roleDef.AssignableScopes[0] = "/subscriptions/4004a9fd-d58e-48dc-aeb2-4a4aec58606f" New-AzRoleDefinitionWithId -Role $roleDef -RoleDefinitionId 678c13e9-6637-4471-8414-e95f7a660b0b $addedRoleDef = Get-AzRoleDefinition -Name "New Custom Reader" - Assert-NotNull $addedRoleDef.Actions + Assert-NotNull $addedRoleDef.Permissions + Assert-NotNull $addedRoleDef.Permissions[0].Actions Assert-AreEqual $roleDef.Description $addedRoleDef.Description Assert-AreEqual $roleDef.AssignableScopes $addedRoleDef.AssignableScopes Assert-AreEqual $true $addedRoleDef.IsCustom @@ -151,7 +149,7 @@ function Test-RDPositiveScenarios { $rd = Get-AzRoleDefinition -Name $rdName # Update the role definition with action that was created in the step above. - $rd.Actions.Add('Microsoft.Authorization/*/read') + $rd.Permissions[0].Actions.Add('Microsoft.Authorization/*/read') $updatedRd = Set-AzRoleDefinition -Role $rd Assert-NotNull $updatedRd @@ -325,26 +323,28 @@ function Test-RoleDefinitionDataActionsCreateTests { $rd = Get-AzRoleDefinition -Name $rdName Assert-AreEqual "Test role" $rd.Description Assert-AreEqual $true $rd.IsCustom - Assert-NotNull $rd.DataActions - Assert-AreEqual "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/*" $rd.DataActions[0] - Assert-NotNull $rd.NotDataActions - Assert-AreEqual "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write" $rd.NotDataActions[0] + Assert-NotNull $rd.Permissions + Assert-NotNull $rd.Permissions[0].DataActions + Assert-AreEqual "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/*" $rd.Permissions[0].DataActions[0] + Assert-NotNull $rd.Permissions[0].NotDataActions + Assert-AreEqual "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write" $rd.Permissions[0].NotDataActions[0] Assert-NotNull $rd.AssignableScopes - Assert-Null $rd.Actions - Assert-Null $rd.NotActions + Assert-True { $rd.Permissions[0].Actions.Count -eq 0 } + Assert-True { $rd.Permissions[0].NotActions.Count -eq 0 } # Basic positive case - read from object $roleDef = Get-AzRoleDefinition -Name "Reader" $roleDef.Id = $null $roleDef.Name = "New Custom Reader" - $roleDef.DataActions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write") + $roleDef.Permissions[0].DataActions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write") $roleDef.Description = "Read, monitor and restart virtual machines" $roleDef.AssignableScopes[0] = "/subscriptions/0b1f6471-1bf0-4dda-aec3-cb9272f09590" New-AzRoleDefinitionWithId -Role $roleDef -RoleDefinitionId 3be51641-acdb-4f4a-801f-a93da8c5762d $addedRoleDef = Get-AzRoleDefinition -Name "New Custom Reader" - Assert-NotNull $addedRoleDef.Actions + Assert-NotNull $addedRoleDef.Permissions + Assert-NotNull $addedRoleDef.Permissions[0].Actions Assert-AreEqual $roleDef.Description $addedRoleDef.Description Assert-AreEqual $roleDef.AssignableScopes $addedRoleDef.AssignableScopes Assert-AreEqual $true $addedRoleDef.IsCustom @@ -481,23 +481,23 @@ function Test-RDDataActionsNegativeTestCases { Assert-NotNull $createdRole $expectedExceptionForActions = "'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/*' does not match any of the actions supported by the providers." - $createdRole.Actions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/blobs/*") + $createdRole.Permissions[0].Actions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/blobs/*") Assert-Throws { New-AzRoleDefinitionWithId -Role $createdRole -RoleDefinitionId 0309cc23-a0be-471f-abeb-dd411a8422c7 } $expectedExceptionForActions - $createdRole.Actions.Clear() + $createdRole.Permissions[0].Actions.Clear() - $createdRole.DataActions.Add("Microsoft.Authorization/*/read") + $createdRole.Permissions[0].DataActions.Add("Microsoft.Authorization/*/read") $expectedExceptionForDataActions = "The resource provider referenced in the action has not published any data operations." Assert-Throws { New-AzRoleDefinitionWithId -Role $createdRole -RoleDefinitionId 06801870-23ba-41ee-8bda-b0e2360164a8 } $expectedExceptionForDataActions - $createdRole.DataActions.Clear() + $createdRole.Permissions[0].DataActions.Clear() - $createdRole.DataActions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/blobs/*") - $createdRole.NotActions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/blobs/*") + $createdRole.Permissions[0].DataActions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/blobs/*") + $createdRole.Permissions[0].NotActions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/blobs/*") Assert-Throws { New-AzRoleDefinitionWithId -Role $createdRole -RoleDefinitionId e4c2893e-f945-4831-8b9f-3568eff03170 } $expectedExceptionForActions - $createdRole.NotActions.Clear() + $createdRole.Permissions[0].NotActions.Clear() - $createdRole.NotDataActions.Add("Microsoft.Authorization/*/read") + $createdRole.Permissions[0].NotDataActions.Add("Microsoft.Authorization/*/read") Assert-Throws { New-AzRoleDefinitionWithId -Role $createdRole -RoleDefinitionId a8ac9ed7-0ce6-4425-a221-c3d4c3063dc2 } $expectedExceptionForDataActions - $createdRole.NotDataActions.Clear() + $createdRole.Permissions[0].NotDataActions.Clear() # Basic positive case - read from object Remove-AzRoleDefinition -Id $createdRole.Id -Force diff --git a/src/Resources/Resources.Test/UnitTests/Authorization/AuthorizationClientExtensionsTests.cs b/src/Resources/Resources.Test/UnitTests/Authorization/AuthorizationClientExtensionsTests.cs new file mode 100644 index 000000000000..b51d1c6e88cf --- /dev/null +++ b/src/Resources/Resources.Test/UnitTests/Authorization/AuthorizationClientExtensionsTests.cs @@ -0,0 +1,460 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Microsoft.Azure.Commands.Resources.Models.Authorization; +using Microsoft.Azure.Management.Authorization.Models; +using Microsoft.WindowsAzure.Commands.ScenarioTest; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Microsoft.Azure.Commands.Resources.Test.UnitTests.Authorization +{ + public class AuthorizationClientExtensionsTests + { + #region Helper Methods + + private static RoleDefinition CreateRoleDefinition( + string id, + string roleName, + string roleType, + List permissions, + string description = "Test role") + { + var guid = id.Split('/').Last(); + return new RoleDefinition( + id: id, + name: guid, + type: "Microsoft.Authorization/roleDefinitions", + roleName: roleName, + description: description, + roleType: roleType, + permissions: permissions, + assignableScopes: ["/"] + ); + } + + private static Permission CreatePermission( + List actions = null, + List notActions = null, + List dataActions = null, + List notDataActions = null, + string condition = null, + string conditionVersion = null) + { + return new Permission( + actions: actions ?? [], + notActions: notActions ?? [], + dataActions: dataActions ?? [], + notDataActions: notDataActions ?? [], + condition: condition, + conditionVersion: conditionVersion); + } + + #endregion + + #region ToPSRoleDefinition - Null handling + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ToPSRoleDefinition_NullRole_ReturnsNull() + { + RoleDefinition roleDefinition = null; + Assert.Null(roleDefinition.ToPSRoleDefinition()); + } + + #endregion + + #region ToPSRoleDefinition - Role Type Tests + + [Theory] + [Trait(Category.AcceptanceType, Category.CheckIn)] + [InlineData("BuiltInRole", false)] + [InlineData("CustomRole", true)] + public void ToPSRoleDefinition_RoleType_SetsIsCustomCorrectly(string roleType, bool expectedIsCustom) + { + var roleDefinition = CreateRoleDefinition( + id: "/providers/Microsoft.Authorization/roleDefinitions/12345678-1234-1234-1234-123456789012", + roleName: "Test Role", + roleType: roleType, + permissions: [CreatePermission(actions: ["*/read"])] + ); + + var result = roleDefinition.ToPSRoleDefinition(); + + Assert.Equal(expectedIsCustom, result.IsCustom); + } + + #endregion + + #region ToPSRoleDefinition - ID Extraction Tests + + [Theory] + [Trait(Category.AcceptanceType, Category.CheckIn)] + [InlineData("/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7", "acdd72a7-3385-48ef-bd42-f606fba81ae7")] + [InlineData("/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleDefinitions/11111111-1111-1111-1111-111111111111", "11111111-1111-1111-1111-111111111111")] + public void ToPSRoleDefinition_ExtractsIdFromFullyQualifiedPath(string fullId, string expectedId) + { + var roleDefinition = CreateRoleDefinition( + id: fullId, + roleName: "Test Role", + roleType: "BuiltInRole", + permissions: [CreatePermission(actions: ["*/read"])] + ); + + var result = roleDefinition.ToPSRoleDefinition(); + + Assert.Equal(expectedId, result.Id); + } + + #endregion + + #region ToPSRoleDefinition - Permission Structure Tests + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ToPSRoleDefinition_SinglePermission_PreservesAllActionTypes() + { + var roleDefinition = CreateRoleDefinition( + id: "/providers/Microsoft.Authorization/roleDefinitions/12345678-1234-1234-1234-123456789012", + roleName: "Full Permission Role", + roleType: "CustomRole", + permissions: + [ + CreatePermission( + actions: ["Microsoft.Storage/*/read", "Microsoft.Compute/*/read"], + notActions: ["Microsoft.Storage/storageAccounts/delete"], + dataActions: ["Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read"], + notDataActions: ["Microsoft.Storage/storageAccounts/blobServices/containers/blobs/delete"]) + ] + ); + + var result = roleDefinition.ToPSRoleDefinition(); + + Assert.Single(result.Permissions); + var perm = result.Permissions[0]; + Assert.Equal(2, perm.Actions.Count); + Assert.Single(perm.NotActions); + Assert.Single(perm.DataActions); + Assert.Single(perm.NotDataActions); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ToPSRoleDefinition_MultiplePermissions_PreservesAllPermissions() + { + var roleDefinition = CreateRoleDefinition( + id: "/providers/Microsoft.Authorization/roleDefinitions/95dd08a6-00bd-4661-84bf-f6726f83a4d0", + roleName: "Azure Container Storage Contributor", + roleType: "BuiltInRole", + permissions: + [ + CreatePermission( + actions: + [ + "Microsoft.KubernetesConfiguration/extensions/write", + "Microsoft.KubernetesConfiguration/extensions/read", + "Microsoft.Authorization/*/read" + ]), + CreatePermission( + actions: + [ + "Microsoft.Authorization/roleAssignments/write", + "Microsoft.Authorization/roleAssignments/delete" + ], + condition: "((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})))", + conditionVersion: "2.0") + ] + ); + + var result = roleDefinition.ToPSRoleDefinition(); + + Assert.Equal(2, result.Permissions.Count); + + // First permission - no condition + Assert.Equal(3, result.Permissions[0].Actions.Count); + Assert.Null(result.Permissions[0].Condition); + Assert.Null(result.Permissions[0].ConditionVersion); + + // Second permission - with condition + Assert.Equal(2, result.Permissions[1].Actions.Count); + Assert.NotNull(result.Permissions[1].Condition); + Assert.Equal("2.0", result.Permissions[1].ConditionVersion); + } + + #endregion + + #region ToPSRoleDefinition - Condition Tests + + [Theory] + [Trait(Category.AcceptanceType, Category.CheckIn)] + [InlineData(null, null)] + [InlineData("((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})))", "2.0")] + public void ToPSRoleDefinition_ConditionAndVersion_PreservedCorrectly(string condition, string conditionVersion) + { + var roleDefinition = CreateRoleDefinition( + id: "/providers/Microsoft.Authorization/roleDefinitions/12345678-1234-1234-1234-123456789012", + roleName: "Test Role", + roleType: "BuiltInRole", + permissions: + [ + CreatePermission( + actions: ["Microsoft.Authorization/roleAssignments/write"], + condition: condition, + conditionVersion: conditionVersion) + ] + ); + + var result = roleDefinition.ToPSRoleDefinition(); + + Assert.Single(result.Permissions); + Assert.Equal(condition, result.Permissions[0].Condition); + Assert.Equal(conditionVersion, result.Permissions[0].ConditionVersion); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ToPSRoleDefinition_ConditionOnlyOnSecondPermission_PreservesCorrectAssociation() + { + // This is the key bug fix test: condition should stay with its permission + var roleDefinition = CreateRoleDefinition( + id: "/providers/Microsoft.Authorization/roleDefinitions/95dd08a6-00bd-4661-84bf-f6726f83a4d0", + roleName: "Azure Container Storage Contributor", + roleType: "BuiltInRole", + permissions: + [ + CreatePermission(actions: ["Microsoft.KubernetesConfiguration/extensions/write"]), + CreatePermission( + actions: ["Microsoft.Authorization/roleAssignments/write"], + condition: "ABAC condition here", + conditionVersion: "2.0") + ] + ); + + var result = roleDefinition.ToPSRoleDefinition(); + + // The condition must be on the SECOND permission, not flattened or on the first + Assert.Null(result.Permissions[0].Condition); + Assert.Null(result.Permissions[0].ConditionVersion); + Assert.Equal("ABAC condition here", result.Permissions[1].Condition); + Assert.Equal("2.0", result.Permissions[1].ConditionVersion); + + // Also verify actions are correctly associated + Assert.Contains("Microsoft.KubernetesConfiguration/extensions/write", result.Permissions[0].Actions); + Assert.Contains("Microsoft.Authorization/roleAssignments/write", result.Permissions[1].Actions); + } + + #endregion + + #region PSPermission Tests + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void PSPermission_AllProperties_CanBeSetAndRetrieved() + { + var permission = new PSPermission + { + Actions = ["action1", "action2"], + NotActions = ["notAction1"], + DataActions = ["dataAction1"], + NotDataActions = ["notDataAction1"], + Condition = "test condition", + ConditionVersion = "2.0" + }; + + Assert.Equal(2, permission.Actions.Count); + Assert.Single(permission.NotActions); + Assert.Single(permission.DataActions); + Assert.Single(permission.NotDataActions); + Assert.Equal("test condition", permission.Condition); + Assert.Equal("2.0", permission.ConditionVersion); + } + + #endregion + + #region Known Azure Role Tests + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ToPSRoleDefinition_ReaderRole_CorrectlyConverted() + { + var roleDefinition = CreateRoleDefinition( + id: "/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7", + roleName: "Reader", + roleType: "BuiltInRole", + permissions: [CreatePermission(actions: ["*/read"])], + description: "View all resources, but does not allow you to make any changes." + ); + + var result = roleDefinition.ToPSRoleDefinition(); + + Assert.Equal("Reader", result.Name); + Assert.Equal("acdd72a7-3385-48ef-bd42-f606fba81ae7", result.Id); + Assert.False(result.IsCustom); + Assert.Single(result.Permissions); + Assert.Single(result.Permissions[0].Actions); + Assert.Equal("*/read", result.Permissions[0].Actions[0]); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ToPSRoleDefinition_KeyVaultDataAccessAdministrator_HasCondition() + { + var roleDefinition = CreateRoleDefinition( + id: "/providers/Microsoft.Authorization/roleDefinitions/8b54135c-b56d-4d72-a534-26097cfdc8d8", + roleName: "Key Vault Data Access Administrator", + roleType: "BuiltInRole", + permissions: + [ + CreatePermission( + actions: + [ + "Microsoft.Authorization/roleAssignments/write", + "Microsoft.Authorization/roleAssignments/delete" + ], + condition: "((!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'})) OR ...)", + conditionVersion: "2.0") + ] + ); + + var result = roleDefinition.ToPSRoleDefinition(); + + Assert.Single(result.Permissions); + Assert.NotNull(result.Permissions[0].Condition); + Assert.Equal("2.0", result.Permissions[0].ConditionVersion); + } + + #endregion + + #region Edge Case Tests + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ToPSRoleDefinition_NullPermissions_ReturnsNullPermissionsList() + { + var roleDefinition = new RoleDefinition( + id: "/providers/Microsoft.Authorization/roleDefinitions/12345678-1234-1234-1234-123456789012", + name: "12345678-1234-1234-1234-123456789012", + type: "Microsoft.Authorization/roleDefinitions", + roleName: "Test Role", + description: "Test", + roleType: "CustomRole", + permissions: null, + assignableScopes: ["/"] + ); + + var result = roleDefinition.ToPSRoleDefinition(); + + Assert.Null(result.Permissions); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ToPSRoleDefinition_EmptyPermissionsList_ReturnsEmptyList() + { + var roleDefinition = CreateRoleDefinition( + id: "/providers/Microsoft.Authorization/roleDefinitions/12345678-1234-1234-1234-123456789012", + roleName: "Test Role", + roleType: "CustomRole", + permissions: [] + ); + + var result = roleDefinition.ToPSRoleDefinition(); + + Assert.NotNull(result.Permissions); + Assert.Empty(result.Permissions); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ToPSRoleDefinition_PermissionWithEmptyActionLists_PreservesEmptyLists() + { + var roleDefinition = CreateRoleDefinition( + id: "/providers/Microsoft.Authorization/roleDefinitions/12345678-1234-1234-1234-123456789012", + roleName: "Test Role", + roleType: "CustomRole", + permissions: + [ + CreatePermission( + actions: [], + notActions: [], + dataActions: [], + notDataActions: []) + ] + ); + + var result = roleDefinition.ToPSRoleDefinition(); + + Assert.Single(result.Permissions); + Assert.Empty(result.Permissions[0].Actions); + Assert.Empty(result.Permissions[0].NotActions); + Assert.Empty(result.Permissions[0].DataActions); + Assert.Empty(result.Permissions[0].NotDataActions); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ToPSRoleDefinition_ThreePermissions_AllPreservedInOrder() + { + var roleDefinition = CreateRoleDefinition( + id: "/providers/Microsoft.Authorization/roleDefinitions/12345678-1234-1234-1234-123456789012", + roleName: "Complex Role", + roleType: "CustomRole", + permissions: + [ + CreatePermission(actions: ["action1"]), + CreatePermission(actions: ["action2"], condition: "cond2"), + CreatePermission(actions: ["action3"], condition: "cond3", conditionVersion: "2.0") + ] + ); + + var result = roleDefinition.ToPSRoleDefinition(); + + Assert.Equal(3, result.Permissions.Count); + Assert.Contains("action1", result.Permissions[0].Actions); + Assert.Null(result.Permissions[0].Condition); + Assert.Contains("action2", result.Permissions[1].Actions); + Assert.Equal("cond2", result.Permissions[1].Condition); + Assert.Contains("action3", result.Permissions[2].Actions); + Assert.Equal("cond3", result.Permissions[2].Condition); + Assert.Equal("2.0", result.Permissions[2].ConditionVersion); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ToPSRoleDefinition_DataActionsOnlyPermission_PreservesCorrectly() + { + var roleDefinition = CreateRoleDefinition( + id: "/providers/Microsoft.Authorization/roleDefinitions/12345678-1234-1234-1234-123456789012", + roleName: "Data Actions Only Role", + roleType: "CustomRole", + permissions: + [ + CreatePermission( + dataActions: ["Microsoft.Storage/storageAccounts/blobServices/containers/blobs/*"], + notDataActions: ["Microsoft.Storage/storageAccounts/blobServices/containers/blobs/delete"]) + ] + ); + + var result = roleDefinition.ToPSRoleDefinition(); + + Assert.Single(result.Permissions); + Assert.Empty(result.Permissions[0].Actions); + Assert.Single(result.Permissions[0].DataActions); + Assert.Single(result.Permissions[0].NotDataActions); + } + + #endregion + } +} diff --git a/src/Resources/Resources/ChangeLog.md b/src/Resources/Resources/ChangeLog.md index b11f31a8279e..a963c17e2268 100644 --- a/src/Resources/Resources/ChangeLog.md +++ b/src/Resources/Resources/ChangeLog.md @@ -19,6 +19,16 @@ --> ## Upcoming Release +* [Breaking Change] Updated `Get-AzRoleDefinition` output model: + - Added `Permissions` property (list of `PSPermission` objects) to `PSRoleDefinition` to preserve full permission structure including per-permission conditions + - Removed `Actions`, `NotActions`, `DataActions`, and `NotDataActions` properties from `PSRoleDefinition` (these are now only available inside each `PSPermission`) + - Removed `Condition` and `ConditionVersion` properties from `PSRoleDefinition` as they incorrectly flattened multi-permission role definitions + - Added `Condition` and `ConditionVersion` properties to `PSPermission` + - Users should now access actions via `$role.Permissions[n].Actions` and conditions via `$role.Permissions[n].Condition` +* [Breaking Change] Updated `New-AzRoleDefinition -InputFile` JSON format: + - JSON input files must now use the `Permissions` array format instead of flattened `Actions`/`NotActions`/`DataActions`/`NotDataActions` properties + - Old format: `{ "Actions": ["*"], "NotActions": [] }` + - New format: `{ "Permissions": [{ "Actions": ["*"], "NotActions": [] }] }` ## Version 9.0.0 * Removed unavailable variant Get-AzRoleEligibleChildResource cmdlet for InputObject parameter. diff --git a/src/Resources/Resources/Models.Authorization/AuthorizationClient.cs b/src/Resources/Resources/Models.Authorization/AuthorizationClient.cs index 5be6a927216b..8e1a6d2680fc 100644 --- a/src/Resources/Resources/Models.Authorization/AuthorizationClient.cs +++ b/src/Resources/Resources/Models.Authorization/AuthorizationClient.cs @@ -649,15 +649,18 @@ private IList ToRoleDefinitionPermissions(PSRoleDefinition role) { IList permissions = new List(); - if (role != null) + if (role?.Permissions != null) { - permissions.Add(new Permission( - role.Actions != null ? new List(role.Actions) : new List(), - role.NotActions != null ? new List(role.NotActions) : new List(), - role.DataActions != null ? new List(role.DataActions) : new List(), - role.NotDataActions != null ? new List(role.NotDataActions) : new List(), - role.Condition, - role.ConditionVersion)); + foreach (var perm in role.Permissions) + { + permissions.Add(new Permission( + perm.Actions ?? new List(), + perm.NotActions ?? new List(), + perm.DataActions ?? new List(), + perm.NotDataActions ?? new List(), + perm.Condition, + perm.ConditionVersion)); + } } return permissions; @@ -680,11 +683,11 @@ private static void ValidateRoleDefinition(PSRoleDefinition roleDefinition) throw new ArgumentException(ProjectResources.InvalidAssignableScopes); } - if ((roleDefinition.Actions == null || !roleDefinition.Actions.Any()) && (roleDefinition.DataActions == null || !roleDefinition.DataActions.Any())) + if (roleDefinition.Permissions == null || !roleDefinition.Permissions.Any(p => + (p.Actions != null && p.Actions.Any()) || (p.DataActions != null && p.DataActions.Any()))) { throw new ArgumentException(ProjectResources.InvalidActions); } - } public static void ValidateScope(string scope, bool allowEmpty) diff --git a/src/Resources/Resources/Models.Authorization/AuthorizationClientExtensions.cs b/src/Resources/Resources/Models.Authorization/AuthorizationClientExtensions.cs index 137fe53372cc..1da0aad8e577 100644 --- a/src/Resources/Resources/Models.Authorization/AuthorizationClientExtensions.cs +++ b/src/Resources/Resources/Models.Authorization/AuthorizationClientExtensions.cs @@ -51,16 +51,19 @@ public static PSRoleDefinition ToPSRoleDefinition(this RoleDefinition role) roleDefinition = new PSRoleDefinition { Name = role.RoleName, - Actions = new List(role.Permissions.SelectMany(r => r.Actions)), - NotActions = new List(role.Permissions.SelectMany(r => r.NotActions)), - DataActions = new List(role.Permissions.SelectMany(r => r.DataActions)), - NotDataActions = new List(role.Permissions.SelectMany(r => r.NotDataActions)), Id = role.Id.GuidFromFullyQualifiedId(), AssignableScopes = role.AssignableScopes.ToList(), Description = role.Description, IsCustom = role.RoleType == CustomRole ? true : false, - Condition = (role.Permissions != null && role.Permissions.Count > 0) ? role.Permissions[0].Condition : null, - ConditionVersion = (role.Permissions != null && role.Permissions.Count > 0) ? role.Permissions[0].ConditionVersion : null + Permissions = role.Permissions?.Select(p => new PSPermission + { + Actions = p.Actions?.ToList() ?? new List(), + NotActions = p.NotActions?.ToList() ?? new List(), + DataActions = p.DataActions?.ToList() ?? new List(), + NotDataActions = p.NotDataActions?.ToList() ?? new List(), + Condition = p.Condition, + ConditionVersion = p.ConditionVersion + }).ToList() }; } diff --git a/src/Resources/Resources/Models.Authorization/PSPermission.cs b/src/Resources/Resources/Models.Authorization/PSPermission.cs index 7045e7a53601..30f25147c9a4 100644 --- a/src/Resources/Resources/Models.Authorization/PSPermission.cs +++ b/src/Resources/Resources/Models.Authorization/PSPermission.cs @@ -33,5 +33,9 @@ public class PSPermission public List NotDataActions { get; set; } public string NotDataActionsString { get { return string.Join(", ", NotDataActions); } } + + public string Condition { get; set; } + + public string ConditionVersion { get; set; } } } diff --git a/src/Resources/Resources/Models.Authorization/PSRoleDefinition.cs b/src/Resources/Resources/Models.Authorization/PSRoleDefinition.cs index b423ecf9198d..109e98e49717 100644 --- a/src/Resources/Resources/Models.Authorization/PSRoleDefinition.cs +++ b/src/Resources/Resources/Models.Authorization/PSRoleDefinition.cs @@ -26,18 +26,8 @@ public class PSRoleDefinition public string Description { get; set; } - public List Actions { get; set; } - - public List NotActions { get; set; } - - public List DataActions { get; set; } - - public List NotDataActions { get; set; } - public List AssignableScopes { get; set; } - - public string Condition { get; set; } - public string ConditionVersion { get; set; } + public List Permissions { get; set; } } } From ed8fac548aff32ebecad14d578552f4925986a46 Mon Sep 17 00:00:00 2001 From: Andrea Tomassilli Date: Wed, 14 Jan 2026 22:10:31 +0000 Subject: [PATCH 02/12] Add issue reference and expand ABAC acronym in ChangeLog --- src/Resources/Resources/ChangeLog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Resources/Resources/ChangeLog.md b/src/Resources/Resources/ChangeLog.md index a963c17e2268..ca5e90c02f13 100644 --- a/src/Resources/Resources/ChangeLog.md +++ b/src/Resources/Resources/ChangeLog.md @@ -19,8 +19,9 @@ --> ## Upcoming Release +* Fixed [#29058](https://github.com/Azure/azure-powershell/issues/29058): `Get-AzRoleDefinition` returned null `Condition` for roles with ABAC (Attribute-Based Access Control) conditions on non-first permission entries * [Breaking Change] Updated `Get-AzRoleDefinition` output model: - - Added `Permissions` property (list of `PSPermission` objects) to `PSRoleDefinition` to preserve full permission structure including per-permission conditions + - Added `Permissions` property (list of `PSPermission` objects) to `PSRoleDefinition` to preserve full permission structure including per-permission ABAC conditions - Removed `Actions`, `NotActions`, `DataActions`, and `NotDataActions` properties from `PSRoleDefinition` (these are now only available inside each `PSPermission`) - Removed `Condition` and `ConditionVersion` properties from `PSRoleDefinition` as they incorrectly flattened multi-permission role definitions - Added `Condition` and `ConditionVersion` properties to `PSPermission` From 39a1429212071125f28fa00f5fe8786aac44e188 Mon Sep 17 00:00:00 2001 From: Andrea Tomassilli Date: Wed, 14 Jan 2026 22:51:04 +0000 Subject: [PATCH 03/12] Add JsonIgnore to PSPermission string helper properties --- src/Resources/Resources/Models.Authorization/PSPermission.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Resources/Resources/Models.Authorization/PSPermission.cs b/src/Resources/Resources/Models.Authorization/PSPermission.cs index 30f25147c9a4..363d0b787e71 100644 --- a/src/Resources/Resources/Models.Authorization/PSPermission.cs +++ b/src/Resources/Resources/Models.Authorization/PSPermission.cs @@ -13,6 +13,7 @@ // ---------------------------------------------------------------------------------- using System.Collections.Generic; +using Newtonsoft.Json; namespace Microsoft.Azure.Commands.Resources.Models.Authorization { @@ -20,18 +21,22 @@ public class PSPermission { public List Actions { get; set; } + [JsonIgnore] public string ActionsString { get { return string.Join(", ", Actions); } } public List NotActions { get; set; } + [JsonIgnore] public string NotActionsString { get { return string.Join(", ", NotActions); } } public List DataActions { get; set; } + [JsonIgnore] public string DataActionsString { get { return string.Join(", ", DataActions); } } public List NotDataActions { get; set; } + [JsonIgnore] public string NotDataActionsString { get { return string.Join(", ", NotDataActions); } } public string Condition { get; set; } From 5b3590d7d101598447b414d6a1017e7039660e6f Mon Sep 17 00:00:00 2001 From: Andrea Tomassilli Date: Wed, 14 Jan 2026 23:16:43 +0000 Subject: [PATCH 04/12] Fix ChangeLog format and add null checks to PSPermission --- src/Resources/Resources/ChangeLog.md | 9 +++++---- .../Resources/Models.Authorization/PSPermission.cs | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Resources/Resources/ChangeLog.md b/src/Resources/Resources/ChangeLog.md index ca5e90c02f13..e50122ff5220 100644 --- a/src/Resources/Resources/ChangeLog.md +++ b/src/Resources/Resources/ChangeLog.md @@ -19,17 +19,18 @@ --> ## Upcoming Release -* Fixed [#29058](https://github.com/Azure/azure-powershell/issues/29058): `Get-AzRoleDefinition` returned null `Condition` for roles with ABAC (Attribute-Based Access Control) conditions on non-first permission entries +* Fixed `Get-AzRoleDefinition` returning null `Condition` for roles with ABAC (Attribute-Based Access Control) conditions on non-first permission entries [#29058] [#25940] * [Breaking Change] Updated `Get-AzRoleDefinition` output model: - Added `Permissions` property (list of `PSPermission` objects) to `PSRoleDefinition` to preserve full permission structure including per-permission ABAC conditions - Removed `Actions`, `NotActions`, `DataActions`, and `NotDataActions` properties from `PSRoleDefinition` (these are now only available inside each `PSPermission`) - Removed `Condition` and `ConditionVersion` properties from `PSRoleDefinition` as they incorrectly flattened multi-permission role definitions - Added `Condition` and `ConditionVersion` properties to `PSPermission` - - Users should now access actions via `$role.Permissions[n].Actions` and conditions via `$role.Permissions[n].Condition` + - Example - Old: `$role.Actions`, `$role.Condition` + - Example - New: `$role.Permissions[0].Actions`, `$role.Permissions[1].Condition` (conditions are now per-permission) * [Breaking Change] Updated `New-AzRoleDefinition -InputFile` JSON format: - JSON input files must now use the `Permissions` array format instead of flattened `Actions`/`NotActions`/`DataActions`/`NotDataActions` properties - - Old format: `{ "Actions": ["*"], "NotActions": [] }` - - New format: `{ "Permissions": [{ "Actions": ["*"], "NotActions": [] }] }` + - Old format: `{ "Actions": ["Microsoft.Storage/*"], "DataActions": ["Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read"] }` + - New format: `{ "Permissions": [{ "Actions": ["Microsoft.Storage/*"], "DataActions": ["Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read"], "Condition": null, "ConditionVersion": null }] }` ## Version 9.0.0 * Removed unavailable variant Get-AzRoleEligibleChildResource cmdlet for InputObject parameter. diff --git a/src/Resources/Resources/Models.Authorization/PSPermission.cs b/src/Resources/Resources/Models.Authorization/PSPermission.cs index 363d0b787e71..cbc29cea76ec 100644 --- a/src/Resources/Resources/Models.Authorization/PSPermission.cs +++ b/src/Resources/Resources/Models.Authorization/PSPermission.cs @@ -22,22 +22,22 @@ public class PSPermission public List Actions { get; set; } [JsonIgnore] - public string ActionsString { get { return string.Join(", ", Actions); } } + public string ActionsString { get { return Actions != null ? string.Join(", ", Actions) : string.Empty; } } public List NotActions { get; set; } [JsonIgnore] - public string NotActionsString { get { return string.Join(", ", NotActions); } } + public string NotActionsString { get { return NotActions != null ? string.Join(", ", NotActions) : string.Empty; } } public List DataActions { get; set; } [JsonIgnore] - public string DataActionsString { get { return string.Join(", ", DataActions); } } + public string DataActionsString { get { return DataActions != null ? string.Join(", ", DataActions) : string.Empty; } } public List NotDataActions { get; set; } [JsonIgnore] - public string NotDataActionsString { get { return string.Join(", ", NotDataActions); } } + public string NotDataActionsString { get { return NotDataActions != null ? string.Join(", ", NotDataActions) : string.Empty; } } public string Condition { get; set; } From 1619580b75f240c155d1cbb48de7e1646d4e0c49 Mon Sep 17 00:00:00 2001 From: Andrea Tomassilli Date: Thu, 15 Jan 2026 09:44:30 +0000 Subject: [PATCH 05/12] Fix tests, help docs, and add breaking change exceptions for RoleDefinition changes --- .../ScenarioTests/RoleAssignmentTests.ps1 | 2 +- src/Resources/Resources/ChangeLog.md | 8 +++-- .../Resources/help/New-AzRoleDefinition.md | 4 ++- .../Resources/help/Set-AzRoleDefinition.md | 36 +++++++++---------- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/Resources/Resources.Test/ScenarioTests/RoleAssignmentTests.ps1 b/src/Resources/Resources.Test/ScenarioTests/RoleAssignmentTests.ps1 index e276db277d9e..607642e02cf1 100644 --- a/src/Resources/Resources.Test/ScenarioTests/RoleAssignmentTests.ps1 +++ b/src/Resources/Resources.Test/ScenarioTests/RoleAssignmentTests.ps1 @@ -435,7 +435,7 @@ function Test-RaPropertiesValidation $roleDef = Get-AzRoleDefinition -Name "Reader" $roleDef.Id = "ff9cd1ab-d763-486f-b253-51a816c92aaf" $roleDef.Name = "Reader vm For Test" - $roleDef.Actions.Add("Microsoft.ClassicCompute/virtualMachines/restart/action") + $roleDef.Permissions[0].Actions.Add("Microsoft.ClassicCompute/virtualMachines/restart/action") $roleDef.Description = "Read, monitor and restart virtual machines" $roleDef.AssignableScopes[0] = '/subscriptions/'+$subscription[0].Id diff --git a/src/Resources/Resources/ChangeLog.md b/src/Resources/Resources/ChangeLog.md index e50122ff5220..2fd2a51380cf 100644 --- a/src/Resources/Resources/ChangeLog.md +++ b/src/Resources/Resources/ChangeLog.md @@ -20,17 +20,21 @@ ## Upcoming Release * Fixed `Get-AzRoleDefinition` returning null `Condition` for roles with ABAC (Attribute-Based Access Control) conditions on non-first permission entries [#29058] [#25940] -* [Breaking Change] Updated `Get-AzRoleDefinition` output model: +* [Breaking Change] Updated `PSRoleDefinition` output model (affects `Get-AzRoleDefinition`, `New-AzRoleDefinition`, `Set-AzRoleDefinition`, `Remove-AzRoleDefinition`): - Added `Permissions` property (list of `PSPermission` objects) to `PSRoleDefinition` to preserve full permission structure including per-permission ABAC conditions - Removed `Actions`, `NotActions`, `DataActions`, and `NotDataActions` properties from `PSRoleDefinition` (these are now only available inside each `PSPermission`) - Removed `Condition` and `ConditionVersion` properties from `PSRoleDefinition` as they incorrectly flattened multi-permission role definitions - Added `Condition` and `ConditionVersion` properties to `PSPermission` - Example - Old: `$role.Actions`, `$role.Condition` - Example - New: `$role.Permissions[0].Actions`, `$role.Permissions[1].Condition` (conditions are now per-permission) -* [Breaking Change] Updated `New-AzRoleDefinition -InputFile` JSON format: +* [Breaking Change] Updated `New-AzRoleDefinition` and `Set-AzRoleDefinition` `-InputFile` JSON format: - JSON input files must now use the `Permissions` array format instead of flattened `Actions`/`NotActions`/`DataActions`/`NotDataActions` properties - Old format: `{ "Actions": ["Microsoft.Storage/*"], "DataActions": ["Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read"] }` - New format: `{ "Permissions": [{ "Actions": ["Microsoft.Storage/*"], "DataActions": ["Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read"], "Condition": null, "ConditionVersion": null }] }` +* [Breaking Change] Updated `Set-AzRoleDefinition -Role` parameter: + - The `PSRoleDefinition` object passed to `-Role` must now have actions in `Permissions[0].Actions` instead of `Actions` + - Example - Old: `$roleDef.Actions.Add("Microsoft.Compute/*")` + - Example - New: `$roleDef.Permissions[0].Actions.Add("Microsoft.Compute/*")` ## Version 9.0.0 * Removed unavailable variant Get-AzRoleEligibleChildResource cmdlet for InputObject parameter. diff --git a/src/Resources/Resources/help/New-AzRoleDefinition.md b/src/Resources/Resources/help/New-AzRoleDefinition.md index ed09df5a9aa6..fc2c4ff8c539 100644 --- a/src/Resources/Resources/help/New-AzRoleDefinition.md +++ b/src/Resources/Resources/help/New-AzRoleDefinition.md @@ -89,7 +89,8 @@ $role.Name = 'Virtual Machine Operator' $role.Description = 'Can monitor, start, and restart virtual machines.' $role.IsCustom = $true $role.AssignableScopes = @("/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") -$role.Actions = @( +$permission = New-Object -TypeName Microsoft.Azure.Commands.Resources.Models.Authorization.PSPermission +$permission.Actions = @( "Microsoft.Compute/*/read" "Microsoft.Compute/virtualMachines/start/action" "Microsoft.Compute/virtualMachines/restart/action" @@ -102,6 +103,7 @@ $role.Actions = @( "Microsoft.Insights/alertRules/*" "Microsoft.Support/*" ) +$role.Permissions = @($permission) New-AzRoleDefinition -Role $role ``` diff --git a/src/Resources/Resources/help/Set-AzRoleDefinition.md b/src/Resources/Resources/help/Set-AzRoleDefinition.md index d8bfd196b2cc..52493f5808d1 100644 --- a/src/Resources/Resources/help/Set-AzRoleDefinition.md +++ b/src/Resources/Resources/help/Set-AzRoleDefinition.md @@ -37,7 +37,7 @@ The Set-AzRoleDefinition cmdlet updates an existing custom role in Azure Role-Ba ### Example 1: Update using PSRoleDefinitionObject ```powershell $roleDef = Get-AzRoleDefinition "Contoso On-Call" -$roleDef.Actions.Add("Microsoft.ClassicCompute/virtualmachines/start/action") +$roleDef.Permissions[0].Actions.Add("Microsoft.ClassicCompute/virtualmachines/start/action") $roleDef.Description = "Can monitor all resources and start and restart virtual machines" $roleDef.AssignableScopes = @("/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") Set-AzRoleDefinition -Role $roleDef @@ -52,23 +52,23 @@ Following is a sample updated role definition json for Set-AzRoleDefinition: "Id": "52a6cc13-ff92-47a8-a39b-2a8205c3087e", "Name": "Updated Role", "Description": "Can monitor all resources and start and restart virtual machines", - "Actions": - [ - "*/read", - "Microsoft.ClassicCompute/virtualmachines/restart/action", - "Microsoft.ClassicCompute/virtualmachines/start/action" - ], - "NotActions": - [ - "*/write" - ], - "DataActions": - [ - "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read" - ], - "NotDataActions": - [ - "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write" + "Permissions": [ + { + "Actions": [ + "*/read", + "Microsoft.ClassicCompute/virtualmachines/restart/action", + "Microsoft.ClassicCompute/virtualmachines/start/action" + ], + "NotActions": [ + "*/write" + ], + "DataActions": [ + "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read" + ], + "NotDataActions": [ + "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write" + ] + } ], "AssignableScopes": ["/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] } From 85f3d11e97404e560dba0193c1ff2cda23600c86 Mon Sep 17 00:00:00 2001 From: Andrea Tomassilli Date: Thu, 15 Jan 2026 10:03:38 +0000 Subject: [PATCH 06/12] Update ChangeLog to clarify breaking changes --- src/Resources/Resources/ChangeLog.md | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/Resources/Resources/ChangeLog.md b/src/Resources/Resources/ChangeLog.md index 2fd2a51380cf..c6910de82873 100644 --- a/src/Resources/Resources/ChangeLog.md +++ b/src/Resources/Resources/ChangeLog.md @@ -19,22 +19,12 @@ --> ## Upcoming Release -* Fixed `Get-AzRoleDefinition` returning null `Condition` for roles with ABAC (Attribute-Based Access Control) conditions on non-first permission entries [#29058] [#25940] -* [Breaking Change] Updated `PSRoleDefinition` output model (affects `Get-AzRoleDefinition`, `New-AzRoleDefinition`, `Set-AzRoleDefinition`, `Remove-AzRoleDefinition`): - - Added `Permissions` property (list of `PSPermission` objects) to `PSRoleDefinition` to preserve full permission structure including per-permission ABAC conditions - - Removed `Actions`, `NotActions`, `DataActions`, and `NotDataActions` properties from `PSRoleDefinition` (these are now only available inside each `PSPermission`) - - Removed `Condition` and `ConditionVersion` properties from `PSRoleDefinition` as they incorrectly flattened multi-permission role definitions - - Added `Condition` and `ConditionVersion` properties to `PSPermission` - - Example - Old: `$role.Actions`, `$role.Condition` - - Example - New: `$role.Permissions[0].Actions`, `$role.Permissions[1].Condition` (conditions are now per-permission) -* [Breaking Change] Updated `New-AzRoleDefinition` and `Set-AzRoleDefinition` `-InputFile` JSON format: - - JSON input files must now use the `Permissions` array format instead of flattened `Actions`/`NotActions`/`DataActions`/`NotDataActions` properties - - Old format: `{ "Actions": ["Microsoft.Storage/*"], "DataActions": ["Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read"] }` - - New format: `{ "Permissions": [{ "Actions": ["Microsoft.Storage/*"], "DataActions": ["Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read"], "Condition": null, "ConditionVersion": null }] }` -* [Breaking Change] Updated `Set-AzRoleDefinition -Role` parameter: - - The `PSRoleDefinition` object passed to `-Role` must now have actions in `Permissions[0].Actions` instead of `Actions` - - Example - Old: `$roleDef.Actions.Add("Microsoft.Compute/*")` - - Example - New: `$roleDef.Permissions[0].Actions.Add("Microsoft.Compute/*")` +* Fixed `Get-AzRoleDefinition` returning null `Condition` for roles with ABAC conditions on non-first permission entries [#29058] [#25940] +* [Breaking Change] Updated role definition cmdlets (`Get-AzRoleDefinition`, `New-AzRoleDefinition`, `Set-AzRoleDefinition`, `Remove-AzRoleDefinition`) to use a permissions array with per-permission conditions + - `PSRoleDefinition` now uses a `Permissions` property to represent actions, data actions, and conditions, including Attribute-Based Access Control (ABAC) conditions + - `Get-AzRoleDefinition` and `Remove-AzRoleDefinition` output type `PSRoleDefinition` no longer has flattened `Actions`, `NotActions`, `DataActions`, `NotDataActions`, `Condition`, and `ConditionVersion` properties; use the `Permissions` collection instead + - JSON input files for `New-AzRoleDefinition` and `Set-AzRoleDefinition -InputFile` must define permissions in the `Permissions` array structure instead of using top-level `Actions`, `NotActions`, `DataActions`, and `NotDataActions` properties + - Scripts that pass a `PSRoleDefinition` object to `Set-AzRoleDefinition -Role` must update how they read and modify actions and conditions to use the `Permissions` collection ## Version 9.0.0 * Removed unavailable variant Get-AzRoleEligibleChildResource cmdlet for InputObject parameter. From b2e70deda005655f5b80ea60e3de4ac6c1bc03d1 Mon Sep 17 00:00:00 2001 From: Andrea Tomassilli Date: Thu, 15 Jan 2026 10:19:21 +0000 Subject: [PATCH 07/12] Add validation tests and InvalidPermissions error message --- .../AuthorizationClientExtensionsTests.cs | 142 ++++++++++++++++++ .../AuthorizationClient.cs | 11 +- .../Properties/Resources.Designer.cs | 9 ++ .../Resources/Properties/Resources.resx | 3 + 4 files changed, 162 insertions(+), 3 deletions(-) diff --git a/src/Resources/Resources.Test/UnitTests/Authorization/AuthorizationClientExtensionsTests.cs b/src/Resources/Resources.Test/UnitTests/Authorization/AuthorizationClientExtensionsTests.cs index b51d1c6e88cf..d5d412723ecd 100644 --- a/src/Resources/Resources.Test/UnitTests/Authorization/AuthorizationClientExtensionsTests.cs +++ b/src/Resources/Resources.Test/UnitTests/Authorization/AuthorizationClientExtensionsTests.cs @@ -15,6 +15,7 @@ using Microsoft.Azure.Commands.Resources.Models.Authorization; using Microsoft.Azure.Management.Authorization.Models; using Microsoft.WindowsAzure.Commands.ScenarioTest; +using System; using System.Collections.Generic; using System.Linq; using Xunit; @@ -456,5 +457,146 @@ public void ToPSRoleDefinition_DataActionsOnlyPermission_PreservesCorrectly() } #endregion + + #region ValidateRoleDefinition Tests + + private static PSRoleDefinition CreatePSRoleDefinition( + string name = "Test Role", + string description = "Test Description", + List assignableScopes = null, + List permissions = null) + { + return new PSRoleDefinition + { + Name = name, + Description = description, + AssignableScopes = assignableScopes ?? ["/subscriptions/00000000-0000-0000-0000-000000000000"], + Permissions = permissions ?? [new PSPermission { Actions = ["*/read"] }] + }; + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ValidateRoleDefinition_ValidRole_NoException() + { + var roleDef = CreatePSRoleDefinition(); + + var exception = Record.Exception(() => AuthorizationClient.ValidateRoleDefinition(roleDef)); + + Assert.Null(exception); + } + + [Theory] + [Trait(Category.AcceptanceType, Category.CheckIn)] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void ValidateRoleDefinition_InvalidName_ThrowsException(string name) + { + var roleDef = CreatePSRoleDefinition(name: name); + + Assert.Throws(() => AuthorizationClient.ValidateRoleDefinition(roleDef)); + } + + [Theory] + [Trait(Category.AcceptanceType, Category.CheckIn)] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void ValidateRoleDefinition_InvalidDescription_ThrowsException(string description) + { + var roleDef = CreatePSRoleDefinition(description: description); + + Assert.Throws(() => AuthorizationClient.ValidateRoleDefinition(roleDef)); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ValidateRoleDefinition_NullAssignableScopes_ThrowsException() + { + var roleDef = new PSRoleDefinition + { + Name = "Test Role", + Description = "Test Description", + AssignableScopes = null, + Permissions = [new PSPermission { Actions = ["*/read"] }] + }; + + Assert.Throws(() => AuthorizationClient.ValidateRoleDefinition(roleDef)); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ValidateRoleDefinition_EmptyAssignableScopes_ThrowsException() + { + var roleDef = CreatePSRoleDefinition(assignableScopes: []); + + Assert.Throws(() => AuthorizationClient.ValidateRoleDefinition(roleDef)); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ValidateRoleDefinition_NullPermissions_ThrowsException() + { + var roleDef = new PSRoleDefinition + { + Name = "Test Role", + Description = "Test Description", + AssignableScopes = ["/subscriptions/00000000-0000-0000-0000-000000000000"], + Permissions = null + }; + + Assert.Throws(() => AuthorizationClient.ValidateRoleDefinition(roleDef)); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ValidateRoleDefinition_EmptyPermissions_ThrowsException() + { + var roleDef = CreatePSRoleDefinition(permissions: []); + + Assert.Throws(() => AuthorizationClient.ValidateRoleDefinition(roleDef)); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ValidateRoleDefinition_PermissionsWithNoActions_ThrowsException() + { + var roleDef = CreatePSRoleDefinition(permissions: + [ + new PSPermission { Actions = [], DataActions = [] } + ]); + + Assert.Throws(() => AuthorizationClient.ValidateRoleDefinition(roleDef)); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ValidateRoleDefinition_PermissionsWithOnlyDataActions_NoException() + { + var roleDef = CreatePSRoleDefinition(permissions: + [ + new PSPermission { DataActions = ["Microsoft.Storage/*/read"] } + ]); + + var exception = Record.Exception(() => AuthorizationClient.ValidateRoleDefinition(roleDef)); + + Assert.Null(exception); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void ValidateRoleDefinition_MultiplePermissionsWithOneEmpty_ThrowsException() + { + var roleDef = CreatePSRoleDefinition(permissions: + [ + new PSPermission { Actions = [] }, // Empty actions - invalid + new PSPermission { Actions = ["*/read"] } + ]); + + Assert.Throws(() => AuthorizationClient.ValidateRoleDefinition(roleDef)); + } + + #endregion } } diff --git a/src/Resources/Resources/Models.Authorization/AuthorizationClient.cs b/src/Resources/Resources/Models.Authorization/AuthorizationClient.cs index 8e1a6d2680fc..fe26c4cca593 100644 --- a/src/Resources/Resources/Models.Authorization/AuthorizationClient.cs +++ b/src/Resources/Resources/Models.Authorization/AuthorizationClient.cs @@ -666,7 +666,7 @@ private IList ToRoleDefinitionPermissions(PSRoleDefinition role) return permissions; } - private static void ValidateRoleDefinition(PSRoleDefinition roleDefinition) + internal static void ValidateRoleDefinition(PSRoleDefinition roleDefinition) { if (string.IsNullOrWhiteSpace(roleDefinition.Name)) { @@ -683,8 +683,13 @@ private static void ValidateRoleDefinition(PSRoleDefinition roleDefinition) throw new ArgumentException(ProjectResources.InvalidAssignableScopes); } - if (roleDefinition.Permissions == null || !roleDefinition.Permissions.Any(p => - (p.Actions != null && p.Actions.Any()) || (p.DataActions != null && p.DataActions.Any()))) + if (roleDefinition.Permissions == null || !roleDefinition.Permissions.Any()) + { + throw new ArgumentException(ProjectResources.InvalidPermissions); + } + + if (roleDefinition.Permissions.Any(p => + (p.Actions == null || !p.Actions.Any()) && (p.DataActions == null || !p.DataActions.Any()))) { throw new ArgumentException(ProjectResources.InvalidActions); } diff --git a/src/Resources/Resources/Properties/Resources.Designer.cs b/src/Resources/Resources/Properties/Resources.Designer.cs index 3215a0d0a671..9da6a2378e68 100644 --- a/src/Resources/Resources/Properties/Resources.Designer.cs +++ b/src/Resources/Resources/Properties/Resources.Designer.cs @@ -294,6 +294,15 @@ internal static string InvalidAssignableScopes { } } + /// + /// Looks up a localized string similar to Invalid value for Permissions. + /// + internal static string InvalidPermissions { + get { + return ResourceManager.GetString("InvalidPermissions", resourceCulture); + } + } + /// /// Looks up a localized string similar to Invalid format of the resource group identifier. Expected 'subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}'.. /// diff --git a/src/Resources/Resources/Properties/Resources.resx b/src/Resources/Resources/Properties/Resources.resx index f40175797608..cd15fcbc55d3 100644 --- a/src/Resources/Resources/Properties/Resources.resx +++ b/src/Resources/Resources/Properties/Resources.resx @@ -315,6 +315,9 @@ Invalid value for Actions + + Invalid value for Permissions + Invalid value for AssignableScopes From 8dd966cca501bbf1c6e72a679f9a7381d435746f Mon Sep 17 00:00:00 2001 From: Andrea Tomassilli Date: Thu, 15 Jan 2026 11:38:30 +0000 Subject: [PATCH 08/12] Add E2E tests for new Permissions array format on role definitions --- .../PermissionsFormatRoleDefinition.json | 16 + .../ScenarioTests/RoleDefinitionTests.cs | 21 + .../ScenarioTests/RoleDefinitionTests.ps1 | 126 ++++ .../RDNewPermissionsFormatCreate.json | 314 ++++++++++ .../RDNewPermissionsFormatDelete.json | 464 +++++++++++++++ .../RDNewPermissionsFormatUpdate.json | 545 ++++++++++++++++++ 6 files changed, 1486 insertions(+) create mode 100644 src/Resources/Resources.Test/Resources/PermissionsFormatRoleDefinition.json create mode 100644 src/Resources/Resources.Test/SessionRecords/Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleDefinitionTests/RDNewPermissionsFormatCreate.json create mode 100644 src/Resources/Resources.Test/SessionRecords/Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleDefinitionTests/RDNewPermissionsFormatDelete.json create mode 100644 src/Resources/Resources.Test/SessionRecords/Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleDefinitionTests/RDNewPermissionsFormatUpdate.json diff --git a/src/Resources/Resources.Test/Resources/PermissionsFormatRoleDefinition.json b/src/Resources/Resources.Test/Resources/PermissionsFormatRoleDefinition.json new file mode 100644 index 000000000000..b82d4e492a89 --- /dev/null +++ b/src/Resources/Resources.Test/Resources/PermissionsFormatRoleDefinition.json @@ -0,0 +1,16 @@ +{ + "Name": "CustomRole Permissions Format Test", + "Description": "Test role for new Permissions array format", + "Permissions": [ + { + "Actions": [ + "Microsoft.Resources/subscriptions/resourceGroups/read", + "Microsoft.Resources/deployments/*" + ], + "NotActions": [], + "DataActions": [], + "NotDataActions": [] + } + ], + "AssignableScopes": ["/subscriptions/4004a9fd-d58e-48dc-aeb2-4a4aec58606f"] +} diff --git a/src/Resources/Resources.Test/ScenarioTests/RoleDefinitionTests.cs b/src/Resources/Resources.Test/ScenarioTests/RoleDefinitionTests.cs index 4cfa07dd2ef4..05d8b64b2713 100644 --- a/src/Resources/Resources.Test/ScenarioTests/RoleDefinitionTests.cs +++ b/src/Resources/Resources.Test/ScenarioTests/RoleDefinitionTests.cs @@ -125,6 +125,27 @@ public void RDWithConditionGetScenario() TestRunner.RunTestScript("Test-RDWithAbacConditionsGet"); } + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void RDNewPermissionsFormatCreate() + { + TestRunner.RunTestScript("Test-RDNewPermissionsFormatCreate"); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void RDNewPermissionsFormatUpdate() + { + TestRunner.RunTestScript("Test-RDNewPermissionsFormatUpdate"); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void RDNewPermissionsFormatDelete() + { + TestRunner.RunTestScript("Test-RDNewPermissionsFormatDelete"); + } + [Fact] [Trait(Category.AcceptanceType, Category.CheckIn)] public void RdValidateInputParameters() diff --git a/src/Resources/Resources.Test/ScenarioTests/RoleDefinitionTests.ps1 b/src/Resources/Resources.Test/ScenarioTests/RoleDefinitionTests.ps1 index 3fe1f49ea6a9..099756071a27 100644 --- a/src/Resources/Resources.Test/ScenarioTests/RoleDefinitionTests.ps1 +++ b/src/Resources/Resources.Test/ScenarioTests/RoleDefinitionTests.ps1 @@ -502,3 +502,129 @@ function Test-RDDataActionsNegativeTestCases { # Basic positive case - read from object Remove-AzRoleDefinition -Id $createdRole.Id -Force } + +<# +.SYNOPSIS +Tests creating a custom role definition using the new Permissions array format. +#> +function Test-RDNewPermissionsFormatCreate +{ + # Create a custom role using the new Permissions array format from PSObject + $rdName = 'CustomRole Permissions Format Test' + $subscription = (Get-AzContext).Subscription.Id + + $rd = Get-AzRoleDefinition -Name "Reader" + $rd.Id = $null + $rd.Name = $rdName + $rd.Description = "Test role for new Permissions array format" + $rd.Permissions[0].Actions.Clear() + $rd.Permissions[0].Actions.Add("Microsoft.Resources/subscriptions/resourceGroups/read") + $rd.Permissions[0].Actions.Add("Microsoft.Resources/deployments/*") + $rd.AssignableScopes.Clear() + $rd.AssignableScopes.Add("/subscriptions/$subscription") + + $createdRd = New-AzRoleDefinitionWithId -Role $rd -RoleDefinitionId a1b2c3d4-e5f6-7890-abcd-ef1234567890 + + # Verify the role was created correctly + Assert-NotNull $createdRd + Assert-AreEqual $rdName $createdRd.Name + Assert-AreEqual "Test role for new Permissions array format" $createdRd.Description + Assert-AreEqual $true $createdRd.IsCustom + + # Verify Permissions structure + Assert-NotNull $createdRd.Permissions + Assert-True { $createdRd.Permissions.Count -eq 1 } + Assert-NotNull $createdRd.Permissions[0].Actions + Assert-AreEqual "Microsoft.Resources/subscriptions/resourceGroups/read" $createdRd.Permissions[0].Actions[0] + Assert-AreEqual "Microsoft.Resources/deployments/*" $createdRd.Permissions[0].Actions[1] + Assert-NotNull $createdRd.Permissions[0].NotActions + Assert-True { $createdRd.Permissions[0].NotActions.Count -eq 0 } + Assert-NotNull $createdRd.AssignableScopes + + # Cleanup + Remove-AzRoleDefinition -Id $createdRd.Id -Force +} + +<# +.SYNOPSIS +Tests updating a custom role definition using the new Permissions array format. +#> +function Test-RDNewPermissionsFormatUpdate +{ + # Create a custom role first + $rdName = 'CustomRole Update Test' + $subscription = (Get-AzContext).Subscription.Id + + $rd = Get-AzRoleDefinition -Name "Reader" + $rd.Id = $null + $rd.Name = $rdName + $rd.Description = "Test role for new Permissions array format" + $rd.Permissions[0].Actions.Clear() + $rd.Permissions[0].Actions.Add("Microsoft.Resources/subscriptions/resourceGroups/read") + $rd.Permissions[0].Actions.Add("Microsoft.Resources/deployments/*") + $rd.AssignableScopes.Clear() + $rd.AssignableScopes.Add("/subscriptions/$subscription") + + $createdRd = New-AzRoleDefinitionWithId -Role $rd -RoleDefinitionId b2c3d4e5-f6a7-8901-bcde-f23456789012 + Assert-NotNull $createdRd "Role creation with New-AzRoleDefinitionWithId should succeed" + + $rd = Get-AzRoleDefinition -Id $createdRd.Id + Assert-NotNull $rd "Role should be retrievable by Id after creation" + Assert-NotNull $rd.Permissions "Role should have Permissions array" + $originalActionCount = $rd.Permissions[0].Actions.Count + + # Update the role by adding an action using new Permissions format + $rd.Permissions[0].Actions.Add("Microsoft.Support/*") + $updatedRd = Set-AzRoleDefinition -Role $rd + + Assert-NotNull $updatedRd + Assert-NotNull $updatedRd.Permissions + Assert-True { $updatedRd.Permissions[0].Actions.Count -eq ($originalActionCount + 1) } + + # Verify the new action is present + $hasNewAction = $updatedRd.Permissions[0].Actions | Where-Object { $_ -eq "Microsoft.Support/*" } + Assert-NotNull $hasNewAction "Updated role should have the new action" + + # Cleanup + Remove-AzRoleDefinition -Id $rd.Id -Force +} + +<# +.SYNOPSIS +Tests deleting a custom role definition and verifying the returned Permissions array format. +#> +function Test-RDNewPermissionsFormatDelete +{ + # Create a custom role to delete + $rdName = 'CustomRole Delete Test' + $subscription = (Get-AzContext).Subscription.Id + + $rd = Get-AzRoleDefinition -Name "Reader" + $rd.Id = $null + $rd.Name = $rdName + $rd.Description = "Test role for new Permissions array format" + $rd.Permissions[0].Actions.Clear() + $rd.Permissions[0].Actions.Add("Microsoft.Resources/subscriptions/resourceGroups/read") + $rd.Permissions[0].Actions.Add("Microsoft.Resources/deployments/*") + $rd.AssignableScopes.Clear() + $rd.AssignableScopes.Add("/subscriptions/$subscription") + + $rd = New-AzRoleDefinitionWithId -Role $rd -RoleDefinitionId c3d4e5f6-a7b8-9012-cdef-345678901234 + $rd = Get-AzRoleDefinition -Name $rdName + + Assert-NotNull $rd + Assert-NotNull $rd.Permissions + + # Delete the role and verify the returned object has Permissions structure + $deletedRd = Remove-AzRoleDefinition -Id $rd.Id -Force -PassThru + + Assert-NotNull $deletedRd + Assert-AreEqual $rd.Name $deletedRd.Name + Assert-NotNull $deletedRd.Permissions + Assert-True { $deletedRd.Permissions.Count -gt 0 } + Assert-NotNull $deletedRd.Permissions[0].Actions + + # Verify the role no longer exists + $readRd = Get-AzRoleDefinition -Name $rd.Name + Assert-Null $readRd +} diff --git a/src/Resources/Resources.Test/SessionRecords/Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleDefinitionTests/RDNewPermissionsFormatCreate.json b/src/Resources/Resources.Test/SessionRecords/Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleDefinitionTests/RDNewPermissionsFormatCreate.json new file mode 100644 index 000000000000..5f9fd579caa2 --- /dev/null +++ b/src/Resources/Resources.Test/SessionRecords/Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleDefinitionTests/RDNewPermissionsFormatCreate.json @@ -0,0 +1,314 @@ +{ + "Entries": [ + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions?$filter=roleName%20eq%20'Reader'&api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zPyRmaWx0ZXI9cm9sZU5hbWUlMjBlcSUyMCdSZWFkZXInJmFwaS12ZXJzaW9uPTIwMjItMDUtMDEtcHJldmlldw==", + "RequestMethod": "GET", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "59d25893-f784-4201-965f-f7ab50f1ca50" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ] + }, + "RequestBody": "", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "25e97772-87fe-4331-bf8a-5ac6fa599129" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/74e07ea2-e49c-4c2e-b993-2e62756e2906" + ], + "x-ms-ratelimit-remaining-subscription-reads": [ + "249" + ], + "x-ms-ratelimit-remaining-subscription-global-reads": [ + "3749" + ], + "x-ms-correlation-request-id": [ + "b1447c22-4865-431f-b459-28b047913833" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112333Z:b1447c22-4865-431f-b459-28b047913833" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: 3979AED1B8D4492787CD7135F92ED2C3 Ref B: DUB241062305040 Ref C: 2026-01-15T11:23:33Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:33 GMT" + ], + "Content-Length": [ + "627" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"value\": [\n {\n \"properties\": {\n \"roleName\": \"Reader\",\n \"type\": \"BuiltInRole\",\n \"description\": \"View all resources, but does not allow you to make any changes.\",\n \"assignableScopes\": [\n \"/\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"*/read\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2015-02-02T21:55:09.8806423Z\",\n \"updatedOn\": \"2021-11-11T20:13:47.8628684Z\",\n \"createdBy\": null,\n \"updatedBy\": null\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"acdd72a7-3385-48ef-bd42-f606fba81ae7\"\n }\n ]\n}", + "StatusCode": 200 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/a1b2c3d4-e5f6-7890-abcd-ef1234567890?api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zL2ExYjJjM2Q0LWU1ZjYtNzg5MC1hYmNkLWVmMTIzNDU2Nzg5MD9hcGktdmVyc2lvbj0yMDIyLTA1LTAxLXByZXZpZXc=", + "RequestMethod": "PUT", + "RequestHeaders": { + "x-ms-client-request-id": [ + "02c4e015-7e5f-4126-a828-c8058db2e9d4" + ], + "Accept-Language": [ + "en-US" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Content-Length": [ + "533" + ] + }, + "RequestBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Permissions Format Test\",\n \"description\": \"Test role for new Permissions array format\",\n \"type\": \"CustomRole\",\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ]\n }\n}", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "c3e7e968-b6a7-4f46-afc1-2e4b6707f527" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/93c7461a-d7a3-4d70-8f61-259f331ab80b" + ], + "x-ms-ratelimit-remaining-subscription-writes": [ + "199" + ], + "x-ms-ratelimit-remaining-subscription-global-writes": [ + "2999" + ], + "x-ms-correlation-request-id": [ + "f37ddb14-1732-45ac-bf3d-5a8689d008d9" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112339Z:f37ddb14-1732-45ac-bf3d-5a8689d008d9" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: 7A6E659FA1CB42A79141CB3BD45D9EDF Ref B: DUB241062306025 Ref C: 2026-01-15T11:23:34Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:38 GMT" + ], + "Content-Length": [ + "788" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Permissions Format Test\",\n \"type\": \"CustomRole\",\n \"description\": \"Test role for new Permissions array format\",\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2026-01-15T11:23:34.5087924Z\",\n \"updatedOn\": \"2026-01-15T11:23:34.5087924Z\",\n \"createdBy\": null,\n \"updatedBy\": \"00000000-0000-0000-0000-000000000002\"\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"\n}", + "StatusCode": 201 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/a1b2c3d4-e5f6-7890-abcd-ef1234567890?api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zL2ExYjJjM2Q0LWU1ZjYtNzg5MC1hYmNkLWVmMTIzNDU2Nzg5MD9hcGktdmVyc2lvbj0yMDIyLTA1LTAxLXByZXZpZXc=", + "RequestMethod": "GET", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "99b16702-bbb1-4611-bc96-5eaa6b67aa3d" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ] + }, + "RequestBody": "", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "c5bf50da-72dc-4015-bd3a-ffbc55c3f1a3" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/02d8770a-b931-4484-8ad7-8a89f76fea6a" + ], + "x-ms-ratelimit-remaining-subscription-reads": [ + "249" + ], + "x-ms-ratelimit-remaining-subscription-global-reads": [ + "3749" + ], + "x-ms-correlation-request-id": [ + "ceb15a36-cdbe-434c-bb72-460c9110d4d3" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112339Z:ceb15a36-cdbe-434c-bb72-460c9110d4d3" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: 1D136AE4EB2D452EA6D9FC3DB57C3781 Ref B: DUB241062308062 Ref C: 2026-01-15T11:23:39Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:39 GMT" + ], + "Content-Length": [ + "822" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Permissions Format Test\",\n \"type\": \"CustomRole\",\n \"description\": \"Test role for new Permissions array format\",\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2026-01-15T11:23:36.6989763Z\",\n \"updatedOn\": \"2026-01-15T11:23:36.6989763Z\",\n \"createdBy\": \"00000000-0000-0000-0000-000000000002\",\n \"updatedBy\": \"00000000-0000-0000-0000-000000000002\"\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"\n}", + "StatusCode": 200 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/a1b2c3d4-e5f6-7890-abcd-ef1234567890?api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zL2ExYjJjM2Q0LWU1ZjYtNzg5MC1hYmNkLWVmMTIzNDU2Nzg5MD9hcGktdmVyc2lvbj0yMDIyLTA1LTAxLXByZXZpZXc=", + "RequestMethod": "DELETE", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "99b16702-bbb1-4611-bc96-5eaa6b67aa3d" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ] + }, + "RequestBody": "", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "fc25d398-35de-45a1-9bc5-d731fc62f699" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/c2e505f9-1535-4702-95f7-6a8b0b4024cb" + ], + "x-ms-ratelimit-remaining-subscription-deletes": [ + "199" + ], + "x-ms-ratelimit-remaining-subscription-global-deletes": [ + "2999" + ], + "x-ms-correlation-request-id": [ + "8731a686-a141-4826-a8b3-f98920ca5f07" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112342Z:8731a686-a141-4826-a8b3-f98920ca5f07" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: F2232B267BE746189686C60F59F6D262 Ref B: DUB241062308062 Ref C: 2026-01-15T11:23:39Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:41 GMT" + ], + "Content-Length": [ + "822" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Permissions Format Test\",\n \"type\": \"CustomRole\",\n \"description\": \"Test role for new Permissions array format\",\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2026-01-15T11:23:36.6989763Z\",\n \"updatedOn\": \"2026-01-15T11:23:40.6882585Z\",\n \"createdBy\": \"00000000-0000-0000-0000-000000000002\",\n \"updatedBy\": \"00000000-0000-0000-0000-000000000002\"\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"\n}", + "StatusCode": 200 + } + ], + "Names": {}, + "Variables": { + "SubscriptionId": "00000000-0000-0000-0000-000000000002" + } +} \ No newline at end of file diff --git a/src/Resources/Resources.Test/SessionRecords/Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleDefinitionTests/RDNewPermissionsFormatDelete.json b/src/Resources/Resources.Test/SessionRecords/Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleDefinitionTests/RDNewPermissionsFormatDelete.json new file mode 100644 index 000000000000..0abfe6caae7e --- /dev/null +++ b/src/Resources/Resources.Test/SessionRecords/Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleDefinitionTests/RDNewPermissionsFormatDelete.json @@ -0,0 +1,464 @@ +{ + "Entries": [ + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions?$filter=roleName%20eq%20'Reader'&api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zPyRmaWx0ZXI9cm9sZU5hbWUlMjBlcSUyMCdSZWFkZXInJmFwaS12ZXJzaW9uPTIwMjItMDUtMDEtcHJldmlldw==", + "RequestMethod": "GET", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "57413aaa-cbe1-454f-bc48-ebff89fc4b87" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ] + }, + "RequestBody": "", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "e0395f76-c33c-4171-a7ff-476f617f9b8c" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/e002f8ef-68d6-4391-88bd-8a0a3c26da04" + ], + "x-ms-ratelimit-remaining-subscription-reads": [ + "249" + ], + "x-ms-ratelimit-remaining-subscription-global-reads": [ + "3749" + ], + "x-ms-correlation-request-id": [ + "ff221b68-bb39-4cf0-90a6-f55f0b481b58" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112324Z:ff221b68-bb39-4cf0-90a6-f55f0b481b58" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: AA07812F3D5C4060B2908C480E5BF029 Ref B: DUB241062305040 Ref C: 2026-01-15T11:23:24Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:24 GMT" + ], + "Content-Length": [ + "627" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"value\": [\n {\n \"properties\": {\n \"roleName\": \"Reader\",\n \"type\": \"BuiltInRole\",\n \"description\": \"View all resources, but does not allow you to make any changes.\",\n \"assignableScopes\": [\n \"/\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"*/read\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2015-02-02T21:55:09.8806423Z\",\n \"updatedOn\": \"2021-11-11T20:13:47.8628684Z\",\n \"createdBy\": null,\n \"updatedBy\": null\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"acdd72a7-3385-48ef-bd42-f606fba81ae7\"\n }\n ]\n}", + "StatusCode": 200 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/c3d4e5f6-a7b8-9012-cdef-345678901234?api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zL2MzZDRlNWY2LWE3YjgtOTAxMi1jZGVmLTM0NTY3ODkwMTIzND9hcGktdmVyc2lvbj0yMDIyLTA1LTAxLXByZXZpZXc=", + "RequestMethod": "PUT", + "RequestHeaders": { + "x-ms-client-request-id": [ + "6acb7a28-30f2-4617-902e-3f7a1bb59d33" + ], + "Accept-Language": [ + "en-US" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Content-Length": [ + "521" + ] + }, + "RequestBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Delete Test\",\n \"description\": \"Test role for new Permissions array format\",\n \"type\": \"CustomRole\",\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ]\n }\n}", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "4bd1cdd7-0a13-4188-8091-14876a6fcd98" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/1331794e-492c-47fd-9dc9-f1b412f0ff96" + ], + "x-ms-ratelimit-remaining-subscription-writes": [ + "199" + ], + "x-ms-ratelimit-remaining-subscription-global-writes": [ + "2999" + ], + "x-ms-correlation-request-id": [ + "48e19f4a-f15b-46bf-92eb-4b13d059eb4b" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112327Z:48e19f4a-f15b-46bf-92eb-4b13d059eb4b" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: EF903DD846F8497693025A6767F5A179 Ref B: DUB241062303060 Ref C: 2026-01-15T11:23:25Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:27 GMT" + ], + "Content-Length": [ + "776" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Delete Test\",\n \"type\": \"CustomRole\",\n \"description\": \"Test role for new Permissions array format\",\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2026-01-15T11:23:25.3339094Z\",\n \"updatedOn\": \"2026-01-15T11:23:25.3339094Z\",\n \"createdBy\": null,\n \"updatedBy\": \"00000000-0000-0000-0000-000000000002\"\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/c3d4e5f6-a7b8-9012-cdef-345678901234\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"c3d4e5f6-a7b8-9012-cdef-345678901234\"\n}", + "StatusCode": 201 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions?$filter=roleName%20eq%20'CustomRole%20Delete%20Test'&api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMC9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zPyRmaWx0ZXI9cm9sZU5hbWUlMjBlcSUyMCdDdXN0b21Sb2xlJTIwRGVsZXRlJTIwVGVzdCcmYXBpLXZlcnNpb249MjAyMi0wNS0wMS1wcmV2aWV3", + "RequestMethod": "GET", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "a00a90f6-e28c-4b6f-ad13-bf0a07c71dc7" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ] + }, + "RequestBody": "", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "81e95118-6d06-476b-973f-5252aeba8495" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/aa4f1c58-9147-45fc-8192-e1249f308787" + ], + "x-ms-ratelimit-remaining-subscription-reads": [ + "249" + ], + "x-ms-ratelimit-remaining-subscription-global-reads": [ + "3749" + ], + "x-ms-correlation-request-id": [ + "8cad85d8-7d13-402c-aeea-761423deae2f" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112327Z:8cad85d8-7d13-402c-aeea-761423deae2f" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: 6E5F59CAA6A74870A155E1084C918FB4 Ref B: DUB241062301031 Ref C: 2026-01-15T11:23:27Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:27 GMT" + ], + "Content-Length": [ + "822" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"value\": [\n {\n \"properties\": {\n \"roleName\": \"CustomRole Delete Test\",\n \"type\": \"CustomRole\",\n \"description\": \"Test role for new Permissions array format\",\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2026-01-15T11:23:25.3639098Z\",\n \"updatedOn\": \"2026-01-15T11:23:25.3639098Z\",\n \"createdBy\": \"00000000-0000-0000-0000-000000000002\",\n \"updatedBy\": \"00000000-0000-0000-0000-000000000002\"\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/c3d4e5f6-a7b8-9012-cdef-345678901234\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"c3d4e5f6-a7b8-9012-cdef-345678901234\"\n }\n ]\n}", + "StatusCode": 200 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions?$filter=roleName%20eq%20'CustomRole%20Delete%20Test'&api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMC9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zPyRmaWx0ZXI9cm9sZU5hbWUlMjBlcSUyMCdDdXN0b21Sb2xlJTIwRGVsZXRlJTIwVGVzdCcmYXBpLXZlcnNpb249MjAyMi0wNS0wMS1wcmV2aWV3", + "RequestMethod": "GET", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "449e211a-7232-4481-beff-6c54ece7d76b" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ] + }, + "RequestBody": "", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "4cde5d23-1dbd-444f-89c5-d68462f13dc2" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/ec3b764b-1994-4b9c-9844-249d6457e8af" + ], + "x-ms-ratelimit-remaining-subscription-reads": [ + "249" + ], + "x-ms-ratelimit-remaining-subscription-global-reads": [ + "3749" + ], + "x-ms-correlation-request-id": [ + "8145fe13-62b5-45f3-aa69-251515cb7ab4" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112332Z:8145fe13-62b5-45f3-aa69-251515cb7ab4" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: 29DC4C0E3B004FA893E3C0234F561B31 Ref B: DUB241062301042 Ref C: 2026-01-15T11:23:32Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:31 GMT" + ], + "Content-Length": [ + "12" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"value\": []\n}", + "StatusCode": 200 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/c3d4e5f6-a7b8-9012-cdef-345678901234?api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zL2MzZDRlNWY2LWE3YjgtOTAxMi1jZGVmLTM0NTY3ODkwMTIzND9hcGktdmVyc2lvbj0yMDIyLTA1LTAxLXByZXZpZXc=", + "RequestMethod": "GET", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "790c3bae-f3f5-4328-8ef4-9e6a80fea187" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ] + }, + "RequestBody": "", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "962c1189-a652-4cbe-be02-e2a1f735a396" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/404e2fc0-db81-4afa-a2e2-888f52ec2583" + ], + "x-ms-ratelimit-remaining-subscription-reads": [ + "249" + ], + "x-ms-ratelimit-remaining-subscription-global-reads": [ + "3749" + ], + "x-ms-correlation-request-id": [ + "53aa2517-6b8f-4abc-babf-a1efe9214114" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112328Z:53aa2517-6b8f-4abc-babf-a1efe9214114" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: F332E01824AA449A8AEDAF951DB20A83 Ref B: DUB241062301036 Ref C: 2026-01-15T11:23:28Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:27 GMT" + ], + "Content-Length": [ + "810" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Delete Test\",\n \"type\": \"CustomRole\",\n \"description\": \"Test role for new Permissions array format\",\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2026-01-15T11:23:25.3639098Z\",\n \"updatedOn\": \"2026-01-15T11:23:25.3639098Z\",\n \"createdBy\": \"00000000-0000-0000-0000-000000000002\",\n \"updatedBy\": \"00000000-0000-0000-0000-000000000002\"\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/c3d4e5f6-a7b8-9012-cdef-345678901234\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"c3d4e5f6-a7b8-9012-cdef-345678901234\"\n}", + "StatusCode": 200 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/c3d4e5f6-a7b8-9012-cdef-345678901234?api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zL2MzZDRlNWY2LWE3YjgtOTAxMi1jZGVmLTM0NTY3ODkwMTIzND9hcGktdmVyc2lvbj0yMDIyLTA1LTAxLXByZXZpZXc=", + "RequestMethod": "DELETE", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "790c3bae-f3f5-4328-8ef4-9e6a80fea187" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ] + }, + "RequestBody": "", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "1814a414-fc86-4ccd-a119-582625303370" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/18761349-dc5f-4c13-a6f1-96fca2a69d14" + ], + "x-ms-ratelimit-remaining-subscription-deletes": [ + "199" + ], + "x-ms-ratelimit-remaining-subscription-global-deletes": [ + "2999" + ], + "x-ms-correlation-request-id": [ + "2fc4bd23-8f06-4b51-b59e-fd3de70e18d0" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112332Z:2fc4bd23-8f06-4b51-b59e-fd3de70e18d0" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: 8CACF90762A04161B5308F98DE5BC081 Ref B: DUB241062301036 Ref C: 2026-01-15T11:23:28Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:31 GMT" + ], + "Content-Length": [ + "810" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Delete Test\",\n \"type\": \"CustomRole\",\n \"description\": \"Test role for new Permissions array format\",\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2026-01-15T11:23:25.3639098Z\",\n \"updatedOn\": \"2026-01-15T11:23:29.7793072Z\",\n \"createdBy\": \"00000000-0000-0000-0000-000000000002\",\n \"updatedBy\": \"00000000-0000-0000-0000-000000000002\"\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/c3d4e5f6-a7b8-9012-cdef-345678901234\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"c3d4e5f6-a7b8-9012-cdef-345678901234\"\n}", + "StatusCode": 200 + } + ], + "Names": {}, + "Variables": { + "SubscriptionId": "00000000-0000-0000-0000-000000000002" + } +} \ No newline at end of file diff --git a/src/Resources/Resources.Test/SessionRecords/Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleDefinitionTests/RDNewPermissionsFormatUpdate.json b/src/Resources/Resources.Test/SessionRecords/Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleDefinitionTests/RDNewPermissionsFormatUpdate.json new file mode 100644 index 000000000000..71ad8b55f7c3 --- /dev/null +++ b/src/Resources/Resources.Test/SessionRecords/Microsoft.Azure.Commands.Resources.Test.ScenarioTests.RoleDefinitionTests/RDNewPermissionsFormatUpdate.json @@ -0,0 +1,545 @@ +{ + "Entries": [ + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions?$filter=roleName%20eq%20'Reader'&api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zPyRmaWx0ZXI9cm9sZU5hbWUlMjBlcSUyMCdSZWFkZXInJmFwaS12ZXJzaW9uPTIwMjItMDUtMDEtcHJldmlldw==", + "RequestMethod": "GET", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "522967ee-28cb-4b19-9db8-3b7d82150fad" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ] + }, + "RequestBody": "", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "686e1a45-b9c2-4c51-880c-c1466b2f7418" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/c0400aa1-d2d1-445e-9e9e-1beac9e2de93" + ], + "x-ms-ratelimit-remaining-subscription-reads": [ + "249" + ], + "x-ms-ratelimit-remaining-subscription-global-reads": [ + "3749" + ], + "x-ms-correlation-request-id": [ + "89fe63a5-c6a3-45b8-9eab-ebbf76e68d95" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112343Z:89fe63a5-c6a3-45b8-9eab-ebbf76e68d95" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: A243D492C0E54266830FCEFFA9B6AF2F Ref B: DUB241062306052 Ref C: 2026-01-15T11:23:42Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:42 GMT" + ], + "Content-Length": [ + "627" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"value\": [\n {\n \"properties\": {\n \"roleName\": \"Reader\",\n \"type\": \"BuiltInRole\",\n \"description\": \"View all resources, but does not allow you to make any changes.\",\n \"assignableScopes\": [\n \"/\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"*/read\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2015-02-02T21:55:09.8806423Z\",\n \"updatedOn\": \"2021-11-11T20:13:47.8628684Z\",\n \"createdBy\": null,\n \"updatedBy\": null\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"acdd72a7-3385-48ef-bd42-f606fba81ae7\"\n }\n ]\n}", + "StatusCode": 200 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/b2c3d4e5-f6a7-8901-bcde-f23456789012?api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zL2IyYzNkNGU1LWY2YTctODkwMS1iY2RlLWYyMzQ1Njc4OTAxMj9hcGktdmVyc2lvbj0yMDIyLTA1LTAxLXByZXZpZXc=", + "RequestMethod": "PUT", + "RequestHeaders": { + "x-ms-client-request-id": [ + "eeb483af-c11d-4daa-a981-2e226a5c829d" + ], + "Accept-Language": [ + "en-US" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Content-Length": [ + "521" + ] + }, + "RequestBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Update Test\",\n \"description\": \"Test role for new Permissions array format\",\n \"type\": \"CustomRole\",\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ]\n }\n}", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "b0cc600f-7f9c-4313-9b77-b95bb0575996" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/9d364542-f2b4-4e91-8361-4e490cb79a0f" + ], + "x-ms-ratelimit-remaining-subscription-writes": [ + "199" + ], + "x-ms-ratelimit-remaining-subscription-global-writes": [ + "2999" + ], + "x-ms-correlation-request-id": [ + "7f32e848-7733-4b66-95c9-b44d24ff909f" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112345Z:7f32e848-7733-4b66-95c9-b44d24ff909f" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: 268759C241644C4CB72C2C49EECD323E Ref B: DUB241062307062 Ref C: 2026-01-15T11:23:43Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:44 GMT" + ], + "Content-Length": [ + "776" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Update Test\",\n \"type\": \"CustomRole\",\n \"description\": \"Test role for new Permissions array format\",\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2026-01-15T11:23:43.7819396Z\",\n \"updatedOn\": \"2026-01-15T11:23:43.7819396Z\",\n \"createdBy\": null,\n \"updatedBy\": \"00000000-0000-0000-0000-000000000002\"\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/b2c3d4e5-f6a7-8901-bcde-f23456789012\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"b2c3d4e5-f6a7-8901-bcde-f23456789012\"\n}", + "StatusCode": 201 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/b2c3d4e5-f6a7-8901-bcde-f23456789012?api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zL2IyYzNkNGU1LWY2YTctODkwMS1iY2RlLWYyMzQ1Njc4OTAxMj9hcGktdmVyc2lvbj0yMDIyLTA1LTAxLXByZXZpZXc=", + "RequestMethod": "PUT", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "a6bad9b7-fa66-4de9-b121-e19fda89ee88" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Content-Length": [ + "554" + ] + }, + "RequestBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Update Test\",\n \"description\": \"Test role for new Permissions array format\",\n \"type\": \"CustomRole\",\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\",\n \"Microsoft.Support/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ]\n }\n}", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "536177fe-af21-4706-a12d-8050b96d8346" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/55b70ed9-e66e-4420-aff7-b0ae13b14fbd" + ], + "x-ms-ratelimit-remaining-subscription-writes": [ + "199" + ], + "x-ms-ratelimit-remaining-subscription-global-writes": [ + "2999" + ], + "x-ms-correlation-request-id": [ + "15053342-cdf4-49ec-8d41-917bffbd7982" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112349Z:15053342-cdf4-49ec-8d41-917bffbd7982" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: 9D82872D823448649B1CA4854C7811B9 Ref B: DUB241062302029 Ref C: 2026-01-15T11:23:46Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:49 GMT" + ], + "Content-Length": [ + "798" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Update Test\",\n \"type\": \"CustomRole\",\n \"description\": \"Test role for new Permissions array format\",\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\",\n \"Microsoft.Support/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2026-01-15T11:23:46.7470254Z\",\n \"updatedOn\": \"2026-01-15T11:23:46.7470254Z\",\n \"createdBy\": null,\n \"updatedBy\": \"00000000-0000-0000-0000-000000000002\"\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/b2c3d4e5-f6a7-8901-bcde-f23456789012\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"b2c3d4e5-f6a7-8901-bcde-f23456789012\"\n}", + "StatusCode": 201 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/b2c3d4e5-f6a7-8901-bcde-f23456789012?api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zL2IyYzNkNGU1LWY2YTctODkwMS1iY2RlLWYyMzQ1Njc4OTAxMj9hcGktdmVyc2lvbj0yMDIyLTA1LTAxLXByZXZpZXc=", + "RequestMethod": "GET", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "9ab21d41-0206-49ae-b552-156e486545e7" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ] + }, + "RequestBody": "", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "0e5904de-9963-4403-a3bb-3e17031fc17a" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/e8659a9d-be95-4b63-98d8-c7c7fb1129bc" + ], + "x-ms-ratelimit-remaining-subscription-reads": [ + "249" + ], + "x-ms-ratelimit-remaining-subscription-global-reads": [ + "3749" + ], + "x-ms-correlation-request-id": [ + "78e4df96-ac53-4c80-9f26-a34e13807022" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112345Z:78e4df96-ac53-4c80-9f26-a34e13807022" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: CA2D4B9DD1474B798AB1BBBAE87C256D Ref B: DUB241062308023 Ref C: 2026-01-15T11:23:45Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:45 GMT" + ], + "Content-Length": [ + "810" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Update Test\",\n \"type\": \"CustomRole\",\n \"description\": \"Test role for new Permissions array format\",\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2026-01-15T11:23:43.8039389Z\",\n \"updatedOn\": \"2026-01-15T11:23:43.8039389Z\",\n \"createdBy\": \"00000000-0000-0000-0000-000000000002\",\n \"updatedBy\": \"00000000-0000-0000-0000-000000000002\"\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/b2c3d4e5-f6a7-8901-bcde-f23456789012\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"b2c3d4e5-f6a7-8901-bcde-f23456789012\"\n}", + "StatusCode": 200 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/b2c3d4e5-f6a7-8901-bcde-f23456789012?api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zL2IyYzNkNGU1LWY2YTctODkwMS1iY2RlLWYyMzQ1Njc4OTAxMj9hcGktdmVyc2lvbj0yMDIyLTA1LTAxLXByZXZpZXc=", + "RequestMethod": "GET", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "a6bad9b7-fa66-4de9-b121-e19fda89ee88" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ] + }, + "RequestBody": "", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "42dc2c1d-1c4f-48c2-9a29-95f3d38dcb64" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/54e46290-e075-4b7a-b9d7-c20afdd659cd" + ], + "x-ms-ratelimit-remaining-subscription-reads": [ + "249" + ], + "x-ms-ratelimit-remaining-subscription-global-reads": [ + "3749" + ], + "x-ms-correlation-request-id": [ + "0416a529-c801-40c6-886e-7e59ecf91bf4" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112346Z:0416a529-c801-40c6-886e-7e59ecf91bf4" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: 0D00D228018A4844919ACA7F6E906921 Ref B: DUB241062302029 Ref C: 2026-01-15T11:23:46Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:46 GMT" + ], + "Content-Length": [ + "810" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Update Test\",\n \"type\": \"CustomRole\",\n \"description\": \"Test role for new Permissions array format\",\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2026-01-15T11:23:43.8039389Z\",\n \"updatedOn\": \"2026-01-15T11:23:43.8039389Z\",\n \"createdBy\": \"00000000-0000-0000-0000-000000000002\",\n \"updatedBy\": \"00000000-0000-0000-0000-000000000002\"\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/b2c3d4e5-f6a7-8901-bcde-f23456789012\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"b2c3d4e5-f6a7-8901-bcde-f23456789012\"\n}", + "StatusCode": 200 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/b2c3d4e5-f6a7-8901-bcde-f23456789012?api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zL2IyYzNkNGU1LWY2YTctODkwMS1iY2RlLWYyMzQ1Njc4OTAxMj9hcGktdmVyc2lvbj0yMDIyLTA1LTAxLXByZXZpZXc=", + "RequestMethod": "GET", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "c78b0c35-e2e3-4210-9159-93b0360b802c" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ] + }, + "RequestBody": "", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "63d20456-7679-4341-ab4f-6fef5306bf91" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/82efc685-40f8-4245-aa92-3518d10dff3f" + ], + "x-ms-ratelimit-remaining-subscription-reads": [ + "249" + ], + "x-ms-ratelimit-remaining-subscription-global-reads": [ + "3749" + ], + "x-ms-correlation-request-id": [ + "aa9331fc-eb2e-41c9-b695-47a5879d9f37" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112349Z:aa9331fc-eb2e-41c9-b695-47a5879d9f37" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: CB9E542A8B8C4E39833C23E9E45D3E4C Ref B: DUB241062306029 Ref C: 2026-01-15T11:23:49Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:49 GMT" + ], + "Content-Length": [ + "832" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Update Test\",\n \"type\": \"CustomRole\",\n \"description\": \"Test role for new Permissions array format\",\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\",\n \"Microsoft.Support/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2026-01-15T11:23:43.8039389Z\",\n \"updatedOn\": \"2026-01-15T11:23:46.7580222Z\",\n \"createdBy\": \"00000000-0000-0000-0000-000000000002\",\n \"updatedBy\": \"00000000-0000-0000-0000-000000000002\"\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/b2c3d4e5-f6a7-8901-bcde-f23456789012\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"b2c3d4e5-f6a7-8901-bcde-f23456789012\"\n}", + "StatusCode": 200 + }, + { + "RequestUri": "//subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/b2c3d4e5-f6a7-8901-bcde-f23456789012?api-version=2022-05-01-preview", + "EncodedRequestUri": "Ly9zdWJzY3JpcHRpb25zLzAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMi9wcm92aWRlcnMvTWljcm9zb2Z0LkF1dGhvcml6YXRpb24vcm9sZURlZmluaXRpb25zL2IyYzNkNGU1LWY2YTctODkwMS1iY2RlLWYyMzQ1Njc4OTAxMj9hcGktdmVyc2lvbj0yMDIyLTA1LTAxLXByZXZpZXc=", + "RequestMethod": "DELETE", + "RequestHeaders": { + "Accept-Language": [ + "en-US" + ], + "x-ms-client-request-id": [ + "c78b0c35-e2e3-4210-9159-93b0360b802c" + ], + "User-Agent": [ + "FxVersion/8.0.2025.41914", + "OSName/MacOs", + "OSVersion/Darwin.24.6.0.Darwin.Kernel.Version.24.6.0.Mon.Aug.11.21.16.30.PDT.2025.root.xnu.11417.140.69.701.11.1RELEASE.ARM64.T8132", + "Microsoft.Azure.Management.Authorization.AuthorizationManagementClient/9.0.0" + ] + }, + "RequestBody": "", + "ResponseHeaders": { + "Cache-Control": [ + "no-cache" + ], + "Pragma": [ + "no-cache" + ], + "x-ms-request-id": [ + "c9bdba1d-a38c-47d0-80c7-70535a524ce8" + ], + "X-Content-Type-Options": [ + "nosniff" + ], + "Strict-Transport-Security": [ + "max-age=31536000; includeSubDomains" + ], + "x-ms-operation-identifier": [ + "tenantId=00000000-0000-0000-0000-000000000001,objectId=00000000-0000-0000-0000-000000000003/northeurope/23dd19c9-898a-4f21-8af7-68f735bfc3b8" + ], + "x-ms-ratelimit-remaining-subscription-deletes": [ + "199" + ], + "x-ms-ratelimit-remaining-subscription-global-deletes": [ + "2999" + ], + "x-ms-correlation-request-id": [ + "b8824dee-66f1-4af3-bc71-052d87742d63" + ], + "x-ms-routing-request-id": [ + "NORTHEUROPE:20260115T112352Z:b8824dee-66f1-4af3-bc71-052d87742d63" + ], + "X-Cache": [ + "CONFIG_NOCACHE" + ], + "X-MSEdge-Ref": [ + "Ref A: 928D4871317148BA94FA5C9131E457B1 Ref B: DUB241062306029 Ref C: 2026-01-15T11:23:49Z" + ], + "Date": [ + "Thu, 15 Jan 2026 11:23:51 GMT" + ], + "Content-Length": [ + "832" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Expires": [ + "-1" + ], + "Retry-After": [ + "0" + ] + }, + "ResponseBody": "{\n \"properties\": {\n \"roleName\": \"CustomRole Update Test\",\n \"type\": \"CustomRole\",\n \"description\": \"Test role for new Permissions array format\",\n \"assignableScopes\": [\n \"/subscriptions/00000000-0000-0000-0000-000000000002\"\n ],\n \"permissions\": [\n {\n \"actions\": [\n \"Microsoft.Resources/subscriptions/resourceGroups/read\",\n \"Microsoft.Resources/deployments/*\",\n \"Microsoft.Support/*\"\n ],\n \"notActions\": [],\n \"dataActions\": [],\n \"notDataActions\": []\n }\n ],\n \"createdOn\": \"2026-01-15T11:23:43.8039389Z\",\n \"updatedOn\": \"2026-01-15T11:23:50.798084Z\",\n \"createdBy\": \"00000000-0000-0000-0000-000000000002\",\n \"updatedBy\": \"00000000-0000-0000-0000-000000000002\"\n },\n \"id\": \"/subscriptions/00000000-0000-0000-0000-000000000002/providers/Microsoft.Authorization/roleDefinitions/b2c3d4e5-f6a7-8901-bcde-f23456789012\",\n \"type\": \"Microsoft.Authorization/roleDefinitions\",\n \"name\": \"b2c3d4e5-f6a7-8901-bcde-f23456789012\"\n}", + "StatusCode": 200 + } + ], + "Names": {}, + "Variables": { + "SubscriptionId": "00000000-0000-0000-0000-000000000002" + } +} \ No newline at end of file From 36deca4d172412a4812e0ae6263969991598ca90 Mon Sep 17 00:00:00 2001 From: Andrea Tomassilli Date: Thu, 15 Jan 2026 11:46:06 +0000 Subject: [PATCH 09/12] Add BreakingChangeIssues.csv for PSRoleDefinition property changes --- .../Az.Resources/BreakingChangeIssues.csv | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tools/StaticAnalysis/Exceptions/Az.Resources/BreakingChangeIssues.csv diff --git a/tools/StaticAnalysis/Exceptions/Az.Resources/BreakingChangeIssues.csv b/tools/StaticAnalysis/Exceptions/Az.Resources/BreakingChangeIssues.csv new file mode 100644 index 000000000000..58f693f3ec47 --- /dev/null +++ b/tools/StaticAnalysis/Exceptions/Az.Resources/BreakingChangeIssues.csv @@ -0,0 +1,25 @@ +"Module","ClassName","Target","Severity","ProblemId","Description","Remediation" +"Az.Resources","Microsoft.Azure.Commands.Resources.GetAzureRoleDefinitionCommand","Get-AzRoleDefinition","0","3010","The property 'Actions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'Actions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.GetAzureRoleDefinitionCommand","Get-AzRoleDefinition","0","3010","The property 'NotActions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'NotActions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.GetAzureRoleDefinitionCommand","Get-AzRoleDefinition","0","3010","The property 'DataActions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'DataActions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.GetAzureRoleDefinitionCommand","Get-AzRoleDefinition","0","3010","The property 'NotDataActions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'NotDataActions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.GetAzureRoleDefinitionCommand","Get-AzRoleDefinition","0","3010","The property 'Condition' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'Condition' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.GetAzureRoleDefinitionCommand","Get-AzRoleDefinition","0","3010","The property 'ConditionVersion' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'ConditionVersion' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.NewAzureRoleDefinitionCommand","New-AzRoleDefinition","0","3010","The property 'Actions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'Actions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.NewAzureRoleDefinitionCommand","New-AzRoleDefinition","0","3010","The property 'NotActions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'NotActions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.NewAzureRoleDefinitionCommand","New-AzRoleDefinition","0","3010","The property 'DataActions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'DataActions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.NewAzureRoleDefinitionCommand","New-AzRoleDefinition","0","3010","The property 'NotDataActions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'NotDataActions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.NewAzureRoleDefinitionCommand","New-AzRoleDefinition","0","3010","The property 'Condition' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'Condition' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.NewAzureRoleDefinitionCommand","New-AzRoleDefinition","0","3010","The property 'ConditionVersion' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'ConditionVersion' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.RemoveAzureRoleDefinitionCommand","Remove-AzRoleDefinition","0","3010","The property 'Actions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'Actions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.RemoveAzureRoleDefinitionCommand","Remove-AzRoleDefinition","0","3010","The property 'NotActions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'NotActions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.RemoveAzureRoleDefinitionCommand","Remove-AzRoleDefinition","0","3010","The property 'DataActions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'DataActions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.RemoveAzureRoleDefinitionCommand","Remove-AzRoleDefinition","0","3010","The property 'NotDataActions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'NotDataActions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.RemoveAzureRoleDefinitionCommand","Remove-AzRoleDefinition","0","3010","The property 'Condition' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'Condition' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.RemoveAzureRoleDefinitionCommand","Remove-AzRoleDefinition","0","3010","The property 'ConditionVersion' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'ConditionVersion' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.SetAzureRoleDefinitionCommand","Set-AzRoleDefinition","0","3010","The property 'Actions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'Actions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.SetAzureRoleDefinitionCommand","Set-AzRoleDefinition","0","3010","The property 'NotActions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'NotActions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.SetAzureRoleDefinitionCommand","Set-AzRoleDefinition","0","3010","The property 'DataActions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'DataActions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.SetAzureRoleDefinitionCommand","Set-AzRoleDefinition","0","3010","The property 'NotDataActions' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'NotDataActions' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.SetAzureRoleDefinitionCommand","Set-AzRoleDefinition","0","3010","The property 'Condition' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'Condition' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." +"Az.Resources","Microsoft.Azure.Commands.Resources.SetAzureRoleDefinitionCommand","Set-AzRoleDefinition","0","3010","The property 'ConditionVersion' of type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition' has been removed.","Add the property 'ConditionVersion' back to type 'Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition'." From 220beab8c9c2c0cd4b860be06cd9dc4aebaebadb Mon Sep 17 00:00:00 2001 From: Andrea Tomassilli Date: Thu, 15 Jan 2026 12:40:09 +0000 Subject: [PATCH 10/12] Update help documentation for PSRoleDefinition Permissions structure - Update Get-AzRoleDefinition.md with Permissions property examples - Update New-AzRoleDefinition.md with Permissions array format and JSON sample - Update Set-AzRoleDefinition.md with Permissions structure and API limitation note - Update Remove-AzRoleDefinition.md with Permissions output description - Update ChangeLog.md with clearer issue reference formatting --- src/Resources/Resources/ChangeLog.md | 4 +- .../Resources/help/Get-AzRoleDefinition.md | 35 ++++++- .../Resources/help/New-AzRoleDefinition.md | 97 ++++++++++--------- .../Resources/help/Remove-AzRoleDefinition.md | 29 +++++- .../Resources/help/Set-AzRoleDefinition.md | 70 +++++++------ 5 files changed, 155 insertions(+), 80 deletions(-) diff --git a/src/Resources/Resources/ChangeLog.md b/src/Resources/Resources/ChangeLog.md index c6910de82873..9e2e6d5b5715 100644 --- a/src/Resources/Resources/ChangeLog.md +++ b/src/Resources/Resources/ChangeLog.md @@ -19,7 +19,9 @@ --> ## Upcoming Release -* Fixed `Get-AzRoleDefinition` returning null `Condition` for roles with ABAC conditions on non-first permission entries [#29058] [#25940] +* Fixed `Get-AzRoleDefinition` returning null `Condition` for roles with ABAC conditions on non-first permission entries + - Fixed issue [#29058] + - Fixed issue [#25940] * [Breaking Change] Updated role definition cmdlets (`Get-AzRoleDefinition`, `New-AzRoleDefinition`, `Set-AzRoleDefinition`, `Remove-AzRoleDefinition`) to use a permissions array with per-permission conditions - `PSRoleDefinition` now uses a `Permissions` property to represent actions, data actions, and conditions, including Attribute-Based Access Control (ABAC) conditions - `Get-AzRoleDefinition` and `Remove-AzRoleDefinition` output type `PSRoleDefinition` no longer has flattened `Actions`, `NotActions`, `DataActions`, `NotDataActions`, `Condition`, and `ConditionVersion` properties; use the `Permissions` collection instead diff --git a/src/Resources/Resources/help/Get-AzRoleDefinition.md b/src/Resources/Resources/help/Get-AzRoleDefinition.md index 5b263ed55aae..5d6df61aee64 100644 --- a/src/Resources/Resources/help/Get-AzRoleDefinition.md +++ b/src/Resources/Resources/help/Get-AzRoleDefinition.md @@ -33,23 +33,48 @@ Get-AzRoleDefinition [-Scope ] [-Custom] [-SkipClientSideScopeValidation ## DESCRIPTION Use the Get-AzRoleDefinition command with a particular role name to view its details. -To inspect individual operations that a role grants access to, review the Actions and NotActions properties of the role. +To inspect individual operations that a role grants access to, review the Permissions property of the role. +Each permission entry contains Actions, NotActions, DataActions, NotDataActions, and optionally Condition and ConditionVersion properties. +Roles with Attribute-Based Access Control (ABAC) conditions will have the Condition and ConditionVersion set on the appropriate permission entry. ## EXAMPLES -### Example 1 +### Example 1: Get a role definition by name ```powershell Get-AzRoleDefinition -Name Reader ``` -Get the Reader role definition +Retrieves the Reader role definition with all its permissions. -### Example 2 +### Example 2: List all RBAC role definitions ```powershell Get-AzRoleDefinition ``` -Lists all RBAC role definitions +Lists all Azure RBAC role definitions available in the current scope. + +### Example 3: Access Actions from a role definition +```powershell +$roleDef = Get-AzRoleDefinition -Name "Virtual Machine Contributor" +$roleDef.Permissions[0].Actions +``` + +Retrieves the actions from the first permission entry of a role definition. + +### Example 4: Get all permissions including conditions +```powershell +$roleDef = Get-AzRoleDefinition -Name "Storage Blob Data Reader" +foreach ($permission in $roleDef.Permissions) { + Write-Host "Actions: $($permission.Actions -join ', ')" + Write-Host "DataActions: $($permission.DataActions -join ', ')" + if ($permission.Condition) { + Write-Host "Condition: $($permission.Condition)" + Write-Host "ConditionVersion: $($permission.ConditionVersion)" + } +} +``` + +Iterates through all permission entries and displays actions and any ABAC conditions. ## PARAMETERS diff --git a/src/Resources/Resources/help/New-AzRoleDefinition.md b/src/Resources/Resources/help/New-AzRoleDefinition.md index fc2c4ff8c539..a67236cb24b8 100644 --- a/src/Resources/Resources/help/New-AzRoleDefinition.md +++ b/src/Resources/Resources/help/New-AzRoleDefinition.md @@ -32,57 +32,63 @@ New-AzRoleDefinition [-Role] [-SkipClientSideScopeValidation] ## DESCRIPTION The New-AzRoleDefinition cmdlet creates a custom role in Azure Role-Based Access Control. Provide a role definition as an input to the command as a JSON file or a PSRoleDefinition object. + The input role definition MUST contain the following properties: 1) DisplayName: the name of the custom role 2) Description: a short description of the role that summarizes the access that the role grants. -3) Actions: the set of operations to which the custom role grants access. -Use Get-AzProviderOperation to get the operation for Azure resource providers that can be secured using Azure RBAC. -Following are some valid operation strings: - - "*/read" grants access to read operations of all Azure resource providers. - - "Microsoft.Network/*/read" grants access to read operations for all resource types in the Microsoft.Network resource provider of Azure. - - "Microsoft.Compute/virtualMachines/*" grants access to all operations of virtual machines and its child resource types. +3) Permissions: an array of permission objects, each containing Actions and/or DataActions. + Use Get-AzProviderOperation to get the operation for Azure resource providers that can be secured using Azure RBAC. + Following are some valid operation strings: + - "*/read" grants access to read operations of all Azure resource providers. + - "Microsoft.Network/*/read" grants access to read operations for all resource types in the Microsoft.Network resource provider of Azure. + - "Microsoft.Compute/virtualMachines/*" grants access to all operations of virtual machines and its child resource types. 4) AssignableScopes: the set of scopes (Azure subscriptions or resource groups) in which the custom role will be available for assignment. -Using AssignableScopes you can make the custom role available for assignment in only the subscriptions or resource groups that need it, and not clutter the user experience for the rest of the subscriptions or resource groups. -Following are some valid assignable scopes: - - "/subscriptions/c276fc76-9cd4-44c9-99a7-4fd71546436e", "/subscriptions/e91d47c4-76f3-4271-a796-21b4ecfe3624": makes the role available for assignment in two subscriptions. - - "/subscriptions/c276fc76-9cd4-44c9-99a7-4fd71546436e": makes the role available for assignment in a single subscription. - - "/subscriptions/c276fc76-9cd4-44c9-99a7-4fd71546436e/resourceGroups/Network": makes the role available for assignment only in the Network resource group. -The input role definition MAY contain the following properties: -1) NotActions: the set of operations that must be excluded from the Actions to determine the effective actions for the custom role. -If there is a specific operation that you do not wish to grant access to in a custom role, it is convenient to use NotActions to exclude it, rather than specifying all operations other than that specific operation in Actions. -2) DataActions: the set of data operations to which the custom role grants access. -3) NotDataActions: the set of operations that must be excluded from the DataActions to determine the effective data actions for the custom role. -If there is a specific data operation that you do not wish to grant access to in a custom role, it is convenient to use NotDataActions to exclude it, rather than specifying all operations other than that specific operation in Actions. -NOTE: If a user is assigned a role that specifies an operation in NotActions and also assigned another role grants access to the same operation - the user will be able to perform that operation. -NotActions is not a deny rule - it is simply a convenient way to create a set of allowed operations when specific operations need to be excluded. -Following is a sample json role definition that can be provided as input + Following are some valid assignable scopes: + - "/subscriptions/c276fc76-9cd4-44c9-99a7-4fd71546436e", "/subscriptions/e91d47c4-76f3-4271-a796-21b4ecfe3624": makes the role available for assignment in two subscriptions. + - "/subscriptions/c276fc76-9cd4-44c9-99a7-4fd71546436e": makes the role available for assignment in a single subscription. + - "/subscriptions/c276fc76-9cd4-44c9-99a7-4fd71546436e/resourceGroups/Network": makes the role available for assignment only in the Network resource group. + +Each permission object in the Permissions array MAY contain: +1) Actions: the set of control plane operations to which the custom role grants access. +2) NotActions: the set of operations that must be excluded from the Actions to determine the effective actions. +3) DataActions: the set of data plane operations to which the custom role grants access. +4) NotDataActions: the set of operations that must be excluded from the DataActions. +5) Condition: an Attribute-Based Access Control (ABAC) condition that restricts the permissions. +6) ConditionVersion: the version of the condition syntax, e.g., "2.0" (required if Condition is specified). + +> [!NOTE] +> The Azure RBAC API currently supports only a single element in the Permissions array when creating custom roles. While the data model supports multiple permission entries, create operations must use exactly one permission object. + +Following is a sample JSON role definition that can be provided as input: +```json { - "Name": "Updated Role", - "Description": "Can monitor all resources and start and restart virtual machines", - "Actions": - \[ - "*/read", - "Microsoft.ClassicCompute/virtualmachines/restart/action", - "Microsoft.ClassicCompute/virtualmachines/start/action" - \], - "NotActions": - \[ - "*/write" - \], - "DataActions": - \[ - "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read" - \], - "NotDataActions": - \[ - "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write" - \], - "AssignableScopes": \["/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"\] + "Name": "Custom VM Operator", + "Description": "Can monitor all resources and start and restart virtual machines", + "Permissions": [ + { + "Actions": [ + "*/read", + "Microsoft.Compute/virtualMachines/restart/action", + "Microsoft.Compute/virtualMachines/start/action" + ], + "NotActions": [ + "*/write" + ], + "DataActions": [ + "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read" + ], + "NotDataActions": [ + "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write" + ] + } + ], + "AssignableScopes": ["/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] } +``` ## EXAMPLES -### Example 1: Create using PSRoleDefinitionObject +### Example 1: Create a custom role using PSRoleDefinition object ```powershell $role = New-Object -TypeName Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition $role.Name = 'Virtual Machine Operator' @@ -94,7 +100,6 @@ $permission.Actions = @( "Microsoft.Compute/*/read" "Microsoft.Compute/virtualMachines/start/action" "Microsoft.Compute/virtualMachines/restart/action" - "Microsoft.Compute/virtualMachines/downloadRemoteDesktopConnectionFile/action" "Microsoft.Network/*/read" "Microsoft.Storage/*/read" "Microsoft.Authorization/*/read" @@ -108,11 +113,15 @@ $role.Permissions = @($permission) New-AzRoleDefinition -Role $role ``` -### Example 2: Create using JSON file +Creates a custom role named "Virtual Machine Operator" with the specified actions. + +### Example 2: Create a custom role using JSON file ```powershell New-AzRoleDefinition -InputFile C:\Temp\roleDefinition.json ``` +Creates a custom role from a JSON definition file. + ## PARAMETERS ### -DefaultProfile diff --git a/src/Resources/Resources/help/Remove-AzRoleDefinition.md b/src/Resources/Resources/help/Remove-AzRoleDefinition.md index 987cce3e2429..ad177522076e 100644 --- a/src/Resources/Resources/help/Remove-AzRoleDefinition.md +++ b/src/Resources/Resources/help/Remove-AzRoleDefinition.md @@ -38,23 +38,46 @@ Remove-AzRoleDefinition -InputObject [-SkipClientSideScopeVal ## DESCRIPTION The Remove-AzRoleDefinition cmdlet deletes a custom role in Azure Role-Based Access Control. - Provide the Id parameter of an existing custom role to delete that custom role. +Provide the Id parameter of an existing custom role to delete that custom role. By default, Remove-AzRoleDefinition prompts you for confirmation. To suppress the prompt, use the Force parameter. If there are existing role assignments made to the custom role to be deleted, the delete will fail. +When using the -PassThru parameter, the cmdlet returns the deleted PSRoleDefinition object. +The returned object contains a Permissions collection with Actions, NotActions, DataActions, NotDataActions, and any Attribute-Based Access Control (ABAC) conditions (Condition and ConditionVersion) for each permission entry. + ## EXAMPLES -### Example 1 +### Example 1: Remove a custom role by piping from Get-AzRoleDefinition ```powershell Get-AzRoleDefinition -Name "Virtual Machine Operator" | Remove-AzRoleDefinition ``` -### Example 2 +Retrieves the "Virtual Machine Operator" custom role and pipes it to Remove-AzRoleDefinition for deletion. +You will be prompted for confirmation before the role is deleted. + +### Example 2: Remove a custom role by Id ```powershell Remove-AzRoleDefinition -Id "00001111-aaaa-2222-bbbb-3333cccc4444" ``` +Deletes the custom role with the specified Id. You will be prompted for confirmation. + +### Example 3: Remove a custom role without confirmation +```powershell +Remove-AzRoleDefinition -Name "Custom Reader Role" -Force +``` + +Deletes the custom role named "Custom Reader Role" without prompting for confirmation. + +### Example 4: Remove and return the deleted role definition +```powershell +$deletedRole = Remove-AzRoleDefinition -Name "Custom Writer Role" -Force -PassThru +$deletedRole.Permissions[0].Actions +``` + +Deletes the role and returns the PSRoleDefinition object, then displays the actions from the first permission entry. + ## PARAMETERS ### -DefaultProfile diff --git a/src/Resources/Resources/help/Set-AzRoleDefinition.md b/src/Resources/Resources/help/Set-AzRoleDefinition.md index 52493f5808d1..326ead9f0fd2 100644 --- a/src/Resources/Resources/help/Set-AzRoleDefinition.md +++ b/src/Resources/Resources/help/Set-AzRoleDefinition.md @@ -30,7 +30,20 @@ Set-AzRoleDefinition -Role [-SkipClientSideScopeValidation] ``` ## DESCRIPTION -The Set-AzRoleDefinition cmdlet updates an existing custom role in Azure Role-Based Access Control. Provide the updated role definition as an input to the command as a JSON file or a PSRoleDefinition object. The role definition for the updated custom role MUST contain the Id and all other required properties of the role even if they are not updated: DisplayName, Description, Actions, AssignableScopes. NotActions, DataActions, NotDataActions are optional. +The Set-AzRoleDefinition cmdlet updates an existing custom role in Azure Role-Based Access Control. +Provide the updated role definition as an input to the command as a JSON file or a PSRoleDefinition object. + +The role definition for the updated custom role MUST contain: +- Id: the unique identifier of the role definition to update +- Name (or DisplayName): the name of the custom role +- Description: a short description of the role +- Permissions: an array of permission objects containing Actions and/or DataActions +- AssignableScopes: the scopes where the role can be assigned + +Each permission object in the Permissions array can contain Actions, NotActions, DataActions, NotDataActions, and optionally Condition and ConditionVersion for Attribute-Based Access Control (ABAC) conditions. + +> [!NOTE] +> The Azure RBAC API currently supports only a single element in the Permissions array when updating custom roles. While the data model supports multiple permission entries, update operations must use exactly one permission object. ## EXAMPLES @@ -43,36 +56,39 @@ $roleDef.AssignableScopes = @("/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx Set-AzRoleDefinition -Role $roleDef ``` -### Example 2: Create using JSON file +### Example 2: Update using JSON file ```powershell Set-AzRoleDefinition -InputFile C:\Temp\roleDefinition.json -<# -Following is a sample updated role definition json for Set-AzRoleDefinition: +``` + +Updates a custom role definition from a JSON file. The JSON file must include the role's Id property. + +Sample JSON file content: +```json { - "Id": "52a6cc13-ff92-47a8-a39b-2a8205c3087e", - "Name": "Updated Role", - "Description": "Can monitor all resources and start and restart virtual machines", - "Permissions": [ - { - "Actions": [ - "*/read", - "Microsoft.ClassicCompute/virtualmachines/restart/action", - "Microsoft.ClassicCompute/virtualmachines/start/action" - ], - "NotActions": [ - "*/write" - ], - "DataActions": [ - "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read" - ], - "NotDataActions": [ - "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write" - ] - } - ], - "AssignableScopes": ["/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] + "Id": "52a6cc13-ff92-47a8-a39b-2a8205c3087e", + "Name": "Updated Role", + "Description": "Can monitor all resources and start and restart virtual machines", + "Permissions": [ + { + "Actions": [ + "*/read", + "Microsoft.Compute/virtualMachines/restart/action", + "Microsoft.Compute/virtualMachines/start/action" + ], + "NotActions": [ + "*/write" + ], + "DataActions": [ + "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read" + ], + "NotDataActions": [ + "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write" + ] + } + ], + "AssignableScopes": ["/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] } -#> ``` ## PARAMETERS From d8c7019889bc9e5f82deea524768278a3f24db49 Mon Sep 17 00:00:00 2001 From: Andrea Tomassilli Date: Thu, 15 Jan 2026 14:07:04 +0000 Subject: [PATCH 11/12] Fix help doc format for platyPS --- .../Resources/help/New-AzRoleDefinition.md | 24 ++++++++-------- .../Resources/help/Set-AzRoleDefinition.md | 28 ------------------- 2 files changed, 11 insertions(+), 41 deletions(-) diff --git a/src/Resources/Resources/help/New-AzRoleDefinition.md b/src/Resources/Resources/help/New-AzRoleDefinition.md index a67236cb24b8..4f8afbf94b38 100644 --- a/src/Resources/Resources/help/New-AzRoleDefinition.md +++ b/src/Resources/Resources/help/New-AzRoleDefinition.md @@ -60,31 +60,29 @@ Each permission object in the Permissions array MAY contain: > The Azure RBAC API currently supports only a single element in the Permissions array when creating custom roles. While the data model supports multiple permission entries, create operations must use exactly one permission object. Following is a sample JSON role definition that can be provided as input: -```json { "Name": "Custom VM Operator", "Description": "Can monitor all resources and start and restart virtual machines", - "Permissions": [ + "Permissions": \[ { - "Actions": [ + "Actions": \[ "*/read", "Microsoft.Compute/virtualMachines/restart/action", "Microsoft.Compute/virtualMachines/start/action" - ], - "NotActions": [ + \], + "NotActions": \[ "*/write" - ], - "DataActions": [ + \], + "DataActions": \[ "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read" - ], - "NotDataActions": [ + \], + "NotDataActions": \[ "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write" - ] + \] } - ], - "AssignableScopes": ["/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] + \], + "AssignableScopes": \["/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"\] } -``` ## EXAMPLES diff --git a/src/Resources/Resources/help/Set-AzRoleDefinition.md b/src/Resources/Resources/help/Set-AzRoleDefinition.md index 326ead9f0fd2..1e48298616cd 100644 --- a/src/Resources/Resources/help/Set-AzRoleDefinition.md +++ b/src/Resources/Resources/help/Set-AzRoleDefinition.md @@ -63,34 +63,6 @@ Set-AzRoleDefinition -InputFile C:\Temp\roleDefinition.json Updates a custom role definition from a JSON file. The JSON file must include the role's Id property. -Sample JSON file content: -```json -{ - "Id": "52a6cc13-ff92-47a8-a39b-2a8205c3087e", - "Name": "Updated Role", - "Description": "Can monitor all resources and start and restart virtual machines", - "Permissions": [ - { - "Actions": [ - "*/read", - "Microsoft.Compute/virtualMachines/restart/action", - "Microsoft.Compute/virtualMachines/start/action" - ], - "NotActions": [ - "*/write" - ], - "DataActions": [ - "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read" - ], - "NotDataActions": [ - "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write" - ] - } - ], - "AssignableScopes": ["/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] -} -``` - ## PARAMETERS ### -DefaultProfile From 002e20ebcfce09473764fc9bfadd7d5e01698e7d Mon Sep 17 00:00:00 2001 From: Andrea Tomassilli Date: Thu, 15 Jan 2026 14:23:27 +0000 Subject: [PATCH 12/12] Expand ABAC acronym on first use in ChangeLog --- src/Resources/Resources/ChangeLog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Resources/Resources/ChangeLog.md b/src/Resources/Resources/ChangeLog.md index 9e2e6d5b5715..819bf0ed87f2 100644 --- a/src/Resources/Resources/ChangeLog.md +++ b/src/Resources/Resources/ChangeLog.md @@ -19,11 +19,11 @@ --> ## Upcoming Release -* Fixed `Get-AzRoleDefinition` returning null `Condition` for roles with ABAC conditions on non-first permission entries +* Fixed `Get-AzRoleDefinition` returning null `Condition` for roles with Attribute-Based Access Control (ABAC) conditions on non-first permission entries - Fixed issue [#29058] - Fixed issue [#25940] * [Breaking Change] Updated role definition cmdlets (`Get-AzRoleDefinition`, `New-AzRoleDefinition`, `Set-AzRoleDefinition`, `Remove-AzRoleDefinition`) to use a permissions array with per-permission conditions - - `PSRoleDefinition` now uses a `Permissions` property to represent actions, data actions, and conditions, including Attribute-Based Access Control (ABAC) conditions + - `PSRoleDefinition` now uses a `Permissions` property to represent actions, data actions, and conditions, including ABAC conditions - `Get-AzRoleDefinition` and `Remove-AzRoleDefinition` output type `PSRoleDefinition` no longer has flattened `Actions`, `NotActions`, `DataActions`, `NotDataActions`, `Condition`, and `ConditionVersion` properties; use the `Permissions` collection instead - JSON input files for `New-AzRoleDefinition` and `Set-AzRoleDefinition -InputFile` must define permissions in the `Permissions` array structure instead of using top-level `Actions`, `NotActions`, `DataActions`, and `NotDataActions` properties - Scripts that pass a `PSRoleDefinition` object to `Set-AzRoleDefinition -Role` must update how they read and modify actions and conditions to use the `Permissions` collection