def authorize(self, transport, **kwargs): """Obtain access information from an OpenStack Identity Service.""" headers = {'Accept': 'application/json'} body = {'auth': {'identity': {}}} ident = body['auth']['identity'] for method in self.auth_methods: name, auth_data = method.get_auth_data(transport, self, headers) ident.setdefault('methods', []).append(name) ident[name] = auth_data if not ident: raise exceptions.AuthorizationFailure('Authentication method ' 'required (e.g. password)') mutual_exclusion = [ bool(self.domain_id or self.domain_name), bool(self.project_id or self.project_name), bool(self.trust_id) ] if sum(mutual_exclusion) > 1: raise exceptions.AuthorizationFailure('Authentication cannot be ' 'scoped to multiple ' 'targets. Pick one of: ' 'project, domain or trust') if self.domain_id: body['auth']['scope'] = {'domain': {'id': self.domain_id}} elif self.domain_name: body['auth']['scope'] = {'domain': {'name': self.domain_name}} elif self.project_id: body['auth']['scope'] = {'project': {'id': self.project_id}} elif self.project_name: scope = body['auth']['scope'] = {'project': {}} scope['project']['name'] = self.project_name if self.project_domain_id: scope['project']['domain'] = {'id': self.project_domain_id} elif self.project_domain_name: scope['project']['domain'] = {'name': self.project_domain_name} elif self.trust_id: body['auth']['scope'] = {'OS-TRUST:trust': {'id': self.trust_id}} # NOTE(jamielennox): we add nocatalog here rather than in token_url # directly as some federation plugins require the base token_url token_url = self.token_url if not self.include_catalog: token_url += '?nocatalog' _logger.debug('Making authentication request to %s', token_url) resp = transport.post(token_url, json=body, headers=headers) try: resp_data = resp.json()['token'] except (KeyError, ValueError): raise exceptions.InvalidResponse(response=resp) return access.AccessInfoV3(resp.headers['X-Subject-Token'], **resp_data)
def download(self, session): """Download the data contained in an image""" # TODO(briancurtin): This method should probably offload the get # operation into another thread or something of that nature. url = utils.urljoin(self.base_path, self.id, 'file') resp = session.get(url, endpoint_filter=self.service) # See the following bug report for details on why the checksum # code may sometimes depend on a second GET call. # https://bugs.launchpad.net/python-openstacksdk/+bug/1619675 checksum = resp.headers.get("Content-MD5") if checksum is None: # If we don't receive the Content-MD5 header with the download, # make an additional call to get the image details and look at # the checksum attribute. details = self.get(session) checksum = details.checksum if checksum is not None: digest = hashlib.md5(resp.content).hexdigest() if digest != checksum: raise exceptions.InvalidResponse( "checksum mismatch: %s != %s" % (checksum, digest)) else: _logger.warn("Unable to verify the integrity of image %s" % (self.id)) return resp.content
def download(self, session, stream=False, output=None, chunk_size=1024): """Download the data contained in an image""" # TODO(briancurtin): This method should probably offload the get # operation into another thread or something of that nature. url = utils.urljoin(self.base_path, self.id, 'file') resp = session.get(url, stream=stream) # See the following bug report for details on why the checksum # code may sometimes depend on a second GET call. # https://storyboard.openstack.org/#!/story/1619675 checksum = resp.headers.get("Content-MD5") if checksum is None: # If we don't receive the Content-MD5 header with the download, # make an additional call to get the image details and look at # the checksum attribute. details = self.fetch(session) checksum = details.checksum if output: try: # In python 2 we might get StringIO - delete it as soon as # py2 support is dropped if isinstance(output, io.IOBase) \ or isinstance(output, six.StringIO): for chunk in resp.iter_content(chunk_size=chunk_size): output.write(chunk) else: with open(output, 'wb') as fd: for chunk in resp.iter_content(chunk_size=chunk_size): fd.write(chunk) return resp except Exception as e: raise exceptions.SDKException("Unable to download image: %s" % e) # if we are returning the repsonse object, ensure that it # has the content-md5 header so that the caller doesn't # need to jump through the same hoops through which we # just jumped. if stream: resp.headers['content-md5'] = checksum return resp if checksum is not None: digest = hashlib.md5(resp.content).hexdigest() if digest != checksum: raise exceptions.InvalidResponse( "checksum mismatch: %s != %s" % (checksum, digest)) else: session.log.warn("Unable to verify the integrity of image %s" % (self.id)) return resp
def test_claim_messages_no_invalid_response(self): sess = mock.Mock() resp = mock.Mock() resp.status_code = 204 sess.post = mock.Mock( side_effect=exceptions.InvalidResponse(response=resp)) sot = claim.Claim() messages = list(sot.claim_messages( sess, claim.Claim.new(client_id=CLIENT, queue_name=QUEUE, **FAKE))) self.assertEqual(0, len(messages))
def download(self, session): """Download the data contained in an image""" # TODO(briancurtin): This method should probably offload the get # operation into another thread or something of that nature. url = utils.urljoin(self.base_path, self.id, 'file') resp = session.get(url, endpoint_filter=self.service) checksum = resp.headers["Content-MD5"] digest = hashlib.md5(resp.content).hexdigest() if digest != checksum: raise exceptions.InvalidResponse("checksum mismatch") return resp.content
def test_claim_messages_invalid_response(self): sess = mock.Mock() resp = mock.Mock() resp.status_code = 400 sess.post = mock.Mock(side_effect=exceptions.InvalidResponse( response=resp)) sot = claim.Claim() try: list( sot.claim_messages( sess, claim.Claim.new(client=CLIENT, queue=QUEUE, **FAKE))) except exceptions.InvalidResponse as e: self.assertEqual(400, e.response.status_code)
def authorize(self, transport, **kwargs): headers = {'Accept': 'application/json'} url = self.auth_url.rstrip('/') + '/tokens' params = {'auth': self.get_auth_data(headers)} if self.tenant_id: params['auth']['tenantId'] = self.tenant_id elif self.tenant_name: params['auth']['tenantName'] = self.tenant_name if self.trust_id: params['auth']['trust_id'] = self.trust_id _logger.debug('Making authentication request to %s', url) resp = transport.post(url, json=params, headers=headers) try: resp_data = resp.json()['access'] except (KeyError, ValueError): raise exceptions.InvalidResponse(response=resp) return access.AccessInfoV2(**resp_data)
def download(self, session, stream=False): """Download the data contained in an image""" # TODO(briancurtin): This method should probably offload the get # operation into another thread or something of that nature. url = utils.urljoin(self.base_path, self.id, 'file') resp = session.get(url, stream=stream) # See the following bug report for details on why the checksum # code may sometimes depend on a second GET call. # https://storyboard.openstack.org/#!/story/1619675 checksum = resp.headers.get("Content-MD5") if checksum is None: # If we don't receive the Content-MD5 header with the download, # make an additional call to get the image details and look at # the checksum attribute. details = self.fetch(session) checksum = details.checksum # if we are returning the repsonse object, ensure that it # has the content-md5 header so that the caller doesn't # need to jump through the same hoops through which we # just jumped. if stream: resp.headers['content-md5'] = checksum return resp if checksum is not None: digest = hashlib.md5(resp.content).hexdigest() if digest != checksum: raise exceptions.InvalidResponse( "checksum mismatch: %s != %s" % (checksum, digest)) else: _logger.warn("Unable to verify the integrity of image %s" % (self.id)) return resp.content
def request(self, method, url, redirect=None, **kwargs): """Send a request Perform an HTTP request. The following arguments differ from ``requests.Session``: :param string method: Request HTTP method :param string url: Request URL :param boolean/integer redirect: (integer) The maximum number of redirections followed in a request. (boolean) No redirections if False, requests.Session handles redirection if True. (optional) The following additional keyword arguments are supported: :param object json: Request body to be encoded as JSON Overwrites ``data`` argument if present :param string accept: Set the ``Accept`` header; overwrites any value that may be in the headers dict. Header is omitted if ``None``. :param string user_agent: Prepend an additional value to the existing ``User-Agent`` header. Remaining keyword arguments from requests.Session.request() supported """ headers = kwargs.setdefault('headers', {}) # JSON-encode the data in json arg if present # Overwrites any existing 'data' value json_data = kwargs.pop('json', None) if json_data is not None: kwargs['data'] = json.dumps(json_data) headers['Content-Type'] = JSON # Prepend the caller's user_agent to User-Agent header if included, # or use the default that this transport was created with. # Note: Only attempt to work with strings and avoid needlessly # concatenating an empty string. user_agent = kwargs.pop('user_agent', None) if isinstance(user_agent, six.string_types) and user_agent != '': headers['User-Agent'] = '%s %s' % (user_agent, self._user_agent) elif 'User-Agent' in headers: # If they've specified their own headers with a User-Agent, # use that directly. pass else: headers.setdefault('User-Agent', self._user_agent) if redirect is None: redirect = self._redirect if isinstance(redirect, bool) and redirect: # Fall back to requests redirect handling kwargs['allow_redirects'] = True else: # Force disable requests redirect handling, we will manage # redirections below kwargs['allow_redirects'] = False if 'accept' in kwargs: accept = kwargs.pop('accept') else: accept = self._accept if accept: headers.setdefault('Accept', accept) resp = self._send_request(method, url, redirect, **kwargs) try: resp.raise_for_status() except requests.RequestException as e: if resp.status_code == 404: exc_type = exceptions.NotFoundException else: exc_type = exceptions.HttpException raise exc_type(six.text_type(e), details=self._parse_error_response(resp), status_code=resp.status_code) if accept == JSON: try: resp.body = resp.json() except ValueError as e: # this may be simplejson.decode.JSONDecodeError # Re-raise into our own exception raise exceptions.InvalidResponse(response=resp) return resp
def _verify_checksum(md5, checksum): if checksum: digest = md5.hexdigest() if digest != checksum: raise exceptions.InvalidResponse( "checksum mismatch: %s != %s" % (checksum, digest))
def request(self, method, url, redirect=None, **kwargs): """Send a request :param string method: Request HTTP method :param string url: Request URL :param boolean/integer redirect: (integer) The maximum number of redirections followed in a request. (boolean) No redirections if False, requests.Session handles redirection if True. (optional) The following additional kw args are supported: :param object json: Request body to be encoded as JSON Overwrites ``data`` argument if present :param string accept: Set the ``Accept`` header; overwrites any value that may be in the headers dict. Header is omitted if ``None``. :param string user_agent: Set the ``User-Agent`` header; overwrites any value that may be in the headers dict. Header is omitted if ``None``. Remaining kw args from requests.Session.request() supported """ headers = kwargs.setdefault('headers', {}) # JSON-encode the data in json arg if present # Overwrites any existing 'data' value json_data = kwargs.pop('json', None) if json_data is not None: kwargs['data'] = json.dumps(json_data) headers['Content-Type'] = JSON # Set User-Agent header if user_agent arg included, or # fall through the default chain as described above if 'user_agent' in kwargs: headers['User-Agent'] = kwargs.pop('user_agent') elif self._user_agent: headers.setdefault('User-Agent', self._user_agent) else: headers.setdefault('User-Agent', DEFAULT_USER_AGENT) if redirect is None: redirect = self._redirect if isinstance(redirect, bool) and redirect: # Fall back to requests redirect handling kwargs['allow_redirects'] = True else: # Force disable requests redirect handling, we will manage # redirections below kwargs['allow_redirects'] = False if 'accept' in kwargs: accept = kwargs.pop('accept') else: accept = self._accept if accept: headers.setdefault('Accept', accept) self._log_request(method, url, **kwargs) resp = self._send_request(method, url, redirect, **kwargs) self._log_response(resp) try: resp.raise_for_status() except requests.RequestException as e: raise exceptions.HttpException(six.text_type(e), details=resp.text) if accept == JSON: try: resp.body = resp.json() except ValueError as e: # this may be simplejson.decode.JSONDecodeError # Re-raise into our own exception raise exceptions.InvalidResponse(response=resp.text) return resp