-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[Role] Fix --role filter failing at non-subscription scopes
#32534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
️✔️AzureCLI-FullTest
|
️✔️AzureCLI-BreakingChangeTest
|
|
Thank you for your contribution! We will review the pull request and get back to you soon. |
|
The git hooks are available for azure-cli and azure-cli-extensions repos. They could help you run required checks before creating the PR. Please sync the latest code with latest dev branch (for azure-cli) or main branch (for azure-cli-extensions). pip install azdev --upgrade
azdev setup -c <your azure-cli repo path> -r <your azure-cli-extensions repo path>
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes a bug where az role assignment list --role <guid> and az role assignment delete --role <guid> returned empty results when querying at non-subscription scopes (tenant root, management groups, service groups). The root cause was a mismatch between tenant-format role definition IDs returned by the API and subscription-format IDs generated by the resolver.
Key changes:
- Modified
_resolve_role_idto return tenant-format role definition IDs for GUIDs - Changed role filtering logic from strict equality to
endswith()comparison - Migrated tests from unittest to pytest and added comprehensive test coverage for multiple scope scenarios
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/azure-cli/azure/cli/command_modules/role/custom.py |
Updated _resolve_role_id to return tenant-format IDs for GUIDs; changed role filtering to use endswith() comparison; added function docstring |
src/azure-cli/azure/cli/command_modules/role/tests/latest/test_role_custom.py |
Converted from unittest to pytest; added comprehensive test coverage for role ID resolution and role assignment filtering across different scopes |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/azure-cli/azure/cli/command_modules/role/tests/latest/test_role_custom.py
Outdated
Show resolved
Hide resolved
src/azure-cli/azure/cli/command_modules/role/tests/latest/test_role_custom.py
Show resolved
Hide resolved
36c5ba3 to
fb88971
Compare
fb88971 to
969b830
Compare
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
| ) | ||
|
|
||
| assert len(result) == 1 | ||
| assert result[0].role_definition_id is not None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A new line is missing.
| """ | ||
| if resource_id: | ||
| try: | ||
| return uuid.UUID(resource_id.rsplit('/', 1)[-1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We usually use azure.mgmt.core.tools.parse_resource_id to parse resource ID, but it seems it cannot parse a tenant-level resource:
from azure.mgmt.core.tools import parse_resource_id
print(parse_resource_id('/providers/Microsoft.Authorization/roleDefinitions/e89f72ce-a487-4671-bffa-5eaf57e59f58'))
print(parse_resource_id('/subscriptions/7e30e593-3cc2-47b7-8339-7d5eb15f8142/providers/Microsoft.Authorization/roleDefinitions/e89f72ce-a487-4671-bffa-5eaf57e59f58'))Output:
{'name': '/providers/Microsoft.Authorization/roleDefinitions/e89f72ce-a487-4671-bffa-5eaf57e59f58'}
{'subscription': '7e30e593-3cc2-47b7-8339-7d5eb15f8142', 'namespace': 'Microsoft.Authorization', 'type': 'roleDefinitions', 'name': 'e89f72ce-a487-4671-bffa-5eaf57e59f58', 'children': '', 'resource_parent': '', 'resource_namespace': 'Microsoft.Authorization', 'resource_type': 'roleDefinitions', 'resource_name': 'e89f72ce-a487-4671-bffa-5eaf57e59f58'}| """ | ||
| # Check if it's a role definition resource ID: /providers/Microsoft.Authorization/roleDefinitions/<guid> | ||
| # optionally prefixed with /subscriptions/... The last segment must be a valid GUID. | ||
| if (re.match(r'(/subscriptions/[^/]+)?/providers/Microsoft.Authorization/roleDefinitions/[^/]+$', role, re.I) and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_valid_resource_id cannot parse tenant-level resource either:
from azure.mgmt.core.tools import is_valid_resource_id
print(is_valid_resource_id('/providers/Microsoft.Authorization/roleDefinitions/e89f72ce-a487-4671-bffa-5eaf57e59f58'))
print(is_valid_resource_id('/subscriptions/7e30e593-3cc2-47b7-8339-7d5eb15f8142/providers/Microsoft.Authorization/roleDefinitions/e89f72ce-a487-4671-bffa-5eaf57e59f58'))Output:
False
TrueThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like only used for subscription scoped resources in this module
azure-cli/src/azure-cli/azure/cli/command_modules/role/custom.py
Lines 559 to 561 in 63606b2
| from azure.mgmt.core.tools import is_valid_resource_id | |
| if scope.startswith('/subscriptions/') and not is_valid_resource_id(scope): | |
| raise CLIError('Invalid scope. Please use --help to view the valid format.') |
also it does not work for management groups and service groups, e.g.,
> python -c "from azure.mgmt.core.tools import is_valid_resource_id; rid='/providers/Microsoft.Management/managementGroups/mgmt1/providers/Microsoft.Authorization/roleAssignments/c4cb02d0-af03-4afc-a6ea-1a5af4e84b9b'; print(is_valid_resource_id(rid))"
False|
I can confirm that the same role definition such as |
| """ | ||
| if resource_id: | ||
| try: | ||
| return uuid.UUID(resource_id.rsplit('/', 1)[-1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no need to build a uuid.UUID object. String comparison is sufficient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is to
- check if valid
- deal with different guid formats (e.g,
c4cb02d0-af03-4afc-a6ea-1a5af4e84b9bvsc4cb02d0af034afca6ea-1a5af4e84b9b) and case differences
| mock_client = mock.Mock() | ||
| mock_client._config.subscription_id = '123' | ||
| test_role_id = 'b24988ac-6180-42a0-ab88-20f738123456' | ||
| @pytest.mark.parametrize("resource_id,expected", [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We currently don't use pytest features in order to keep max compatibility with unittest.
| return role | ||
|
|
||
| if is_guid(role): | ||
| return f"/providers/Microsoft.Authorization/roleDefinitions/{role}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this is only a dummy resource ID because the /providers/Microsoft.Authorization/roleDefinitions/ prefix will be stripped by _get_role_definition_id later. Perhaps we can make _resolve_role_id return the role name (GUID)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is because this method is also used by the create role assignment path and it needs a full resource ID (not just a guid).
Agree, this can just return the guid and the create can build the resource path internally
azure-cli/src/azure-cli/azure/cli/command_modules/role/custom.py
Lines 208 to 219 in 63606b2
| def _create_role_assignment(cli_ctx, role, assignee, resource_group_name=None, scope=None, | |
| resolve_assignee=True, assignee_principal_type=None, description=None, | |
| condition=None, condition_version=None, assignment_name=None): | |
| """Prepare scope, role ID and resolve object ID from Graph API.""" | |
| assignment_name = assignment_name or _gen_guid() | |
| factory = _auth_client_factory(cli_ctx, scope) | |
| assignments_client = factory.role_assignments | |
| definitions_client = factory.role_definitions | |
| scope = _build_role_scope(resource_group_name, scope, | |
| assignments_client._config.subscription_id) | |
| role_id = _resolve_role_id(role, scope, definitions_client) |
Closes #32533
Related command
az role assignment listaz role assignment deleteDescription
Fix
az role assignment list --role <guid>andaz role assignment delete --role <guid>returning empty results when querying at non-subscription scopes://providers/Microsoft.Management/managementGroups/{mg-name}/providers/Microsoft.Management/serviceGroups/{service-group-name}Root Cause:
Role definition IDs can have two formats:
/providers/Microsoft.Authorization/roleDefinitions/{guid}/subscriptions/{sub-id}/providers/Microsoft.Authorization/roleDefinitions/{guid}When listing role assignments at tenant/management group/service group scope, the returned
roleDefinitionIduses the tenant format, while the previous code resolved--role <guid>to the subscription format. The strict equality comparison then failed to match assignments.azure-cli/src/azure-cli/azure/cli/command_modules/role/custom.py
Lines 575 to 577 in 63606b2
Then,
_search_role_assignmentsperformed a strict equality comparison:azure-cli/src/azure-cli/azure/cli/command_modules/role/custom.py
Lines 541 to 543 in 63606b2
Example of the mismatch:
roleDefinitionId(from API at root/managed group/service group scope)/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-...role_id(from_resolve_role_id)/subscriptions/xxx/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-...Changes:
_resolve_role_idnow returns tenant-format role definition IDs (/providers/Microsoft.Authorization/roleDefinitions/{guid}) when given a GUID, ensuring consistency across scopes. Also improved regex validation to reject malformed paths._get_role_definition_idhelper function that extracts the role definition GUID from a full resource ID as auuid.UUIDobject for case-insensitive comparison (handling both tenant-format and subscription-format IDs). ReturnsNonefor invalid inputs._search_role_assignmentsnow compares extracted GUIDs (as UUID objects) instead of full resource IDs, so that assignments match regardless of whether they use tenant-format or subscription-format role definition IDs. Includes early return if the role ID couldn't be parsed to a valid GUID.Testing Guide
History Notes
[Role]
az role assignment list/delete: Fix--rolefilter returning empty results at tenant, management group, and service group scopesThis checklist is used to make sure that common guidelines for a pull request are followed.
The PR title and description has followed the guideline in Submitting Pull Requests.
I adhere to the Command Guidelines.
I adhere to the Error Handling Guidelines.