Exemplo n.º 1
0
    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)
Exemplo n.º 3
0
 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)
Exemplo n.º 4
0
    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)
Exemplo n.º 5
0
    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))
Exemplo n.º 6
0
    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)
Exemplo n.º 7
0
    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
Exemplo n.º 8
0
    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)
Exemplo n.º 9
0
    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)
Exemplo n.º 10
0
    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)
Exemplo n.º 11
0
    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)
Exemplo n.º 12
0
    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)
Exemplo n.º 13
0
    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
Exemplo n.º 14
0
    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)
Exemplo n.º 16
0
    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)
Exemplo n.º 17
0
    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)
Exemplo n.º 19
0
 def test_using_default_message(self):
     exc = exceptions.AuthorizationFailure()
     self.assertEqual(exceptions.AuthorizationFailure.message,
                      exc.message)