Skip to content

Commit 9c3e665

Browse files
committed
at-scope
1 parent a4c9d4d commit 9c3e665

File tree

5 files changed

+282
-12
lines changed

5 files changed

+282
-12
lines changed

src/azure-cli/azure/cli/command_modules/role/_help.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -793,11 +793,19 @@
793793
type: command
794794
short-summary: List role assignments.
795795
long-summary: >-
796-
By default, only assignments scoped to subscription will be displayed.
797-
To view assignments scoped by resource or group, use `--all`.
796+
By default, the scope is the current subscription. Specifying the scope with `--scope` is recommended.
797+
798+
799+
By default, only role assignments exactly at the scope are included, not including role assignments at
800+
parent scopes or sub-scopes. For example, when `--scope` is a subscription, role assignments at management
801+
groups or resource groups are not included.
802+
To include role assignments at parent scopes, use `--include-inherited`.
803+
To include role assignments at parent scopes and sub-scopes, use `--at-scope false`.
798804
examples:
799-
- name: List role assignments at the subscription scope.
805+
- name: List role assignments exactly at the subscription scope.
800806
text: az role assignment list --scope /subscriptions/00000000-0000-0000-0000-000000000000
807+
- name: List role assignments at the subscription scope, including parent scopes and sub-scopes.
808+
text: az role assignment list --scope /subscriptions/00000000-0000-0000-0000-000000000000 --at-scope false
801809
- name: List role assignments at the subscription scope, without filling roleDefinitionName property.
802810
text: az role assignment list --scope /subscriptions/00000000-0000-0000-0000-000000000000 --fill-role-definition-name false
803811
- name: List role assignments with "Reader" role at the subscription scope.

src/azure-cli/azure/cli/command_modules/role/_params.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,12 @@ def load_arguments(self, _):
330330

