aboutsummaryrefslogtreecommitdiffstats
path: root/keystonemiddleware-moon/keystonemiddleware/auth_token
diff options
context:
space:
mode:
Diffstat (limited to 'keystonemiddleware-moon/keystonemiddleware/auth_token')
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/auth_token/__init__.py1129
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/auth_token/_auth.py194
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/auth_token/_base.py13
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/auth_token/_cache.py338
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/auth_token/_exceptions.py27
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/auth_token/_identity.py252
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/auth_token/_memcache_crypt.py210
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/auth_token/_memcache_pool.py184
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/auth_token/_request.py224
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/auth_token/_revocations.py128
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/auth_token/_signing_dir.py83
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/auth_token/_user_plugin.py193
-rw-r--r--keystonemiddleware-moon/keystonemiddleware/auth_token/_utils.py32
13 files changed, 0 insertions, 3007 deletions
diff --git a/keystonemiddleware-moon/keystonemiddleware/auth_token/__init__.py b/keystonemiddleware-moon/keystonemiddleware/auth_token/__init__.py
deleted file mode 100644
index be268da3..00000000
--- a/keystonemiddleware-moon/keystonemiddleware/auth_token/__init__.py
+++ /dev/null
@@ -1,1129 +0,0 @@
-# Copyright 2010-2012 OpenStack Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Token-based Authentication Middleware
-
-This WSGI component:
-
-* Verifies that incoming client requests have valid tokens by validating
- tokens with the auth service.
-* Rejects unauthenticated requests unless the auth_token middleware is in
- ``delay_auth_decision`` mode, which means the final decision is delegated to
- the downstream WSGI component (usually the OpenStack service).
-* Collects and forwards identity information based on a valid token
- such as user name, domain, project, etc.
-
-Refer to: http://docs.openstack.org/developer/keystonemiddleware/\
-middlewarearchitecture.html
-
-
-Headers
--------
-
-The auth_token middleware uses headers sent in by the client on the request
-and sets headers and environment variables for the downstream WSGI component.
-
-Coming in from initial call from client or customer
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-HTTP_X_AUTH_TOKEN
- The client token being passed in.
-
-HTTP_X_SERVICE_TOKEN
- A service token being passed in.
-
-Used for communication between components
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-WWW-Authenticate
- HTTP header returned to a user indicating which endpoint to use
- to retrieve a new token.
-
-What auth_token adds to the request for use by the OpenStack service
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-When using composite authentication (a user and service token are
-present) additional service headers relating to the service user
-will be added. They take the same form as the standard headers but add
-``_SERVICE_``. These headers will not exist in the environment if no
-service token is present.
-
-HTTP_X_IDENTITY_STATUS, HTTP_X_SERVICE_IDENTITY_STATUS
- Will be set to either ``Confirmed`` or ``Invalid``.
-
- The underlying service will only see a value of 'Invalid' if the middleware
- is configured to run in ``delay_auth_decision`` mode. As with all such
- headers, ``HTTP_X_SERVICE_IDENTITY_STATUS`` will only exist in the
- environment if a service token is presented. This is different than
- ``HTTP_X_IDENTITY_STATUS`` which is always set even if no user token is
- presented. This allows the underlying service to determine if a
- denial should use ``401 Unauthenticated`` or ``403 Forbidden``.
-
-HTTP_X_DOMAIN_ID, HTTP_X_SERVICE_DOMAIN_ID
- Identity service managed unique identifier, string. Only present if
- this is a domain-scoped token.
-
-HTTP_X_DOMAIN_NAME, HTTP_X_SERVICE_DOMAIN_NAME
- Unique domain name, string. Only present if this is a domain-scoped
- token.
-
-HTTP_X_PROJECT_ID, HTTP_X_SERVICE_PROJECT_ID
- Identity service managed unique identifier, string. Only present if
- this is a project-scoped token.
-
-HTTP_X_PROJECT_NAME, HTTP_X_SERVICE_PROJECT_NAME
- Project name, unique within owning domain, string. Only present if
- this is a project-scoped token.
-
-HTTP_X_PROJECT_DOMAIN_ID, HTTP_X_SERVICE_PROJECT_DOMAIN_ID
- Identity service managed unique identifier of owning domain of
- project, string. Only present if this is a project-scoped v3 token. If
- this variable is set, this indicates that the PROJECT_NAME can only
- be assumed to be unique within this domain.
-
-HTTP_X_PROJECT_DOMAIN_NAME, HTTP_X_SERVICE_PROJECT_DOMAIN_NAME
- Name of owning domain of project, string. Only present if this is a
- project-scoped v3 token. If this variable is set, this indicates that
- the PROJECT_NAME can only be assumed to be unique within this domain.
-
-HTTP_X_USER_ID, HTTP_X_SERVICE_USER_ID
- Identity-service managed unique identifier, string.
-
-HTTP_X_USER_NAME, HTTP_X_SERVICE_USER_NAME
- User identifier, unique within owning domain, string.
-
-HTTP_X_USER_DOMAIN_ID, HTTP_X_SERVICE_USER_DOMAIN_ID
- Identity service managed unique identifier of owning domain of
- user, string. If this variable is set, this indicates that the USER_NAME
- can only be assumed to be unique within this domain.
-
-HTTP_X_USER_DOMAIN_NAME, HTTP_X_SERVICE_USER_DOMAIN_NAME
- Name of owning domain of user, string. If this variable is set, this
- indicates that the USER_NAME can only be assumed to be unique within
- this domain.
-
-HTTP_X_ROLES, HTTP_X_SERVICE_ROLES
- Comma delimited list of case-sensitive role names.
-
-HTTP_X_SERVICE_CATALOG
- service catalog (optional, JSON string).
-
- For compatibility reasons this catalog will always be in the V2 catalog
- format even if it is a v3 token.
-
- .. note:: This is an exception in that it contains 'SERVICE' but relates to
- a user token, not a service token. The existing user's catalog can be
- very large; it was decided not to present a catalog relating to the
- service token to avoid using more HTTP header space.
-
-HTTP_X_TENANT_ID
- *Deprecated* in favor of HTTP_X_PROJECT_ID.
-
- Identity service managed unique identifier, string. For v3 tokens, this
- will be set to the same value as HTTP_X_PROJECT_ID.
-
-HTTP_X_TENANT_NAME
- *Deprecated* in favor of HTTP_X_PROJECT_NAME.
-
- Project identifier, unique within owning domain, string. For v3 tokens,
- this will be set to the same value as HTTP_X_PROJECT_NAME.
-
-HTTP_X_TENANT
- *Deprecated* in favor of HTTP_X_TENANT_ID and HTTP_X_TENANT_NAME.
-
- Identity server-assigned unique identifier, string. For v3 tokens, this
- will be set to the same value as HTTP_X_PROJECT_ID.
-
-HTTP_X_USER
- *Deprecated* in favor of HTTP_X_USER_ID and HTTP_X_USER_NAME.
-
- User name, unique within owning domain, string.
-
-HTTP_X_ROLE
- *Deprecated* in favor of HTTP_X_ROLES.
-
- Will contain the same values as HTTP_X_ROLES.
-
-Environment Variables
-^^^^^^^^^^^^^^^^^^^^^
-
-These variables are set in the request environment for use by the downstream
-WSGI component.
-
-keystone.token_info
- Information about the token discovered in the process of validation. This
- may include extended information returned by the token validation call, as
- well as basic information about the project and user.
-
-keystone.token_auth
- A keystoneclient auth plugin that may be used with a
- :py:class:`keystoneclient.session.Session`. This plugin will load the
- authentication data provided to auth_token middleware.
-
-
-Configuration
--------------
-
-auth_token middleware configuration can be in the main application's
-configuration file, e.g. in ``nova.conf``:
-
-.. code-block:: ini
-
- [keystone_authtoken]
- auth_plugin = password
- auth_url = http://keystone:35357/
- username = nova
- user_domain_id = default
- password = whyarewestillusingpasswords
- project_name = service
- project_domain_id = default
-
-Configuration can also be in the ``api-paste.ini`` file with the same options,
-but this is discouraged.
-
-Swift
------
-
-When deploy auth_token middleware with Swift, user may elect to use Swift
-memcache instead of the local auth_token memcache. Swift memcache is passed in
-from the request environment and it's identified by the ``swift.cache`` key.
-However it could be different, depending on deployment. To use Swift memcache,
-you must set the ``cache`` option to the environment key where the Swift cache
-object is stored.
-
-"""
-
-import binascii
-import datetime
-import logging
-
-from keystoneclient import access
-from keystoneclient import adapter
-from keystoneclient import auth
-from keystoneclient.common import cms
-from keystoneclient import discover
-from keystoneclient import exceptions
-from keystoneclient import session
-from oslo_config import cfg
-from oslo_serialization import jsonutils
-import pkg_resources
-import six
-import webob.dec
-
-from keystonemiddleware.auth_token import _auth
-from keystonemiddleware.auth_token import _base
-from keystonemiddleware.auth_token import _cache
-from keystonemiddleware.auth_token import _exceptions as exc
-from keystonemiddleware.auth_token import _identity
-from keystonemiddleware.auth_token import _request
-from keystonemiddleware.auth_token import _revocations
-from keystonemiddleware.auth_token import _signing_dir
-from keystonemiddleware.auth_token import _user_plugin
-from keystonemiddleware.i18n import _, _LC, _LE, _LI, _LW
-
-
-# NOTE(jamielennox): A number of options below are deprecated however are left
-# in the list and only mentioned as deprecated in the help string. This is
-# because we have to provide the same deprecation functionality for arguments
-# passed in via the conf in __init__ (from paste) and there is no way to test
-# that the default value was set or not in CONF.
-# Also if we were to remove the options from the CONF list (as typical CONF
-# deprecation works) then other projects will not be able to override the
-# options via CONF.
-
-_OPTS = [
- cfg.StrOpt('auth_uri',
- default=None,
- # FIXME(dolph): should be default='http://127.0.0.1:5000/v2.0/',
- # or (depending on client support) an unversioned, publicly
- # accessible identity endpoint (see bug 1207517)
- help='Complete public Identity API endpoint.'),
- cfg.StrOpt('auth_version',
- default=None,
- help='API version of the admin Identity API endpoint.'),
- cfg.BoolOpt('delay_auth_decision',
- default=False,
- help='Do not handle authorization requests within the'
- ' middleware, but delegate the authorization decision to'
- ' downstream WSGI components.'),
- cfg.IntOpt('http_connect_timeout',
- default=None,
- help='Request timeout value for communicating with Identity'
- ' API server.'),
- cfg.IntOpt('http_request_max_retries',
- default=3,
- help='How many times are we trying to reconnect when'
- ' communicating with Identity API Server.'),
- cfg.StrOpt('cache',
- default=None,
- help='Env key for the swift cache.'),
- cfg.StrOpt('certfile',
- help='Required if identity server requires client certificate'),
- cfg.StrOpt('keyfile',
- help='Required if identity server requires client certificate'),
- cfg.StrOpt('cafile', default=None,
- help='A PEM encoded Certificate Authority to use when '
- 'verifying HTTPs connections. Defaults to system CAs.'),
- cfg.BoolOpt('insecure', default=False, help='Verify HTTPS connections.'),
- cfg.StrOpt('region_name', default=None,
- help='The region in which the identity server can be found.'),
- cfg.StrOpt('signing_dir',
- help='Directory used to cache files related to PKI tokens.'),
- cfg.ListOpt('memcached_servers',
- deprecated_name='memcache_servers',
- help='Optionally specify a list of memcached server(s) to'
- ' use for caching. If left undefined, tokens will instead be'
- ' cached in-process.'),
- cfg.IntOpt('token_cache_time',
- default=300,
- help='In order to prevent excessive effort spent validating'
- ' tokens, the middleware caches previously-seen tokens for a'
- ' configurable duration (in seconds). Set to -1 to disable'
- ' caching completely.'),
- cfg.IntOpt('revocation_cache_time',
- default=10,
- help='Determines the frequency at which the list of revoked'
- ' tokens is retrieved from the Identity service (in seconds). A'
- ' high number of revocation events combined with a low cache'
- ' duration may significantly reduce performance.'),
- cfg.StrOpt('memcache_security_strategy',
- default=None,
- help='(Optional) If defined, indicate whether token data'
- ' should be authenticated or authenticated and encrypted.'
- ' Acceptable values are MAC or ENCRYPT. If MAC, token data is'
- ' authenticated (with HMAC) in the cache. If ENCRYPT, token'
- ' data is encrypted and authenticated in the cache. If the'
- ' value is not one of these options or empty, auth_token will'
- ' raise an exception on initialization.'),
- cfg.StrOpt('memcache_secret_key',
- default=None,
- secret=True,
- help='(Optional, mandatory if memcache_security_strategy is'
- ' defined) This string is used for key derivation.'),
- cfg.IntOpt('memcache_pool_dead_retry',
- default=5 * 60,
- help='(Optional) Number of seconds memcached server is'
- ' considered dead before it is tried again.'),
- cfg.IntOpt('memcache_pool_maxsize',
- default=10,
- help='(Optional) Maximum total number of open connections to'
- ' every memcached server.'),
- cfg.IntOpt('memcache_pool_socket_timeout',
- default=3,
- help='(Optional) Socket timeout in seconds for communicating '
- 'with a memcached server.'),
- cfg.IntOpt('memcache_pool_unused_timeout',
- default=60,
- help='(Optional) Number of seconds a connection to memcached'
- ' is held unused in the pool before it is closed.'),
- cfg.IntOpt('memcache_pool_conn_get_timeout',
- default=10,
- help='(Optional) Number of seconds that an operation will wait '
- 'to get a memcached client connection from the pool.'),
- cfg.BoolOpt('memcache_use_advanced_pool',
- default=False,
- help='(Optional) Use the advanced (eventlet safe) memcached '
- 'client pool. The advanced pool will only work under '
- 'python 2.x.'),
- cfg.BoolOpt('include_service_catalog',
- default=True,
- help='(Optional) Indicate whether to set the X-Service-Catalog'
- ' header. If False, middleware will not ask for service'
- ' catalog on token validation and will not set the'
- ' X-Service-Catalog header.'),
- cfg.StrOpt('enforce_token_bind',
- default='permissive',
- help='Used to control the use and type of token binding. Can'
- ' be set to: "disabled" to not check token binding.'
- ' "permissive" (default) to validate binding information if the'
- ' bind type is of a form known to the server and ignore it if'
- ' not. "strict" like "permissive" but if the bind type is'
- ' unknown the token will be rejected. "required" any form of'
- ' token binding is needed to be allowed. Finally the name of a'
- ' binding method that must be present in tokens.'),
- cfg.BoolOpt('check_revocations_for_cached', default=False,
- help='If true, the revocation list will be checked for cached'
- ' tokens. This requires that PKI tokens are configured on the'
- ' identity server.'),
- cfg.ListOpt('hash_algorithms', default=['md5'],
- help='Hash algorithms to use for hashing PKI tokens. This may'
- ' be a single algorithm or multiple. The algorithms are those'
- ' supported by Python standard hashlib.new(). The hashes will'
- ' be tried in the order given, so put the preferred one first'
- ' for performance. The result of the first hash will be stored'
- ' in the cache. This will typically be set to multiple values'
- ' only while migrating from a less secure algorithm to a more'
- ' secure one. Once all the old tokens are expired this option'
- ' should be set to a single value for better performance.'),
-]
-
-CONF = cfg.CONF
-CONF.register_opts(_OPTS, group=_base.AUTHTOKEN_GROUP)
-
-_LOG = logging.getLogger(__name__)
-
-
-class _BIND_MODE(object):
- DISABLED = 'disabled'
- PERMISSIVE = 'permissive'
- STRICT = 'strict'
- REQUIRED = 'required'
- KERBEROS = 'kerberos'
-
-
-def _token_is_v2(token_info):
- return ('access' in token_info)
-
-
-def _token_is_v3(token_info):
- return ('token' in token_info)
-
-
-def _conf_values_type_convert(conf):
- """Convert conf values into correct type."""
- if not conf:
- return {}
-
- opt_types = {}
- for o in (_OPTS + _auth.AuthTokenPlugin.get_options()):
- type_dest = (getattr(o, 'type', str), o.dest)
- opt_types[o.dest] = type_dest
- # Also add the deprecated name with the same type and dest.
- for d_o in o.deprecated_opts:
- opt_types[d_o.name] = type_dest
-
- opts = {}
- for k, v in six.iteritems(conf):
- dest = k
- try:
- if v is not None:
- type_, dest = opt_types[k]
- v = type_(v)
- except KeyError:
- # This option is not known to auth_token.
- pass
- except ValueError as e:
- raise exc.ConfigurationError(
- _('Unable to convert the value of %(key)s option into correct '
- 'type: %(ex)s') % {'key': k, 'ex': e})
- opts[dest] = v
- return opts
-
-
-def _get_project_version(project):
- return pkg_resources.get_distribution(project).version
-
-
-class _BaseAuthProtocol(object):
- """A base class for AuthProtocol token checking implementations.
-
- :param Callable app: The next application to call after middleware.
- :param logging.Logger log: The logging object to use for output. By default
- it will use a logger in the
- keystonemiddleware.auth_token namespace.
- :param str enforce_token_bind: The style of token binding enforcement to
- perform.
- """
-
- def __init__(self,
- app,
- log=_LOG,
- enforce_token_bind=_BIND_MODE.PERMISSIVE):
- self.log = log
- self._app = app
- self._enforce_token_bind = enforce_token_bind
-
- @webob.dec.wsgify(RequestClass=_request._AuthTokenRequest)
- def __call__(self, req):
- """Handle incoming request."""
- response = self.process_request(req)
- if response:
- return response
- response = req.get_response(self._app)
- return self.process_response(response)
-
- def process_request(self, request):
- """Process request.
-
- If this method returns a value then that value will be used as the
- response. The next application down the stack will not be executed and
- process_response will not be called.
-
- Otherwise, the next application down the stack will be executed and
- process_response will be called with the generated response.
-
- By default this method does not return a value.
-
- :param request: Incoming request
- :type request: _request.AuthTokenRequest
-
- """
- request.remove_auth_headers()
-
- user_auth_ref = None
- serv_auth_ref = None
-
- if request.user_token:
- self.log.debug('Authenticating user token')
- try:
- data, user_auth_ref = self._do_fetch_token(request.user_token)
- self._validate_token(user_auth_ref)
- self._confirm_token_bind(user_auth_ref, request)
- except exc.InvalidToken:
- self.log.info(_LI('Invalid user token'))
- request.user_token_valid = False
- else:
- request.user_token_valid = True
- request.environ['keystone.token_info'] = data
-
- if request.service_token:
- self.log.debug('Authenticating service token')
- try:
- _, serv_auth_ref = self._do_fetch_token(request.service_token)
- self._validate_token(serv_auth_ref)
- self._confirm_token_bind(serv_auth_ref, request)
- except exc.InvalidToken:
- self.log.info(_LI('Invalid service token'))
- request.service_token_valid = False
- else:
- request.service_token_valid = True
-
- p = _user_plugin.UserAuthPlugin(user_auth_ref, serv_auth_ref)
- request.environ['keystone.token_auth'] = p
-
- def _validate_token(self, auth_ref):
- """Perform the validation steps on the token.
-
- :param auth_ref: The token data
- :type auth_ref: keystoneclient.access.AccessInfo
-
- :raises exc.InvalidToken: if token is rejected
- """
- # 0 seconds of validity means is it valid right now.
- if auth_ref.will_expire_soon(stale_duration=0):
- raise exc.InvalidToken(_('Token authorization failed'))
-
- def _do_fetch_token(self, token):
- """Helper method to fetch a token and convert it into an AccessInfo"""
- data = self._fetch_token(token)
-
- try:
- return data, access.AccessInfo.factory(body=data, auth_token=token)
- except Exception:
- self.log.warning(_LW('Invalid token contents.'), exc_info=True)
- raise exc.InvalidToken(_('Token authorization failed'))
-
- def _fetch_token(self, token):
- """Fetch the token data based on the value in the header.
-
- Retrieve the data associated with the token value that was in the
- header. This can be from PKI, contacting the identity server or
- whatever is required.
-
- :param str token: The token present in the request header.
-
- :raises exc.InvalidToken: if token is invalid.
-
- :returns: The token data
- :rtype: dict
- """
- raise NotImplemented()
-
- def process_response(self, response):
- """Do whatever you'd like to the response.
-
- By default the response is returned unmodified.
-
- :param response: Response object
- :type response: ._request._AuthTokenResponse
- """
- return response
-
- def _invalid_user_token(self, msg=False):
- # NOTE(jamielennox): use False as the default so that None is valid
- if msg is False:
- msg = _('Token authorization failed')
-
- raise exc.InvalidToken(msg)
-
- def _confirm_token_bind(self, auth_ref, req):
- if self._enforce_token_bind == _BIND_MODE.DISABLED:
- return
-
- try:
- if auth_ref.version == 'v2.0':
- bind = auth_ref['token']['bind']
- elif auth_ref.version == 'v3':
- bind = auth_ref['bind']
- else:
- self._invalid_user_token()
- except KeyError:
- bind = {}
-
- # permissive and strict modes don't require there to be a bind
- permissive = self._enforce_token_bind in (_BIND_MODE.PERMISSIVE,
- _BIND_MODE.STRICT)
-
- if not bind:
- if permissive:
- # no bind provided and none required
- return
- else:
- self.log.info(_LI('No bind information present in token.'))
- self._invalid_user_token()
-
- # get the named mode if bind_mode is not one of the predefined
- if permissive or self._enforce_token_bind == _BIND_MODE.REQUIRED:
- name = None
- else:
- name = self._enforce_token_bind
-
- if name and name not in bind:
- self.log.info(_LI('Named bind mode %s not in bind information'),
- name)
- self._invalid_user_token()
-
- for bind_type, identifier in six.iteritems(bind):
- if bind_type == _BIND_MODE.KERBEROS:
- if req.auth_type != 'negotiate':
- self.log.info(_LI('Kerberos credentials required and '
- 'not present.'))
- self._invalid_user_token()
-
- if req.remote_user != identifier:
- self.log.info(_LI('Kerberos credentials do not match '
- 'those in bind.'))
- self._invalid_user_token()
-
- self.log.debug('Kerberos bind authentication successful.')
-
- elif self._enforce_token_bind == _BIND_MODE.PERMISSIVE:
- self.log.debug('Ignoring Unknown bind for permissive mode: '
- '%(bind_type)s: %(identifier)s.',
- {'bind_type': bind_type,
- 'identifier': identifier})
-
- else:
- self.log.info(
- _LI('Couldn`t verify unknown bind: %(bind_type)s: '
- '%(identifier)s.'),
- {'bind_type': bind_type, 'identifier': identifier})
- self._invalid_user_token()
-
-
-class AuthProtocol(_BaseAuthProtocol):
- """Middleware that handles authenticating client calls."""
-
- _SIGNING_CERT_FILE_NAME = 'signing_cert.pem'
- _SIGNING_CA_FILE_NAME = 'cacert.pem'
-
- def __init__(self, app, conf):
- log = logging.getLogger(conf.get('log_name', __name__))
- log.info(_LI('Starting Keystone auth_token middleware'))
-
- # NOTE(wanghong): If options are set in paste file, all the option
- # values passed into conf are string type. So, we should convert the
- # conf value into correct type.
- self._conf = _conf_values_type_convert(conf)
-
- # NOTE(sileht): If we don't want to use oslo.config global object
- # we can set the paste "oslo_config_project" and the middleware
- # will load the configuration with a local oslo.config object.
- self._local_oslo_config = None
- if 'oslo_config_project' in conf:
- if 'oslo_config_file' in conf:
- default_config_files = [conf['oslo_config_file']]
- else:
- default_config_files = None
-
- # For unit tests, support passing in a ConfigOpts in
- # oslo_config_config.
- self._local_oslo_config = conf.get('oslo_config_config',
- cfg.ConfigOpts())
- self._local_oslo_config(
- {}, project=conf['oslo_config_project'],
- default_config_files=default_config_files,
- validate_default_values=True)
-
- self._local_oslo_config.register_opts(
- _OPTS, group=_base.AUTHTOKEN_GROUP)
- auth.register_conf_options(self._local_oslo_config,
- group=_base.AUTHTOKEN_GROUP)
-
- super(AuthProtocol, self).__init__(
- app,
- log=log,
- enforce_token_bind=self._conf_get('enforce_token_bind'))
-
- # delay_auth_decision means we still allow unauthenticated requests
- # through and we let the downstream service make the final decision
- self._delay_auth_decision = self._conf_get('delay_auth_decision')
- self._include_service_catalog = self._conf_get(
- 'include_service_catalog')
- self._hash_algorithms = self._conf_get('hash_algorithms')
-
- self._identity_server = self._create_identity_server()
-
- self._auth_uri = self._conf_get('auth_uri')
- if not self._auth_uri:
- self.log.warning(
- _LW('Configuring auth_uri to point to the public identity '
- 'endpoint is required; clients may not be able to '
- 'authenticate against an admin endpoint'))
-
- # FIXME(dolph): drop support for this fallback behavior as
- # documented in bug 1207517.
-
- self._auth_uri = self._identity_server.auth_uri
-
- self._signing_directory = _signing_dir.SigningDirectory(
- directory_name=self._conf_get('signing_dir'), log=self.log)
-
- self._token_cache = self._token_cache_factory()
-
- revocation_cache_timeout = datetime.timedelta(
- seconds=self._conf_get('revocation_cache_time'))
- self._revocations = _revocations.Revocations(revocation_cache_timeout,
- self._signing_directory,
- self._identity_server,
- self._cms_verify,
- self.log)
-
- self._check_revocations_for_cached = self._conf_get(
- 'check_revocations_for_cached')
-
- def _conf_get(self, name, group=_base.AUTHTOKEN_GROUP):
- # try config from paste-deploy first
- if name in self._conf:
- return self._conf[name]
- elif self._local_oslo_config:
- return self._local_oslo_config[group][name]
- else:
- return CONF[group][name]
-
- def process_request(self, request):
- """Process request.
-
- Evaluate the headers in a request and attempt to authenticate the
- request. If authenticated then additional headers are added to the
- request for use by applications. If not authenticated the request will
- be rejected or marked unauthenticated depending on configuration.
- """
- self._token_cache.initialize(request.environ)
-
- resp = super(AuthProtocol, self).process_request(request)
- if resp:
- return resp
-
- if not request.user_token:
- # if no user token is present then that's an invalid request
- request.user_token_valid = False
-
- # NOTE(jamielennox): The service status is allowed to be missing if a
- # service token is not passed. If the service status is missing that's
- # a valid request. We should find a better way to expose this from the
- # request object.
- user_status = request.user_token and request.user_token_valid
- service_status = request.headers.get('X-Service-Identity-Status',
- 'Confirmed')
-
- if not (user_status and service_status == 'Confirmed'):
- if self._delay_auth_decision:
- self.log.info(_LI('Deferring reject downstream'))
- else:
- self.log.info(_LI('Rejecting request'))
- self._reject_request()
-
- if request.user_token_valid:
- request.set_user_headers(request.token_auth._user_auth_ref,
- self._include_service_catalog)
-
- if request.service_token and request.service_token_valid:
- request.set_service_headers(request.token_auth._serv_auth_ref)
-
- if self.log.isEnabledFor(logging.DEBUG):
- self.log.debug('Received request from %s',
- request.token_auth._log_format)
-
- def process_response(self, response):
- """Process Response.
-
- Add ``WWW-Authenticate`` headers to requests that failed with
- ``401 Unauthenticated`` so users know where to authenticate for future
- requests.
- """
- if response.status_int == 401:
- response.headers.extend(self._reject_auth_headers)
-
- return response
-
- @property
- def _reject_auth_headers(self):
- header_val = 'Keystone uri=\'%s\'' % self._auth_uri
- return [('WWW-Authenticate', header_val)]
-
- def _reject_request(self):
- """Redirect client to auth server.
-
- :param env: wsgi request environment
- :param start_response: wsgi response callback
- :returns: HTTPUnauthorized http response
-
- """
- raise webob.exc.HTTPUnauthorized(body='Authentication required',
- headers=self._reject_auth_headers)
-
- def _token_hashes(self, token):
- """Generate a list of hashes that the current token may be cached as.
-
- With PKI tokens we have multiple hashing algorithms that we test with
- revocations. This generates that whole list.
-
- The first element of this list is the preferred algorithm and is what
- new cache values should be saved as.
-
- :param str token: The token being presented by a user.
-
- :returns: list of str token hashes.
- """
- if cms.is_asn1_token(token) or cms.is_pkiz(token):
- return list(cms.cms_hash_token(token, mode=algo)
- for algo in self._hash_algorithms)
- else:
- return [token]
-
- def _cache_get_hashes(self, token_hashes):
- """Check if the token is cached already.
-
- Functions takes a list of hashes that might be in the cache and matches
- the first one that is present. If nothing is found in the cache it
- returns None.
-
- :returns: token data if found else None.
- """
-
- for token in token_hashes:
- cached = self._token_cache.get(token)
-
- if cached:
- return cached
-
- def _fetch_token(self, token):
- """Retrieve a token from either a PKI bundle or the identity server.
-
- :param str token: token id
-
- :raises exc.InvalidToken: if token is rejected
- """
- data = None
- token_hashes = None
-
- try:
- token_hashes = self._token_hashes(token)
- cached = self._cache_get_hashes(token_hashes)
-
- if cached:
- data = cached
-
- if self._check_revocations_for_cached:
- # A token stored in Memcached might have been revoked
- # regardless of initial mechanism used to validate it,
- # and needs to be checked.
- self._revocations.check(token_hashes)
- else:
- data = self._validate_offline(token, token_hashes)
- if not data:
- data = self._identity_server.verify_token(token)
-
- self._token_cache.store(token_hashes[0], data)
-
- except (exceptions.ConnectionRefused, exceptions.RequestTimeout,
- exc.RevocationListError, exc.ServiceError) as e:
- self.log.critical(_LC('Unable to validate token: %s'), e)
- raise webob.exc.HTTPServiceUnavailable()
- except exc.InvalidToken:
- self.log.debug('Token validation failure.', exc_info=True)
- if token_hashes:
- self._token_cache.store_invalid(token_hashes[0])
- self.log.warning(_LW('Authorization failed for token'))
- raise
- except Exception:
- self.log.critical(_LC('Unable to validate token'), exc_info=True)
- raise webob.exc.HTTPInternalServerError()
-
- return data
-
- def _validate_offline(self, token, token_hashes):
- try:
- if cms.is_pkiz(token):
- verified = self._verify_pkiz_token(token, token_hashes)
- elif cms.is_asn1_token(token):
- verified = self._verify_signed_token(token, token_hashes)
- else:
- # Can't do offline validation for this type of token.
- return
- except exceptions.CertificateConfigError:
- self.log.warning(_LW('Fetch certificate config failed, '
- 'fallback to online validation.'))
- except exc.RevocationListError:
- self.log.warning(_LW('Fetch revocation list failed, '
- 'fallback to online validation.'))
- else:
- data = jsonutils.loads(verified)
-
- audit_ids = None
- if 'access' in data:
- # It's a v2 token.
- audit_ids = data['access']['token'].get('audit_ids')
- else:
- # It's a v3 token
- audit_ids = data['token'].get('audit_ids')
-
- if audit_ids:
- self._revocations.check_by_audit_id(audit_ids)
-
- return data
-
- def _validate_token(self, auth_ref):
- super(AuthProtocol, self)._validate_token(auth_ref)
-
- if auth_ref.version == 'v2.0' and not auth_ref.project_id:
- msg = _('Unable to determine service tenancy.')
- raise exc.InvalidToken(msg)
-
- def _cms_verify(self, data, inform=cms.PKI_ASN1_FORM):
- """Verifies the signature of the provided data's IAW CMS syntax.
-
- If either of the certificate files might be missing, fetch them and
- retry.
- """
- def verify():
- try:
- signing_cert_path = self._signing_directory.calc_path(
- self._SIGNING_CERT_FILE_NAME)
- signing_ca_path = self._signing_directory.calc_path(
- self._SIGNING_CA_FILE_NAME)
- return cms.cms_verify(data, signing_cert_path,
- signing_ca_path,
- inform=inform).decode('utf-8')
- except (exceptions.CMSError,
- cms.subprocess.CalledProcessError) as err:
- self.log.warning(_LW('Verify error: %s'), err)
- raise exc.InvalidToken(_('Token authorization failed'))
-
- try:
- return verify()
- except exceptions.CertificateConfigError:
- # the certs might be missing; unconditionally fetch to avoid racing
- self._fetch_signing_cert()
- self._fetch_ca_cert()
-
- try:
- # retry with certs in place
- return verify()
- except exceptions.CertificateConfigError as err:
- # if this is still occurring, something else is wrong and we
- # need err.output to identify the problem
- self.log.error(_LE('CMS Verify output: %s'), err.output)
- raise
-
- def _verify_signed_token(self, signed_text, token_ids):
- """Check that the token is unrevoked and has a valid signature."""
- self._revocations.check(token_ids)
- formatted = cms.token_to_cms(signed_text)
- verified = self._cms_verify(formatted)
- return verified
-
- def _verify_pkiz_token(self, signed_text, token_ids):
- self._revocations.check(token_ids)
- try:
- uncompressed = cms.pkiz_uncompress(signed_text)
- verified = self._cms_verify(uncompressed, inform=cms.PKIZ_CMS_FORM)
- return verified
- # TypeError If the signed_text is not zlib compressed
- # binascii.Error if signed_text has incorrect base64 padding (py34)
- except (TypeError, binascii.Error):
- raise exc.InvalidToken(signed_text)
-
- def _fetch_signing_cert(self):
- self._signing_directory.write_file(
- self._SIGNING_CERT_FILE_NAME,
- self._identity_server.fetch_signing_cert())
-
- def _fetch_ca_cert(self):
- self._signing_directory.write_file(
- self._SIGNING_CA_FILE_NAME,
- self._identity_server.fetch_ca_cert())
-
- def _get_auth_plugin(self):
- # NOTE(jamielennox): Ideally this would use get_from_conf_options
- # however that is not possible because we have to support the override
- # pattern we use in _conf_get. There is a somewhat replacement for this
- # in keystoneclient in load_from_options_getter which should be used
- # when available. Until then this is essentially a copy and paste of
- # the ksc load_from_conf_options code because we need to get a fix out
- # for this quickly.
-
- # FIXME(jamielennox): update to use load_from_options_getter when
- # https://review.openstack.org/162529 merges.
-
- # !!! - UNDER NO CIRCUMSTANCES COPY ANY OF THIS CODE - !!!
-
- group = self._conf_get('auth_section') or _base.AUTHTOKEN_GROUP
- plugin_name = self._conf_get('auth_plugin', group=group)
- plugin_kwargs = dict()
-
- if plugin_name:
- plugin_class = auth.get_plugin_class(plugin_name)
- else:
- plugin_class = _auth.AuthTokenPlugin
- # logger object is a required parameter of the default plugin
- plugin_kwargs['log'] = self.log
-
- plugin_opts = plugin_class.get_options()
- (self._local_oslo_config or CONF).register_opts(plugin_opts,
- group=group)
-
- for opt in plugin_opts:
- val = self._conf_get(opt.dest, group=group)
- if val is not None:
- val = opt.type(val)
- plugin_kwargs[opt.dest] = val
-
- return plugin_class.load_from_options(**plugin_kwargs)
-
- def _determine_project(self):
- """Determine a project name from all available config sources.
-
- The sources are checked in the following order:
-
- 1. The paste-deploy config for auth_token middleware
- 2. The keystone_authtoken in the project's config
- 3. The oslo.config CONF.project property
-
- """
- try:
- return self._conf_get('project')
- except cfg.NoSuchOptError:
- # Prefer local oslo config object
- if self._local_oslo_config:
- return self._local_oslo_config.project
- try:
- # CONF.project will exist only if the service uses
- # oslo.config. It will only be set when the project
- # calls CONF(...) and when not set oslo.config oddly
- # raises a NoSuchOptError exception.
- return CONF.project
- except cfg.NoSuchOptError:
- return ''
-
- def _build_useragent_string(self):
- project = self._determine_project()
- if project:
- project_version = _get_project_version(project)
- project = '{project}/{project_version} '.format(
- project=project,
- project_version=project_version)
-
- ua_template = ('{project}'
- 'keystonemiddleware.auth_token/{ksm_version}')
- return ua_template.format(
- project=project,
- ksm_version=_get_project_version('keystonemiddleware'))
-
- def _create_identity_server(self):
- # NOTE(jamielennox): Loading Session here should be exactly the
- # same as calling Session.load_from_conf_options(CONF, GROUP)
- # however we can't do that because we have to use _conf_get to
- # support the paste.ini options.
- sess = session.Session.construct(dict(
- cert=self._conf_get('certfile'),
- key=self._conf_get('keyfile'),
- cacert=self._conf_get('cafile'),
- insecure=self._conf_get('insecure'),
- timeout=self._conf_get('http_connect_timeout'),
- user_agent=self._build_useragent_string()
- ))
-
- auth_plugin = self._get_auth_plugin()
-
- adap = adapter.Adapter(
- sess,
- auth=auth_plugin,
- service_type='identity',
- interface='admin',
- region_name=self._conf_get('region_name'),
- connect_retries=self._conf_get('http_request_max_retries'))
-
- auth_version = self._conf_get('auth_version')
- if auth_version is not None:
- auth_version = discover.normalize_version_number(auth_version)
- return _identity.IdentityServer(
- self.log,
- adap,
- include_service_catalog=self._include_service_catalog,
- requested_auth_version=auth_version)
-
- def _token_cache_factory(self):
- security_strategy = self._conf_get('memcache_security_strategy')
-
- cache_kwargs = dict(
- cache_time=int(self._conf_get('token_cache_time')),
- env_cache_name=self._conf_get('cache'),
- memcached_servers=self._conf_get('memcached_servers'),
- use_advanced_pool=self._conf_get('memcache_use_advanced_pool'),
- memcache_pool_dead_retry=self._conf_get(
- 'memcache_pool_dead_retry'),
- memcache_pool_maxsize=self._conf_get('memcache_pool_maxsize'),
- memcache_pool_unused_timeout=self._conf_get(
- 'memcache_pool_unused_timeout'),
- memcache_pool_conn_get_timeout=self._conf_get(
- 'memcache_pool_conn_get_timeout'),
- memcache_pool_socket_timeout=self._conf_get(
- 'memcache_pool_socket_timeout'),
- )
-
- if security_strategy:
- secret_key = self._conf_get('memcache_secret_key')
- return _cache.SecureTokenCache(self.log,
- security_strategy,
- secret_key,
- **cache_kwargs)
- else:
- return _cache.TokenCache(self.log, **cache_kwargs)
-
-
-def filter_factory(global_conf, **local_conf):
- """Returns a WSGI filter app for use with paste.deploy."""
- conf = global_conf.copy()
- conf.update(local_conf)
-
- def auth_filter(app):
- return AuthProtocol(app, conf)
- return auth_filter
-
-
-def app_factory(global_conf, **local_conf):
- conf = global_conf.copy()
- conf.update(local_conf)
- return AuthProtocol(None, conf)
-
-
-# NOTE(jamielennox): Maintained here for public API compatibility.
-InvalidToken = exc.InvalidToken
-ServiceError = exc.ServiceError
-ConfigurationError = exc.ConfigurationError
-RevocationListError = exc.RevocationListError
diff --git a/keystonemiddleware-moon/keystonemiddleware/auth_token/_auth.py b/keystonemiddleware-moon/keystonemiddleware/auth_token/_auth.py
deleted file mode 100644
index cf7ed84d..00000000
--- a/keystonemiddleware-moon/keystonemiddleware/auth_token/_auth.py
+++ /dev/null
@@ -1,194 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import logging
-
-from keystoneclient import auth
-from keystoneclient.auth.identity import v2
-from keystoneclient.auth import token_endpoint
-from keystoneclient import discover
-from oslo_config import cfg
-
-from keystonemiddleware.auth_token import _base
-from keystonemiddleware.i18n import _, _LW
-
-
-_LOG = logging.getLogger(__name__)
-
-
-class AuthTokenPlugin(auth.BaseAuthPlugin):
-
- def __init__(self, auth_host, auth_port, auth_protocol, auth_admin_prefix,
- admin_user, admin_password, admin_tenant_name, admin_token,
- identity_uri, log):
-
- log.warning(_LW(
- "Use of the auth_admin_prefix, auth_host, auth_port, "
- "auth_protocol, identity_uri, admin_token, admin_user, "
- "admin_password, and admin_tenant_name configuration options is "
- "deprecated in favor of auth_plugin and related options and may "
- "be removed in a future release."))
-
- # NOTE(jamielennox): it does appear here that our default arguments
- # are backwards. We need to do it this way so that we can handle the
- # same deprecation strategy for CONF and the conf variable.
- if not identity_uri:
- log.warning(_LW('Configuring admin URI using auth fragments. '
- 'This is deprecated, use \'identity_uri\''
- ' instead.'))
-
- if ':' in auth_host:
- # Note(dzyu) it is an IPv6 address, so it needs to be wrapped
- # with '[]' to generate a valid IPv6 URL, based on
- # http://www.ietf.org/rfc/rfc2732.txt
- auth_host = '[%s]' % auth_host
-
- identity_uri = '%s://%s:%s' % (auth_protocol,
- auth_host,
- auth_port)
-
- if auth_admin_prefix:
- identity_uri = '%s/%s' % (identity_uri,
- auth_admin_prefix.strip('/'))
-
- self._identity_uri = identity_uri.rstrip('/')
-
- # FIXME(jamielennox): Yes. This is wrong. We should be determining the
- # plugin to use based on a combination of discovery and inputs. Much
- # of this can be changed when we get keystoneclient 0.10. For now this
- # hardcoded path is EXACTLY the same as the original auth_token did.
- auth_url = '%s/v2.0' % self._identity_uri
-
- if admin_token:
- log.warning(_LW(
- "The admin_token option in the auth_token middleware is "
- "deprecated and should not be used. The admin_user and "
- "admin_password options should be used instead. The "
- "admin_token option may be removed in a future release."))
- self._plugin = token_endpoint.Token(auth_url, admin_token)
- else:
- self._plugin = v2.Password(auth_url,
- username=admin_user,
- password=admin_password,
- tenant_name=admin_tenant_name)
-
- self._LOG = log
- self._discover = None
-
- def get_token(self, *args, **kwargs):
- return self._plugin.get_token(*args, **kwargs)
-
- def get_endpoint(self, session, interface=None, version=None, **kwargs):
- """Return an endpoint for the client.
-
- There are no required keyword arguments to ``get_endpoint`` as a plugin
- implementation should use best effort with the information available to
- determine the endpoint.
-
- :param session: The session object that the auth_plugin belongs to.
- :type session: keystoneclient.session.Session
- :param version: The version number required for this endpoint.
- :type version: tuple or str
- :param str interface: what visibility the endpoint should have.
-
- :returns: The base URL that will be used to talk to the required
- service or None if not available.
- :rtype: string
- """
- if interface == auth.AUTH_INTERFACE:
- return self._identity_uri
-
- if not version:
- # NOTE(jamielennox): This plugin can only be used within auth_token
- # and auth_token will always provide version= with requests.
- return None
-
- if not self._discover:
- self._discover = discover.Discover(session,
- auth_url=self._identity_uri,
- authenticated=False)
-
- if not self._discover.url_for(version):
- # NOTE(jamielennox): The requested version is not supported by the
- # identity server.
- return None
-
- # NOTE(jamielennox): for backwards compatibility here we don't
- # actually use the URL from discovery we hack it up instead. :(
- # NOTE(blk-u): Normalizing the version is a workaround for bug 1450272.
- # This can be removed once that's fixed. Also fix the docstring for the
- # version parameter to be just "tuple".
- version = discover.normalize_version_number(version)
- if discover.version_match((2, 0), version):
- return '%s/v2.0' % self._identity_uri
- elif discover.version_match((3, 0), version):
- return '%s/v3' % self._identity_uri
-
- # NOTE(jamielennox): This plugin will only get called from auth_token
- # middleware. The middleware should never request a version that the
- # plugin doesn't know how to handle.
- msg = _('Invalid version asked for in auth_token plugin')
- raise NotImplementedError(msg)
-
- def invalidate(self):
- return self._plugin.invalidate()
-
- @classmethod
- def get_options(cls):
- options = super(AuthTokenPlugin, cls).get_options()
-
- options.extend([
- cfg.StrOpt('auth_admin_prefix',
- default='',
- help='Prefix to prepend at the beginning of the path. '
- 'Deprecated, use identity_uri.'),
- cfg.StrOpt('auth_host',
- default='127.0.0.1',
- help='Host providing the admin Identity API endpoint. '
- 'Deprecated, use identity_uri.'),
- cfg.IntOpt('auth_port',
- default=35357,
- help='Port of the admin Identity API endpoint. '
- 'Deprecated, use identity_uri.'),
- cfg.StrOpt('auth_protocol',
- default='https',
- help='Protocol of the admin Identity API endpoint '
- '(http or https). Deprecated, use identity_uri.'),
- cfg.StrOpt('identity_uri',
- default=None,
- help='Complete admin Identity API endpoint. This '
- 'should specify the unversioned root endpoint '
- 'e.g. https://localhost:35357/'),
- cfg.StrOpt('admin_token',
- secret=True,
- help='This option is deprecated and may be removed in '
- 'a future release. Single shared secret with the '
- 'Keystone configuration used for bootstrapping a '
- 'Keystone installation, or otherwise bypassing '
- 'the normal authentication process. This option '
- 'should not be used, use `admin_user` and '
- '`admin_password` instead.'),
- cfg.StrOpt('admin_user',
- help='Service username.'),
- cfg.StrOpt('admin_password',
- secret=True,
- help='Service user password.'),
- cfg.StrOpt('admin_tenant_name',
- default='admin',
- help='Service tenant name.'),
- ])
-
- return options
-
-
-auth.register_conf_options(cfg.CONF, _base.AUTHTOKEN_GROUP)
-AuthTokenPlugin.register_conf_options(cfg.CONF, _base.AUTHTOKEN_GROUP)
diff --git a/keystonemiddleware-moon/keystonemiddleware/auth_token/_base.py b/keystonemiddleware-moon/keystonemiddleware/auth_token/_base.py
deleted file mode 100644
index ee4ec13c..00000000
--- a/keystonemiddleware-moon/keystonemiddleware/auth_token/_base.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-AUTHTOKEN_GROUP = 'keystone_authtoken'
diff --git a/keystonemiddleware-moon/keystonemiddleware/auth_token/_cache.py b/keystonemiddleware-moon/keystonemiddleware/auth_token/_cache.py
deleted file mode 100644
index ce5faf66..00000000
--- a/keystonemiddleware-moon/keystonemiddleware/auth_token/_cache.py
+++ /dev/null
@@ -1,338 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import contextlib
-import hashlib
-
-from oslo_serialization import jsonutils
-import six
-
-from keystonemiddleware.auth_token import _exceptions as exc
-from keystonemiddleware.auth_token import _memcache_crypt as memcache_crypt
-from keystonemiddleware.i18n import _, _LE
-from keystonemiddleware.openstack.common import memorycache
-
-
-def _hash_key(key):
- """Turn a set of arguments into a SHA256 hash.
-
- Using a known-length cache key is important to ensure that memcache
- maximum key length is not exceeded causing failures to validate.
- """
- if isinstance(key, six.text_type):
- # NOTE(morganfainberg): Ensure we are always working with a bytes
- # type required for the hasher. In python 2.7 it is possible to
- # get a text_type (unicode). In python 3.4 all strings are
- # text_type and not bytes by default. This encode coerces the
- # text_type to the appropriate bytes values.
- key = key.encode('utf-8')
- return hashlib.sha256(key).hexdigest()
-
-
-class _CachePool(list):
- """A lazy pool of cache references."""
-
- def __init__(self, cache, memcached_servers):
- self._environment_cache = cache
- self._memcached_servers = memcached_servers
-
- @contextlib.contextmanager
- def reserve(self):
- """Context manager to manage a pooled cache reference."""
- if self._environment_cache is not None:
- # skip pooling and just use the cache from the upstream filter
- yield self._environment_cache
- return # otherwise the context manager will continue!
-
- try:
- c = self.pop()
- except IndexError:
- # the pool is empty, so we need to create a new client
- c = memorycache.get_client(self._memcached_servers)
-
- try:
- yield c
- finally:
- self.append(c)
-
-
-class _MemcacheClientPool(object):
- """An advanced memcached client pool that is eventlet safe."""
- def __init__(self, memcache_servers, memcache_dead_retry=None,
- memcache_pool_maxsize=None, memcache_pool_unused_timeout=None,
- memcache_pool_conn_get_timeout=None,
- memcache_pool_socket_timeout=None):
- # NOTE(morganfainberg): import here to avoid hard dependency on
- # python-memcached library.
- global _memcache_pool
- from keystonemiddleware.auth_token import _memcache_pool
-
- self._pool = _memcache_pool.MemcacheClientPool(
- memcache_servers,
- arguments={
- 'dead_retry': memcache_dead_retry,
- 'socket_timeout': memcache_pool_socket_timeout,
- },
- maxsize=memcache_pool_maxsize,
- unused_timeout=memcache_pool_unused_timeout,
- conn_get_timeout=memcache_pool_conn_get_timeout,
- )
-
- @contextlib.contextmanager
- def reserve(self):
- with self._pool.get() as client:
- yield client
-
-
-class TokenCache(object):
- """Encapsulates the auth_token token cache functionality.
-
- auth_token caches tokens that it's seen so that when a token is re-used the
- middleware doesn't have to do a more expensive operation (like going to the
- identity server) to validate the token.
-
- initialize() must be called before calling the other methods.
-
- Store a valid token in the cache using store(); mark a token as invalid in
- the cache using store_invalid().
-
- Check if a token is in the cache and retrieve it using get().
-
- """
-
- _CACHE_KEY_TEMPLATE = 'tokens/%s'
- _INVALID_INDICATOR = 'invalid'
-
- def __init__(self, log, cache_time=None,
- env_cache_name=None, memcached_servers=None,
- use_advanced_pool=False, memcache_pool_dead_retry=None,
- memcache_pool_maxsize=None, memcache_pool_unused_timeout=None,
- memcache_pool_conn_get_timeout=None,
- memcache_pool_socket_timeout=None):
- self._LOG = log
- self._cache_time = cache_time
- self._env_cache_name = env_cache_name
- self._memcached_servers = memcached_servers
- self._use_advanced_pool = use_advanced_pool
- self._memcache_pool_dead_retry = memcache_pool_dead_retry,
- self._memcache_pool_maxsize = memcache_pool_maxsize,
- self._memcache_pool_unused_timeout = memcache_pool_unused_timeout
- self._memcache_pool_conn_get_timeout = memcache_pool_conn_get_timeout
- self._memcache_pool_socket_timeout = memcache_pool_socket_timeout
-
- self._cache_pool = None
- self._initialized = False
-
- def _get_cache_pool(self, cache, memcache_servers, use_advanced_pool=False,
- memcache_dead_retry=None, memcache_pool_maxsize=None,
- memcache_pool_unused_timeout=None,
- memcache_pool_conn_get_timeout=None,
- memcache_pool_socket_timeout=None):
- if use_advanced_pool is True and memcache_servers and cache is None:
- return _MemcacheClientPool(
- memcache_servers,
- memcache_dead_retry=memcache_dead_retry,
- memcache_pool_maxsize=memcache_pool_maxsize,
- memcache_pool_unused_timeout=memcache_pool_unused_timeout,
- memcache_pool_conn_get_timeout=memcache_pool_conn_get_timeout,
- memcache_pool_socket_timeout=memcache_pool_socket_timeout)
- else:
- return _CachePool(cache, memcache_servers)
-
- def initialize(self, env):
- if self._initialized:
- return
-
- self._cache_pool = self._get_cache_pool(
- env.get(self._env_cache_name),
- self._memcached_servers,
- use_advanced_pool=self._use_advanced_pool,
- memcache_dead_retry=self._memcache_pool_dead_retry,
- memcache_pool_maxsize=self._memcache_pool_maxsize,
- memcache_pool_unused_timeout=self._memcache_pool_unused_timeout,
- memcache_pool_conn_get_timeout=self._memcache_pool_conn_get_timeout
- )
-
- self._initialized = True
-
- def store(self, token_id, data):
- """Put token data into the cache.
- """
- self._LOG.debug('Storing token in cache')
- self._cache_store(token_id, data)
-
- def store_invalid(self, token_id):
- """Store invalid token in cache."""
- self._LOG.debug('Marking token as unauthorized in cache')
- self._cache_store(token_id, self._INVALID_INDICATOR)
-
- def _get_cache_key(self, token_id):
- """Get a unique key for this token id.
-
- Turn the token_id into something that can uniquely identify that token
- in a key value store.
-
- As this is generally the first function called in a key lookup this
- function also returns a context object. This context object is not
- modified or used by the Cache object but is passed back on subsequent
- functions so that decryption or other data can be shared throughout a
- cache lookup.
-
- :param str token_id: The unique token id.
-
- :returns: A tuple of a string key and an implementation specific
- context object
- """
- # NOTE(jamielennox): in the basic implementation there is no need for
- # a context so just pass None as it will only get passed back later.
- unused_context = None
- return self._CACHE_KEY_TEMPLATE % _hash_key(token_id), unused_context
-
- def _deserialize(self, data, context):
- """Deserialize data from the cache back into python objects.
-
- Take data retrieved from the cache and return an appropriate python
- dictionary.
-
- :param str data: The data retrieved from the cache.
- :param object context: The context that was returned from
- _get_cache_key.
-
- :returns: The python object that was saved.
- """
- # memory cache will handle deserialization for us
- return data
-
- def _serialize(self, data, context):
- """Serialize data so that it can be saved to the cache.
-
- Take python objects and serialize them so that they can be saved into
- the cache.
-
- :param object data: The data to be cached.
- :param object context: The context that was returned from
- _get_cache_key.
-
- :returns: The python object that was saved.
- """
- # memory cache will handle serialization for us
- return data
-
- def get(self, token_id):
- """Return token information from cache.
-
- If token is invalid raise exc.InvalidToken
- return token only if fresh (not expired).
- """
-
- if not token_id:
- # Nothing to do
- return
-
- key, context = self._get_cache_key(token_id)
-
- with self._cache_pool.reserve() as cache:
- serialized = cache.get(key)
-
- if serialized is None:
- return None
-
- if isinstance(serialized, six.text_type):
- serialized = serialized.encode('utf8')
- data = self._deserialize(serialized, context)
-
- # Note that _INVALID_INDICATOR and (data, expires) are the only
- # valid types of serialized cache entries, so there is not
- # a collision with jsonutils.loads(serialized) == None.
- if not isinstance(data, six.string_types):
- data = data.decode('utf-8')
- cached = jsonutils.loads(data)
- if cached == self._INVALID_INDICATOR:
- self._LOG.debug('Cached Token is marked unauthorized')
- raise exc.InvalidToken(_('Token authorization failed'))
-
- # NOTE(jamielennox): Cached values used to be stored as a tuple of data
- # and expiry time. They no longer are but we have to allow some time to
- # transition the old format so if it's a tuple just return the data.
- try:
- data, expires = cached
- except ValueError:
- data = cached
-
- return data
-
- def _cache_store(self, token_id, data):
- """Store value into memcache.
-
- data may be _INVALID_INDICATOR or a tuple like (data, expires)
-
- """
- data = jsonutils.dumps(data)
- if isinstance(data, six.text_type):
- data = data.encode('utf-8')
-
- cache_key, context = self._get_cache_key(token_id)
- data_to_store = self._serialize(data, context)
-
- with self._cache_pool.reserve() as cache:
- cache.set(cache_key, data_to_store, time=self._cache_time)
-
-
-class SecureTokenCache(TokenCache):
- """A token cache that stores tokens encrypted.
-
- A more secure version of TokenCache that will encrypt tokens before
- caching them.
- """
-
- def __init__(self, log, security_strategy, secret_key, **kwargs):
- super(SecureTokenCache, self).__init__(log, **kwargs)
-
- security_strategy = security_strategy.upper()
-
- if security_strategy not in ('MAC', 'ENCRYPT'):
- msg = _('memcache_security_strategy must be ENCRYPT or MAC')
- raise exc.ConfigurationError(msg)
- if not secret_key:
- msg = _('memcache_secret_key must be defined when a '
- 'memcache_security_strategy is defined')
- raise exc.ConfigurationError(msg)
-
- if isinstance(security_strategy, six.string_types):
- security_strategy = security_strategy.encode('utf-8')
- if isinstance(secret_key, six.string_types):
- secret_key = secret_key.encode('utf-8')
-
- self._security_strategy = security_strategy
- self._secret_key = secret_key
-
- def _get_cache_key(self, token_id):
- context = memcache_crypt.derive_keys(token_id,
- self._secret_key,
- self._security_strategy)
- key = self._CACHE_KEY_TEMPLATE % memcache_crypt.get_cache_key(context)
- return key, context
-
- def _deserialize(self, data, context):
- try:
- # unprotect_data will return None if raw_cached is None
- return memcache_crypt.unprotect_data(context, data)
- except Exception:
- msg = _LE('Failed to decrypt/verify cache data')
- self._LOG.exception(msg)
-
- # this should have the same effect as data not
- # found in cache
- return None
-
- def _serialize(self, data, context):
- return memcache_crypt.protect_data(context, data)
diff --git a/keystonemiddleware-moon/keystonemiddleware/auth_token/_exceptions.py b/keystonemiddleware-moon/keystonemiddleware/auth_token/_exceptions.py
deleted file mode 100644
index be045c96..00000000
--- a/keystonemiddleware-moon/keystonemiddleware/auth_token/_exceptions.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-
-class InvalidToken(Exception):
- pass
-
-
-class ServiceError(Exception):
- pass
-
-
-class ConfigurationError(Exception):
- pass
-
-
-class RevocationListError(Exception):
- pass
diff --git a/keystonemiddleware-moon/keystonemiddleware/auth_token/_identity.py b/keystonemiddleware-moon/keystonemiddleware/auth_token/_identity.py
deleted file mode 100644
index 6fbeac27..00000000
--- a/keystonemiddleware-moon/keystonemiddleware/auth_token/_identity.py
+++ /dev/null
@@ -1,252 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import functools
-
-from keystoneclient import auth
-from keystoneclient import discover
-from keystoneclient import exceptions
-from keystoneclient.v2_0 import client as v2_client
-from keystoneclient.v3 import client as v3_client
-from six.moves import urllib
-
-from keystonemiddleware.auth_token import _auth
-from keystonemiddleware.auth_token import _exceptions as exc
-from keystonemiddleware.i18n import _, _LE, _LI, _LW
-
-
-def _convert_fetch_cert_exception(fetch_cert):
- @functools.wraps(fetch_cert)
- def wrapper(self):
- try:
- text = fetch_cert(self)
- except exceptions.HTTPError as e:
- raise exceptions.CertificateConfigError(e.details)
- return text
-
- return wrapper
-
-
-class _RequestStrategy(object):
-
- AUTH_VERSION = None
-
- def __init__(self, adap, include_service_catalog=None):
- self._include_service_catalog = include_service_catalog
-
- def verify_token(self, user_token):
- pass
-
- @_convert_fetch_cert_exception
- def fetch_signing_cert(self):
- return self._fetch_signing_cert()
-
- def _fetch_signing_cert(self):
- pass
-
- @_convert_fetch_cert_exception
- def fetch_ca_cert(self):
- return self._fetch_ca_cert()
-
- def _fetch_ca_cert(self):
- pass
-
- def fetch_revocation_list(self):
- pass
-
-
-class _V2RequestStrategy(_RequestStrategy):
-
- AUTH_VERSION = (2, 0)
-
- def __init__(self, adap, **kwargs):
- super(_V2RequestStrategy, self).__init__(adap, **kwargs)
- self._client = v2_client.Client(session=adap)
-
- def verify_token(self, token):
- auth_ref = self._client.tokens.validate_access_info(token)
-
- if not auth_ref:
- msg = _('Failed to fetch token data from identity server')
- raise exc.InvalidToken(msg)
-
- return {'access': auth_ref}
-
- def _fetch_signing_cert(self):
- return self._client.certificates.get_signing_certificate()
-
- def _fetch_ca_cert(self):
- return self._client.certificates.get_ca_certificate()
-
- def fetch_revocation_list(self):
- return self._client.tokens.get_revoked()
-
-
-class _V3RequestStrategy(_RequestStrategy):
-
- AUTH_VERSION = (3, 0)
-
- def __init__(self, adap, **kwargs):
- super(_V3RequestStrategy, self).__init__(adap, **kwargs)
- self._client = v3_client.Client(session=adap)
-
- def verify_token(self, token):
- auth_ref = self._client.tokens.validate(
- token,
- include_catalog=self._include_service_catalog)
-
- if not auth_ref:
- msg = _('Failed to fetch token data from identity server')
- raise exc.InvalidToken(msg)
-
- return {'token': auth_ref}
-
- def _fetch_signing_cert(self):
- return self._client.simple_cert.get_certificates()
-
- def _fetch_ca_cert(self):
- return self._client.simple_cert.get_ca_certificates()
-
- def fetch_revocation_list(self):
- return self._client.tokens.get_revoked()
-
-
-_REQUEST_STRATEGIES = [_V3RequestStrategy, _V2RequestStrategy]
-
-
-class IdentityServer(object):
- """Base class for operations on the Identity API server.
-
- The auth_token middleware needs to communicate with the Identity API server
- to validate UUID tokens, fetch the revocation list, signing certificates,
- etc. This class encapsulates the data and methods to perform these
- operations.
-
- """
-
- def __init__(self, log, adap, include_service_catalog=None,
- requested_auth_version=None):
- self._LOG = log
- self._adapter = adap
- self._include_service_catalog = include_service_catalog
- self._requested_auth_version = requested_auth_version
-
- # Built on-demand with self._request_strategy.
- self._request_strategy_obj = None
-
- @property
- def auth_uri(self):
- auth_uri = self._adapter.get_endpoint(interface=auth.AUTH_INTERFACE)
-
- # NOTE(jamielennox): This weird stripping of the prefix hack is
- # only relevant to the legacy case. We urljoin '/' to get just the
- # base URI as this is the original behaviour.
- if isinstance(self._adapter.auth, _auth.AuthTokenPlugin):
- auth_uri = urllib.parse.urljoin(auth_uri, '/').rstrip('/')
-
- return auth_uri
-
- @property
- def auth_version(self):
- return self._request_strategy.AUTH_VERSION
-
- @property
- def _request_strategy(self):
- if not self._request_strategy_obj:
- strategy_class = self._get_strategy_class()
- self._adapter.version = strategy_class.AUTH_VERSION
-
- self._request_strategy_obj = strategy_class(
- self._adapter,
- include_service_catalog=self._include_service_catalog)
-
- return self._request_strategy_obj
-
- def _get_strategy_class(self):
- if self._requested_auth_version:
- # A specific version was requested.
- if discover.version_match(_V3RequestStrategy.AUTH_VERSION,
- self._requested_auth_version):
- return _V3RequestStrategy
-
- # The version isn't v3 so we don't know what to do. Just assume V2.
- return _V2RequestStrategy
-
- # Specific version was not requested then we fall through to
- # discovering available versions from the server
- for klass in _REQUEST_STRATEGIES:
- if self._adapter.get_endpoint(version=klass.AUTH_VERSION):
- msg = _LI('Auth Token confirmed use of %s apis')
- self._LOG.info(msg, self._requested_auth_version)
- return klass
-
- versions = ['v%d.%d' % s.AUTH_VERSION for s in _REQUEST_STRATEGIES]
- self._LOG.error(_LE('No attempted versions [%s] supported by server'),
- ', '.join(versions))
-
- msg = _('No compatible apis supported by server')
- raise exc.ServiceError(msg)
-
- def verify_token(self, user_token, retry=True):
- """Authenticate user token with identity server.
-
- :param user_token: user's token id
- :param retry: flag that forces the middleware to retry
- user authentication when an indeterminate
- response is received. Optional.
- :returns: access info received from identity server on success
- :rtype: :py:class:`keystoneclient.access.AccessInfo`
- :raises exc.InvalidToken: if token is rejected
- :raises exc.ServiceError: if unable to authenticate token
-
- """
- try:
- auth_ref = self._request_strategy.verify_token(user_token)
- except exceptions.NotFound as e:
- self._LOG.warning(_LW('Authorization failed for token'))
- self._LOG.warning(_LW('Identity response: %s'), e.response.text)
- raise exc.InvalidToken(_('Token authorization failed'))
- except exceptions.Unauthorized as e:
- self._LOG.info(_LI('Identity server rejected authorization'))
- self._LOG.warning(_LW('Identity response: %s'), e.response.text)
- if retry:
- self._LOG.info(_LI('Retrying validation'))
- return self.verify_token(user_token, False)
- msg = _('Identity server rejected authorization necessary to '
- 'fetch token data')
- raise exc.ServiceError(msg)
- except exceptions.HttpError as e:
- self._LOG.error(
- _LE('Bad response code while validating token: %s'),
- e.http_status)
- self._LOG.warning(_LW('Identity response: %s'), e.response.text)
- msg = _('Failed to fetch token data from identity server')
- raise exc.ServiceError(msg)
- else:
- return auth_ref
-
- def fetch_revocation_list(self):
- try:
- data = self._request_strategy.fetch_revocation_list()
- except exceptions.HTTPError as e:
- msg = _('Failed to fetch token revocation list: %d')
- raise exc.RevocationListError(msg % e.http_status)
- if 'signed' not in data:
- msg = _('Revocation list improperly formatted.')
- raise exc.RevocationListError(msg)
- return data['signed']
-
- def fetch_signing_cert(self):
- return self._request_strategy.fetch_signing_cert()
-
- def fetch_ca_cert(self):
- return self._request_strategy.fetch_ca_cert()
diff --git a/keystonemiddleware-moon/keystonemiddleware/auth_token/_memcache_crypt.py b/keystonemiddleware-moon/keystonemiddleware/auth_token/_memcache_crypt.py
deleted file mode 100644
index 2e45571f..00000000
--- a/keystonemiddleware-moon/keystonemiddleware/auth_token/_memcache_crypt.py
+++ /dev/null
@@ -1,210 +0,0 @@
-# Copyright 2010-2013 OpenStack Foundation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-# implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Utilities for memcache encryption and integrity check.
-
-Data should be serialized before entering these functions. Encryption
-has a dependency on the pycrypto. If pycrypto is not available,
-CryptoUnavailableError will be raised.
-
-This module will not be called unless signing or encryption is enabled
-in the config. It will always validate signatures, and will decrypt
-data if encryption is enabled. It is not valid to mix protection
-modes.
-
-"""
-
-import base64
-import functools
-import hashlib
-import hmac
-import math
-import os
-import six
-import sys
-
-from keystonemiddleware.i18n import _
-
-# make sure pycrypto is available
-try:
- from Crypto.Cipher import AES
-except ImportError:
- AES = None
-
-HASH_FUNCTION = hashlib.sha384
-DIGEST_LENGTH = HASH_FUNCTION().digest_size
-DIGEST_SPLIT = DIGEST_LENGTH // 3
-DIGEST_LENGTH_B64 = 4 * int(math.ceil(DIGEST_LENGTH / 3.0))
-
-
-class InvalidMacError(Exception):
- """raise when unable to verify MACed data.
-
- This usually indicates that data had been expectedly modified in memcache.
-
- """
- pass
-
-
-class DecryptError(Exception):
- """raise when unable to decrypt encrypted data.
-
- """
- pass
-
-
-class CryptoUnavailableError(Exception):
- """raise when Python Crypto module is not available.
-
- """
- pass
-
-
-def assert_crypto_availability(f):
- """Ensure Crypto module is available."""
-
- @functools.wraps(f)
- def wrapper(*args, **kwds):
- if AES is None:
- raise CryptoUnavailableError()
- return f(*args, **kwds)
- return wrapper
-
-
-if sys.version_info >= (3, 3):
- constant_time_compare = hmac.compare_digest
-else:
- def constant_time_compare(first, second):
- """Returns True if both string inputs are equal, otherwise False.
-
- This function should take a constant amount of time regardless of
- how many characters in the strings match.
-
- """
- if len(first) != len(second):
- return False
- result = 0
- if six.PY3 and isinstance(first, bytes) and isinstance(second, bytes):
- for x, y in zip(first, second):
- result |= x ^ y
- else:
- for x, y in zip(first, second):
- result |= ord(x) ^ ord(y)
- return result == 0
-
-
-def derive_keys(token, secret, strategy):
- """Derives keys for MAC and ENCRYPTION from the user-provided
- secret. The resulting keys should be passed to the protect and
- unprotect functions.
-
- As suggested by NIST Special Publication 800-108, this uses the
- first 128 bits from the sha384 KDF for the obscured cache key
- value, the second 128 bits for the message authentication key and
- the remaining 128 bits for the encryption key.
-
- This approach is faster than computing a separate hmac as the KDF
- for each desired key.
- """
- digest = hmac.new(secret, token + strategy, HASH_FUNCTION).digest()
- return {'CACHE_KEY': digest[:DIGEST_SPLIT],
- 'MAC': digest[DIGEST_SPLIT: 2 * DIGEST_SPLIT],
- 'ENCRYPTION': digest[2 * DIGEST_SPLIT:],
- 'strategy': strategy}
-
-
-def sign_data(key, data):
- """Sign the data using the defined function and the derived key."""
- mac = hmac.new(key, data, HASH_FUNCTION).digest()
- return base64.b64encode(mac)
-
-
-@assert_crypto_availability
-def encrypt_data(key, data):
- """Encrypt the data with the given secret key.
-
- Padding is n bytes of the value n, where 1 <= n <= blocksize.
- """
- iv = os.urandom(16)
- cipher = AES.new(key, AES.MODE_CBC, iv)
- padding = 16 - len(data) % 16
- return iv + cipher.encrypt(data + six.int2byte(padding) * padding)
-
-
-@assert_crypto_availability
-def decrypt_data(key, data):
- """Decrypt the data with the given secret key."""
- iv = data[:16]
- cipher = AES.new(key, AES.MODE_CBC, iv)
- try:
- result = cipher.decrypt(data[16:])
- except Exception:
- raise DecryptError(_('Encrypted data appears to be corrupted.'))
-
- # Strip the last n padding bytes where n is the last value in
- # the plaintext
- return result[:-1 * six.byte2int([result[-1]])]
-
-
-def protect_data(keys, data):
- """Given keys and serialized data, returns an appropriately
- protected string suitable for storage in the cache.
-
- """
- if keys['strategy'] == b'ENCRYPT':
- data = encrypt_data(keys['ENCRYPTION'], data)
-
- encoded_data = base64.b64encode(data)
-
- signature = sign_data(keys['MAC'], encoded_data)
- return signature + encoded_data
-
-
-def unprotect_data(keys, signed_data):
- """Given keys and cached string data, verifies the signature,
- decrypts if necessary, and returns the original serialized data.
-
- """
- # cache backends return None when no data is found. We don't mind
- # that this particular special value is unsigned.
- if signed_data is None:
- return None
-
- # First we calculate the signature
- provided_mac = signed_data[:DIGEST_LENGTH_B64]
- calculated_mac = sign_data(
- keys['MAC'],
- signed_data[DIGEST_LENGTH_B64:])
-
- # Then verify that it matches the provided value
- if not constant_time_compare(provided_mac, calculated_mac):
- raise InvalidMacError(_('Invalid MAC; data appears to be corrupted.'))
-
- data = base64.b64decode(signed_data[DIGEST_LENGTH_B64:])
-
- # then if necessary decrypt the data
- if keys['strategy'] == b'ENCRYPT':
- data = decrypt_data(keys['ENCRYPTION'], data)
-
- return data
-
-
-def get_cache_key(keys):
- """Given keys generated by derive_keys(), returns a base64
- encoded value suitable for use as a cache key in memcached.
-
- """
- return base64.b64encode(keys['CACHE_KEY'])
diff --git a/keystonemiddleware-moon/keystonemiddleware/auth_token/_memcache_pool.py b/keystonemiddleware-moon/keystonemiddleware/auth_token/_memcache_pool.py
deleted file mode 100644
index 77652868..00000000
--- a/keystonemiddleware-moon/keystonemiddleware/auth_token/_memcache_pool.py
+++ /dev/null
@@ -1,184 +0,0 @@
-# Copyright 2014 Mirantis Inc
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""Thread-safe connection pool for python-memcached."""
-
-# NOTE(yorik-sar): this file is copied between keystone and keystonemiddleware
-# and should be kept in sync until we can use external library for this.
-
-import collections
-import contextlib
-import itertools
-import logging
-import time
-
-from six.moves import queue
-
-from keystonemiddleware.i18n import _LC
-
-
-_PoolItem = collections.namedtuple('_PoolItem', ['ttl', 'connection'])
-
-
-class ConnectionGetTimeoutException(Exception):
- pass
-
-
-class ConnectionPool(queue.Queue):
- """Base connection pool class
-
- This class implements the basic connection pool logic as an abstract base
- class.
- """
- def __init__(self, maxsize, unused_timeout, conn_get_timeout=None):
- """Initialize the connection pool.
-
- :param maxsize: maximum number of client connections for the pool
- :type maxsize: int
- :param unused_timeout: idle time to live for unused clients (in
- seconds). If a client connection object has been
- in the pool and idle for longer than the
- unused_timeout, it will be reaped. This is to
- ensure resources are released as utilization
- goes down.
- :type unused_timeout: int
- :param conn_get_timeout: maximum time in seconds to wait for a
- connection. If set to `None` timeout is
- indefinite.
- :type conn_get_timeout: int
- """
- queue.Queue.__init__(self, maxsize)
- self._unused_timeout = unused_timeout
- self._connection_get_timeout = conn_get_timeout
- self._acquired = 0
- self._LOG = logging.getLogger(__name__)
-
- def _create_connection(self):
- raise NotImplementedError
-
- def _destroy_connection(self, conn):
- raise NotImplementedError
-
- @contextlib.contextmanager
- def acquire(self):
- try:
- conn = self.get(timeout=self._connection_get_timeout)
- except queue.Empty:
- self._LOG.critical(_LC('Unable to get a connection from pool id '
- '%(id)s after %(seconds)s seconds.'),
- {'id': id(self),
- 'seconds': self._connection_get_timeout})
- raise ConnectionGetTimeoutException()
- try:
- yield conn
- finally:
- self.put(conn)
-
- def _qsize(self):
- return self.maxsize - self._acquired
-
- if not hasattr(queue.Queue, '_qsize'):
- qsize = _qsize
-
- def _get(self):
- if self.queue:
- conn = self.queue.pop().connection
- else:
- conn = self._create_connection()
- self._acquired += 1
- return conn
-
- def _put(self, conn):
- self.queue.append(_PoolItem(
- ttl=time.time() + self._unused_timeout,
- connection=conn,
- ))
- self._acquired -= 1
- # Drop all expired connections from the right end of the queue
- now = time.time()
- while self.queue and self.queue[0].ttl < now:
- conn = self.queue.popleft().connection
- self._destroy_connection(conn)
-
-
-class MemcacheClientPool(ConnectionPool):
- def __init__(self, urls, arguments, **kwargs):
- ConnectionPool.__init__(self, **kwargs)
- self._urls = urls
- self._arguments = arguments
- # NOTE(morganfainberg): The host objects expect an int for the
- # deaduntil value. Initialize this at 0 for each host with 0 indicating
- # the host is not dead.
- self._hosts_deaduntil = [0] * len(urls)
-
- # NOTE(morganfainberg): Lazy import to allow middleware to work with
- # python 3k even if memcache will not due to python 3k
- # incompatibilities within the python-memcache library.
- global memcache
- import memcache
-
- # This 'class' is taken from http://stackoverflow.com/a/22520633/238308
- # Don't inherit client from threading.local so that we can reuse
- # clients in different threads
- MemcacheClient = type('_MemcacheClient', (object,),
- dict(memcache.Client.__dict__))
-
- self._memcache_client_class = MemcacheClient
-
- def _create_connection(self):
- return self._memcache_client_class(self._urls, **self._arguments)
-
- def _destroy_connection(self, conn):
- conn.disconnect_all()
-
- def _get(self):
- conn = ConnectionPool._get(self)
- try:
- # Propagate host state known to us to this client's list
- now = time.time()
- for deaduntil, host in zip(self._hosts_deaduntil, conn.servers):
- if deaduntil > now and host.deaduntil <= now:
- host.mark_dead('propagating death mark from the pool')
- host.deaduntil = deaduntil
- except Exception:
- # We need to be sure that connection doesn't leak from the pool.
- # This code runs before we enter context manager's try-finally
- # block, so we need to explicitly release it here
- ConnectionPool._put(self, conn)
- raise
- return conn
-
- def _put(self, conn):
- try:
- # If this client found that one of the hosts is dead, mark it as
- # such in our internal list
- now = time.time()
- for i, deaduntil, host in zip(itertools.count(),
- self._hosts_deaduntil,
- conn.servers):
- # Do nothing if we already know this host is dead
- if deaduntil <= now:
- if host.deaduntil > now:
- self._hosts_deaduntil[i] = host.deaduntil
- else:
- self._hosts_deaduntil[i] = 0
- # If all hosts are dead we should forget that they're dead. This
- # way we won't get completely shut off until dead_retry seconds
- # pass, but will be checking servers as frequent as we can (over
- # way smaller socket_timeout)
- if all(deaduntil > now for deaduntil in self._hosts_deaduntil):
- self._hosts_deaduntil[:] = [0] * len(self._hosts_deaduntil)
- finally:
- ConnectionPool._put(self, conn)
diff --git a/keystonemiddleware-moon/keystonemiddleware/auth_token/_request.py b/keystonemiddleware-moon/keystonemiddleware/auth_token/_request.py
deleted file mode 100644
index 72fd5380..00000000
--- a/keystonemiddleware-moon/keystonemiddleware/auth_token/_request.py
+++ /dev/null
@@ -1,224 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import itertools
-
-from oslo_serialization import jsonutils
-import six
-import webob
-
-
-def _v3_to_v2_catalog(catalog):
- """Convert a catalog to v2 format.
-
- X_SERVICE_CATALOG must be specified in v2 format. If you get a token
- that is in v3 convert it.
- """
- v2_services = []
- for v3_service in catalog:
- # first copy over the entries we allow for the service
- v2_service = {'type': v3_service['type']}
- try:
- v2_service['name'] = v3_service['name']
- except KeyError:
- pass
-
- # now convert the endpoints. Because in v3 we specify region per
- # URL not per group we have to collect all the entries of the same
- # region together before adding it to the new service.
- regions = {}
- for v3_endpoint in v3_service.get('endpoints', []):
- region_name = v3_endpoint.get('region')
- try:
- region = regions[region_name]
- except KeyError:
- region = {'region': region_name} if region_name else {}
- regions[region_name] = region
-
- interface_name = v3_endpoint['interface'].lower() + 'URL'
- region[interface_name] = v3_endpoint['url']
-
- v2_service['endpoints'] = list(regions.values())
- v2_services.append(v2_service)
-
- return v2_services
-
-
-# NOTE(jamielennox): this should probably be moved into its own file, but at
-# the moment there's no real logic here so just keep it locally.
-class _AuthTokenResponse(webob.Response):
-
- default_content_type = None # prevents webob assigning a content type
-
-
-class _AuthTokenRequest(webob.Request):
-
- ResponseClass = _AuthTokenResponse
-
- _HEADER_TEMPLATE = {
- 'X%s-Domain-Id': 'domain_id',
- 'X%s-Domain-Name': 'domain_name',
- 'X%s-Project-Id': 'project_id',
- 'X%s-Project-Name': 'project_name',
- 'X%s-Project-Domain-Id': 'project_domain_id',
- 'X%s-Project-Domain-Name': 'project_domain_name',
- 'X%s-User-Id': 'user_id',
- 'X%s-User-Name': 'username',
- 'X%s-User-Domain-Id': 'user_domain_id',
- 'X%s-User-Domain-Name': 'user_domain_name',
- }
-
- _ROLES_TEMPLATE = 'X%s-Roles'
-
- _USER_HEADER_PREFIX = ''
- _SERVICE_HEADER_PREFIX = '-Service'
-
- _USER_STATUS_HEADER = 'X-Identity-Status'
- _SERVICE_STATUS_HEADER = 'X-Service-Identity-Status'
-
- _SERVICE_CATALOG_HEADER = 'X-Service-Catalog'
- _TOKEN_AUTH = 'keystone.token_auth'
-
- _CONFIRMED = 'Confirmed'
- _INVALID = 'Invalid'
-
- # header names that have been deprecated in favour of something else.
- _DEPRECATED_HEADER_MAP = {
- 'X-Role': 'X-Roles',
- 'X-User': 'X-User-Name',
- 'X-Tenant-Id': 'X-Project-Id',
- 'X-Tenant-Name': 'X-Project-Name',
- 'X-Tenant': 'X-Project-Name',
- }
-
- def _confirmed(cls, value):
- return cls._CONFIRMED if value else cls._INVALID
-
- @property
- def user_token_valid(self):
- """User token is marked as valid.
-
- :returns: True if the X-Identity-Status header is set to Confirmed.
- :rtype: bool
- """
- return self.headers[self._USER_STATUS_HEADER] == self._CONFIRMED
-
- @user_token_valid.setter
- def user_token_valid(self, value):
- self.headers[self._USER_STATUS_HEADER] = self._confirmed(value)
-
- @property
- def user_token(self):
- return self.headers.get('X-Auth-Token',
- self.headers.get('X-Storage-Token'))
-
- @property
- def service_token_valid(self):
- """Service token is marked as valid.
-
- :returns: True if the X-Service-Identity-Status header
- is set to Confirmed.
- :rtype: bool
- """
- return self.headers[self._SERVICE_STATUS_HEADER] == self._CONFIRMED
-
- @service_token_valid.setter
- def service_token_valid(self, value):
- self.headers[self._SERVICE_STATUS_HEADER] = self._confirmed(value)
-
- @property
- def service_token(self):
- return self.headers.get('X-Service-Token')
-
- def _set_auth_headers(self, auth_ref, prefix):
- names = ','.join(auth_ref.role_names)
- self.headers[self._ROLES_TEMPLATE % prefix] = names
-
- for header_tmplt, attr in six.iteritems(self._HEADER_TEMPLATE):
- self.headers[header_tmplt % prefix] = getattr(auth_ref, attr)
-
- def set_user_headers(self, auth_ref, include_service_catalog):
- """Convert token object into headers.
-
- Build headers that represent authenticated user - see main
- doc info at start of __init__ file for details of headers to be defined
- """
- self._set_auth_headers(auth_ref, self._USER_HEADER_PREFIX)
-
- for k, v in six.iteritems(self._DEPRECATED_HEADER_MAP):
- self.headers[k] = self.headers[v]
-
- if include_service_catalog and auth_ref.has_service_catalog():
- catalog = auth_ref.service_catalog.get_data()
- if auth_ref.version == 'v3':
- catalog = _v3_to_v2_catalog(catalog)
-
- c = jsonutils.dumps(catalog)
- self.headers[self._SERVICE_CATALOG_HEADER] = c
-
- self.user_token_valid = True
-
- def set_service_headers(self, auth_ref):
- """Convert token object into service headers.
-
- Build headers that represent authenticated user - see main
- doc info at start of __init__ file for details of headers to be defined
- """
- self._set_auth_headers(auth_ref, self._SERVICE_HEADER_PREFIX)
- self.service_token_valid = True
-
- def _all_auth_headers(self):
- """All the authentication headers that can be set on the request"""
- yield self._SERVICE_CATALOG_HEADER
- yield self._USER_STATUS_HEADER
- yield self._SERVICE_STATUS_HEADER
-
- for header in self._DEPRECATED_HEADER_MAP:
- yield header
-
- prefixes = (self._USER_HEADER_PREFIX, self._SERVICE_HEADER_PREFIX)
-
- for tmpl, prefix in itertools.product(self._HEADER_TEMPLATE, prefixes):
- yield tmpl % prefix
-
- for prefix in prefixes:
- yield self._ROLES_TEMPLATE % prefix
-
- def remove_auth_headers(self):
- """Remove headers so a user can't fake authentication."""
- for header in self._all_auth_headers():
- self.headers.pop(header, None)
-
- @property
- def auth_type(self):
- """The authentication type that was performed by the web server.
-
- The returned string value is always lower case.
-
- :returns: The AUTH_TYPE environ string or None if not present.
- :rtype: str or None
- """
- try:
- auth_type = self.environ['AUTH_TYPE']
- except KeyError:
- return None
- else:
- return auth_type.lower()
-
- @property
- def token_auth(self):
- """The auth plugin that will be associated with this request"""
- return self.environ.get(self._TOKEN_AUTH)
-
- @token_auth.setter
- def token_auth(self, v):
- self.environ[self._TOKEN_AUTH] = v
diff --git a/keystonemiddleware-moon/keystonemiddleware/auth_token/_revocations.py b/keystonemiddleware-moon/keystonemiddleware/auth_token/_revocations.py
deleted file mode 100644
index a68356a8..00000000
--- a/keystonemiddleware-moon/keystonemiddleware/auth_token/_revocations.py
+++ /dev/null
@@ -1,128 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import datetime
-import logging
-import os
-
-from oslo_serialization import jsonutils
-from oslo_utils import timeutils
-
-from keystonemiddleware.auth_token import _exceptions as exc
-from keystonemiddleware.i18n import _
-
-_LOG = logging.getLogger(__name__)
-
-
-class Revocations(object):
- _FILE_NAME = 'revoked.pem'
-
- def __init__(self, timeout, signing_directory, identity_server,
- cms_verify, log=_LOG):
- self._cache_timeout = timeout
- self._signing_directory = signing_directory
- self._identity_server = identity_server
- self._cms_verify = cms_verify
- self._log = log
-
- self._fetched_time_prop = None
- self._list_prop = None
-
- @property
- def _fetched_time(self):
- if not self._fetched_time_prop:
- # If the fetched list has been written to disk, use its
- # modification time.
- file_path = self._signing_directory.calc_path(self._FILE_NAME)
- if os.path.exists(file_path):
- mtime = os.path.getmtime(file_path)
- fetched_time = datetime.datetime.utcfromtimestamp(mtime)
- # Otherwise the list will need to be fetched.
- else:
- fetched_time = datetime.datetime.min
- self._fetched_time_prop = fetched_time
- return self._fetched_time_prop
-
- @_fetched_time.setter
- def _fetched_time(self, value):
- self._fetched_time_prop = value
-
- def _fetch(self):
- revocation_list_data = self._identity_server.fetch_revocation_list()
- return self._cms_verify(revocation_list_data)
-
- @property
- def _list(self):
- timeout = self._fetched_time + self._cache_timeout
- list_is_current = timeutils.utcnow() < timeout
-
- if list_is_current:
- # Load the list from disk if required
- if not self._list_prop:
- self._list_prop = jsonutils.loads(
- self._signing_directory.read_file(self._FILE_NAME))
- else:
- self._list = self._fetch()
- return self._list_prop
-
- @_list.setter
- def _list(self, value):
- """Save a revocation list to memory and to disk.
-
- :param value: A json-encoded revocation list
-
- """
- self._list_prop = jsonutils.loads(value)
- self._fetched_time = timeutils.utcnow()
- self._signing_directory.write_file(self._FILE_NAME, value)
-
- def _is_revoked(self, token_id):
- """Indicate whether the token_id appears in the revocation list."""
- revoked_tokens = self._list.get('revoked', None)
- if not revoked_tokens:
- return False
-
- revoked_ids = (x['id'] for x in revoked_tokens)
- return token_id in revoked_ids
-
- def _any_revoked(self, token_ids):
- for token_id in token_ids:
- if self._is_revoked(token_id):
- return True
- return False
-
- def check(self, token_ids):
- if self._any_revoked(token_ids):
- self._log.debug('Token is marked as having been revoked')
- raise exc.InvalidToken(_('Token has been revoked'))
-
- def check_by_audit_id(self, audit_ids):
- """Check whether the audit_id appears in the revocation list.
-
- :raises keystonemiddleware.auth_token._exceptions.InvalidToken:
- if the audit ID(s) appear in the revocation list.
-
- """
- revoked_tokens = self._list.get('revoked', None)
- if not revoked_tokens:
- # There's no revoked tokens, so nothing to do.
- return
-
- # The audit_id may not be present in the revocation events because
- # earlier versions of the identity server didn't provide them.
- revoked_ids = set(
- x['audit_id'] for x in revoked_tokens if 'audit_id' in x)
- for audit_id in audit_ids:
- if audit_id in revoked_ids:
- self._log.debug(
- 'Token is marked as having been revoked by audit id')
- raise exc.InvalidToken(_('Token has been revoked'))
diff --git a/keystonemiddleware-moon/keystonemiddleware/auth_token/_signing_dir.py b/keystonemiddleware-moon/keystonemiddleware/auth_token/_signing_dir.py
deleted file mode 100644
index f8b1a410..00000000
--- a/keystonemiddleware-moon/keystonemiddleware/auth_token/_signing_dir.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import logging
-import os
-import stat
-import tempfile
-
-import six
-
-from keystonemiddleware.auth_token import _exceptions as exc
-from keystonemiddleware.i18n import _, _LI, _LW
-
-_LOG = logging.getLogger(__name__)
-
-
-class SigningDirectory(object):
-
- def __init__(self, directory_name=None, log=None):
- self._log = log or _LOG
-
- if directory_name is None:
- directory_name = tempfile.mkdtemp(prefix='keystone-signing-')
- self._log.info(
- _LI('Using %s as cache directory for signing certificate'),
- directory_name)
- self._directory_name = directory_name
-
- self._verify_signing_dir()
-
- def write_file(self, file_name, new_contents):
-
- # In Python2, encoding is slow so the following check avoids it if it
- # is not absolutely necessary.
- if isinstance(new_contents, six.text_type):
- new_contents = new_contents.encode('utf-8')
-
- def _atomic_write():
- with tempfile.NamedTemporaryFile(dir=self._directory_name,
- delete=False) as f:
- f.write(new_contents)
- os.rename(f.name, self.calc_path(file_name))
-
- try:
- _atomic_write()
- except (OSError, IOError):
- self._verify_signing_dir()
- _atomic_write()
-
- def read_file(self, file_name):
- path = self.calc_path(file_name)
- open_kwargs = {'encoding': 'utf-8'} if six.PY3 else {}
- with open(path, 'r', **open_kwargs) as f:
- return f.read()
-
- def calc_path(self, file_name):
- return os.path.join(self._directory_name, file_name)
-
- def _verify_signing_dir(self):
- if os.path.isdir(self._directory_name):
- if not os.access(self._directory_name, os.W_OK):
- raise exc.ConfigurationError(
- _('unable to access signing_dir %s') %
- self._directory_name)
- uid = os.getuid()
- if os.stat(self._directory_name).st_uid != uid:
- self._log.warning(_LW('signing_dir is not owned by %s'), uid)
- current_mode = stat.S_IMODE(os.stat(self._directory_name).st_mode)
- if current_mode != stat.S_IRWXU:
- self._log.warning(
- _LW('signing_dir mode is %(mode)s instead of %(need)s'),
- {'mode': oct(current_mode), 'need': oct(stat.S_IRWXU)})
- else:
- os.makedirs(self._directory_name, stat.S_IRWXU)
diff --git a/keystonemiddleware-moon/keystonemiddleware/auth_token/_user_plugin.py b/keystonemiddleware-moon/keystonemiddleware/auth_token/_user_plugin.py
deleted file mode 100644
index 93075c5c..00000000
--- a/keystonemiddleware-moon/keystonemiddleware/auth_token/_user_plugin.py
+++ /dev/null
@@ -1,193 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from keystoneclient.auth.identity import base as base_identity
-
-
-class _TokenData(object):
- """An abstraction to show auth_token consumers some of the token contents.
-
- This is a simplified and cleaned up keystoneclient.access.AccessInfo object
- with which services relying on auth_token middleware can find details of
- the current token.
- """
-
- def __init__(self, auth_ref):
- self._stored_auth_ref = auth_ref
-
- @property
- def _is_v2(self):
- return self._stored_auth_ref.version == 'v2.0'
-
- @property
- def auth_token(self):
- """The token data used to authenticate requests.
-
- :returns: token data.
- :rtype: str
- """
- return self._stored_auth_ref.auth_token
-
- @property
- def user_id(self):
- """The user id associated with the authentication request.
-
- :rtype: str
- """
- return self._stored_auth_ref.user_id
-
- @property
- def user_domain_id(self):
- """Returns the domain id of the user associated with the authentication
- request.
-
- :returns: str
- """
- # NOTE(jamielennox): v2 AccessInfo returns 'default' for domain_id
- # because it can't know that value. We want to return None instead.
- if self._is_v2:
- return None
-
- return self._stored_auth_ref.user_domain_id
-
- @property
- def project_id(self):
- """The project ID associated with the authentication.
-
- :rtype: str
- """
- return self._stored_auth_ref.project_id
-
- @property
- def project_domain_id(self):
- """The domain id of the project associated with the authentication
- request.
-
- :rtype: str
- """
- # NOTE(jamielennox): v2 AccessInfo returns 'default' for domain_id
- # because it can't know that value. We want to return None instead.
- if self._is_v2:
- return None
-
- return self._stored_auth_ref.project_domain_id
-
- @property
- def trust_id(self):
- """Returns the trust id associated with the authentication request..
-
- :rtype: str
- """
- return self._stored_auth_ref.trust_id
-
- @property
- def role_ids(self):
- """Role ids of the user associated with the authentication request.
-
- :rtype: set(str)
- """
- return frozenset(self._stored_auth_ref.role_ids or [])
-
- @property
- def role_names(self):
- """Role names of the user associated with the authentication request.
-
- :rtype: set(str)
- """
- return frozenset(self._stored_auth_ref.role_names or [])
-
- @property
- def _log_format(self):
- roles = ','.join(self.role_names)
- return 'user_id %s, project_id %s, roles %s' % (self.user_id,
- self.project_id,
- roles)
-
-
-class UserAuthPlugin(base_identity.BaseIdentityPlugin):
- """The incoming authentication credentials.
-
- A plugin that represents the incoming user credentials. This can be
- consumed by applications.
-
- This object is not expected to be constructed directly by users. It is
- created and passed by auth_token middleware and then can be used as the
- authentication plugin when communicating via a session.
- """
-
- def __init__(self, user_auth_ref, serv_auth_ref):
- super(UserAuthPlugin, self).__init__(reauthenticate=False)
-
- # NOTE(jamielennox): _user_auth_ref and _serv_auth_ref are private
- # because this object ends up in the environ that is passed to the
- # service, however they are used within auth_token middleware.
- self._user_auth_ref = user_auth_ref
- self._serv_auth_ref = serv_auth_ref
-
- self._user_data = None
- self._serv_data = None
-
- @property
- def has_user_token(self):
- """Did this authentication request contained a user auth token."""
- return self._user_auth_ref is not None
-
- @property
- def user(self):
- """Authentication information about the user token.
-
- Will return None if a user token was not passed with this request.
- """
- if not self.has_user_token:
- return None
-
- if not self._user_data:
- self._user_data = _TokenData(self._user_auth_ref)
-
- return self._user_data
-
- @property
- def has_service_token(self):
- """Did this authentication request contained a service token."""
- return self._serv_auth_ref is not None
-
- @property
- def service(self):
- """Authentication information about the service token.
-
- Will return None if a user token was not passed with this request.
- """
- if not self.has_service_token:
- return None
-
- if not self._serv_data:
- self._serv_data = _TokenData(self._serv_auth_ref)
-
- return self._serv_data
-
- def get_auth_ref(self, session, **kwargs):
- # NOTE(jamielennox): We will always use the auth_ref that was
- # calculated by the middleware. reauthenticate=False in __init__ should
- # ensure that this function is only called on the first access.
- return self._user_auth_ref
-
- @property
- def _log_format(self):
- msg = []
-
- if self.has_user_token:
- msg.append('user: %s' % self.user._log_format)
-
- if self.has_service_token:
- msg.append('service: %s' % self.service._log_format)
-
- return ' '.join(msg)
diff --git a/keystonemiddleware-moon/keystonemiddleware/auth_token/_utils.py b/keystonemiddleware-moon/keystonemiddleware/auth_token/_utils.py
deleted file mode 100644
index daed02dd..00000000
--- a/keystonemiddleware-moon/keystonemiddleware/auth_token/_utils.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from six.moves import urllib
-
-
-def safe_quote(s):
- """URL-encode strings that are not already URL-encoded."""
- return urllib.parse.quote(s) if s == urllib.parse.unquote(s) else s
-
-
-class MiniResp(object):
-
- def __init__(self, error_message, env, headers=[]):
- # The HEAD method is unique: it must never return a body, even if
- # it reports an error (RFC-2616 clause 9.4). We relieve callers
- # from varying the error responses depending on the method.
- if env['REQUEST_METHOD'] == 'HEAD':
- self.body = ['']
- else:
- self.body = [error_message.encode()]
- self.headers = list(headers)
- self.headers.append(('Content-type', 'text/plain'))