def _get_login_token(login_server, only_refresh_token=True, repository=None): """Obtains refresh and access tokens for an AAD-enabled registry. :param str login_server: The registry login server URL to log in to :param bool only_refresh_token: Whether to ask for only refresh token, or for both refresh and access tokens :param str repository: Repository for which the access token is requested """ login_server = login_server.rstrip('/') challenge = requests.get('https://' + login_server + '/v2/') if challenge.status_code not in [401] or 'WWW-Authenticate' not in challenge.headers: raise CLIError("Registry '{}' did not issue a challenge.".format(login_server)) authenticate = challenge.headers['WWW-Authenticate'] tokens = authenticate.split(' ', 2) if len(tokens) < 2 or tokens[0].lower() != 'bearer': raise CLIError("Registry '{}' does not support AAD login.".format(login_server)) params = {y[0]: y[1].strip('"') for y in (x.strip().split('=', 2) for x in tokens[1].split(','))} if 'realm' not in params or 'service' not in params: raise CLIError("Registry '{}' does not support AAD login.".format(login_server)) authurl = urlparse(params['realm']) authhost = urlunparse((authurl[0], authurl[1], '/oauth2/exchange', '', '', '')) from azure.cli.core._profile import Profile profile = Profile() sp_id, refresh, access, tenant = profile.get_refresh_token() headers = {'Content-Type': 'application/x-www-form-urlencoded'} if not sp_id: if not refresh: content = { 'grant_type': 'access_token', 'service': params['service'], 'tenant': tenant, 'access_token': access } else: content = { 'grant_type': 'access_token_refresh_token', 'service': params['service'], 'tenant': tenant, 'access_token': access, 'refresh_token': refresh } else: content = { 'grant_type': 'spn', 'service': params['service'], 'tenant': tenant, 'username': sp_id, 'password': refresh } response = requests.post(authhost, urlencode(content), headers=headers) if response.status_code not in [200]: raise CLIError( "Access to registry '{}' was denied. Response code: {}.".format( login_server, response.status_code)) refresh_token = loads(response.content.decode("utf-8"))["refresh_token"] if only_refresh_token: return refresh_token, None authhost = urlunparse((authurl[0], authurl[1], '/oauth2/token', '', '', '')) if repository is None: scope = 'registry:catalog:*' else: scope = 'repository:' + repository + ':*' content = { 'grant_type': 'refresh_token', 'service': login_server, 'scope': scope, 'refresh_token': refresh_token } response = requests.post(authhost, urlencode(content), headers=headers) access_token = loads(response.content.decode("utf-8"))["access_token"] return refresh_token, access_token
def _get_aad_token(cli_ctx, login_server, only_refresh_token, repository=None, permission='*'): """Obtains refresh and access tokens for an AAD-enabled registry. :param str login_server: The registry login server URL to log in to :param bool only_refresh_token: Whether to ask for only refresh token, or for both refresh and access tokens :param str repository: Repository for which the access token is requested :param str permission: The requested permission on the repository, '*' or 'pull' """ login_server = login_server.rstrip('/') challenge = requests.get('https://' + login_server + '/v2/', verify=(not should_disable_connection_verify())) if challenge.status_code not in [ 401 ] or 'WWW-Authenticate' not in challenge.headers: raise CLIError( "Registry '{}' did not issue a challenge.".format(login_server)) authenticate = challenge.headers['WWW-Authenticate'] tokens = authenticate.split(' ', 2) if len(tokens) < 2 or tokens[0].lower() != 'bearer': raise CLIError( "Registry '{}' does not support AAD login.".format(login_server)) params = { y[0]: y[1].strip('"') for y in (x.strip().split('=', 2) for x in tokens[1].split(',')) } if 'realm' not in params or 'service' not in params: raise CLIError( "Registry '{}' does not support AAD login.".format(login_server)) authurl = urlparse(params['realm']) authhost = urlunparse( (authurl[0], authurl[1], '/oauth2/exchange', '', '', '')) from azure.cli.core._profile import Profile profile = Profile(cli_ctx=cli_ctx) sp_id, refresh, access, tenant = profile.get_refresh_token() headers = {'Content-Type': 'application/x-www-form-urlencoded'} if not sp_id: if not refresh: content = { 'grant_type': 'access_token', 'service': params['service'], 'tenant': tenant, 'access_token': access } else: content = { 'grant_type': 'access_token_refresh_token', 'service': params['service'], 'tenant': tenant, 'access_token': access, 'refresh_token': refresh } else: content = { 'grant_type': 'spn', 'service': params['service'], 'tenant': tenant, 'username': sp_id, 'password': refresh } response = requests.post(authhost, urlencode(content), headers=headers, verify=(not should_disable_connection_verify())) if response.status_code not in [200]: raise CLIError( "Access to registry '{}' was denied. Response code: {}.".format( login_server, response.status_code)) refresh_token = loads(response.content.decode("utf-8"))["refresh_token"] if only_refresh_token: return refresh_token authhost = urlunparse( (authurl[0], authurl[1], '/oauth2/token', '', '', '')) if repository is None: scope = 'registry:catalog:*' else: scope = 'repository:{}:{}'.format(repository, permission) content = { 'grant_type': 'refresh_token', 'service': login_server, 'scope': scope, 'refresh_token': refresh_token } response = requests.post(authhost, urlencode(content), headers=headers, verify=(not should_disable_connection_verify())) access_token = loads(response.content.decode("utf-8"))["access_token"] return access_token
class MediaV2Client(): """ Media V2 Client """ def __init__(self, cli_ctx, resource_group_name, account_name): from azure.cli.core._profile import Profile self.profile = Profile(cli_ctx=cli_ctx) self._old_rp_api_version = '2015-10-01' self.v2_media_api_resource = cli_ctx.cloud.endpoints.media_resource_id self.api_endpoint = self._get_v2_api_endpoint(cli_ctx, resource_group_name, account_name) self.access_token = self._get_v2_access_token(cli_ctx) def _get_v2_api_endpoint(self, cli_ctx, resource_group_name, account_name): from msrestazure.tools import resource_id from azure.cli.command_modules.ams._sdk_utils import (get_media_namespace, get_media_type) from azure.cli.core.commands.client_factory import get_subscription_id refresh_token_obj = self.profile.get_refresh_token() access_token = refresh_token_obj[2] media_old_rp_url = resource_id(subscription=get_subscription_id(cli_ctx), resource_group=resource_group_name, namespace=get_media_namespace(), type=get_media_type(), name=account_name) + '?api-version={}'.format(self._old_rp_api_version) media_service_res = requests.get(cli_ctx.cloud.endpoints.resource_manager[:-1] + media_old_rp_url, headers={'Authorization': 'Bearer {}'.format(access_token)}) if not media_service_res.ok: err_info = 'Request to 2015-10-01 Media API failed.' res_text = json.loads(media_service_res.text) if res_text is not None and res_text.get('error') is not None: err_info = res_text.get('error').get('message') raise CLIError(err_info) media_service = media_service_res.json() api_endpoints = media_service.get('properties').get('apiEndpoints') api_endpoint = next((x for x in api_endpoints if x.get('majorVersion') == '2'), api_endpoints[0]) if not api_endpoint: raise CLIError('v2 Media API endpoint was not found.') return api_endpoint def _get_v2_access_token(self, cli_ctx): from adal import AuthenticationContext refresh_token_obj = self.profile.get_refresh_token() refresh_token = refresh_token_obj[1] tenant = refresh_token_obj[3] # pylint: disable=protected-access client_id = self.profile.get_login_credentials()[0]._token_retriever()[2]['_clientId'] authority = '{}/{}'.format(cli_ctx.cloud.endpoints.active_directory, tenant) context = AuthenticationContext(authority) return context.acquire_token_with_refresh_token(refresh_token, client_id, self.v2_media_api_resource).get('accessToken') def set_mru(self, account_id, count, type): headers = {} headers['Authorization'] = 'Bearer {}'.format(self.access_token) headers['Content-Type'] = 'application/json;odata=verbose' headers['Accept'] = 'application/json;odata=verbose' url_request_template = "{}EncodingReservedUnitTypes(guid'{}')?api-version=2.19" response = requests.put(url_request_template.format(self.api_endpoint.get('endpoint'), account_id), headers=headers, data='{{"ReservedUnitType":{}, "CurrentReservedUnits":{}}}'.format(type, count)) if not response.ok: err_info = 'No error information available' if json.loads(response.text) is not None and json.loads(response.text).get('error') is not None: err_info = json.loads(response.text).get('error').get('message').get('value') raise CLIError('Request to EncodingReservedUnitTypes v2 API endpoint failed. ' + err_info) def get_mru(self): headers = {} headers['Authorization'] = 'Bearer {}'.format(self.access_token) headers['Content-Type'] = 'application/json;odata=minimalmetadata' headers['Accept'] = 'application/json;odata=minimalmetadata' headers['Accept-Charset'] = 'UTF-8' url_request_template = '{}EncodingReservedUnitTypes?api-version=2.19' response = requests.get(url_request_template.format(self.api_endpoint.get('endpoint')), headers=headers) if not response.ok: raise CLIError('Request to EncodingReservedUnitTypes v2 API endpoint failed.') return response.json().get('value')[0]