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, generated_client_request_id_name='x-ms-client-request-id'): import uuid from requests import Session, Request from requests.structures import CaseInsensitiveDict result = CaseInsensitiveDict() for s in headers or []: try: temp = shell_safe_json_parse(s) result.update(temp) except CLIError: key, value = s.split('=', 1) result[key] = value headers = result # If Authorization header is already provided, don't bother with the token if 'Authorization' in headers: skip_authorization_header = True # Handle User-Agent agents = [get_az_rest_user_agent()] # Borrow AZURE_HTTP_USER_AGENT from msrest # https://github.com/Azure/msrest-for-python/blob/4cc8bc84e96036f03b34716466230fb257e27b36/msrest/pipeline/universal.py#L70 _ENV_ADDITIONAL_USER_AGENT = 'AZURE_HTTP_USER_AGENT' import os if _ENV_ADDITIONAL_USER_AGENT in os.environ: agents.append(os.environ[_ENV_ADDITIONAL_USER_AGENT]) # Custom User-Agent provided as command argument if 'User-Agent' in headers: agents.append(headers['User-Agent']) headers['User-Agent'] = ' '.join(agents) if generated_client_request_id_name: headers[generated_client_request_id_name] = str(uuid.uuid4()) # try to figure out the correct content type if body: try: _ = shell_safe_json_parse(body) if 'Content-Type' not in headers: headers['Content-Type'] = 'application/json' except Exception: # pylint: disable=broad-except pass # add telemetry headers['CommandName'] = cli_ctx.data['command'] if cli_ctx.data.get('safe_params'): headers['ParameterSetName'] = ' '.join(cli_ctx.data['safe_params']) result = {} for s in uri_parameters or []: try: temp = shell_safe_json_parse(s) result.update(temp) except CLIError: key, value = s.split('=', 1) result[key] = value uri_parameters = result or None endpoints = cli_ctx.cloud.endpoints # If url is an ARM resource ID, like /subscriptions/xxx/resourcegroups/xxx?api-version=2019-07-01, # default to Azure Resource Manager. # https://management.azure.com + /subscriptions/xxx/resourcegroups/xxx?api-version=2019-07-01 if '://' not in url: url = endpoints.resource_manager.rstrip('/') + url # Replace common tokens with real values. It is for smooth experience if users copy and paste the url from # Azure Rest API doc from azure.cli.core._profile import Profile profile = Profile(cli_ctx=cli_ctx) if '{subscriptionId}' in url: url = url.replace( '{subscriptionId}', cli_ctx.data['subscription_id'] or profile.get_subscription_id()) # 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: # If url starts with ARM endpoint, like `https://management.azure.com/`, # use `active_directory_resource_id` for resource, like `https://management.core.windows.net/`. # 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 else: from azure.cli.core.cloud import CloudEndpointNotSetException for p in [x for x in dir(endpoints) if not x.startswith('_')]: try: value = getattr(endpoints, p) except CloudEndpointNotSetException: continue if isinstance(value, six.string_types) and url.lower().startswith( value.lower()): resource = value break if resource: # 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 # TODO: In the future when multi-tenant subscription is supported, we won't be able to uniquely identify # the token from subscription anymore. token_subscription = 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) else: logger.debug('Retrieving token for resource %s', resource) token_info, _, _ = profile.get_raw_token(resource) 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" ) # https://requests.readthedocs.io/en/latest/user/advanced/#prepared-requests s = Session() req = Request(method=method, url=url, headers=headers, params=uri_parameters, data=body) prepped = s.prepare_request(req) # Merge environment settings into session settings = s.merge_environment_settings( prepped.url, {}, None, not should_disable_connection_verify(), None) _log_request(prepped) r = s.send(prepped, **settings) _log_response(r) if not r.ok: reason = r.reason if r.text: reason += '({})'.format(r.text) raise CLIError(reason) if output_file: with open(output_file, 'wb') as fd: for chunk in r.iter_content(chunk_size=128): fd.write(chunk) return r
def send_raw_request( cli_ctx, method, uri, 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, generated_client_request_id_name='x-ms-client-request-id'): import uuid import requests from azure.cli.core.commands.client_factory import UA_AGENT result = {} for s in headers or []: try: temp = shell_safe_json_parse(s) result.update(temp) except CLIError: key, value = s.split('=', 1) result[key] = value headers = result headers.update({ 'User-Agent': UA_AGENT, }) if generated_client_request_id_name: headers[generated_client_request_id_name] = str(uuid.uuid4()) # try to figure out the correct content type if body: try: _ = shell_safe_json_parse(body) if 'Content-Type' not in headers: headers['Content-Type'] = 'application/json' except Exception: # pylint: disable=broad-except pass # add telemetry headers['CommandName'] = cli_ctx.data['command'] if cli_ctx.data.get('safe_params'): headers['ParameterSetName'] = ' '.join(cli_ctx.data['safe_params']) result = {} for s in uri_parameters or []: try: temp = shell_safe_json_parse(s) result.update(temp) except CLIError: key, value = s.split('=', 1) result[key] = value uri_parameters = result or None if '://' not in uri: uri = cli_ctx.cloud.endpoints.resource_manager + uri.lstrip('/') # Replace common tokens with real values. It is for smooth experience if users copy and paste the url from # Azure Rest API doc from azure.cli.core._profile import Profile profile = Profile() if '{subscriptionId}' in uri: uri = uri.replace('{subscriptionId}', profile.get_subscription_id()) if not skip_authorization_header and uri.lower().startswith('https://'): if not resource: endpoints = cli_ctx.cloud.endpoints from azure.cli.core.cloud import CloudEndpointNotSetException for p in [x for x in dir(endpoints) if not x.startswith('_')]: try: value = getattr(endpoints, p) except CloudEndpointNotSetException: continue if isinstance(value, six.string_types) and uri.lower().startswith( value.lower()): resource = value break if resource: token_info, _, _ = profile.get_raw_token(resource) logger.debug('Retrievd AAD token for resource: %s', resource or 'ARM') 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" ) try: r = requests.request(method, uri, params=uri_parameters, data=body, headers=headers, verify=not should_disable_connection_verify()) logger.debug("Response Header : %s", r.headers if r else '') except Exception as ex: # pylint: disable=broad-except raise CLIError(ex) if not r.ok: reason = r.reason if r.text: reason += '({})'.format(r.text) raise CLIError(reason) if output_file: with open(output_file, 'wb') as fd: for chunk in r.iter_content(chunk_size=128): fd.write(chunk) return r