diff options
26 files changed, 276 insertions, 136 deletions
diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample index 4c0327cf..a49a9a5e 100644 --- a/etc/keystone.conf.sample +++ b/etc/keystone.conf.sample @@ -128,7 +128,8 @@ # driver = keystone.token.backends.sql.Token # Controls the token construction, validation, and revocation operations. -# provider = keystone.token.providers.pki.Provider +# Core providers are keystone.token.providers.[pki|uuid].Provider +# provider = # Amount of time a token should remain valid (in seconds) # expiration = 86400 @@ -165,7 +166,8 @@ [signing] # Deprecated in favor of provider in the [token] section -#token_format = PKI +# Allowed values are PKI or UUID +#token_format = #certfile = /etc/keystone/pki/certs/signing_cert.pem #keyfile = /etc/keystone/pki/private/signing_key.pem diff --git a/keystone/assignment/backends/kvs.py b/keystone/assignment/backends/kvs.py index 4dfd908f..30d7b2eb 100644 --- a/keystone/assignment/backends/kvs.py +++ b/keystone/assignment/backends/kvs.py @@ -16,11 +16,13 @@ from keystone import assignment from keystone import clean +from keystone.common import dependency from keystone.common import kvs from keystone import exception from keystone import identity +@dependency.requires('identity_api') class Assignment(kvs.Base, assignment.Driver): def __init__(self): super(Assignment, self).__init__() diff --git a/keystone/assignment/backends/ldap.py b/keystone/assignment/backends/ldap.py index f8c81eae..9b273e40 100644 --- a/keystone/assignment/backends/ldap.py +++ b/keystone/assignment/backends/ldap.py @@ -21,6 +21,7 @@ import ldap as ldap from keystone import assignment from keystone import clean +from keystone.common import dependency from keystone.common import ldap as common_ldap from keystone.common import logging from keystone.common import models @@ -39,6 +40,7 @@ DEFAULT_DOMAIN = { } +@dependency.requires('identity_api') class Assignment(assignment.Driver): def __init__(self): super(Assignment, self).__init__() diff --git a/keystone/assignment/backends/sql.py b/keystone/assignment/backends/sql.py index 5ec435ff..024a291a 100644 --- a/keystone/assignment/backends/sql.py +++ b/keystone/assignment/backends/sql.py @@ -16,15 +16,14 @@ from keystone import assignment from keystone import clean +from keystone.common import dependency from keystone.common import sql from keystone.common.sql import migration from keystone import exception +@dependency.requires('identity_api') class Assignment(sql.Base, assignment.Driver): - def __init__(self): - super(Assignment, self).__init__() - self.identity_api = None # Internal interface to manage the database def db_sync(self, version=None): diff --git a/keystone/assignment/core.py b/keystone/assignment/core.py index b71e2a18..64edb3fa 100644 --- a/keystone/assignment/core.py +++ b/keystone/assignment/core.py @@ -35,6 +35,7 @@ DEFAULT_DOMAIN = {'description': @dependency.provider('assignment_api') +@dependency.requires('identity_api') class Manager(manager.Manager): """Default pivot point for the Assignment backend. @@ -45,18 +46,14 @@ class Manager(manager.Manager): api object by both managers. """ - def __init__(self, identity_api=None): - if identity_api is None: - from keystone import identity - identity_api = identity.Manager(self) - + def __init__(self): assignment_driver = CONF.assignment.driver + if assignment_driver is None: - assignment_driver = identity_api.default_assignment_driver() + identity_driver = dependency.REGISTRY['identity_api'].driver + assignment_driver = identity_driver.default_assignment_driver() + super(Manager, self).__init__(assignment_driver) - self.driver.identity_api = identity_api - self.identity_api = identity_api - self.identity_api.assignment_api = self def get_roles_for_user_and_project(self, user_id, tenant_id): """Get the roles associated with a user within given project. diff --git a/keystone/common/config.py b/keystone/common/config.py index b0a534f8..10c47a35 100644 --- a/keystone/common/config.py +++ b/keystone/common/config.py @@ -240,7 +240,7 @@ def configure(): # signing register_str( - 'token_format', group='signing', default="PKI") + 'token_format', group='signing', default=None) register_str( 'certfile', group='signing', diff --git a/keystone/common/dependency.py b/keystone/common/dependency.py index dc3e4ac4..a640031d 100644 --- a/keystone/common/dependency.py +++ b/keystone/common/dependency.py @@ -16,6 +16,8 @@ REGISTRY = {} +_future_dependencies = {} + class UnresolvableDependencyException(Exception): def __init__(self, name): @@ -32,6 +34,8 @@ def provider(name): init(self, *args, **kwargs) REGISTRY[name] = self + resolve_future_dependencies(name) + return __wrapped_init__ cls.__init__ = wrapped(cls.__init__) @@ -48,7 +52,13 @@ def requires(*dependencies): for dependency in self._dependencies: if dependency not in REGISTRY: - raise UnresolvableDependencyException(dependency) + if dependency in _future_dependencies: + _future_dependencies[dependency] += [self] + else: + _future_dependencies[dependency] = [self] + + continue + setattr(self, dependency, REGISTRY[dependency]) def wrapped(cls): @@ -65,3 +75,34 @@ def requires(*dependencies): return cls return wrapped + + +def resolve_future_dependencies(provider_name=None): + if provider_name: + targets = _future_dependencies.pop(provider_name, []) + + for target in targets: + setattr(target, provider_name, REGISTRY[provider_name]) + + return + + try: + for dependency, targets in _future_dependencies.iteritems(): + if dependency not in REGISTRY: + raise UnresolvableDependencyException(dependency) + + for target in targets: + setattr(target, dependency, REGISTRY[dependency]) + finally: + _future_dependencies.clear() + + +def reset(): + """Reset the registry of providers. + + This is useful for unit testing to ensure that tests don't use providers + from previous tests. + """ + + REGISTRY.clear() + _future_dependencies.clear() diff --git a/keystone/common/sql/core.py b/keystone/common/sql/core.py index 7978fcc5..2d3114f2 100644 --- a/keystone/common/sql/core.py +++ b/keystone/common/sql/core.py @@ -243,9 +243,10 @@ class Base(object): def get_session(self, autocommit=True, expire_on_commit=False): """Return a SQLAlchemy session.""" - self._engine = self._engine or self.get_engine() - self._sessionmaker = self._sessionmaker or self.get_sessionmaker( - self._engine) + if not self._engine: + self._engine = self.get_engine() + self._sessionmaker = self.get_sessionmaker(self._engine) + register_global_engine_callback(self.clear_engine) return self._sessionmaker(autocommit=autocommit, expire_on_commit=expire_on_commit) @@ -300,6 +301,10 @@ class Base(object): autocommit=autocommit, expire_on_commit=expire_on_commit) + def clear_engine(self): + self._engine = None + self._sessionmaker = None + def handle_conflicts(type='object'): """Converts IntegrityError into HTTP 409 Conflict.""" diff --git a/keystone/common/wsgi.py b/keystone/common/wsgi.py index c7e30576..87636dbe 100644 --- a/keystone/common/wsgi.py +++ b/keystone/common/wsgi.py @@ -203,7 +203,7 @@ class BaseApplication(object): class Application(BaseApplication): - @webob.dec.wsgify + @webob.dec.wsgify(RequestClass=Request) def __call__(self, req): arg_dict = req.environ['wsgiorg.routing_args'][1] action = arg_dict.pop('action') @@ -583,7 +583,7 @@ def render_exception(error): body = {'error': { 'code': error.code, 'title': error.title, - 'message': str(error) + 'message': unicode(error) }} if isinstance(error, exception.AuthPluginException): body['error']['identity'] = error.authentication diff --git a/keystone/exception.py b/keystone/exception.py index db5f5005..5e1defba 100644 --- a/keystone/exception.py +++ b/keystone/exception.py @@ -13,10 +13,10 @@ # 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 re from keystone.common import config from keystone.common import logging +from keystone.openstack.common.gettextutils import _ # noqa CONF = config.CONF @@ -29,15 +29,15 @@ _FATAL_EXCEPTION_FORMAT_ERRORS = False class Error(StandardError): """Base error class. - Child classes should define an HTTP status code, title, and a doc string. + Child classes should define an HTTP status code, title, and a + message_format. """ code = None title = None + message_format = None def __init__(self, message=None, **kwargs): - """Use the doc string as the error message by default.""" - try: message = self._build_message(message, **kwargs) except KeyError: @@ -45,8 +45,8 @@ class Error(StandardError): if _FATAL_EXCEPTION_FORMAT_ERRORS: raise else: - LOG.warning('missing exception kwargs (programmer error)') - message = self.__doc__ + LOG.warning(_('missing exception kwargs (programmer error)')) + message = self.message_format super(Error, self).__init__(message) @@ -57,42 +57,31 @@ class Error(StandardError): """ if not message: - message = re.sub('[ \n]+', ' ', self.__doc__ % kwargs) - message = message.strip() + message = self.message_format % kwargs return message class ValidationError(Error): - """Expecting to find %(attribute)s in %(target)s. - - The server could not comply with the request since it is either malformed - or otherwise incorrect. - - The client is assumed to be in error. - - """ + message_format = _("Expecting to find %(attribute)s in %(target)s." + " The server could not comply with the request" + " since it is either malformed or otherwise" + " incorrect. The client is assumed to be in error.") code = 400 title = 'Bad Request' class StringLengthExceeded(ValidationError): - """String length exceeded. - - The length of string "%(string)s" exceeded the limit of column - %(type)s(CHAR(%(length)d)). - - """ + message_format = _("String length exceeded.The length of" + " string '%(string)s' exceeded the limit" + " of column %(type)s(CHAR(%(length)d)).") class ValidationSizeError(Error): - """Request attribute %(attribute)s must be less than or equal to %(size)i. - - The server could not comply with the request because the attribute - size is invalid (too large). - - The client is assumed to be in error. - - """ + message_format = _("Request attribute %(attribute)s must be" + " less than or equal to %(size)i. The server" + " could not comply with the request because" + " the attribute size is invalid (too large)." + " The client is assumed to be in error.") code = 400 title = 'Bad Request' @@ -103,19 +92,19 @@ class SecurityError(Error): def _build_message(self, message, **kwargs): """Only returns detailed messages in debug mode.""" if CONF.debug: - return message or self.__doc__ % kwargs + return message or self.message_format % kwargs else: - return self.__doc__ % kwargs + return self.message_format % kwargs class Unauthorized(SecurityError): - """The request you have made requires authentication.""" + message_format = _("The request you have made requires authentication.") code = 401 title = 'Unauthorized' class AuthPluginException(Unauthorized): - """Authentication plugin error.""" + message_format = _("Authentication plugin error.") def __init__(self, *args, **kwargs): super(AuthPluginException, self).__init__(*args, **kwargs) @@ -123,7 +112,7 @@ class AuthPluginException(Unauthorized): class AuthMethodNotSupported(AuthPluginException): - """Attempted to authenticate with an unsupported method.""" + message_format = _("Attempted to authenticate with an unsupported method.") def __init__(self, *args, **kwargs): super(AuthMethodNotSupported, self).__init__(*args, **kwargs) @@ -131,7 +120,7 @@ class AuthMethodNotSupported(AuthPluginException): class AdditionalAuthRequired(AuthPluginException): - """Additional authentications steps required.""" + message_format = _("Additional authentications steps required.") def __init__(self, auth_response=None, **kwargs): super(AdditionalAuthRequired, self).__init__(message=None, **kwargs) @@ -139,112 +128,111 @@ class AdditionalAuthRequired(AuthPluginException): class Forbidden(SecurityError): - """You are not authorized to perform the requested action.""" + message_format = _("You are not authorized to perform the" + " requested action.") code = 403 title = 'Forbidden' class ForbiddenAction(Forbidden): - """You are not authorized to perform the requested action, %(action)s.""" + message_format = _("You are not authorized to perform the" + " requested action, %(action)s.") class NotFound(Error): - """Could not find, %(target)s.""" + message_format = _("Could not find, %(target)s.") code = 404 title = 'Not Found' class EndpointNotFound(NotFound): - """Could not find endpoint, %(endpoint_id)s.""" + message_format = _("Could not find endpoint, %(endpoint_id)s.") class MetadataNotFound(NotFound): - """An unhandled exception has occurred: Could not find metadata.""" - # (dolph): metadata is not a user-facing concept, - # so this exception should not be exposed + """(dolph): metadata is not a user-facing concept, + so this exception should not be exposed + """ + message_format = _("An unhandled exception has occurred:" + " Could not find metadata.") class PolicyNotFound(NotFound): - """Could not find policy, %(policy_id)s.""" + message_format = _("Could not find policy, %(policy_id)s.") class RoleNotFound(NotFound): - """Could not find role, %(role_id)s.""" + message_format = _("Could not find role, %(role_id)s.") class ServiceNotFound(NotFound): - """Could not find service, %(service_id)s.""" + message_format = _("Could not find service, %(service_id)s.") class DomainNotFound(NotFound): - """Could not find domain, %(domain_id)s.""" + message_format = _("Could not find domain, %(domain_id)s.") class ProjectNotFound(NotFound): - """Could not find project, %(project_id)s.""" + message_format = _("Could not find project, %(project_id)s.") class TokenNotFound(NotFound): - """Could not find token, %(token_id)s.""" + message_format = _("Could not find token, %(token_id)s.") class UserNotFound(NotFound): - """Could not find user, %(user_id)s.""" + message_format = _("Could not find user, %(user_id)s.") class GroupNotFound(NotFound): - """Could not find group, %(group_id)s.""" + message_format = _("Could not find group, %(group_id)s.") class TrustNotFound(NotFound): - """Could not find trust, %(trust_id)s.""" + message_format = _("Could not find trust, %(trust_id)s.") class CredentialNotFound(NotFound): - """Could not find credential, %(credential_id)s.""" + message_format = _("Could not find credential, %(credential_id)s.") class VersionNotFound(NotFound): - """Could not find version, %(version)s.""" + message_format = _("Could not find version, %(version)s.") class Conflict(Error): - """Conflict occurred attempting to store %(type)s. - - %(details)s - - """ + message_format = _("Conflict occurred attempting to store %(type)s." + " %(details)s") code = 409 title = 'Conflict' class RequestTooLarge(Error): - """Request is too large.""" + message_format = _("Request is too large.") code = 413 title = 'Request is too large.' class UnexpectedError(Error): - """An unexpected error prevented the server from fulfilling your request. - - %(exception)s - - """ + message_format = _("An unexpected error prevented the server" + " from fulfilling your request. %(exception)s") code = 500 title = 'Internal Server Error' class MalformedEndpoint(UnexpectedError): - """Malformed endpoint URL (%(endpoint)s), see ERROR log for details.""" + message_format = _("Malformed endpoint URL (%(endpoint)s)," + " see ERROR log for details.") class NotImplemented(Error): - """The action you have requested has not been implemented.""" + message_format = _("The action you have requested has not" + " been implemented.") code = 501 title = 'Not Implemented' class PasteConfigNotFound(UnexpectedError): - """The Keystone paste configuration file %(config_file)s could not be - found. - """ + message_format = _("The Keystone paste configuration file" + " %(config_file)s could not be found.") diff --git a/keystone/identity/backends/kvs.py b/keystone/identity/backends/kvs.py index 83535108..0323d3d0 100644 --- a/keystone/identity/backends/kvs.py +++ b/keystone/identity/backends/kvs.py @@ -28,7 +28,7 @@ class Identity(kvs.Base, identity.Driver): return "keystone.assignment.backends.kvs.Assignment" # Public interface - def authenticate(self, user_id=None, password=None): + def authenticate(self, user_id, password): user_ref = None try: user_ref = self._get_user(user_id) diff --git a/keystone/identity/backends/ldap.py b/keystone/identity/backends/ldap.py index 7f5cedc3..91ea1e41 100644 --- a/keystone/identity/backends/ldap.py +++ b/keystone/identity/backends/ldap.py @@ -19,6 +19,7 @@ import uuid import ldap from keystone import clean +from keystone.common import dependency from keystone.common import ldap as common_ldap from keystone.common import logging from keystone.common import models @@ -38,6 +39,7 @@ DEFAULT_DOMAIN = { } +@dependency.requires('assignment_api') class Identity(identity.Driver): def __init__(self): super(Identity, self).__init__() @@ -52,7 +54,7 @@ class Identity(identity.Driver): def create_project(self, project_id, project): return self.assignment_api.create_project(project_id, project) - def authenticate(self, user_id=None, password=None): + def authenticate(self, user_id, password): try: user_ref = self._get_user(user_id) except exception.UserNotFound: @@ -77,7 +79,8 @@ class Identity(identity.Driver): return self.assignment_api._set_default_domain(ref) def list_users(self): - return self.assignment_api._set_default_domain(self.user.get_all()) + return (self.assignment_api._set_default_domain + (self.user.get_all_filtered())) def get_user_by_name(self, user_name, domain_id): self.assignment_api._validate_default_domain_id(domain_id) @@ -181,7 +184,7 @@ class Identity(identity.Driver): for user_dn in self.group.list_group_users(group_id): user_id = self.user._dn_to_id(user_dn) try: - users.append(self.user.get(user_id)) + users.append(self.user.get_filtered(user_id)) except exception.UserNotFound: LOG.debug(_("Group member '%(user_dn)s' not found in" " '%(group_id)s'. The user should be removed" @@ -264,6 +267,9 @@ class UserApi(common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap): user = self.get(user_id) return identity.filter_user(user) + def get_all_filtered(self): + return [identity.filter_user(user) for user in self.get_all()] + class GroupApi(common_ldap.BaseLdap): DEFAULT_OU = 'ou=UserGroups' diff --git a/keystone/identity/backends/pam.py b/keystone/identity/backends/pam.py index 5cfa5b16..2a6ee621 100644 --- a/keystone/identity/backends/pam.py +++ b/keystone/identity/backends/pam.py @@ -58,7 +58,7 @@ class PamIdentity(identity.Driver): Tenant is always the same as User, root user has admin role. """ - def authenticate(self, user_id=None, password=None): + def authenticate(self, user_id, password): auth = pam.authenticate if pam else PAM_authenticate if not auth(user_id, password): raise AssertionError('Invalid user / password') diff --git a/keystone/identity/backends/sql.py b/keystone/identity/backends/sql.py index 2c00088e..bff41106 100644 --- a/keystone/identity/backends/sql.py +++ b/keystone/identity/backends/sql.py @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. +from keystone.common import dependency from keystone.common import sql from keystone.common.sql import migration from keystone.common import utils @@ -61,6 +62,7 @@ class UserGroupMembership(sql.ModelBase, sql.DictBase): primary_key=True) +@dependency.requires('assignment_api') class Identity(sql.Base, identity.Driver): def default_assignment_driver(self): return "keystone.assignment.backends.sql.Assignment" @@ -84,7 +86,7 @@ class Identity(sql.Base, identity.Driver): return utils.check_password(password, user_ref.password) # Identity interface - def authenticate(self, user_id=None, password=None): + def authenticate(self, user_id, password): session = self.get_session() user_ref = None try: diff --git a/keystone/identity/core.py b/keystone/identity/core.py index b3efc0a7..b2b3eaf0 100644 --- a/keystone/identity/core.py +++ b/keystone/identity/core.py @@ -16,7 +16,6 @@ """Main entry point into the Identity service.""" -from keystone import assignment from keystone import clean from keystone.common import dependency from keystone.common import logging @@ -53,6 +52,7 @@ def filter_user(user_ref): @dependency.provider('identity_api') +@dependency.requires('assignment_api') class Manager(manager.Manager): """Default pivot point for the Identity backend. @@ -61,12 +61,8 @@ class Manager(manager.Manager): """ - def __init__(self, assignment_api=None): + def __init__(self): super(Manager, self).__init__(CONF.identity.driver) - if assignment_api is None: - assignment_api = assignment.Manager(self) - self.assignment_api = assignment_api - self.driver.assignment_api = assignment_api def create_user(self, user_id, user_ref): user = user_ref.copy() @@ -216,7 +212,7 @@ class Manager(manager.Manager): class Driver(object): """Interface description for an Identity driver.""" - def authenticate_user(self, user_id, password): + def authenticate(self, user_id, password): """Authenticate a given user and password. :returns: user_ref :raises: AssertionError diff --git a/keystone/service.py b/keystone/service.py index 6b0c3708..ce64aba8 100644 --- a/keystone/service.py +++ b/keystone/service.py @@ -16,8 +16,10 @@ import routes +from keystone import assignment from keystone import auth from keystone import catalog +from keystone.common import dependency from keystone.common import logging from keystone.common import wsgi from keystone import config @@ -34,16 +36,25 @@ from keystone import trust CONF = config.CONF LOG = logging.getLogger(__name__) + +# Ensure that the identity driver is created before the assignment manager. +# The default assignment driver is determined by the identity driver, so the +# identity driver must be available to the assignment manager. +_IDENTITY_API = identity.Manager() + DRIVERS = dict( + assignment_api=assignment.Manager(), catalog_api=catalog.Manager(), credentials_api=credential.Manager(), ec2_api=ec2.Manager(), - identity_api=identity.Manager(), + identity_api=_IDENTITY_API, policy_api=policy.Manager(), token_api=token.Manager(), trust_api=trust.Manager(), token_provider_api=token.provider.Manager()) +dependency.resolve_future_dependencies() + @logging.fail_gracefully def public_app_factory(global_conf, **local_conf): diff --git a/keystone/test.py b/keystone/test.py index 0c51d76d..5d1ad505 100644 --- a/keystone/test.py +++ b/keystone/test.py @@ -38,18 +38,21 @@ environment.use_eventlet() from keystone import assignment from keystone import catalog +from keystone.common import dependency from keystone.common import kvs from keystone.common import logging from keystone.common import sql from keystone.common import utils from keystone.common import wsgi from keystone import config +from keystone.contrib import ec2 from keystone import credential from keystone import exception from keystone import identity from keystone.openstack.common import timeutils from keystone import policy from keystone import token +from keystone.token import provider as token_provider from keystone import trust @@ -239,6 +242,11 @@ class TestCase(NoModule, unittest.TestCase): for path in self._paths: if path in sys.path: sys.path.remove(path) + + # Clear the registry of providers so that providers from previous + # tests aren't used. + dependency.reset() + kvs.INMEMDB.clear() CONF.reset() @@ -252,11 +260,23 @@ class TestCase(NoModule, unittest.TestCase): def load_backends(self): """Initializes each manager and assigns them to an attribute.""" - for manager in [assignment, catalog, credential, identity, policy, - token, trust]: + + # TODO(blk-u): Shouldn't need to clear the registry here, but some + # tests call load_backends multiple times. These should be fixed to + # only call load_backends once. + dependency.reset() + + # NOTE(blk-u): identity must be before assignment to ensure that the + # identity driver is available to the assignment manager because the + # assignment manager gets the default assignment driver from the + # identity driver. + for manager in [identity, assignment, catalog, credential, ec2, policy, + token, token_provider, trust]: manager_name = '%s_api' % manager.__name__.split('.')[-1] setattr(self, manager_name, manager.Manager()) + dependency.resolve_future_dependencies() + def load_fixtures(self, fixtures): """Hacky basic and naive fixture loading based on a python module. diff --git a/keystone/token/provider.py b/keystone/token/provider.py index 2459f843..2864be6f 100644 --- a/keystone/token/provider.py +++ b/keystone/token/provider.py @@ -77,6 +77,10 @@ class Manager(manager.Manager): 'conflicts with keystone.conf [token] provider')) return CONF.token.provider else: + if not CONF.signing.token_format: + # No token provider and no format, so use default (PKI) + return PKI_PROVIDER + msg = _('keystone.conf [signing] token_format is deprecated in ' 'favor of keystone.conf [token] provider') if CONF.signing.token_format == 'PKI': diff --git a/tests/test_exception.py b/tests/test_exception.py index 33250835..d442d572 100644 --- a/tests/test_exception.py +++ b/tests/test_exception.py @@ -67,14 +67,14 @@ class ExceptionTestCase(test.TestCase): attribute = uuid.uuid4().hex e = exception.ValidationError(target=target, attribute=attribute) self.assertValidJsonRendering(e) - self.assertIn(target, str(e)) - self.assertIn(attribute, str(e)) + self.assertIn(target, unicode(e)) + self.assertIn(attribute, unicode(e)) def test_not_found(self): target = uuid.uuid4().hex e = exception.NotFound(target=target) self.assertValidJsonRendering(e) - self.assertIn(target, str(e)) + self.assertIn(target, unicode(e)) def test_403_title(self): e = exception.Forbidden() @@ -101,7 +101,7 @@ class SecurityErrorTestCase(ExceptionTestCase): risky_info = uuid.uuid4().hex e = exception.Unauthorized(message=risky_info) self.assertValidJsonRendering(e) - self.assertNotIn(risky_info, str(e)) + self.assertNotIn(risky_info, unicode(e)) def test_unauthorized_exposure_in_debug(self): self.opt(debug=True) @@ -109,7 +109,7 @@ class SecurityErrorTestCase(ExceptionTestCase): risky_info = uuid.uuid4().hex e = exception.Unauthorized(message=risky_info) self.assertValidJsonRendering(e) - self.assertIn(risky_info, str(e)) + self.assertIn(risky_info, unicode(e)) def test_forbidden_exposure(self): self.opt(debug=False) @@ -117,7 +117,7 @@ class SecurityErrorTestCase(ExceptionTestCase): risky_info = uuid.uuid4().hex e = exception.Forbidden(message=risky_info) self.assertValidJsonRendering(e) - self.assertNotIn(risky_info, str(e)) + self.assertNotIn(risky_info, unicode(e)) def test_forbidden_exposure_in_debug(self): self.opt(debug=True) @@ -125,7 +125,7 @@ class SecurityErrorTestCase(ExceptionTestCase): risky_info = uuid.uuid4().hex e = exception.Forbidden(message=risky_info) self.assertValidJsonRendering(e) - self.assertIn(risky_info, str(e)) + self.assertIn(risky_info, unicode(e)) def test_forbidden_action_exposure(self): self.opt(debug=False) @@ -134,12 +134,12 @@ class SecurityErrorTestCase(ExceptionTestCase): action = uuid.uuid4().hex e = exception.ForbiddenAction(message=risky_info, action=action) self.assertValidJsonRendering(e) - self.assertNotIn(risky_info, str(e)) - self.assertIn(action, str(e)) + self.assertNotIn(risky_info, unicode(e)) + self.assertIn(action, unicode(e)) e = exception.ForbiddenAction(action=risky_info) self.assertValidJsonRendering(e) - self.assertIn(risky_info, str(e)) + self.assertIn(risky_info, unicode(e)) def test_forbidden_action_exposure_in_debug(self): self.opt(debug=True) @@ -148,8 +148,16 @@ class SecurityErrorTestCase(ExceptionTestCase): e = exception.ForbiddenAction(message=risky_info) self.assertValidJsonRendering(e) - self.assertIn(risky_info, str(e)) + self.assertIn(risky_info, unicode(e)) e = exception.ForbiddenAction(action=risky_info) self.assertValidJsonRendering(e) - self.assertIn(risky_info, str(e)) + self.assertIn(risky_info, unicode(e)) + + def test_unicode_argument_message(self): + self.opt(debug=False) + + risky_info = u'\u7ee7\u7eed\u884c\u7f29\u8fdb\u6216' + e = exception.Forbidden(message=risky_info) + self.assertValidJsonRendering(e) + self.assertNotIn(risky_info, unicode(e)) diff --git a/tests/test_injection.py b/tests/test_injection.py index 4b6fc8ba..36cd0126 100644 --- a/tests/test_injection.py +++ b/tests/test_injection.py @@ -21,6 +21,10 @@ from keystone.common import dependency class TestDependencyInjection(unittest.TestCase): + def tearDown(self): + dependency.reset() + super(TestDependencyInjection, self).tearDown() + def test_dependency_injection(self): class Interface(object): def do_work(self): @@ -165,3 +169,43 @@ class TestDependencyInjection(unittest.TestCase): with self.assertRaises(dependency.UnresolvableDependencyException): Consumer() + dependency.resolve_future_dependencies() + + def test_circular_dependency(self): + p1_name = uuid.uuid4().hex + p2_name = uuid.uuid4().hex + + @dependency.provider(p1_name) + @dependency.requires(p2_name) + class P1(object): + pass + + @dependency.provider(p2_name) + @dependency.requires(p1_name) + class P2(object): + pass + + p1 = P1() + p2 = P2() + + dependency.resolve_future_dependencies() + + self.assertIs(getattr(p1, p2_name), p2) + self.assertIs(getattr(p2, p1_name), p1) + + def test_reset(self): + # Can reset the registry of providers. + + p_id = uuid.uuid4().hex + + @dependency.provider(p_id) + class P(object): + pass + + p_inst = P() + + self.assertIs(dependency.REGISTRY[p_id], p_inst) + + dependency.reset() + + self.assertFalse(dependency.REGISTRY) diff --git a/tests/test_keystoneclient.py b/tests/test_keystoneclient.py index ecab6a01..a26d6595 100644 --- a/tests/test_keystoneclient.py +++ b/tests/test_keystoneclient.py @@ -36,12 +36,8 @@ class CompatTestCase(test.TestCase): def setUp(self): super(CompatTestCase, self).setUp() - self.public_server = self.serveapp('keystone', name='main') - self.admin_server = self.serveapp('keystone', name='admin') - - revdir = test.checkout_vendor(*self.get_checkout()) - self.add_path(revdir) - self.clear_module('keystoneclient') + # The backends should be loaded and initialized before the servers are + # started because the servers use the backends. self.load_backends() self.token_provider_api = token.provider.Manager() @@ -54,6 +50,13 @@ class CompatTestCase(test.TestCase): self.tenant_bar['id'], self.role_admin['id']) + self.public_server = self.serveapp('keystone', name='main') + self.admin_server = self.serveapp('keystone', name='admin') + + revdir = test.checkout_vendor(*self.get_checkout()) + self.add_path(revdir) + self.clear_module('keystoneclient') + def tearDown(self): self.public_server.kill() self.admin_server.kill() diff --git a/tests/test_pki_token_provider.conf b/tests/test_pki_token_provider.conf index ec8df231..255972c3 100644 --- a/tests/test_pki_token_provider.conf +++ b/tests/test_pki_token_provider.conf @@ -1,5 +1,2 @@ -[signing] -token_format = PKI - [token] provider = keystone.token.providers.pki.Provider diff --git a/tests/test_sql_core.py b/tests/test_sql_core.py index bb413485..e60005f5 100644 --- a/tests/test_sql_core.py +++ b/tests/test_sql_core.py @@ -172,3 +172,11 @@ class TestBase(test.TestCase): self.assertFalse(session.autocommit) self.assertTrue(session.expire_on_commit) + + def test_get_session_invalidated(self): + # If clear the global engine, a new engine is used for get_session(). + base = sql.Base() + session1 = base.get_session() + sql.set_global_engine(None) + session2 = base.get_session() + self.assertIsNot(session1.bind, session2.bind) diff --git a/tests/test_token_provider.py b/tests/test_token_provider.py index 1bcf1a21..ac0b0d6b 100644 --- a/tests/test_token_provider.py +++ b/tests/test_token_provider.py @@ -410,11 +410,16 @@ class TestTokenProvider(test.TestCase): self.assertRaises(exception.UnexpectedError, token.provider.Manager.get_token_provider) + def test_uuid_provider(self): + self.opt_in_group('token', provider=token.provider.UUID_PROVIDER) + self.assertEqual(token.provider.Manager.get_token_provider(), + token.provider.UUID_PROVIDER) + def test_provider_override_token_format(self): self.opt_in_group('token', provider='keystone.token.providers.pki.Test') - self.assertRaises(exception.UnexpectedError, - token.provider.Manager.get_token_provider) + self.assertEqual(token.provider.Manager.get_token_provider(), + 'keystone.token.providers.pki.Test') self.opt_in_group('signing', token_format='UUID') self.opt_in_group('token', diff --git a/tests/test_uuid_token_provider.conf b/tests/test_uuid_token_provider.conf index d1ac5fdf..d127ea3b 100644 --- a/tests/test_uuid_token_provider.conf +++ b/tests/test_uuid_token_provider.conf @@ -1,5 +1,2 @@ -[signing] -token_format = UUID - [token] provider = keystone.token.providers.uuid.Provider diff --git a/tests/test_wsgi.py b/tests/test_wsgi.py index 8ac594a8..003f7571 100644 --- a/tests/test_wsgi.py +++ b/tests/test_wsgi.py @@ -14,8 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -import webob - from keystone import test from keystone.common import wsgi @@ -34,7 +32,7 @@ class BaseWSGITest(test.TestCase): super(BaseWSGITest, self).setUp() def _make_request(self, url='/'): - req = webob.Request.blank(url) + req = wsgi.Request.blank(url) args = {'action': 'index', 'controller': None} req.environ['wsgiorg.routing_args'] = [None, args] return req @@ -134,6 +132,11 @@ class ApplicationTest(BaseWSGITest): self.assertIn("testkey", app.kwargs) self.assertEquals("test", app.kwargs["testkey"]) + def test_render_exception(self): + e = exception.Unauthorized(message=u'\u7f51\u7edc') + resp = wsgi.render_exception(e) + self.assertEqual(resp.status_int, 401) + class ExtensionRouterTest(BaseWSGITest): def test_extensionrouter_local_config(self): |