331331
with self.argument_context('role assignment') as c:
332332
c.argument('role', help='role name or id', completer=get_role_definition_name_completion_list)
333-
c.argument('show_all', options_list=['--all'], action='store_true', help='show all assignments under the current subscription')
334-
c.argument('include_inherited', action='store_true', help='include assignments applied on parent scopes')
333+
c.argument('show_all', options_list=['--all'], action='store_true',
334+
deprecate_info=c.deprecate(target='--all'),
335+
help="Show all assignments under the current subscription. This argument is deprecated. "
336+
"Use '--at-scope false' instead.")
337+
c.argument('include_inherited', action='store_true',
338+
help='Include role assignments at parent scopes. This argument only takes effect when --at-scope is true.')
335339
c.argument('can_delegate', action='store_true', help='when set, the assignee will be able to create further role assignments to the same role')
336340
c.argument('assignee', help='represent a user, group, or service principal. supported format: object id, user sign-in name, or service principal name')
337341
c.argument('assignee_object_id',
@@ -345,6 +349,11 @@ def load_arguments(self, _):
345349
c.argument('condition_version', is_preview=True, min_api='2020-04-01-preview', help='Version of the condition syntax. If --condition is specified without --condition-version, default to 2.0.')
346350
c.argument('assignment_name', name_arg_type,
347351
help='A GUID for the role assignment. It must be unique and different for each role assignment. If omitted, a new GUID is generated.')
352+
c.argument('at_scope', arg_type=get_three_state_flag(),
353+
help='If true, only assignments exactly at the scope are included, not including role assignments '
354+
'at parent scopes or sub-scopes. Specify --include-inherited to include assignments at '
355+
'parent scopes. '
356+
'If false, assignments on parent scopes and sub-scopes are included.')
348357

349358
with self.argument_context('role assignment list') as c:
350359
c.argument('fill_principal_name', arg_type=get_three_state_flag(),

src/azure-cli/azure/cli/command_modules/role/custom.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@ def _create_role_assignment(cli_ctx, role, assignee, resource_group_name=None, s
227227
def list_role_assignments(cmd, # pylint: disable=too-many-locals, too-many-branches
228228
assignee=None, assignee_object_id=None,
229229
role=None,
230-
resource_group_name=None, scope=None,
230+
scope=None, at_scope=True,
231+
resource_group_name=None,
231232
include_inherited=False,
232233
show_all=False, include_groups=False,
233234
fill_role_definition_name=True, fill_principal_name=True):
@@ -251,7 +252,8 @@ def list_role_assignments(cmd, # pylint: disable=too-many-locals, too-many-bran
251252
assignee_object_id = _resolve_object_id(cmd.cli_ctx, assignee, fallback_to_object_id=True)
252253
assignments = _search_role_assignments(assignments_client, definitions_client,
253254
scope, assignee_object_id, role,
254-
include_inherited, include_groups)
255+
include_inherited, include_groups,
256+
at_scope=at_scope)
255257

256258
results = todict(assignments) if assignments else []
257259

@@ -499,7 +501,7 @@ def delete_role_assignments(cmd, ids=None,
499501
assignee_object_id = _resolve_object_id(cmd.cli_ctx, assignee, fallback_to_object_id=True)
500502
assignments = _search_role_assignments(assignments_client, definitions_client,
501503
scope, assignee_object_id, role, include_inherited,
502-
include_groups=False)
504+
include_groups=False, at_scope=True)
503505

504506
if assignments:
505507
for a in assignments:
@@ -509,14 +511,20 @@ def delete_role_assignments(cmd, ids=None,
509511

510512

511513
def _search_role_assignments(assignments_client, definitions_client,
512-
scope, assignee_object_id, role, include_inherited, include_groups):
514+
scope, assignee_object_id, role, include_inherited, include_groups,
515+
at_scope=None):
513516
# https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-list-rest
514517
# "atScope()" and "principalId eq '{value}'" query cannot be used together (API limitation).
515518
# always use "scope" if provided, so we can get assignments beyond subscription e.g. management groups
516519
if scope:
517-
f = 'atScope()' # atScope() excludes role assignments at subscopes
520+
filters = []
521+
if at_scope:
522+
filters.append('atScope()') # atScope() excludes role assignments at subscopes
523+
if assignee_object_id and not include_groups and not at_scope:
524+
filters.append("principalId eq '{}'".format(assignee_object_id))
518525
if assignee_object_id and include_groups:
519-
f = f + " and assignedTo('{}')".format(assignee_object_id)
526+
filters.append("assignedTo('{}')".format(assignee_object_id))
527+
f = ' and '.join(filters) if filters else None
520528
assignments = list(assignments_client.list_for_scope(scope=scope, filter=f))
521529
elif assignee_object_id:
522530
if include_groups:
@@ -529,8 +537,10 @@ def _search_role_assignments(assignments_client, definitions_client,
529537

530538
if assignments:
531539
assignments = [ra for ra in assignments if (
532-
# If no scope, list all assignments
540+
# If no scope (--all), list all assignments
533541
not scope or
542+
# If --at-scope false, list all assignments
543+
not at_scope or
534544
# If scope is provided with include_inherited, list assignments at and above the scope.
535545
# Note that assignments below the scope are already excluded by atScope()
536546
include_inherited or
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
interactions:
2+
- request:
3+
body: '{"location": "westus"}'
4+
headers:
5+
Accept:
6+
- application/json
7+
Accept-Encoding:
8+
- gzip, deflate
9+
CommandName:
10+
- identity create
11+
Connection:
12+
- keep-alive
13+
Content-Length:
14+
- '22'
15+
Content-Type:
16+
- application/json
17+
ParameterSetName:
18+
- -g -n --location
19+
User-Agent:
20+
- AZURECLI/2.71.0 azsdk-python-core/1.31.0 Python/3.12.10 (Windows-11-10.0.26100-SP0)
21+
method: PUT
22+
uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_role_assign000001/providers/Microsoft.ManagedIdentity/userAssignedIdentities/clitest000002?api-version=2023-01-31
23+
response:
24+
body:
25+
string: '{"location":"westus","tags":{},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_role_assign000001/providers/Microsoft.ManagedIdentity/userAssignedIdentities/clitest000002","name":"clitest000002","type":"Microsoft.ManagedIdentity/userAssignedIdentities","properties":{"tenantId":"54826b22-38d6-4fb2-bad9-b7b93a3e9c5a","principalId":"88c53162-584e-4c98-984b-62149c2e9bca","clientId":"8cc2fab2-7cc6-4863-aae8-9ee986d5a5ac"}}'
26+
headers:
27+
cache-control:
28+
- no-cache
29+
content-length:
30+
- '449'
31+
content-type:
32+
- application/json; charset=utf-8
33+
date:
34+
- Mon, 14 Apr 2025 08:54:23 GMT
35+
expires:
36+
- '-1'
37+
location:
38+
- /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/cli_role_assign000001/providers/Microsoft.ManagedIdentity/userAssignedIdentities/clitest000002
39+
pragma:
40+
- no-cache
41+
strict-transport-security:
42+
- max-age=31536000; includeSubDomains
43+
x-cache:
44+
- CONFIG_NOCACHE
45+
x-content-type-options:
46+
- nosniff
47+
x-ms-operation-identifier:
48+
- tenantId=54826b22-38d6-4fb2-bad9-b7b93a3e9c5a,objectId=0d504196-1423-4569-9a6e-15149656f0ee/japanwest/90ea6275-c25c-4d73-ba0a-55e6fbe522bb
49+
x-ms-ratelimit-remaining-subscription-global-writes:
50+
- '2999'
51+
x-ms-ratelimit-remaining-subscription-writes:
52+
- '199'
53+
x-msedge-ref:
54+
- 'Ref A: 4359A8C8A1DE4A538CC6C54BFB82E1A0 Ref B: TYO201100114011 Ref C: 2025-04-14T08:54:21Z'
55+
status:
56+
code: 201
57+
message: Created
58+
- request:
59+
body: '{"properties": {"roleDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7",
60+
"principalId": "88c53162-584e-4c98-984b-62149c2e9bca", "principalType": "ServicePrincipal"}}'
61+
headers:
62+
Accept:
63+
- application/json
64+
Accept-Encoding:
65+
- gzip, deflate
66+
CommandName:
67+
- role assignment create
68+
Connection:
69+
- keep-alive
70+
Content-Length:
71+
- '270'
72+
Content-Type:
73+
- application/json
74+
ParameterSetName:
75+
- --assignee-object-id --assignee-principal-type --role --scope
76+
User-Agent:
77+
- AZURECLI/2.71.0 azsdk-python-core/1.31.0 Python/3.12.10 (Windows-11-10.0.26100-SP0)
78+
method: PUT
79+
uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_role_assign000001/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001?api-version=2022-04-01
80+
response:
81+
body:
82+
string: '{"properties":{"roleDefinitionId":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7","principalId":"88c53162-584e-4c98-984b-62149c2e9bca","principalType":"ServicePrincipal","scope":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_role_assign000001","condition":null,"conditionVersion":null,"createdOn":"2025-04-14T08:54:25.8958593Z","updatedOn":"2025-04-14T08:54:26.7153899Z","createdBy":null,"updatedBy":"0d504196-1423-4569-9a6e-15149656f0ee","delegatedManagedIdentityResourceId":null,"description":null},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_role_assign000001/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001","type":"Microsoft.Authorization/roleAssignments","name":"88888888-0000-0000-0000-000000000001"}'
83+
headers:
84+
cache-control:
85+
- no-cache
86+
content-length:
87+
- '897'
88+
content-type:
89+
- application/json; charset=utf-8
90+
date:
91+
- Mon, 14 Apr 2025 08:54:30 GMT
92+
expires:
93+
- '-1'
94+
pragma:
95+
- no-cache
96+
strict-transport-security:
97+
- max-age=31536000; includeSubDomains
98+
x-cache:
99+
- CONFIG_NOCACHE
100+
x-content-type-options:
101+
- nosniff
102+
x-ms-operation-identifier:
103+
- tenantId=54826b22-38d6-4fb2-bad9-b7b93a3e9c5a,objectId=0d504196-1423-4569-9a6e-15149656f0ee/japaneast/5aef1660-efc8-4259-ada4-ff3377587bdb
104+
x-ms-ratelimit-remaining-subscription-global-writes:
105+
- '2999'
106+
x-ms-ratelimit-remaining-subscription-writes:
107+
- '199'
108+
x-msedge-ref:
109+
- 'Ref A: 06485B5DC8264B9C917E7D0942EDE412 Ref B: TYO201151002025 Ref C: 2025-04-14T08:54:25Z'
110+
status:
111+
code: 201
112+
message: Created
113+
- request:
114+
body: null
115+
headers:
116+
Accept:
117+
- application/json
118+
Accept-Encoding:
119+
- gzip, deflate
120+
CommandName:
121+
- role assignment list
122+
Connection:
123+
- keep-alive
124+
ParameterSetName:
125+
- --scope --at-scope --assignee-object-id --fill-role-definition-name --fill-principal-name
126+
User-Agent:
127+
- AZURECLI/2.71.0 azsdk-python-core/1.31.0 Python/3.12.10 (Windows-11-10.0.26100-SP0)
128+
method: GET
129+
uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments?$filter=principalId%20eq%20'88c53162-584e-4c98-984b-62149c2e9bca'&api-version=2022-04-01
130+
response:
131+
body:
132+
string: '{"value":[{"properties":{"roleDefinitionId":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7","principalId":"88c53162-584e-4c98-984b-62149c2e9bca","principalType":"ServicePrincipal","scope":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_role_assign000001","condition":null,"conditionVersion":null,"createdOn":"2025-04-14T08:54:26.7153899Z","updatedOn":"2025-04-14T08:54:26.7153899Z","createdBy":"0d504196-1423-4569-9a6e-15149656f0ee","updatedBy":"0d504196-1423-4569-9a6e-15149656f0ee","delegatedManagedIdentityResourceId":null,"description":null},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_role_assign000001/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001","type":"Microsoft.Authorization/roleAssignments","name":"88888888-0000-0000-0000-000000000001"}]}'
133+
headers:
134+
cache-control:
135+
- no-cache
136+
content-length:
137+
- '943'
138+
content-type:
139+
- application/json; charset=utf-8
140+
date:
141+
- Mon, 14 Apr 2025 08:54:31 GMT
142+
expires:
143+
- '-1'
144+
pragma:
145+
- no-cache
146+
strict-transport-security:
147+
- max-age=31536000; includeSubDomains
148+
x-cache:
149+
- CONFIG_NOCACHE
150+
x-content-type-options:
151+
- nosniff
152+
x-ms-operation-identifier:
153+
- tenantId=54826b22-38d6-4fb2-bad9-b7b93a3e9c5a,objectId=0d504196-1423-4569-9a6e-15149656f0ee/japaneast/4f3785c6-7da5-4a37-9e9a-37b877a366e0
154+
x-ms-ratelimit-remaining-subscription-global-reads:
155+
- '3749'
156+
x-msedge-ref:
157+
- 'Ref A: F01D790831084190BDA2FD89661CF517 Ref B: TYO201151001054 Ref C: 2025-04-14T08:54:31Z'
158+
status:
159+
code: 200
160+
message: OK
161+
- request:
162+
body: null
163+
headers:
164+
Accept:
165+
- application/json
166+
Accept-Encoding:
167+
- gzip, deflate
168+
CommandName:
169+
- role assignment list
170+
Connection:
171+
- keep-alive
172+
ParameterSetName:
173+
- --scope --at-scope --assignee-object-id --fill-role-definition-name --fill-principal-name
174+
User-Agent:
175+
- AZURECLI/2.71.0 azsdk-python-core/1.31.0 Python/3.12.10 (Windows-11-10.0.26100-SP0)
176+
method: GET
177+
uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_role_assign000001/providers/Microsoft.Authorization/roleAssignments?$filter=principalId%20eq%20'88c53162-584e-4c98-984b-62149c2e9bca'&api-version=2022-04-01
178+
response:
179+
body:
180+
string: '{"value":[{"properties":{"roleDefinitionId":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7","principalId":"88c53162-584e-4c98-984b-62149c2e9bca","principalType":"ServicePrincipal","scope":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_role_assign000001","condition":null,"conditionVersion":null,"createdOn":"2025-04-14T08:54:26.7153899Z","updatedOn":"2025-04-14T08:54:26.7153899Z","createdBy":"0d504196-1423-4569-9a6e-15149656f0ee","updatedBy":"0d504196-1423-4569-9a6e-15149656f0ee","delegatedManagedIdentityResourceId":null,"description":null},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/cli_role_assign000001/providers/Microsoft.Authorization/roleAssignments/88888888-0000-0000-0000-000000000001","type":"Microsoft.Authorization/roleAssignments","name":"88888888-0000-0000-0000-000000000001"}]}'
181+
headers:
182+
cache-control:
183+
- no-cache
184+
content-length:
185+
- '943'
186+
content-type:
187+
- application/json; charset=utf-8
188+
date:
189+
- Mon, 14 Apr 2025 08:54:31 GMT
190+
expires:
191+
- '-1'
192+
pragma:
193+
- no-cache
194+
strict-transport-security:
195+
- max-age=31536000; includeSubDomains
196+
x-cache:
197+
- CONFIG_NOCACHE
198+
x-content-type-options:
199+
- nosniff
200+
x-ms-operation-identifier:
201+
- tenantId=54826b22-38d6-4fb2-bad9-b7b93a3e9c5a,objectId=0d504196-1423-4569-9a6e-15149656f0ee/japaneast/b891f240-fe35-43fb-a8ef-cd9597db7f7e
202+
x-ms-ratelimit-remaining-subscription-global-reads:
203+
- '3749'
204+
x-msedge-ref:
205+
- 'Ref A: 0DD953D8592F4F9796E6678A325D0EAB Ref B: TYO201151002025 Ref C: 2025-04-14T08:54:32Z'
206+
status:
207+
code: 200
208+
message: OK
209+
version: 1

0 commit comments

Comments
 (0)