def _send_idp_saml2_authn_request(self, session): """Present modified SAML2 authn assertion from the Service Provider.""" self._prepare_idp_saml2_request(self.saml2_authn_request) idp_saml2_authn_request = self.saml2_authn_request # Currently HTTPBasicAuth method is hardcoded into the plugin idp_response = session.post( self.identity_provider_url, headers={'Content-type': 'text/xml'}, data=etree.tostring(idp_saml2_authn_request), requests_auth=(self.username, self.password), authenticated=False, log=False) try: self.saml2_idp_authn_response = etree.XML(idp_response.content) except etree.XMLSyntaxError as e: msg = ('SAML2: Error parsing XML returned ' 'from Identity Provider, reason: %s') % e raise exceptions.AuthorizationFailure(msg) idp_response_consumer_url = self.saml2_idp_authn_response.xpath( self.ECP_IDP_CONSUMER_URL, namespaces=self.ECP_SAML2_NAMESPACES) self.idp_response_consumer_url = self._first(idp_response_consumer_url) self._check_consumer_urls(session, self.idp_response_consumer_url, self.sp_response_consumer_url)
def test_keystone_request_raises_auth_failure_exception( self, mock_from_resp): kwargs = { "body": { "volume": { "status": "creating", "imageRef": "username", "attach_status": "detached" }, "authenticated": "True" } } with mock.patch.object(adapter.Adapter, 'request', side_effect= keystone_exception.AuthorizationFailure()): session_client = cinderclient.client.SessionClient( session=mock.Mock()) self.assertRaises(keystone_exception.AuthorizationFailure, session_client.request, mock.sentinel.url, 'POST', **kwargs) # As keystonesession.request method will raise # AuthorizationFailure exception, check exceptions.from_response # is not getting called. self.assertFalse(mock_from_resp.called)
def str_to_xml(content, msg=None, include_exc=True): try: return etree.XML(content) except etree.XMLSyntaxError as e: if not msg: msg = str(e) else: msg = msg % e if include_exc else msg raise exceptions.AuthorizationFailure(msg)
def _get_adfs_security_token(self, session): """Send ADFS Security token to the ADFS server. Store the result in the instance attribute and raise an exception in case the response is not valid XML data. If a user cannot authenticate due to providing bad credentials, the ADFS2.0 server will return a HTTP 500 response and a XML Fault message. If ``exceptions.InternalServerError`` is caught, the method tries to parse the XML response. If parsing is unsuccessful, an ``exceptions.AuthorizationFailure`` is raised with a reason from the XML fault. Otherwise an original ``exceptions.InternalServerError`` is re-raised. :param session : a session object to send out HTTP requests. :type session: keystoneauth1.session.Session :raises keystoneauth1.exceptions.AuthorizationFailure: when HTTP response from the ADFS server is not a valid XML ADFS security token. :raises keystoneauth1.exceptions.InternalServerError: If response status code is HTTP 500 and the response XML cannot be recognized. """ def _get_failure(e): xpath = '/s:Envelope/s:Body/s:Fault/s:Code/s:Subcode/s:Value' content = e.response.content try: obj = self.str_to_xml(content).xpath( xpath, namespaces=self.NAMESPACES) obj = self._first(obj) return obj.text # NOTE(marek-denis): etree.Element.xpath() doesn't raise an # exception, it just returns an empty list. In that case, _first() # will raise IndexError and we should treat it as an indication XML # is not valid. exceptions.AuthorizationFailure can be raised from # str_to_xml(), however since server returned HTTP 500 we should # re-raise exceptions.InternalServerError. except (IndexError, exceptions.AuthorizationFailure): raise e request_security_token = self.xml_to_str(self.prepared_request) try: response = session.post(url=self.identity_provider_url, headers=self.HEADER_SOAP, data=request_security_token, authenticated=False) except exceptions.InternalServerError as e: reason = _get_failure(e) raise exceptions.AuthorizationFailure(reason) msg = ('Error parsing XML returned from ' 'the ADFS Identity Provider, reason: %s') self.adfs_token = self.str_to_xml(response.content, msg)
def test_authorize_not_authorized(self): fake_session = mock.Mock() ex_auth = ksa_exc.AuthorizationFailure("not authorized") fake_session.get_auth_headers.side_effect = ex_auth sot = connection.Connection(session=fake_session, authenticator=mock.Mock()) ex = self.assertRaises(exceptions.HttpException, sot.authorize) self.assertEqual(401, ex.status_code) self.assertEqual( 'HttpException: Authentication Failure, not ' 'authorized', six.text_type(ex))
def get_unscoped_auth_ref(self, session, **kwargs): method = self.get_requests_auth() auth = _SamlAuth(self.identity_provider_url, method) try: resp = session.get(self.federated_token_url, requests_auth=auth, authenticated=False) except SamlException as e: raise exceptions.AuthorizationFailure(str(e)) return access.create(resp=resp)
def _send_service_provider_request(self, session): """Initial HTTP GET request to the SAML2 protected endpoint. It's crucial to include HTTP headers indicating that the client is willing to take advantage of the ECP SAML2 extension and receive data as the SOAP. Unlike standard authentication methods in the OpenStack Identity, the client accesses:: ``/v3/OS-FEDERATION/identity_providers/{identity_providers}/ protocols/{protocol}/auth`` After a successful HTTP call the HTTP response should include SAML2 authn request in the XML format. If a HTTP response contains ``X-Subject-Token`` in the headers and the response body is a valid JSON assume the user was already authenticated and Keystone returned a valid unscoped token. Return True indicating the user was already authenticated. :param session: a session object to send out HTTP requests. :type session: keystoneauth1.session.Session """ sp_response = session.get(self.federated_token_url, headers=self.ECP_SP_EMPTY_REQUEST_HEADERS, authenticated=False) if 'X-Subject-Token' in sp_response.headers: self.authenticated_response = sp_response return True try: self.saml2_authn_request = etree.XML(sp_response.content) except etree.XMLSyntaxError as e: msg = ('SAML2: Error parsing XML returned ' 'from Service Provider, reason: %s') % e raise exceptions.AuthorizationFailure(msg) relay_state = self.saml2_authn_request.xpath( self.ECP_RELAY_STATE, namespaces=self.ECP_SAML2_NAMESPACES) self.relay_state = self._first(relay_state) sp_response_consumer_url = self.saml2_authn_request.xpath( self.ECP_SERVICE_PROVIDER_CONSUMER_URL, namespaces=self.ECP_SAML2_NAMESPACES) self.sp_response_consumer_url = self._first(sp_response_consumer_url) return False
def _get_ecp_assertion(self, session): body = { 'auth': { 'identity': { 'methods': ['token'], 'token': { 'id': self._local_cloud_plugin.get_token(session) } }, 'scope': { 'service_provider': { 'id': self._sp_id } } } } endpoint_filter = { 'version': (3, 0), 'interface': plugin.AUTH_INTERFACE } headers = {'Accept': 'application/json'} resp = session.post(self.REQUEST_ECP_URL, json=body, auth=self._local_cloud_plugin, endpoint_filter=endpoint_filter, headers=headers, authenticated=False, raise_exc=False) # NOTE(marek-denis): I am not sure whether disabling exceptions in the # Session object and testing if resp.ok is sufficient. An alternative # would be catching locally all exceptions and reraising with custom # warning. if not resp.ok: msg = ("Error while requesting ECP wrapped assertion: response " "exit code: %(status_code)d, reason: %(err)s") msg = msg % {'status_code': resp.status_code, 'err': resp.reason} raise exceptions.AuthorizationFailure(msg) if not resp.text: raise exceptions.InvalidResponse(resp) return six.text_type(resp.text)
def _get_ecp_assertion(self, session): url = self._local_cloud_plugin.get_endpoint( session, interface=plugin.AUTH_INTERFACE) body = self._ecp_assertion_request(session) resp = session.post(url=url + self.REQUEST_ECP_URL, json=body, raise_exc=False) # NOTE(marek-denis): I am not sure whether disabling exceptions in the # Session object and testing if resp.ok is sufficient. An alternative # would be catching locally all exceptions and reraising with custom # warning. if not resp.ok: msg = ("Error while requesting ECP wrapped assertion: response " "exit code: %(status_code)d, reason: %(err)s") msg = msg % {'status_code': resp.status_code, 'err': resp.reason} raise exceptions.AuthorizationFailure(msg) if not resp.text: raise exceptions.InvalidResponse(resp) return six.text_type(resp.text)
def get_unscoped_auth_ref(self, session, **kwargs): """Obtain unscoped token after authenticating with SAML IdP. :param session: :type session: keystoneauth1.session.Session """ saml_response = self._authenticate(session) # Exit if authentication failed if saml_response is False: sys.exit("Invalid username / password provided.") self._get_auth_cookie(session, saml_response) if self._cookies(session) is False: raise exceptions.AuthorizationFailure( "Session object doesn't contain a cookie, therefore you are " "not allowed to enter the Identity Provider's protected area.") resp = session.get(self.federated_token_url, authenticated=False, cookies=session.session.cookies.get_dict()) return access.create(body=resp.json(), resp=resp)
def _access_service_provider(self, session): """Access protected endpoint and fetch unscoped token. After federated authentication workflow a protected endpoint should be accessible with the session object. The access is granted basing on the cookies stored within the session object. If, for some reason no cookies are present (quantity test) it means something went wrong and user will not be able to fetch an unscoped token. In that case an ``exceptions.AuthorizationFailure` exception is raised and no HTTP call is even made. :param session : a session object to send out HTTP requests. :type session: keystoneauth1.session.Session :raises keystoneauth1.exceptions.AuthorizationFailure: in case session object has empty cookie jar. """ if self._cookies(session) is False: raise exceptions.AuthorizationFailure( "Session object doesn't contain a cookie, therefore you are " "not allowed to enter the Identity Provider's protected area.") self.authenticated_response = session.get(self.federated_token_url, authenticated=False)
def _check_consumer_urls(self, session, sp_response_consumer_url, idp_sp_response_consumer_url): """Check if consumer URLs issued by SP and IdP are equal. In the initial SAML2 authn Request issued by a Service Provider there is a url called ``consumer url``. A trusted Identity Provider should issue identical url. If the URLs are not equal the federated authn process should be interrupted and the user should be warned. :param session: session object to send out HTTP requests. :type session: keystoneauth1.session.Session :param sp_response_consumer_url: consumer URL issued by a SP :type sp_response_consumer_url: string :param idp_sp_response_consumer_url: consumer URL issued by an IdP :type idp_sp_response_consumer_url: string """ if sp_response_consumer_url != idp_sp_response_consumer_url: # send fault message to the SP, discard the response session.post(sp_response_consumer_url, data=self.SOAP_FAULT, headers=self.ECP_SP_SAML2_REQUEST_HEADERS, authenticated=False) # prepare error message and raise an exception. msg = ('Consumer URLs from Service Provider %(service_provider)s ' '%(sp_consumer_url)s and Identity Provider ' '%(identity_provider)s %(idp_consumer_url)s are not equal') msg = msg % { 'service_provider': self.federated_token_url, 'sp_consumer_url': sp_response_consumer_url, 'identity_provider': self.identity_provider, 'idp_consumer_url': idp_sp_response_consumer_url } raise exceptions.AuthorizationFailure(msg)
def request(self, url, method, json=None, original_ip=None, user_agent=None, redirect=None, authenticated=None, endpoint_filter=None, auth=None, requests_auth=None, raise_exc=True, allow_reauth=True, log=True, endpoint_override=None, connect_retries=0, logger=_logger, **kwargs): """Send an HTTP request with the specified characteristics. Wrapper around `requests.Session.request` to handle tasks such as setting headers, JSON encoding/decoding, and error handling. Arguments that are not handled are passed through to the requests library. :param str url: Path or fully qualified URL of HTTP request. If only a path is provided then endpoint_filter must also be provided such that the base URL can be determined. If a fully qualified URL is provided then endpoint_filter will be ignored. :param str method: The http method to use. (e.g. 'GET', 'POST') :param str original_ip: Mark this request as forwarded for this ip. (optional) :param dict headers: Headers to be included in the request. (optional) :param json: Some data to be represented as JSON. (optional) :param str user_agent: A user_agent to use for the request. If present will override one present in headers. (optional) :param int/bool redirect: the maximum number of redirections that can be followed by a request. Either an integer for a specific count or True/False for forever/never. (optional) :param int connect_retries: the maximum number of retries that should be attempted for connection errors. (optional, defaults to 0 - never retry). :param bool authenticated: True if a token should be attached to this request, False if not or None for attach if an auth_plugin is available. (optional, defaults to None) :param dict endpoint_filter: Data to be provided to an auth plugin with which it should be able to determine an endpoint to use for this request. If not provided then URL is expected to be a fully qualified URL. (optional) :param str endpoint_override: The URL to use instead of looking up the endpoint in the auth plugin. This will be ignored if a fully qualified URL is provided but take priority over an endpoint_filter. This string may contain the values ``%(project_id)s`` and ``%(user_id)s`` to have those values replaced by the project_id/user_id of the current authentication. (optional) :param auth: The auth plugin to use when authenticating this request. This will override the plugin that is attached to the session (if any). (optional) :type auth: keystoneauth1.plugin.BaseAuthPlugin :param requests_auth: A requests library auth plugin that cannot be passed via kwarg because the `auth` kwarg collides with our own auth plugins. (optional) :type requests_auth: :py:class:`requests.auth.AuthBase` :param bool raise_exc: If True then raise an appropriate exception for failed HTTP requests. If False then return the request object. (optional, default True) :param bool allow_reauth: Allow fetching a new token and retrying the request on receiving a 401 Unauthorized response. (optional, default True) :param bool log: If True then log the request and response data to the debug log. (optional, default True) :param logger: The logger object to use to log request and responses. If not provided the keystoneauth1.session default logger will be used. :type logger: logging.Logger :param kwargs: any other parameter that can be passed to :meth:`requests.Session.request` (such as `headers`). Except: - `data` will be overwritten by the data in the `json` param. - `allow_redirects` is ignored as redirects are handled by the session. :raises keystoneauth1.exceptions.base.ClientException: For connection failure, or to indicate an error response code. :returns: The response to the request. """ headers = kwargs.setdefault('headers', dict()) if authenticated is None: authenticated = bool(auth or self.auth) if authenticated: auth_headers = self.get_auth_headers(auth) if auth_headers is None: msg = 'No valid authentication is available' raise exceptions.AuthorizationFailure(msg) headers.update(auth_headers) if osprofiler_web: headers.update(osprofiler_web.get_trace_id_headers()) # if we are passed a fully qualified URL and an endpoint_filter we # should ignore the filter. This will make it easier for clients who # want to overrule the default endpoint_filter data added to all client # requests. We check fully qualified here by the presence of a host. if not urllib.parse.urlparse(url).netloc: base_url = None if endpoint_override: base_url = endpoint_override % _StringFormatter(self, auth) elif endpoint_filter: base_url = self.get_endpoint(auth, **endpoint_filter) if not base_url: raise exceptions.EndpointNotFound() url = '%s/%s' % (base_url.rstrip('/'), url.lstrip('/')) if self.cert: kwargs.setdefault('cert', self.cert) if self.timeout is not None: kwargs.setdefault('timeout', self.timeout) if user_agent: headers['User-Agent'] = user_agent elif self.user_agent: user_agent = headers.setdefault('User-Agent', self.user_agent) else: user_agent = headers.setdefault('User-Agent', DEFAULT_USER_AGENT) if self.original_ip: headers.setdefault('Forwarded', 'for=%s;by=%s' % (self.original_ip, user_agent)) if json is not None: headers['Content-Type'] = 'application/json' kwargs['data'] = self._json.encode(json) kwargs.setdefault('verify', self.verify) if requests_auth: kwargs['auth'] = requests_auth if log: self._http_log_request(url, method=method, data=kwargs.get('data'), headers=headers, logger=logger) # Force disable requests redirect handling. We will manage this below. kwargs['allow_redirects'] = False if redirect is None: redirect = self.redirect send = functools.partial(self._send_request, url, method, redirect, log, logger, connect_retries) try: connection_params = self.get_auth_connection_params(auth=auth) except exceptions.MissingAuthPlugin: # NOTE(jamielennox): If we've gotten this far without an auth # plugin then we should be happy with allowing no additional # connection params. This will be the typical case for plugins # anyway. pass else: if connection_params: kwargs.update(connection_params) resp = send(**kwargs) # handle getting a 401 Unauthorized response by invalidating the plugin # and then retrying the request. This is only tried once. if resp.status_code == 401 and authenticated and allow_reauth: if self.invalidate(auth): auth_headers = self.get_auth_headers(auth) if auth_headers is not None: headers.update(auth_headers) resp = send(**kwargs) if raise_exc and resp.status_code >= 400: logger.debug('Request returned failure status: %s', resp.status_code) raise exceptions.from_response(resp, method, url) return resp
def request(self, url, method, json=None, original_ip=None, user_agent=None, redirect=None, authenticated=None, endpoint_filter=None, auth=None, requests_auth=None, raise_exc=True, allow_reauth=True, log=True, endpoint_override=None, connect_retries=0, logger=_logger, allow={}, client_name=None, client_version=None, microversion=None, microversion_service_type=None, **kwargs): """Send an HTTP request with the specified characteristics. Wrapper around `requests.Session.request` to handle tasks such as setting headers, JSON encoding/decoding, and error handling. Arguments that are not handled are passed through to the requests library. :param str url: Path or fully qualified URL of HTTP request. If only a path is provided then endpoint_filter must also be provided such that the base URL can be determined. If a fully qualified URL is provided then endpoint_filter will be ignored. :param str method: The http method to use. (e.g. 'GET', 'POST') :param str original_ip: Mark this request as forwarded for this ip. (optional) :param dict headers: Headers to be included in the request. (optional) :param json: Some data to be represented as JSON. (optional) :param str user_agent: A user_agent to use for the request. If present will override one present in headers. (optional) :param int/bool redirect: the maximum number of redirections that can be followed by a request. Either an integer for a specific count or True/False for forever/never. (optional) :param int connect_retries: the maximum number of retries that should be attempted for connection errors. (optional, defaults to 0 - never retry). :param bool authenticated: True if a token should be attached to this request, False if not or None for attach if an auth_plugin is available. (optional, defaults to None) :param dict endpoint_filter: Data to be provided to an auth plugin with which it should be able to determine an endpoint to use for this request. If not provided then URL is expected to be a fully qualified URL. (optional) :param str endpoint_override: The URL to use instead of looking up the endpoint in the auth plugin. This will be ignored if a fully qualified URL is provided but take priority over an endpoint_filter. This string may contain the values ``%(project_id)s`` and ``%(user_id)s`` to have those values replaced by the project_id/user_id of the current authentication. (optional) :param auth: The auth plugin to use when authenticating this request. This will override the plugin that is attached to the session (if any). (optional) :type auth: keystoneauth1.plugin.BaseAuthPlugin :param requests_auth: A requests library auth plugin that cannot be passed via kwarg because the `auth` kwarg collides with our own auth plugins. (optional) :type requests_auth: :py:class:`requests.auth.AuthBase` :param bool raise_exc: If True then raise an appropriate exception for failed HTTP requests. If False then return the request object. (optional, default True) :param bool allow_reauth: Allow fetching a new token and retrying the request on receiving a 401 Unauthorized response. (optional, default True) :param bool log: If True then log the request and response data to the debug log. (optional, default True) :param logger: The logger object to use to log request and responses. If not provided the keystoneauth1.session default logger will be used. :type logger: logging.Logger :param dict allow: Extra filters to pass when discovering API versions. (optional) :param microversion: Microversion to send for this request. microversion can be given as a string or a tuple. (optional) :param str microversion_service_type: The service_type to be sent in the microversion header, if a microversion is given. Defaults to the value of service_type from endpoint_filter if one exists. If endpoint_filter is not provided or does not have a service_type, microversion is given and microversion_service_type is not provided, an exception will be raised. :param kwargs: any other parameter that can be passed to :meth:`requests.Session.request` (such as `headers`). Except: - `data` will be overwritten by the data in the `json` param. - `allow_redirects` is ignored as redirects are handled by the session. :raises keystoneauth1.exceptions.base.ClientException: For connection failure, or to indicate an error response code. :returns: The response to the request. """ headers = kwargs.setdefault('headers', dict()) if microversion: self._set_microversion_headers(headers, microversion, microversion_service_type, endpoint_filter) if authenticated is None: authenticated = bool(auth or self.auth) if authenticated: auth_headers = self.get_auth_headers(auth) if auth_headers is None: msg = 'No valid authentication is available' raise exceptions.AuthorizationFailure(msg) headers.update(auth_headers) if osprofiler_web: headers.update(osprofiler_web.get_trace_id_headers()) # if we are passed a fully qualified URL and an endpoint_filter we # should ignore the filter. This will make it easier for clients who # want to overrule the default endpoint_filter data added to all client # requests. We check fully qualified here by the presence of a host. if not urllib.parse.urlparse(url).netloc: base_url = None if endpoint_override: base_url = endpoint_override % _StringFormatter(self, auth) elif endpoint_filter: base_url = self.get_endpoint(auth, allow=allow, **endpoint_filter) if not base_url: raise exceptions.EndpointNotFound() url = '%s/%s' % (base_url.rstrip('/'), url.lstrip('/')) if self.cert: kwargs.setdefault('cert', self.cert) if self.timeout is not None: kwargs.setdefault('timeout', self.timeout) if user_agent: headers['User-Agent'] = user_agent elif self.user_agent: user_agent = headers.setdefault('User-Agent', self.user_agent) else: # Per RFC 7231 Section 5.5.3, identifiers in a user-agent should be # ordered by decreasing significance. If a user sets their product # that value will be used. Otherwise we attempt to derive a useful # product value. The value will be prepended it to the KSA version, # requests version, and then the Python version. agent = [] if self.app_name and self.app_version: agent.append('%s/%s' % (self.app_name, self.app_version)) elif self.app_name: agent.append(self.app_name) for additional in self.additional_user_agent: agent.append('%s/%s' % additional) if client_name and client_version: agent.append('%s/%s' % (client_name, client_version)) elif client_name: agent.append(client_name) if not agent: # NOTE(jamielennox): determine_user_agent will return an empty # string on failure so checking for None will ensure it is only # called once even on failure. if self._determined_user_agent is None: self._determined_user_agent = _determine_user_agent() if self._determined_user_agent: agent.append(self._determined_user_agent) agent.append(DEFAULT_USER_AGENT) user_agent = headers.setdefault('User-Agent', ' '.join(agent)) if self.original_ip: headers.setdefault('Forwarded', 'for=%s;by=%s' % (self.original_ip, user_agent)) if json is not None: headers.setdefault('Content-Type', 'application/json') kwargs['data'] = self._json.encode(json) for k, v in self.additional_headers.items(): headers.setdefault(k, v) kwargs.setdefault('verify', self.verify) if requests_auth: kwargs['auth'] = requests_auth # Query parameters that are included in the url string will # be logged properly, but those sent in the `params` parameter # (which the requests library handles) need to be explicitly # picked out so they can be included in the URL that gets loggged. query_params = kwargs.get('params', dict()) if log: self._http_log_request(url, method=method, data=kwargs.get('data'), headers=headers, query_params=query_params, logger=logger) # Force disable requests redirect handling. We will manage this below. kwargs['allow_redirects'] = False if redirect is None: redirect = self.redirect send = functools.partial(self._send_request, url, method, redirect, log, logger, connect_retries) try: connection_params = self.get_auth_connection_params(auth=auth) except exceptions.MissingAuthPlugin: # NOTE(jamielennox): If we've gotten this far without an auth # plugin then we should be happy with allowing no additional # connection params. This will be the typical case for plugins # anyway. pass else: if connection_params: kwargs.update(connection_params) resp = send(**kwargs) # log callee and caller request-id for each api call if log: # service_name should be fetched from endpoint_filter if it is not # present then use service_type as service_name. service_name = None if endpoint_filter: service_name = endpoint_filter.get('service_name') if not service_name: service_name = endpoint_filter.get('service_type') # Nova uses 'x-compute-request-id' and other services like # Glance, Cinder etc are using 'x-openstack-request-id' to store # request-id in the header request_id = (resp.headers.get('x-openstack-request-id') or resp.headers.get('x-compute-request-id')) if request_id: logger.debug( '%(method)s call to %(service_name)s for ' '%(url)s used request id ' '%(response_request_id)s', { 'method': resp.request.method, 'service_name': service_name, 'url': resp.url, 'response_request_id': request_id }) # handle getting a 401 Unauthorized response by invalidating the plugin # and then retrying the request. This is only tried once. if resp.status_code == 401 and authenticated and allow_reauth: if self.invalidate(auth): auth_headers = self.get_auth_headers(auth) if auth_headers is not None: headers.update(auth_headers) resp = send(**kwargs) if raise_exc and resp.status_code >= 400: logger.debug('Request returned failure status: %s', resp.status_code) raise exceptions.from_response(resp, method, url) return resp
def get_auth_ref(self, session, **kwargs): # First do regular authorization auth_access = super(Agency, self).get_auth_ref(session, **kwargs) # And now reauth with another scope headers = { 'Accept': 'application/json', 'X-Auth-Token': auth_access._auth_token } body = {'auth': {'identity': {}}} ident = body['auth']['identity'] rkwargs = {} for method in self.auth_methods: name, auth_data = method.get_assume_role_auth_data( session, self, headers, request_kwargs=rkwargs) # NOTE(adriant): Methods like ReceiptMethod don't # want anything added to the request data, so they # explicitly return None, which we check for. if name: ident.setdefault('methods', []).append(name) ident[name] = auth_data if not ident: raise exceptions.AuthorizationFailure( 'Authentication method required (e.g. password)') if self.target_project_id: body['auth']['scope'] = {'project': {'id': self.target_project_id}} elif self.target_project_name: scope = body['auth']['scope'] = {'project': {}} scope['project']['name'] = self.target_project_name # If project is not set - get a domain scope elif self.target_domain_id: body['auth']['scope'] = {'domain': {'id': self.target_domain_id}} elif self.target_domain_name: body['auth']['scope'] = {'domain': {'name': self.target_domain_name}} token_url = self.token_url if not self.auth_url.rstrip('/').endswith('v3'): token_url = '%s/v3/auth/tokens' % self.auth_url.rstrip('/') if not self.include_catalog: token_url += '?nocatalog' _logger.debug('Making authentication request to %s', token_url) resp = session.post(token_url, json=body, headers=headers, authenticated=False, log=False, **rkwargs) try: _logger.debug(json.dumps(resp.json())) resp_data = resp.json() except ValueError: raise exceptions.InvalidResponse(response=resp) if 'token' not in resp_data: raise exceptions.InvalidResponse(response=resp) return access.AccessInfoV3(auth_token=resp.headers['X-Subject-Token'], body=resp_data)
def get_auth_ref(self, session, **kwargs): headers = {'Accept': 'application/json'} body = {'auth': {'identity': {}}} ident = body['auth']['identity'] rkwargs = {} for method in self.auth_methods: name, auth_data = method.get_auth_data(session, self, headers, request_kwargs=rkwargs) 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), bool(self.unscoped) ] if sum(mutual_exclusion) > 1: raise exceptions.AuthorizationFailure( 'Authentication cannot be scoped to multiple targets. Pick ' 'one of: project, domain, trust or unscoped') 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}} elif self.unscoped: body['auth']['scope'] = {'unscoped': {}} # 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 = session.post(token_url, json=body, headers=headers, authenticated=False, log=False, **rkwargs) try: _logger.debug(json.dumps(resp.json())) resp_data = resp.json() except ValueError: raise exceptions.InvalidResponse(response=resp) if 'token' not in resp_data: raise exceptions.InvalidResponse(response=resp) return access.AccessInfoV3(auth_token=resp.headers['X-Subject-Token'], body=resp_data)
def request(self, url, method, json=None, original_ip=None, user_agent=None, redirect=None, endpoint_filter=None, raise_exc=True, log=True, microversion=None, endpoint_override=None, connect_retries=0, allow=None, client_name=None, client_version=None, **kwargs): self._determined_user_agent = None headers = kwargs.setdefault('headers', dict()) auth_headers = self.get_auth_headers() if auth_headers is None: msg = 'No valid authentication is available' raise exceptions.AuthorizationFailure(msg) headers.update(auth_headers) base_url = "" if not urllib.parse.urlparse(url).netloc: if endpoint_override: base_url = endpoint_override # base_url = endpoint_override % {"project_id": self.project_id} elif endpoint_filter: base_url = self.get_endpoint( interface=endpoint_filter.interface, service_type=endpoint_filter.service_type) if not urllib.parse.urlparse(base_url).netloc: raise exceptions.EndpointNotFound() url = '%s/%s' % (base_url.rstrip('/'), url.lstrip('/')) headers.setdefault("Host", urllib.parse.urlparse(url).netloc) if self.cert: kwargs.setdefault('cert', self.cert) if self.timeout is not None: kwargs.setdefault('timeout', self.timeout) if user_agent: headers['User-Agent'] = user_agent elif self.user_agent: user_agent = headers.setdefault('User-Agent', self.user_agent) else: agent = [] if self.app_name and self.app_version: agent.append('%s/%s' % (self.app_name, self.app_version)) elif self.app_name: agent.append(self.app_name) if client_name and client_version: agent.append('%s/%s' % (client_name, client_version)) elif client_name: agent.append(client_name) for additional in self.additional_user_agent: agent.append('%s/%s' % additional) if not agent: # NOTE(jamielennox): determine_user_agent will return an empty # string on failure so checking for None will ensure it is only # called once even on failure. if self._determined_user_agent is None: self._determined_user_agent = _determine_user_agent() if self._determined_user_agent: agent.append(self._determined_user_agent) agent.append(DEFAULT_USER_AGENT) user_agent = headers.setdefault('User-Agent', ' '.join(agent)) if self.original_ip: headers.setdefault('Forwarded', 'for=%s;by=%s' % (self.original_ip, user_agent)) if json is not None: kwargs['data'] = self._json.encode(json) headers.setdefault('Content-Type', 'application/json') if self.additional_headers: for k, v in self.additional_headers.items(): headers.setdefault(k, v) kwargs.setdefault('verify', self.verify) # if requests_auth: # kwargs['auth'] = requests_auth # Query parameters that are included in the url string will # be logged properly, but those sent in the `params` parameter # (which the requests library handles) need to be explicitly # picked out so they can be included in the URL that gets loggged. query_params = kwargs.get('params', dict()) if log: self._http_log_request(url, method=method, data=kwargs.get('data'), headers=headers, query_params=query_params, logger=_logger) # Force disable requests redirect handling. We will manage this below. kwargs['allow_redirects'] = False if redirect is None: redirect = self.redirect send = functools.partial(self._send_request, url, method, redirect, log, _logger, connect_retries) resp = send(**kwargs) # log callee and caller request-id for each api call if log: # service_name should be fetched from endpoint_filter if it is not # present then use service_type as service_name. service_name = None if endpoint_filter: service_name = endpoint_filter.get('service_name') if not service_name: service_name = endpoint_filter.get('service_type') # Nova uses 'x-compute-request-id' and other services like # Glance, Cinder etc are using 'x-openstack-request-id' to store # request-id in the header request_id = (resp.headers.get('x-openstack-request-id') or resp.headers.get('x-compute-request-id')) if request_id: _logger.debug( '%(method)s call to %(service_name)s for ' '%(url)s used request id ' '%(response_request_id)s', { 'method': resp.request.method, 'service_name': service_name, 'url': resp.url, 'response_request_id': request_id }) if raise_exc and resp.status_code >= 400: _logger.debug('Request returned failure status: %s', resp.status_code) raise exceptions.from_response(resp, method, url) return resp
def get_auth_ref(self, session, **kwargs): headers = {'Accept': 'application/json'} body = {'auth': {'identity': {}}} ident = body['auth']['identity'] rkwargs = {} for method in self.auth_methods: name, auth_data = method.get_auth_data(session, self, headers, request_kwargs=rkwargs) 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), bool(self.unscoped) ] if sum(mutual_exclusion) > 1: raise exceptions.AuthorizationFailure( 'Authentication cannot be scoped to multiple targets. Pick ' 'one of: project, domain, trust or unscoped') 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}} elif self.unscoped: body['auth']['scope'] = 'unscoped' elif self.system_scope: # NOTE(lbragstad): Right now it's only possible to have role # assignments on the entire system. In the future that might change # so that users and groups can have roles on parts of the system, # like a specific service in a specific region. If that happens, # this will have to be accounted for here. Until then we'll only # support scoping to the entire system. if self.system_scope == 'all': body['auth']['scope'] = {'system': {'all': True}} # 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 = session.post(token_url, json=body, headers=headers, authenticated=False, log=False, **rkwargs) try: _logger.debug(json.dumps(resp.json())) resp_data = resp.json() except ValueError: raise exceptions.InvalidResponse(response=resp) if 'token' not in resp_data: raise exceptions.InvalidResponse(response=resp) return access.AccessInfoV3(auth_token=resp.headers['X-Subject-Token'], body=resp_data)
def test_using_default_message(self): exc = exceptions.AuthorizationFailure() self.assertEqual(exceptions.AuthorizationFailure.message, exc.message)