# 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): # 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 tuple version: The version number required for this endpoint. :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. :( if version[0] == 2: return '%s/v2.0' % self._identity_uri elif version[0] == 3: 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)