Skip to content

Add New-AzDenyAssignment and Remove-AzDenyAssignment cmdlets#29340

Merged
VeryEarly merged 7 commits intoAzure:mainfrom
jruttle:jruttle/add-deny-assignment-cmdlets
Apr 8, 2026
Merged

Add New-AzDenyAssignment and Remove-AzDenyAssignment cmdlets#29340
VeryEarly merged 7 commits intoAzure:mainfrom
jruttle:jruttle/add-deny-assignment-cmdlets

Conversation

@jruttle
Copy link
Copy Markdown
Contributor

@jruttle jruttle commented Mar 30, 2026

Description

Add New-AzDenyAssignment and Remove-AzDenyAssignment cmdlets for user-assigned deny assignments using the 2024-07-01-preview Authorization API.

Cmdlets Added

  • New-AzDenyAssignment — Creates a user-assigned deny assignment at subscription or resource group scope
  • Remove-AzDenyAssignment — Removes a user-assigned deny assignment by ID, name+scope, or pipeline input

Key Design Decisions (PP1 Model)

  • Principals default to "Everyone" (SystemDefined with Guid.Empty) — this is the only supported principal type in PP1
  • ExcludePrincipalId is required (at least one principal must be excluded when denying Everyone)
  • DataActions and DoNotApplyToChildScopes are not supported — tests validate these are rejected with proper errors
  • Only write/delete/action permissions can be denied (read actions are rejected by the API)

Changes

  • SDK regenerated from merged swagger PR Azure/azure-rest-api-specs#41104 (commit 31fb4b3e)
  • New files: 2 cmdlets, 1 options model, C# test harness, PS1 test scripts, 12 session recordings
  • Modified: AuthorizationClient.cs (create/remove logic), Az.Resources.psd1 (exports), SDK README (commit hash)

Tests

  • 12 LiveOnly tests covering: subscription scope, RG scope, custom ID, input file, exclude principals, DataActions (negative), ChildScopes (negative), remove by ID/name/InputObject/PassThru, full E2E CRUD cycle
  • All 12 tests pass in Record mode against live Azure
  • Session recordings included as documentation

Related

Copilot AI review requested due to automatic review settings March 30, 2026 13:07
@azure-client-tools-bot-prd
Copy link
Copy Markdown

Thanks for your contribution! The pull request validation has started. Please revisit this comment for updated status.

@NoriZC
Copy link
Copy Markdown
Contributor

NoriZC commented Mar 30, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds authoring support for user-assigned deny assignments in Az.Resources by introducing New-AzDenyAssignment and Remove-AzDenyAssignment, backed by the regenerated Authorization.Management.Sdk targeting 2024-07-01-preview.

Changes:

  • Added new cmdlets and a create-options model for deny assignment CRUD.
  • Extended AuthorizationClient / extension mapping to support new deny assignment principal model types.
  • Added LiveOnly scenario tests plus session recordings; updated SDK regeneration README and generated SDK code.

Reviewed changes

