Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions src/azure-cli-core/azure/cli/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -867,11 +867,16 @@ def check_connectivity(url='https://azure.microsoft.com', max_retries=5, timeout


def send_raw_request(cli_ctx, method, url, headers=None, uri_parameters=None, # pylint: disable=too-many-locals,too-many-branches,too-many-statements
body=None, skip_authorization_header=False, resource=None, output_file=None,
body=None, skip_authorization_header=False, resource=None, scopes=None, output_file=None,
generated_client_request_id_name='x-ms-client-request-id'):
import uuid
from requests import Session, Request
from requests.structures import CaseInsensitiveDict
from .auth.util import resource_to_scopes

# Prefer MSAL-styled scopes over ADAL-styled resource
if resource and not scopes:
scopes = resource_to_scopes(resource)

result = CaseInsensitiveDict()
for s in headers or []:
Expand Down Expand Up @@ -952,13 +957,13 @@ def send_raw_request(cli_ctx, method, url, headers=None, uri_parameters=None, #

# Prepare the Bearer token for `Authorization` header
if not skip_authorization_header and url.lower().startswith('https://'):
# Prepare `resource` for `get_raw_token`
if not resource:
# Prepare `scopes` for `get_raw_token`
if not scopes:
# If url starts with ARM endpoint, like `https://management.azure.com/`,
# use `active_directory_resource_id` for resource, like `https://management.core.windows.net/`.
# use `active_directory_resource_id` for scopes, like `https://management.core.windows.net//.default`.
# This follows the same behavior as `azure.cli.core.commands.client_factory._get_mgmt_service_client`
if url.lower().startswith(endpoints.resource_manager.rstrip('/')):
resource = endpoints.active_directory_resource_id
scopes = resource_to_scopes(endpoints.active_directory_resource_id)
else:
from azure.cli.core.cloud import CloudEndpointNotSetException
for p in [x for x in dir(endpoints) if not x.startswith('_')]:
Expand All @@ -967,9 +972,9 @@ def send_raw_request(cli_ctx, method, url, headers=None, uri_parameters=None, #
except CloudEndpointNotSetException:
continue
if isinstance(value, str) and url.lower().startswith(value.lower()):
resource = value
scopes = resource_to_scopes(value)
break
if resource:
if scopes:
# Prepare `subscription` for `get_raw_token`
# If this is an ARM request, try to extract subscription ID from the URL.
# But there are APIs which don't require subscription ID, like /subscriptions, /tenants
Expand All @@ -979,17 +984,17 @@ def send_raw_request(cli_ctx, method, url, headers=None, uri_parameters=None, #
if url.lower().startswith(endpoints.resource_manager.rstrip('/')):
token_subscription = _extract_subscription_id(url)
if token_subscription:
logger.debug('Retrieving token for resource %s, subscription %s', resource, token_subscription)
token_info, _, _ = profile.get_raw_token(resource, subscription=token_subscription)
logger.debug('Retrieving token for scopes %s, subscription %s', scopes, token_subscription)
token_info, _, _ = profile.get_raw_token(scopes=scopes, subscription=token_subscription)
else:
logger.debug('Retrieving token for resource %s', resource)
token_info, _, _ = profile.get_raw_token(resource)
logger.debug('Retrieving token for scopes %s', scopes)
token_info, _, _ = profile.get_raw_token(scopes=scopes)
token_type, token, _ = token_info
headers = headers or {}
headers['Authorization'] = '{} {}'.format(token_type, token)
else:
logger.warning("Can't derive appropriate Azure AD resource from --url to acquire an access token. "
"If access token is required, use --resource to specify the resource")
logger.warning("Can't derive appropriate Microsoft Entra ID scopes from --url to acquire an access token. "
"If access token is required, use --scope to specify the scopes")

# https://requests.readthedocs.io/en/latest/user/advanced/#prepared-requests
s = Session()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,10 @@ class GraphClient:

def __init__(self, cli_ctx):
self._cli_ctx = cli_ctx
self._scopes = resource_to_scopes(cli_ctx.cloud.endpoints.microsoft_graph_resource_id)

# https://graph.microsoft.com/ (AzureCloud)
# https://microsoftgraph.chinacloudapi.cn (AzureChinaCloud)
self._resource = cli_ctx.cloud.endpoints.microsoft_graph_resource_id
# https://graph.microsoft.com//.default (AzureCloud)
# https://microsoftgraph.chinacloudapi.cn/.default (AzureChinaCloud)
self._scopes = resource_to_scopes(cli_ctx.cloud.endpoints.microsoft_graph_resource_id)

# https://graph.microsoft.com
# https://microsoftgraph.chinacloudapi.cn
Expand All @@ -54,7 +53,7 @@ def _send(self, method, url, param=None, body=None, api_version=V1_0):

while True:
try:
r = send_raw_request(self._cli_ctx, method, url, resource=self._resource, uri_parameters=param,
r = send_raw_request(self._cli_ctx, method, url, scopes=self._scopes, uri_parameters=param,
body=body)
except HTTPError as ex:
raise GraphError(ex.response.json()['error']['message'], ex.response) from ex
Expand Down
5 changes: 4 additions & 1 deletion src/azure-cli/azure/cli/command_modules/util/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ def load_arguments(self, _):
'see https://github.com/Azure/azure-cli/blob/dev/doc/use_cli_effectively.md#quoting-issues')
c.argument('output_file', help='save response payload to a file')
c.argument('resource',
help='Resource url for which CLI should acquire a token from AAD in order to access '
help='Resource url for which CLI should acquire a token from Microsoft Entra ID in order to access '
'the service. Using --scope is preferred.')
c.argument('scopes', options_list=['--scope'], nargs='+',
help='Scopes for which CLI should acquire a token from Microsoft Entra ID in order to access '
'the service. The token will be placed in the Authorization header. By default, '
'CLI can figure this out based on --url argument, unless you use ones not in the list '
'of "az cloud show --query endpoints"')
Expand Down
7 changes: 4 additions & 3 deletions src/azure-cli/azure/cli/command_modules/util/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@


def rest_call(cmd, url, method=None, headers=None, uri_parameters=None,
body=None, skip_authorization_header=False, resource=None, output_file=None):
body=None, skip_authorization_header=False, resource=None, scopes=None, output_file=None):
from azure.cli.core.commands.transform import unregister_global_transforms
# No transform should be performed on `az rest`.
unregister_global_transforms(cmd.cli_ctx)

from azure.cli.core.util import send_raw_request
r = send_raw_request(cmd.cli_ctx, method, url, headers, uri_parameters, body,
skip_authorization_header, resource, output_file)
r = send_raw_request(cmd.cli_ctx, method, url, headers=headers, uri_parameters=uri_parameters, body=body,
skip_authorization_header=skip_authorization_header, resource=resource, scopes=scopes,
output_file=output_file)
if not output_file and r.content:
try:
return r.json()
Expand Down
Loading