Skip to content

Commit 3c34079

Browse files
authored
[Role] az role assignment create: Do not invoke Graph API if --assignee-principal-type is provided (#19219)
1 parent 8dbfa30 commit 3c34079

File tree

2 files changed

+43
-42
lines changed

2 files changed

+43
-42
lines changed

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

Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def create_role_assignment(cmd, role, assignee=None, assignee_object_id=None, re
147147
raise CLIError('usage error: --assignee STRING | --assignee-object-id GUID')
148148

149149
if assignee_principal_type and not assignee_object_id:
150-
raise CLIError('usage error: --assignee-object-id GUID [--assignee-principal-type]')
150+
raise CLIError('usage error: --assignee-object-id GUID --assignee-principal-type TYPE')
151151

152152
# If condition is set and condition-version is empty, condition-version defaults to "2.0".
153153
if condition and not condition_version:
@@ -157,8 +157,18 @@ def create_role_assignment(cmd, role, assignee=None, assignee_object_id=None, re
157157
if condition_version and not condition:
158158
raise CLIError('usage error: When --condition-version is set, --condition must be set as well.')
159159

160-
object_id, principal_type = _resolve_assignee_object(cmd.cli_ctx, assignee, assignee_object_id,
161-
assignee_principal_type)
160+
if assignee:
161+
object_id, principal_type = _resolve_object_id_and_type(cmd.cli_ctx, assignee, fallback_to_object_id=True)
162+
else:
163+
object_id = assignee_object_id
164+
if assignee_principal_type:
165+
# If principal type is provided, nothing to resolve, do not call Graph
166+
principal_type = assignee_principal_type
167+
else:
168+
# Try best to get principal type
169+
logger.warning('RBAC service might reject creating role assignment without --assignee-principal-type '
170+
'in the future. Better to specify --assignee-principal-type manually.')
171+
principal_type = _get_principal_type_from_object_id(cmd.cli_ctx, assignee_object_id)
162172

163173
try:
164174
return _create_role_assignment(cmd.cli_ctx, role, object_id, resource_group_name, scope, resolve_assignee=False,
@@ -1766,46 +1776,24 @@ def _encode_custom_key_description(key_description):
17661776
return key_description.encode('utf-16')
17671777

17681778

1769-
def _resolve_assignee_object(cli_ctx, assignee, assignee_object_id, assignee_principal_type):
1779+
def _get_principal_type_from_object_id(cli_ctx, assignee_object_id):
17701780
client = _graph_client_factory(cli_ctx)
1771-
result = None
1772-
1773-
# resolve assignee (same as _resolve_object_id)
1774-
if assignee:
1775-
if assignee.find('@') >= 0: # looks like a user principal name
1776-
result = list(client.users.list(filter="userPrincipalName eq '{}'".format(assignee)))
1777-
if not result:
1778-
result = list(client.service_principals.list(
1779-
filter="servicePrincipalNames/any(c:c eq '{}')".format(assignee)))
1780-
if not result and is_guid(assignee): # assume an object id, let us verify it
1781-
result = _get_object_stubs(client, [assignee])
1782-
1783-
# 2+ matches should never happen, so we only check 'no match' here
1784-
if not result:
1785-
raise CLIError("Cannot find user or service principal in graph database for '{assignee}'. "
1786-
"If the assignee is an appId, make sure the corresponding service principal is created "
1787-
"with 'az ad sp create --id {assignee}'.".format(assignee=assignee))
1788-
1789-
return result[0].object_id, result[0].object_type
1790-
1791-
# try to resolve assignee object id
17921781
try:
17931782
result = _get_object_stubs(client, [assignee_object_id])
17941783
if result:
1795-
return result[0].object_id, result[0].object_type
1784+
return result[0].object_type
17961785
except CloudError:
1797-
pass
1798-
1799-
# If failed to verify assignee object id, DO NOT raise exception
1800-
# since --assignee-object-id is exposed to bypass Graph API
1801-
if not assignee_principal_type:
1802-
logger.warning('Failed to query --assignee-principal-type for %s by invoking Graph API.\n'
1803-
'RBAC server might reject creating role assignment without --assignee-principal-type '
1804-
'in the future. Better to specify --assignee-principal-type manually.', assignee_object_id)
1805-
return assignee_object_id, assignee_principal_type
1786+
logger.warning('Failed to query --assignee-principal-type for --assignee-object-id %s by invoking Graph API.',
1787+
assignee_object_id)
1788+
return None
18061789

18071790

18081791
def _resolve_object_id(cli_ctx, assignee, fallback_to_object_id=False):
1792+
object_id, _ = _resolve_object_id_and_type(cli_ctx, assignee, fallback_to_object_id=fallback_to_object_id)
1793+
return object_id
1794+
1795+
1796+
def _resolve_object_id_and_type(cli_ctx, assignee, fallback_to_object_id=False):
18091797
client = _graph_client_factory(cli_ctx)
18101798
result = None
18111799
try:
@@ -1823,10 +1811,14 @@ def _resolve_object_id(cli_ctx, assignee, fallback_to_object_id=False):
18231811
"If the assignee is an appId, make sure the corresponding service principal is created "
18241812
"with 'az ad sp create --id {assignee}'.".format(assignee=assignee))
18251813

1826-
return result[0].object_id
1814+
return result[0].object_id, result[0].object_type
18271815
except (CloudError, GraphErrorException):
1816+
logger.warning('Failed to query %s by invoking Graph API. '
1817+
'If you don\'t have permission to query Graph API, please '
1818+
'specify --assignee-object-id and --assignee-principal-type.', assignee)
18281819
if fallback_to_object_id and is_guid(assignee):
1829-
return assignee
1820+
logger.warning('Assuming %s as an object ID.', assignee)
1821+
return assignee, None
18301822
raise
18311823

18321824

src/azure-cli/azure/cli/command_modules/role/tests/latest/test_role.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -358,11 +358,20 @@ def _test_role_assignment(assignee_object_id, assignee_principal_type=None):
358358
self.kwargs['object_id'] = assignee_object_id
359359
self.kwargs['principal_type'] = assignee_principal_type
360360
# test role assignment on subscription level
361-
if assignee_principal_type:
362-
self.cmd(
363-
'role assignment create --assignee-object-id {object_id} --assignee-principal-type {principal_type} --role Reader -g {rg}')
364-
else:
365-
self.cmd('role assignment create --assignee-object-id {object_id} --role Reader -g {rg}')
361+
362+
with mock.patch('azure.graphrbac.operations.ObjectsOperations.get_objects_by_object_ids') \
363+
as get_objects_by_object_ids_mock:
364+
if assignee_principal_type:
365+
self.cmd(
366+
'role assignment create --assignee-object-id {object_id} '
367+
'--assignee-principal-type {principal_type} --role Reader -g {rg}')
368+
# Verify no graph call
369+
get_objects_by_object_ids_mock.assert_not_called()
370+
else:
371+
self.cmd('role assignment create --assignee-object-id {object_id} --role Reader -g {rg}')
372+
# Verify 1 graph call to resolve principal type
373+
get_objects_by_object_ids_mock.assert_called_once()
374+
366375
self.cmd('role assignment list -g {rg}', checks=self.check("length([])", 1))
367376
self.cmd('role assignment delete -g {rg}')
368377
self.cmd('role assignment list -g {rg}', checks=self.check("length([])", 0))

0 commit comments

Comments
 (0)