Copilot reviewed 21 out of 36 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/Resources/Resources/Models.Authorization/CreateDenyAssignmentOptions.cs New options model used to build deny assignment create payloads / input file format.
src/Resources/Resources/Models.Authorization/AuthorizationClientExtensions.cs Adds principal mapping overloads for the regenerated deny assignment principal types.
src/Resources/Resources/Models.Authorization/AuthorizationClient.cs Implements create/delete operations for deny assignments using regenerated SDK APIs.
src/Resources/Resources/DenyAssignments/NewAzureDenyAssignmentCommand.cs New New-AzDenyAssignment cmdlet implementation.
src/Resources/Resources/DenyAssignments/RemoveAzureDenyAssignmentCommand.cs New Remove-AzDenyAssignment cmdlet implementation.
src/Resources/Resources/Az.Resources.psd1 Exports the newly added cmdlets.
src/Resources/Resources.Test/ScenarioTests/DenyAssignmentCrudTests.ps1 Adds LiveOnly scenario test scripts for deny assignment CRUD and negative cases.
src/Resources/Resources.Test/ScenarioTests/DenyAssignmentCrudTests.cs Adds C# test harness wiring to run the new scenario scripts.
src/Resources/Resources.Test/SessionRecords/DenyAssignmentCrudTests/.json Adds session recordings for the new tests.
src/Resources/Authorization.Management.Sdk/README.md Updates swagger commit reference and input-file paths used for SDK regeneration.
src/Resources/Authorization.Management.Sdk/Generated/* Regenerated SDK with deny assignment CRUD, updated models, and supporting types.

Comment thread src/Resources/Resources/Models.Authorization/AuthorizationClient.cs Outdated
Comment thread src/Resources/Resources.Test/ScenarioTests/DenyAssignmentCrudTests.ps1 Outdated
Comment thread src/Resources/Resources/Models.Authorization/AuthorizationClient.cs
Comment thread src/Resources/Resources/Models.Authorization/AuthorizationClient.cs
Comment thread src/Resources/Resources/DenyAssignments/RemoveAzureDenyAssignmentCommand.cs Outdated
Comment thread src/Resources/Resources/DenyAssignments/NewAzureDenyAssignmentCommand.cs Outdated
Comment thread src/Resources/Resources/DenyAssignments/NewAzureDenyAssignmentCommand.cs Outdated
…, typed helpers

- Fix Azure#4: Pass Force.IsPresent to ConfirmAction so -Force suppresses prompt
- Fix Azure#7/Azure#8: Remove unused ProjectResources aliases (CS8019)
- Fix Azure#1/Azure#9: Remove PrincipalId param, reject non-Everyone principals with clear error
- Fix Azure#10: Add InputFile validation for ExcludePrincipalIds
- Fix Azure#5: Remove stale scaffolded TODO comment
- Fix Azure#6: Throw when multiple DAs match by name (disambiguate by ID)
- Fix Azure#2: Replace dynamic ToPSPrincipalsCore with generic typed helper
- Fix Azure#3: Update ExcludePrincipals test to use multiple principals with per-type array

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@NoriZC
Copy link
Copy Markdown
Contributor

NoriZC commented Mar 30, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

Copilot AI review requested due to automatic review settings March 30, 2026 18:16
@NoriZC
Copy link
Copy Markdown
Contributor

NoriZC commented Mar 30, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 39 changed files in this pull request and generated 6 comments.

Comment thread src/Resources/Resources/help/Remove-AzDenyAssignment.md
Comment thread src/Resources/Resources/ChangeLog.md Outdated
Comment thread src/Resources/Resources.Test/ScenarioTests/DenyAssignmentCrudTests.ps1 Outdated
Comment thread src/Resources/Resources/DenyAssignments/RemoveAzureDenyAssignmentCommand.cs Outdated
Comment thread src/Resources/Resources/help/New-AzDenyAssignment.md
@VeryEarly
Copy link
Copy Markdown
Collaborator

@VeryEarly VeryEarly self-assigned this Mar 31, 2026
@NoriZC
Copy link
Copy Markdown
Contributor

NoriZC commented Mar 31, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

@jruttle
Copy link
Copy Markdown
Contributor Author

jruttle commented Mar 31, 2026

Thanks @VeryEarly - this was addressed in commit da9de1f (the 4th push). New-AzDenyAssignment now has SupportsShouldProcess = true on the [Cmdlet] attribute and the create call is wrapped in a ShouldProcess() guard. Remove-AzDenyAssignment already had it from the initial implementation.

Copilot AI review requested due to automatic review settings March 31, 2026 19:35
@NoriZC
Copy link
Copy Markdown
Contributor

NoriZC commented Mar 31, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 39 changed files in this pull request and generated 4 comments.

Comment on lines +660 to +671
var excludePrincipals = new List<DenyAssignmentPrincipal>();
var excludeIds = options.ExcludePrincipalIds ?? new List<string>();
var excludeTypes = options.ExcludePrincipalTypes;
var defaultType = options.ExcludePrincipalType ?? "User";

for (int i = 0; i < excludeIds.Count; i++)
{
var type = (excludeTypes != null && i < excludeTypes.Count)
? excludeTypes[i]
: defaultType;
excludePrincipals.Add(new DenyAssignmentPrincipal { Id = excludeIds[i], Type = type });
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExcludePrincipalTypes is intended to support either one type applied to all ExcludePrincipalIds or one per ID (as described by the cmdlet help/validation), but the current mapping only uses excludeTypes[i] when i < excludeTypes.Count. If the caller supplies a single ExcludePrincipalType value with multiple IDs, only the first excluded principal gets that type and the rest fall back to ExcludePrincipalType (legacy) / "User". Update the mapping so that when ExcludePrincipalTypes.Count == 1, that value is applied to every excluded principal (and consider validating mismatched counts > 1).

Copilot uses AI. Check for mistakes.
Comment on lines +133 to +150
$excludeUser = Get-TestExcludePrincipalId
$spId = "c090fe3f-66fa-4e18-8142-107d8f4cd0e4" # DenyAssignmentTestApp
$daName = "Test-DA-ExcludePrincipals-" + [Guid]::NewGuid().ToString().Substring(0, 8)

try
{
# Exclude both the user and the test service principal
$da = New-AzDenyAssignment `
-DenyAssignmentName $daName `
-Description "Test deny assignment with multiple exclude principals" `
-Scope $subscriptionScope `
-Action "Microsoft.Storage/storageAccounts/write" `
-ExcludePrincipalId @($excludeUser, $spId) `
-ExcludePrincipalType @("User", "ServicePrincipal")

Assert-NotNull $da
Assert-True { $da.ExcludePrincipals.Count -ge 2 }
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test claims to exclude both a user and a service principal, but $spId is set to an application/client ID value. The cmdlet parameter and API expect principal object IDs, so this may not actually be validating multi-principal exclusion (and may be tenant-dependent). Consider resolving the service principal object ID (or making it configurable via an environment variable) and ensure the assertion reflects the actual excluded principals returned.

Copilot uses AI. Check for mistakes.
"696"
]
},
"RequestBody": "{\r\n \"properties\": {\r\n \"denyAssignmentName\": \"Test-DA-ExcludePrincipals-15a41d72\",\r\n \"description\": \"Test deny assignment with multiple exclude principals\",\r\n \"permissions\": [\r\n {\r\n \"actions\": [\r\n \"Microsoft.Storage/storageAccounts/write\"\r\n ],\r\n \"notActions\": [],\r\n \"dataActions\": [],\r\n \"notDataActions\": []\r\n }\r\n ],\r\n \"doNotApplyToChildScopes\": false,\r\n \"principals\": [\r\n {\r\n \"id\": \"00000000-0000-0000-0000-000000000000\",\r\n \"type\": \"SystemDefined\"\r\n }\r\n ],\r\n \"excludePrincipals\": [\r\n {\r\n \"id\": \"1840cc0e-55b5-442d-bbf6-52c0c7e27302\",\r\n \"type\": \"User\"\r\n }\r\n ]\r\n }\r\n}",
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The session recording for the "multiple exclude principals" scenario only contains a single entry in excludePrincipals (the user). This is inconsistent with the corresponding test (Test-NewDaWithExcludePrincipals) which passes two excluded principals and asserts ExcludePrincipals.Count -ge 2. Please re-record/update this session file (or adjust the test) so the recording reflects the current scenario intent.

Suggested change
"RequestBody": "{\r\n \"properties\": {\r\n \"denyAssignmentName\": \"Test-DA-ExcludePrincipals-15a41d72\",\r\n \"description\": \"Test deny assignment with multiple exclude principals\",\r\n \"permissions\": [\r\n {\r\n \"actions\": [\r\n \"Microsoft.Storage/storageAccounts/write\"\r\n ],\r\n \"notActions\": [],\r\n \"dataActions\": [],\r\n \"notDataActions\": []\r\n }\r\n ],\r\n \"doNotApplyToChildScopes\": false,\r\n \"principals\": [\r\n {\r\n \"id\": \"00000000-0000-0000-0000-000000000000\",\r\n \"type\": \"SystemDefined\"\r\n }\r\n ],\r\n \"excludePrincipals\": [\r\n {\r\n \"id\": \"1840cc0e-55b5-442d-bbf6-52c0c7e27302\",\r\n \"type\": \"User\"\r\n }\r\n ]\r\n }\r\n}",
"RequestBody": "{\r\n \"properties\": {\r\n \"denyAssignmentName\": \"Test-DA-ExcludePrincipals-15a41d72\",\r\n \"description\": \"Test deny assignment with multiple exclude principals\",\r\n \"permissions\": [\r\n {\r\n \"actions\": [\r\n \"Microsoft.Storage/storageAccounts/write\"\r\n ],\r\n \"notActions\": [],\r\n \"dataActions\": [],\r\n \"notDataActions\": []\r\n }\r\n ],\r\n \"doNotApplyToChildScopes\": false,\r\n \"principals\": [\r\n {\r\n \"id\": \"00000000-0000-0000-0000-000000000000\",\r\n \"type\": \"SystemDefined\"\r\n }\r\n ],\r\n \"excludePrincipals\": [\r\n {\r\n \"id\": \"1840cc0e-55b5-442d-bbf6-52c0c7e27302\",\r\n \"type\": \"User\"\r\n },\r\n {\r\n \"id\": \"8c1e7f2e-4f5a-4f4c-9f3a-2c8f5d9b7a11\",\r\n \"type\": \"ServicePrincipal\"\r\n }\r\n ]\r\n }\r\n}",

Copilot uses AI. Check for mistakes.
Comment on lines +133 to +137
// PP1 client-side validation: DataActions not supported
if (DataAction != null && DataAction.Length > 0)
{
throw new PSArgumentException(
"DataActions are not supported for PP1 user-assigned deny assignments. " +
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Client-side PP1 validation currently blocks -DataAction but not -NotDataAction, even though NotDataAction is also a data-action list and the cmdlet help says data actions are not supported in PP1. This allows unsupported input through and yields inconsistent (service-side) failures. Consider adding the same validation for NotDataAction (and optionally for input-file scenarios if consistent behavior is desired).

Copilot uses AI. Check for mistakes.
@NoriZC
Copy link
Copy Markdown
Contributor

NoriZC commented Mar 31, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

Copilot AI review requested due to automatic review settings March 31, 2026 21:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 39 changed files in this pull request and generated 6 comments.

Comment on lines +68 to +74
[Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, ParameterSetName = ScopeWithPrincipalsParameterSet,
HelpMessage = "Data actions to deny. Not supported for user-assigned deny assignments (Private Preview).")]
public string[] DataAction { get; set; }

[Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, ParameterSetName = ScopeWithPrincipalsParameterSet,
HelpMessage = "Data actions to exclude from the deny assignment. Not supported for user-assigned deny assignments (Private Preview).")]
public string[] NotDataAction { get; set; }
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter HelpMessage strings refer to "Private Preview", but the cmdlet/help docs describe this as PP1/Public Preview 1. Please align the HelpMessage wording with the actual release stage to avoid confusing users (e.g., consistently use "Public Preview 1 (PP1)").

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +122
try
{
options = JsonConvert.DeserializeObject<CreateDenyAssignmentOptions>(File.ReadAllText(fileName));
}
catch (JsonException ex)
{
throw new PSArgumentException(string.Format("Error parsing input file: {0}", ex.Message));
}

options.Scope = Scope;

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JsonConvert.DeserializeObject<CreateDenyAssignmentOptions>(...) can legally return null (e.g., file contains null/empty JSON). The current code would then throw a NullReferenceException on options.Scope = Scope. Add an explicit null check after deserialization and throw a PSArgumentException with a clear message.

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +129
options.Scope = Scope;

// PP1 requires at least one excluded principal
if (options.ExcludePrincipalIds == null || options.ExcludePrincipalIds.Count == 0)
{
throw new PSArgumentException(
"Input file must specify at least one ExcludePrincipalIds entry. " +
"PP1 deny assignments apply to Everyone and require at least one excluded principal.");
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The InputFile path only validates ExcludePrincipalIds, but does not enforce other PP1 rules that the non-InputFile path enforces (e.g., disallow DataActions/NotDataActions and DoNotApplyToChildScopes, require at least one Actions/NotActions, validate ExcludePrincipalTypes count vs IDs, and ensure DenyAssignmentName is present). This lets users bypass client-side validation by using -InputFile and can lead to less actionable service errors.

Copilot uses AI. Check for mistakes.
Comment on lines +86 to +88
[Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, ParameterSetName = ScopeWithPrincipalsParameterSet,
HelpMessage = "If set, the deny assignment does not apply to child scopes. Not supported for user-assigned deny assignments (Private Preview).")]
public SwitchParameter DoNotApplyToChildScope { get; set; }
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The -DoNotApplyToChildScope HelpMessage also says "Private Preview" while the cmdlet is positioned as PP1/Public Preview 1. Please update this HelpMessage to match the rest of the cmdlet/help wording.

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +13
## SYNOPSIS
Creates a user-assigned deny assignment at the specified scope.
PP1 deny assignments apply to all principals (Everyone) and require at least one excluded principal.

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The synopsis uses the acronym "PP1" without expanding it. Since this is user-facing help, please spell it out on first use (for example, "Public Preview 1 (PP1)") to improve clarity.

Copilot uses AI. Check for mistakes.
Comment on lines +660 to +666
var excludePrincipals = new List<DenyAssignmentPrincipal>();
var excludeIds = options.ExcludePrincipalIds ?? new List<string>();
var excludeTypes = options.ExcludePrincipalTypes;
var defaultType = options.ExcludePrincipalType ?? "User";

for (int i = 0; i < excludeIds.Count; i++)
{
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateDenyAssignment builds and sends a request even when ExcludePrincipalIds is empty (it will send an empty excludePrincipals array). PP1 deny assignments require at least one excluded principal, so this should be enforced here as a last line of defense (especially for -InputFile scenarios) with a clear ArgumentException/PSArgumentException.

Copilot uses AI. Check for mistakes.
@NoriZC
Copy link
Copy Markdown
Contributor

NoriZC commented Mar 31, 2026

/azp run

@azure-pipelines
Copy link
Copy Markdown
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

@VeryEarly VeryEarly merged commit 04a0a8f into Azure:main Apr 8, 2026
15 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants