diff options
120 files changed, 8570 insertions, 4414 deletions
@@ -13,7 +13,6 @@ cover/* covhtml pep8.txt nosetests.xml -*.db doc/build .DS_Store doc/source/modules.rst @@ -24,7 +23,6 @@ build/ dist/ etc/keystone.conf etc/logging.conf -tests/test.db.pristine -tests/no_admin_token_auth-paste.ini +tests/tmp/ .project .pydevproject diff --git a/MANIFEST.in b/MANIFEST.in index 93b762f7..2373ea28 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -20,4 +20,4 @@ graft tests graft tools graft examples recursive-include keystone *.json *.xml *.cfg *.pem README *.po *.pot *.sql -global-exclude *.pyc *.sdx *.log *.db *.swp +global-exclude *.pyc *.sdx *.log *.db *.swp tests/tmp/* diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index 03fa1d63..c13da952 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -74,11 +74,12 @@ following sections: * ``[s3]`` - Amazon S3 authentication driver configuration. * ``[identity]`` - identity system driver configuration * ``[catalog]`` - service catalog driver configuration -* ``[token]`` - token driver configuration +* ``[token]`` - token driver & token provider configuration * ``[policy]`` - policy system driver configuration for RBAC * ``[signing]`` - cryptographic signatures for PKI based tokens * ``[ssl]`` - SSL configuration * ``[auth]`` - Authentication plugin configuration +* ``[os_inherit]`` - Inherited Role Assignment extension * ``[paste_deploy]`` - Pointer to the PasteDeploy configuration file The Keystone primary configuration file is expected to be named ``keystone.conf``. @@ -106,7 +107,10 @@ file. It is up to the plugin to register its own configuration options. * ``methods`` - comma-delimited list of authentication plugin names * ``<plugin name>`` - specify the class which handles to authentication method, in the same manner as one would specify a backend driver. -Keystone provides two authentication methods by default. ``password`` handles password authentication and ``token`` handles token authentication. +Keystone provides three authentication methods by default. ``password`` handles password +authentication and ``token`` handles token authentication. ``external`` is used in conjunction +with authentication performed by a container web server that sets the ``REMOTE_USER`` +environment variable. How to Implement an Authentication Plugin ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -148,6 +152,32 @@ invoked, all plugins must succeed in order to for the entire authentication to be successful. Furthermore, all the plugins invoked must agree on the ``user_id`` in the ``auth_context``. +The ``REMOTE_USER`` environment variable is only set from a containing webserver. However, +to ensure that a user must go through other authentication mechanisms, even if this variable +is set, remove ``external`` from the list of plugins specified in ``methods``. This effectively +disables external authentication. + + +Token Provider +-------------- + +Keystone supports customizable token provider and it is specified in the +``[token]`` section of the configuration file. Keystone provides both UUID and +PKI token providers, with PKI token provider enabled as default. However, users +may register their own token provider by configuring the following property. + +* ``provider`` - token provider driver. Defaults to + ``keystone.token.providers.pki.Provider`` + +Note that ``token_format`` in the ``[signing]`` section is deprecated but still +being supported for backward compatibility. Therefore, if ``provider`` is set +to ``keystone.token.providers.pki.Provider``, ``token_format`` must be ``PKI``. +Conversely, if ``provider`` is ``keystone.token.providers.uuid.Provider``, +``token_format`` must be ``UUID``. + +For a customized provider, ``token_format`` must not set to ``PKI`` or +``UUID``. + Certificates for PKI -------------------- @@ -163,7 +193,9 @@ private key should only be readable by the system user that will run Keystone. The values that specify where to read the certificates are under the ``[signing]`` section of the configuration file. The configuration values are: -* ``token_format`` - Determines the algorithm used to generate tokens. Can be either ``UUID`` or ``PKI``. Defaults to ``PKI`` +* ``token_format`` - Determines the algorithm used to generate tokens. Can be + either ``UUID`` or ``PKI``. Defaults to ``PKI``. This option must be used in + conjunction with ``provider`` configuration in the ``[token]`` section. * ``certfile`` - Location of certificate used to verify tokens. Default is ``/etc/keystone/ssl/certs/signing_cert.pem`` * ``keyfile`` - Location of private key used to sign tokens. Default is ``/etc/keystone/ssl/private/signing_key.pem`` * ``ca_certs`` - Location of certificate for the authority that issued the above certificate. Default is ``/etc/keystone/ssl/certs/ca.pem`` @@ -463,6 +495,55 @@ In addition to changing their password all of the users current tokens will be deleted (if the backend used is kvs or sql) +Inherited Role Assignment Extension +----------------------------------- + +Keystone provides an optional extension that adds the capability to assign roles to a domain that, rather than +affect the domain itself, are instead inherited to all projects owned by theat domain. This extension is disabled by +default, but can be enabled by including the following in ``keystone.conf``. + + [os_inherit] + enabled = True + + +Token Binding +------------- + +Token binding refers to the practice of embedding information from external +authentication providers (like a company's Kerberos server) inside the token +such that a client may enforce that the token only be used in conjunction with +that specified authentication. This is an additional security mechanism as it +means that if a token is stolen it will not be usable without also providing the +external authentication. + +To activate token binding you must specify the types of authentication that +token binding should be used for in ``keystone.conf`` e.g.:: + + [token] + bind = kerberos + +Currently only ``kerberos`` is supported. + +To enforce checking of token binding the ``enforce_token_bind`` parameter +should be set to one of the following modes: + +* ``disabled`` disable token bind checking +* ``permissive`` enable bind checking, if a token is bound to a mechanism that + is unknown to the server then ignore it. This is the default. +* ``strict`` enable bind checking, if a token is bound to a mechanism that is + unknown to the server then this token should be rejected. +* ``required`` enable bind checking and require that at least 1 bind mechanism + is used for tokens. +* named enable bind checking and require that the specified authentication + mechanism is used. e.g.:: + + [token] + enforce_token_bind = kerberos + + *Do not* set ``enforce_token_bind = named`` as there is not an authentication + mechanism called ``named``. + + Sample Configuration Files -------------------------- @@ -1079,7 +1160,7 @@ if the backend is providing too much output, in such case the configuration will look like:: [ldap] - user_filter = (memberof=CN=openstack-users,OU=workgroups,DC=openstack,DC=com) + user_filter = (memberof=CN=openstack-users,OU=workgroups,DC=openstack,DC=org) tenant_filter = role_filter = diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample index 3f4f1637..509165c5 100644 --- a/etc/keystone.conf.sample +++ b/etc/keystone.conf.sample @@ -109,6 +109,11 @@ # delegation and impersonation features can be optionally disabled # enabled = True +[os_inherit] +# role-assignment inheritance to projects from owning domain can be +# optionally enabled +# enabled = False + [catalog] # dynamic, sql-based backend (supports API/CLI-based management commands) # driver = keystone.catalog.backends.sql.Catalog @@ -119,11 +124,24 @@ # template_file = default_catalog.templates [token] +# Provides token persistence. # driver = keystone.token.backends.sql.Token +# Controls the token construction, validation, and revocation operations. +# provider = keystone.token.providers.pki.Provider + # Amount of time a token should remain valid (in seconds) # expiration = 86400 +# External auth mechanisms that should add bind information to token. +# eg kerberos, x509 +# bind = + +# Enforcement policy on tokens presented to keystone with bind information. +# One of disabled, permissive, strict, required or a specifically required bind +# mode e.g. kerberos or x509 to require binding to that authentication. +# enforce_token_bind = permissive + [policy] # driver = keystone.policy.backends.sql.Policy @@ -143,7 +161,9 @@ #cert_subject = /C=US/ST=Unset/L=Unset/O=Unset/CN=localhost [signing] +# Deprecated in favor of provider in the [token] section #token_format = PKI + #certfile = /etc/keystone/pki/certs/signing_cert.pem #keyfile = /etc/keystone/pki/private/signing_key.pem #ca_certs = /etc/keystone/pki/certs/cacert.pem @@ -253,7 +273,8 @@ # user_additional_attribute_mapping = [auth] -methods = password,token +methods = external,password,token +#external = keystone.auth.plugins.external.ExternalDefault password = keystone.auth.plugins.password.Password token = keystone.auth.plugins.token.Token diff --git a/keystone/assignment/__init__.py b/keystone/assignment/__init__.py new file mode 100644 index 00000000..5a848308 --- /dev/null +++ b/keystone/assignment/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# flake8: noqa + +# Copyright 2013 OpenStack LLC +# +# 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 keystone.assignment.core import * diff --git a/keystone/assignment/backends/__init__.py b/keystone/assignment/backends/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/keystone/assignment/backends/__init__.py diff --git a/keystone/assignment/backends/kvs.py b/keystone/assignment/backends/kvs.py new file mode 100644 index 00000000..4dfd908f --- /dev/null +++ b/keystone/assignment/backends/kvs.py @@ -0,0 +1,497 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# 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 keystone import assignment +from keystone import clean +from keystone.common import kvs +from keystone import exception +from keystone import identity + + +class Assignment(kvs.Base, assignment.Driver): + def __init__(self): + super(Assignment, self).__init__() + + # Public interface + + def get_project(self, tenant_id): + try: + return self.db.get('tenant-%s' % tenant_id) + except exception.NotFound: + raise exception.ProjectNotFound(project_id=tenant_id) + + def list_projects(self, domain_id=None): + project_keys = filter(lambda x: x.startswith("tenant-"), + self.db.keys()) + project_refs = [self.db.get(key) for key in project_keys] + + if domain_id: + self.get_domain(domain_id) + project_refs = filter(lambda x: domain_id in x['domain_id'], + project_refs) + return project_refs + + def get_project_by_name(self, tenant_name, domain_id): + try: + return self.db.get('tenant_name-%s' % tenant_name) + except exception.NotFound: + raise exception.ProjectNotFound(project_id=tenant_name) + + def get_project_users(self, tenant_id): + self.get_project(tenant_id) + user_keys = filter(lambda x: x.startswith("user-"), self.db.keys()) + user_refs = [self.db.get(key) for key in user_keys] + user_refs = filter(lambda x: tenant_id in x['tenants'], user_refs) + return [identity.filter_user(user_ref) for user_ref in user_refs] + + def _get_user(self, user_id): + try: + return self.db.get('user-%s' % user_id) + except exception.NotFound: + raise exception.UserNotFound(user_id=user_id) + + def _get_user_by_name(self, user_name, domain_id): + try: + return self.db.get('user_name-%s' % user_name) + except exception.NotFound: + raise exception.UserNotFound(user_id=user_name) + + def _get_metadata(self, user_id=None, tenant_id=None, + domain_id=None, group_id=None): + try: + if user_id: + if tenant_id: + return self.db.get('metadata-%s-%s' % (tenant_id, + user_id)) + else: + return self.db.get('metadata-%s-%s' % (domain_id, + user_id)) + else: + if tenant_id: + return self.db.get('metadata-%s-%s' % (tenant_id, + group_id)) + else: + return self.db.get('metadata-%s-%s' % (domain_id, + group_id)) + except exception.NotFound: + raise exception.MetadataNotFound() + + def get_role(self, role_id): + try: + return self.db.get('role-%s' % role_id) + except exception.NotFound: + raise exception.RoleNotFound(role_id=role_id) + + def list_roles(self): + role_ids = self.db.get('role_list', []) + return [self.get_role(x) for x in role_ids] + + def get_projects_for_user(self, user_id): + user_ref = self._get_user(user_id) + return user_ref.get('tenants', []) + + def add_role_to_user_and_project(self, user_id, tenant_id, role_id): + self.identity_api.get_user(user_id) + self.get_project(tenant_id) + self.get_role(role_id) + try: + metadata_ref = self._get_metadata(user_id, tenant_id) + except exception.MetadataNotFound: + metadata_ref = {} + + try: + metadata_ref['roles'] = self._add_role_to_role_dicts( + role_id, False, metadata_ref.get('roles', []), + allow_existing=False) + except KeyError: + msg = ('User %s already has role %s in tenant %s' + % (user_id, role_id, tenant_id)) + raise exception.Conflict(type='role grant', details=msg) + + self._update_metadata(user_id, tenant_id, metadata_ref) + + def remove_role_from_user_and_project(self, user_id, tenant_id, role_id): + try: + metadata_ref = self._get_metadata(user_id, tenant_id) + except exception.MetadataNotFound: + metadata_ref = {} + + try: + metadata_ref['roles'] = self._remove_role_from_role_dicts( + role_id, False, metadata_ref.get('roles', [])) + except KeyError: + raise exception.RoleNotFound(message=_( + 'Cannot remove role that has not been granted, %s') % + role_id) + + if len(metadata_ref['roles']): + self._update_metadata(user_id, tenant_id, metadata_ref) + else: + + self.db.delete('metadata-%s-%s' % (tenant_id, user_id)) + user_ref = self._get_user(user_id) + tenants = set(user_ref.get('tenants', [])) + tenants.remove(tenant_id) + user_ref['tenants'] = list(tenants) + self.identity_api.update_user(user_id, user_ref) + + def list_role_assignments(self): + """List the role assignments. + + The kvs backend stores role assignments as key-values: + + "metadata-{target}-{actor}", with the value being a role list + + i.e. "metadata-MyProjectID-MyUserID" [{'id': role1}, {'id': role2}] + + ...so we enumerate the list and extract the targets, actors + and roles. + + """ + assignment_list = [] + metadata_keys = filter(lambda x: x.startswith("metadata-"), + self.db.keys()) + for key in metadata_keys: + template = {} + meta_id1 = key.split('-')[1] + meta_id2 = key.split('-')[2] + try: + self.get_project(meta_id1) + template['project_id'] = meta_id1 + except exception.NotFound: + template['domain_id'] = meta_id1 + try: + self._get_user(meta_id2) + template['user_id'] = meta_id2 + except exception.NotFound: + template['group_id'] = meta_id2 + + entry = self.db.get(key) + for r in self._roles_from_role_dicts(entry.get('roles', {}), + False): + role_assignment = template.copy() + role_assignment['role_id'] = r + assignment_list.append(role_assignment) + + return assignment_list + + # CRUD + def create_project(self, tenant_id, tenant): + tenant['name'] = clean.project_name(tenant['name']) + try: + self.get_project(tenant_id) + except exception.ProjectNotFound: + pass + else: + msg = 'Duplicate ID, %s.' % tenant_id + raise exception.Conflict(type='tenant', details=msg) + + try: + self.get_project_by_name(tenant['name'], tenant['domain_id']) + except exception.ProjectNotFound: + pass + else: + msg = 'Duplicate name, %s.' % tenant['name'] + raise exception.Conflict(type='tenant', details=msg) + + self.db.set('tenant-%s' % tenant_id, tenant) + self.db.set('tenant_name-%s' % tenant['name'], tenant) + return tenant + + def update_project(self, tenant_id, tenant): + if 'name' in tenant: + tenant['name'] = clean.project_name(tenant['name']) + try: + existing = self.db.get('tenant_name-%s' % tenant['name']) + if existing and tenant_id != existing['id']: + msg = 'Duplicate name, %s.' % tenant['name'] + raise exception.Conflict(type='tenant', details=msg) + except exception.NotFound: + pass + # get the old name and delete it too + try: + old_project = self.db.get('tenant-%s' % tenant_id) + except exception.NotFound: + raise exception.ProjectNotFound(project_id=tenant_id) + new_project = old_project.copy() + new_project.update(tenant) + new_project['id'] = tenant_id + self.db.delete('tenant_name-%s' % old_project['name']) + self.db.set('tenant-%s' % tenant_id, new_project) + self.db.set('tenant_name-%s' % new_project['name'], new_project) + return new_project + + def delete_project(self, tenant_id): + try: + old_project = self.db.get('tenant-%s' % tenant_id) + except exception.NotFound: + raise exception.ProjectNotFound(project_id=tenant_id) + self.db.delete('tenant_name-%s' % old_project['name']) + self.db.delete('tenant-%s' % tenant_id) + + def _create_metadata(self, user_id, tenant_id, metadata, + domain_id=None, group_id=None): + + return self._update_metadata(user_id, tenant_id, metadata, + domain_id, group_id) + + def _update_metadata(self, user_id, tenant_id, metadata, + domain_id=None, group_id=None): + if user_id: + if tenant_id: + self.db.set('metadata-%s-%s' % (tenant_id, user_id), metadata) + user_ref = self._get_user(user_id) + tenants = set(user_ref.get('tenants', [])) + if tenant_id not in tenants: + tenants.add(tenant_id) + user_ref['tenants'] = list(tenants) + self.identity_api.update_user(user_id, user_ref) + else: + self.db.set('metadata-%s-%s' % (domain_id, user_id), metadata) + else: + if tenant_id: + self.db.set('metadata-%s-%s' % (tenant_id, group_id), metadata) + else: + self.db.set('metadata-%s-%s' % (domain_id, group_id), metadata) + return metadata + + def create_role(self, role_id, role): + try: + self.get_role(role_id) + except exception.RoleNotFound: + pass + else: + msg = 'Duplicate ID, %s.' % role_id + raise exception.Conflict(type='role', details=msg) + + for role_ref in self.list_roles(): + if role['name'] == role_ref['name']: + msg = 'Duplicate name, %s.' % role['name'] + raise exception.Conflict(type='role', details=msg) + self.db.set('role-%s' % role_id, role) + role_list = set(self.db.get('role_list', [])) + role_list.add(role_id) + self.db.set('role_list', list(role_list)) + return role + + def update_role(self, role_id, role): + old_role_ref = None + for role_ref in self.list_roles(): + if role['name'] == role_ref['name'] and role_id != role_ref['id']: + msg = 'Duplicate name, %s.' % role['name'] + raise exception.Conflict(type='role', details=msg) + if role_id == role_ref['id']: + old_role_ref = role_ref + if old_role_ref is None: + raise exception.RoleNotFound(role_id=role_id) + new_role = old_role_ref.copy() + new_role.update(role) + new_role['id'] = role_id + self.db.set('role-%s' % role_id, new_role) + return role + + def delete_role(self, role_id): + self.get_role(role_id) + metadata_keys = filter(lambda x: x.startswith("metadata-"), + self.db.keys()) + for key in metadata_keys: + meta_id1 = key.split('-')[1] + meta_id2 = key.split('-')[2] + try: + self.delete_grant(role_id, project_id=meta_id1, + user_id=meta_id2) + except exception.NotFound: + pass + try: + self.delete_grant(role_id, project_id=meta_id1, + group_id=meta_id2) + except exception.NotFound: + pass + try: + self.delete_grant(role_id, domain_id=meta_id1, + user_id=meta_id2) + except exception.NotFound: + pass + try: + self.delete_grant(role_id, domain_id=meta_id1, + group_id=meta_id2) + except exception.NotFound: + pass + self.db.delete('role-%s' % role_id) + role_list = set(self.db.get('role_list', [])) + role_list.remove(role_id) + self.db.set('role_list', list(role_list)) + + def create_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + + self.get_role(role_id) + if user_id: + self.identity_api.get_user(user_id) + if group_id: + self.identity_api.get_group(group_id) + if domain_id: + self.get_domain(domain_id) + if project_id: + self.get_project(project_id) + + try: + metadata_ref = self._get_metadata(user_id, project_id, + domain_id, group_id) + except exception.MetadataNotFound: + metadata_ref = {} + + metadata_ref['roles'] = self._add_role_to_role_dicts( + role_id, inherited_to_projects, metadata_ref.get('roles', [])) + + self._update_metadata(user_id, project_id, metadata_ref, + domain_id, group_id) + + def list_grants(self, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + if user_id: + self.identity_api.get_user(user_id) + if group_id: + self.identity_api.get_group(group_id) + if domain_id: + self.get_domain(domain_id) + if project_id: + self.get_project(project_id) + + try: + metadata_ref = self._get_metadata(user_id, project_id, + domain_id, group_id) + except exception.MetadataNotFound: + metadata_ref = {} + + return [self.get_role(x) for x in + self._roles_from_role_dicts(metadata_ref.get('roles', []), + inherited_to_projects)] + + def get_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + self.get_role(role_id) + if user_id: + self.identity_api.get_user(user_id) + if group_id: + self.get_group(group_id) + if domain_id: + self.get_domain(domain_id) + if project_id: + self.get_project(project_id) + + try: + metadata_ref = self._get_metadata(user_id, project_id, + domain_id, group_id) + except exception.MetadataNotFound: + metadata_ref = {} + + role_ids = set(self._roles_from_role_dicts( + metadata_ref.get('roles', []), inherited_to_projects)) + + if role_id not in role_ids: + raise exception.RoleNotFound(role_id=role_id) + return self.get_role(role_id) + + def delete_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + self.get_role(role_id) + if user_id: + self.identity_api.get_user(user_id) + if group_id: + self.identity_api.get_group(group_id) + if domain_id: + self.get_domain(domain_id) + if project_id: + self.get_project(project_id) + + try: + metadata_ref = self._get_metadata(user_id, project_id, + domain_id, group_id) + except exception.MetadataNotFound: + metadata_ref = {} + + try: + metadata_ref['roles'] = self._remove_role_from_role_dicts( + role_id, inherited_to_projects, metadata_ref.get('roles', [])) + except KeyError: + raise exception.RoleNotFound(role_id=role_id) + + self._update_metadata(user_id, project_id, metadata_ref, + domain_id, group_id) + + # domain crud + + def create_domain(self, domain_id, domain): + try: + self.get_domain(domain_id) + except exception.DomainNotFound: + pass + else: + msg = 'Duplicate ID, %s.' % domain_id + raise exception.Conflict(type='domain', details=msg) + + try: + self.get_domain_by_name(domain['name']) + except exception.DomainNotFound: + pass + else: + msg = 'Duplicate name, %s.' % domain['name'] + raise exception.Conflict(type='domain', details=msg) + + self.db.set('domain-%s' % domain_id, domain) + self.db.set('domain_name-%s' % domain['name'], domain) + domain_list = set(self.db.get('domain_list', [])) + domain_list.add(domain_id) + self.db.set('domain_list', list(domain_list)) + return domain + + def list_domains(self): + domain_ids = self.db.get('domain_list', []) + return [self.get_domain(x) for x in domain_ids] + + def get_domain(self, domain_id): + try: + return self.db.get('domain-%s' % domain_id) + except exception.NotFound: + raise exception.DomainNotFound(domain_id=domain_id) + + def get_domain_by_name(self, domain_name): + try: + return self.db.get('domain_name-%s' % domain_name) + except exception.NotFound: + raise exception.DomainNotFound(domain_id=domain_name) + + def update_domain(self, domain_id, domain): + orig_domain = self.get_domain(domain_id) + domain['id'] = domain_id + self.db.set('domain-%s' % domain_id, domain) + self.db.set('domain_name-%s' % domain['name'], domain) + if domain['name'] != orig_domain['name']: + self.db.delete('domain_name-%s' % orig_domain['name']) + return domain + + def delete_domain(self, domain_id): + domain = self.get_domain(domain_id) + self.db.delete('domain-%s' % domain_id) + self.db.delete('domain_name-%s' % domain['name']) + domain_list = set(self.db.get('domain_list', [])) + domain_list.remove(domain_id) + self.db.set('domain_list', list(domain_list)) diff --git a/keystone/assignment/backends/ldap.py b/keystone/assignment/backends/ldap.py new file mode 100644 index 00000000..b1b3f99f --- /dev/null +++ b/keystone/assignment/backends/ldap.py @@ -0,0 +1,553 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012-2013 OpenStack LLC +# +# 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 __future__ import absolute_import + +import uuid + +import ldap as ldap + +from keystone import assignment +from keystone import clean +from keystone.common import ldap as common_ldap +from keystone.common import logging +from keystone.common import models +from keystone import config +from keystone import exception +from keystone.identity.backends import ldap as ldap_identity + + +CONF = config.CONF +LOG = logging.getLogger(__name__) + +DEFAULT_DOMAIN = { + 'id': CONF.identity.default_domain_id, + 'name': 'Default', + 'enabled': True +} + + +class Assignment(assignment.Driver): + def __init__(self): + super(Assignment, self).__init__() + self.LDAP_URL = CONF.ldap.url + self.LDAP_USER = CONF.ldap.user + self.LDAP_PASSWORD = CONF.ldap.password + self.suffix = CONF.ldap.suffix + + #These are the only deep dependency from assignment back + #to identity. The assumption is that if you are using + #LDAP for assignments, you are using it for Id as well. + self.user = ldap_identity.UserApi(CONF) + self.group = ldap_identity.GroupApi(CONF) + + self.project = ProjectApi(CONF) + self.role = RoleApi(CONF) + + self._identity_api = None + + @property + def identity_api(self): + return self._identity_api + + @identity_api.setter + def identity_api(self, value): + self._identity_api = value + #TODO(ayoung): only left here to prevent unit test from breaking + #once we remove here. the getter and setter can be removed as well. + self._identity_api.driver.project = self.project + + def get_project(self, tenant_id): + return self._set_default_domain(self.project.get(tenant_id)) + + def list_projects(self, domain_id=None): + # We don't support multiple domains within this driver, so ignore + # any domain passed. + return self._set_default_domain(self.project.get_all()) + + def get_project_by_name(self, tenant_name, domain_id): + self._validate_default_domain_id(domain_id) + return self._set_default_domain(self.project.get_by_name(tenant_name)) + + def create_project(self, tenant_id, tenant): + tenant = self._validate_default_domain(tenant) + tenant['name'] = clean.project_name(tenant['name']) + data = tenant.copy() + if 'id' not in data or data['id'] is None: + data['id'] = str(uuid.uuid4().hex) + if 'description' in data and data['description'] in ['', None]: + data.pop('description') + return self._set_default_domain(self.project.create(data)) + + def update_project(self, tenant_id, tenant): + tenant = self._validate_default_domain(tenant) + if 'name' in tenant: + tenant['name'] = clean.project_name(tenant['name']) + return self._set_default_domain(self.project.update(tenant_id, tenant)) + + def _get_metadata(self, user_id=None, tenant_id=None, + domain_id=None, group_id=None): + + def _get_roles_for_just_user_and_project(user_id, tenant_id): + self.identity_api.get_user(user_id) + self.get_project(tenant_id) + user_dn = self.user._id_to_dn(user_id) + return [self.role._dn_to_id(a.role_dn) + for a in self.role.get_role_assignments + (self.project._id_to_dn(tenant_id)) + if a.user_dn == user_dn] + + if domain_id is not None: + msg = 'Domain metadata not supported by LDAP' + raise exception.NotImplemented(message=msg) + if (not self.get_project(tenant_id) or + not self.identity_api.get_user(user_id)): + return {} + + metadata_ref = _get_roles_for_just_user_and_project(user_id, tenant_id) + if not metadata_ref: + return {} + return {'roles': [self._role_to_dict(r, False) for r in metadata_ref]} + + def get_role(self, role_id): + return self.role.get(role_id) + + def list_roles(self): + return self.role.get_all() + + def get_projects_for_user(self, user_id): + self.identity_api.get_user(user_id) + user_dn = self.user._id_to_dn(user_id) + associations = (self.role.list_project_roles_for_user + (user_dn, self.project.tree_dn)) + return [p['id'] for p in + self.project.get_user_projects(user_dn, associations)] + + def get_project_users(self, tenant_id): + self.get_project(tenant_id) + tenant_dn = self.project._id_to_dn(tenant_id) + rolegrants = self.role.get_role_assignments(tenant_dn) + users = [self.user.get_filtered(self.user._dn_to_id(user_id)) + for user_id in + self.project.get_user_dns(tenant_id, rolegrants)] + return self._set_default_domain(users) + + def _subrole_id_to_dn(self, role_id, tenant_id): + if tenant_id is None: + return self.role._id_to_dn(role_id) + else: + return '%s=%s,%s' % (self.role.id_attr, + ldap.dn.escape_dn_chars(role_id), + self.project._id_to_dn(tenant_id)) + + def add_role_to_user_and_project(self, user_id, tenant_id, role_id): + self.identity_api.get_user(user_id) + self.get_project(tenant_id) + self.get_role(role_id) + user_dn = self.user._id_to_dn(user_id) + role_dn = self._subrole_id_to_dn(role_id, tenant_id) + self.role.add_user(role_id, role_dn, user_dn, user_id, tenant_id) + tenant_dn = self.project._id_to_dn(tenant_id) + return UserRoleAssociation( + role_dn=role_dn, + user_dn=user_dn, + tenant_dn=tenant_dn) + + def _create_metadata(self, user_id, tenant_id, metadata): + return {} + + def create_role(self, role_id, role): + try: + self.get_role(role_id) + except exception.NotFound: + pass + else: + msg = 'Duplicate ID, %s.' % role_id + raise exception.Conflict(type='role', details=msg) + + try: + self.role.get_by_name(role['name']) + except exception.NotFound: + pass + else: + msg = 'Duplicate name, %s.' % role['name'] + raise exception.Conflict(type='role', details=msg) + + return self.role.create(role) + + def delete_role(self, role_id): + return self.role.delete(role_id, self.project.tree_dn) + + def delete_project(self, tenant_id): + if self.project.subtree_delete_enabled: + self.project.deleteTree(id) + else: + tenant_dn = self.project._id_to_dn(tenant_id) + self.role.roles_delete_subtree_by_project(tenant_dn) + self.project.delete(tenant_id) + + def remove_role_from_user_and_project(self, user_id, tenant_id, role_id): + role_dn = self._subrole_id_to_dn(role_id, tenant_id) + return self.role.delete_user(role_dn, + self.user._id_to_dn(user_id), + self.project._id_to_dn(tenant_id), + user_id, role_id) + + def update_role(self, role_id, role): + self.get_role(role_id) + self.role.update(role_id, role) + + def create_domain(self, domain_id, domain): + if domain_id == CONF.identity.default_domain_id: + msg = 'Duplicate ID, %s.' % domain_id + raise exception.Conflict(type='domain', details=msg) + raise exception.Forbidden('Domains are read-only against LDAP') + + def get_domain(self, domain_id): + self._validate_default_domain_id(domain_id) + return DEFAULT_DOMAIN + + def update_domain(self, domain_id, domain): + self._validate_default_domain_id(domain_id) + raise exception.Forbidden('Domains are read-only against LDAP') + + def delete_domain(self, domain_id): + self._validate_default_domain_id(domain_id) + raise exception.Forbidden('Domains are read-only against LDAP') + + def list_domains(self): + return [assignment.DEFAULT_DOMAIN] + +#Bulk actions on User From identity + def delete_user(self, user_id): + user_dn = self.user._id_to_dn(user_id) + for ref in self.role.list_global_roles_for_user(user_dn): + self.role.delete_user(ref.role_dn, ref.user_dn, ref.project_dn, + user_id, self.role._dn_to_id(ref.role_dn)) + for ref in self.role.list_project_roles_for_user(user_dn, + self.project.tree_dn): + self.role.delete_user(ref.role_dn, ref.user_dn, ref.project_dn, + user_id, self.role._dn_to_id(ref.role_dn)) + + user = self.user.get(user_id) + if hasattr(user, 'tenant_id'): + self.project.remove_user(user.tenant_id, + self.user._id_to_dn(user_id)) + + #LDAP assignments only supports LDAP identity. Assignments under identity + #are already deleted + def delete_group(self, group_id): + if not self.group.subtree_delete_enabled: + # TODO(spzala): this is only placeholder for group and domain + # role support which will be added under bug 1101287 + conn = self.group.get_connection() + query = '(objectClass=%s)' % self.group.object_class + dn = None + dn = self.group._id_to_dn(id) + if dn: + try: + roles = conn.search_s(dn, ldap.SCOPE_ONELEVEL, + query, ['%s' % '1.1']) + for role_dn, _ in roles: + conn.delete_s(role_dn) + except ldap.NO_SUCH_OBJECT: + pass + + +# TODO(termie): turn this into a data object and move logic to driver +class ProjectApi(common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap): + DEFAULT_OU = 'ou=Groups' + DEFAULT_STRUCTURAL_CLASSES = [] + DEFAULT_OBJECTCLASS = 'groupOfNames' + DEFAULT_ID_ATTR = 'cn' + DEFAULT_MEMBER_ATTRIBUTE = 'member' + DEFAULT_ATTRIBUTE_IGNORE = [] + NotFound = exception.ProjectNotFound + notfound_arg = 'project_id' # NOTE(yorik-sar): while options_name = tenant + options_name = 'tenant' + attribute_mapping = {'name': 'ou', + 'description': 'description', + 'tenantId': 'cn', + 'enabled': 'enabled', + 'domain_id': 'domain_id'} + model = models.Project + + def __init__(self, conf): + super(ProjectApi, self).__init__(conf) + self.attribute_mapping['name'] = conf.ldap.tenant_name_attribute + self.attribute_mapping['description'] = conf.ldap.tenant_desc_attribute + self.attribute_mapping['enabled'] = conf.ldap.tenant_enabled_attribute + self.attribute_mapping['domain_id'] = ( + conf.ldap.tenant_domain_id_attribute) + self.member_attribute = (getattr(conf.ldap, 'tenant_member_attribute') + or self.DEFAULT_MEMBER_ATTRIBUTE) + self.attribute_ignore = (getattr(conf.ldap, 'tenant_attribute_ignore') + or self.DEFAULT_ATTRIBUTE_IGNORE) + + def create(self, values): + self.affirm_unique(values) + data = values.copy() + if data.get('id') is None: + data['id'] = uuid.uuid4().hex + return super(ProjectApi, self).create(data) + + def get_user_projects(self, user_dn, associations): + """Returns list of tenants a user has access to + """ + + project_ids = set() + for assoc in associations: + project_ids.add(self._dn_to_id(assoc.project_dn)) + projects = [] + for project_id in project_ids: + #slower to get them one at a time, but a huge list could blow out + #the connection. This is the safer way + projects.append(self.get(project_id)) + return projects + + def add_user(self, tenant_id, user_dn): + conn = self.get_connection() + try: + conn.modify_s( + self._id_to_dn(tenant_id), + [(ldap.MOD_ADD, + self.member_attribute, + user_dn)]) + except ldap.TYPE_OR_VALUE_EXISTS: + # As adding a user to a tenant is done implicitly in several + # places, and is not part of the exposed API, it's easier for us to + # just ignore this instead of raising exception.Conflict. + pass + + def remove_user(self, tenant_id, user_dn, user_id): + conn = self.get_connection() + try: + conn.modify_s(self._id_to_dn(tenant_id), + [(ldap.MOD_DELETE, + self.member_attribute, + user_dn)]) + except ldap.NO_SUCH_ATTRIBUTE: + raise exception.NotFound(user_id) + + def get_user_dns(self, tenant_id, rolegrants, role_dn=None): + tenant = self._ldap_get(tenant_id) + res = set() + if not role_dn: + # Get users who have default tenant mapping + for user_dn in tenant[1].get(self.member_attribute, []): + if self.use_dumb_member and user_dn == self.dumb_member: + continue + res.add(user_dn) + + # Get users who are explicitly mapped via a tenant + for rolegrant in rolegrants: + if role_dn is None or rolegrant.role_dn == role_dn: + res.add(rolegrant.user_dn) + return list(res) + + def update(self, id, values): + old_obj = self.get(id) + if old_obj['name'] != values['name']: + msg = 'Changing Name not supported by LDAP' + raise exception.NotImplemented(message=msg) + return super(ProjectApi, self).update(id, values, old_obj) + + +class UserRoleAssociation(object): + """Role Grant model.""" + + def __init__(self, user_dn=None, role_dn=None, tenant_dn=None, + *args, **kw): + self.user_dn = user_dn + self.role_dn = role_dn + self.project_dn = tenant_dn + + +class GroupRoleAssociation(object): + """Role Grant model.""" + + def __init__(self, group_dn=None, role_dn=None, tenant_dn=None, + *args, **kw): + self.group_dn = group_dn + self.role_dn = role_dn + self.project_dn = tenant_dn + + +# TODO(termie): turn this into a data object and move logic to driver +class RoleApi(common_ldap.BaseLdap): + DEFAULT_OU = 'ou=Roles' + DEFAULT_STRUCTURAL_CLASSES = [] + DEFAULT_OBJECTCLASS = 'organizationalRole' + DEFAULT_MEMBER_ATTRIBUTE = 'roleOccupant' + DEFAULT_ATTRIBUTE_IGNORE = [] + NotFound = exception.RoleNotFound + options_name = 'role' + attribute_mapping = {'name': 'ou', + #'serviceId': 'service_id', + } + model = models.Role + + def __init__(self, conf): + super(RoleApi, self).__init__(conf) + self.attribute_mapping['name'] = conf.ldap.role_name_attribute + self.member_attribute = (getattr(conf.ldap, 'role_member_attribute') + or self.DEFAULT_MEMBER_ATTRIBUTE) + self.attribute_ignore = (getattr(conf.ldap, 'role_attribute_ignore') + or self.DEFAULT_ATTRIBUTE_IGNORE) + + def get(self, id, filter=None): + model = super(RoleApi, self).get(id, filter) + return model + + def create(self, values): + return super(RoleApi, self).create(values) + + def add_user(self, role_id, role_dn, user_dn, user_id, tenant_id=None): + conn = self.get_connection() + try: + conn.modify_s(role_dn, [(ldap.MOD_ADD, + self.member_attribute, user_dn)]) + except ldap.TYPE_OR_VALUE_EXISTS: + msg = ('User %s already has role %s in tenant %s' + % (user_id, role_id, tenant_id)) + raise exception.Conflict(type='role grant', details=msg) + except ldap.NO_SUCH_OBJECT: + if tenant_id is None or self.get(role_id) is None: + raise Exception(_("Role %s not found") % (role_id,)) + + attrs = [('objectClass', [self.object_class]), + (self.member_attribute, [user_dn])] + + if self.use_dumb_member: + attrs[1][1].append(self.dumb_member) + try: + conn.add_s(role_dn, attrs) + except Exception as inst: + raise inst + + def delete_user(self, role_dn, user_dn, tenant_dn, + user_id, role_id): + conn = self.get_connection() + try: + conn.modify_s(role_dn, [(ldap.MOD_DELETE, + self.member_attribute, user_dn)]) + except ldap.NO_SUCH_OBJECT: + if tenant_dn is None: + raise exception.RoleNotFound(role_id=role_id) + attrs = [('objectClass', [self.object_class]), + (self.member_attribute, [user_dn])] + + if self.use_dumb_member: + attrs[1][1].append(self.dumb_member) + try: + conn.add_s(role_dn, attrs) + except Exception as inst: + raise inst + except ldap.NO_SUCH_ATTRIBUTE: + raise exception.UserNotFound(user_id=user_id) + + def get_role_assignments(self, tenant_dn): + conn = self.get_connection() + query = '(objectClass=%s)' % self.object_class + + try: + roles = conn.search_s(tenant_dn, ldap.SCOPE_ONELEVEL, query) + except ldap.NO_SUCH_OBJECT: + return [] + + res = [] + for role_dn, attrs in roles: + try: + user_dns = attrs[self.member_attribute] + except KeyError: + continue + for user_dn in user_dns: + if self.use_dumb_member and user_dn == self.dumb_member: + continue + res.append(UserRoleAssociation( + user_dn=user_dn, + role_dn=role_dn, + tenant_dn=tenant_dn)) + + return res + + def list_global_roles_for_user(self, user_dn): + roles = self.get_all('(%s=%s)' % (self.member_attribute, user_dn)) + return [UserRoleAssociation( + role_dn=role.dn, + user_dn=user_dn) for role in roles] + + def list_project_roles_for_user(self, user_dn, project_subtree): + conn = self.get_connection() + query = '(&(objectClass=%s)(%s=%s))' % (self.object_class, + self.member_attribute, + user_dn) + try: + roles = conn.search_s(project_subtree, + ldap.SCOPE_SUBTREE, + query) + except ldap.NO_SUCH_OBJECT: + return [] + + res = [] + for role_dn, _ in roles: + #ldap.dn.dn2str returns an array, where the first + #element is the first segment. + #For a role assignment, this contains the role ID, + #The remainder is the DN of the tenant. + tenant = ldap.dn.str2dn(role_dn) + tenant.pop(0) + tenant_dn = ldap.dn.dn2str(tenant) + res.append(UserRoleAssociation( + user_dn=user_dn, + role_dn=role_dn, + tenant_dn=tenant_dn)) + return res + + def roles_delete_subtree_by_project(self, tenant_dn): + conn = self.get_connection() + query = '(objectClass=%s)' % self.object_class + try: + roles = conn.search_s(tenant_dn, ldap.SCOPE_ONELEVEL, query) + for role_dn, _ in roles: + try: + conn.delete_s(role_dn) + except Exception as inst: + raise inst + except ldap.NO_SUCH_OBJECT: + pass + + def update(self, role_id, role): + if role['id'] != role_id: + raise exception.ValidationError('Cannot change role ID') + try: + old_name = self.get_by_name(role['name']) + raise exception.Conflict('Cannot duplicate name %s' % old_name) + except exception.NotFound: + pass + return super(RoleApi, self).update(role_id, role) + + def delete(self, id, tenant_dn): + conn = self.get_connection() + query = '(&(objectClass=%s)(%s=%s))' % (self.object_class, + self.id_attr, id) + try: + for role_dn, _ in conn.search_s(tenant_dn, + ldap.SCOPE_SUBTREE, + query): + conn.delete_s(role_dn) + except ldap.NO_SUCH_OBJECT: + pass + super(RoleApi, self).delete(id) diff --git a/keystone/assignment/backends/sql.py b/keystone/assignment/backends/sql.py new file mode 100644 index 00000000..5ec435ff --- /dev/null +++ b/keystone/assignment/backends/sql.py @@ -0,0 +1,748 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012-13 OpenStack LLC +# +# 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 keystone import assignment +from keystone import clean +from keystone.common import sql +from keystone.common.sql import migration +from keystone import exception + + +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): + migration.db_sync(version=version) + + def _get_project(self, session, project_id): + project_ref = session.query(Project).get(project_id) + if project_ref is None: + raise exception.ProjectNotFound(project_id=project_id) + return project_ref + + def get_project(self, tenant_id): + session = self.get_session() + return self._get_project(session, tenant_id).to_dict() + + def get_project_by_name(self, tenant_name, domain_id): + session = self.get_session() + query = session.query(Project) + query = query.filter_by(name=tenant_name) + query = query.filter_by(domain_id=domain_id) + try: + project_ref = query.one() + except sql.NotFound: + raise exception.ProjectNotFound(project_id=tenant_name) + return project_ref.to_dict() + + def get_project_user_ids(self, tenant_id): + session = self.get_session() + self.get_project(tenant_id) + query = session.query(UserProjectGrant) + query = query.filter(UserProjectGrant.project_id == + tenant_id) + project_refs = query.all() + return [project_ref.user_id for project_ref in project_refs] + + def get_project_users(self, tenant_id): + self.get_session() + self.get_project(tenant_id) + user_refs = [] + #TODO(ayoung): Move to controller or manager + for user_id in self.get_project_user_ids(tenant_id): + user_ref = self.identity_api.get_user(user_id) + user_refs.append(user_ref) + return user_refs + + def _get_metadata(self, user_id=None, tenant_id=None, + domain_id=None, group_id=None): + session = self.get_session() + + if user_id: + if tenant_id: + q = session.query(UserProjectGrant) + q = q.filter_by(project_id=tenant_id) + elif domain_id: + q = session.query(UserDomainGrant) + q = q.filter_by(domain_id=domain_id) + q = q.filter_by(user_id=user_id) + elif group_id: + if tenant_id: + q = session.query(GroupProjectGrant) + q = q.filter_by(project_id=tenant_id) + elif domain_id: + q = session.query(GroupDomainGrant) + q = q.filter_by(domain_id=domain_id) + q = q.filter_by(group_id=group_id) + try: + return q.one().data + except sql.NotFound: + raise exception.MetadataNotFound() + + def create_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + if user_id: + self.identity_api.get_user(user_id) + if group_id: + self.identity_api.get_group(group_id) + + session = self.get_session() + self._get_role(session, role_id) + + if domain_id: + self._get_domain(session, domain_id) + if project_id: + self._get_project(session, project_id) + + if project_id and inherited_to_projects: + msg = _('Inherited roles can only be assigned to domains') + raise exception.Conflict(type='role grant', details=msg) + + try: + metadata_ref = self._get_metadata(user_id, project_id, + domain_id, group_id) + is_new = False + except exception.MetadataNotFound: + metadata_ref = {} + is_new = True + + metadata_ref['roles'] = self._add_role_to_role_dicts( + role_id, inherited_to_projects, metadata_ref.get('roles', [])) + + if is_new: + self._create_metadata(user_id, project_id, metadata_ref, + domain_id, group_id) + else: + self._update_metadata(user_id, project_id, metadata_ref, + domain_id, group_id) + + def list_grants(self, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + if user_id: + self.identity_api.get_user(user_id) + if group_id: + self.identity_api.get_group(group_id) + session = self.get_session() + if domain_id: + self._get_domain(session, domain_id) + if project_id: + self._get_project(session, project_id) + + try: + metadata_ref = self._get_metadata(user_id, project_id, + domain_id, group_id) + except exception.MetadataNotFound: + metadata_ref = {} + + return [self.get_role(x) for x in + self._roles_from_role_dicts(metadata_ref.get('roles', []), + inherited_to_projects)] + + def get_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + if user_id: + self.identity_api.get_user(user_id) + if group_id: + self.identity_api.get_group(group_id) + + session = self.get_session() + role_ref = self._get_role(session, role_id) + + if domain_id: + self._get_domain(session, domain_id) + if project_id: + self._get_project(session, project_id) + + try: + metadata_ref = self._get_metadata(user_id, project_id, + domain_id, group_id) + except exception.MetadataNotFound: + metadata_ref = {} + role_ids = set(self._roles_from_role_dicts( + metadata_ref.get('roles', []), inherited_to_projects)) + if role_id not in role_ids: + raise exception.RoleNotFound(role_id=role_id) + return role_ref.to_dict() + + def delete_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + if user_id: + self.identity_api.get_user(user_id) + if group_id: + self.identity_api.get_group(group_id) + + session = self.get_session() + self._get_role(session, role_id) + + if domain_id: + self._get_domain(session, domain_id) + if project_id: + self._get_project(session, project_id) + + try: + metadata_ref = self._get_metadata(user_id, project_id, + domain_id, group_id) + is_new = False + except exception.MetadataNotFound: + metadata_ref = {} + is_new = True + + try: + metadata_ref['roles'] = self._remove_role_from_role_dicts( + role_id, inherited_to_projects, metadata_ref.get('roles', [])) + except KeyError: + raise exception.RoleNotFound(role_id=role_id) + + if is_new: + # TODO(henry-nash) It seems odd that you would create a new + # entry in response to trying to delete a role that was not + # assigned. Although benign, this should probably be removed. + self._create_metadata(user_id, project_id, metadata_ref, + domain_id, group_id) + else: + self._update_metadata(user_id, project_id, metadata_ref, + domain_id, group_id) + + def list_projects(self, domain_id=None): + session = self.get_session() + if domain_id: + self._get_domain(session, domain_id) + + query = session.query(Project) + if domain_id: + query = query.filter_by(domain_id=domain_id) + project_refs = query.all() + return [project_ref.to_dict() for project_ref in project_refs] + + def get_projects_for_user(self, user_id): + + # FIXME(henry-nash) The following should take into account + # both group and inherited roles. In fact, I don't see why this + # call can't be handled at the controller level like we do + # with 'get_roles_for_user_and_project()'. Further, this + # call seems essentially the same as 'list_user_projects()' + # later in this driver. Both should be removed. + + self.identity_api.get_user(user_id) + session = self.get_session() + query = session.query(UserProjectGrant) + query = query.filter_by(user_id=user_id) + membership_refs = query.all() + return [x.project_id for x in membership_refs] + + def add_role_to_user_and_project(self, user_id, tenant_id, role_id): + self.identity_api.get_user(user_id) + session = self.get_session() + self._get_project(session, tenant_id) + self._get_role(session, role_id) + try: + metadata_ref = self._get_metadata(user_id, tenant_id) + is_new = False + except exception.MetadataNotFound: + metadata_ref = {} + is_new = True + + try: + metadata_ref['roles'] = self._add_role_to_role_dicts( + role_id, False, metadata_ref.get('roles', []), + allow_existing=False) + except KeyError: + msg = ('User %s already has role %s in tenant %s' + % (user_id, role_id, tenant_id)) + raise exception.Conflict(type='role grant', details=msg) + + if is_new: + self._create_metadata(user_id, tenant_id, metadata_ref) + else: + self._update_metadata(user_id, tenant_id, metadata_ref) + + def remove_role_from_user_and_project(self, user_id, tenant_id, role_id): + try: + metadata_ref = self._get_metadata(user_id, tenant_id) + try: + metadata_ref['roles'] = self._remove_role_from_role_dicts( + role_id, False, metadata_ref.get('roles', [])) + except KeyError: + raise exception.RoleNotFound(message=_( + 'Cannot remove role that has not been granted, %s') % + role_id) + + if len(metadata_ref['roles']): + self._update_metadata(user_id, tenant_id, metadata_ref) + else: + session = self.get_session() + q = session.query(UserProjectGrant) + q = q.filter_by(user_id=user_id) + q = q.filter_by(project_id=tenant_id) + q.delete() + except exception.MetadataNotFound: + msg = 'Cannot remove role that has not been granted, %s' % role_id + raise exception.RoleNotFound(message=msg) + + def list_role_assignments(self): + + # TODO(henry-nash): The current implementation is really simulating + # us having a common role assignment table, rather than having the + # four different grant tables we have today. When we move to role + # assignment as a first class entity, we should create the single + # assignment table, simplifying the logic of this (and many other) + # functions. + + session = self.get_session() + assignment_list = [] + refs = session.query(UserDomainGrant).all() + for x in refs: + for r in self._roles_from_role_dicts( + x.data.get('roles', {}), False): + assignment_list.append({'user_id': x.user_id, + 'domain_id': x.domain_id, + 'role_id': r}) + for r in self._roles_from_role_dicts( + x.data.get('roles', {}), True): + assignment_list.append({'user_id': x.user_id, + 'domain_id': x.domain_id, + 'role_id': r, + 'inherited_to_projects': True}) + refs = session.query(UserProjectGrant).all() + for x in refs: + for r in self._roles_from_role_dicts( + x.data.get('roles', {}), False): + assignment_list.append({'user_id': x.user_id, + 'project_id': x.project_id, + 'role_id': r}) + refs = session.query(GroupDomainGrant).all() + for x in refs: + for r in self._roles_from_role_dicts( + x.data.get('roles', {}), False): + assignment_list.append({'group_id': x.group_id, + 'domain_id': x.domain_id, + 'role_id': r}) + for r in self._roles_from_role_dicts( + x.data.get('roles', {}), True): + assignment_list.append({'group_id': x.group_id, + 'domain_id': x.domain_id, + 'role_id': r, + 'inherited_to_projects': True}) + refs = session.query(GroupProjectGrant).all() + for x in refs: + for r in self._roles_from_role_dicts( + x.data.get('roles', {}), False): + assignment_list.append({'group_id': x.group_id, + 'project_id': x.project_id, + 'role_id': r}) + return assignment_list + + # CRUD + @sql.handle_conflicts(type='project') + def create_project(self, tenant_id, tenant): + tenant['name'] = clean.project_name(tenant['name']) + session = self.get_session() + with session.begin(): + tenant_ref = Project.from_dict(tenant) + session.add(tenant_ref) + session.flush() + return tenant_ref.to_dict() + + @sql.handle_conflicts(type='project') + def update_project(self, tenant_id, tenant): + session = self.get_session() + + if 'name' in tenant: + tenant['name'] = clean.project_name(tenant['name']) + + with session.begin(): + tenant_ref = self._get_project(session, tenant_id) + old_project_dict = tenant_ref.to_dict() + for k in tenant: + old_project_dict[k] = tenant[k] + new_project = Project.from_dict(old_project_dict) + for attr in Project.attributes: + if attr != 'id': + setattr(tenant_ref, attr, getattr(new_project, attr)) + tenant_ref.extra = new_project.extra + session.flush() + return tenant_ref.to_dict(include_extra_dict=True) + + @sql.handle_conflicts(type='project') + def delete_project(self, tenant_id): + session = self.get_session() + + with session.begin(): + tenant_ref = self._get_project(session, tenant_id) + + q = session.query(UserProjectGrant) + q = q.filter_by(project_id=tenant_id) + q.delete(False) + + q = session.query(UserProjectGrant) + q = q.filter_by(project_id=tenant_id) + q.delete(False) + + q = session.query(GroupProjectGrant) + q = q.filter_by(project_id=tenant_id) + q.delete(False) + + session.delete(tenant_ref) + session.flush() + + @sql.handle_conflicts(type='metadata') + def _create_metadata(self, user_id, tenant_id, metadata, + domain_id=None, group_id=None): + session = self.get_session() + with session.begin(): + if user_id: + if tenant_id: + session.add(UserProjectGrant + (user_id=user_id, + project_id=tenant_id, + data=metadata)) + elif domain_id: + session.add(UserDomainGrant + (user_id=user_id, + domain_id=domain_id, + data=metadata)) + elif group_id: + if tenant_id: + session.add(GroupProjectGrant + (group_id=group_id, + project_id=tenant_id, + data=metadata)) + elif domain_id: + session.add(GroupDomainGrant + (group_id=group_id, + domain_id=domain_id, + data=metadata)) + session.flush() + return metadata + + @sql.handle_conflicts(type='metadata') + def _update_metadata(self, user_id, tenant_id, metadata, + domain_id=None, group_id=None): + session = self.get_session() + with session.begin(): + if user_id: + if tenant_id: + q = session.query(UserProjectGrant) + q = q.filter_by(user_id=user_id) + q = q.filter_by(project_id=tenant_id) + elif domain_id: + q = session.query(UserDomainGrant) + q = q.filter_by(user_id=user_id) + q = q.filter_by(domain_id=domain_id) + elif group_id: + if tenant_id: + q = session.query(GroupProjectGrant) + q = q.filter_by(group_id=group_id) + q = q.filter_by(project_id=tenant_id) + elif domain_id: + q = session.query(GroupDomainGrant) + q = q.filter_by(group_id=group_id) + q = q.filter_by(domain_id=domain_id) + metadata_ref = q.first() + data = metadata_ref.data.copy() + data.update(metadata) + metadata_ref.data = data + session.flush() + return metadata_ref + + # domain crud + + @sql.handle_conflicts(type='domain') + def create_domain(self, domain_id, domain): + session = self.get_session() + with session.begin(): + ref = Domain.from_dict(domain) + session.add(ref) + session.flush() + return ref.to_dict() + + def list_domains(self): + session = self.get_session() + refs = session.query(Domain).all() + return [ref.to_dict() for ref in refs] + + def _get_domain(self, session, domain_id): + ref = session.query(Domain).get(domain_id) + if ref is None: + raise exception.DomainNotFound(domain_id=domain_id) + return ref + + def get_domain(self, domain_id): + session = self.get_session() + return self._get_domain(session, domain_id).to_dict() + + def get_domain_by_name(self, domain_name): + session = self.get_session() + try: + ref = (session.query(Domain). + filter_by(name=domain_name).one()) + except sql.NotFound: + raise exception.DomainNotFound(domain_id=domain_name) + return ref.to_dict() + + @sql.handle_conflicts(type='domain') + def update_domain(self, domain_id, domain): + session = self.get_session() + with session.begin(): + ref = self._get_domain(session, domain_id) + old_dict = ref.to_dict() + for k in domain: + old_dict[k] = domain[k] + new_domain = Domain.from_dict(old_dict) + for attr in Domain.attributes: + if attr != 'id': + setattr(ref, attr, getattr(new_domain, attr)) + ref.extra = new_domain.extra + session.flush() + return ref.to_dict() + + def delete_domain(self, domain_id): + session = self.get_session() + with session.begin(): + ref = self._get_domain(session, domain_id) + session.delete(ref) + session.flush() + + def list_user_projects(self, user_id): + + # FIXME(henry-nash) The following should take into account + # both group and inherited roles. In fact, I don't see why this + # call can't be handled at the controller level like we do + # with 'get_roles_for_user_and_project()'. Further, this + # call seems essentially the same as 'get_projects_for_user()' + # earlier in this driver. Both should be removed. + + session = self.get_session() + user = self.identity_api.get_user(user_id) + metadata_refs = session\ + .query(UserProjectGrant)\ + .filter_by(user_id=user_id) + project_ids = set([x.project_id for x in metadata_refs + if x.data.get('roles')]) + if user.get('project_id'): + project_ids.add(user['project_id']) + + # FIXME(dolph): this should be removed with proper migrations + if user.get('tenant_id'): + project_ids.add(user['tenant_id']) + + return [self.get_project(x) for x in project_ids] + + # role crud + + @sql.handle_conflicts(type='role') + def create_role(self, role_id, role): + session = self.get_session() + with session.begin(): + ref = Role.from_dict(role) + session.add(ref) + session.flush() + return ref.to_dict() + + def list_roles(self): + session = self.get_session() + refs = session.query(Role).all() + return [ref.to_dict() for ref in refs] + + def _get_role(self, session, role_id): + ref = session.query(Role).get(role_id) + if ref is None: + raise exception.RoleNotFound(role_id=role_id) + return ref + + def get_role(self, role_id): + session = self.get_session() + return self._get_role(session, role_id).to_dict() + + @sql.handle_conflicts(type='role') + def update_role(self, role_id, role): + session = self.get_session() + with session.begin(): + ref = self._get_role(session, role_id) + old_dict = ref.to_dict() + for k in role: + old_dict[k] = role[k] + new_role = Role.from_dict(old_dict) + for attr in Role.attributes: + if attr != 'id': + setattr(ref, attr, getattr(new_role, attr)) + ref.extra = new_role.extra + session.flush() + return ref.to_dict() + + def delete_role(self, role_id): + session = self.get_session() + + with session.begin(): + ref = self._get_role(session, role_id) + for metadata_ref in session.query(UserProjectGrant): + try: + self.delete_grant(role_id, user_id=metadata_ref.user_id, + project_id=metadata_ref.project_id) + except exception.RoleNotFound: + pass + for metadata_ref in session.query(UserDomainGrant): + try: + self.delete_grant(role_id, user_id=metadata_ref.user_id, + domain_id=metadata_ref.domain_id) + except exception.RoleNotFound: + pass + for metadata_ref in session.query(GroupProjectGrant): + try: + self.delete_grant(role_id, group_id=metadata_ref.group_id, + project_id=metadata_ref.project_id) + except exception.RoleNotFound: + pass + for metadata_ref in session.query(GroupDomainGrant): + try: + self.delete_grant(role_id, group_id=metadata_ref.group_id, + domain_id=metadata_ref.domain_id) + except exception.RoleNotFound: + pass + + session.delete(ref) + session.flush() + + def delete_user(self, user_id): + session = self.get_session() + + with session.begin(): + q = session.query(UserProjectGrant) + q = q.filter_by(user_id=user_id) + q.delete(False) + + q = session.query(UserDomainGrant) + q = q.filter_by(user_id=user_id) + q.delete(False) + + session.flush() + + def delete_group(self, group_id): + session = self.get_session() + + with session.begin(): + + q = session.query(GroupProjectGrant) + q = q.filter_by(group_id=group_id) + q.delete(False) + + q = session.query(GroupDomainGrant) + q = q.filter_by(group_id=group_id) + q.delete(False) + + session.flush() + + +class Domain(sql.ModelBase, sql.DictBase): + __tablename__ = 'domain' + attributes = ['id', 'name', 'enabled'] + id = sql.Column(sql.String(64), primary_key=True) + name = sql.Column(sql.String(64), unique=True, nullable=False) + enabled = sql.Column(sql.Boolean, default=True) + extra = sql.Column(sql.JsonBlob()) + + +class Project(sql.ModelBase, sql.DictBase): + __tablename__ = 'project' + attributes = ['id', 'name', 'domain_id', 'description', 'enabled'] + id = sql.Column(sql.String(64), primary_key=True) + name = sql.Column(sql.String(64), nullable=False) + domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'), + nullable=False) + description = sql.Column(sql.Text()) + enabled = sql.Column(sql.Boolean) + extra = sql.Column(sql.JsonBlob()) + # Unique constraint across two columns to create the separation + # rather than just only 'name' being unique + __table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {}) + + +class Role(sql.ModelBase, sql.DictBase): + __tablename__ = 'role' + attributes = ['id', 'name'] + id = sql.Column(sql.String(64), primary_key=True) + name = sql.Column(sql.String(64), unique=True, nullable=False) + extra = sql.Column(sql.JsonBlob()) + + +class BaseGrant(sql.DictBase): + """Base Grant class. + + There are four grant tables in the current implementation, one for + each type of grant: + + - User for Project + - User for Domain + - Group for Project + - Group for Domain + + Each is a table with the two attributes above as a combined primary key, + with the data field holding all roles for that combination. The data + field is a list of dicts. For regular role assignments each dict in + the list of of the form: + + {'id': role_id} + + If the OS-INHERIT extension is enabled and the role on a domain is an + inherited role, the dict will be of the form: + + {'id': role_id, 'inherited_to': 'projects'} + + """ + def to_dict(self): + """Override parent to_dict() method with a simpler implementation. + + Grant tables don't have non-indexed 'extra' attributes, so the + parent implementation is not applicable. + """ + return dict(self.iteritems()) + + +class UserProjectGrant(sql.ModelBase, BaseGrant): + __tablename__ = 'user_project_metadata' + user_id = sql.Column(sql.String(64), + primary_key=True) + project_id = sql.Column(sql.String(64), + primary_key=True) + data = sql.Column(sql.JsonBlob()) + + +class UserDomainGrant(sql.ModelBase, BaseGrant): + __tablename__ = 'user_domain_metadata' + user_id = sql.Column(sql.String(64), primary_key=True) + domain_id = sql.Column(sql.String(64), primary_key=True) + data = sql.Column(sql.JsonBlob()) + + +class GroupProjectGrant(sql.ModelBase, BaseGrant): + __tablename__ = 'group_project_metadata' + group_id = sql.Column(sql.String(64), primary_key=True) + project_id = sql.Column(sql.String(64), primary_key=True) + data = sql.Column(sql.JsonBlob()) + + +class GroupDomainGrant(sql.ModelBase, BaseGrant): + __tablename__ = 'group_domain_metadata' + group_id = sql.Column(sql.String(64), primary_key=True) + domain_id = sql.Column(sql.String(64), primary_key=True) + data = sql.Column(sql.JsonBlob()) diff --git a/keystone/assignment/core.py b/keystone/assignment/core.py new file mode 100644 index 00000000..b71e2a18 --- /dev/null +++ b/keystone/assignment/core.py @@ -0,0 +1,551 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# 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. + +"""Main entry point into the assignment service.""" + +from keystone.common import dependency +from keystone.common import logging +from keystone.common import manager +from keystone import config +from keystone import exception + + +CONF = config.CONF +LOG = logging.getLogger(__name__) + +DEFAULT_DOMAIN = {'description': + (u'Owns users and tenants (i.e. projects)' + ' available on Identity API v2.'), + 'enabled': True, + 'id': CONF.identity.default_domain_id, + 'name': u'Default'} + + +@dependency.provider('assignment_api') +class Manager(manager.Manager): + """Default pivot point for the Assignment backend. + + See :mod:`keystone.common.manager.Manager` for more details on how this + dynamically calls the backend. + assignment.Manager() and identity.Manager() have a circular dependency. + The late import works around this. THe if block prevents creation of the + 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) + + assignment_driver = CONF.assignment.driver + if assignment_driver is None: + assignment_driver = identity_api.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. + + This includes roles directly assigned to the user on the + project, as well as those by virtue of group membership. If + the OS-INHERIT extension is enabled, then this will also + include roles inherited from the domain. + + :returns: a list of role ids. + :raises: keystone.exception.UserNotFound, + keystone.exception.ProjectNotFound + + """ + def _get_group_project_roles(user_id, project_ref): + role_list = [] + group_refs = (self.identity_api.list_groups_for_user + (user_id=user_id)) + for x in group_refs: + try: + metadata_ref = self._get_metadata( + group_id=x['id'], tenant_id=project_ref['id']) + role_list += self._roles_from_role_dicts( + metadata_ref.get('roles', {}), False) + except exception.MetadataNotFound: + # no group grant, skip + pass + + if CONF.os_inherit.enabled: + # Now get any inherited group roles for the owning domain + try: + metadata_ref = self._get_metadata( + group_id=x['id'], + domain_id=project_ref['domain_id']) + role_list += self._roles_from_role_dicts( + metadata_ref.get('roles', {}), True) + except (exception.MetadataNotFound, + exception.NotImplemented): + pass + + return role_list + + def _get_user_project_roles(user_id, project_ref): + role_list = [] + try: + metadata_ref = self._get_metadata(user_id=user_id, + tenant_id=project_ref['id']) + role_list = self._roles_from_role_dicts( + metadata_ref.get('roles', {}), False) + except exception.MetadataNotFound: + pass + + if CONF.os_inherit.enabled: + # Now get any inherited roles for the owning domain + try: + metadata_ref = self._get_metadata( + user_id=user_id, domain_id=project_ref['domain_id']) + role_list += self._roles_from_role_dicts( + metadata_ref.get('roles', {}), True) + except (exception.MetadataNotFound, exception.NotImplemented): + pass + + return role_list + + self.identity_api.get_user(user_id) + project_ref = self.get_project(tenant_id) + user_role_list = _get_user_project_roles(user_id, project_ref) + group_role_list = _get_group_project_roles(user_id, project_ref) + # Use set() to process the list to remove any duplicates + return list(set(user_role_list + group_role_list)) + + def get_roles_for_user_and_domain(self, user_id, domain_id): + """Get the roles associated with a user within given domain. + + :returns: a list of role ids. + :raises: keystone.exception.UserNotFound, + keystone.exception.DomainNotFound + + """ + + def _get_group_domain_roles(user_id, domain_id): + role_list = [] + group_refs = (self.identity_api. + list_groups_for_user(user_id=user_id)) + for x in group_refs: + try: + metadata_ref = self._get_metadata(group_id=x['id'], + domain_id=domain_id) + role_list += self._roles_from_role_dicts( + metadata_ref.get('roles', {}), False) + except (exception.MetadataNotFound, exception.NotImplemented): + # MetadataNotFound implies no group grant, so skip. + # Ignore NotImplemented since not all backends support + # domains. + pass + return role_list + + def _get_user_domain_roles(user_id, domain_id): + metadata_ref = {} + try: + metadata_ref = self._get_metadata(user_id=user_id, + domain_id=domain_id) + except (exception.MetadataNotFound, exception.NotImplemented): + # MetadataNotFound implies no user grants. + # Ignore NotImplemented since not all backends support + # domains + pass + return self._roles_from_role_dicts( + metadata_ref.get('roles', {}), False) + + self.identity_api.get_user(user_id) + self.get_domain(domain_id) + user_role_list = _get_user_domain_roles(user_id, domain_id) + group_role_list = _get_group_domain_roles(user_id, domain_id) + # Use set() to process the list to remove any duplicates + return list(set(user_role_list + group_role_list)) + + def add_user_to_project(self, tenant_id, user_id): + """Add user to a tenant by creating a default role relationship. + + :raises: keystone.exception.ProjectNotFound, + keystone.exception.UserNotFound + + """ + self.driver.add_role_to_user_and_project(user_id, + tenant_id, + config.CONF.member_role_id) + + def remove_user_from_project(self, tenant_id, user_id): + """Remove user from a tenant + + :raises: keystone.exception.ProjectNotFound, + keystone.exception.UserNotFound + + """ + roles = self.get_roles_for_user_and_project(user_id, tenant_id) + if not roles: + raise exception.NotFound(tenant_id) + for role_id in roles: + self.remove_role_from_user_and_project(user_id, tenant_id, role_id) + + +class Driver(object): + + def _role_to_dict(self, role_id, inherited): + role_dict = {'id': role_id} + if inherited: + role_dict['inherited_to'] = 'projects' + return role_dict + + def _roles_from_role_dicts(self, dict_list, inherited): + role_list = [] + for d in dict_list: + if ((not d.get('inherited_to') and not inherited) or + (d.get('inherited_to') == 'projects' and inherited)): + role_list.append(d['id']) + return role_list + + def _add_role_to_role_dicts(self, role_id, inherited, dict_list, + allow_existing=True): + # There is a difference in error semantics when trying to + # assign a role that already exists between the coded v2 and v3 + # API calls. v2 will error if the assignment already exists, + # while v3 is silent. Setting the 'allow_existing' parameter + # appropriately lets this call be used for both. + role_set = set([frozenset(r.items()) for r in dict_list]) + key = frozenset(self._role_to_dict(role_id, inherited).items()) + if not allow_existing and key in role_set: + raise KeyError + role_set.add(key) + return [dict(r) for r in role_set] + + def _remove_role_from_role_dicts(self, role_id, inherited, dict_list): + role_set = set([frozenset(r.items()) for r in dict_list]) + role_set.remove(frozenset(self._role_to_dict(role_id, + inherited).items())) + return [dict(r) for r in role_set] + + def get_project_by_name(self, tenant_name, domain_id): + """Get a tenant by name. + + :returns: tenant_ref + :raises: keystone.exception.ProjectNotFound + + """ + raise exception.NotImplemented() + + def get_project_users(self, tenant_id): + """Lists all users with a relationship to the specified project. + + :returns: a list of user_refs or an empty set. + :raises: keystone.exception.ProjectNotFound + + """ + raise exception.NotImplemented() + + def get_projects_for_user(self, user_id): + """Get the tenants associated with a given user. + + :returns: a list of tenant_id's. + :raises: keystone.exception.UserNotFound + + """ + raise exception.NotImplemented() + + def add_role_to_user_and_project(self, user_id, tenant_id, role_id): + """Add a role to a user within given tenant. + + :raises: keystone.exception.UserNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.RoleNotFound + """ + raise exception.NotImplemented() + + def remove_role_from_user_and_project(self, user_id, tenant_id, role_id): + """Remove a role from a user within given tenant. + + :raises: keystone.exception.UserNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + # assignment/grant crud + + def create_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + """Creates a new assignment/grant. + + If the assignment is to a domain, then optionally it may be + specified as inherited to owned projects (this requires + the OS-INHERIT extension to be enabled). + + :raises: keystone.exception.UserNotFound, + keystone.exception.GroupNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.DomainNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + def list_grants(self, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + """Lists assignments/grants. + + :raises: keystone.exception.UserNotFound, + keystone.exception.GroupNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.DomainNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + def get_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + """Lists assignments/grants. + + :raises: keystone.exception.UserNotFound, + keystone.exception.GroupNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.DomainNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + def delete_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + """Lists assignments/grants. + + :raises: keystone.exception.UserNotFound, + keystone.exception.GroupNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.DomainNotFound, + keystone.exception.ProjectNotFound, + keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + def list_role_assignments(self): + + raise exception.NotImplemented() + + # domain crud + def create_domain(self, domain_id, domain): + """Creates a new domain. + + :raises: keystone.exception.Conflict + + """ + raise exception.NotImplemented() + + def list_domains(self): + """List all domains in the system. + + :returns: a list of domain_refs or an empty list. + + """ + raise exception.NotImplemented() + + def get_domain(self, domain_id): + """Get a domain by ID. + + :returns: domain_ref + :raises: keystone.exception.DomainNotFound + + """ + raise exception.NotImplemented() + + def get_domain_by_name(self, domain_name): + """Get a domain by name. + + :returns: domain_ref + :raises: keystone.exception.DomainNotFound + + """ + raise exception.NotImplemented() + + def update_domain(self, domain_id, domain): + """Updates an existing domain. + + :raises: keystone.exception.DomainNotFound, + keystone.exception.Conflict + + """ + raise exception.NotImplemented() + + def delete_domain(self, domain_id): + """Deletes an existing domain. + + :raises: keystone.exception.DomainNotFound + + """ + raise exception.NotImplemented() + + # project crud + def create_project(self, project_id, project): + """Creates a new project. + + :raises: keystone.exception.Conflict + + """ + raise exception.NotImplemented() + + def list_projects(self, domain_id=None): + """List all projects in the system. + + :returns: a list of project_refs or an empty list. + + """ + raise exception.NotImplemented() + + def list_user_projects(self, user_id): + """List all projects associated with a given user. + + :returns: a list of project_refs or an empty list. + + """ + raise exception.NotImplemented() + + def get_project(self, project_id): + """Get a project by ID. + + :returns: project_ref + :raises: keystone.exception.ProjectNotFound + + """ + raise exception.NotImplemented() + + def update_project(self, project_id, project): + """Updates an existing project. + + :raises: keystone.exception.ProjectNotFound, + keystone.exception.Conflict + + """ + raise exception.NotImplemented() + + def delete_project(self, project_id): + """Deletes an existing project. + + :raises: keystone.exception.ProjectNotFound + + """ + raise exception.NotImplemented() + + """Interface description for an assignment driver.""" + # role crud + + def create_role(self, role_id, role): + """Creates a new role. + + :raises: keystone.exception.Conflict + + """ + raise exception.NotImplemented() + + def list_roles(self): + """List all roles in the system. + + :returns: a list of role_refs or an empty list. + + """ + raise exception.NotImplemented() + + def get_role(self, role_id): + """Get a role by ID. + + :returns: role_ref + :raises: keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + def update_role(self, role_id, role): + """Updates an existing role. + + :raises: keystone.exception.RoleNotFound, + keystone.exception.Conflict + + """ + raise exception.NotImplemented() + + def delete_role(self, role_id): + """Deletes an existing role. + + :raises: keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + +#TODO(ayoung): determine what else these two functions raise + def delete_user(self, user_id): + """Deletes all assignments for a user. + + :raises: keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + def delete_group(self, group_id): + """Deletes all assignments for a group. + + :raises: keystone.exception.RoleNotFound + + """ + raise exception.NotImplemented() + + #domain management functions for backends that only allow a single domain. + #currently, this is only LDAP, but might be used by PAM or other backends + #as well. This is used by both identity and assignment drivers. + def _set_default_domain(self, ref): + """If the domain ID has not been set, set it to the default.""" + if isinstance(ref, dict): + if 'domain_id' not in ref: + ref = ref.copy() + ref['domain_id'] = CONF.identity.default_domain_id + return ref + elif isinstance(ref, list): + return [self._set_default_domain(x) for x in ref] + else: + raise ValueError(_('Expected dict or list: %s') % type(ref)) + + def _validate_default_domain(self, ref): + """Validate that either the default domain or nothing is specified. + + Also removes the domain from the ref so that LDAP doesn't have to + persist the attribute. + + """ + ref = ref.copy() + domain_id = ref.pop('domain_id', CONF.identity.default_domain_id) + self._validate_default_domain_id(domain_id) + return ref + + def _validate_default_domain_id(self, domain_id): + """Validate that the domain ID specified belongs to the default domain. + + """ + if domain_id != CONF.identity.default_domain_id: + raise exception.DomainNotFound(domain_id=domain_id) diff --git a/keystone/auth/controllers.py b/keystone/auth/controllers.py index bef4128d..d1bd764f 100644 --- a/keystone/auth/controllers.py +++ b/keystone/auth/controllers.py @@ -14,12 +14,11 @@ # License for the specific language governing permissions and limitations # under the License. -import json -from keystone.auth import token_factory -from keystone.common import cms from keystone.common import controller +from keystone.common import dependency from keystone.common import logging +from keystone.common import wsgi from keystone import config from keystone import exception from keystone import identity @@ -190,6 +189,10 @@ class AuthInfo(object): self._scope_data = (None, None, trust_ref) def _validate_auth_methods(self): + if 'identity' not in self.auth: + raise exception.ValidationError(attribute='identity', + target='auth') + # make sure auth methods are provided if 'methods' not in self.auth['identity']: raise exception.ValidationError(attribute='methods', @@ -222,7 +225,7 @@ class AuthInfo(object): :returns: list of auth method names """ - return self.auth['identity']['methods'] + return self.auth['identity']['methods'] or [] def get_method_data(self, method): """Get the auth method payload. @@ -267,6 +270,7 @@ class AuthInfo(object): self._scope_data = (domain_id, project_id, trust) +@dependency.requires('token_provider_api') class Auth(controller.V3Controller): def __init__(self, *args, **kw): super(Auth, self).__init__(*args, **kw) @@ -275,19 +279,31 @@ class Auth(controller.V3Controller): def authenticate_for_token(self, context, auth=None): """Authenticate user and issue a token.""" + include_catalog = 'nocatalog' not in context['query_string'] + try: auth_info = AuthInfo(context, auth=auth) - auth_context = {'extras': {}, 'method_names': []} + auth_context = {'extras': {}, 'method_names': [], 'bind': {}} self.authenticate(context, auth_info, auth_context) self._check_and_set_default_scoping(auth_info, auth_context) - (token_id, token_data) = token_factory.create_token( - auth_context, auth_info) - return token_factory.render_token_data_response( - token_id, token_data, created=True) - except exception.SecurityError: - raise - except Exception as e: - LOG.exception(e) + (domain_id, project_id, trust) = auth_info.get_scope() + method_names = auth_info.get_method_names() + method_names += auth_context.get('method_names', []) + # make sure the list is unique + method_names = list(set(method_names)) + + (token_id, token_data) = self.token_provider_api.issue_token( + user_id=auth_context['user_id'], + method_names=method_names, + expires_at=auth_context.get('expires_at'), + project_id=project_id, + domain_id=domain_id, + auth_context=auth_context, + trust=trust, + include_catalog=include_catalog) + return render_token_data_response(token_id, token_data, + created=True) + except exception.TrustNotFound as e: raise exception.Unauthorized(e) def _check_and_set_default_scoping(self, auth_info, auth_context): @@ -309,31 +325,13 @@ class Auth(controller.V3Controller): LOG.exception(e) raise exception.Unauthorized(e) - def _build_remote_user_auth_context(self, context, auth_info, - auth_context): - username = context['REMOTE_USER'] - # FIXME(gyee): REMOTE_USER is not good enough since we are - # requiring domain_id to do user lookup now. Try to get - # the user_id from auth_info for now, assuming external auth - # has check to make sure user is the same as the one specify - # in "identity". - if 'password' in auth_info.get_method_names(): - user_info = auth_info.get_method_data('password') - user_ref = auth_info.lookup_user(user_info['user']) - auth_context['user_id'] = user_ref['id'] - else: - msg = _('Unable to lookup user %s') % (username) - raise exception.Unauthorized(msg) - def authenticate(self, context, auth_info, auth_context): """Authenticate user.""" # user have been authenticated externally if 'REMOTE_USER' in context: - self._build_remote_user_auth_context(context, - auth_info, - auth_context) - return + external = get_auth_method('external') + external.authenticate(context, auth_info, auth_context) # need to aggregate the results in case two or more methods # are specified @@ -355,44 +353,41 @@ class Auth(controller.V3Controller): msg = _('User not found') raise exception.Unauthorized(msg) - def _get_token_ref(self, context, token_id, belongs_to=None): - token_ref = self.token_api.get_token(token_id) - if cms.is_ans1_token(token_id): - verified_token = cms.cms_verify(cms.token_to_cms(token_id), - CONF.signing.certfile, - CONF.signing.ca_certs) - token_ref = json.loads(verified_token) - if belongs_to: - assert token_ref['project']['id'] == belongs_to - return token_ref - @controller.protected def check_token(self, context): - try: - token_id = context.get('subject_token_id') - belongs_to = context['query_string'].get('belongsTo') - assert self._get_token_ref(context, token_id, belongs_to) - except Exception as e: - LOG.error(e) - raise exception.Unauthorized(e) + token_id = context.get('subject_token_id') + self.token_provider_api.check_token(token_id) @controller.protected def revoke_token(self, context): token_id = context.get('subject_token_id') - return self.token_controllers_ref.delete_token(context, token_id) + return self.token_provider_api.revoke_token(token_id) @controller.protected def validate_token(self, context): token_id = context.get('subject_token_id') - self.check_token(context) - token_ref = self.token_api.get_token(token_id) - token_data = token_factory.recreate_token_data( - token_ref.get('token_data'), - token_ref['expires'], - token_ref.get('user'), - token_ref.get('tenant')) - return token_factory.render_token_data_response(token_id, token_data) + token_data = self.token_provider_api.validate_token(token_id) + return render_token_data_response(token_id, token_data) @controller.protected def revocation_list(self, context, auth=None): return self.token_controllers_ref.revocation_list(context, auth) + + +#FIXME(gyee): not sure if it belongs here or keystone.common. Park it here +# for now. +def render_token_data_response(token_id, token_data, created=False): + """Render token data HTTP response. + + Stash token ID into the X-Subject-Token header. + + """ + headers = [('X-Subject-Token', token_id)] + + if created: + status = (201, 'Created') + else: + status = (200, 'OK') + + return wsgi.render_response(body=token_data, + status=status, headers=headers) diff --git a/keystone/auth/plugins/external.py b/keystone/auth/plugins/external.py new file mode 100644 index 00000000..67b11001 --- /dev/null +++ b/keystone/auth/plugins/external.py @@ -0,0 +1,87 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack LLC +# +# 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. + +"""Keystone External Authentication Plugin""" + +from keystone.common import config +from keystone import exception + + +CONF = config.CONF + + +class ExternalDefault(object): + def authenticate(self, context, auth_info, auth_context): + """Use REMOTE_USER to look up the user in the identity backend + + auth_context is an in-out variable that will be updated with the + username from the REMOTE_USER environment variable. + """ + try: + REMOTE_USER = context['REMOTE_USER'] + except KeyError: + msg = _('No authenticated user') + raise exception.Unauthorized(msg) + try: + names = REMOTE_USER.split('@') + username = names.pop(0) + domain_id = CONF.identity.default_domain_id + user_ref = auth_info.identity_api.get_user_by_name(username, + domain_id) + auth_context['user_id'] = user_ref['id'] + if ('kerberos' in CONF.token.bind and + context.get('AUTH_TYPE', '').lower() == 'negotiate'): + auth_context['bind']['kerberos'] = username + except Exception: + msg = _('Unable to lookup user %s') % (REMOTE_USER) + raise exception.Unauthorized(msg) + + +class ExternalDomain(object): + def authenticate(self, context, auth_info, auth_context): + """Use REMOTE_USER to look up the user in the identity backend + + auth_context is an in-out variable that will be updated with the + username from the REMOTE_USER environment variable. + + If REMOTE_USER contains an `@` assume that the substring before the @ + is the username, and the substring after the @ is the domain name. + """ + try: + REMOTE_USER = context['REMOTE_USER'] + except KeyError: + msg = _('No authenticated user') + raise exception.Unauthorized(msg) + try: + names = REMOTE_USER.split('@') + username = names.pop(0) + if names: + domain_name = names[0] + domain_ref = (auth_info.identity_api. + get_domain_by_name(domain_name)) + domain_id = domain_ref['id'] + else: + domain_id = CONF.identity.default_domain_id + user_ref = auth_info.identity_api.get_user_by_name(username, + domain_id) + auth_context['user_id'] = user_ref['id'] + if ('kerberos' in CONF.token.bind and + context.get('AUTH_TYPE', '').lower() == 'negotiate'): + auth_context['bind']['kerberos'] = username + + except Exception: + msg = _('Unable to lookup user %s') % (REMOTE_USER) + raise exception.Unauthorized(msg) diff --git a/keystone/auth/plugins/password.py b/keystone/auth/plugins/password.py index 631ce08d..f3cfeba8 100644 --- a/keystone/auth/plugins/password.py +++ b/keystone/auth/plugins/password.py @@ -103,8 +103,14 @@ class Password(auth.AuthMethodHandler): # FIXME(gyee): identity.authenticate() can use some refactoring since # all we care is password matches - self.identity_api.authenticate( - user_id=user_info.user_id, - password=user_info.password) + try: + self.identity_api.authenticate( + user_id=user_info.user_id, + password=user_info.password) + except AssertionError: + # authentication failed because of invalid username or password + msg = _('Invalid username or password') + raise exception.Unauthorized(msg) + if 'user_id' not in user_context: user_context['user_id'] = user_info.user_id diff --git a/keystone/auth/plugins/token.py b/keystone/auth/plugins/token.py index d9b3d2f8..720eccac 100644 --- a/keystone/auth/plugins/token.py +++ b/keystone/auth/plugins/token.py @@ -16,6 +16,7 @@ from keystone import auth from keystone.common import logging +from keystone.common import wsgi from keystone import exception from keystone import token @@ -36,6 +37,7 @@ class Token(auth.AuthMethodHandler): target=METHOD_NAME) token_id = auth_payload['id'] token_ref = self.token_api.get_token(token_id) + wsgi.validate_token_bind(context, token_ref) user_context.setdefault( 'user_id', token_ref['token_data']['token']['user']['id']) # to support Grizzly-3 to Grizzly-RC1 transition @@ -46,7 +48,8 @@ class Token(auth.AuthMethodHandler): token_ref['token_data']['token']['extras']) user_context['method_names'].extend( token_ref['token_data']['token']['methods']) - if 'trust' in token_ref['token_data']: + if ('OS-TRUST:trust' in token_ref['token_data']['token'] or + 'trust' in token_ref['token_data']['token']): raise exception.Forbidden() except AssertionError as e: LOG.error(e) diff --git a/keystone/auth/token_factory.py b/keystone/auth/token_factory.py deleted file mode 100644 index 22bc8363..00000000 --- a/keystone/auth/token_factory.py +++ /dev/null @@ -1,368 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2013 OpenStack LLC -# -# 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 Factory""" - -import json -import sys -import uuid -import webob - -from keystone import catalog -from keystone.common import cms -from keystone.common import environment -from keystone.common import logging -from keystone.common import utils -from keystone import config -from keystone import exception -from keystone import identity -from keystone.openstack.common import jsonutils -from keystone.openstack.common import timeutils -from keystone import token as token_module -from keystone import trust - - -CONF = config.CONF - -LOG = logging.getLogger(__name__) - - -class TokenDataHelper(object): - """Token data helper.""" - def __init__(self): - self.identity_api = identity.Manager() - self.catalog_api = catalog.Manager() - self.trust_api = trust.Manager() - - def _get_filtered_domain(self, domain_id): - domain_ref = self.identity_api.get_domain(domain_id) - return {'id': domain_ref['id'], 'name': domain_ref['name']} - - def _populate_scope(self, token_data, domain_id, project_id): - if 'domain' in token_data or 'project' in token_data: - return - - if domain_id: - token_data['domain'] = self._get_filtered_domain(domain_id) - if project_id: - project_ref = self.identity_api.get_project(project_id) - filtered_project = { - 'id': project_ref['id'], - 'name': project_ref['name']} - filtered_project['domain'] = self._get_filtered_domain( - project_ref['domain_id']) - token_data['project'] = filtered_project - - def _get_project_roles_for_user(self, user_id, project_id): - roles = self.identity_api.get_roles_for_user_and_project( - user_id, project_id) - roles_ref = [] - for role_id in roles: - role_ref = self.identity_api.get_role(role_id) - role_ref.setdefault('project_id', project_id) - roles_ref.append(role_ref) - # user have no project roles, therefore access denied - if len(roles_ref) == 0: - msg = _('User have no access to project') - LOG.debug(msg) - raise exception.Unauthorized(msg) - return roles_ref - - def _get_domain_roles_for_user(self, user_id, domain_id): - roles = self.identity_api.get_roles_for_user_and_domain( - user_id, domain_id) - roles_ref = [] - for role_id in roles: - role_ref = self.identity_api.get_role(role_id) - role_ref.setdefault('domain_id', domain_id) - roles_ref.append(role_ref) - # user have no domain roles, therefore access denied - if len(roles_ref) == 0: - msg = _('User have no access to domain') - LOG.debug(msg) - raise exception.Unauthorized(msg) - return roles_ref - - def _get_roles_for_user(self, user_id, domain_id, project_id): - roles = [] - if domain_id: - roles = self._get_domain_roles_for_user(user_id, domain_id) - if project_id: - roles = self._get_project_roles_for_user(user_id, project_id) - return roles - - def _populate_user(self, token_data, user_id, domain_id, project_id, - trust): - if 'user' in token_data: - return - - user_ref = self.identity_api.get_user(user_id) - if CONF.trust.enabled and trust: - trustor_user_ref = self.identity_api.get_user( - trust['trustor_user_id']) - if not trustor_user_ref['enabled']: - raise exception.Forbidden() - if trust['impersonation']: - user_ref = trustor_user_ref - token_data['OS-TRUST:trust'] = ( - { - 'id': trust['id'], - 'trustor_user': {'id': trust['trustor_user_id']}, - 'trustee_user': {'id': trust['trustee_user_id']}, - 'impersonation': trust['impersonation'] - }) - filtered_user = { - 'id': user_ref['id'], - 'name': user_ref['name'], - 'domain': self._get_filtered_domain(user_ref['domain_id'])} - token_data['user'] = filtered_user - - def _populate_roles(self, token_data, user_id, domain_id, project_id, - trust): - if 'roles' in token_data: - return - - if CONF.trust.enabled and trust: - token_user_id = trust['trustor_user_id'] - token_project_id = trust['project_id'] - #trusts do not support domains yet - token_domain_id = None - else: - token_user_id = user_id - token_project_id = project_id - token_domain_id = domain_id - - if token_domain_id or token_project_id: - roles = self._get_roles_for_user(token_user_id, - token_domain_id, - token_project_id) - filtered_roles = [] - if CONF.trust.enabled and trust: - for trust_role in trust['roles']: - match_roles = [x for x in roles - if x['id'] == trust_role['id']] - if match_roles: - filtered_roles.append(match_roles[0]) - else: - raise exception.Forbidden() - else: - for role in roles: - filtered_roles.append({'id': role['id'], - 'name': role['name']}) - token_data['roles'] = filtered_roles - - def _populate_service_catalog(self, token_data, user_id, - domain_id, project_id, trust): - if 'catalog' in token_data: - return - - if CONF.trust.enabled and trust: - user_id = trust['trustor_user_id'] - if project_id or domain_id: - try: - service_catalog = self.catalog_api.get_v3_catalog( - user_id, project_id) - # TODO(ayoung): KVS backend needs a sample implementation - except exception.NotImplemented: - service_catalog = {} - # TODO(gyee): v3 service catalog is not quite completed yet - # TODO(ayoung): Enforce Endpoints for trust - token_data['catalog'] = service_catalog - - def _populate_token(self, token_data, expires=None, trust=None): - if not expires: - expires = token_module.default_expire_time() - if not isinstance(expires, basestring): - expires = timeutils.isotime(expires, subsecond=True) - token_data['expires_at'] = expires - token_data['issued_at'] = timeutils.isotime(subsecond=True) - - def get_token_data(self, user_id, method_names, extras, - domain_id=None, project_id=None, expires=None, - trust=None, token=None): - token_data = {'methods': method_names, - 'extras': extras} - - # We've probably already written these to the token - for x in ('roles', 'user', 'catalog', 'project', 'domain'): - if token and x in token: - token_data[x] = token[x] - - if CONF.trust.enabled and trust: - if user_id != trust['trustee_user_id']: - raise exception.Forbidden() - - self._populate_scope(token_data, domain_id, project_id) - self._populate_user(token_data, user_id, domain_id, project_id, trust) - self._populate_roles(token_data, user_id, domain_id, project_id, trust) - self._populate_service_catalog(token_data, user_id, domain_id, - project_id, trust) - self._populate_token(token_data, expires, trust) - return {'token': token_data} - - -def recreate_token_data(token_data=None, expires=None, - user_ref=None, project_ref=None): - """Recreate token from an existing token. - - Repopulate the ephemeral data and return the new token data. - - """ - new_expires = expires - project_id = None - user_id = None - domain_id = None - methods = ['password', 'token'] - extras = {} - - # NOTE(termie): Let's get some things straight here, because this code - # is wrong but tested as such: - # token_data, if it exists, is going to look like: - # {'token': ... the actual token data + a superfluous extras field ...} - # this data is actually stored in the database in the 'extras' column and - # then deserialized and added to the token_ref, that already has the - # the 'expires', 'user_id', and 'id' columns from the db. - # the 'user' and 'tenant' fields are being added to the - # token_ref due to being deserialized from the 'extras' column - # - # So, how this all looks in the db: - # id = some_id - # user_id = some_user_id - # expires = some_expiration - # extras = {'user': {'id': some_used_id}, - # 'tenant': {'id': some_tenant_id}, - # 'token_data': 'token': {'domain': {'id': some_domain_id}, - # 'project': {'id': some_project_id}, - # 'domain': {'id': some_domain_id}, - # 'user': {'id': some_user_id}, - # 'roles': [{'id': some_role_id}, ...], - # 'catalog': ..., - # 'expires_at': some_expiry_time, - # 'issued_at': now(), - # 'methods': ['password', 'token'], - # 'extras': { ... empty? ...} - # - # TODO(termie): reduce stored token complexity, bug filed at: - # https://bugs.launchpad.net/keystone/+bug/1159990 - if token_data: - # peel the outer layer so its easier to operate - token = token_data['token'] - domain_id = (token['domain']['id'] if 'domain' in token - else None) - project_id = (token['project']['id'] if 'project' in token - else None) - if not new_expires: - # support Grizzly-3 to Grizzly-RC1 transition - # tokens issued in G3 has 'expires' instead of 'expires_at' - new_expires = token.get('expires_at', - token.get('expires')) - user_id = token['user']['id'] - methods = token['methods'] - extras = token['extras'] - else: - token = None - project_id = project_ref['id'] if project_ref else None - user_id = user_ref['id'] - token_data_helper = TokenDataHelper() - return token_data_helper.get_token_data(user_id, - methods, - extras, - domain_id, - project_id, - new_expires, - token=token) - - -def create_token(auth_context, auth_info): - token_data_helper = TokenDataHelper() - (domain_id, project_id, trust) = auth_info.get_scope() - method_names = list(set(auth_info.get_method_names() + - auth_context.get('method_names', []))) - token_data = token_data_helper.get_token_data( - auth_context['user_id'], - method_names, - auth_context['extras'], - domain_id, - project_id, - auth_context.get('expires_at', None), - trust) - - if CONF.signing.token_format == 'UUID': - token_id = uuid.uuid4().hex - elif CONF.signing.token_format == 'PKI': - try: - token_id = cms.cms_sign_token(json.dumps(token_data), - CONF.signing.certfile, - CONF.signing.keyfile) - except environment.subprocess.CalledProcessError: - raise exception.UnexpectedError(_( - 'Unable to sign token.')) - else: - raise exception.UnexpectedError(_( - 'Invalid value for token_format: %s.' - ' Allowed values are PKI or UUID.') % - CONF.signing.token_format) - token_api = token_module.Manager() - try: - expiry = token_data['token']['expires_at'] - if isinstance(expiry, basestring): - expiry = timeutils.normalize_time(timeutils.parse_isotime(expiry)) - role_ids = [] - if 'project' in token_data['token']: - # project-scoped token, fill in the v2 token data - # all we care are the role IDs - role_ids = [role['id'] for role in token_data['token']['roles']] - metadata_ref = {'roles': role_ids} - data = dict(key=token_id, - id=token_id, - expires=expiry, - user=token_data['token']['user'], - tenant=token_data['token'].get('project'), - metadata=metadata_ref, - token_data=token_data, - trust_id=trust['id'] if trust else None) - token_api.create_token(token_id, data) - except Exception: - exc_info = sys.exc_info() - # an identical token may have been created already. - # if so, return the token_data as it is also identical - try: - token_api.get_token(token_id) - except exception.TokenNotFound: - raise exc_info[0], exc_info[1], exc_info[2] - - return (token_id, token_data) - - -def render_token_data_response(token_id, token_data, created=False): - """Render token data HTTP response. - - Stash token ID into the X-Auth-Token header. - - """ - headers = [('X-Subject-Token', token_id)] - headers.append(('Vary', 'X-Auth-Token')) - headers.append(('Content-Type', 'application/json')) - - if created: - status = (201, 'Created') - else: - status = (200, 'OK') - - body = jsonutils.dumps(token_data, cls=utils.SmarterEncoder) - return webob.Response(body=body, - status='%s %s' % status, - headerlist=headers) diff --git a/keystone/cli.py b/keystone/cli.py index 598bd409..21d2ad40 100644 --- a/keystone/cli.py +++ b/keystone/cli.py @@ -74,7 +74,7 @@ class DbVersion(BaseApp): @staticmethod def main(): - print migration.db_version() + print(migration.db_version()) class BaseCertificateSetup(BaseApp): @@ -180,7 +180,7 @@ class ExportLegacyCatalog(BaseApp): def main(): from keystone.common.sql import legacy migration = legacy.LegacyMigration(CONF.command.old_db) - print '\n'.join(migration.dump_catalog()) + print('\n'.join(migration.dump_catalog())) class ImportNovaAuth(BaseApp): diff --git a/keystone/common/config.py b/keystone/common/config.py index c0bd34e6..b0a534f8 100644 --- a/keystone/common/config.py +++ b/keystone/common/config.py @@ -24,7 +24,7 @@ from keystone.common import logging _DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s" _DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" -_DEFAULT_AUTH_METHODS = ['password', 'token'] +_DEFAULT_AUTH_METHODS = ['external', 'password', 'token'] COMMON_CLI_OPTS = [ cfg.BoolOpt('debug', @@ -214,6 +214,13 @@ def configure(): # trust register_bool('enabled', group='trust', default=True) + # os_inherit + register_bool('enabled', group='os_inherit', default=False) + + # binding + register_list('bind', group='token', default=[]) + register_str('enforce_token_bind', group='token', default='permissive') + # ssl register_bool('enable', group='ssl', default=False) register_str('certfile', group='ssl', @@ -259,6 +266,12 @@ def configure(): default='sqlite:///keystone.db') register_int('idle_timeout', group='sql', default=200) + #assignment has no default for backward compatibility reasons. + #If assignment is not specified, the identity driver chooses the backend + register_str( + 'driver', + group='assignment', + default=None) register_str( 'driver', group='catalog', @@ -369,23 +382,6 @@ def configure(): register_list( 'group_additional_attribute_mapping', group='ldap', default=None) - register_str('domain_tree_dn', group='ldap', default=None) - register_str('domain_filter', group='ldap', default=None) - register_str('domain_objectclass', group='ldap', default='groupOfNames') - register_str('domain_id_attribute', group='ldap', default='cn') - register_str('domain_name_attribute', group='ldap', default='ou') - register_str('domain_member_attribute', group='ldap', default='member') - register_str('domain_desc_attribute', group='ldap', default='description') - register_str('domain_enabled_attribute', group='ldap', default='enabled') - register_list('domain_attribute_ignore', group='ldap', default='') - register_bool('domain_allow_create', group='ldap', default=True) - register_bool('domain_allow_update', group='ldap', default=True) - register_bool('domain_allow_delete', group='ldap', default=True) - register_bool('domain_enabled_emulation', group='ldap', default=False) - register_str('domain_enabled_emulation_dn', group='ldap', default=None) - register_list( - 'domain_additional_attribute_mapping', group='ldap', default=None) - register_str('tls_cacertfile', group='ldap', default=None) register_str('tls_cacertdir', group='ldap', default=None) register_bool('use_tls', group='ldap', default=False) @@ -402,7 +398,11 @@ def configure(): register_str( 'token', group='auth', default='keystone.auth.plugins.password.Password') - + #deals with REMOTE_USER authentication + register_str( + 'external', + group='auth', + default='keystone.auth.plugins.external.ExternalDefault') # register any non-default auth methods here (used by extensions, etc) for method_name in CONF.auth.methods: if method_name not in _DEFAULT_AUTH_METHODS: @@ -410,3 +410,9 @@ def configure(): # PasteDeploy config file register_str('config_file', group='paste_deploy', default=None) + + # token provider + register_str( + 'provider', + group='token', + default=None) diff --git a/keystone/common/controller.py b/keystone/common/controller.py index 3ca1bf8b..affc34de 100644 --- a/keystone/common/controller.py +++ b/keystone/common/controller.py @@ -25,8 +25,12 @@ def _build_policy_check_credentials(self, action, context, kwargs): LOG.warning(_('RBAC: Invalid token')) raise exception.Unauthorized() + # NOTE(jamielennox): whilst this maybe shouldn't be within this function + # it would otherwise need to reload the token_ref from backing store. + wsgi.validate_token_bind(context, token_ref) + creds = {} - if 'token_data' in token_ref: + if 'token_data' in token_ref and 'token' in token_ref['token_data']: #V3 Tokens token_data = token_ref['token_data']['token'] try: @@ -146,7 +150,8 @@ def filterprotected(*filters): @dependency.requires('identity_api', 'policy_api', 'token_api', - 'trust_api', 'catalog_api', 'credential_api') + 'trust_api', 'catalog_api', 'credential_api', + 'assignment_api') class V2Controller(wsgi.Application): """Base controller class for Identity API v2.""" @@ -312,7 +317,7 @@ class V3Controller(V2Controller): # worth the duplication of state try: token_ref = self.token_api.get_token( - context=context, token_id=context['token_id']) + token_id=context['token_id']) except exception.TokenNotFound: LOG.warning(_('Invalid token in normalize_domain_id')) raise exception.Unauthorized() diff --git a/keystone/common/extension.py b/keystone/common/extension.py new file mode 100644 index 00000000..f176a197 --- /dev/null +++ b/keystone/common/extension.py @@ -0,0 +1,47 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack LLC +# +# 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. + + +ADMIN_EXTENSIONS = {} +PUBLIC_EXTENSIONS = {} + + +def register_admin_extension(url_prefix, extension_data): + """Register extension with collection of admin extensions. + + Extensions register the information here that will show + up in the /extensions page as a way to indicate that the extension is + active. + + url_prefix: unique key for the extension that will appear in the + urls generated by the extension. + + extension_data is a dictionary. The expected fields are: + 'name': short, human readable name of the extnsion + 'namespace': xml namespace + 'alias': identifier for the extension + 'updated': date the extension was last updated + 'description': text description of the extension + 'links': hyperlinks to documents describing the extension + + """ + ADMIN_EXTENSIONS[url_prefix] = extension_data + + +def register_public_extension(url_prefix, extension_data): + """Same as register_admin_extension but for public extensions.""" + + PUBLIC_EXTENSIONS[url_prefix] = extension_data diff --git a/keystone/common/kvs.py b/keystone/common/kvs.py index b517bc5d..09693999 100644 --- a/keystone/common/kvs.py +++ b/keystone/common/kvs.py @@ -50,6 +50,8 @@ class Base(object): def __init__(self, db=None): if db is None: db = INMEMDB + elif isinstance(db, DictKvs): + db = db elif isinstance(db, dict): db = DictKvs(db) self.db = db diff --git a/keystone/common/serializer.py b/keystone/common/serializer.py index 597fbfd8..4355a059 100644 --- a/keystone/common/serializer.py +++ b/keystone/common/serializer.py @@ -125,7 +125,8 @@ class XmlDeserializer(object): values = values or text or {} decoded_tag = XmlDeserializer._tag_name(element.tag, namespace) list_item_tag = None - if decoded_tag[-1] == 's' and len(values) == 0: + if (decoded_tag[-1] == 's' and len(values) == 0 and + decoded_tag != 'access'): # FIXME(gyee): special-case lists for now unti we # figure out how to properly handle them. # If any key ends with an 's', we are assuming it is a list. diff --git a/keystone/common/sql/core.py b/keystone/common/sql/core.py index f3a3deeb..7978fcc5 100644 --- a/keystone/common/sql/core.py +++ b/keystone/common/sql/core.py @@ -80,13 +80,6 @@ def initialize_decorator(init): v = str(v) if column.type.length and \ column.type.length < len(v): - #if signing.token_format == 'PKI', the id will - #store it's public key which is very long. - if config.CONF.signing.token_format == 'PKI' and \ - self.__tablename__ == 'token' and \ - k == 'id': - continue - raise exception.StringLengthExceeded( string=v, type=k, length=column.type.length) @@ -253,7 +246,8 @@ class Base(object): self._engine = self._engine or self.get_engine() self._sessionmaker = self._sessionmaker or self.get_sessionmaker( self._engine) - return self._sessionmaker() + return self._sessionmaker(autocommit=autocommit, + expire_on_commit=expire_on_commit) def get_engine(self, allow_global_engine=True): """Return a SQLAlchemy engine. diff --git a/keystone/common/sql/legacy.py b/keystone/common/sql/legacy.py index f2e0b994..c8adc900 100644 --- a/keystone/common/sql/legacy.py +++ b/keystone/common/sql/legacy.py @@ -19,6 +19,8 @@ import re import sqlalchemy from sqlalchemy import exc + +from keystone.assignment.backends import sql as assignment_sql from keystone.common import logging from keystone import config from keystone.contrib.ec2.backends import sql as ec2_sql @@ -57,8 +59,14 @@ def _translate_replacements(s): class LegacyMigration(object): def __init__(self, db_string): self.db = sqlalchemy.create_engine(db_string) + #TODO(ayoung): Replace with call via Manager self.identity_driver = identity_sql.Identity() + self.assignment_driver = assignment_sql.Assignment() + self.identity_driver.assignment_api = self.assignment_driver + self.assignment_driver.identity_api = self.identity_driver self.identity_driver.db_sync() + self.assignment_driver.db_sync() + self.ec2_driver = ec2_sql.Ec2() self._data = {} self._user_map = {} @@ -113,7 +121,7 @@ class LegacyMigration(object): self._project_map[x.get('id')] = new_dict['id'] # create #print 'create_project(%s, %s)' % (new_dict['id'], new_dict) - self.identity_driver.create_project(new_dict['id'], new_dict) + self.assignment_driver.create_project(new_dict['id'], new_dict) def _migrate_users(self): for x in self._data['users']: @@ -144,7 +152,7 @@ class LegacyMigration(object): # track internal ids self._role_map[x.get('id')] = new_dict['id'] # create - self.identity_driver.create_role(new_dict['id'], new_dict) + self.assignment_driver.create_role(new_dict['id'], new_dict) def _migrate_user_roles(self): for x in self._data['user_roles']: @@ -162,7 +170,7 @@ class LegacyMigration(object): except Exception: pass - self.identity_driver.add_role_to_user_and_project( + self.assignment_driver.add_role_to_user_and_project( user_id, tenant_id, role_id) def _migrate_tokens(self): diff --git a/keystone/common/sql/migrate_repo/versions/027_set_engine_mysql_innodb.py b/keystone/common/sql/migrate_repo/versions/027_set_engine_mysql_innodb.py new file mode 100644 index 00000000..ca8ccd08 --- /dev/null +++ b/keystone/common/sql/migrate_repo/versions/027_set_engine_mysql_innodb.py @@ -0,0 +1,143 @@ + +import sqlalchemy as sql +from sqlalchemy import MetaData + +from keystone.common.sql import migration_helpers + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + + if migrate_engine.name != 'mysql': + # InnoDB / MyISAM only applies to MySQL. + return + + # This is a list of all the tables that might have been created with MyISAM + # rather than InnoDB. + tables = [ + 'credential', + 'domain', + 'ec2_credential', + 'endpoint', + 'group', + 'group_domain_metadata', + 'group_project_metadata', + 'policy', + 'project', + 'role', + 'service', + 'token', + 'trust', + 'trust_role', + 'user', + 'user_domain_metadata', + 'user_group_membership', + 'user_project_metadata', + ] + + meta = MetaData() + meta.bind = migrate_engine + + domain_table = sql.Table('domain', meta, autoload=True) + endpoint_table = sql.Table('endpoint', meta, autoload=True) + group_table = sql.Table('group', meta, autoload=True) + group_domain_metadata_table = sql.Table('group_domain_metadata', meta, + autoload=True) + group_project_metadata_table = sql.Table('group_project_metadata', meta, + autoload=True) + project_table = sql.Table('project', meta, autoload=True) + service_table = sql.Table('service', meta, autoload=True) + user_table = sql.Table('user', meta, autoload=True) + user_domain_metadata_table = sql.Table('user_domain_metadata', meta, + autoload=True) + user_group_membership_table = sql.Table('user_group_membership', meta, + autoload=True) + + # Mapping of table name to the constraints on that table, + # so we can create them. + table_constraints = { + 'endpoint': [{'table': endpoint_table, + 'fk_column': 'service_id', + 'ref_column': service_table.c.id}, + ], + 'group': [{'table': group_table, + 'fk_column': 'domain_id', + 'ref_column': domain_table.c.id}, + ], + 'group_domain_metadata': [{'table': group_domain_metadata_table, + 'fk_column': 'domain_id', + 'ref_column': domain_table.c.id}, + ], + 'group_project_metadata': [{'table': group_project_metadata_table, + 'fk_column': 'project_id', + 'ref_column': project_table.c.id}, + ], + 'project': [{'table': project_table, + 'fk_column': 'domain_id', + 'ref_column': domain_table.c.id}, + ], + 'user': [{'table': user_table, + 'fk_column': 'domain_id', + 'ref_column': domain_table.c.id}, + ], + 'user_domain_metadata': [{'table': user_domain_metadata_table, + 'fk_column': 'domain_id', + 'ref_column': domain_table.c.id}, + ], + 'user_group_membership': [{'table': user_group_membership_table, + 'fk_column': 'user_id', + 'ref_column': user_table.c.id}, + {'table': user_group_membership_table, + 'fk_column': 'group_id', + 'ref_column': group_table.c.id}, + ], + 'user_project_metadata': [{'table': group_project_metadata_table, + 'fk_column': 'project_id', + 'ref_column': project_table.c.id}, + ], + } + + # Maps a table name to the tables that reference it as a FK constraint + # (See the map above). + ref_tables_map = { + 'service': ['endpoint', ], + 'domain': ['group', 'group_domain_metadata', 'project', 'user', + 'user_domain_metadata', ], + 'project': ['group_project_metadata', 'user_project_metadata', ], + 'user': ['user_group_membership', ], + 'group': ['user_group_membership', ], + } + + # The names of tables that need to have their FKs added. + fk_table_names = set() + + d = migrate_engine.execute("SHOW TABLE STATUS WHERE Engine!='InnoDB';") + for row in d.fetchall(): + table_name = row[0] + + if table_name not in tables: + # Skip this table since it's not a Keystone table. + continue + + migrate_engine.execute("ALTER TABLE `%s` Engine=InnoDB" % table_name) + + # Will add the FKs to the table if any of + # a) the table itself was converted + # b) the tables that the table referenced were converted + + if table_name in table_constraints: + fk_table_names.add(table_name) + + ref_tables = ref_tables_map.get(table_name, []) + for other_table_name in ref_tables: + fk_table_names.add(other_table_name) + + # Now add all the FK constraints to those tables + for table_name in fk_table_names: + constraints = table_constraints.get(table_name) + migration_helpers.add_constraints(constraints) + + +def downgrade(migrate_engine): + pass diff --git a/keystone/common/sql/migrate_repo/versions/028_fixup_group_metadata.py b/keystone/common/sql/migrate_repo/versions/028_fixup_group_metadata.py new file mode 100644 index 00000000..66055a99 --- /dev/null +++ b/keystone/common/sql/migrate_repo/versions/028_fixup_group_metadata.py @@ -0,0 +1,190 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack LLC +# +# 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 sqlalchemy as sql + + +def upgrade(migrate_engine): + # The group_project_metadata table was not updated in terms of its + # FK to the tenant table when the tenant->project change was made at + # the 015 migration for sqlite. This upgrade fixes that. + # We need to create a fake tenant table so that we can first load + # the group_project_metadata at all, then do a dance of copying tables + # to get us to the correct schema. + meta = sql.MetaData() + meta.bind = migrate_engine + + if migrate_engine.name != 'sqlite': + return + + temp_tenant_table = sql.Table( + 'tenant', + meta, + sql.Column('id', sql.String(64), primary_key=True)) + temp_tenant_table.create(migrate_engine, checkfirst=True) + + sql.Table('user', meta, autoload=True) + old_group_metadata_table = sql.Table('group_project_metadata', + meta, autoload=True) + + # OK, we now have the table loaded, create a first + # temporary table of a different name with the correct FK + sql.Table('project', meta, autoload=True) + temp_group_project_metadata_table = sql.Table( + 'temp_group_project_metadata', + meta, + sql.Column( + 'group_id', + sql.String(64), + primary_key=True), + sql.Column( + 'project_id', + sql.String(64), + sql.ForeignKey('project.id'), + primary_key=True), + sql.Column('data', sql.Text())) + temp_group_project_metadata_table.create(migrate_engine, checkfirst=True) + + # Populate the new temporary table, and then drop the old one + session = sql.orm.sessionmaker(bind=migrate_engine)() + + for metadata in session.query(old_group_metadata_table): + q = temp_group_project_metadata_table.insert().values( + group_id=metadata.group_id, + project_id=metadata.project_id, + data=metadata.data) + session.execute(q) + session.commit() + old_group_metadata_table.drop() + temp_tenant_table.drop() + + # Now do a final table copy to get the table of the right name. + # Re-init the metadata so that sqlalchemy does not get confused with + # multiple versions of the same named table. + meta2 = sql.MetaData() + meta2.bind = migrate_engine + + sql.Table('project', meta2, autoload=True) + new_group_project_metadata_table = sql.Table( + 'group_project_metadata', + meta2, + sql.Column( + 'group_id', + sql.String(64), + primary_key=True), + sql.Column( + 'project_id', + sql.String(64), + sql.ForeignKey('project.id'), + primary_key=True), + sql.Column('data', sql.Text())) + new_group_project_metadata_table.create(migrate_engine, checkfirst=True) + + for metadata in session.query(temp_group_project_metadata_table): + q = new_group_project_metadata_table.insert().values( + group_id=metadata.group_id, + project_id=metadata.project_id, + data=metadata.data) + session.execute(q) + session.commit() + + temp_group_project_metadata_table.drop() + + +def downgrade(migrate_engine): + # Put the group_project_metadata table back the way it was in its rather + # broken state. We don't try and re-write history, since otherwise people + # get out of step. + meta = sql.MetaData() + meta.bind = migrate_engine + + if migrate_engine.name != 'sqlite': + return + + sql.Table('user', meta, autoload=True) + sql.Table('project', meta, autoload=True) + group_metadata_table = sql.Table('group_project_metadata', + meta, autoload=True) + + # We want to create a temp group meta table with the FK + # set to the wrong place. + temp_tenant_table = sql.Table( + 'tenant', + meta, + sql.Column('id', sql.String(64), primary_key=True)) + temp_tenant_table.create(migrate_engine, checkfirst=True) + + temp_group_project_metadata_table = sql.Table( + 'temp_group_project_metadata', + meta, + sql.Column( + 'group_id', + sql.String(64), + primary_key=True), + sql.Column( + 'project_id', + sql.String(64), + sql.ForeignKey('tenant.id'), + primary_key=True), + sql.Column('data', sql.Text())) + temp_group_project_metadata_table.create(migrate_engine, checkfirst=True) + + # Now populate the temp table and drop the real one + session = sql.orm.sessionmaker(bind=migrate_engine)() + + for metadata in session.query(group_metadata_table): + q = temp_group_project_metadata_table.insert().values( + group_id=metadata.group_id, + project_id=metadata.project_id, + data=metadata.data) + session.execute(q) + + session.commit() + group_metadata_table.drop() + + # Now copy again into the correctly named table. Re-init the metadata + # so that sqlalchemy does not get confused with multiple versions of the + # same named table. + meta2 = sql.MetaData() + meta2.bind = migrate_engine + + sql.Table('tenant', meta2, autoload=True) + new_group_project_metadata_table = sql.Table( + 'group_project_metadata', + meta2, + sql.Column( + 'group_id', + sql.String(64), + primary_key=True), + sql.Column( + 'project_id', + sql.String(64), + sql.ForeignKey('tenant.id'), + primary_key=True), + sql.Column('data', sql.Text())) + new_group_project_metadata_table.create(migrate_engine, checkfirst=True) + + for metadata in session.query(temp_group_project_metadata_table): + q = new_group_project_metadata_table.insert().values( + group_id=metadata.group_id, + project_id=metadata.project_id, + data=metadata.data) + session.execute(q) + + session.commit() + + temp_group_project_metadata_table.drop() + temp_tenant_table.drop() diff --git a/keystone/common/sql/migrate_repo/versions/029_update_assignment_metadata.py b/keystone/common/sql/migrate_repo/versions/029_update_assignment_metadata.py new file mode 100644 index 00000000..a9276804 --- /dev/null +++ b/keystone/common/sql/migrate_repo/versions/029_update_assignment_metadata.py @@ -0,0 +1,102 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack LLC +# +# 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 json + +import sqlalchemy as sql + + +def build_update(table_name, upgrade_table, row, values): + if table_name == 'user_project_metadata': + update = upgrade_table.update().where( + upgrade_table.c.user_id == row.user_id).where( + upgrade_table.c.project_id == row.project_id).values(values) + elif table_name == 'group_project_metadata': + update = upgrade_table.update().where( + upgrade_table.c.group_id == row.group_id).where( + upgrade_table.c.project_id == row.project_id).values(values) + elif table_name == 'user_domain_metadata': + update = upgrade_table.update().where( + upgrade_table.c.user_id == row.user_id).where( + upgrade_table.c.domain_id == row.domain_id).values(values) + else: + update = upgrade_table.update().where( + upgrade_table.c.group_id == row.group_id).where( + upgrade_table.c.domain_id == row.domain_id).values(values) + return update + + +def upgrade_grant_table(meta, migrate_engine, session, table_name): + + # Convert the roles component of the metadata from a list + # of ids to a list of dicts + + def list_to_dict_list(metadata): + json_metadata = json.loads(metadata) + if 'roles' in json_metadata: + json_metadata['roles'] = ( + [{'id': x} for x in json_metadata['roles']]) + return json.dumps(json_metadata) + + upgrade_table = sql.Table(table_name, meta, autoload=True) + for assignment in session.query(upgrade_table): + values = {'data': list_to_dict_list(assignment.data)} + update = build_update(table_name, upgrade_table, assignment, values) + migrate_engine.execute(update) + + +def downgrade_grant_table(meta, migrate_engine, session, table_name): + + # Convert the roles component of the metadata from a list + # of dicts to a simple list of ids. Any inherited roles are deleted + # since they would have no meaning + + def dict_list_to_list(metadata): + json_metadata = json.loads(metadata) + if 'roles' in json_metadata: + json_metadata['roles'] = ([x['id'] for x in json_metadata['roles'] + if 'inherited_to' not in x]) + return json.dumps(json_metadata) + + downgrade_table = sql.Table(table_name, meta, autoload=True) + for assignment in session.query(downgrade_table): + values = {'data': dict_list_to_list(assignment.data)} + update = build_update(table_name, downgrade_table, assignment, values) + migrate_engine.execute(update) + + +def upgrade(migrate_engine): + meta = sql.MetaData() + meta.bind = migrate_engine + session = sql.orm.sessionmaker(bind=migrate_engine)() + + for grant_table in ['user_project_metadata', 'user_domain_metadata', + 'group_project_metadata', 'group_domain_metadata']: + upgrade_grant_table(meta, migrate_engine, session, grant_table) + session.commit() + session.close() + + +def downgrade(migrate_engine): + meta = sql.MetaData() + meta.bind = migrate_engine + session = sql.orm.sessionmaker(bind=migrate_engine)() + + for grant_table in ['user_project_metadata', 'user_domain_metadata', + 'group_project_metadata', 'group_domain_metadata']: + downgrade_grant_table(meta, migrate_engine, session, grant_table) + session.commit() + session.close() diff --git a/keystone/common/sql/migration_helpers.py b/keystone/common/sql/migration_helpers.py index fb2c8095..8a581924 100644 --- a/keystone/common/sql/migration_helpers.py +++ b/keystone/common/sql/migration_helpers.py @@ -52,6 +52,19 @@ def remove_constraints(constraints): def add_constraints(constraints): for constraint_def in constraints: + + if constraint_def['table'].kwargs.get('mysql_engine') == 'MyISAM': + # Don't try to create constraint when using MyISAM because it's + # not supported. + continue + + ref_col = constraint_def['ref_column'] + ref_engine = ref_col.table.kwargs.get('mysql_engine') + if ref_engine == 'MyISAM': + # Don't try to create constraint when using MyISAM because it's + # not supported. + continue + migrate.ForeignKeyConstraint( columns=[getattr(constraint_def['table'].c, constraint_def['fk_column'])], diff --git a/keystone/common/sql/nova.py b/keystone/common/sql/nova.py index c6d452cd..fd8d2481 100644 --- a/keystone/common/sql/nova.py +++ b/keystone/common/sql/nova.py @@ -18,10 +18,11 @@ import uuid +from keystone import assignment from keystone.common import logging from keystone import config from keystone.contrib.ec2.backends import sql as ec2_sql -from keystone.identity.backends import sql as identity_sql +from keystone import identity LOG = logging.getLogger(__name__) @@ -30,18 +31,20 @@ DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id def import_auth(data): - identity_api = identity_sql.Identity() - tenant_map = _create_projects(identity_api, data['tenants']) + identity_api = identity.Manager() + assignment_api = assignment.Manager() + + tenant_map = _create_projects(assignment_api, data['tenants']) user_map = _create_users(identity_api, data['users']) - _create_memberships(identity_api, data['user_tenant_list'], + _create_memberships(assignment_api, data['user_tenant_list'], user_map, tenant_map) - role_map = _create_roles(identity_api, data['roles']) - _assign_roles(identity_api, data['role_user_tenant_list'], + role_map = _create_roles(assignment_api, data['roles']) + _assign_roles(assignment_api, data['role_user_tenant_list'], role_map, user_map, tenant_map) ec2_api = ec2_sql.Ec2() ec2_creds = data['ec2_credentials'] - _create_ec2_creds(ec2_api, identity_api, ec2_creds, user_map) + _create_ec2_creds(ec2_api, assignment_api, ec2_creds, user_map) def _generate_uuid(): @@ -120,10 +123,10 @@ def _assign_roles(api, assignments, role_map, user_map, tenant_map): api.add_role_to_user_and_project(user_id, tenant_id, role_id) -def _create_ec2_creds(ec2_api, identity_api, ec2_creds, user_map): +def _create_ec2_creds(ec2_api, assignment_api, ec2_creds, user_map): for ec2_cred in ec2_creds: user_id = user_map[ec2_cred['user_id']] - for tenant_id in identity_api.get_projects_for_user(user_id): + for tenant_id in assignment_api.get_projects_for_user(user_id): cred_dict = { 'access': '%s:%s' % (tenant_id, ec2_cred['access_key']), 'secret': ec2_cred['secret_key'], diff --git a/keystone/common/sql/util.py b/keystone/common/sql/util.py deleted file mode 100644 index c31e50c0..00000000 --- a/keystone/common/sql/util.py +++ /dev/null @@ -1,42 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 OpenStack LLC -# -# 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 os -import shutil - -from keystone.common.sql import core -from keystone.common.sql import migration -from keystone import config - - -CONF = config.CONF - - -def setup_test_database(): - try: - if os.path.exists('test.db'): - os.unlink('test.db') - if not os.path.exists('test.db.pristine'): - migration.db_sync() - shutil.copyfile('test.db', 'test.db.pristine') - else: - shutil.copyfile('test.db.pristine', 'test.db') - except Exception: - pass - - -def teardown_test_database(): - core.set_global_engine(None) diff --git a/keystone/common/wsgi.py b/keystone/common/wsgi.py index 91c28034..c7e30576 100644 --- a/keystone/common/wsgi.py +++ b/keystone/common/wsgi.py @@ -73,6 +73,55 @@ def mask_password(message, is_unicode=False, secret="***"): return result +def validate_token_bind(context, token_ref): + bind_mode = CONF.token.enforce_token_bind + + if bind_mode == 'disabled': + return + + bind = token_ref.get('bind', {}) + + # permissive and strict modes don't require there to be a bind + permissive = bind_mode in ('permissive', 'strict') + + # get the named mode if bind_mode is not one of the known + name = None if permissive or bind_mode == 'required' else bind_mode + + if not bind: + if permissive: + # no bind provided and none required + return + else: + LOG.info(_("No bind information present in token")) + raise exception.Unauthorized() + + if name and name not in bind: + LOG.info(_("Named bind mode %s not in bind information"), name) + raise exception.Unauthorized() + + for bind_type, identifier in bind.iteritems(): + if bind_type == 'kerberos': + if not context.get('AUTH_TYPE', '').lower() == 'negotiate': + LOG.info(_("Kerberos credentials required and not present")) + raise exception.Unauthorized() + + if not context.get('REMOTE_USER') == identifier: + LOG.info(_("Kerberos credentials do not match those in bind")) + raise exception.Unauthorized() + + LOG.info(_("Kerberos bind authentication successful")) + + elif bind_mode == 'permissive': + LOG.debug(_("Ignoring unknown bind for permissive mode: " + "{%(bind_type)s: %(identifier)s}"), + {'bind_type': bind_type, 'identifier': identifier}) + else: + LOG.info(_("Couldn't verify unknown bind: " + "{%(bind_type)s: %(identifier)s}"), + {'bind_type': bind_type, 'identifier': identifier}) + raise exception.Unauthorized() + + class WritableLogger(object): """A thin wrapper that responds to `write` and logs.""" @@ -103,18 +152,18 @@ class BaseApplication(object): [app:wadl] latest_version = 1.3 - paste.app_factory = nova.api.fancy_api:Wadl.factory + paste.app_factory = keystone.fancy_api:Wadl.factory which would result in a call to the `Wadl` class as - import nova.api.fancy_api - fancy_api.Wadl(latest_version='1.3') + import keystone.fancy_api + keystone.fancy_api.Wadl(latest_version='1.3') You could of course re-implement the `factory` method in subclasses, but using the kwarg passing it shouldn't be necessary. """ - return cls() + return cls(**local_config) def __call__(self, environ, start_response): r"""Subclasses will probably want to implement __call__ like this: @@ -167,10 +216,16 @@ class Application(BaseApplication): context['headers'] = dict(req.headers.iteritems()) context['path'] = req.environ['PATH_INFO'] params = req.environ.get(PARAMS_ENV, {}) - if 'REMOTE_USER' in req.environ: - context['REMOTE_USER'] = req.environ['REMOTE_USER'] - elif context.get('REMOTE_USER', None) is not None: - del context['REMOTE_USER'] + + for name in ['REMOTE_USER', 'AUTH_TYPE']: + try: + context[name] = req.environ[name] + except KeyError: + try: + del context[name] + except KeyError: + pass + params.update(arg_dict) context.setdefault('is_admin', False) @@ -233,6 +288,7 @@ class Application(BaseApplication): except exception.TokenNotFound as e: raise exception.Unauthorized(e) + validate_token_bind(context, user_token_ref) creds = user_token_ref['metadata'].copy() try: @@ -276,12 +332,12 @@ class Middleware(Application): [filter:analytics] redis_host = 127.0.0.1 - paste.filter_factory = nova.api.analytics:Analytics.factory + paste.filter_factory = keystone.analytics:Analytics.factory which would result in a call to the `Analytics` class as - import nova.api.analytics - analytics.Analytics(app_from_paste, redis_host='127.0.0.1') + import keystone.analytics + keystone.analytics.Analytics(app, redis_host='127.0.0.1') You could of course re-implement the `factory` method in subclasses, but using the kwarg passing it shouldn't be necessary. @@ -486,12 +542,12 @@ class ExtensionRouter(Router): [filter:analytics] redis_host = 127.0.0.1 - paste.filter_factory = nova.api.analytics:Analytics.factory + paste.filter_factory = keystone.analytics:Analytics.factory which would result in a call to the `Analytics` class as - import nova.api.analytics - analytics.Analytics(app_from_paste, redis_host='127.0.0.1') + import keystone.analytics + keystone.analytics.Analytics(app, redis_host='127.0.0.1') You could of course re-implement the `factory` method in subclasses, but using the kwarg passing it shouldn't be necessary. @@ -500,7 +556,7 @@ class ExtensionRouter(Router): def _factory(app): conf = global_config.copy() conf.update(local_config) - return cls(app) + return cls(app, **local_config) return _factory diff --git a/keystone/contrib/admin_crud/core.py b/keystone/contrib/admin_crud/core.py index c06afcf7..f98397ae 100644 --- a/keystone/contrib/admin_crud/core.py +++ b/keystone/contrib/admin_crud/core.py @@ -14,10 +14,31 @@ # License for the specific language governing permissions and limitations # under the License. from keystone import catalog +from keystone.common import extension from keystone.common import wsgi from keystone import identity +extension.register_admin_extension( + 'OS-KSADM', { + 'name': 'OpenStack Keystone Admin', + 'namespace': 'http://docs.openstack.org/identity/api/ext/' + 'OS-KSADM/v1.0', + 'alias': 'OS-KSADM', + 'updated': '2013-07-11T17:14:00-00:00', + 'description': 'OpenStack extensions to Keystone v2.0 API ' + 'enabling Administrative Operations.', + 'links': [ + { + 'rel': 'describedby', + # TODO(dolph): link needs to be revised after + # bug 928059 merges + 'type': 'text/html', + 'href': 'https://github.com/openstack/identity-api', + } + ]}) + + class CrudExtension(wsgi.ExtensionRouter): """Previously known as the OS-KSADM extension. diff --git a/keystone/contrib/ec2/core.py b/keystone/contrib/ec2/core.py index e8471ec6..adba7e74 100644 --- a/keystone/contrib/ec2/core.py +++ b/keystone/contrib/ec2/core.py @@ -40,6 +40,7 @@ from keystoneclient.contrib.ec2 import utils as ec2_utils from keystone.common import controller from keystone.common import dependency +from keystone.common import extension from keystone.common import manager from keystone.common import utils from keystone.common import wsgi @@ -51,6 +52,25 @@ from keystone import token CONF = config.CONF +EXTENSION_DATA = { + 'name': 'OpenStack EC2 API', + 'namespace': 'http://docs.openstack.org/identity/api/ext/' + 'OS-EC2/v1.0', + 'alias': 'OS-EC2', + 'updated': '2013-07-07T12:00:0-00:00', + 'description': 'OpenStack EC2 Credentials backend.', + 'links': [ + { + 'rel': 'describedby', + # TODO(ayoung): needs a description + 'type': 'text/html', + 'href': 'https://github.com/openstack/identity-api', + } + ]} +extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA) +extension.register_public_extension(EXTENSION_DATA['alias'], EXTENSION_DATA) + + @dependency.provider('ec2_api') class Manager(manager.Manager): """Default pivot point for the EC2 Credentials backend. @@ -97,7 +117,7 @@ class Ec2Extension(wsgi.ExtensionRouter): conditions=dict(method=['DELETE'])) -@dependency.requires('catalog_api', 'ec2_api') +@dependency.requires('catalog_api', 'ec2_api', 'token_provider_api') class Ec2Controller(controller.V2Controller): def check_signature(self, creds_ref, credentials): signer = ec2_utils.Ec2Signer(creds_ref['secret']) @@ -153,16 +173,14 @@ class Ec2Controller(controller.V2Controller): token_id = uuid.uuid4().hex tenant_ref = self.identity_api.get_project(creds_ref['tenant_id']) user_ref = self.identity_api.get_user(creds_ref['user_id']) - metadata_ref = self.identity_api.get_metadata( - user_id=user_ref['id'], - tenant_id=tenant_ref['id']) + metadata_ref = {} + metadata_ref['roles'] = ( + self.identity_api.get_roles_for_user_and_project( + user_ref['id'], tenant_ref['id'])) # Validate that the auth info is valid and nothing is disabled token.validate_auth_info(self, user_ref, tenant_ref) - # TODO(termie): optimize this call at some point and put it into the - # the return for metadata - # fill out the roles in the metadata roles = metadata_ref.get('roles', []) if not roles: raise exception.Unauthorized(message='User not valid for tenant.') @@ -174,17 +192,16 @@ class Ec2Controller(controller.V2Controller): tenant_id=tenant_ref['id'], metadata=metadata_ref) - token_ref = self.token_api.create_token( - token_id, dict(id=token_id, - user=user_ref, - tenant=tenant_ref, - metadata=metadata_ref)) - - # TODO(termie): i don't think the ec2 middleware currently expects a - # full return, but it contains a note saying that it - # would be better to expect a full return - return token.controllers.Auth.format_authenticate( - token_ref, roles_ref, catalog_ref) + auth_token_data = dict(user=user_ref, + tenant=tenant_ref, + metadata=metadata_ref, + id='placeholder') + (token_id, token_data) = self.token_provider_api.issue_token( + version=token.provider.V2, + token_ref=auth_token_data, + roles_ref=roles_ref, + catalog_ref=catalog_ref) + return token_data def create_credential(self, context, user_id, tenant_id): """Create a secret/access pair for use with ec2 style auth. diff --git a/keystone/contrib/s3/core.py b/keystone/contrib/s3/core.py index 44b038d4..29ea4fe2 100644 --- a/keystone/contrib/s3/core.py +++ b/keystone/contrib/s3/core.py @@ -27,6 +27,7 @@ import base64 import hashlib import hmac +from keystone.common import extension from keystone.common import utils from keystone.common import wsgi from keystone import config @@ -35,6 +36,23 @@ from keystone import exception CONF = config.CONF +EXTENSION_DATA = { + 'name': 'OpenStack S3 API', + 'namespace': 'http://docs.openstack.org/identity/api/ext/' + 's3tokens/v1.0', + 'alias': 's3tokens', + 'updated': '2013-07-07T12:00:0-00:00', + 'description': 'OpenStack S3 API.', + 'links': [ + { + 'rel': 'describedby', + # TODO(ayoung): needs a description + 'type': 'text/html', + 'href': 'https://github.com/openstack/identity-api', + } + ]} +extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA) + class S3Extension(wsgi.ExtensionRouter): def add_routes(self, mapper): diff --git a/keystone/contrib/stats/core.py b/keystone/contrib/stats/core.py index 0325c4fa..1d7b2cdf 100644 --- a/keystone/contrib/stats/core.py +++ b/keystone/contrib/stats/core.py @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. +from keystone.common import extension from keystone.common import logging from keystone.common import manager from keystone.common import wsgi @@ -27,6 +28,23 @@ from keystone import token CONF = config.CONF LOG = logging.getLogger(__name__) +extension_data = { + 'name': 'Openstack Keystone Stats API', + 'namespace': 'http://docs.openstack.org/identity/api/ext/' + 'OS-STATS/v1.0', + 'alias': 'OS-STATS', + 'updated': '2013-07-07T12:00:0-00:00', + 'description': 'Openstack Keystone Stats API.', + 'links': [ + { + 'rel': 'describedby', + # TODO(ayoung): needs a description + 'type': 'text/html', + 'href': 'https://github.com/openstack/identity-api', + } + ]} +extension.register_admin_extension(extension_data['alias'], extension_data) + class Manager(manager.Manager): """Default pivot point for the Stats backend. diff --git a/keystone/contrib/user_crud/core.py b/keystone/contrib/user_crud/core.py index 79144ae5..f9f09b89 100644 --- a/keystone/contrib/user_crud/core.py +++ b/keystone/contrib/user_crud/core.py @@ -17,6 +17,7 @@ import copy import uuid +from keystone.common import extension from keystone.common import logging from keystone.common import wsgi from keystone import exception @@ -26,6 +27,25 @@ from keystone import identity LOG = logging.getLogger(__name__) +extension.register_public_extension( + 'OS-KSCRUD', { + 'name': 'OpenStack Keystone User CRUD', + 'namespace': 'http://docs.openstack.org/identity/api/ext/' + 'OS-KSCRUD/v1.0', + 'alias': 'OS-KSCRUD', + 'updated': '2013-07-07T12:00:0-00:00', + 'description': 'OpenStack extensions to Keystone v2.0 API ' + 'enabling User Operations.', + 'links': [ + { + 'rel': 'describedby', + # TODO(ayoung): needs a description + 'type': 'text/html', + 'href': 'https://github.com/openstack/identity-api', + } + ]}) + + class UserController(identity.controllers.User): def set_user_password(self, context, user_id, user): token_id = context.get('token_id') @@ -43,7 +63,7 @@ class UserController(identity.controllers.User): try: user_ref = self.identity_api.authenticate( user_id=user_id_from_token, - password=original_password)[0] + password=original_password) if not user_ref.get('enabled', True): # NOTE(dolph): why can't you set a disabled user's password? raise exception.Unauthorized('User is disabled') diff --git a/keystone/controllers.py b/keystone/controllers.py index 6dd303e1..8ffa073a 100644 --- a/keystone/controllers.py +++ b/keystone/controllers.py @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. +from keystone.common import extension from keystone.common import logging from keystone.common import wsgi from keystone import config @@ -32,10 +33,10 @@ _VERSIONS = [] class Extensions(wsgi.Application): """Base extensions controller to be extended by public and admin API's.""" - def __init__(self, extensions=None): - super(Extensions, self).__init__() - - self.extensions = extensions or {} + #extend in subclass to specify the set of extensions + @property + def extensions(self): + return None def get_extensions_info(self, context): return {'extensions': {'values': self.extensions.values()}} @@ -48,34 +49,15 @@ class Extensions(wsgi.Application): class AdminExtensions(Extensions): - def __init__(self, *args, **kwargs): - super(AdminExtensions, self).__init__(*args, **kwargs) - - # TODO(dolph): Extensions should obviously provide this information - # themselves, but hardcoding it here allows us to match - # the API spec in the short term with minimal complexity. - self.extensions['OS-KSADM'] = { - 'name': 'Openstack Keystone Admin', - 'namespace': 'http://docs.openstack.org/identity/api/ext/' - 'OS-KSADM/v1.0', - 'alias': 'OS-KSADM', - 'updated': '2011-08-19T13:25:27-06:00', - 'description': 'Openstack extensions to Keystone v2.0 API ' - 'enabling Admin Operations.', - 'links': [ - { - 'rel': 'describedby', - # TODO(dolph): link needs to be revised after - # bug 928059 merges - 'type': 'text/html', - 'href': 'https://github.com/openstack/identity-api', - } - ] - } + @property + def extensions(self): + return extension.ADMIN_EXTENSIONS class PublicExtensions(Extensions): - pass + @property + def extensions(self): + return extension.PUBLIC_EXTENSIONS def register_version(version): diff --git a/keystone/identity/backends/kvs.py b/keystone/identity/backends/kvs.py index 89c05aaf..83535108 100644 --- a/keystone/identity/backends/kvs.py +++ b/keystone/identity/backends/kvs.py @@ -14,7 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -from keystone import clean from keystone.common import kvs from keystone.common import utils from keystone import exception @@ -22,8 +21,14 @@ from keystone import identity class Identity(kvs.Base, identity.Driver): + def __init__(self): + super(Identity, self).__init__() + + def default_assignment_driver(self): + return "keystone.assignment.backends.kvs.Assignment" + # Public interface - def authenticate_user(self, user_id=None, password=None): + def authenticate(self, user_id=None, password=None): user_ref = None try: user_ref = self._get_user(user_id) @@ -31,48 +36,7 @@ class Identity(kvs.Base, identity.Driver): raise AssertionError('Invalid user / password') if not utils.check_password(password, user_ref.get('password')): raise AssertionError('Invalid user / password') - return user_ref - - def authorize_for_project(self, user_ref, tenant_id=None): - user_id = user_ref['id'] - tenant_ref = None - metadata_ref = {} - if tenant_id is not None: - if tenant_id not in self.get_projects_for_user(user_id): - raise AssertionError('Invalid tenant') - try: - tenant_ref = self.get_project(tenant_id) - metadata_ref = self.get_metadata(user_id, tenant_id) - except exception.ProjectNotFound: - tenant_ref = None - metadata_ref = {} - except exception.MetadataNotFound: - metadata_ref = {} - return (identity.filter_user(user_ref), tenant_ref, metadata_ref) - - def get_project(self, tenant_id): - try: - return self.db.get('tenant-%s' % tenant_id) - except exception.NotFound: - raise exception.ProjectNotFound(project_id=tenant_id) - - def list_projects(self): - tenant_keys = filter(lambda x: x.startswith("tenant-"), - self.db.keys()) - return [self.db.get(key) for key in tenant_keys] - - def get_project_by_name(self, tenant_name, domain_id): - try: - return self.db.get('tenant_name-%s' % tenant_name) - except exception.NotFound: - raise exception.ProjectNotFound(project_id=tenant_name) - - def get_project_users(self, tenant_id): - self.get_project(tenant_id) - user_keys = filter(lambda x: x.startswith("user-"), self.db.keys()) - user_refs = [self.db.get(key) for key in user_keys] - user_refs = filter(lambda x: tenant_id in x['tenants'], user_refs) - return [identity.filter_user(user_ref) for user_ref in user_refs] + return identity.filter_user(user_ref) def _get_user(self, user_id): try: @@ -93,123 +57,10 @@ class Identity(kvs.Base, identity.Driver): return identity.filter_user( self._get_user_by_name(user_name, domain_id)) - def get_metadata(self, user_id=None, tenant_id=None, - domain_id=None, group_id=None): - try: - if user_id: - if tenant_id: - return self.db.get('metadata-%s-%s' % (tenant_id, - user_id)) - else: - return self.db.get('metadata-%s-%s' % (domain_id, - user_id)) - else: - if tenant_id: - return self.db.get('metadata-%s-%s' % (tenant_id, - group_id)) - else: - return self.db.get('metadata-%s-%s' % (domain_id, - group_id)) - except exception.NotFound: - raise exception.MetadataNotFound() - - def get_role(self, role_id): - try: - return self.db.get('role-%s' % role_id) - except exception.NotFound: - raise exception.RoleNotFound(role_id=role_id) - def list_users(self): user_ids = self.db.get('user_list', []) return [self.get_user(x) for x in user_ids] - def list_roles(self): - role_ids = self.db.get('role_list', []) - return [self.get_role(x) for x in role_ids] - - def get_projects_for_user(self, user_id): - user_ref = self._get_user(user_id) - return user_ref.get('tenants', []) - - def add_role_to_user_and_project(self, user_id, tenant_id, role_id): - self.get_user(user_id) - self.get_project(tenant_id) - self.get_role(role_id) - try: - metadata_ref = self.get_metadata(user_id, tenant_id) - except exception.MetadataNotFound: - metadata_ref = {} - roles = set(metadata_ref.get('roles', [])) - if role_id in roles: - msg = ('User %s already has role %s in tenant %s' - % (user_id, role_id, tenant_id)) - raise exception.Conflict(type='role grant', details=msg) - roles.add(role_id) - metadata_ref['roles'] = list(roles) - self.update_metadata(user_id, tenant_id, metadata_ref) - - def remove_role_from_user_and_project(self, user_id, tenant_id, role_id): - try: - metadata_ref = self.get_metadata(user_id, tenant_id) - except exception.MetadataNotFound: - metadata_ref = {} - roles = set(metadata_ref.get('roles', [])) - if role_id not in roles: - msg = 'Cannot remove role that has not been granted, %s' % role_id - raise exception.RoleNotFound(message=msg) - - roles.remove(role_id) - metadata_ref['roles'] = list(roles) - - if not len(roles): - self.db.delete('metadata-%s-%s' % (tenant_id, user_id)) - user_ref = self._get_user(user_id) - tenants = set(user_ref.get('tenants', [])) - tenants.remove(tenant_id) - user_ref['tenants'] = list(tenants) - self.update_user(user_id, user_ref) - else: - self.update_metadata(user_id, tenant_id, metadata_ref) - - def list_role_assignments(self): - """List the role assignments. - - The kvs backend stores role assignments as key-values: - - "metadata-{target}-{actor}", with the value being a role list - - i.e. "metadata-MyProjectID-MyUserID" [role1, role2] - - ...so we enumerate the list and extract the targets, actors - and roles. - - """ - assignment_list = [] - metadata_keys = filter(lambda x: x.startswith("metadata-"), - self.db.keys()) - for key in metadata_keys: - template = {} - meta_id1 = key.split('-')[1] - meta_id2 = key.split('-')[2] - try: - self.get_project(meta_id1) - template['project_id'] = meta_id1 - except exception.NotFound: - template['domain_id'] = meta_id1 - try: - self._get_user(meta_id2) - template['user_id'] = meta_id2 - except exception.NotFound: - template['group_id'] = meta_id2 - - entry = self.db.get(key) - for r in entry.get('roles', []): - role_assignment = template.copy() - role_assignment['role_id'] = r - assignment_list.append(role_assignment) - - return assignment_list - # CRUD def create_user(self, user_id, user): try: @@ -308,301 +159,6 @@ class Identity(kvs.Base, identity.Driver): user_list.remove(user_id) self.db.set('user_list', list(user_list)) - def create_project(self, tenant_id, tenant): - tenant['name'] = clean.project_name(tenant['name']) - try: - self.get_project(tenant_id) - except exception.ProjectNotFound: - pass - else: - msg = 'Duplicate ID, %s.' % tenant_id - raise exception.Conflict(type='tenant', details=msg) - - try: - self.get_project_by_name(tenant['name'], tenant['domain_id']) - except exception.ProjectNotFound: - pass - else: - msg = 'Duplicate name, %s.' % tenant['name'] - raise exception.Conflict(type='tenant', details=msg) - - self.db.set('tenant-%s' % tenant_id, tenant) - self.db.set('tenant_name-%s' % tenant['name'], tenant) - return tenant - - def update_project(self, tenant_id, tenant): - if 'name' in tenant: - tenant['name'] = clean.project_name(tenant['name']) - try: - existing = self.db.get('tenant_name-%s' % tenant['name']) - if existing and tenant_id != existing['id']: - msg = 'Duplicate name, %s.' % tenant['name'] - raise exception.Conflict(type='tenant', details=msg) - except exception.NotFound: - pass - # get the old name and delete it too - try: - old_project = self.db.get('tenant-%s' % tenant_id) - except exception.NotFound: - raise exception.ProjectNotFound(project_id=tenant_id) - new_project = old_project.copy() - new_project.update(tenant) - new_project['id'] = tenant_id - self.db.delete('tenant_name-%s' % old_project['name']) - self.db.set('tenant-%s' % tenant_id, new_project) - self.db.set('tenant_name-%s' % new_project['name'], new_project) - return new_project - - def delete_project(self, tenant_id): - try: - old_project = self.db.get('tenant-%s' % tenant_id) - except exception.NotFound: - raise exception.ProjectNotFound(project_id=tenant_id) - self.db.delete('tenant_name-%s' % old_project['name']) - self.db.delete('tenant-%s' % tenant_id) - - def create_metadata(self, user_id, tenant_id, metadata, - domain_id=None, group_id=None): - - return self.update_metadata(user_id, tenant_id, metadata, - domain_id, group_id) - - def update_metadata(self, user_id, tenant_id, metadata, - domain_id=None, group_id=None): - if user_id: - if tenant_id: - self.db.set('metadata-%s-%s' % (tenant_id, user_id), metadata) - user_ref = self._get_user(user_id) - tenants = set(user_ref.get('tenants', [])) - if tenant_id not in tenants: - tenants.add(tenant_id) - user_ref['tenants'] = list(tenants) - self.update_user(user_id, user_ref) - else: - self.db.set('metadata-%s-%s' % (domain_id, user_id), metadata) - else: - if tenant_id: - self.db.set('metadata-%s-%s' % (tenant_id, group_id), metadata) - else: - self.db.set('metadata-%s-%s' % (domain_id, group_id), metadata) - return metadata - - def create_role(self, role_id, role): - try: - self.get_role(role_id) - except exception.RoleNotFound: - pass - else: - msg = 'Duplicate ID, %s.' % role_id - raise exception.Conflict(type='role', details=msg) - - for role_ref in self.list_roles(): - if role['name'] == role_ref['name']: - msg = 'Duplicate name, %s.' % role['name'] - raise exception.Conflict(type='role', details=msg) - self.db.set('role-%s' % role_id, role) - role_list = set(self.db.get('role_list', [])) - role_list.add(role_id) - self.db.set('role_list', list(role_list)) - return role - - def update_role(self, role_id, role): - old_role_ref = None - for role_ref in self.list_roles(): - if role['name'] == role_ref['name'] and role_id != role_ref['id']: - msg = 'Duplicate name, %s.' % role['name'] - raise exception.Conflict(type='role', details=msg) - if role_id == role_ref['id']: - old_role_ref = role_ref - if old_role_ref is None: - raise exception.RoleNotFound(role_id=role_id) - new_role = old_role_ref.copy() - new_role.update(role) - new_role['id'] = role_id - self.db.set('role-%s' % role_id, new_role) - return role - - def delete_role(self, role_id): - self.get_role(role_id) - metadata_keys = filter(lambda x: x.startswith("metadata-"), - self.db.keys()) - for key in metadata_keys: - meta_id1 = key.split('-')[1] - meta_id2 = key.split('-')[2] - try: - self.delete_grant(role_id, project_id=meta_id1, - user_id=meta_id2) - except exception.NotFound: - pass - try: - self.delete_grant(role_id, project_id=meta_id1, - group_id=meta_id2) - except exception.NotFound: - pass - try: - self.delete_grant(role_id, domain_id=meta_id1, - user_id=meta_id2) - except exception.NotFound: - pass - try: - self.delete_grant(role_id, domain_id=meta_id1, - group_id=meta_id2) - except exception.NotFound: - pass - self.db.delete('role-%s' % role_id) - role_list = set(self.db.get('role_list', [])) - role_list.remove(role_id) - self.db.set('role_list', list(role_list)) - - def create_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): - - self.get_role(role_id) - if user_id: - self.get_user(user_id) - if group_id: - self.get_group(group_id) - if domain_id: - self.get_domain(domain_id) - if project_id: - self.get_project(project_id) - - try: - metadata_ref = self.get_metadata(user_id, project_id, - domain_id, group_id) - except exception.MetadataNotFound: - metadata_ref = {} - roles = set(metadata_ref.get('roles', [])) - roles.add(role_id) - metadata_ref['roles'] = list(roles) - self.update_metadata(user_id, project_id, metadata_ref, - domain_id, group_id) - - def list_grants(self, user_id=None, group_id=None, - domain_id=None, project_id=None): - if user_id: - self.get_user(user_id) - if group_id: - self.get_group(group_id) - if domain_id: - self.get_domain(domain_id) - if project_id: - self.get_project(project_id) - - try: - metadata_ref = self.get_metadata(user_id, project_id, - domain_id, group_id) - except exception.MetadataNotFound: - metadata_ref = {} - return [self.get_role(x) for x in metadata_ref.get('roles', [])] - - def get_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): - self.get_role(role_id) - if user_id: - self.get_user(user_id) - if group_id: - self.get_group(group_id) - if domain_id: - self.get_domain(domain_id) - if project_id: - self.get_project(project_id) - - try: - metadata_ref = self.get_metadata(user_id, project_id, - domain_id, group_id) - except exception.MetadataNotFound: - metadata_ref = {} - role_ids = set(metadata_ref.get('roles', [])) - if role_id not in role_ids: - raise exception.RoleNotFound(role_id=role_id) - return self.get_role(role_id) - - def delete_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): - self.get_role(role_id) - if user_id: - self.get_user(user_id) - if group_id: - self.get_group(group_id) - if domain_id: - self.get_domain(domain_id) - if project_id: - self.get_project(project_id) - - try: - metadata_ref = self.get_metadata(user_id, project_id, - domain_id, group_id) - except exception.MetadataNotFound: - metadata_ref = {} - roles = set(metadata_ref.get('roles', [])) - try: - roles.remove(role_id) - except KeyError: - raise exception.RoleNotFound(role_id=role_id) - metadata_ref['roles'] = list(roles) - self.update_metadata(user_id, project_id, metadata_ref, - domain_id, group_id) - - # domain crud - - def create_domain(self, domain_id, domain): - try: - self.get_domain(domain_id) - except exception.DomainNotFound: - pass - else: - msg = 'Duplicate ID, %s.' % domain_id - raise exception.Conflict(type='domain', details=msg) - - try: - self.get_domain_by_name(domain['name']) - except exception.DomainNotFound: - pass - else: - msg = 'Duplicate name, %s.' % domain['name'] - raise exception.Conflict(type='domain', details=msg) - - self.db.set('domain-%s' % domain_id, domain) - self.db.set('domain_name-%s' % domain['name'], domain) - domain_list = set(self.db.get('domain_list', [])) - domain_list.add(domain_id) - self.db.set('domain_list', list(domain_list)) - return domain - - def list_domains(self): - domain_ids = self.db.get('domain_list', []) - return [self.get_domain(x) for x in domain_ids] - - def get_domain(self, domain_id): - try: - return self.db.get('domain-%s' % domain_id) - except exception.NotFound: - raise exception.DomainNotFound(domain_id=domain_id) - - def get_domain_by_name(self, domain_name): - try: - return self.db.get('domain_name-%s' % domain_name) - except exception.NotFound: - raise exception.DomainNotFound(domain_id=domain_name) - - def update_domain(self, domain_id, domain): - orig_domain = self.get_domain(domain_id) - domain['id'] = domain_id - self.db.set('domain-%s' % domain_id, domain) - self.db.set('domain_name-%s' % domain['name'], domain) - if domain['name'] != orig_domain['name']: - self.db.delete('domain_name-%s' % orig_domain['name']) - return domain - - def delete_domain(self, domain_id): - domain = self.get_domain(domain_id) - self.db.delete('domain-%s' % domain_id) - self.db.delete('domain_name-%s' % domain['name']) - domain_list = set(self.db.get('domain_list', [])) - domain_list.remove(domain_id) - self.db.set('domain_list', list(domain_list)) - # group crud def create_group(self, group_id, group): diff --git a/keystone/identity/backends/ldap.py b/keystone/identity/backends/ldap.py index 2387c84e..c06737c8 100644 --- a/keystone/identity/backends/ldap.py +++ b/keystone/identity/backends/ldap.py @@ -47,43 +47,20 @@ class Identity(identity.Driver): self.suffix = CONF.ldap.suffix self.user = UserApi(CONF) - self.project = ProjectApi(CONF) - self.role = RoleApi(CONF) self.group = GroupApi(CONF) - def _validate_domain(self, ref): - """Validate that either the default domain or nothing is specified. + def default_assignment_driver(self): + return "keystone.assignment.backends.ldap.Assignment" - Also removes the domain from the ref so that LDAP doesn't have to - persist the attribute. - - """ - ref = ref.copy() - domain_id = ref.pop('domain_id', CONF.identity.default_domain_id) - self._validate_domain_id(domain_id) - return ref + # Identity interface - def _validate_domain_id(self, domain_id): - """Validate that the domain ID specified belongs to the default domain. + def create_project(self, project_id, project): + return self.assignment.create_project(project_id, project) - """ - if domain_id != CONF.identity.default_domain_id: - raise exception.DomainNotFound(domain_id=domain_id) - - def _set_default_domain(self, ref): - """Overrides any domain reference with the default domain.""" - if isinstance(ref, dict): - ref = ref.copy() - ref['domain_id'] = CONF.identity.default_domain_id - return ref - elif isinstance(ref, list): - return [self._set_default_domain(x) for x in ref] - else: - raise ValueError(_('Expected dict or list: %s') % type(ref)) - - # Identity interface + def get_project(self, project_id): + return self.assignment.get_project(project_id) - def authenticate_user(self, user_id=None, password=None): + def authenticate(self, user_id=None, password=None): try: user_ref = self._get_user(user_id) except exception.UserNotFound: @@ -97,135 +74,36 @@ class Identity(identity.Driver): raise AssertionError('Invalid user / password') except Exception: raise AssertionError('Invalid user / password') - return user_ref - - def authorize_for_project(self, user_ref, tenant_id=None): - user_id = user_ref['id'] - tenant_ref = None - metadata_ref = {} - - if tenant_id is not None: - if tenant_id not in self.get_projects_for_user(user_id): - raise AssertionError('Invalid tenant') - - try: - tenant_ref = self.get_project(tenant_id) - # TODO(termie): this should probably be made into a - # get roles call - metadata_ref = self.get_metadata(user_id, tenant_id) - except exception.ProjectNotFound: - tenant_ref = None - metadata_ref = {} - except exception.MetadataNotFound: - metadata_ref = {} - - user_ref = self._set_default_domain(identity.filter_user(user_ref)) - return (user_ref, tenant_ref, metadata_ref) - - def get_project(self, tenant_id): - return self._set_default_domain(self.project.get(tenant_id)) - - def list_projects(self): - return self._set_default_domain(self.project.get_all()) - - def get_project_by_name(self, tenant_name, domain_id): - self._validate_domain_id(domain_id) - return self._set_default_domain(self.project.get_by_name(tenant_name)) + return self.assignment._set_default_domain( + identity.filter_user(user_ref)) def _get_user(self, user_id): return self.user.get(user_id) def get_user(self, user_id): ref = identity.filter_user(self._get_user(user_id)) - return self._set_default_domain(ref) + return self.assignment._set_default_domain(ref) def list_users(self): - return self._set_default_domain(self.user.get_all()) + return self.assignment._set_default_domain(self.user.get_all()) def get_user_by_name(self, user_name, domain_id): - self._validate_domain_id(domain_id) + self.assignment._validate_default_domain_id(domain_id) ref = identity.filter_user(self.user.get_by_name(user_name)) - return self._set_default_domain(ref) - - def get_metadata(self, user_id=None, tenant_id=None, - domain_id=None, group_id=None): - - def _get_roles_for_just_user_and_project(user_id, tenant_id): - self.get_user(user_id) - self.get_project(tenant_id) - user_dn = self.user._id_to_dn(user_id) - return [self.role._dn_to_id(a.role_dn) - for a in self.role.get_role_assignments - (self.project._id_to_dn(tenant_id)) - if a.user_dn == user_dn] - - if domain_id is not None: - msg = 'Domain metadata not supported by LDAP' - raise exception.NotImplemented(message=msg) - if not self.get_project(tenant_id) or not self.get_user(user_id): - return {} - - metadata_ref = _get_roles_for_just_user_and_project(user_id, tenant_id) - if not metadata_ref: - return {} - return {'roles': metadata_ref} - - def get_role(self, role_id): - return self.role.get(role_id) - - def list_roles(self): - return self.role.get_all() - - def get_projects_for_user(self, user_id): - self.get_user(user_id) - user_dn = self.user._id_to_dn(user_id) - associations = (self.role.list_project_roles_for_user - (user_dn, self.project.tree_dn)) - return [p['id'] for p in - self.project.get_user_projects(user_dn, associations)] - - def get_project_users(self, tenant_id): - self.get_project(tenant_id) - tenant_dn = self.project._id_to_dn(tenant_id) - rolegrants = self.role.get_role_assignments(tenant_dn) - users = [self.user.get_filtered(self.user._dn_to_id(user_id)) - for user_id in - self.project.get_user_dns(tenant_id, rolegrants)] - return self._set_default_domain(users) - - def _subrole_id_to_dn(self, role_id, tenant_id): - if tenant_id is None: - return self.role._id_to_dn(role_id) - else: - return '%s=%s,%s' % (self.role.id_attr, - ldap.dn.escape_dn_chars(role_id), - self.project._id_to_dn(tenant_id)) - - def add_role_to_user_and_project(self, user_id, tenant_id, role_id): - self.get_user(user_id) - self.get_project(tenant_id) - self.get_role(role_id) - user_dn = self.user._id_to_dn(user_id) - role_dn = self._subrole_id_to_dn(role_id, tenant_id) - self.role.add_user(role_id, role_dn, user_dn, user_id, tenant_id) - tenant_dn = self.project._id_to_dn(tenant_id) - return UserRoleAssociation( - role_dn=role_dn, - user_dn=user_dn, - tenant_dn=tenant_dn) + return self.assignment._set_default_domain(ref) # CRUD def create_user(self, user_id, user): - user = self._validate_domain(user) + user = self.assignment._validate_default_domain(user) user_ref = self.user.create(user) tenant_id = user.get('tenant_id') - user_dn = self.user._id_to_dn(user['id']) if tenant_id is not None: - self.project.add_user(tenant_id, user_dn) - return self._set_default_domain(identity.filter_user(user_ref)) + self.assignment.add_user_to_project(tenant_id, user_id) + return (self.assignment._set_default_domain + (identity.filter_user(user_ref))) def update_user(self, user_id, user): - user = self._validate_domain(user) + user = self.assignment._validate_default_domain(user) if 'id' in user and user['id'] != user_id: raise exception.ValidationError('Cannot change user ID') old_obj = self.user.get(user_id) @@ -248,67 +126,12 @@ class Identity(identity.Driver): user['enabled_nomask'] = old_obj['enabled_nomask'] self.user.mask_enabled_attribute(user) self.user.update(user_id, user, old_obj) - return self._set_default_domain(self.user.get_filtered(user_id)) - - def create_project(self, tenant_id, tenant): - tenant = self._validate_domain(tenant) - tenant['name'] = clean.project_name(tenant['name']) - data = tenant.copy() - if 'id' not in data or data['id'] is None: - data['id'] = str(uuid.uuid4().hex) - if 'description' in data and data['description'] in ['', None]: - data.pop('description') - return self._set_default_domain(self.project.create(data)) - - def update_project(self, tenant_id, tenant): - tenant = self._validate_domain(tenant) - if 'name' in tenant: - tenant['name'] = clean.project_name(tenant['name']) - return self._set_default_domain(self.project.update(tenant_id, tenant)) - - def create_metadata(self, user_id, tenant_id, metadata): - return {} - - def create_role(self, role_id, role): - try: - self.get_role(role_id) - except exception.NotFound: - pass - else: - msg = 'Duplicate ID, %s.' % role_id - raise exception.Conflict(type='role', details=msg) - - try: - self.role.get_by_name(role['name']) - except exception.NotFound: - pass - else: - msg = 'Duplicate name, %s.' % role['name'] - raise exception.Conflict(type='role', details=msg) - - return self.role.create(role) - - def delete_role(self, role_id): - return self.role.delete(role_id, self.project.tree_dn) - - def delete_project(self, tenant_id): - if self.project.subtree_delete_enabled: - self.project.deleteTree(id) - else: - tenant_dn = self.project._id_to_dn(tenant_id) - self.role.roles_delete_subtree_by_project(tenant_dn) - self.project.delete(tenant_id) + return (self.assignment._set_default_domain + (self.user.get_filtered(user_id))) def delete_user(self, user_id): + self.assignment.delete_user(user_id) user_dn = self.user._id_to_dn(user_id) - for ref in self.role.list_global_roles_for_user(user_dn): - self.role.delete_user(ref.role_dn, ref.user_dn, ref.project_dn, - user_id, self.role._dn_to_id(ref.role_dn)) - for ref in self.role.list_project_roles_for_user(user_dn, - self.project.tree_dn): - self.role.delete_user(ref.role_dn, ref.user_dn, ref.project_dn, - user_id, self.role._dn_to_id(ref.role_dn)) - groups = self.group.list_user_groups(user_dn) for group in groups: self.group.remove_user(user_dn, group['id'], user_id) @@ -319,30 +142,20 @@ class Identity(identity.Driver): self.user._id_to_dn(user_id)) self.user.delete(user_id) - def remove_role_from_user_and_project(self, user_id, tenant_id, role_id): - role_dn = self._subrole_id_to_dn(role_id, tenant_id) - return self.role.delete_user(role_dn, - self.user._id_to_dn(user_id), - self.project._id_to_dn(tenant_id), - user_id, role_id) - - def update_role(self, role_id, role): - self.get_role(role_id) - self.role.update(role_id, role) - def create_group(self, group_id, group): - group = self._validate_domain(group) + group = self.assignment._validate_default_domain(group) group['name'] = clean.group_name(group['name']) - return self._set_default_domain(self.group.create(group)) + return self.assignment._set_default_domain(self.group.create(group)) def get_group(self, group_id): - return self._set_default_domain(self.group.get(group_id)) + return self.assignment._set_default_domain(self.group.get(group_id)) def update_group(self, group_id, group): - group = self._validate_domain(group) + group = self.assignment._validate_default_domain(group) if 'name' in group: group['name'] = clean.group_name(group['name']) - return self._set_default_domain(self.group.update(group_id, group)) + return (self.assignment._set_default_domain + (self.group.update(group_id, group))) def delete_group(self, group_id): return self.group.delete(group_id) @@ -362,10 +175,11 @@ class Identity(identity.Driver): def list_groups_for_user(self, user_id): self.get_user(user_id) user_dn = self.user._id_to_dn(user_id) - return self._set_default_domain(self.group.list_user_groups(user_dn)) + return (self.assignment._set_default_domain + (self.group.list_user_groups(user_dn))) def list_groups(self): - return self._set_default_domain(self.group.get_all()) + return self.assignment._set_default_domain(self.group.get_all()) def list_users_in_group(self, group_id): self.get_group(group_id) @@ -379,7 +193,7 @@ class Identity(identity.Driver): " '%(group_id)s'. The user should be removed" " from the group. The user will be ignored.") % dict(user_dn=user_dn, group_id=group_id)) - return self._set_default_domain(users) + return self.assignment._set_default_domain(users) def check_user_in_group(self, user_id, group_id): self.get_user(user_id) @@ -392,27 +206,6 @@ class Identity(identity.Driver): break return found - def create_domain(self, domain_id, domain): - if domain_id == CONF.identity.default_domain_id: - msg = 'Duplicate ID, %s.' % domain_id - raise exception.Conflict(type='domain', details=msg) - raise exception.Forbidden('Domains are read-only against LDAP') - - def get_domain(self, domain_id): - self._validate_domain_id(domain_id) - return DEFAULT_DOMAIN - - def update_domain(self, domain_id, domain): - self._validate_domain_id(domain_id) - raise exception.Forbidden('Domains are read-only against LDAP') - - def delete_domain(self, domain_id): - self._validate_domain_id(domain_id) - raise exception.Forbidden('Domains are read-only against LDAP') - - def list_domains(self): - return [DEFAULT_DOMAIN] - # TODO(termie): turn this into a data object and move logic to driver class UserApi(common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap): diff --git a/keystone/identity/backends/pam.py b/keystone/identity/backends/pam.py index 9c4bbf38..5cfa5b16 100644 --- a/keystone/identity/backends/pam.py +++ b/keystone/identity/backends/pam.py @@ -58,21 +58,13 @@ class PamIdentity(identity.Driver): Tenant is always the same as User, root user has admin role. """ - def authenticate_user(self, user_id=None, password=None): + def authenticate(self, user_id=None, password=None): auth = pam.authenticate if pam else PAM_authenticate if not auth(user_id, password): raise AssertionError('Invalid user / password') user = {'id': user_id, 'name': user_id} return user - def authorize_for_project(self, user_ref, tenant_id=None): - user_id = user_ref['id'] - metadata = {} - if user_id == 'root': - metadata['is_admin'] = True - tenant = {'id': user_id, 'name': user_id} - return (user_ref, tenant, metadata) - def get_project(self, tenant_id): return {'id': tenant_id, 'name': tenant_id} @@ -134,16 +126,16 @@ class PamIdentity(identity.Driver): def delete_project(self, tenant_id, tenant): raise NotImplementedError() - def get_metadata(self, user_id, tenant_id): + def _get_metadata(self, user_id, tenant_id): metadata = {} if user_id == 'root': metadata['is_admin'] = True return metadata - def create_metadata(self, user_id, tenant_id, metadata): + def _create_metadata(self, user_id, tenant_id, metadata): raise NotImplementedError() - def update_metadata(self, user_id, tenant_id, metadata): + def _update_metadata(self, user_id, tenant_id, metadata): raise NotImplementedError() def create_role(self, role_id, role): diff --git a/keystone/identity/backends/sql.py b/keystone/identity/backends/sql.py index c02ffd0b..f82c34f8 100644 --- a/keystone/identity/backends/sql.py +++ b/keystone/identity/backends/sql.py @@ -14,7 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -from keystone import clean from keystone.common import sql from keystone.common.sql import migration from keystone.common import utils @@ -51,78 +50,6 @@ class Group(sql.ModelBase, sql.DictBase): __table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {}) -class Domain(sql.ModelBase, sql.DictBase): - __tablename__ = 'domain' - attributes = ['id', 'name', 'enabled'] - id = sql.Column(sql.String(64), primary_key=True) - name = sql.Column(sql.String(64), unique=True, nullable=False) - enabled = sql.Column(sql.Boolean, default=True) - extra = sql.Column(sql.JsonBlob()) - - -class Project(sql.ModelBase, sql.DictBase): - __tablename__ = 'project' - attributes = ['id', 'name', 'domain_id', 'description', 'enabled'] - id = sql.Column(sql.String(64), primary_key=True) - name = sql.Column(sql.String(64), nullable=False) - domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'), - nullable=False) - description = sql.Column(sql.Text()) - enabled = sql.Column(sql.Boolean) - extra = sql.Column(sql.JsonBlob()) - # Unique constraint across two columns to create the separation - # rather than just only 'name' being unique - __table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {}) - - -class Role(sql.ModelBase, sql.DictBase): - __tablename__ = 'role' - attributes = ['id', 'name'] - id = sql.Column(sql.String(64), primary_key=True) - name = sql.Column(sql.String(64), unique=True, nullable=False) - extra = sql.Column(sql.JsonBlob()) - - -class BaseGrant(sql.DictBase): - def to_dict(self): - """Override parent to_dict() method with a simpler implementation. - - Grant tables don't have non-indexed 'extra' attributes, so the - parent implementation is not applicable. - """ - return dict(self.iteritems()) - - -class UserProjectGrant(sql.ModelBase, BaseGrant): - __tablename__ = 'user_project_metadata' - user_id = sql.Column(sql.String(64), - primary_key=True) - project_id = sql.Column(sql.String(64), - primary_key=True) - data = sql.Column(sql.JsonBlob()) - - -class UserDomainGrant(sql.ModelBase, BaseGrant): - __tablename__ = 'user_domain_metadata' - user_id = sql.Column(sql.String(64), primary_key=True) - domain_id = sql.Column(sql.String(64), primary_key=True) - data = sql.Column(sql.JsonBlob()) - - -class GroupProjectGrant(sql.ModelBase, BaseGrant): - __tablename__ = 'group_project_metadata' - group_id = sql.Column(sql.String(64), primary_key=True) - project_id = sql.Column(sql.String(64), primary_key=True) - data = sql.Column(sql.JsonBlob()) - - -class GroupDomainGrant(sql.ModelBase, BaseGrant): - __tablename__ = 'group_domain_metadata' - group_id = sql.Column(sql.String(64), primary_key=True) - domain_id = sql.Column(sql.String(64), primary_key=True) - data = sql.Column(sql.JsonBlob()) - - class UserGroupMembership(sql.ModelBase, sql.DictBase): """Group membership join table.""" __tablename__ = 'user_group_membership' @@ -135,6 +62,9 @@ class UserGroupMembership(sql.ModelBase, sql.DictBase): class Identity(sql.Base, identity.Driver): + def default_assignment_driver(self): + return "keystone.assignment.backends.sql.Assignment" + # Internal interface to manage the database def db_sync(self, version=None): migration.db_sync(version=version) @@ -154,7 +84,7 @@ class Identity(sql.Base, identity.Driver): return utils.check_password(password, user_ref.password) # Identity interface - def authenticate_user(self, user_id=None, password=None): + def authenticate(self, user_id=None, password=None): session = self.get_session() user_ref = None try: @@ -163,475 +93,7 @@ class Identity(sql.Base, identity.Driver): raise AssertionError('Invalid user / password') if not self._check_password(password, user_ref): raise AssertionError('Invalid user / password') - return user_ref - - def authorize_for_project(self, user_ref, tenant_id=None): - user_id = user_ref['id'] - tenant_ref = None - metadata_ref = {} - if tenant_id is not None: - # FIXME(gyee): this should really be - # get_roles_for_user_and_project() after the dusts settle - if tenant_id not in self.get_projects_for_user(user_id): - raise AssertionError('Invalid project') - try: - tenant_ref = self.get_project(tenant_id) - metadata_ref = self.get_metadata(user_id, tenant_id) - except exception.ProjectNotFound: - tenant_ref = None - metadata_ref = {} - except exception.MetadataNotFound: - metadata_ref = {} - user_ref = identity.filter_user(user_ref.to_dict()) - return (user_ref, tenant_ref, metadata_ref) - - def _get_project(self, session, project_id): - project_ref = session.query(Project).get(project_id) - if project_ref is None: - raise exception.ProjectNotFound(project_id=project_id) - return project_ref - - def get_project(self, tenant_id): - session = self.get_session() - return self._get_project(session, tenant_id).to_dict() - - def get_project_by_name(self, tenant_name, domain_id): - session = self.get_session() - query = session.query(Project) - query = query.filter_by(name=tenant_name) - query = query.filter_by(domain_id=domain_id) - try: - project_ref = query.one() - except sql.NotFound: - raise exception.ProjectNotFound(project_id=tenant_name) - return project_ref.to_dict() - - def get_project_user_ids(self, tenant_id): - session = self.get_session() - self.get_project(tenant_id) - query = session.query(UserProjectGrant) - query = query.filter(UserProjectGrant.project_id == tenant_id) - project_refs = query.all() - return [project_ref.user_id for project_ref in project_refs] - - def get_project_users(self, tenant_id): - session = self.get_session() - self.get_project(tenant_id) - user_refs = [] - for user_id in self.get_project_user_ids(tenant_id): - query = session.query(User) - query = query.filter(User.id == user_id) - user_ref = query.first() - user_refs.append(identity.filter_user(user_ref.to_dict())) - return user_refs - - def get_metadata(self, user_id=None, tenant_id=None, - domain_id=None, group_id=None): - session = self.get_session() - - if user_id: - if tenant_id: - q = session.query(UserProjectGrant) - q = q.filter_by(project_id=tenant_id) - elif domain_id: - q = session.query(UserDomainGrant) - q = q.filter_by(domain_id=domain_id) - q = q.filter_by(user_id=user_id) - elif group_id: - if tenant_id: - q = session.query(GroupProjectGrant) - q = q.filter_by(project_id=tenant_id) - elif domain_id: - q = session.query(GroupDomainGrant) - q = q.filter_by(domain_id=domain_id) - q = q.filter_by(group_id=group_id) - try: - return q.one().data - except sql.NotFound: - raise exception.MetadataNotFound() - - def create_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): - session = self.get_session() - self._get_role(session, role_id) - if user_id: - self._get_user(session, user_id) - if group_id: - self._get_group(session, group_id) - if domain_id: - self._get_domain(session, domain_id) - if project_id: - self._get_project(session, project_id) - - try: - metadata_ref = self.get_metadata(user_id, project_id, - domain_id, group_id) - is_new = False - except exception.MetadataNotFound: - metadata_ref = {} - is_new = True - roles = set(metadata_ref.get('roles', [])) - roles.add(role_id) - metadata_ref['roles'] = list(roles) - if is_new: - self.create_metadata(user_id, project_id, metadata_ref, - domain_id, group_id) - else: - self.update_metadata(user_id, project_id, metadata_ref, - domain_id, group_id) - - def list_grants(self, user_id=None, group_id=None, - domain_id=None, project_id=None): - session = self.get_session() - if user_id: - self._get_user(session, user_id) - if group_id: - self._get_group(session, group_id) - if domain_id: - self._get_domain(session, domain_id) - if project_id: - self._get_project(session, project_id) - - try: - metadata_ref = self.get_metadata(user_id, project_id, - domain_id, group_id) - except exception.MetadataNotFound: - metadata_ref = {} - return [self.get_role(x) for x in metadata_ref.get('roles', [])] - - def get_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): - session = self.get_session() - role_ref = self._get_role(session, role_id) - if user_id: - self._get_user(session, user_id) - if group_id: - self._get_group(session, group_id) - if domain_id: - self._get_domain(session, domain_id) - if project_id: - self._get_project(session, project_id) - - try: - metadata_ref = self.get_metadata(user_id, project_id, - domain_id, group_id) - except exception.MetadataNotFound: - metadata_ref = {} - role_ids = set(metadata_ref.get('roles', [])) - if role_id not in role_ids: - raise exception.RoleNotFound(role_id=role_id) - return role_ref.to_dict() - - def delete_grant(self, role_id, user_id=None, group_id=None, - domain_id=None, project_id=None): - session = self.get_session() - self._get_role(session, role_id) - if user_id: - self._get_user(session, user_id) - if group_id: - self._get_group(session, group_id) - if domain_id: - self._get_domain(session, domain_id) - if project_id: - self._get_project(session, project_id) - - try: - metadata_ref = self.get_metadata(user_id, project_id, - domain_id, group_id) - is_new = False - except exception.MetadataNotFound: - metadata_ref = {} - is_new = True - roles = set(metadata_ref.get('roles', [])) - try: - roles.remove(role_id) - except KeyError: - raise exception.RoleNotFound(role_id=role_id) - metadata_ref['roles'] = list(roles) - if is_new: - self.create_metadata(user_id, project_id, metadata_ref, - domain_id, group_id) - else: - self.update_metadata(user_id, project_id, metadata_ref, - domain_id, group_id) - - def list_role_assignments(self): - - # TODO(henry-nash): The current implementation is really simulating - # us having a common role assignment table, rather than having the - # four different grant tables we have today. When we move to role - # assignment as a first class entity, we should create the single - # assignment table, simplifying the logic of this (and many other) - # functions. - - session = self.get_session() - assignment_list = [] - refs = session.query(UserDomainGrant).all() - for x in refs: - for r in x.data.get('roles', []): - assignment_list.append({'user_id': x.user_id, - 'domain_id': x.domain_id, - 'role_id': r}) - refs = session.query(UserProjectGrant).all() - for x in refs: - for r in x.data.get('roles', []): - assignment_list.append({'user_id': x.user_id, - 'project_id': x.project_id, - 'role_id': r}) - refs = session.query(GroupDomainGrant).all() - for x in refs: - for r in x.data.get('roles', []): - assignment_list.append({'group_id': x.group_id, - 'domain_id': x.domain_id, - 'role_id': r}) - refs = session.query(GroupProjectGrant).all() - for x in refs: - for r in x.data.get('roles', []): - assignment_list.append({'group_id': x.group_id, - 'project_id': x.project_id, - 'role_id': r}) - return assignment_list - - def list_projects(self): - session = self.get_session() - tenant_refs = session.query(Project).all() - return [tenant_ref.to_dict() for tenant_ref in tenant_refs] - - def get_projects_for_user(self, user_id): - session = self.get_session() - self._get_user(session, user_id) - query = session.query(UserProjectGrant) - query = query.filter_by(user_id=user_id) - membership_refs = query.all() - return [x.project_id for x in membership_refs] - - def add_role_to_user_and_project(self, user_id, tenant_id, role_id): - session = self.get_session() - self._get_user(session, user_id) - self._get_project(session, tenant_id) - self._get_role(session, role_id) - try: - metadata_ref = self.get_metadata(user_id, tenant_id) - is_new = False - except exception.MetadataNotFound: - metadata_ref = {} - is_new = True - roles = set(metadata_ref.get('roles', [])) - if role_id in roles: - msg = ('User %s already has role %s in tenant %s' - % (user_id, role_id, tenant_id)) - raise exception.Conflict(type='role grant', details=msg) - roles.add(role_id) - metadata_ref['roles'] = list(roles) - if is_new: - self.create_metadata(user_id, tenant_id, metadata_ref) - else: - self.update_metadata(user_id, tenant_id, metadata_ref) - - def remove_role_from_user_and_project(self, user_id, tenant_id, role_id): - try: - metadata_ref = self.get_metadata(user_id, tenant_id) - roles = set(metadata_ref.get('roles', [])) - if role_id not in roles: - raise exception.RoleNotFound(message=_( - 'Cannot remove role that has not been granted, %s') % - role_id) - roles.remove(role_id) - metadata_ref['roles'] = list(roles) - if len(roles): - self.update_metadata(user_id, tenant_id, metadata_ref) - else: - session = self.get_session() - q = session.query(UserProjectGrant) - q = q.filter_by(user_id=user_id) - q = q.filter_by(project_id=tenant_id) - q.delete() - except exception.MetadataNotFound: - msg = 'Cannot remove role that has not been granted, %s' % role_id - raise exception.RoleNotFound(message=msg) - - # CRUD - @sql.handle_conflicts(type='project') - def create_project(self, tenant_id, tenant): - tenant['name'] = clean.project_name(tenant['name']) - session = self.get_session() - with session.begin(): - tenant_ref = Project.from_dict(tenant) - session.add(tenant_ref) - session.flush() - return tenant_ref.to_dict() - - @sql.handle_conflicts(type='project') - def update_project(self, tenant_id, tenant): - session = self.get_session() - - if 'name' in tenant: - tenant['name'] = clean.project_name(tenant['name']) - - with session.begin(): - tenant_ref = self._get_project(session, tenant_id) - old_project_dict = tenant_ref.to_dict() - for k in tenant: - old_project_dict[k] = tenant[k] - new_project = Project.from_dict(old_project_dict) - for attr in Project.attributes: - if attr != 'id': - setattr(tenant_ref, attr, getattr(new_project, attr)) - tenant_ref.extra = new_project.extra - session.flush() - return tenant_ref.to_dict(include_extra_dict=True) - - @sql.handle_conflicts(type='project') - def delete_project(self, tenant_id): - session = self.get_session() - - with session.begin(): - tenant_ref = self._get_project(session, tenant_id) - - q = session.query(UserProjectGrant) - q = q.filter_by(project_id=tenant_id) - q.delete(False) - - q = session.query(UserProjectGrant) - q = q.filter_by(project_id=tenant_id) - q.delete(False) - - q = session.query(GroupProjectGrant) - q = q.filter_by(project_id=tenant_id) - q.delete(False) - - session.delete(tenant_ref) - session.flush() - - @sql.handle_conflicts(type='metadata') - def create_metadata(self, user_id, tenant_id, metadata, - domain_id=None, group_id=None): - session = self.get_session() - with session.begin(): - if user_id: - if tenant_id: - session.add(UserProjectGrant(user_id=user_id, - project_id=tenant_id, - data=metadata)) - elif domain_id: - session.add(UserDomainGrant(user_id=user_id, - domain_id=domain_id, - data=metadata)) - elif group_id: - if tenant_id: - session.add(GroupProjectGrant(group_id=group_id, - project_id=tenant_id, - data=metadata)) - elif domain_id: - session.add(GroupDomainGrant(group_id=group_id, - domain_id=domain_id, - data=metadata)) - session.flush() - return metadata - - @sql.handle_conflicts(type='metadata') - def update_metadata(self, user_id, tenant_id, metadata, - domain_id=None, group_id=None): - session = self.get_session() - with session.begin(): - if user_id: - if tenant_id: - q = session.query(UserProjectGrant) - q = q.filter_by(user_id=user_id) - q = q.filter_by(project_id=tenant_id) - elif domain_id: - q = session.query(UserDomainGrant) - q = q.filter_by(user_id=user_id) - q = q.filter_by(domain_id=domain_id) - elif group_id: - if tenant_id: - q = session.query(GroupProjectGrant) - q = q.filter_by(group_id=group_id) - q = q.filter_by(project_id=tenant_id) - elif domain_id: - q = session.query(GroupDomainGrant) - q = q.filter_by(group_id=group_id) - q = q.filter_by(domain_id=domain_id) - metadata_ref = q.first() - data = metadata_ref.data.copy() - data.update(metadata) - metadata_ref.data = data - session.flush() - return metadata_ref - - # domain crud - - @sql.handle_conflicts(type='domain') - def create_domain(self, domain_id, domain): - session = self.get_session() - with session.begin(): - ref = Domain.from_dict(domain) - session.add(ref) - session.flush() - return ref.to_dict() - - def list_domains(self): - session = self.get_session() - refs = session.query(Domain).all() - return [ref.to_dict() for ref in refs] - - def _get_domain(self, session, domain_id): - ref = session.query(Domain).get(domain_id) - if ref is None: - raise exception.DomainNotFound(domain_id=domain_id) - return ref - - def get_domain(self, domain_id): - session = self.get_session() - return self._get_domain(session, domain_id).to_dict() - - def get_domain_by_name(self, domain_name): - session = self.get_session() - try: - ref = session.query(Domain).filter_by(name=domain_name).one() - except sql.NotFound: - raise exception.DomainNotFound(domain_id=domain_name) - return ref.to_dict() - - @sql.handle_conflicts(type='domain') - def update_domain(self, domain_id, domain): - session = self.get_session() - with session.begin(): - ref = self._get_domain(session, domain_id) - old_dict = ref.to_dict() - for k in domain: - old_dict[k] = domain[k] - new_domain = Domain.from_dict(old_dict) - for attr in Domain.attributes: - if attr != 'id': - setattr(ref, attr, getattr(new_domain, attr)) - ref.extra = new_domain.extra - session.flush() - return ref.to_dict() - - def delete_domain(self, domain_id): - session = self.get_session() - with session.begin(): - ref = self._get_domain(session, domain_id) - session.delete(ref) - session.flush() - - def list_user_projects(self, user_id): - session = self.get_session() - user = self.get_user(user_id) - metadata_refs = session\ - .query(UserProjectGrant)\ - .filter_by(user_id=user_id) - project_ids = set([x.project_id for x in metadata_refs - if x.data.get('roles')]) - if user.get('project_id'): - project_ids.add(user['project_id']) - - # FIXME(dolph): this should be removed with proper migrations - if user.get('tenant_id'): - project_ids.add(user['tenant_id']) - - return [self.get_project(x) for x in project_ids] + return identity.filter_user(user_ref.to_dict()) # user crud @@ -753,20 +215,13 @@ class Identity(sql.Base, identity.Driver): with session.begin(): ref = self._get_user(session, user_id) - q = session.query(UserProjectGrant) - q = q.filter_by(user_id=user_id) - q.delete(False) - - q = session.query(UserDomainGrant) - q = q.filter_by(user_id=user_id) - q.delete(False) - q = session.query(UserGroupMembership) q = q.filter_by(user_id=user_id) q.delete(False) session.delete(ref) session.flush() + self.assignment.delete_user(user_id) # group crud @@ -817,92 +272,10 @@ class Identity(sql.Base, identity.Driver): with session.begin(): ref = self._get_group(session, group_id) - q = session.query(GroupProjectGrant) - q = q.filter_by(group_id=group_id) - q.delete(False) - - q = session.query(GroupDomainGrant) - q = q.filter_by(group_id=group_id) - q.delete(False) - q = session.query(UserGroupMembership) q = q.filter_by(group_id=group_id) q.delete(False) session.delete(ref) session.flush() - - # role crud - - @sql.handle_conflicts(type='role') - def create_role(self, role_id, role): - session = self.get_session() - with session.begin(): - ref = Role.from_dict(role) - session.add(ref) - session.flush() - return ref.to_dict() - - def list_roles(self): - session = self.get_session() - refs = session.query(Role).all() - return [ref.to_dict() for ref in refs] - - def _get_role(self, session, role_id): - ref = session.query(Role).get(role_id) - if ref is None: - raise exception.RoleNotFound(role_id=role_id) - return ref - - def get_role(self, role_id): - session = self.get_session() - return self._get_role(session, role_id).to_dict() - - @sql.handle_conflicts(type='role') - def update_role(self, role_id, role): - session = self.get_session() - with session.begin(): - ref = self._get_role(session, role_id) - old_dict = ref.to_dict() - for k in role: - old_dict[k] = role[k] - new_role = Role.from_dict(old_dict) - for attr in Role.attributes: - if attr != 'id': - setattr(ref, attr, getattr(new_role, attr)) - ref.extra = new_role.extra - session.flush() - return ref.to_dict() - - def delete_role(self, role_id): - session = self.get_session() - - with session.begin(): - ref = self._get_role(session, role_id) - for metadata_ref in session.query(UserProjectGrant): - try: - self.delete_grant(role_id, user_id=metadata_ref.user_id, - project_id=metadata_ref.project_id) - except exception.RoleNotFound: - pass - for metadata_ref in session.query(UserDomainGrant): - try: - self.delete_grant(role_id, user_id=metadata_ref.user_id, - domain_id=metadata_ref.domain_id) - except exception.RoleNotFound: - pass - for metadata_ref in session.query(GroupProjectGrant): - try: - self.delete_grant(role_id, group_id=metadata_ref.group_id, - project_id=metadata_ref.project_id) - except exception.RoleNotFound: - pass - for metadata_ref in session.query(GroupDomainGrant): - try: - self.delete_grant(role_id, group_id=metadata_ref.group_id, - domain_id=metadata_ref.domain_id) - except exception.RoleNotFound: - pass - - session.delete(ref) - session.flush() + self.assignment.delete_group(group_id) diff --git a/keystone/identity/controllers.py b/keystone/identity/controllers.py index 9271f3d9..b4e8ea6a 100644 --- a/keystone/identity/controllers.py +++ b/keystone/identity/controllers.py @@ -748,6 +748,11 @@ class RoleV3(controller.V3Controller): msg = 'Specify a user or group, not both' raise exception.ValidationError(msg) + def _check_if_inherited(self, context): + return (CONF.os_inherit.enabled and + context['path'].startswith('/OS-INHERIT') and + context['path'].endswith('/inherited_to_projects')) + @controller.protected def create_grant(self, context, role_id, user_id=None, group_id=None, domain_id=None, project_id=None): @@ -756,7 +761,8 @@ class RoleV3(controller.V3Controller): self._require_user_xor_group(user_id, group_id) self.identity_api.create_grant( - role_id, user_id, group_id, domain_id, project_id) + role_id, user_id, group_id, domain_id, project_id, + self._check_if_inherited(context)) @controller.protected def list_grants(self, context, user_id=None, group_id=None, @@ -766,7 +772,8 @@ class RoleV3(controller.V3Controller): self._require_user_xor_group(user_id, group_id) refs = self.identity_api.list_grants( - user_id, group_id, domain_id, project_id) + user_id, group_id, domain_id, project_id, + self._check_if_inherited(context)) return RoleV3.wrap_collection(context, refs) @controller.protected @@ -777,7 +784,8 @@ class RoleV3(controller.V3Controller): self._require_user_xor_group(user_id, group_id) self.identity_api.get_grant( - role_id, user_id, group_id, domain_id, project_id) + role_id, user_id, group_id, domain_id, project_id, + self._check_if_inherited(context)) @controller.protected def revoke_grant(self, context, role_id, user_id=None, group_id=None, @@ -787,7 +795,8 @@ class RoleV3(controller.V3Controller): self._require_user_xor_group(user_id, group_id) self.identity_api.delete_grant( - role_id, user_id, group_id, domain_id, project_id) + role_id, user_id, group_id, domain_id, project_id, + self._check_if_inherited(context)) # Now delete any tokens for this user or, in the case of a group, # tokens from all the uses who are members of this group. @@ -815,13 +824,55 @@ class RoleAssignmentV3(controller.V3Controller): pass def _format_entity(self, entity): + """Format an assignment entity for API response. + + The driver layer returns entities as dicts containing the ids of the + actor (e.g. user or group), target (e.g. domain or project) and role. + If it is an inherited role, then this is also indicated. Examples: + + {'user_id': user_id, + 'project_id': domain_id, + 'role_id': role_id} + + or, for an inherited role: + + {'user_id': user_id, + 'domain_id': domain_id, + 'role_id': role_id, + 'inherited_to_projects': true} + + This function maps this into the format to be returned via the API, + e.g. for the second example above: + + { + 'user': { + {'id': user_id} + }, + 'scope': { + 'domain': { + {'id': domain_id} + }, + 'OS-INHERIT:inherited_to': 'projects + }, + 'role': { + {'id': role_id} + }, + 'links': { + 'assignment': '/domains/domain_id/users/user_id/roles/' + 'role_id/inherited_to_projects' + } + } + + """ + formatted_entity = {} + suffix = "" if 'user_id' in entity: formatted_entity['user'] = {'id': entity['user_id']} - actor_link = '/users/%s' % entity['user_id'] + actor_link = 'users/%s' % entity['user_id'] if 'group_id' in entity: formatted_entity['group'] = {'id': entity['group_id']} - actor_link = '/groups/%s' % entity['group_id'] + actor_link = 'groups/%s' % entity['group_id'] if 'role_id' in entity: formatted_entity['role'] = {'id': entity['role_id']} if 'project_id' in entity: @@ -831,12 +882,21 @@ class RoleAssignmentV3(controller.V3Controller): if 'domain_id' in entity: formatted_entity['scope'] = ( {'domain': {'id': entity['domain_id']}}) - target_link = '/domains/%s' % entity['domain_id'] - + if 'inherited_to_projects' in entity: + formatted_entity['scope']['OS-INHERIT:inherited_to'] = ( + 'projects') + target_link = '/OS-INHERIT/domains/%s' % entity['domain_id'] + suffix = '/inherited_to_projects' + else: + target_link = '/domains/%s' % entity['domain_id'] formatted_entity.setdefault('links', {}) formatted_entity['links']['assignment'] = ( - self.base_url(target_link + actor_link + - '/roles/%s' % entity['role_id'])) + self.base_url('%(target)s/%(actor)s/roles/%(role)s%(suffix)s' % { + 'target': target_link, + 'actor': actor_link, + 'role': entity['role_id'], + 'suffix': suffix})) + return formatted_entity def _expand_indirect_assignments(self, refs): @@ -846,6 +906,10 @@ class RoleAssignmentV3(controller.V3Controller): entity for each member of that group, and then remove the group assignment entity itself from the list. + If the OS-INHERIT extension is enabled, then honor any inherited + roles on the domain by creating the equivalent on all projects + owned by the domain. + For any new entity created by virtue of group membership, add in an additional link to that membership. @@ -881,7 +945,8 @@ class RoleAssignmentV3(controller.V3Controller): 'role': ref.get('role_id')}) return members - def _build_equivalent_user_assignment(user, group_id, template): + def _build_user_assignment_equivalent_of_group( + user, group_id, template): """Create a user assignment equivalent to the group one. The template has had the 'group' entity removed, so @@ -900,33 +965,112 @@ class RoleAssignmentV3(controller.V3Controller): '/projects/%s' % scope['project']['id']) user_entry['links']['assignment'] = ( self.base_url('%s/users/%s/roles/%s' % - (target_link, m['id'], + (target_link, user['id'], user_entry['role']['id']))) user_entry['links']['membership'] = ( self.base_url('/groups/%s/users/%s' % (group_id, user['id']))) return user_entry - # Scan the list of entities for any group assignments, expanding - # them into equivalent user entities. Due to potential large - # expansion of group entities, rather than modify the + def _build_project_equivalent_of_user_domain_role( + project_id, domain_id, template): + """Create a user project assignment equivalent to the domain one. + + The template has had the 'domain' entity removed, so + substitute a 'project' one, modifying the 'assignment' link + to match. + + """ + project_entry = copy.deepcopy(template) + project_entry['scope']['project'] = {'id': project_id} + project_entry['links']['assignment'] = ( + self.base_url( + '/OS-INHERIT/domains/%s/users/%s/roles/%s' + '/inherited_to_projects' % ( + domain_id, project_entry['user']['id'], + project_entry['role']['id']))) + return project_entry + + def _build_project_equivalent_of_group_domain_role( + user_id, group_id, project_id, domain_id, template): + """Create a user project equivalent to the domain group one. + + The template has had the 'domain' and 'group' entities removed, so + substitute a 'user-project' one, modifying the 'assignment' link + to match. + + """ + project_entry = copy.deepcopy(template) + project_entry['user'] = {'id': user_id} + project_entry['scope']['project'] = {'id': project_id} + project_entry['links']['assignment'] = ( + self.base_url('/OS-INHERIT/domains/%s/groups/%s/roles/%s' + '/inherited_to_projects' % ( + domain_id, group_id, + project_entry['role']['id']))) + project_entry['links']['membership'] = ( + self.base_url('/groups/%s/users/%s' % + (group_id, user_id))) + return project_entry + + # Scan the list of entities for any assignments that need to be + # expanded. + # + # If the OS-INERIT extension is enabled, the refs lists may + # contain roles to be inherited from domain to project, so expand + # these as well into project equivalents + # + # For any regular group entries, expand these into user entries based + # on membership of that group. + # + # Due to the potentially large expansions, rather than modify the # list we are enumerating, we build a new one as we go. + # + new_refs = [] for r in refs: - if 'group' in r: - # As it is a group role assignment, first get the list of - # members. - + if 'OS-INHERIT:inherited_to' in r['scope']: + # It's an inherited domain role - so get the list of projects + # owned by this domain. A domain scope is guaranteed since we + # checked this when we built the refs list + project_ids = ( + [x['id'] for x in self.assignment_api.list_projects( + r['scope']['domain']['id'])]) + base_entry = copy.deepcopy(r) + domain_id = base_entry['scope']['domain']['id'] + base_entry['scope'].pop('domain') + # For each project, create an equivalent role assignment + for p in project_ids: + # If it's a group assignment, then create equivalent user + # roles based on membership of the group + if 'group' in base_entry: + members = _get_group_members(base_entry) + sub_entry = copy.deepcopy(base_entry) + group_id = sub_entry['group']['id'] + sub_entry.pop('group') + for m in members: + new_entry = ( + _build_project_equivalent_of_group_domain_role( + m['id'], group_id, p, + domain_id, sub_entry)) + new_refs.append(new_entry) + else: + new_entry = ( + _build_project_equivalent_of_user_domain_role( + p, domain_id, base_entry)) + new_refs.append(new_entry) + elif 'group' in r: + # It's a non-inherited group role assignment, so get the list + # of members. members = _get_group_members(r) # Now replace that group role assignment entry with an # equivalent user role assignment for each of the group members - base_entry = copy.deepcopy(r) group_id = base_entry['group']['id'] base_entry.pop('group') for m in members: - user_entry = _build_equivalent_user_assignment( + user_entry = _build_user_assignment_equivalent_of_group( m, group_id, base_entry) new_refs.append(user_entry) else: @@ -954,9 +1098,16 @@ class RoleAssignmentV3(controller.V3Controller): val = True return val + def _filter_inherited(self, entry): + if ('inherited_to_projects' in entry and + not CONF.os_inherit.enabled): + return False + else: + return True + @controller.filterprotected('group.id', 'role.id', 'scope.domain.id', 'scope.project.id', - 'user.id') + 'scope.OS-INHERIT:inherited_to', 'user.id') def list_role_assignments(self, context, filters): # TODO(henry-nash): This implementation uses the standard filtering @@ -966,7 +1117,9 @@ class RoleAssignmentV3(controller.V3Controller): # kept a minimum. refs = self.identity_api.list_role_assignments() - formatted_refs = [self._format_entity(x) for x in refs] + formatted_refs = ( + [self._format_entity(x) for x in refs + if self._filter_inherited(x)]) if ('effective' in context['query_string'] and self._query_filter_is_true( diff --git a/keystone/identity/core.py b/keystone/identity/core.py index 1e2ed733..d04902ae 100644 --- a/keystone/identity/core.py +++ b/keystone/identity/core.py @@ -16,6 +16,7 @@ """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 @@ -60,17 +61,12 @@ class Manager(manager.Manager): """ - def __init__(self): + def __init__(self, assignment_api=None): super(Manager, self).__init__(CONF.identity.driver) - - def authenticate(self, user_id=None, tenant_id=None, password=None): - """Authenticate a given user and password and - authorize them for a tenant. - :returns: (user_ref, tenant_ref, metadata_ref) - :raises: AssertionError - """ - user_ref = self.driver.authenticate_user(user_id, password) - return self.driver.authorize_for_project(user_ref, tenant_id) + if assignment_api is None: + assignment_api = assignment.Manager(self) + self.assignment = assignment_api + self.driver.assignment = assignment_api def create_user(self, user_id, user_ref): user = user_ref.copy() @@ -97,326 +93,133 @@ class Manager(manager.Manager): tenant.setdefault('enabled', True) tenant['enabled'] = clean.project_enabled(tenant['enabled']) tenant.setdefault('description', '') - return self.driver.create_project(tenant_id, tenant) + return self.assignment_api.create_project(tenant_id, tenant) def update_project(self, tenant_id, tenant_ref): tenant = tenant_ref.copy() if 'enabled' in tenant: tenant['enabled'] = clean.project_enabled(tenant['enabled']) - return self.driver.update_project(tenant_id, tenant) - - -class Driver(object): - """Interface description for an Identity driver.""" - - def authenticate_user(self, user_id, password): - """Authenticate a given user and password. - :returns: user_ref - :raises: AssertionError - """ - raise exception.NotImplemented() - - def authorize_for_project(self, tenant_id, user_ref): - """Authenticate a given user for a tenant. - :returns: (user_ref, tenant_ref, metadata_ref) - :raises: AssertionError - """ - raise exception.NotImplemented() + return self.assignment_api.update_project(tenant_id, tenant) def get_project_by_name(self, tenant_name, domain_id): - """Get a tenant by name. - - :returns: tenant_ref - :raises: keystone.exception.ProjectNotFound - - """ - raise exception.NotImplemented() - - def get_user_by_name(self, user_name, domain_id): - """Get a user by name. - - :returns: user_ref - :raises: keystone.exception.UserNotFound - - """ - raise exception.NotImplemented() - - def add_user_to_project(self, tenant_id, user_id): - """Add user to a tenant by creating a default role relationship. + return self.assignment.get_project_by_name(tenant_name, domain_id) - :raises: keystone.exception.ProjectNotFound, - keystone.exception.UserNotFound + def get_project(self, tenant_id): + return self.assignment.get_project(tenant_id) - """ - self.add_role_to_user_and_project(user_id, - tenant_id, - config.CONF.member_role_id) + def list_projects(self, domain_id=None): + return self.assignment.list_projects(domain_id) - def remove_user_from_project(self, tenant_id, user_id): - """Remove user from a tenant - - :raises: keystone.exception.ProjectNotFound, - keystone.exception.UserNotFound - - """ - roles = self.get_roles_for_user_and_project(user_id, tenant_id) - if not roles: - raise exception.NotFound(tenant_id) - for role_id in roles: - self.remove_role_from_user_and_project(user_id, tenant_id, role_id) - - def get_project_users(self, tenant_id): - """Lists all users with a relationship to the specified project. - - :returns: a list of user_refs or an empty set. - :raises: keystone.exception.ProjectNotFound + def get_role(self, role_id): + return self.assignment.get_role(role_id) - """ - raise exception.NotImplemented() + def list_roles(self): + return self.assignment.list_roles() def get_projects_for_user(self, user_id): - """Get the tenants associated with a given user. - - :returns: a list of tenant_id's. - :raises: keystone.exception.UserNotFound + return self.assignment.get_projects_for_user(user_id) - """ - raise exception.NotImplemented() + def get_project_users(self, tenant_id): + return self.assignment.get_project_users(tenant_id) def get_roles_for_user_and_project(self, user_id, tenant_id): - """Get the roles associated with a user within given tenant. - - This includes roles directly assigned to the user on the - project, as well as those by virtue of group membership. - - :returns: a list of role ids. - :raises: keystone.exception.UserNotFound, - keystone.exception.ProjectNotFound - - """ - def _get_group_project_roles(user_id, tenant_id): - role_list = [] - group_refs = self.list_groups_for_user(user_id=user_id) - for x in group_refs: - try: - metadata_ref = self.get_metadata(group_id=x['id'], - tenant_id=tenant_id) - role_list += metadata_ref.get('roles', []) - except exception.MetadataNotFound: - # no group grant, skip - pass - return role_list - - def _get_user_project_roles(user_id, tenant_id): - metadata_ref = {} - try: - metadata_ref = self.get_metadata(user_id=user_id, - tenant_id=tenant_id) - except exception.MetadataNotFound: - pass - return metadata_ref.get('roles', []) - - self.get_user(user_id) - self.get_project(tenant_id) - user_role_list = _get_user_project_roles(user_id, tenant_id) - group_role_list = _get_group_project_roles(user_id, tenant_id) - # Use set() to process the list to remove any duplicates - return list(set(user_role_list + group_role_list)) + return self.assignment.get_roles_for_user_and_project(user_id, + tenant_id) def get_roles_for_user_and_domain(self, user_id, domain_id): - """Get the roles associated with a user within given domain. - - This includes roles directly assigned to the user on the - domain, as well as those by virtue of group membership. - - :returns: a list of role ids. - :raises: keystone.exception.UserNotFound, - keystone.exception.DomainNotFound - - """ - - def _get_group_domain_roles(user_id, domain_id): - role_list = [] - group_refs = self.list_groups_for_user(user_id=user_id) - for x in group_refs: - try: - metadata_ref = self.get_metadata(group_id=x['id'], - domain_id=domain_id) - role_list += metadata_ref.get('roles', []) - except (exception.MetadataNotFound, exception.NotImplemented): - # MetadataNotFound implies no group grant, so skip. - # Ignore NotImplemented since not all backends support - # domains. - pass - return role_list - - def _get_user_domain_roles(user_id, domain_id): - metadata_ref = {} - try: - metadata_ref = self.get_metadata(user_id=user_id, - domain_id=domain_id) - except (exception.MetadataNotFound, exception.NotImplemented): - # MetadataNotFound implies no user grants. - # Ignore NotImplemented since not all backends support - # domains. - pass - return metadata_ref.get('roles', []) - - self.get_user(user_id) - self.get_domain(domain_id) - user_role_list = _get_user_domain_roles(user_id, domain_id) - group_role_list = _get_group_domain_roles(user_id, domain_id) - # Use set() to process the list to remove any duplicates - return list(set(user_role_list + group_role_list)) - - def add_role_to_user_and_project(self, user_id, tenant_id, role_id): - """Add a role to a user within given tenant. - - :raises: keystone.exception.UserNotFound, - keystone.exception.ProjectNotFound, - keystone.exception.RoleNotFound - """ - raise exception.NotImplemented() - - def remove_role_from_user_and_project(self, user_id, tenant_id, role_id): - """Remove a role from a user within given tenant. - - :raises: keystone.exception.UserNotFound, - keystone.exception.ProjectNotFound, - keystone.exception.RoleNotFound - - """ - raise exception.NotImplemented() - - # metadata crud - def get_metadata(self, user_id=None, tenant_id=None, - domain_id=None, group_id=None): - """Gets the metadata for the specified user/group on project/domain. + return (self.assignment.get_roles_for_user_and_domain + (user_id, domain_id)) - :raises: keystone.exception.MetadataNotFound - :returns: metadata - - """ - raise exception.NotImplemented() + def _subrole_id_to_dn(self, role_id, tenant_id): + return self.assignment._subrole_id_to_dn(role_id, tenant_id) - def create_metadata(self, user_id, tenant_id, metadata, - domain_id=None, group_id=None): - """Creates the metadata for the specified user/group on project/domain. + def add_role_to_user_and_project(self, user_id, + tenant_id, role_id): + return (self.assignment_api.add_role_to_user_and_project + (user_id, tenant_id, role_id)) - :returns: metadata created + def create_role(self, role_id, role): + return self.assignment.create_role(role_id, role) - """ - raise exception.NotImplemented() + def delete_role(self, role_id): + return self.assignment.delete_role(role_id) - def update_metadata(self, user_id, tenant_id, metadata, - domain_id=None, group_id=None): - """Updates the metadata for the specified user/group on project/domain. + def delete_project(self, tenant_id): + return self.assignment.delete_project(tenant_id) - :returns: metadata updated + def remove_role_from_user_and_project(self, user_id, + tenant_id, role_id): + return (self.assignment_api.remove_role_from_user_and_project + (user_id, tenant_id, role_id)) - """ - raise exception.NotImplemented() + def update_role(self, role_id, role): + return self.assignment.update_role(role_id, role) + + def create_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + return (self.assignment.create_grant + (role_id, user_id, group_id, domain_id, project_id, + inherited_to_projects)) + + def list_grants(self, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + return (self.assignment.list_grants + (user_id, group_id, domain_id, project_id, + inherited_to_projects)) + + def get_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + return (self.assignment.get_grant + (role_id, user_id, group_id, domain_id, project_id, + inherited_to_projects)) + + def delete_grant(self, role_id, user_id=None, group_id=None, + domain_id=None, project_id=None, + inherited_to_projects=False): + return (self.assignment.delete_grant + (role_id, user_id, group_id, domain_id, project_id, + inherited_to_projects)) - # domain crud def create_domain(self, domain_id, domain): - """Creates a new domain. - - :raises: keystone.exception.Conflict - - """ - raise exception.NotImplemented() - - def list_domains(self): - """List all domains in the system. - - :returns: a list of domain_refs or an empty list. - - """ - raise exception.NotImplemented() - - def get_domain(self, domain_id): - """Get a domain by ID. - - :returns: domain_ref - :raises: keystone.exception.DomainNotFound - - """ - raise exception.NotImplemented() + return self.assignment.create_domain(domain_id, domain) def get_domain_by_name(self, domain_name): - """Get a domain by name. - - :returns: domain_ref - :raises: keystone.exception.DomainNotFound + return self.assignment.get_domain_by_name(domain_name) - """ - raise exception.NotImplemented() + def get_domain(self, domain_id): + return self.assignment.get_domain(domain_id) def update_domain(self, domain_id, domain): - """Updates an existing domain. - - :raises: keystone.exception.DomainNotFound, - keystone.exception.Conflict - - """ - raise exception.NotImplemented() + return self.assignment.update_domain(domain_id, domain) def delete_domain(self, domain_id): - """Deletes an existing domain. - - :raises: keystone.exception.DomainNotFound - - """ - raise exception.NotImplemented() - - # project crud - def create_project(self, project_id, project): - """Creates a new project. - - :raises: keystone.exception.Conflict + return self.assignment.delete_domain(domain_id) - """ - raise exception.NotImplemented() - - def list_projects(self): - """List all projects in the system. - - :returns: a list of project_refs or an empty list. - - """ - raise exception.NotImplemented() + def list_domains(self): + return self.assignment.list_domains() def list_user_projects(self, user_id): - """List all projects associated with a given user. - - :returns: a list of project_refs or an empty list. - - """ - raise exception.NotImplemented() + return self.assignment.list_user_projects(user_id) - def get_project(self, project_id): - """Get a project by ID. - - :returns: project_ref - :raises: keystone.exception.ProjectNotFound - - """ - raise exception.NotImplemented() - - def update_project(self, project_id, project): - """Updates an existing project. - - :raises: keystone.exception.ProjectNotFound, - keystone.exception.Conflict + def add_user_to_project(self, tenant_id, user_id): + return self.assignment.add_user_to_project(tenant_id, user_id) - """ - raise exception.NotImplemented() + def remove_user_from_project(self, tenant_id, user_id): + return self.assignment.remove_user_from_project(tenant_id, user_id) - def delete_project(self, project_id): - """Deletes an existing project. + def list_role_assignments(self): + return self.assignment_api.list_role_assignments() - :raises: keystone.exception.ProjectNotFound +class Driver(object): + """Interface description for an Identity driver.""" + def authenticate_user(self, user_id, password): + """Authenticate a given user and password. + :returns: user_ref + :raises: AssertionError """ raise exception.NotImplemented() @@ -498,54 +301,15 @@ class Driver(object): """ raise exception.NotImplemented() - # role crud - - def create_role(self, role_id, role): - """Creates a new role. - - :raises: keystone.exception.Conflict - - """ - raise exception.NotImplemented() - - def list_roles(self): - """List all roles in the system. - - :returns: a list of role_refs or an empty list. - - """ - raise exception.NotImplemented() - - def get_role(self, role_id): - """Get a role by ID. - - :returns: role_ref - :raises: keystone.exception.RoleNotFound - - """ - raise exception.NotImplemented() - - def update_role(self, role_id, role): - """Updates an existing role. - - :raises: keystone.exception.RoleNotFound, - keystone.exception.Conflict - - """ - raise exception.NotImplemented() - - def delete_role(self, role_id): - """Deletes an existing role. + def get_user_by_name(self, user_name, domain_id): + """Get a user by name. - :raises: keystone.exception.RoleNotFound + :returns: user_ref + :raises: keystone.exception.UserNotFound """ raise exception.NotImplemented() - def list_role_assignments(self): - - raise exception.NotImplemented() - # group crud def create_group(self, group_id, group): @@ -597,3 +361,7 @@ class Driver(object): """ raise exception.NotImplemented() + + #end of identity + + # Assignments diff --git a/keystone/identity/routers.py b/keystone/identity/routers.py index ab71eb4f..5f236842 100644 --- a/keystone/identity/routers.py +++ b/keystone/identity/routers.py @@ -16,6 +16,7 @@ """WSGI Routers for the Identity service.""" from keystone.common import router from keystone.common import wsgi +from keystone import config from keystone.identity import controllers @@ -174,6 +175,47 @@ def append_v3_routers(mapper, routers): action='revoke_grant', conditions=dict(method=['DELETE'])) + if config.CONF.os_inherit.enabled: + mapper.connect(('/OS-INHERIT/domains/{domain_id}/users/{user_id}' + '/roles/{role_id}/inherited_to_projects'), + controller=role_controller, + action='create_grant', + conditions=dict(method=['PUT'])) + mapper.connect(('/OS-INHERIT/domains/{domain_id}/groups/{group_id}' + '/roles/{role_id}/inherited_to_projects'), + controller=role_controller, + action='create_grant', + conditions=dict(method=['PUT'])) + mapper.connect(('/OS-INHERIT/domains/{domain_id}/users/{user_id}' + '/roles/{role_id}/inherited_to_projects'), + controller=role_controller, + action='check_grant', + conditions=dict(method=['HEAD'])) + mapper.connect(('/OS-INHERIT/domains/{domain_id}/groups/{group_id}' + '/roles/{role_id}/inherited_to_projects'), + controller=role_controller, + action='check_grant', + conditions=dict(method=['HEAD'])) + mapper.connect(('/OS-INHERIT/domains/{domain_id}/users/{user_id}' + '/roles/inherited_to_projects'), + controller=role_controller, + action='list_grants', + conditions=dict(method=['GET'])) + mapper.connect(('/OS-INHERIT/domains/{domain_id}/groups/{group_id}' + '/roles/inherited_to_projects'), + controller=role_controller, + action='list_grants', + conditions=dict(method=['GET'])) + mapper.connect(('/OS-INHERIT/domains/{domain_id}/users/{user_id}' + '/roles/{role_id}/inherited_to_projects'), + controller=role_controller, + action='revoke_grant', + conditions=dict(method=['DELETE'])) + mapper.connect(('/OS-INHERIT/domains/{domain_id}/groups/{group_id}' + '/roles/{role_id}/inherited_to_projects'), + controller=role_controller, + action='revoke_grant', + conditions=dict(method=['DELETE'])) routers.append( router.Router(controllers.RoleAssignmentV3(), 'role_assignments', 'role_assignment')) diff --git a/keystone/locale/bg_BG/LC_MESSAGES/keystone.po b/keystone/locale/bg_BG/LC_MESSAGES/keystone.po index 77677455..ef61d876 100644 --- a/keystone/locale/bg_BG/LC_MESSAGES/keystone.po +++ b/keystone/locale/bg_BG/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-05-17 16:06+0000\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Language-Team: Bulgarian (Bulgaria) " @@ -38,11 +38,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -179,7 +195,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -195,12 +211,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -247,13 +263,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -441,12 +457,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -455,37 +475,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -497,41 +517,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -552,15 +557,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -586,10 +596,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/ca/LC_MESSAGES/keystone.po b/keystone/locale/ca/LC_MESSAGES/keystone.po index 0a5f732e..3d83598f 100644 --- a/keystone/locale/ca/LC_MESSAGES/keystone.po +++ b/keystone/locale/ca/LC_MESSAGES/keystone.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2012-11-03 03:08+0000\n" "Last-Translator: Sergi Almacellas <pokoli@gmail.com>\n" "Language-Team: ca <LL@li.org>\n" @@ -38,11 +38,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,12 +103,12 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 #, fuzzy msgid "Unable to sign token." msgstr "No es pot afegir el token a la llista d'usuaris." -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -180,7 +196,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -196,12 +212,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -248,13 +264,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -442,12 +458,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -456,37 +476,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -498,41 +518,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -553,15 +558,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -587,10 +597,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "No es pot afegir el token a la llista de revocats." -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/cs/LC_MESSAGES/keystone.po b/keystone/locale/cs/LC_MESSAGES/keystone.po index ac761b39..60948581 100644 --- a/keystone/locale/cs/LC_MESSAGES/keystone.po +++ b/keystone/locale/cs/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-05-17 16:06+0000\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Language-Team: Czech " @@ -38,11 +38,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -179,7 +195,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -195,12 +211,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -247,13 +263,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -441,12 +457,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -455,37 +475,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -497,41 +517,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -552,15 +557,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -586,10 +596,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/da/LC_MESSAGES/keystone.po b/keystone/locale/da/LC_MESSAGES/keystone.po index 930adae2..ed07f63b 100644 --- a/keystone/locale/da/LC_MESSAGES/keystone.po +++ b/keystone/locale/da/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-05-17 16:06+0000\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Language-Team: Danish " @@ -38,11 +38,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -179,7 +195,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -195,12 +211,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -247,13 +263,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -441,12 +457,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -455,37 +475,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -497,41 +517,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -552,15 +557,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -586,10 +596,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/de/LC_MESSAGES/keystone.po b/keystone/locale/de/LC_MESSAGES/keystone.po index d4a7b49e..b47004fb 100644 --- a/keystone/locale/de/LC_MESSAGES/keystone.po +++ b/keystone/locale/de/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-05-22 03:45+0000\n" "Last-Translator: daisy.ycguo <daisy.ycguo@gmail.com>\n" "Language-Team: German " @@ -38,11 +38,27 @@ msgstr "%(property_name)s sollte nicht größer als %(max_length)s Zeichen sein. msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "%s wurde nicht ausgecheckt" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "Rolle %s nicht gefunden" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "Nicht gewährte Rolle kann nicht entfernt werden, %s" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "Benutzer hat keinen Zugriff auf Projekt" msgid "User have no access to domain" msgstr "Benutzer hat keinen Zugriff auf Domäne" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "Token kann nicht unterzeichnet werden." -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -189,7 +205,7 @@ msgstr "" "Rollenbasierte Zugriffssteuerung: Hinzufügen von Abfragefilterparametern " "(%s)" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "Ungültiges Token in 'normalize_domain_id'" @@ -205,12 +221,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "arg_dict: %s" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, fuzzy, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "Autorisierung fehlgeschlagen. %s von %s" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "Die Ressource konnte nicht gefunden werden." @@ -257,13 +273,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "Doppelter Name, %s." -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "Doppelte ID, %s." @@ -457,14 +473,18 @@ msgstr "FakeLdap-Suche fehlgeschlagen: dn für 'SCOPE_BASE' nicht gefunden" msgid "Search scope %s not implemented." msgstr "Suchbereich %s nicht implementiert." -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" "Es wurde festgestellt, dass keine Verbindung zum mysql-Server mehr " "vorhanden ist: %s" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "EC2-Berechtigungsnachweis kann nicht migriert werden: %s" @@ -473,37 +493,37 @@ msgstr "EC2-Berechtigungsnachweis kann nicht migriert werden: %s" msgid "version should be an integer" msgstr "Version sollte eine Ganzzahl sein" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "Nutzer %s erstellen" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "Benutzer %s erstellen" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, fuzzy, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "Benutzer %s zu Nutzer %s hinzufügen" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "Vorhandene Rolle %s ignorieren" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "Rolle %s erstellen" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, fuzzy, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "Rolle %s Benutzer %s auf Nutzer %s zuweisen" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, fuzzy, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "Erstellen von EC2-Berechtigungsnachweis für Benutzer %s und Nutzer %s" @@ -515,41 +535,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "Benutzer nicht in Gruppe gefunden" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "Rolle %s nicht gefunden" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "Änderung von Namen wird von LDAP nicht unterstützt" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, fuzzy, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "Benutzer %s ist bereits Mitglied der Gruppe %s." -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "Nicht gewährte Rolle kann nicht entfernt werden, %s" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -570,15 +575,20 @@ msgstr "Regel '%(rule)r' konnte nicht verstanden werden" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "Token gehört nicht zu angegebenem Nutzer." -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "Nicht-Standard-Domäne wird nicht unterstützt" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "Bereichsorientiertes Token der Domäne wird nicht unterstützt" @@ -604,10 +614,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "Token kann nicht zu Widerrufsliste hinzugefügt werden." -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/es/LC_MESSAGES/keystone.po b/keystone/locale/es/LC_MESSAGES/keystone.po index 308213c3..7f1d56a4 100644 --- a/keystone/locale/es/LC_MESSAGES/keystone.po +++ b/keystone/locale/es/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-05-21 09:13+0000\n" "Last-Translator: daisy.ycguo <daisy.ycguo@gmail.com>\n" "Language-Team: Spanish " @@ -38,11 +38,27 @@ msgstr "%(property_name)s no debe tener más de %(max_length)s caracteres." msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "No se ha podido extraer %s" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "No se ha encontrado el rol %s" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "No se puede eliminar un rol que no se ha otorgado, %s" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "El usuario no tiene acceso al proyecto" msgid "User have no access to domain" msgstr "El usuario no tiene acceso al dominio" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "No se ha podido firmar la señal." -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -191,7 +207,7 @@ msgstr "RBAC: Autorización otorgada" msgid "RBAC: Adding query filter params (%s)" msgstr "RBAC: añadiendo parámetros de filtro de consultas (%s)" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "Señal no válida en normalize_domain_id" @@ -207,12 +223,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "arg_dict: %s" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, fuzzy, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "Ha fallado la autorización. %s de %s" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "El recurso no se ha podido encontrar." @@ -259,13 +275,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "Nombre duplicado, %s." -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "ID duplicado, %s." @@ -466,12 +482,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "Ámbito de búsqueda %s no implementado." -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "Se ha notificado que mysql server ha desaparecido: %s" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "No se puede migrar la credencial EC2: %s " @@ -480,37 +500,37 @@ msgstr "No se puede migrar la credencial EC2: %s " msgid "version should be an integer" msgstr "la versión debe ser un entero" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "Crear el arrendatario %s" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "Crear el usuario %s" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, fuzzy, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "Añadir el usuario %s al arrendatario %s" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "Ignorando el rol existente %s" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "Crear el rol %s" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, fuzzy, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "Asignar el rol %s al usuario %s en el arrendatario %s" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, fuzzy, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "Creando credencial ec2 para el usuario %s y el arrendatario %s" @@ -522,41 +542,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "Usuario no encontrado en grupo" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "No se ha encontrado el rol %s" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "LDAP no soporta el cambio de nombre" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, fuzzy, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "El usuario %s ya es miembro del grupo %s" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "No se puede eliminar un rol que no se ha otorgado, %s" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -577,15 +582,20 @@ msgstr "No se ha podido comprender la regla %(rule)r" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "La señal no pertenece al arrendatario especificado." -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "El dominio no predeterminado no está soportado" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "La señal con ámbito de dominio no está soportada" @@ -611,10 +621,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "No se puede añadir señal a lista de revocación. " -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/fi_FI/LC_MESSAGES/keystone.po b/keystone/locale/fi_FI/LC_MESSAGES/keystone.po index 13953bea..1f8b79e2 100644 --- a/keystone/locale/fi_FI/LC_MESSAGES/keystone.po +++ b/keystone/locale/fi_FI/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-05-17 16:06+0000\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Language-Team: Finnish (Finland) " @@ -38,11 +38,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -179,7 +195,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -195,12 +211,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -247,13 +263,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -441,12 +457,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -455,37 +475,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -497,41 +517,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -552,15 +557,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -586,10 +596,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/fr/LC_MESSAGES/keystone.po b/keystone/locale/fr/LC_MESSAGES/keystone.po index e41e0ec4..61e4adde 100644 --- a/keystone/locale/fr/LC_MESSAGES/keystone.po +++ b/keystone/locale/fr/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-05-17 16:06+0000\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Language-Team: French " @@ -38,11 +38,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -179,7 +195,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -195,12 +211,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -247,13 +263,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -441,12 +457,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -455,37 +475,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -497,41 +517,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -552,15 +557,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -586,10 +596,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/hu/LC_MESSAGES/keystone.po b/keystone/locale/hu/LC_MESSAGES/keystone.po index 8d24f2a8..ca656d2e 100644 --- a/keystone/locale/hu/LC_MESSAGES/keystone.po +++ b/keystone/locale/hu/LC_MESSAGES/keystone.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2012-12-15 14:14+0000\n" "Last-Translator: kelemeng <kelemeng@gnome.hu>\n" "Language-Team: hu <LL@li.org>\n" @@ -39,11 +39,27 @@ msgstr "%(property_name)s nem lehet több, mint %(max_length)s karakter." msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "%(property_name)s nem %(display_expected_type)s" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "Nem sikerült %s kiiktatása" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -88,12 +104,12 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 #, fuzzy msgid "Unable to sign token." msgstr "Nem vehető fel a token felhasználólistája." -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -183,7 +199,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -199,12 +215,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "arg_dict: %s" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, fuzzy, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "Hitelesítés sikertelen. %s innen: %s" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "Az erőforrás nem található." @@ -251,13 +267,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "Többszörös név: %s." -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "Többszörös azonosító: %s." @@ -449,12 +465,16 @@ msgstr "FakeLdap keresés sikertelen: a dn nem található a SCOPE_BASE-hez" msgid "Search scope %s not implemented." msgstr "A(z) %s keresési hatókör nincs megvalósítva." -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "A kapott MySQL szerver eltűnt: %s" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "Nem migrálhatók az EC2 hitelesítési adatok: %s" @@ -463,37 +483,37 @@ msgstr "Nem migrálhatók az EC2 hitelesítési adatok: %s" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "%s bérlő létrehozása" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "%s felhasználó létrehozása" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, fuzzy, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "%s felhasználó hozzáadása %s bérlőhöz" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "Meglévő %s szerep figyelmen kívül hagyása" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "%s szerep létrehozása" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, fuzzy, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "%s szerep hozzárendelése %s felhasználóhoz %s bérlőben" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, fuzzy, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "EC2 hitelesítési adatok létrehozása %s felhasználóhoz és %s bérlőhöz" @@ -505,41 +525,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -560,15 +565,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -594,10 +604,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "A token nem adható a visszavonási listához." -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/it/LC_MESSAGES/keystone.po b/keystone/locale/it/LC_MESSAGES/keystone.po index 92701c53..790166b4 100644 --- a/keystone/locale/it/LC_MESSAGES/keystone.po +++ b/keystone/locale/it/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-05-17 16:06+0000\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Language-Team: Italian " @@ -38,11 +38,27 @@ msgstr "%(property_name)s non può essere superiore a %(max_length)s caratteri." msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "Impossibile eseguire il checkout %s" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "Ruolo %s non trovato" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "Impossibile rimuovere un ruolo che non è stato concesso, %s" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "L'utente non ha accesso al progetto" msgid "User have no access to domain" msgstr "L'utente non ha accesso al dominio" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "Impossibile firmare il token." -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -187,7 +203,7 @@ msgstr "RBAC: autorizzazione concessa" msgid "RBAC: Adding query filter params (%s)" msgstr "RBAC: aggiunta parametri del filtro della query (%s)" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "Token non valido in normalize_domain_id" @@ -203,12 +219,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "arg_dict: %s" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, fuzzy, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "Autorizzazione non riuscita. %s da %s" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "Impossibile trovare la risorsa." @@ -255,13 +271,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "Nome duplicato, %s." -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "ID duplicato, %s." @@ -455,12 +471,16 @@ msgstr "FakeLdap ricerca non riuscita: dn non trovato per SCOPE_BASE" msgid "Search scope %s not implemented." msgstr "Ambito di ricerca %s non implementato." -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "Ricevuto messaggio di interruzione della connessione del server mysql: %s" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "Impossibile migrare la credenziale EC2: %s" @@ -469,37 +489,37 @@ msgstr "Impossibile migrare la credenziale EC2: %s" msgid "version should be an integer" msgstr "la versione deve essere un numero intero" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "Crea tenant %s" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "Crea utente %s" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, fuzzy, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "Aggiungi utente %s al tenant %s" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "Il ruolo esistente viene ignorato %s" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "Crea ruolo %s" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, fuzzy, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "Assegna il ruolo %s all'utente %s nel tenant %s" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, fuzzy, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "Creazione credenziale ec2 per l'utente %s e del tenant %s" @@ -511,41 +531,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "Utente non trovato nel gruppo" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "Ruolo %s non trovato" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "Modifica nome non supportato da LDAP" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, fuzzy, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "L'utente %s è già membro del gruppo %s" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "Impossibile rimuovere un ruolo che non è stato concesso, %s" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -566,15 +571,20 @@ msgstr "Impossibile comprendere la regola %(rule)r" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "Il token non appartiene al tenant specificato." -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "Il dominio non predefinito non è supportato" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "L'ambito del dominio token non è supportato" @@ -600,10 +610,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "Impossibile aggiungere un token ad un elenco di revoca." -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/ja/LC_MESSAGES/keystone.po b/keystone/locale/ja/LC_MESSAGES/keystone.po index 783b63ff..bd31a1d3 100644 --- a/keystone/locale/ja/LC_MESSAGES/keystone.po +++ b/keystone/locale/ja/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2012-11-03 01:25+0000\n" "Last-Translator: Tomoyuki KATO <tomo@dream.daynight.jp>\n" "Language-Team: Japanese " @@ -38,11 +38,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,12 +103,12 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 #, fuzzy msgid "Unable to sign token." msgstr "ユーザーリストにトークンを追加できません。" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -180,7 +196,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -196,12 +212,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -248,13 +264,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -442,12 +458,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -456,37 +476,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -498,41 +518,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -553,15 +558,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -587,10 +597,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "失効リストにトークンを追加できません。" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/ka_GE/LC_MESSAGES/keystone.po b/keystone/locale/ka_GE/LC_MESSAGES/keystone.po index ee8c1f87..090bf2e2 100644 --- a/keystone/locale/ka_GE/LC_MESSAGES/keystone.po +++ b/keystone/locale/ka_GE/LC_MESSAGES/keystone.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-05-17 16:06+0000\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Language-Team: ka_GE <LL@li.org>\n" @@ -38,11 +38,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "როლი %s ვერ მოიძებნა" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "მომხმარებელს არ აქვს წვდომ msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -179,7 +195,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -195,12 +211,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -247,13 +263,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -441,12 +457,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -455,37 +475,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "მომხმარებლის შექმნა %s" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -497,41 +517,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "როლი %s ვერ მოიძებნა" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -552,15 +557,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -586,10 +596,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/keystone.pot b/keystone/locale/keystone.pot index 9184134a..b5773f2e 100644 --- a/keystone/locale/keystone.pot +++ b/keystone/locale/keystone.pot @@ -7,9 +7,9 @@ msgid "" msgstr "" "Project-Id-Version: keystone " -"jenkins.keystone.propose.translation.update.255\n" +"jenkins.keystone.propose.translation.update.258\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -38,11 +38,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -179,7 +195,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -195,12 +211,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -247,13 +263,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -441,12 +457,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -455,37 +475,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -497,41 +517,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -552,15 +557,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" diff --git a/keystone/locale/ko_KR/LC_MESSAGES/keystone.po b/keystone/locale/ko_KR/LC_MESSAGES/keystone.po index b4592528..47659fa0 100644 --- a/keystone/locale/ko_KR/LC_MESSAGES/keystone.po +++ b/keystone/locale/ko_KR/LC_MESSAGES/keystone.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-03-21 18:34+0000\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Language-Team: ko_KR <LL@li.org>\n" @@ -38,11 +38,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -179,7 +195,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -195,12 +211,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -247,13 +263,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -441,12 +457,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -455,37 +475,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "Role %s 생성" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -497,41 +517,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -552,15 +557,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -586,10 +596,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/pl_PL/LC_MESSAGES/keystone.po b/keystone/locale/pl_PL/LC_MESSAGES/keystone.po index b3c338c9..960d8df6 100644 --- a/keystone/locale/pl_PL/LC_MESSAGES/keystone.po +++ b/keystone/locale/pl_PL/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-06-28 06:06+0000\n" "Last-Translator: daisy.ycguo <daisy.ycguo@gmail.com>\n" "Language-Team: Polish (Poland) " @@ -39,11 +39,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -88,11 +104,11 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -180,7 +196,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -196,12 +212,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -248,13 +264,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -442,12 +458,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -456,37 +476,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -498,41 +518,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -553,15 +558,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" diff --git a/keystone/locale/pt_BR/LC_MESSAGES/keystone.po b/keystone/locale/pt_BR/LC_MESSAGES/keystone.po index be8cdddb..fed29df8 100644 --- a/keystone/locale/pt_BR/LC_MESSAGES/keystone.po +++ b/keystone/locale/pt_BR/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2012-11-02 18:30+0000\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: Portuguese (Brazil) " @@ -38,11 +38,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -179,7 +195,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -195,12 +211,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -247,13 +263,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -441,12 +457,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -455,37 +475,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -497,41 +517,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -552,15 +557,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -586,10 +596,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/ro/LC_MESSAGES/keystone.po b/keystone/locale/ro/LC_MESSAGES/keystone.po index 4b835f61..e1b5889a 100644 --- a/keystone/locale/ro/LC_MESSAGES/keystone.po +++ b/keystone/locale/ro/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-06-08 07:51+0000\n" "Last-Translator: daisy.ycguo <daisy.ycguo@gmail.com>\n" "Language-Team: Romanian " @@ -39,11 +39,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -88,11 +104,11 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -180,7 +196,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -196,12 +212,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -248,13 +264,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -442,12 +458,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -456,37 +476,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -498,41 +518,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -553,15 +558,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -587,10 +597,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/ru/LC_MESSAGES/keystone.po b/keystone/locale/ru/LC_MESSAGES/keystone.po index 70dfdaa9..5a1f5c36 100644 --- a/keystone/locale/ru/LC_MESSAGES/keystone.po +++ b/keystone/locale/ru/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-05-17 16:06+0000\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Language-Team: Russian " @@ -39,11 +39,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -88,11 +104,11 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -180,7 +196,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -196,12 +212,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -248,13 +264,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -442,12 +458,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -456,37 +476,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -498,41 +518,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -553,15 +558,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -587,10 +597,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/sl_SI/LC_MESSAGES/keystone.po b/keystone/locale/sl_SI/LC_MESSAGES/keystone.po index 422278d7..4066e381 100644 --- a/keystone/locale/sl_SI/LC_MESSAGES/keystone.po +++ b/keystone/locale/sl_SI/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-06-28 06:06+0000\n" "Last-Translator: daisy.ycguo <daisy.ycguo@gmail.com>\n" "Language-Team: Slovenian (Slovenia) " @@ -39,11 +39,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -88,11 +104,11 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -180,7 +196,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -196,12 +212,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -248,13 +264,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -442,12 +458,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -456,37 +476,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -498,41 +518,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -553,15 +558,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" diff --git a/keystone/locale/vi_VN/LC_MESSAGES/keystone.po b/keystone/locale/vi_VN/LC_MESSAGES/keystone.po index 1551e232..20fbd476 100644 --- a/keystone/locale/vi_VN/LC_MESSAGES/keystone.po +++ b/keystone/locale/vi_VN/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-05-17 16:06+0000\n" "Last-Translator: openstackjenkins <jenkins@openstack.org>\n" "Language-Team: Vietnamese (Viet Nam) " @@ -38,11 +38,27 @@ msgstr "" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "" msgid "User have no access to domain" msgstr "" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "" @@ -179,7 +195,7 @@ msgstr "" msgid "RBAC: Adding query filter params (%s)" msgstr "" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "" @@ -195,12 +211,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "" @@ -247,13 +263,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "" @@ -441,12 +457,16 @@ msgstr "" msgid "Search scope %s not implemented." msgstr "" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "" @@ -455,37 +475,37 @@ msgstr "" msgid "version should be an integer" msgstr "" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "" @@ -497,41 +517,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -552,15 +557,20 @@ msgstr "" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "" @@ -586,10 +596,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/zh_CN/LC_MESSAGES/keystone.po b/keystone/locale/zh_CN/LC_MESSAGES/keystone.po index 40d453ae..b3d557a1 100644 --- a/keystone/locale/zh_CN/LC_MESSAGES/keystone.po +++ b/keystone/locale/zh_CN/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-05-21 06:08+0000\n" "Last-Translator: daisy.ycguo <daisy.ycguo@gmail.com>\n" "Language-Team: Chinese (China) " @@ -38,11 +38,27 @@ msgstr "%(property_name)s 不应该超过 %(max_length)s 个字符。" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "未能检出 %s" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "找不到角色 %s" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "无法除去尚未授予的角色 %s" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "用户对项目没有任何访问权限" msgid "User have no access to domain" msgstr "用户对域没有任何访问权限" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "无法对令牌进行签名。" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "token_format 的值 %s 无效。允许值是 PKI 或 UUID。" @@ -179,7 +195,7 @@ msgstr "RBAC:已授予权限" msgid "RBAC: Adding query filter params (%s)" msgstr "RBAC:正在添加查询过滤器参数 (%s)" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "normalize_domain_id 中的令牌无效" @@ -195,12 +211,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "arg_dict:%s" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, fuzzy, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "授权失败。%s 来自 %s" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "找不到该资源。" @@ -247,13 +263,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "名称 %s 重复。" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "标识 %s 重复。" @@ -441,12 +457,16 @@ msgstr "FakeLdap search 失败:对于 SCOPE_BASE,找不到 dn" msgid "Search scope %s not implemented." msgstr "未实现搜索范围 %s。" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "mysql 服务器已不存在:%s" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "无法迁移 EC2 凭证:%s" @@ -455,37 +475,37 @@ msgstr "无法迁移 EC2 凭证:%s" msgid "version should be an integer" msgstr "版本应该为整数" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "请创建租户 %s" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "请创建用户 %s" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, fuzzy, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "请将用户 %s 添加至租户 %s" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "正在忽略现有角色 %s" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "请创建角色 %s" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, fuzzy, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "请将角色 %s 分配给用户 %s(在租户 %s 上)" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, fuzzy, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "正在为用户 %s 和租户 %s 创建 ec2 凭证" @@ -497,41 +517,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "在组中找不到用户" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "找不到角色 %s" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "更改名称不受 LDAP 支持" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, fuzzy, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "用户 %s 已是组 %s 的成员" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "无法除去尚未授予的角色 %s" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -552,15 +557,20 @@ msgstr "未能理解规则 %(rule)r" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "令牌不属于指定的租户。" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "非缺省域不受支持" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "作用域限定到域的令牌不受支持" @@ -586,10 +596,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "无法将令牌添加至撤销列表。" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/locale/zh_TW/LC_MESSAGES/keystone.po b/keystone/locale/zh_TW/LC_MESSAGES/keystone.po index f6c82401..ae04c15a 100644 --- a/keystone/locale/zh_TW/LC_MESSAGES/keystone.po +++ b/keystone/locale/zh_TW/LC_MESSAGES/keystone.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Keystone\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/keystone\n" -"POT-Creation-Date: 2013-07-08 17:06+0000\n" +"POT-Creation-Date: 2013-07-11 17:05+0000\n" "PO-Revision-Date: 2013-05-22 03:11+0000\n" "Last-Translator: daisy.ycguo <daisy.ycguo@gmail.com>\n" "Language-Team: Chinese (Taiwan) " @@ -38,11 +38,27 @@ msgstr "%(property_name)s 不應超過 %(max_length)s 個字元。" msgid "%(property_name)s is not a %(display_expected_type)s" msgstr "" -#: keystone/test.py:105 +#: keystone/test.py:106 #, python-format msgid "Failed to checkout %s" msgstr "無法移出 %s" +#: keystone/assignment/backends/ldap.py:110 +#, python-format +msgid "Expected dict or list: %s" +msgstr "" + +#: keystone/assignment/backends/ldap.py:456 +#: keystone/identity/backends/ldap.py:433 +#, python-format +msgid "Role %s not found" +msgstr "找不到角色 %s" + +#: keystone/assignment/backends/sql.py:244 +#, python-format +msgid "Cannot remove role that has not been granted, %s" +msgstr "無法移除尚未授權的角色,%s" + #: keystone/auth/controllers.py:72 #, python-format msgid "Project is disabled: %s" @@ -87,11 +103,11 @@ msgstr "使用者無法存取專案" msgid "User have no access to domain" msgstr "使用者無法存取網域" -#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:121 +#: keystone/auth/token_factory.py:311 keystone/token/controllers.py:119 msgid "Unable to sign token." msgstr "無法簽署記號。" -#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:124 +#: keystone/auth/token_factory.py:314 keystone/token/controllers.py:122 #, python-format msgid "Invalid value for token_format: %s. Allowed values are PKI or UUID." msgstr "token_format 的值無效:%s。接受的值為 PKI 或 UUID。" @@ -179,7 +195,7 @@ msgstr "RBAC:已授與權限" msgid "RBAC: Adding query filter params (%s)" msgstr "RBAC:正在新增查詢過濾器參數 (%s)" -#: keystone/common/controller.py:317 +#: keystone/common/controller.py:318 msgid "Invalid token in normalize_domain_id" msgstr "normalize_domain_id 中的記號無效" @@ -195,12 +211,12 @@ msgstr "" msgid "arg_dict: %s" msgstr "arg_dict:%s" -#: keystone/common/wsgi.py:186 +#: keystone/common/wsgi.py:188 #, fuzzy, python-format msgid "Authorization failed. %(exception)s from %(remote_addr)s" msgstr "授權失敗。%s(自 %s)" -#: keystone/common/wsgi.py:429 +#: keystone/common/wsgi.py:431 msgid "The resource could not be found." msgstr "找不到資源。" @@ -247,13 +263,13 @@ msgid "" "\"%(attr_map)s\" must use one of %(keys)s." msgstr "" -#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:630 -#: keystone/identity/backends/kvs.py:658 +#: keystone/common/ldap/core.py:279 keystone/identity/backends/kvs.py:177 +#: keystone/identity/backends/kvs.py:205 #, python-format msgid "Duplicate name, %s." msgstr "重複的名稱,%s。" -#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:623 +#: keystone/common/ldap/core.py:289 keystone/identity/backends/kvs.py:170 #, python-format msgid "Duplicate ID, %s." msgstr "重複的 ID,%s。" @@ -441,12 +457,16 @@ msgstr "FakeLdap 搜尋失敗:找不到 SCOPE_BASE 的 DN" msgid "Search scope %s not implemented." msgstr "未實作搜尋範圍 %s。" -#: keystone/common/sql/core.py:198 +#: keystone/common/sql/core.py:126 +msgid "Global engine callback raised." +msgstr "" + +#: keystone/common/sql/core.py:240 #, python-format msgid "Got mysql server has gone away: %s" msgstr "已取得 mysql 伺服器已斷線的訊息:%s" -#: keystone/common/sql/legacy.py:180 +#: keystone/common/sql/legacy.py:188 #, python-format msgid "Cannot migrate EC2 credential: %s" msgstr "無法移轉 EC2 認證:%s" @@ -455,37 +475,37 @@ msgstr "無法移轉 EC2 認證:%s" msgid "version should be an integer" msgstr "版本應該是整數" -#: keystone/common/sql/nova.py:62 +#: keystone/common/sql/nova.py:65 #, python-format msgid "Create tenant %s" msgstr "建立 Tenant %s" -#: keystone/common/sql/nova.py:79 +#: keystone/common/sql/nova.py:82 #, python-format msgid "Create user %s" msgstr "建立使用者 %s" -#: keystone/common/sql/nova.py:88 +#: keystone/common/sql/nova.py:91 #, fuzzy, python-format msgid "Add user %(user_id)s to tenant %(tenant_id)s" msgstr "將使用者 %s 新增至 Tenant %s" -#: keystone/common/sql/nova.py:97 +#: keystone/common/sql/nova.py:100 #, python-format msgid "Ignoring existing role %s" msgstr "正在忽略現有角色 %s" -#: keystone/common/sql/nova.py:104 +#: keystone/common/sql/nova.py:107 #, python-format msgid "Create role %s" msgstr "建立角色 %s" -#: keystone/common/sql/nova.py:114 +#: keystone/common/sql/nova.py:117 #, fuzzy, python-format msgid "Assign role %(role_id)s to user %(user_id)s on tenant %(tenant_id)s" msgstr "將角色 %s 指派給使用者 %s(在 Tenant %s 上)" -#: keystone/common/sql/nova.py:133 +#: keystone/common/sql/nova.py:136 #, fuzzy, python-format msgid "Creating ec2 cred for user %(user_id)s and tenant %(tenant_id)s" msgstr "正在給使用者 %s 及 Tenant %s 建立 EC2 Cred" @@ -497,41 +517,26 @@ msgid "" "%(role)s" msgstr "" -#: keystone/identity/backends/kvs.py:284 keystone/identity/backends/kvs.py:293 +#: keystone/identity/backends/kvs.py:126 keystone/identity/backends/kvs.py:135 msgid "User not found in group" msgstr "在群組中找不到使用者" -#: keystone/identity/backends/ldap.py:82 -#, python-format -msgid "Expected dict or list: %s" -msgstr "" - -#: keystone/identity/backends/ldap.py:376 +#: keystone/identity/backends/ldap.py:192 #, python-format msgid "" "Group member '%(user_dn)s' not found in '%(group_id)s'. The user should " "be removed from the group. The user will be ignored." msgstr "" -#: keystone/identity/backends/ldap.py:638 -#, python-format -msgid "Role %s not found" -msgstr "找不到角色 %s" - -#: keystone/identity/backends/ldap.py:826 +#: keystone/identity/backends/ldap.py:621 msgid "Changing Name not supported by LDAP" msgstr "LDAP 不支援變更名稱" -#: keystone/identity/backends/ldap.py:839 +#: keystone/identity/backends/ldap.py:634 #, fuzzy, python-format msgid "User %(user_id)s is already a member of group %(group_id)s" msgstr "使用者 %s 已是群組 %s 的成員" -#: keystone/identity/backends/sql.py:462 -#, python-format -msgid "Cannot remove role that has not been granted, %s" -msgstr "無法移除尚未授權的角色,%s" - #: keystone/openstack/common/policy.py:394 #, python-format msgid "Failed to understand rule %(rule)s" @@ -552,15 +557,20 @@ msgstr "無法理解規則 %(rule)r" msgid "enforce %(action)s: %(credentials)s" msgstr "" -#: keystone/token/controllers.py:453 keystone/token/controllers.py:456 +#: keystone/token/controllers.py:396 +#, python-format +msgid "User %(u_id)s is unauthorized for tenant %(t_id)s" +msgstr "" + +#: keystone/token/controllers.py:413 keystone/token/controllers.py:416 msgid "Token does not belong to specified tenant." msgstr "記號不屬於所指定的 Tenant。" -#: keystone/token/controllers.py:463 +#: keystone/token/controllers.py:423 msgid "Non-default domain is not supported" msgstr "不支援非預設網域" -#: keystone/token/controllers.py:471 +#: keystone/token/controllers.py:431 msgid "Domain scoped token is not supported" msgstr "不支援網域範圍的記號" @@ -586,10 +596,3 @@ msgstr "" msgid "Unable to add token to revocation list." msgstr "無法將記號新增至撤銷清冊。" -#~ msgid "" -#~ "Group member '%(user_dn)s' not found in" -#~ " '%(group_dn)s'. The user should be " -#~ "removed from the group. The user " -#~ "will be ignored." -#~ msgstr "" - diff --git a/keystone/openstack/common/crypto/__init__.py b/keystone/openstack/common/crypto/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/keystone/openstack/common/crypto/__init__.py diff --git a/keystone/openstack/common/crypto/utils.py b/keystone/openstack/common/crypto/utils.py new file mode 100644 index 00000000..ef178cab --- /dev/null +++ b/keystone/openstack/common/crypto/utils.py @@ -0,0 +1,179 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Red Hat, Inc. +# +# 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 base64 + +from Crypto.Hash import HMAC +from Crypto import Random + +from keystone.openstack.common.gettextutils import _ +from keystone.openstack.common.importutils import import_module + + +class CryptoutilsException(Exception): + """Generic Exception for Crypto utilities.""" + + message = _("An unknown error occurred in crypto utils.") + + +class CipherBlockLengthTooBig(CryptoutilsException): + """The block size is too big.""" + + def __init__(self, requested, permitted): + msg = _("Block size of %(given)d is too big, max = %(maximum)d") + message = msg % {'given': requested, 'maximum': permitted} + super(CryptoutilsException, self).__init__(message) + + +class HKDFOutputLengthTooLong(CryptoutilsException): + """The amount of Key Material asked is too much.""" + + def __init__(self, requested, permitted): + msg = _("Length of %(given)d is too long, max = %(maximum)d") + message = msg % {'given': requested, 'maximum': permitted} + super(CryptoutilsException, self).__init__(message) + + +class HKDF(object): + """An HMAC-based Key Derivation Function implementation (RFC5869) + + This class creates an object that allows to use HKDF to derive keys. + """ + + def __init__(self, hashtype='SHA256'): + self.hashfn = import_module('Crypto.Hash.' + hashtype) + self.max_okm_length = 255 * self.hashfn.digest_size + + def extract(self, ikm, salt=None): + """An extract function that can be used to derive a robust key given + weak Input Key Material (IKM) which could be a password. + Returns a pseudorandom key (of HashLen octets) + + :param ikm: input keying material (ex a password) + :param salt: optional salt value (a non-secret random value) + """ + if salt is None: + salt = '\x00' * self.hashfn.digest_size + + return HMAC.new(salt, ikm, self.hashfn).digest() + + def expand(self, prk, info, length): + """An expand function that will return arbitrary length output that can + be used as keys. + Returns a buffer usable as key material. + + :param prk: a pseudorandom key of at least HashLen octets + :param info: optional string (can be a zero-length string) + :param length: length of output keying material (<= 255 * HashLen) + """ + if length > self.max_okm_length: + raise HKDFOutputLengthTooLong(length, self.max_okm_length) + + N = (length + self.hashfn.digest_size - 1) / self.hashfn.digest_size + + okm = "" + tmp = "" + for block in range(1, N + 1): + tmp = HMAC.new(prk, tmp + info + chr(block), self.hashfn).digest() + okm += tmp + + return okm[:length] + + +MAX_CB_SIZE = 256 + + +class SymmetricCrypto(object): + """Symmetric Key Crypto object. + + This class creates a Symmetric Key Crypto object that can be used + to encrypt, decrypt, or sign arbitrary data. + + :param enctype: Encryption Cipher name (default: AES) + :param hashtype: Hash/HMAC type name (default: SHA256) + """ + + def __init__(self, enctype='AES', hashtype='SHA256'): + self.cipher = import_module('Crypto.Cipher.' + enctype) + self.hashfn = import_module('Crypto.Hash.' + hashtype) + + def new_key(self, size): + return Random.new().read(size) + + def encrypt(self, key, msg, b64encode=True): + """Encrypt the provided msg and returns the cyphertext optionally + base64 encoded. + + Uses AES-128-CBC with a Random IV by default. + + The plaintext is padded to reach blocksize length. + The last byte of the block is the length of the padding. + The length of the padding does not include the length byte itself. + + :param key: The Encryption key. + :param msg: the plain text. + + :returns encblock: a block of encrypted data. + """ + iv = Random.new().read(self.cipher.block_size) + cipher = self.cipher.new(key, self.cipher.MODE_CBC, iv) + + # CBC mode requires a fixed block size. Append padding and length of + # padding. + if self.cipher.block_size > MAX_CB_SIZE: + raise CipherBlockLengthTooBig(self.cipher.block_size, MAX_CB_SIZE) + r = len(msg) % self.cipher.block_size + padlen = self.cipher.block_size - r - 1 + msg += '\x00' * padlen + msg += chr(padlen) + + enc = iv + cipher.encrypt(msg) + if b64encode: + enc = base64.b64encode(enc) + return enc + + def decrypt(self, key, msg, b64decode=True): + """Decrypts the provided ciphertext, optionally base 64 encoded, and + returns the plaintext message, after padding is removed. + + Uses AES-128-CBC with an IV by default. + + :param key: The Encryption key. + :param msg: the ciphetext, the first block is the IV + """ + if b64decode: + msg = base64.b64decode(msg) + iv = msg[:self.cipher.block_size] + cipher = self.cipher.new(key, self.cipher.MODE_CBC, iv) + + padded = cipher.decrypt(msg[self.cipher.block_size:]) + l = ord(padded[-1]) + 1 + plain = padded[:-l] + return plain + + def sign(self, key, msg, b64encode=True): + """Signs a message string and returns a base64 encoded signature. + + Uses HMAC-SHA-256 by default. + + :param key: The Signing key. + :param msg: the message to sign. + """ + h = HMAC.new(key, msg, self.hashfn) + out = h.digest() + if b64encode: + out = base64.b64encode(out) + return out diff --git a/keystone/service.py b/keystone/service.py index 406004d6..6b0c3708 100644 --- a/keystone/service.py +++ b/keystone/service.py @@ -41,7 +41,8 @@ DRIVERS = dict( identity_api=identity.Manager(), policy_api=policy.Manager(), token_api=token.Manager(), - trust_api=trust.Manager()) + trust_api=trust.Manager(), + token_provider_api=token.provider.Manager()) @logging.fail_gracefully diff --git a/keystone/test.py b/keystone/test.py index e68676ca..0c51d76d 100644 --- a/keystone/test.py +++ b/keystone/test.py @@ -17,6 +17,7 @@ import datetime import errno import os +import shutil import socket import StringIO import sys @@ -35,9 +36,11 @@ gettext.install('keystone', unicode=1) from keystone.common import environment environment.use_eventlet() +from keystone import assignment from keystone import catalog 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 @@ -55,6 +58,8 @@ ROOTDIR = os.path.dirname(os.path.abspath(os.curdir)) VENDOR = os.path.join(ROOTDIR, 'vendor') TESTSDIR = os.path.join(ROOTDIR, 'tests') ETCDIR = os.path.join(ROOTDIR, 'etc') +TMPDIR = os.path.join(TESTSDIR, 'tmp') + CONF = config.CONF cd = os.chdir @@ -75,6 +80,10 @@ def testsdir(*p): return os.path.join(TESTSDIR, *p) +def tmpdir(*p): + return os.path.join(TMPDIR, *p) + + def checkout_vendor(repo, rev): # TODO(termie): this function is a good target for some optimizations :PERF name = repo.split('/')[-1] @@ -107,6 +116,26 @@ def checkout_vendor(repo, rev): return revdir +def setup_test_database(): + db = tmpdir('test.db') + pristine = tmpdir('test.db.pristine') + + try: + if os.path.exists(db): + os.unlink(db) + if not os.path.exists(pristine): + sql.migration.db_sync() + shutil.copyfile(db, pristine) + else: + shutil.copyfile(pristine, db) + except Exception: + pass + + +def teardown_test_database(): + sql.core.set_global_engine(None) + + class TestClient(object): def __init__(self, app=None, token=None): self.app = app @@ -223,7 +252,8 @@ class TestCase(NoModule, unittest.TestCase): def load_backends(self): """Initializes each manager and assigns them to an attribute.""" - for manager in [catalog, credential, identity, policy, token, trust]: + for manager in [assignment, catalog, credential, identity, policy, + token, trust]: manager_name = '%s_api' % manager.__name__.split('.')[-1] setattr(self, manager_name, manager.Manager()) @@ -276,19 +306,6 @@ class TestCase(NoModule, unittest.TestCase): pass setattr(self, 'user_%s' % user['id'], user_copy) - for metadata in fixtures.METADATA: - metadata_ref = metadata.copy() - # TODO(termie): these will probably end up in the model anyway, - # so this may be futile - del metadata_ref['user_id'] - del metadata_ref['tenant_id'] - rv = self.identity_api.create_metadata(metadata['user_id'], - metadata['tenant_id'], - metadata_ref) - setattr(self, - 'metadata_%s%s' % (metadata['user_id'], - metadata['tenant_id']), rv) - def _paste_config(self, config): if not config.startswith('config:'): test_path = os.path.join(TESTSDIR, config) diff --git a/keystone/token/__init__.py b/keystone/token/__init__.py index 889cd39a..ffd9bc44 100644 --- a/keystone/token/__init__.py +++ b/keystone/token/__init__.py @@ -17,4 +17,5 @@ from keystone.token import controllers from keystone.token.core import * +from keystone.token import provider from keystone.token import routers diff --git a/keystone/token/backends/kvs.py b/keystone/token/backends/kvs.py index c16dd61b..0927aba1 100644 --- a/keystone/token/backends/kvs.py +++ b/keystone/token/backends/kvs.py @@ -26,7 +26,6 @@ class Token(kvs.Base, token.Driver): # Public interface def get_token(self, token_id): - token_id = token.unique_id(token_id) try: ref = self.db.get('token-%s' % token_id) except exception.NotFound: @@ -41,7 +40,6 @@ class Token(kvs.Base, token.Driver): raise exception.TokenNotFound(token_id=token_id) def create_token(self, token_id, data): - token_id = token.unique_id(token_id) data_copy = copy.deepcopy(data) data_copy['id'] = token_id if not data_copy.get('expires'): @@ -52,7 +50,6 @@ class Token(kvs.Base, token.Driver): return copy.deepcopy(data_copy) def delete_token(self, token_id): - token_id = token.unique_id(token_id) try: token_ref = self.get_token(token_id) self.db.delete('token-%s' % token_id) diff --git a/keystone/token/backends/memcache.py b/keystone/token/backends/memcache.py index e9d8482f..06e89d60 100644 --- a/keystone/token/backends/memcache.py +++ b/keystone/token/backends/memcache.py @@ -65,7 +65,7 @@ class Token(token.Driver): def get_token(self, token_id): if token_id is None: raise exception.TokenNotFound(token_id='') - ptk = self._prefix_token_id(token.unique_id(token_id)) + ptk = self._prefix_token_id(token_id) token_ref = self.client.get(ptk) if token_ref is None: raise exception.TokenNotFound(token_id=token_id) @@ -74,7 +74,7 @@ class Token(token.Driver): def create_token(self, token_id, data): data_copy = copy.deepcopy(data) - ptk = self._prefix_token_id(token.unique_id(token_id)) + ptk = self._prefix_token_id(token_id) if not data_copy.get('expires'): data_copy['expires'] = token.default_expire_time() if not data_copy.get('user_id'): @@ -118,7 +118,7 @@ class Token(token.Driver): if record is not None: token_list = jsonutils.loads('[%s]' % record) for token_i in token_list: - ptk = self._prefix_token_id(token.unique_id(token_i)) + ptk = self._prefix_token_id(token_i) token_ref = self.client.get(ptk) if not token_ref: # skip tokens that do not exist in memcache @@ -174,8 +174,8 @@ class Token(token.Driver): def delete_token(self, token_id): # Test for existence - data = self.get_token(token.unique_id(token_id)) - ptk = self._prefix_token_id(token.unique_id(token_id)) + data = self.get_token(token_id) + ptk = self._prefix_token_id(token_id) result = self.client.delete(ptk) self._add_to_revocation_list(data) return result @@ -186,7 +186,7 @@ class Token(token.Driver): user_record = self.client.get(user_key) or "" token_list = jsonutils.loads('[%s]' % user_record) for token_id in token_list: - ptk = self._prefix_token_id(token.unique_id(token_id)) + ptk = self._prefix_token_id(token_id) token_ref = self.client.get(ptk) if token_ref: if tenant_id is not None: diff --git a/keystone/token/backends/sql.py b/keystone/token/backends/sql.py index 57dbf410..0e8a916d 100644 --- a/keystone/token/backends/sql.py +++ b/keystone/token/backends/sql.py @@ -41,7 +41,7 @@ class Token(sql.Base, token.Driver): if token_id is None: raise exception.TokenNotFound(token_id=token_id) session = self.get_session() - token_ref = session.query(TokenModel).get(token.unique_id(token_id)) + token_ref = session.query(TokenModel).get(token_id) now = datetime.datetime.utcnow() if not token_ref or not token_ref.valid: raise exception.TokenNotFound(token_id=token_id) @@ -59,7 +59,6 @@ class Token(sql.Base, token.Driver): data_copy['user_id'] = data_copy['user']['id'] token_ref = TokenModel.from_dict(data_copy) - token_ref.id = token.unique_id(token_id) token_ref.valid = True session = self.get_session() with session.begin(): @@ -69,9 +68,8 @@ class Token(sql.Base, token.Driver): def delete_token(self, token_id): session = self.get_session() - key = token.unique_id(token_id) with session.begin(): - token_ref = session.query(TokenModel).get(key) + token_ref = session.query(TokenModel).get(token_id) if not token_ref or not token_ref.valid: raise exception.TokenNotFound(token_id=token_id) token_ref.valid = False diff --git a/keystone/token/controllers.py b/keystone/token/controllers.py index 34700106..d2b7d9fd 100644 --- a/keystone/token/controllers.py +++ b/keystone/token/controllers.py @@ -1,17 +1,17 @@ import json -import sys -import uuid from keystone.common import cms from keystone.common import controller from keystone.common import dependency -from keystone.common import environment from keystone.common import logging from keystone.common import utils +from keystone.common import wsgi from keystone import config from keystone import exception from keystone.openstack.common import timeutils from keystone.token import core +from keystone.token import provider as token_provider + CONF = config.CONF LOG = logging.getLogger(__name__) @@ -23,7 +23,7 @@ class ExternalAuthNotApplicable(Exception): pass -@dependency.requires('catalog_api', 'trust_api', 'token_api') +@dependency.requires('token_provider_api') class Auth(controller.V2Controller): def ca_cert(self, context, auth=None): ca_file = open(CONF.signing.ca_certs, 'r') @@ -79,9 +79,8 @@ class Auth(controller.V2Controller): auth_info = self._authenticate_local( context, auth) - user_ref, tenant_ref, metadata_ref, expiry = auth_info + user_ref, tenant_ref, metadata_ref, expiry, bind = auth_info core.validate_auth_info(self, user_ref, tenant_ref) - trust_id = metadata_ref.get('trust_id') user_ref = self._filter_domain_id(user_ref) if tenant_ref: tenant_ref = self._filter_domain_id(tenant_ref) @@ -99,52 +98,19 @@ class Auth(controller.V2Controller): catalog_ref = {} auth_token_data['id'] = 'placeholder' + if bind: + auth_token_data['bind'] = bind roles_ref = [] for role_id in metadata_ref.get('roles', []): role_ref = self.identity_api.get_role(role_id) roles_ref.append(dict(name=role_ref['name'])) - token_data = Auth.format_token(auth_token_data, roles_ref) - - service_catalog = Auth.format_catalog(catalog_ref) - token_data['access']['serviceCatalog'] = service_catalog - - if CONF.signing.token_format == 'UUID': - token_id = uuid.uuid4().hex - elif CONF.signing.token_format == 'PKI': - try: - token_id = cms.cms_sign_token(json.dumps(token_data), - CONF.signing.certfile, - CONF.signing.keyfile) - except environment.subprocess.CalledProcessError: - raise exception.UnexpectedError(_( - 'Unable to sign token.')) - else: - raise exception.UnexpectedError(_( - 'Invalid value for token_format: %s.' - ' Allowed values are PKI or UUID.') % - CONF.signing.token_format) - try: - self.token_api.create_token( - token_id, dict(key=token_id, - id=token_id, - expires=auth_token_data['expires'], - user=user_ref, - tenant=tenant_ref, - metadata=metadata_ref, - trust_id=trust_id)) - except Exception: - exc_info = sys.exc_info() - # an identical token may have been created already. - # if so, return the token_data as it is also identical - try: - self.token_api.get_token(token_id) - except exception.TokenNotFound: - raise exc_info[0], exc_info[1], exc_info[2] - - token_data['access']['token']['id'] = token_id - + (token_id, token_data) = self.token_provider_api.issue_token( + version=token_provider.V2, + token_ref=auth_token_data, + roles_ref=roles_ref, + catalog_ref=catalog_ref) return token_data def _authenticate_token(self, context, auth): @@ -170,6 +136,8 @@ class Auth(controller.V2Controller): except exception.NotFound as e: raise exception.Unauthorized(e) + wsgi.validate_token_bind(context, old_token_ref) + #A trust token cannot be used to get another token if 'trust' in old_token_ref: raise exception.Forbidden() @@ -207,17 +175,10 @@ class Auth(controller.V2Controller): else: current_user_ref = self.identity_api.get_user(user_id) + metadata_ref = {} tenant_id = self._get_project_id_from_auth(auth) - - tenant_ref = self._get_project_ref(user_id, tenant_id) - metadata_ref = self._get_metadata_ref(user_id, tenant_id) - - # TODO(henry-nash): If no tenant was specified, instead check for a - # domain and find any related user/group roles - - self._append_roles(metadata_ref, - self._get_group_metadata_ref( - context, user_id, tenant_id)) + tenant_ref, metadata_ref['roles'] = self._get_project_roles_and_ref( + user_id, tenant_id) expiry = old_token_ref['expires'] if CONF.trust.enabled and 'trust_id' in auth: @@ -238,7 +199,9 @@ class Auth(controller.V2Controller): metadata_ref['trustee_user_id'] = trust_ref['trustee_user_id'] metadata_ref['trust_id'] = trust_id - return (current_user_ref, tenant_ref, metadata_ref, expiry) + bind = old_token_ref.get('bind', None) + + return (current_user_ref, tenant_ref, metadata_ref, expiry, bind) def _authenticate_local(self, context, auth): """Try to authenticate against the identity backend. @@ -283,30 +246,20 @@ class Auth(controller.V2Controller): except exception.UserNotFound as e: raise exception.Unauthorized(e) - tenant_id = self._get_project_id_from_auth(auth) - try: - auth_info = self.identity_api.authenticate( + user_ref = self.identity_api.authenticate( user_id=user_id, - password=password, - tenant_id=tenant_id) + password=password) except AssertionError as e: raise exception.Unauthorized(e) - (user_ref, tenant_ref, metadata_ref) = auth_info - # By now we will have authorized and if a tenant/project was - # specified, we will have obtained its metadata. In this case - # we just need to add in any group roles. - # - # TODO(henry-nash): If no tenant was specified, instead check for a - # domain and find any related user/group roles - - self._append_roles(metadata_ref, - self._get_group_metadata_ref( - context, user_id, tenant_id)) + metadata_ref = {} + tenant_id = self._get_project_id_from_auth(auth) + tenant_ref, metadata_ref['roles'] = self._get_project_roles_and_ref( + user_id, tenant_id) expiry = core.default_expire_time() - return (user_ref, tenant_ref, metadata_ref, expiry) + return (user_ref, tenant_ref, metadata_ref, expiry, None) def _authenticate_external(self, context, auth): """Try to authenticate an external user via REMOTE_USER variable. @@ -316,6 +269,11 @@ class Auth(controller.V2Controller): if 'REMOTE_USER' not in context: raise ExternalAuthNotApplicable() + #NOTE(jamielennox): xml and json differ and get confused about what + # empty auth should look like so just reset it. + if not auth: + auth = {} + username = context['REMOTE_USER'] try: user_ref = self.identity_api.get_user_by_name( @@ -324,20 +282,18 @@ class Auth(controller.V2Controller): except exception.UserNotFound as e: raise exception.Unauthorized(e) + metadata_ref = {} tenant_id = self._get_project_id_from_auth(auth) - - tenant_ref = self._get_project_ref(user_id, tenant_id) - metadata_ref = self._get_metadata_ref(user_id, tenant_id) - - # TODO(henry-nash): If no tenant was specified, instead check for a - # domain and find any related user/group roles - - self._append_roles(metadata_ref, - self._get_group_metadata_ref( - context, user_id, tenant_id)) + tenant_ref, metadata_ref['roles'] = self._get_project_roles_and_ref( + user_id, tenant_id) expiry = core.default_expire_time() - return (user_ref, tenant_ref, metadata_ref, expiry) + bind = None + if ('kerberos' in CONF.token.bind and + context.get('AUTH_TYPE', '').lower() == 'negotiate'): + bind = {'kerberos': username} + + return (user_ref, tenant_ref, metadata_ref, expiry, bind) def _get_auth_token_data(self, user, tenant, metadata, expiry): return dict(user=user, @@ -405,40 +361,26 @@ class Auth(controller.V2Controller): exception.Unauthorized(e) return tenant_ref - def _get_metadata_ref(self, user_id=None, tenant_id=None, domain_id=None, - group_id=None): - """Returns metadata_ref for a user or group in a tenant or domain.""" + def _get_project_roles_and_ref(self, user_id, tenant_id): + """Returns the project roles for this user, and the project ref.""" - metadata_ref = {} - if (user_id or group_id) and (tenant_id or domain_id): + tenant_ref = None + role_list = [] + if tenant_id: try: - metadata_ref = self.identity_api.get_metadata( - user_id=user_id, tenant_id=tenant_id, - domain_id=domain_id, group_id=group_id) - except exception.MetadataNotFound: + tenant_ref = self.identity_api.get_project(tenant_id) + role_list = self.identity_api.get_roles_for_user_and_project( + user_id, tenant_id) + except exception.ProjectNotFound: pass - return metadata_ref - - def _get_group_metadata_ref(self, context, user_id, - tenant_id=None, domain_id=None): - """Return any metadata for this project/domain due to group grants.""" - group_refs = self.identity_api.list_groups_for_user(user_id) - metadata_ref = {} - for x in group_refs: - metadata_ref.update(self._get_metadata_ref( - group_id=x['id'], tenant_id=tenant_id, domain_id=domain_id)) - return metadata_ref - - def _append_roles(self, metadata, additional_metadata): - """Add additional roles to the roles in metadata. - The final set of roles represents the union of existing roles and - additional roles. - """ + if not role_list: + msg = _('User %(u_id)s is unauthorized for tenant %(t_id)s') + msg = msg % {'u_id': user_id, 't_id': tenant_id} + LOG.warning(msg) + raise exception.Unauthorized(msg) - first = set(metadata.get('roles', [])) - second = set(additional_metadata.get('roles', [])) - metadata['roles'] = list(first.union(second)) + return (tenant_ref, role_list) def _get_token_ref(self, token_id, belongs_to=None): """Returns a token if a valid one exists. @@ -456,45 +398,6 @@ class Auth(controller.V2Controller): _('Token does not belong to specified tenant.')) return data - def _assert_default_domain(self, token_ref): - """Make sure we are operating on default domain only.""" - if token_ref.get('token_data'): - # this is a V3 token - msg = _('Non-default domain is not supported') - # user in a non-default is prohibited - if (token_ref['token_data']['token']['user']['domain']['id'] != - DEFAULT_DOMAIN_ID): - raise exception.Unauthorized(msg) - # domain scoping is prohibited - if token_ref['token_data']['token'].get('domain'): - raise exception.Unauthorized( - _('Domain scoped token is not supported')) - # project in non-default domain is prohibited - if token_ref['token_data']['token'].get('project'): - project = token_ref['token_data']['token']['project'] - project_domain_id = project['domain']['id'] - # scoped to project in non-default domain is prohibited - if project_domain_id != DEFAULT_DOMAIN_ID: - raise exception.Unauthorized(msg) - # if token is scoped to trust, both trustor and trustee must - # be in the default domain. Furthermore, the delegated project - # must also be in the default domain - metadata_ref = token_ref['metadata'] - if CONF.trust.enabled and 'trust_id' in metadata_ref: - trust_ref = self.trust_api.get_trust(metadata_ref['trust_id']) - trustee_user_ref = self.identity_api.get_user( - trust_ref['trustee_user_id']) - if trustee_user_ref['domain_id'] != DEFAULT_DOMAIN_ID: - raise exception.Unauthorized(msg) - trustor_user_ref = self.identity_api.get_user( - trust_ref['trustor_user_id']) - if trustor_user_ref['domain_id'] != DEFAULT_DOMAIN_ID: - raise exception.Unauthorized(msg) - project_ref = self.identity_api.get_project( - trust_ref['project_id']) - if project_ref['domain_id'] != DEFAULT_DOMAIN_ID: - raise exception.Unauthorized(msg) - @controller.protected def validate_token_head(self, context, token_id): """Check that a token is valid. @@ -505,9 +408,9 @@ class Auth(controller.V2Controller): """ belongs_to = context['query_string'].get('belongsTo') - token_ref = self._get_token_ref(token_id, belongs_to) - assert token_ref - self._assert_default_domain(token_ref) + self.token_provider_api.check_token(token_id, + belongs_to=belongs_to, + version=token_provider.V2) @controller.protected def validate_token(self, context, token_id): @@ -519,26 +422,9 @@ class Auth(controller.V2Controller): """ belongs_to = context['query_string'].get('belongsTo') - token_ref = self._get_token_ref(token_id, belongs_to) - self._assert_default_domain(token_ref) - - # TODO(termie): optimize this call at some point and put it into the - # the return for metadata - # fill out the roles in the metadata - metadata_ref = token_ref['metadata'] - roles_ref = [] - for role_id in metadata_ref.get('roles', []): - roles_ref.append(self.identity_api.get_role(role_id)) - - # Get a service catalog if possible - # This is needed for on-behalf-of requests - catalog_ref = None - if token_ref.get('tenant'): - catalog_ref = self.catalog_api.get_catalog( - user_id=token_ref['user']['id'], - tenant_id=token_ref['tenant']['id'], - metadata=metadata_ref) - return Auth.format_token(token_ref, roles_ref, catalog_ref) + return self.token_provider_api.validate_token( + token_id, belongs_to=belongs_to, + version=token_provider.V2) def delete_token(self, context, token_id): """Delete a token, effectively invalidating it for authz.""" @@ -578,99 +464,6 @@ class Auth(controller.V2Controller): return Auth.format_endpoint_list(catalog_ref) @classmethod - def format_authenticate(cls, token_ref, roles_ref, catalog_ref): - o = Auth.format_token(token_ref, roles_ref) - o['access']['serviceCatalog'] = Auth.format_catalog(catalog_ref) - return o - - @classmethod - def format_token(cls, token_ref, roles_ref, catalog_ref=None): - user_ref = token_ref['user'] - metadata_ref = token_ref['metadata'] - expires = token_ref['expires'] - if expires is not None: - if not isinstance(expires, unicode): - expires = timeutils.isotime(expires) - o = {'access': {'token': {'id': token_ref['id'], - 'expires': expires, - 'issued_at': timeutils.strtime() - }, - 'user': {'id': user_ref['id'], - 'name': user_ref['name'], - 'username': user_ref['name'], - 'roles': roles_ref, - 'roles_links': metadata_ref.get('roles_links', - []) - } - } - } - if 'tenant' in token_ref and token_ref['tenant']: - token_ref['tenant']['enabled'] = True - o['access']['token']['tenant'] = token_ref['tenant'] - if catalog_ref is not None: - o['access']['serviceCatalog'] = Auth.format_catalog(catalog_ref) - if metadata_ref: - if 'is_admin' in metadata_ref: - o['access']['metadata'] = {'is_admin': - metadata_ref['is_admin']} - else: - o['access']['metadata'] = {'is_admin': 0} - if 'roles' in metadata_ref: - o['access']['metadata']['roles'] = metadata_ref['roles'] - if CONF.trust.enabled and 'trust_id' in metadata_ref: - o['access']['trust'] = {'trustee_user_id': - metadata_ref['trustee_user_id'], - 'id': metadata_ref['trust_id'] - } - return o - - @classmethod - def format_catalog(cls, catalog_ref): - """Munge catalogs from internal to output format - Internal catalogs look like: - - {$REGION: { - {$SERVICE: { - $key1: $value1, - ... - } - } - } - - The legacy api wants them to look like - - [{'name': $SERVICE[name], - 'type': $SERVICE, - 'endpoints': [{ - 'tenantId': $tenant_id, - ... - 'region': $REGION, - }], - 'endpoints_links': [], - }] - - """ - if not catalog_ref: - return [] - - services = {} - for region, region_ref in catalog_ref.iteritems(): - for service, service_ref in region_ref.iteritems(): - new_service_ref = services.get(service, {}) - new_service_ref['name'] = service_ref.pop('name') - new_service_ref['type'] = service - new_service_ref['endpoints_links'] = [] - service_ref['region'] = region - - endpoints_ref = new_service_ref.get('endpoints', []) - endpoints_ref.append(service_ref) - - new_service_ref['endpoints'] = endpoints_ref - services[service] = new_service_ref - - return services.values() - - @classmethod def format_endpoint_list(cls, catalog_ref): """Formats a list of endpoints according to Identity API v2. diff --git a/keystone/token/core.py b/keystone/token/core.py index a8a3b82d..bc27b80d 100644 --- a/keystone/token/core.py +++ b/keystone/token/core.py @@ -16,6 +16,7 @@ """Main entry point into the Token service.""" +import copy import datetime from keystone.common import cms @@ -32,19 +33,6 @@ config.register_int('expiration', group='token', default=86400) LOG = logging.getLogger(__name__) -def unique_id(token_id): - """Return a unique ID for a token. - - The returned value is useful as the primary key of a database table, - memcache store, or other lookup table. - - :returns: Given a PKI token, returns it's hashed value. Otherwise, returns - the passed-in value (such as a UUID token ID or an existing - hash). - """ - return cms.cms_hash_token(token_id) - - def default_expire_time(): """Determine when a fresh token should expire. @@ -114,6 +102,29 @@ class Manager(manager.Manager): def __init__(self): super(Manager, self).__init__(CONF.token.driver) + def _unique_id(self, token_id): + """Return a unique ID for a token. + + The returned value is useful as the primary key of a database table, + memcache store, or other lookup table. + + :returns: Given a PKI token, returns it's hashed value. Otherwise, + returns the passed-in value (such as a UUID token ID or an + existing hash). + """ + return cms.cms_hash_token(token_id) + + def get_token(self, token_id): + return self.driver.get_token(self._unique_id(token_id)) + + def create_token(self, token_id, data): + data_copy = copy.deepcopy(data) + data_copy['id'] = self._unique_id(token_id) + return self.driver.create_token(self._unique_id(token_id), data_copy) + + def delete_token(self, token_id): + return self.driver.delete_token(self._unique_id(token_id)) + class Driver(object): """Interface description for a Token driver.""" diff --git a/keystone/token/provider.py b/keystone/token/provider.py new file mode 100644 index 00000000..554d575c --- /dev/null +++ b/keystone/token/provider.py @@ -0,0 +1,165 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# 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 provider interface.""" + + +from keystone.common import dependency +from keystone.common import logging +from keystone.common import manager +from keystone import config +from keystone import exception + + +CONF = config.CONF +LOG = logging.getLogger(__name__) + + +# supported token versions +V2 = 'v2.0' +V3 = 'v3.0' + +# default token providers +PKI_PROVIDER = 'keystone.token.providers.pki.Provider' +UUID_PROVIDER = 'keystone.token.providers.uuid.Provider' + + +class UnsupportedTokenVersionException(Exception): + """Token version is unrecognizable or unsupported.""" + pass + + +@dependency.provider('token_provider_api') +class Manager(manager.Manager): + """Default pivot point for the token provider backend. + + See :mod:`keystone.common.manager.Manager` for more details on how this + dynamically calls the backend. + + """ + + @classmethod + def check_and_get_token_provider(cls): + """Make sure we still support token_format for backward compatibility. + + Return the provider based on token_format if provider property is not + set. Otherwise, ignore token_format and return the configured provider + instead. + + """ + if CONF.token.provider: + # FIXME(gyee): we are deprecating CONF.signing.token_format. This + # code is to ensure the token provider configuration agrees with + # CONF.signing.token_format. + if ((CONF.signing.token_format == 'PKI' and + CONF.token.provider != PKI_PROVIDER or + (CONF.signing.token_format == 'UUID' and + CONF.token.provider != UUID_PROVIDER))): + raise exception.UnexpectedError( + '[signing] token_format conflicts with [token] provider ' + 'in keystone.conf') + return CONF.token.provider + else: + if CONF.signing.token_format == 'PKI': + return PKI_PROVIDER + elif CONF.signing.token_format == 'UUID': + return UUID_PROVIDER + else: + raise exception.UnexpectedError( + 'unrecognized token format. Must be either ' + '\'UUID\' or \'PKI\'') + + def __init__(self): + super(Manager, self).__init__(self.check_and_get_token_provider()) + + +class Provider(object): + """Interface description for a Token provider.""" + + def get_token_version(self, token_data): + """Return the version of the given token data. + + If the given token data is unrecognizable, + UnsupportedTokenVersionException is raised. + + """ + raise exception.NotImplemented() + + def issue_token(self, version='v3.0', **kwargs): + """Issue a V3 token. + + For V3 tokens, 'user_id', 'method_names', must present in kwargs. + Optionally, kwargs may contain 'expires_at' for rescope tokens; + 'project_id' for project-scoped token; 'domain_id' for + domain-scoped token; and 'auth_context' from the authentication + plugins. + + For V2 tokens, 'token_ref' must be present in kwargs. + Optionally, kwargs may contain 'roles_ref' and 'catalog_ref'. + + :param context: request context + :type context: dictionary + :param version: version of the token to be issued + :type version: string + :param kwargs: information needed for token creation. Parameters + may be different depending on token version. + :type kwargs: dictionary + :returns: (token_id, token_data) + + """ + raise exception.NotImplemented() + + def revoke_token(self, token_id): + """Revoke a given token. + + :param token_id: identity of the token + :type token_id: string + :returns: None. + """ + raise exception.NotImplemented() + + def validate_token(self, token_id, belongs_to=None, version='v3.0'): + """Validate the given token and return the token data. + + Must raise Unauthorized exception if unable to validate token. + + :param token_id: identity of the token + :type token_id: string + :param belongs_to: identity of the scoped project to validate + :type belongs_to: string + :param version: version of the token to be validated + :type version: string + :returns: token data + :raises: keystone.exception.Unauthorized + + """ + raise exception.NotImplemented() + + def check_token(self, token_id, belongs_to=None, version='v3.0'): + """Check the validity of the given V3 token. + + Must raise Unauthorized exception if unable to check token. + + :param token_id: identity of the token + :type token_id: string + :param belongs_to: identity of the scoped project to validate + :type belongs_to: string + :param version: version of the token to check + :type version: string + :returns: None + :raises: keystone.exception.Unauthorized + + """ diff --git a/keystone/token/providers/__init__.py b/keystone/token/providers/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/keystone/token/providers/__init__.py diff --git a/keystone/token/providers/pki.py b/keystone/token/providers/pki.py new file mode 100644 index 00000000..81abe5d4 --- /dev/null +++ b/keystone/token/providers/pki.py @@ -0,0 +1,44 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack LLC +# +# 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. + +"""Keystone PKI Token Provider""" + +import json + +from keystone.common import cms +from keystone.common import environment +from keystone.common import logging +from keystone import config +from keystone import exception +from keystone.token.providers import uuid + + +CONF = config.CONF + +LOG = logging.getLogger(__name__) + + +class Provider(uuid.Provider): + def _get_token_id(self, token_data): + try: + token_id = cms.cms_sign_token(json.dumps(token_data), + CONF.signing.certfile, + CONF.signing.keyfile) + return token_id + except environment.subprocess.CalledProcessError: + LOG.exception('Unable to sign token') + raise exception.UnexpectedError(_( + 'Unable to sign token.')) diff --git a/keystone/token/providers/uuid.py b/keystone/token/providers/uuid.py new file mode 100644 index 00000000..94896920 --- /dev/null +++ b/keystone/token/providers/uuid.py @@ -0,0 +1,577 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack LLC +# +# 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. + +"""Keystone UUID Token Provider""" + +from __future__ import absolute_import + +import sys +import uuid + +from keystone.common import dependency +from keystone.common import logging +from keystone import config +from keystone import exception +from keystone.openstack.common import timeutils +from keystone import token +from keystone import trust + + +LOG = logging.getLogger(__name__) +CONF = config.CONF +DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id + + +@dependency.requires('catalog_api', 'identity_api') +class V2TokenDataHelper(object): + """Creates V2 token data.""" + @classmethod + def format_token(cls, token_ref, roles_ref, catalog_ref=None): + user_ref = token_ref['user'] + metadata_ref = token_ref['metadata'] + expires = token_ref.get('expires', token.default_expire_time()) + if expires is not None: + if not isinstance(expires, unicode): + expires = timeutils.isotime(expires) + o = {'access': {'token': {'id': token_ref['id'], + 'expires': expires, + 'issued_at': timeutils.strtime() + }, + 'user': {'id': user_ref['id'], + 'name': user_ref['name'], + 'username': user_ref['name'], + 'roles': roles_ref, + 'roles_links': metadata_ref.get('roles_links', + []) + } + } + } + if 'bind' in token_ref: + o['access']['token']['bind'] = token_ref['bind'] + if 'tenant' in token_ref and token_ref['tenant']: + token_ref['tenant']['enabled'] = True + o['access']['token']['tenant'] = token_ref['tenant'] + if catalog_ref is not None: + o['access']['serviceCatalog'] = V2TokenDataHelper.format_catalog( + catalog_ref) + if metadata_ref: + if 'is_admin' in metadata_ref: + o['access']['metadata'] = {'is_admin': + metadata_ref['is_admin']} + else: + o['access']['metadata'] = {'is_admin': 0} + if 'roles' in metadata_ref: + o['access']['metadata']['roles'] = metadata_ref['roles'] + if CONF.trust.enabled and 'trust_id' in metadata_ref: + o['access']['trust'] = {'trustee_user_id': + metadata_ref['trustee_user_id'], + 'id': metadata_ref['trust_id'] + } + return o + + @classmethod + def format_catalog(cls, catalog_ref): + """Munge catalogs from internal to output format + Internal catalogs look like: + + {$REGION: { + {$SERVICE: { + $key1: $value1, + ... + } + } + } + + The legacy api wants them to look like + + [{'name': $SERVICE[name], + 'type': $SERVICE, + 'endpoints': [{ + 'tenantId': $tenant_id, + ... + 'region': $REGION, + }], + 'endpoints_links': [], + }] + + """ + if not catalog_ref: + return [] + + services = {} + for region, region_ref in catalog_ref.iteritems(): + for service, service_ref in region_ref.iteritems(): + new_service_ref = services.get(service, {}) + new_service_ref['name'] = service_ref.pop('name') + new_service_ref['type'] = service + new_service_ref['endpoints_links'] = [] + service_ref['region'] = region + + endpoints_ref = new_service_ref.get('endpoints', []) + endpoints_ref.append(service_ref) + + new_service_ref['endpoints'] = endpoints_ref + services[service] = new_service_ref + + return services.values() + + @classmethod + def get_token_data(cls, **kwargs): + if 'token_ref' not in kwargs: + raise ValueError('Require token_ref to create V2 token data') + token_ref = kwargs.get('token_ref') + roles_ref = kwargs.get('roles_ref', []) + catalog_ref = kwargs.get('catalog_ref') + return V2TokenDataHelper.format_token( + token_ref, roles_ref, catalog_ref) + + +@dependency.requires('catalog_api', 'identity_api') +class V3TokenDataHelper(object): + """Token data helper.""" + def __init__(self): + if CONF.trust.enabled: + self.trust_api = trust.Manager() + + def _get_filtered_domain(self, domain_id): + domain_ref = self.identity_api.get_domain(domain_id) + return {'id': domain_ref['id'], 'name': domain_ref['name']} + + def _get_filtered_project(self, project_id): + project_ref = self.identity_api.get_project(project_id) + filtered_project = { + 'id': project_ref['id'], + 'name': project_ref['name']} + filtered_project['domain'] = self._get_filtered_domain( + project_ref['domain_id']) + return filtered_project + + def _populate_scope(self, token_data, domain_id, project_id): + if 'domain' in token_data or 'project' in token_data: + # scope already exist, no need to populate it again + return + + if domain_id: + token_data['domain'] = self._get_filtered_domain(domain_id) + if project_id: + token_data['project'] = self._get_filtered_project(project_id) + + def _get_roles_for_user(self, user_id, domain_id, project_id): + roles = [] + if domain_id: + roles = self.identity_api.get_roles_for_user_and_domain( + user_id, domain_id) + if project_id: + roles = self.identity_api.get_roles_for_user_and_project( + user_id, project_id) + return [self.identity_api.get_role(role_id) for role_id in roles] + + def _populate_user(self, token_data, user_id, domain_id, project_id, + trust): + if 'user' in token_data: + # no need to repopulate user if it already exists + return + + user_ref = self.identity_api.get_user(user_id) + if CONF.trust.enabled and trust and 'OS-TRUST:trust' not in token_data: + trustor_user_ref = (self.identity_api.get_user( + trust['trustor_user_id'])) + if not trustor_user_ref['enabled']: + raise exception.Forbidden(_('Trustor is disabled.')) + if trust['impersonation']: + user_ref = trustor_user_ref + token_data['OS-TRUST:trust'] = ( + { + 'id': trust['id'], + 'trustor_user': {'id': trust['trustor_user_id']}, + 'trustee_user': {'id': trust['trustee_user_id']}, + 'impersonation': trust['impersonation'] + }) + filtered_user = { + 'id': user_ref['id'], + 'name': user_ref['name'], + 'domain': self._get_filtered_domain(user_ref['domain_id'])} + token_data['user'] = filtered_user + + def _populate_roles(self, token_data, user_id, domain_id, project_id, + trust): + if 'roles' in token_data: + # no need to repopulate roles + return + + if CONF.trust.enabled and trust: + token_user_id = trust['trustor_user_id'] + token_project_id = trust['project_id'] + #trusts do not support domains yet + token_domain_id = None + else: + token_user_id = user_id + token_project_id = project_id + token_domain_id = domain_id + + if token_domain_id or token_project_id: + roles = self._get_roles_for_user(token_user_id, + token_domain_id, + token_project_id) + filtered_roles = [] + if CONF.trust.enabled and trust: + for trust_role in trust['roles']: + match_roles = [x for x in roles + if x['id'] == trust_role['id']] + if match_roles: + filtered_roles.append(match_roles[0]) + else: + raise exception.Forbidden( + _('Trustee has no delegated roles.')) + else: + for role in roles: + filtered_roles.append({'id': role['id'], + 'name': role['name']}) + + # user has no project or domain roles, therefore access denied + if not filtered_roles: + if token_project_id: + msg = _('User %(user_id)s has no access ' + 'to project %(project_id)s') % { + 'user_id': user_id, + 'project_id': token_project_id} + else: + msg = _('User %(user_id)s has no access ' + 'to domain %(domain_id)s') % { + 'user_id': user_id, + 'domain_id': token_domain_id} + LOG.debug(msg) + raise exception.Unauthorized(msg) + + token_data['roles'] = filtered_roles + + def _populate_service_catalog(self, token_data, user_id, + domain_id, project_id, trust): + if 'catalog' in token_data: + # no need to repopulate service catalog + return + + if CONF.trust.enabled and trust: + user_id = trust['trustor_user_id'] + if project_id or domain_id: + try: + service_catalog = self.catalog_api.get_v3_catalog( + user_id, project_id) + # TODO(ayoung): KVS backend needs a sample implementation + except exception.NotImplemented: + service_catalog = {} + # TODO(gyee): v3 service catalog is not quite completed yet + # TODO(ayoung): Enforce Endpoints for trust + token_data['catalog'] = service_catalog + + def _populate_token_dates(self, token_data, expires=None, trust=None): + if not expires: + expires = token.default_expire_time() + if not isinstance(expires, basestring): + expires = timeutils.isotime(expires, subsecond=True) + token_data['expires_at'] = expires + token_data['issued_at'] = timeutils.isotime(subsecond=True) + + def get_token_data(self, user_id, method_names, extras, + domain_id=None, project_id=None, expires=None, + trust=None, token=None, include_catalog=True, + bind=None): + token_data = {'methods': method_names, + 'extras': extras} + + # We've probably already written these to the token + if token: + for x in ('roles', 'user', 'catalog', 'project', 'domain'): + if x in token: + token_data[x] = token[x] + + if CONF.trust.enabled and trust: + if user_id != trust['trustee_user_id']: + raise exception.Forbidden(_('User is not a trustee.')) + + if bind: + token_data['bind'] = bind + + self._populate_scope(token_data, domain_id, project_id) + self._populate_user(token_data, user_id, domain_id, project_id, trust) + self._populate_roles(token_data, user_id, domain_id, project_id, trust) + if include_catalog: + self._populate_service_catalog(token_data, user_id, domain_id, + project_id, trust) + self._populate_token_dates(token_data, expires=expires, trust=trust) + return {'token': token_data} + + +@dependency.requires('token_api', 'identity_api', 'catalog_api') +class Provider(token.provider.Provider): + def __init__(self, *args, **kwargs): + super(Provider, self).__init__(*args, **kwargs) + if CONF.trust.enabled: + self.trust_api = trust.Manager() + self.v3_token_data_helper = V3TokenDataHelper() + self.v2_token_data_helper = V2TokenDataHelper() + + def get_token_version(self, token_data): + if token_data and isinstance(token_data, dict): + if 'access' in token_data: + return token.provider.V2 + if 'token' in token_data and 'methods' in token_data['token']: + return token.provider.V3 + raise token.provider.UnsupportedTokenVersionException() + + def _get_token_id(self, token_data): + return uuid.uuid4().hex + + def _issue_v2_token(self, **kwargs): + token_data = self.v2_token_data_helper.get_token_data(**kwargs) + token_id = self._get_token_id(token_data) + token_data['access']['token']['id'] = token_id + try: + expiry = token_data['access']['token']['expires'] + token_ref = kwargs.get('token_ref') + if isinstance(expiry, basestring): + expiry = timeutils.normalize_time( + timeutils.parse_isotime(expiry)) + data = dict(key=token_id, + id=token_id, + expires=expiry, + user=token_ref['user'], + tenant=token_ref['tenant'], + metadata=token_ref['metadata'], + token_data=token_data, + bind=token_ref.get('bind'), + trust_id=token_ref['metadata'].get('trust_id')) + self.token_api.create_token(token_id, data) + except Exception: + exc_info = sys.exc_info() + # an identical token may have been created already. + # if so, return the token_data as it is also identical + try: + self.token_api.get_token(token_id) + except exception.TokenNotFound: + raise exc_info[0], exc_info[1], exc_info[2] + + return (token_id, token_data) + + def _issue_v3_token(self, **kwargs): + user_id = kwargs.get('user_id') + method_names = kwargs.get('method_names') + expires_at = kwargs.get('expires_at') + project_id = kwargs.get('project_id') + domain_id = kwargs.get('domain_id') + auth_context = kwargs.get('auth_context') + trust = kwargs.get('trust') + metadata_ref = kwargs.get('metadata_ref') + include_catalog = kwargs.get('include_catalog') + # for V2, trust is stashed in metadata_ref + if (CONF.trust.enabled and not trust and metadata_ref and + 'trust_id' in metadata_ref): + trust = self.trust_api.get_trust(metadata_ref['trust_id']) + token_data = self.v3_token_data_helper.get_token_data( + user_id, + method_names, + auth_context.get('extras') if auth_context else None, + domain_id=domain_id, + project_id=project_id, + expires=expires_at, + trust=trust, + bind=auth_context.get('bind') if auth_context else None, + include_catalog=include_catalog) + + token_id = self._get_token_id(token_data) + try: + expiry = token_data['token']['expires_at'] + if isinstance(expiry, basestring): + expiry = timeutils.normalize_time( + timeutils.parse_isotime(expiry)) + # FIXME(gyee): is there really a need to store roles in metadata? + role_ids = [] + metadata_ref = kwargs.get('metadata_ref', {}) + if 'project' in token_data['token']: + # project-scoped token, fill in the v2 token data + # all we care are the role IDs + role_ids = [r['id'] for r in token_data['token']['roles']] + metadata_ref = {'roles': role_ids} + if trust: + metadata_ref.setdefault('trust_id', trust['id']) + metadata_ref.setdefault('trustee_user_id', + trust['trustee_user_id']) + data = dict(key=token_id, + id=token_id, + expires=expiry, + user=token_data['token']['user'], + tenant=token_data['token'].get('project'), + metadata=metadata_ref, + token_data=token_data, + trust_id=trust['id'] if trust else None) + self.token_api.create_token(token_id, data) + except Exception: + exc_info = sys.exc_info() + # an identical token may have been created already. + # if so, return the token_data as it is also identical + try: + self.token_api.get_token(token_id) + except exception.TokenNotFound: + raise exc_info[0], exc_info[1], exc_info[2] + + return (token_id, token_data) + + def issue_token(self, version='v3.0', **kwargs): + if version == token.provider.V3: + return self._issue_v3_token(**kwargs) + elif version == token.provider.V2: + return self._issue_v2_token(**kwargs) + raise token.provider.UnsupportedTokenVersionException + + def _verify_token(self, token_id, belongs_to=None): + """Verify the given token and return the token_ref.""" + token_ref = self.token_api.get_token(token_id=token_id) + assert token_ref + if belongs_to: + assert (token_ref['tenant'] and + token_ref['tenant']['id'] == belongs_to) + return token_ref + + def revoke_token(self, token_id): + self.token_api.delete_token(token_id=token_id) + + def _assert_default_domain(self, token_ref): + """Make sure we are operating on default domain only.""" + if (token_ref.get('token_data') and + self.get_token_version(token_ref.get('token_data')) == + token.provider.V3): + # this is a V3 token + msg = _('Non-default domain is not supported') + # user in a non-default is prohibited + if (token_ref['token_data']['token']['user']['domain']['id'] != + DEFAULT_DOMAIN_ID): + raise exception.Unauthorized(msg) + # domain scoping is prohibited + if token_ref['token_data']['token'].get('domain'): + raise exception.Unauthorized( + _('Domain scoped token is not supported')) + # project in non-default domain is prohibited + if token_ref['token_data']['token'].get('project'): + project = token_ref['token_data']['token']['project'] + project_domain_id = project['domain']['id'] + # scoped to project in non-default domain is prohibited + if project_domain_id != DEFAULT_DOMAIN_ID: + raise exception.Unauthorized(msg) + # if token is scoped to trust, both trustor and trustee must + # be in the default domain. Furthermore, the delegated project + # must also be in the default domain + metadata_ref = token_ref['metadata'] + if CONF.trust.enabled and 'trust_id' in metadata_ref: + trust_ref = self.trust_api.get_trust(metadata_ref['trust_id']) + trustee_user_ref = self.identity_api.get_user( + trust_ref['trustee_user_id']) + if trustee_user_ref['domain_id'] != DEFAULT_DOMAIN_ID: + raise exception.Unauthorized(msg) + trustor_user_ref = self.identity_api.get_user( + trust_ref['trustor_user_id']) + if trustor_user_ref['domain_id'] != DEFAULT_DOMAIN_ID: + raise exception.Unauthorized(msg) + project_ref = self.identity_api.get_project( + trust_ref['project_id']) + if project_ref['domain_id'] != DEFAULT_DOMAIN_ID: + raise exception.Unauthorized(msg) + + def _validate_v2_token(self, token_id, belongs_to=None, **kwargs): + try: + token_ref = self._verify_token(token_id, belongs_to=belongs_to) + self._assert_default_domain(token_ref) + # FIXME(gyee): performance or correctness? Should we return the + # cached token or reconstruct it? Obviously if we are going with + # the cached token, any role, project, or domain name changes + # will not be reflected. One may argue that with PKI tokens, + # we are essentially doing cached token validation anyway. + # Lets go with the cached token strategy. Since token + # management layer is now pluggable, one can always provide + # their own implementation to suit their needs. + token_data = token_ref.get('token_data') + if (not token_data or + self.get_token_version(token_data) != + token.provider.V2): + # token is created by old v2 logic + metadata_ref = token_ref['metadata'] + role_refs = [] + for role_id in metadata_ref.get('roles', []): + role_refs.append(self.identity_api.get_role(role_id)) + + # Get a service catalog if possible + # This is needed for on-behalf-of requests + catalog_ref = None + if token_ref.get('tenant'): + catalog_ref = self.catalog_api.get_catalog( + token_ref['user']['id'], + token_ref['tenant']['id'], + metadata=metadata_ref) + token_data = self.v2_token_data_helper.get_token_data( + token_ref=token_ref, + roles_ref=role_refs, + catalog_ref=catalog_ref) + return token_data + except AssertionError as e: + LOG.exception(_('Failed to validate token')) + raise exception.Unauthorized(e) + + def _validate_v3_token(self, token_id): + token_ref = self._verify_token(token_id) + # FIXME(gyee): performance or correctness? Should we return the + # cached token or reconstruct it? Obviously if we are going with + # the cached token, any role, project, or domain name changes + # will not be reflected. One may argue that with PKI tokens, + # we are essentially doing cached token validation anyway. + # Lets go with the cached token strategy. Since token + # management layer is now pluggable, one can always provide + # their own implementation to suit their needs. + token_data = token_ref.get('token_data') + if not token_data or 'token' not in token_data: + # token ref is created by V2 API + project_id = None + project_ref = token_ref.get('tenant') + if project_ref: + project_id = project_ref['id'] + token_data = self.v3_token_data_helper.get_token_data( + token_ref['user']['id'], + ['password', 'token'], + {}, + project_id=project_id, + bind=token_ref.get('bind'), + expires=token_ref['expires']) + return token_data + + def validate_token(self, token_id, belongs_to=None, version='v3.0'): + try: + if version == token.provider.V3: + return self._validate_v3_token(token_id) + elif version == token.provider.V2: + return self._validate_v2_token(token_id, + belongs_to=belongs_to) + raise token.provider.UnsupportedTokenVersionException() + except exception.TokenNotFound as e: + LOG.exception(_('Failed to verify token')) + raise exception.Unauthorized(e) + + def check_token(self, token_id, belongs_to=None, + version='v3.0', **kwargs): + try: + token_ref = self._verify_token(token_id, belongs_to=belongs_to) + if version == token.provider.V2: + self._assert_default_domain(token_ref) + except exception.TokenNotFound as e: + LOG.exception(_('Failed to verify token')) + raise exception.Unauthorized(e) diff --git a/openstack-common.conf b/openstack-common.conf index 170f200f..17062942 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,6 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common +module=crypto module=importutils module=install_venv_common module=jsonutils diff --git a/requirements.txt b/requirements.txt index 45054f24..e54bb6a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,5 @@ sqlalchemy-migrate>=0.7.2 passlib lxml iso8601>=0.1.4 -python-keystoneclient>=0.2.1,<0.3 +python-keystoneclient>=0.3.0 oslo.config>=1.1.0 @@ -1,5 +1,6 @@ [metadata] name = keystone +version = 2013.2 summary = OpenStack Identity description-file = README.rst diff --git a/tests/_ldap_livetest.py b/tests/_ldap_livetest.py index 0943f8d0..0bd707a4 100644 --- a/tests/_ldap_livetest.py +++ b/tests/_ldap_livetest.py @@ -68,9 +68,6 @@ class LiveLDAPIdentity(test_backend_ldap.LDAPIdentity): create_object(CONF.ldap.tenant_tree_dn, {'objectclass': 'organizationalUnit', 'ou': 'Projects'}) - create_object(CONF.ldap.domain_tree_dn, - {'objectclass': 'organizationalUnit', - 'ou': 'Domains'}) create_object(CONF.ldap.group_tree_dn, {'objectclass': 'organizationalUnit', 'ou': 'UserGroups'}) diff --git a/tests/auth_plugin_external_disabled.conf b/tests/auth_plugin_external_disabled.conf new file mode 100644 index 00000000..fed281d4 --- /dev/null +++ b/tests/auth_plugin_external_disabled.conf @@ -0,0 +1,2 @@ +[auth] +methods = password, token diff --git a/tests/auth_plugin_external_domain.conf b/tests/auth_plugin_external_domain.conf new file mode 100644 index 00000000..b7be122f --- /dev/null +++ b/tests/auth_plugin_external_domain.conf @@ -0,0 +1,3 @@ +[auth] +methods = external, password, token +external = keystone.auth.plugins.external.ExternalDomain diff --git a/tests/backend_ldap_sql.conf b/tests/backend_ldap_sql.conf new file mode 100644 index 00000000..8dcfa40d --- /dev/null +++ b/tests/backend_ldap_sql.conf @@ -0,0 +1,36 @@ +[sql] +connection = sqlite:// +#For a file based sqlite use +#connection = sqlite:////tmp/keystone.db +#To Test MySQL: +#connection = mysql://keystone:keystone@localhost/keystone?charset=utf8 +#To Test PostgreSQL: +#connection = postgresql://keystone:keystone@localhost/keystone?client_encoding=utf8 +idle_timeout = 200 + +[ldap] +url = fake://memory +user = cn=Admin +password = password +suffix = cn=example,cn=com + +[identity] +driver = keystone.identity.backends.ldap.Identity + +[assignment] +driver = keystone.assignment.backends.sql.Assignment + +[token] +driver = keystone.token.backends.sql.Token + +[ec2] +driver = keystone.contrib.ec2.backends.sql.Ec2 + +[catalog] +driver = keystone.catalog.backends.sql.Catalog + +[policy] +driver = keystone.policy.backends.sql.Policy + +[trust] +driver = keystone.trust.backends.sql.Trust diff --git a/tests/backend_liveldap.conf b/tests/backend_liveldap.conf index abcd441d..297d96d6 100644 --- a/tests/backend_liveldap.conf +++ b/tests/backend_liveldap.conf @@ -6,11 +6,9 @@ suffix = dc=openstack,dc=org group_tree_dn = ou=UserGroups,dc=openstack,dc=org role_tree_dn = ou=Roles,dc=openstack,dc=org tenant_tree_dn = ou=Projects,dc=openstack,dc=org -domain_tree_dn = ou=Domains,dc=openstack,dc=org user_tree_dn = ou=Users,dc=openstack,dc=org tenant_enabled_emulation = True user_enabled_emulation = True -domain_enabled_emulation = True user_mail_attribute = mail use_dumb_member = True diff --git a/tests/backend_sql_disk.conf b/tests/backend_sql_disk.conf index a4be9117..0f8dfea7 100644 --- a/tests/backend_sql_disk.conf +++ b/tests/backend_sql_disk.conf @@ -1,2 +1,2 @@ [sql] -connection = sqlite:///test.db +connection = sqlite:///tmp/test.db diff --git a/tests/backend_tls_liveldap.conf b/tests/backend_tls_liveldap.conf index 0c9d7f23..409af674 100644 --- a/tests/backend_tls_liveldap.conf +++ b/tests/backend_tls_liveldap.conf @@ -6,11 +6,9 @@ suffix = dc=openstack,dc=org group_tree_dn = ou=UserGroups,dc=openstack,dc=org role_tree_dn = ou=Roles,dc=openstack,dc=org tenant_tree_dn = ou=Projects,dc=openstack,dc=org -domain_tree_dn = ou=Domains,dc=openstack,dc=org user_tree_dn = ou=Users,dc=openstack,dc=org tenant_enabled_emulation = True user_enabled_emulation = True -domain_enabled_emulation = True user_mail_attribute = mail use_dumb_member = True use_tls = True diff --git a/tests/default_fixtures.py b/tests/default_fixtures.py index 256bb4b7..2695da88 100644 --- a/tests/default_fixtures.py +++ b/tests/default_fixtures.py @@ -17,6 +17,7 @@ # NOTE(dolph): please try to avoid additional fixtures if possible; test suite # performance may be negatively affected. +from keystone import assignment from keystone import config @@ -54,7 +55,7 @@ TENANTS = [ } ] -# NOTE(ja): a role of keystone_admin and attribute "is_admin" is done in setUp +# NOTE(ja): a role of keystone_admin is done in setUp USERS = [ { 'id': 'foo', @@ -95,13 +96,6 @@ USERS = [ } ] -METADATA = [ - { - 'user_id': 'sna', - 'tenant_id': 'mtu', - } -] - ROLES = [ { 'id': 'admin', @@ -125,16 +119,6 @@ ROLES = [ 'id': 'service', 'name': 'Service', } - - ] -DOMAINS = [ - { - 'id': DEFAULT_DOMAIN_ID, - 'name': 'Default', - 'enabled': True, - 'description': 'Owns users and tenants (i.e. projects) available ' - 'on Identity API v2.' - } -] +DOMAINS = [assignment.DEFAULT_DOMAIN] diff --git a/tests/test_auth.py b/tests/test_auth.py index b14977b9..1416269b 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -68,6 +68,10 @@ class AuthTest(test.TestCase): self.load_backends() self.load_fixtures(default_fixtures) + # need to register the token provider first because auth controller + # depends on it + token.provider.Manager() + self.controller = token.controllers.Auth() def assertEqualTokens(self, a, b): @@ -369,6 +373,33 @@ class AuthWithToken(AuthTest): dict(is_admin=True, query_string={'belongsTo': 'BAR'}), token_id=scoped_token_id) + def test_token_auth_with_binding(self): + CONF.token.bind = ['kerberos'] + body_dict = _build_user_auth() + context = {'REMOTE_USER': 'FOO', 'AUTH_TYPE': 'Negotiate'} + unscoped_token = self.controller.authenticate(context, body_dict) + + # the token should have bind information in it + bind = unscoped_token['access']['token']['bind'] + self.assertEqual(bind['kerberos'], 'FOO') + + body_dict = _build_user_auth( + token=unscoped_token['access']['token'], + tenant_name='BAR') + + # using unscoped token without remote user context fails + self.assertRaises( + exception.Unauthorized, + self.controller.authenticate, + {}, body_dict) + + # using token with remote user context succeeds + scoped_token = self.controller.authenticate(context, body_dict) + + # the bind information should be carried over from the original token + bind = scoped_token['access']['token']['bind'] + self.assertEqual(bind['kerberos'], 'FOO') + class AuthWithPasswordCredentials(AuthTest): def setUp(self): @@ -427,6 +458,13 @@ class AuthWithPasswordCredentials(AuthTest): self.controller.authenticate, {}, body_dict) + def test_bind_without_remote_user(self): + CONF.token.bind = ['kerberos'] + body_dict = _build_user_auth(username='FOO', password='foo2', + tenant_name='BAR') + token = self.controller.authenticate({}, body_dict) + self.assertNotIn('bind', token['access']['token']) + class AuthWithRemoteUser(AuthTest): def setUp(self): @@ -494,6 +532,20 @@ class AuthWithRemoteUser(AuthTest): {'REMOTE_USER': uuid.uuid4().hex}, body_dict) + def test_bind_with_kerberos(self): + CONF.token.bind = ['kerberos'] + kerb = {'REMOTE_USER': 'FOO', 'AUTH_TYPE': 'Negotiate'} + body_dict = _build_user_auth(tenant_name="BAR") + token = self.controller.authenticate(kerb, body_dict) + self.assertEqual(token['access']['token']['bind']['kerberos'], 'FOO') + + def test_bind_without_config_opt(self): + CONF.token.bind = ['x509'] + kerb = {'REMOTE_USER': 'FOO', 'AUTH_TYPE': 'Negotiate'} + body_dict = _build_user_auth(tenant_name='BAR') + token = self.controller.authenticate(kerb, body_dict) + self.assertNotIn('bind', token['access']['token']) + class AuthWithTrust(AuthTest): def setUp(self): @@ -621,7 +673,7 @@ class AuthWithTrust(AuthTest): 'project': { 'id': self.tenant_baz['id']}}} auth_response = (self.auth_v3_controller.authenticate_for_token - ({}, v3_password_data)) + ({'query_string': {}}, v3_password_data)) token = auth_response.headers['X-Subject-Token'] v3_req_with_trust = { @@ -631,7 +683,7 @@ class AuthWithTrust(AuthTest): "scope": { "OS-TRUST:trust": {"id": self.new_trust['id']}}} token_auth_response = (self.auth_v3_controller.authenticate_for_token - ({}, v3_req_with_trust)) + ({'query_string': {}}, v3_req_with_trust)) return token_auth_response def test_create_v3_token_from_trust(self): @@ -653,14 +705,14 @@ class AuthWithTrust(AuthTest): def test_v3_trust_token_get_token_fails(self): auth_response = self.fetch_v3_token_from_trust() trust_token = auth_response.headers['X-Subject-Token'] - v3_token_data = { - "methods": ["token"], - "token": {"id": trust_token} - } + v3_token_data = {'identity': { + 'methods': ['token'], + 'token': {'id': trust_token} + }} self.assertRaises( - exception.Unauthorized, + exception.Forbidden, self.auth_v3_controller.authenticate_for_token, - {}, v3_token_data) + {'query_string': {}}, v3_token_data) def test_token_from_trust(self): auth_response = self.fetch_v2_token_from_trust() diff --git a/tests/test_auth_plugin.conf b/tests/test_auth_plugin.conf index efe4bcb4..edec8f79 100644 --- a/tests/test_auth_plugin.conf +++ b/tests/test_auth_plugin.conf @@ -1,3 +1,3 @@ [auth] -methods = password,token,simple-challenge-response +methods = external,password,token,simple-challenge-response simple-challenge-response = challenge_response_method.SimpleChallengeResponse diff --git a/tests/test_auth_plugin.py b/tests/test_auth_plugin.py index 22357471..d158ec46 100644 --- a/tests/test_auth_plugin.py +++ b/tests/test_auth_plugin.py @@ -20,6 +20,7 @@ from keystone import test from keystone import auth from keystone import exception +from keystone import token # for testing purposes only @@ -49,6 +50,11 @@ class TestAuthPlugin(test.TestCase): test.testsdir('test_auth_plugin.conf')]) self.load_backends() auth.controllers.AUTH_METHODS[METHOD_NAME] = SimpleChallengeResponse() + + # need to register the token provider first because auth controller + # depends on it + token.provider.Manager() + self.api = auth.controllers.Auth() def test_unsupported_auth_method(self): diff --git a/tests/test_backend.py b/tests/test_backend.py index 87762244..8599ba24 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -62,39 +62,17 @@ class IdentityTests(object): self.assertRaises(AssertionError, self.identity_api.authenticate, user_id=uuid.uuid4().hex, - tenant_id=self.tenant_bar['id'], password=self.user_foo['password']) def test_authenticate_bad_password(self): self.assertRaises(AssertionError, self.identity_api.authenticate, user_id=self.user_foo['id'], - tenant_id=self.tenant_bar['id'], password=uuid.uuid4().hex) - def test_authenticate_bad_project(self): - self.assertRaises(AssertionError, - self.identity_api.authenticate, - user_id=self.user_foo['id'], - tenant_id=uuid.uuid4().hex, - password=self.user_foo['password']) - - def test_authenticate_no_project(self): - user_ref, tenant_ref, metadata_ref = self.identity_api.authenticate( - user_id=self.user_foo['id'], - password=self.user_foo['password']) - # NOTE(termie): the password field is left in user_foo to make - # it easier to authenticate in tests, but should - # not be returned by the api - self.user_foo.pop('password') - self.assertDictEqual(user_ref, self.user_foo) - self.assert_(tenant_ref is None) - self.assert_(not metadata_ref) - def test_authenticate(self): - user_ref, tenant_ref, metadata_ref = self.identity_api.authenticate( + user_ref = self.identity_api.authenticate( user_id=self.user_sna['id'], - tenant_id=self.tenant_bar['id'], password=self.user_sna['password']) # NOTE(termie): the password field is left in user_foo to make # it easier to authenticate in tests, but should @@ -102,21 +80,8 @@ class IdentityTests(object): self.user_sna.pop('password') self.user_sna['enabled'] = True self.assertDictEqual(user_ref, self.user_sna) - self.assertDictEqual(tenant_ref, self.tenant_bar) - metadata_ref.pop('roles') - self.assertDictEqual(metadata_ref, self.metadata_snamtu) - def test_authenticate_role_return(self): - self.identity_api.add_role_to_user_and_project( - self.user_foo['id'], self.tenant_baz['id'], self.role_admin['id']) - user_ref, tenant_ref, metadata_ref = self.identity_api.authenticate( - user_id=self.user_foo['id'], - tenant_id=self.tenant_baz['id'], - password=self.user_foo['password']) - self.assertIn('roles', metadata_ref) - self.assertIn(self.role_admin['id'], metadata_ref['roles']) - - def test_authenticate_no_metadata(self): + def test_authenticate_and_get_roles_no_metadata(self): user = { 'id': 'no_meta', 'name': 'NO_META', @@ -126,18 +91,18 @@ class IdentityTests(object): self.identity_api.create_user(user['id'], user) self.identity_api.add_user_to_project(self.tenant_baz['id'], user['id']) - user_ref, tenant_ref, metadata_ref = self.identity_api.authenticate( + user_ref = self.identity_api.authenticate( user_id=user['id'], - tenant_id=self.tenant_baz['id'], password=user['password']) # NOTE(termie): the password field is left in user_foo to make # it easier to authenticate in tests, but should # not be returned by the api user.pop('password') - self.assertEquals(metadata_ref, {"roles": - [CONF.member_role_id]}) self.assertDictContainsSubset(user, user_ref) - self.assertDictEqual(tenant_ref, self.tenant_baz) + role_list = self.identity_api.get_roles_for_user_and_project( + user['id'], self.tenant_baz['id']) + self.assertEqual(len(role_list), 1) + self.assertIn(CONF.member_role_id, role_list) def test_password_hashed(self): user_ref = self.identity_api._get_user(self.user_foo['id']) @@ -218,25 +183,6 @@ class IdentityTests(object): user_name=uuid.uuid4().hex, domain_id=DEFAULT_DOMAIN_ID) - def test_get_metadata(self): - metadata_ref = self.identity_api.get_metadata( - user_id=self.user_sna['id'], - tenant_id=self.tenant_bar['id']) - metadata_ref.pop('roles') - self.assertDictEqual(metadata_ref, self.metadata_snamtu) - - def test_get_metadata_404(self): - # FIXME(dolph): these exceptions could be more specific - self.assertRaises(exception.NotFound, - self.identity_api.get_metadata, - user_id=uuid.uuid4().hex, - tenant_id=self.tenant_bar['id']) - - self.assertRaises(exception.NotFound, - self.identity_api.get_metadata, - user_id=self.user_foo['id'], - tenant_id=uuid.uuid4().hex) - def test_get_role(self): role_ref = self.identity_api.get_role( role_id=self.role_admin['id']) @@ -1160,12 +1106,17 @@ class IdentityTests(object): group1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex, 'domain_id': domain1['id'], 'enabled': True} self.identity_api.create_group(group1['id'], group1) + group2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex, + 'domain_id': domain1['id'], 'enabled': True} + self.identity_api.create_group(group2['id'], group2) project1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex, 'domain_id': domain1['id']} self.identity_api.create_project(project1['id'], project1) self.identity_api.add_user_to_group(user1['id'], group1['id']) + self.identity_api.add_user_to_group(user1['id'], + group2['id']) roles_ref = self.identity_api.list_grants( user_id=user1['id'], @@ -1820,6 +1771,30 @@ class IdentityTests(object): self.assertIn(self.tenant_bar['id'], project_ids) self.assertIn(self.tenant_baz['id'], project_ids) + def test_list_projects_for_domain(self): + project_ids = ([x['id'] for x in + self.assignment_api.list_projects(DEFAULT_DOMAIN_ID)]) + self.assertEquals(len(project_ids), 4) + self.assertIn(self.tenant_bar['id'], project_ids) + self.assertIn(self.tenant_baz['id'], project_ids) + self.assertIn(self.tenant_mtu['id'], project_ids) + self.assertIn(self.tenant_service['id'], project_ids) + + def test_list_projects_for_alternate_domain(self): + domain1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + self.assignment_api.create_domain(domain1['id'], domain1) + project1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex, + 'domain_id': domain1['id']} + self.assignment_api.create_project(project1['id'], project1) + project2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex, + 'domain_id': domain1['id']} + self.assignment_api.create_project(project2['id'], project2) + project_ids = ([x['id'] for x in + self.assignment_api.list_projects(domain1['id'])]) + self.assertEquals(len(project_ids), 2) + self.assertIn(project1['id'], project_ids) + self.assertIn(project2['id'], project_ids) + def test_list_roles(self): roles = self.identity_api.list_roles() for test_role in default_fixtures.ROLES: @@ -2518,7 +2493,7 @@ class TrustTests(object): {'id': 'browser'}], trust_data['roles']) def test_list_trust_by_trustee(self): - for i in range(0, 3): + for i in range(3): self.create_sample_trust(uuid.uuid4().hex) trusts = self.trust_api.list_trusts_for_trustee(self.trustee['id']) self.assertEqual(len(trusts), 3) @@ -2527,7 +2502,7 @@ class TrustTests(object): self.assertEqual(len(trusts), 0) def test_list_trust_by_trustor(self): - for i in range(0, 3): + for i in range(3): self.create_sample_trust(uuid.uuid4().hex) trusts = self.trust_api.list_trusts_for_trustor(self.trustor['id']) self.assertEqual(len(trusts), 3) @@ -2536,7 +2511,7 @@ class TrustTests(object): self.assertEqual(len(trusts), 0) def test_list_trusts(self): - for i in range(0, 3): + for i in range(3): self.create_sample_trust(uuid.uuid4().hex) trusts = self.trust_api.list_trusts() self.assertEqual(len(trusts), 3) @@ -2744,3 +2719,164 @@ class PolicyTests(object): self.assertRaises(exception.PolicyNotFound, self.policy_api.delete_policy, uuid.uuid4().hex) + + +class InheritanceTests(object): + + def test_inherited_role_grants_for_user(self): + """Test inherited user roles. + + Test Plan: + - Enable OS-INHERIT extension + - Create 3 roles + - Create a domain, with a project and a user + - Check no roles yet exit + - Assign a direct user role to the project and a (non-inherited) + user role to the domain + - Get a list of effective roles - should only get the one direct role + - Now add an inherited user role to the domain + - Get a list of effective roles - should have two roles, one + direct and one by virtue of the inherited user role + - Also get effective roles for the domain - the role marked as + inherited should not show up + + """ + self.opt_in_group('os_inherit', enabled=True) + role_list = [] + for _ in range(3): + role = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + self.identity_api.create_role(role['id'], role) + role_list.append(role) + domain1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + self.identity_api.create_domain(domain1['id'], domain1) + user1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex, + 'domain_id': domain1['id'], 'password': uuid.uuid4().hex, + 'enabled': True} + self.identity_api.create_user(user1['id'], user1) + project1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex, + 'domain_id': domain1['id']} + self.identity_api.create_project(project1['id'], project1) + + roles_ref = self.identity_api.list_grants( + user_id=user1['id'], + project_id=project1['id']) + self.assertEquals(len(roles_ref), 0) + + # Create the first two roles - the domain one is not inherited + self.identity_api.create_grant(user_id=user1['id'], + project_id=project1['id'], + role_id=role_list[0]['id']) + self.identity_api.create_grant(user_id=user1['id'], + domain_id=domain1['id'], + role_id=role_list[1]['id']) + + # Now get the effective roles for the user and project, this + # should only include the direct role assignment on the project + combined_role_list = self.identity_api.get_roles_for_user_and_project( + user1['id'], project1['id']) + self.assertEquals(len(combined_role_list), 1) + self.assertIn(role_list[0]['id'], combined_role_list) + + # Now add an inherited role on the domain + self.identity_api.create_grant(user_id=user1['id'], + domain_id=domain1['id'], + role_id=role_list[2]['id'], + inherited_to_projects=True) + + # Now get the effective roles for the user and project again, this + # should now include the inherited role on the domain + combined_role_list = self.identity_api.get_roles_for_user_and_project( + user1['id'], project1['id']) + self.assertEquals(len(combined_role_list), 2) + self.assertIn(role_list[0]['id'], combined_role_list) + self.assertIn(role_list[2]['id'], combined_role_list) + + # Finally, check that the inherited role does not appear as a valid + # directly assigned role on the domain itself + combined_role_list = self.identity_api.get_roles_for_user_and_domain( + user1['id'], domain1['id']) + self.assertEquals(len(combined_role_list), 1) + self.assertIn(role_list[1]['id'], combined_role_list) + + def test_inherited_role_grants_for_group(self): + """Test inherited group roles. + + Test Plan: + - Enable OS-INHERIT extension + - Create 4 roles + - Create a domain, with a project, user and two groups + - Make the user a member of both groups + - Check no roles yet exit + - Assign a direct user role to the project and a (non-inherited) + group role on the domain + - Get a list of effective roles - should only get the one direct role + - Now add two inherited group roles to the domain + - Get a list of effective roles - should have three roles, one + direct and two by virtue of inherited group roles + + """ + self.opt_in_group('os_inherit', enabled=True) + role_list = [] + for _ in range(4): + role = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + self.identity_api.create_role(role['id'], role) + role_list.append(role) + domain1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + self.identity_api.create_domain(domain1['id'], domain1) + user1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex, + 'domain_id': domain1['id'], 'password': uuid.uuid4().hex, + 'enabled': True} + self.identity_api.create_user(user1['id'], user1) + group1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex, + 'domain_id': domain1['id'], 'enabled': True} + self.identity_api.create_group(group1['id'], group1) + group2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex, + 'domain_id': domain1['id'], 'enabled': True} + self.identity_api.create_group(group2['id'], group2) + project1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex, + 'domain_id': domain1['id']} + self.identity_api.create_project(project1['id'], project1) + + self.identity_api.add_user_to_group(user1['id'], + group1['id']) + self.identity_api.add_user_to_group(user1['id'], + group2['id']) + + roles_ref = self.identity_api.list_grants( + user_id=user1['id'], + project_id=project1['id']) + self.assertEquals(len(roles_ref), 0) + + # Create two roles - the domain one is not inherited + self.identity_api.create_grant(user_id=user1['id'], + project_id=project1['id'], + role_id=role_list[0]['id']) + self.identity_api.create_grant(group_id=group1['id'], + domain_id=domain1['id'], + role_id=role_list[1]['id']) + + # Now get the effective roles for the user and project, this + # should only include the direct role assignment on the project + combined_role_list = self.identity_api.get_roles_for_user_and_project( + user1['id'], project1['id']) + self.assertEquals(len(combined_role_list), 1) + self.assertIn(role_list[0]['id'], combined_role_list) + + # Now add to more group roles, both inherited, to the domain + self.identity_api.create_grant(group_id=group2['id'], + domain_id=domain1['id'], + role_id=role_list[2]['id'], + inherited_to_projects=True) + self.identity_api.create_grant(group_id=group2['id'], + domain_id=domain1['id'], + role_id=role_list[3]['id'], + inherited_to_projects=True) + + # Now get the effective roles for the user and project again, this + # should now include the inherited roles on the domain + combined_role_list = self.identity_api.get_roles_for_user_and_project( + user1['id'], project1['id']) + self.assertEquals(len(combined_role_list), 3) + self.assertIn(role_list[0]['id'], combined_role_list) + self.assertIn(role_list[2]['id'], combined_role_list) + self.assertIn(role_list[3]['id'], combined_role_list) diff --git a/tests/test_backend_ldap.py b/tests/test_backend_ldap.py index f52e30cc..ce4a297c 100644 --- a/tests/test_backend_ldap.py +++ b/tests/test_backend_ldap.py @@ -19,12 +19,13 @@ import uuid import nose.exc -from keystone import test - +from keystone import assignment from keystone.common.ldap import fakeldap +from keystone.common import sql from keystone import config from keystone import exception from keystone import identity +from keystone import test import default_fixtures import test_backend @@ -33,7 +34,7 @@ import test_backend CONF = config.CONF -class LDAPIdentity(test.TestCase, test_backend.IdentityTests): +class BaseLDAPIdentity(test_backend.IdentityTests): def _get_domain_fixture(self): """Domains in LDAP are read-only, so just return the static one.""" return self.identity_api.get_domain(CONF.identity.default_domain_id) @@ -47,14 +48,6 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests): test.testsdir('test_overrides.conf'), test.testsdir('backend_ldap.conf')]) - def setUp(self): - super(LDAPIdentity, self).setUp() - self._set_config() - self.clear_database() - - self.load_backends() - self.load_fixtures(default_fixtures) - def test_build_tree(self): """Regression test for building the tree names """ @@ -104,6 +97,202 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests): self.identity_api.delete_user, self.user_foo['id']) + def test_user_filter(self): + user_ref = self.identity_api.get_user(self.user_foo['id']) + self.user_foo.pop('password') + self.assertDictEqual(user_ref, self.user_foo) + + CONF.ldap.user_filter = '(CN=DOES_NOT_MATCH)' + self.load_backends() + self.assertRaises(exception.UserNotFound, + self.identity_api.get_user, + self.user_foo['id']) + + def test_get_role_grant_by_user_and_project(self): + raise nose.exc.SkipTest('Blocked by bug 1101287') + + def test_get_role_grants_for_user_and_project_404(self): + raise nose.exc.SkipTest('Blocked by bug 1101287') + + def test_add_role_grant_to_user_and_project_404(self): + raise nose.exc.SkipTest('Blocked by bug 1101287') + + def test_remove_role_grant_from_user_and_project(self): + raise nose.exc.SkipTest('Blocked by bug 1101287') + + def test_get_and_remove_role_grant_by_group_and_project(self): + raise nose.exc.SkipTest('Blocked by bug 1101287') + + def test_get_and_remove_role_grant_by_group_and_domain(self): + raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') + + def test_get_and_remove_role_grant_by_user_and_domain(self): + raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') + + def test_get_and_remove_correct_role_grant_from_a_mix(self): + raise nose.exc.SkipTest('Blocked by bug 1101287') + + def test_get_and_remove_role_grant_by_group_and_cross_domain(self): + raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') + + def test_get_and_remove_role_grant_by_user_and_cross_domain(self): + raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') + + def test_role_grant_by_group_and_cross_domain_project(self): + raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') + + def test_role_grant_by_user_and_cross_domain_project(self): + raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') + + def test_multi_role_grant_by_user_group_on_project_domain(self): + raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') + + def test_delete_role_with_user_and_group_grants(self): + raise nose.exc.SkipTest('Blocked by bug 1101287') + + def test_delete_user_with_group_project_domain_links(self): + raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') + + def test_delete_group_with_user_project_domain_links(self): + raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') + + def test_list_user_projects(self): + raise nose.exc.SkipTest('Blocked by bug 1101287') + + def test_create_duplicate_user_name_in_different_domains(self): + raise nose.exc.SkipTest('Blocked by bug 1101276') + + def test_create_duplicate_project_name_in_different_domains(self): + raise nose.exc.SkipTest('Blocked by bug 1101276') + + def test_create_duplicate_group_name_in_different_domains(self): + raise nose.exc.SkipTest( + 'N/A: LDAP does not support multiple domains') + + def test_move_user_between_domains(self): + raise nose.exc.SkipTest('Blocked by bug 1101276') + + def test_move_user_between_domains_with_clashing_names_fails(self): + raise nose.exc.SkipTest('Blocked by bug 1101276') + + def test_move_group_between_domains(self): + raise nose.exc.SkipTest( + 'N/A: LDAP does not support multiple domains') + + def test_move_group_between_domains_with_clashing_names_fails(self): + raise nose.exc.SkipTest('Blocked by bug 1101276') + + def test_move_project_between_domains(self): + raise nose.exc.SkipTest('Blocked by bug 1101276') + + def test_move_project_between_domains_with_clashing_names_fails(self): + raise nose.exc.SkipTest('Blocked by bug 1101276') + + def test_get_roles_for_user_and_domain(self): + raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') + + def test_list_role_assignments_unfiltered(self): + raise nose.exc.SkipTest('Blocked by bug 1195019') + + def test_multi_group_grants_on_project_domain(self): + raise nose.exc.SkipTest('Blocked by bug 1101287') + + def test_list_group_members_missing_entry(self): + """List group members with deleted user. + + If a group has a deleted entry for a member, the non-deleted members + are returned. + + """ + + # Create a group + group_id = None + group = dict(name=uuid.uuid4().hex) + group_id = self.identity_api.create_group(group_id, group)['id'] + + # Create a couple of users and add them to the group. + user_id = None + user = dict(name=uuid.uuid4().hex, id=uuid.uuid4().hex) + user_1_id = self.identity_api.create_user(user_id, user)['id'] + + self.identity_api.add_user_to_group(user_1_id, group_id) + + user_id = None + user = dict(name=uuid.uuid4().hex, id=uuid.uuid4().hex) + user_2_id = self.identity_api.create_user(user_id, user)['id'] + + self.identity_api.add_user_to_group(user_2_id, group_id) + + # Delete user 2 + # NOTE(blk-u): need to go directly to user interface to keep from + # updating the group. + self.identity_api.driver.user.delete(user_2_id) + + # List group users and verify only user 1. + res = self.identity_api.list_users_in_group(group_id) + + self.assertEqual(len(res), 1, "Expected 1 entry (user_1)") + self.assertEqual(res[0]['id'], user_1_id, "Expected user 1 id") + + def test_list_domains(self): + domains = self.identity_api.list_domains() + self.assertEquals( + domains, + [assignment.DEFAULT_DOMAIN]) + + def test_authenticate_requires_simple_bind(self): + user = { + 'id': 'no_meta', + 'name': 'NO_META', + 'domain_id': test_backend.DEFAULT_DOMAIN_ID, + 'password': 'no_meta2', + 'enabled': True, + } + self.identity_api.create_user(user['id'], user) + self.identity_api.add_user_to_project(self.tenant_baz['id'], + user['id']) + self.identity_api.driver.user.LDAP_USER = None + self.identity_api.driver.user.LDAP_PASSWORD = None + + self.assertRaises(AssertionError, + self.identity_api.authenticate, + user_id=user['id'], + password=None) + + # (spzala)The group and domain crud tests below override the standard ones + # in test_backend.py so that we can exclude the update name test, since we + # do not yet support the update of either group or domain names with LDAP. + # In the tests below, the update is demonstrated by updating description. + # Refer to bug 1136403 for more detail. + def test_group_crud(self): + group = { + 'id': uuid.uuid4().hex, + 'domain_id': CONF.identity.default_domain_id, + 'name': uuid.uuid4().hex, + 'description': uuid.uuid4().hex} + self.identity_api.create_group(group['id'], group) + group_ref = self.identity_api.get_group(group['id']) + self.assertDictEqual(group_ref, group) + group['description'] = uuid.uuid4().hex + self.identity_api.update_group(group['id'], group) + group_ref = self.identity_api.get_group(group['id']) + self.assertDictEqual(group_ref, group) + + self.identity_api.delete_group(group['id']) + self.assertRaises(exception.GroupNotFound, + self.identity_api.get_group, + group['id']) + + +class LDAPIdentity(test.TestCase, BaseLDAPIdentity): + def setUp(self): + super(LDAPIdentity, self).setUp() + self._set_config() + self.clear_database() + + self.load_backends() + self.load_fixtures(default_fixtures) + def test_configurable_allowed_project_actions(self): tenant = {'id': 'fake1', 'name': 'fake1', 'enabled': True} self.identity_api.create_project('fake1', tenant) @@ -175,17 +364,6 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests): self.identity_api.delete_role, self.role_member['id']) - def test_user_filter(self): - user_ref = self.identity_api.get_user(self.user_foo['id']) - self.user_foo.pop('password') - self.assertDictEqual(user_ref, self.user_foo) - - CONF.ldap.user_filter = '(CN=DOES_NOT_MATCH)' - self.load_backends() - self.assertRaises(exception.UserNotFound, - self.identity_api.get_user, - self.user_foo['id']) - def test_project_filter(self): tenant_ref = self.identity_api.get_project(self.tenant_bar['id']) self.assertDictEqual(tenant_ref, self.tenant_bar) @@ -216,40 +394,6 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests): self.identity_api.get_user, 'dumb') - def test_user_attribute_mapping(self): - CONF.ldap.user_name_attribute = 'sn' - CONF.ldap.user_mail_attribute = 'mail' - CONF.ldap.user_enabled_attribute = 'enabled' - self.clear_database() - self.load_backends() - self.load_fixtures(default_fixtures) - user_ref = self.identity_api.get_user(self.user_two['id']) - self.assertEqual(user_ref['id'], self.user_two['id']) - self.assertEqual(user_ref['name'], self.user_two['name']) - self.assertEqual(user_ref['email'], self.user_two['email']) - - CONF.ldap.user_name_attribute = 'mail' - CONF.ldap.user_mail_attribute = 'sn' - self.load_backends() - user_ref = self.identity_api.get_user(self.user_two['id']) - self.assertEqual(user_ref['id'], self.user_two['id']) - self.assertEqual(user_ref['name'], self.user_two['email']) - self.assertEqual(user_ref['email'], self.user_two['name']) - - def test_user_attribute_ignore(self): - CONF.ldap.user_attribute_ignore = ['email', 'password', - 'tenant_id', 'enabled', 'tenants'] - self.clear_database() - self.load_backends() - self.load_fixtures(default_fixtures) - user_ref = self.identity_api.get_user(self.user_two['id']) - self.assertEqual(user_ref['id'], self.user_two['id']) - self.assertNotIn('email', user_ref) - self.assertNotIn('password', user_ref) - self.assertNotIn('tenant_id', user_ref) - self.assertNotIn('enabled', user_ref) - self.assertNotIn('tenants', user_ref) - def test_project_attribute_mapping(self): CONF.ldap.tenant_name_attribute = 'ou' CONF.ldap.tenant_desc_attribute = 'description' @@ -384,30 +528,6 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests): # TODO(henry-nash): These need to be removed when the full LDAP implementation # is submitted - see Bugs 1092187, 1101287, 1101276, 1101289 - # (spzala)The group and domain crud tests below override the standard ones - # in test_backend.py so that we can exclude the update name test, since we - # do not yet support the update of either group or domain names with LDAP. - # In the tests below, the update is demonstrated by updating description. - # Refer to bug 1136403 for more detail. - def test_group_crud(self): - group = { - 'id': uuid.uuid4().hex, - 'domain_id': CONF.identity.default_domain_id, - 'name': uuid.uuid4().hex, - 'description': uuid.uuid4().hex} - self.identity_api.create_group(group['id'], group) - group_ref = self.identity_api.get_group(group['id']) - self.assertDictEqual(group_ref, group) - group['description'] = uuid.uuid4().hex - self.identity_api.update_group(group['id'], group) - group_ref = self.identity_api.get_group(group['id']) - self.assertDictEqual(group_ref, group) - - self.identity_api.delete_group(group['id']) - self.assertRaises(exception.GroupNotFound, - self.identity_api.get_group, - group['id']) - def test_domain_crud(self): domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex, 'enabled': True, 'description': uuid.uuid4().hex} @@ -434,33 +554,6 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests): self.identity_api.get_domain, domain['id']) - def test_get_role_grant_by_user_and_project(self): - raise nose.exc.SkipTest('Blocked by bug 1101287') - - def test_get_role_grants_for_user_and_project_404(self): - raise nose.exc.SkipTest('Blocked by bug 1101287') - - def test_add_role_grant_to_user_and_project_404(self): - raise nose.exc.SkipTest('Blocked by bug 1101287') - - def test_remove_role_grant_from_user_and_project(self): - raise nose.exc.SkipTest('Blocked by bug 1101287') - - def test_get_and_remove_role_grant_by_group_and_project(self): - raise nose.exc.SkipTest('Blocked by bug 1101287') - - def test_get_and_remove_role_grant_by_group_and_domain(self): - raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') - - def test_get_and_remove_role_grant_by_user_and_domain(self): - raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') - - def test_get_and_remove_correct_role_grant_from_a_mix(self): - raise nose.exc.SkipTest('Blocked by bug 1101287') - - def test_list_role_assignments_unfiltered(self): - raise nose.exc.SkipTest('Blocked by bug 1195019') - def test_project_crud(self): # NOTE(topol): LDAP implementation does not currently support the # updating of a project name so this method override @@ -488,18 +581,6 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests): self.identity_api.get_project, project['id']) - def test_get_and_remove_role_grant_by_group_and_cross_domain(self): - raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') - - def test_get_and_remove_role_grant_by_user_and_cross_domain(self): - raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') - - def test_role_grant_by_group_and_cross_domain_project(self): - raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') - - def test_role_grant_by_user_and_cross_domain_project(self): - raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') - def test_multi_role_grant_by_user_group_on_project_domain(self): # This is a partial implementation of the standard test that # is defined in test_backend.py. It omits both domain and @@ -549,117 +630,10 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests): user1['id'], CONF.identity.default_domain_id) self.assertEquals(len(combined_role_list), 0) - def test_multi_group_grants_on_project_domain(self): - raise nose.exc.SkipTest('Blocked by bug 1101287') - - def test_delete_role_with_user_and_group_grants(self): - raise nose.exc.SkipTest('Blocked by bug 1101287') - - def test_delete_user_with_group_project_domain_links(self): - raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') - - def test_delete_group_with_user_project_domain_links(self): - raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') - - def test_list_user_projects(self): - raise nose.exc.SkipTest('Blocked by bug 1101287') - - def test_create_duplicate_user_name_in_different_domains(self): - raise nose.exc.SkipTest('Blocked by bug 1101276') - - def test_create_duplicate_project_name_in_different_domains(self): - raise nose.exc.SkipTest('Blocked by bug 1101276') - - def test_create_duplicate_group_name_in_different_domains(self): + def test_list_projects_for_alternate_domain(self): raise nose.exc.SkipTest( 'N/A: LDAP does not support multiple domains') - def test_move_user_between_domains(self): - raise nose.exc.SkipTest('Blocked by bug 1101276') - - def test_move_user_between_domains_with_clashing_names_fails(self): - raise nose.exc.SkipTest('Blocked by bug 1101276') - - def test_move_group_between_domains(self): - raise nose.exc.SkipTest( - 'N/A: LDAP does not support multiple domains') - - def test_move_group_between_domains_with_clashing_names_fails(self): - raise nose.exc.SkipTest('Blocked by bug 1101276') - - def test_move_project_between_domains(self): - raise nose.exc.SkipTest('Blocked by bug 1101276') - - def test_move_project_between_domains_with_clashing_names_fails(self): - raise nose.exc.SkipTest('Blocked by bug 1101276') - - def test_get_roles_for_user_and_domain(self): - raise nose.exc.SkipTest('N/A: LDAP does not support multiple domains') - - def test_list_group_members_missing_entry(self): - """List group members with deleted user. - - If a group has a deleted entry for a member, the non-deleted members - are returned. - - """ - - # Create a group - group_id = None - group = dict(name=uuid.uuid4().hex) - group_id = self.identity_api.create_group(group_id, group)['id'] - - # Create a couple of users and add them to the group. - user_id = None - user = dict(name=uuid.uuid4().hex, id=uuid.uuid4().hex) - user_1_id = self.identity_api.create_user(user_id, user)['id'] - - self.identity_api.add_user_to_group(user_1_id, group_id) - - user_id = None - user = dict(name=uuid.uuid4().hex, id=uuid.uuid4().hex) - user_2_id = self.identity_api.create_user(user_id, user)['id'] - - self.identity_api.add_user_to_group(user_2_id, group_id) - - # Delete user 2 - # NOTE(blk-u): need to go directly to user interface to keep from - # updating the group. - self.identity_api.driver.user.delete(user_2_id) - - # List group users and verify only user 1. - res = self.identity_api.list_users_in_group(group_id) - - self.assertEqual(len(res), 1, "Expected 1 entry (user_1)") - self.assertEqual(res[0]['id'], user_1_id, "Expected user 1 id") - - def test_list_domains(self): - domains = self.identity_api.list_domains() - self.assertEquals( - domains, - [{'id': CONF.identity.default_domain_id, - 'name': 'Default', - 'enabled': True}]) - - def test_authenticate_requires_simple_bind(self): - user = { - 'id': 'no_meta', - 'name': 'NO_META', - 'domain_id': test_backend.DEFAULT_DOMAIN_ID, - 'password': 'no_meta2', - 'enabled': True, - } - self.identity_api.create_user(user['id'], user) - self.identity_api.add_user_to_project(self.tenant_baz['id'], - user['id']) - self.identity_api.driver.user.LDAP_USER = None - self.identity_api.driver.user.LDAP_PASSWORD = None - - self.assertRaises(AssertionError, - self.identity_api.authenticate_user, - user_id=user['id'], - password=None) - class LDAPIdentityEnabledEmulation(LDAPIdentity): def setUp(self): @@ -676,30 +650,6 @@ class LDAPIdentityEnabledEmulation(LDAPIdentity): self.user_two, self.user_badguy]: obj.setdefault('enabled', True) - def test_authenticate_no_metadata(self): - user = { - 'id': 'no_meta', - 'name': 'NO_META', - 'domain_id': test_backend.DEFAULT_DOMAIN_ID, - 'password': 'no_meta2', - 'enabled': True, - } - self.identity_api.create_user(user['id'], user) - self.identity_api.add_user_to_project(self.tenant_baz['id'], - user['id']) - user_ref, tenant_ref, metadata_ref = self.identity_api.authenticate( - user_id=user['id'], - tenant_id=self.tenant_baz['id'], - password=user['password']) - # NOTE(termie): the password field is left in user_foo to make - # it easier to authenticate in tests, but should - # not be returned by the api - user.pop('password') - self.assertEquals(metadata_ref, {"roles": - [CONF.member_role_id]}) - self.assertDictEqual(user_ref, user) - self.assertDictEqual(tenant_ref, self.tenant_baz) - def test_project_crud(self): # NOTE(topol): LDAPIdentityEnabledEmulation will create an # enabled key in the project dictionary so this @@ -757,3 +707,41 @@ class LDAPIdentityEnabledEmulation(LDAPIdentity): def test_user_enable_attribute_mask(self): raise nose.exc.SkipTest( "Enabled emulation conflicts with enabled mask") + + +class LdapIdentitySqlAssignment(sql.Base, test.TestCase, BaseLDAPIdentity): + + def _set_config(self): + self.config([test.etcdir('keystone.conf.sample'), + test.testsdir('test_overrides.conf'), + test.testsdir('backend_ldap_sql.conf')]) + + def setUp(self): + self._set_config() + self.clear_database() + self.load_backends() + self.engine = self.get_engine() + sql.ModelBase.metadata.create_all(bind=self.engine) + self.load_fixtures(default_fixtures) + #defaulted by the data load + self.user_foo['enabled'] = True + + def tearDown(self): + sql.ModelBase.metadata.drop_all(bind=self.engine) + self.engine.dispose() + sql.set_global_engine(None) + + def test_domain_crud(self): + pass + + def test_list_domains(self): + domains = self.identity_api.list_domains() + self.assertEquals(domains, [assignment.DEFAULT_DOMAIN]) + + def test_project_filter(self): + raise nose.exc.SkipTest( + 'N/A: Not part of SQL backend') + + def test_role_filter(self): + raise nose.exc.SkipTest( + 'N/A: Not part of SQL backend') diff --git a/tests/test_backend_memcache.py b/tests/test_backend_memcache.py index 66401e09..7516e0dd 100644 --- a/tests/test_backend_memcache.py +++ b/tests/test_backend_memcache.py @@ -164,7 +164,7 @@ class MemcacheToken(test.TestCase, test_backend.TokenTests): user_token_list = jsonutils.loads('[%s]' % user_record) self.assertEquals(len(user_token_list), 2) expired_token_ptk = self.token_api.driver._prefix_token_id( - token.unique_id(expired_token_id)) + expired_token_id) expired_token = self.token_api.driver.client.get(expired_token_ptk) expired_token['expires'] = (timeutils.utcnow() - expire_delta) self.token_api.driver.client.set(expired_token_ptk, expired_token) diff --git a/tests/test_backend_pam.py b/tests/test_backend_pam.py index 3a66f014..b66faa9c 100644 --- a/tests/test_backend_pam.py +++ b/tests/test_backend_pam.py @@ -57,12 +57,12 @@ class PamIdentity(test.TestCase): self.assertDictEqual(self.user_in, user_out) def test_get_metadata_for_non_root(self): - metadata_out = self.identity_api.get_metadata(self.user_in['id'], - self.tenant_in['id']) + metadata_out = self.identity_api._get_metadata(self.user_in['id'], + self.tenant_in['id']) self.assertDictEqual({}, metadata_out) def test_get_metadata_for_root(self): metadata = {'is_admin': True} - metadata_out = self.identity_api.get_metadata('root', - self.tenant_in['id']) + metadata_out = self.identity_api._get_metadata('root', + self.tenant_in['id']) self.assertDictEqual(metadata, metadata_out) diff --git a/tests/test_backend_sql.py b/tests/test_backend_sql.py index 46d7c013..38eddaa4 100644 --- a/tests/test_backend_sql.py +++ b/tests/test_backend_sql.py @@ -16,6 +16,8 @@ import uuid +import sqlalchemy + from keystone import test from keystone.common import sql @@ -57,6 +59,94 @@ class SqlTests(test.TestCase, sql.Base): super(SqlTests, self).tearDown() +class SqlModels(SqlTests): + def setUp(self): + super(SqlModels, self).setUp() + + self.metadata = sql.ModelBase.metadata + self.metadata.bind = self.engine + + def select_table(self, name): + table = sqlalchemy.Table(name, + self.metadata, + autoload=True) + s = sqlalchemy.select([table]) + return s + + def assertExpectedSchema(self, table, cols): + table = self.select_table(table) + for col, type_, length in cols: + self.assertIsInstance(table.c[col].type, type_) + if length: + self.assertEquals(table.c[col].type.length, length) + + def test_user_model(self): + cols = (('id', sql.String, 64), + ('name', sql.String, 64), + ('password', sql.String, 128), + ('domain_id', sql.String, 64), + ('enabled', sql.Boolean, None), + ('extra', sql.JsonBlob, None)) + self.assertExpectedSchema('user', cols) + + def test_group_model(self): + cols = (('id', sql.String, 64), + ('name', sql.String, 64), + ('description', sql.Text, None), + ('domain_id', sql.String, 64), + ('extra', sql.JsonBlob, None)) + self.assertExpectedSchema('group', cols) + + def test_domain_model(self): + cols = (('id', sql.String, 64), + ('name', sql.String, 64), + ('enabled', sql.Boolean, None)) + self.assertExpectedSchema('domain', cols) + + def test_project_model(self): + cols = (('id', sql.String, 64), + ('name', sql.String, 64), + ('description', sql.Text, None), + ('domain_id', sql.String, 64), + ('enabled', sql.Boolean, None), + ('extra', sql.JsonBlob, None)) + self.assertExpectedSchema('project', cols) + + def test_role_model(self): + cols = (('id', sql.String, 64), + ('name', sql.String, 64)) + self.assertExpectedSchema('role', cols) + + def test_user_project_metadata_model(self): + cols = (('user_id', sql.String, 64), + ('project_id', sql.String, 64), + ('data', sql.JsonBlob, None)) + self.assertExpectedSchema('user_project_metadata', cols) + + def test_user_domain_metadata_model(self): + cols = (('user_id', sql.String, 64), + ('domain_id', sql.String, 64), + ('data', sql.JsonBlob, None)) + self.assertExpectedSchema('user_domain_metadata', cols) + + def test_group_project_metadata_model(self): + cols = (('group_id', sql.String, 64), + ('project_id', sql.String, 64), + ('data', sql.JsonBlob, None)) + self.assertExpectedSchema('group_project_metadata', cols) + + def test_group_domain_metadata_model(self): + cols = (('group_id', sql.String, 64), + ('domain_id', sql.String, 64), + ('data', sql.JsonBlob, None)) + self.assertExpectedSchema('group_domain_metadata', cols) + + def test_user_group_membership(self): + cols = (('group_id', sql.String, 64), + ('user_id', sql.String, 64)) + self.assertExpectedSchema('user_group_membership', cols) + + class SqlIdentity(SqlTests, test_backend.IdentityTests): def test_password_hashed(self): session = self.identity_api.get_session() @@ -132,33 +222,51 @@ class SqlIdentity(SqlTests, test_backend.IdentityTests): tenants = self.identity_api.get_projects_for_user(user['id']) self.assertEquals(tenants, []) - def test_delete_user_with_metadata(self): - user = {'id': 'fake', - 'name': 'fakeuser', + def test_metadata_removed_on_delete_user(self): + # A test to check that the internal representation + # or roles is correctly updated when a user is deleted + user = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, 'domain_id': DEFAULT_DOMAIN_ID, 'password': 'passwd'} - self.identity_api.create_user('fake', user) - self.identity_api.create_metadata(user['id'], - self.tenant_bar['id'], - {'extra': 'extra'}) + self.identity_api.create_user(user['id'], user) + role = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex} + self.identity_api.create_role(role['id'], role) + self.identity_api.add_role_to_user_and_project( + user['id'], + self.tenant_bar['id'], + role['id']) self.identity_api.delete_user(user['id']) + + # Now check whether the internal representation of roles + # has been deleted self.assertRaises(exception.MetadataNotFound, - self.identity_api.get_metadata, + self.assignment_api._get_metadata, user['id'], self.tenant_bar['id']) - def test_delete_project_with_metadata(self): - user = {'id': 'fake', - 'name': 'fakeuser', + def test_metadata_removed_on_delete_project(self): + # A test to check that the internal representation + # or roles is correctly updated when a project is deleted + user = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, 'domain_id': DEFAULT_DOMAIN_ID, 'password': 'passwd'} - self.identity_api.create_user('fake', user) - self.identity_api.create_metadata(user['id'], - self.tenant_bar['id'], - {'extra': 'extra'}) + self.identity_api.create_user(user['id'], user) + role = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex} + self.identity_api.create_role(role['id'], role) + self.identity_api.add_role_to_user_and_project( + user['id'], + self.tenant_bar['id'], + role['id']) self.identity_api.delete_project(self.tenant_bar['id']) + + # Now check whether the internal representation of roles + # has been deleted self.assertRaises(exception.MetadataNotFound, - self.identity_api.get_metadata, + self.assignment_api._get_metadata, user['id'], self.tenant_bar['id']) @@ -302,3 +410,7 @@ class SqlCatalog(SqlTests, test_backend.CatalogTests): class SqlPolicy(SqlTests, test_backend.PolicyTests): pass + + +class SqlInheritance(SqlTests, test_backend.InheritanceTests): + pass diff --git a/tests/test_cert_setup.py b/tests/test_cert_setup.py index 74e5466a..e6c395e9 100644 --- a/tests/test_cert_setup.py +++ b/tests/test_cert_setup.py @@ -61,7 +61,6 @@ class CertSetupTestCase(test.TestCase): self.controller = token.controllers.Auth() def test_can_handle_missing_certs(self): - self.opt_in_group('signing', token_format='PKI') self.opt_in_group('signing', certfile='invalid') user = { 'id': 'fake1', diff --git a/tests/test_content_types.py b/tests/test_content_types.py index 820dc0dd..3213656c 100644 --- a/tests/test_content_types.py +++ b/tests/test_content_types.py @@ -23,6 +23,7 @@ import webtest from keystone import test +from keystone.common import extension from keystone.common import serializer from keystone.openstack.common import jsonutils @@ -70,14 +71,12 @@ class RestfulTestCase(test.TestCase): self.admin_app = webtest.TestApp( self.loadapp('keystone', name='admin')) - # TODO(termie): is_admin is being deprecated once the policy stuff - # is all working # TODO(termie): add an admin user to the fixtures and use that user # override the fixtures, for now - self.metadata_foobar = self.identity_api.update_metadata( + self.metadata_foobar = self.identity_api.add_role_to_user_and_project( self.user_foo['id'], self.tenant_bar['id'], - dict(roles=[self.role_admin['id']], is_admin='1')) + self.role_admin['id']) def tearDown(self): """Kill running servers and release references to avoid leaks.""" @@ -336,14 +335,14 @@ class CoreApiTests(object): self.assertValidVersionResponse(r) def test_public_extensions(self): - self.public_request(path='/v2.0/extensions',) - - # TODO(dolph): can't test this without any public extensions defined - # self.assertValidExtensionListResponse(r) + r = self.public_request(path='/v2.0/extensions') + self.assertValidExtensionListResponse(r, + extension.PUBLIC_EXTENSIONS) def test_admin_extensions(self): - r = self.admin_request(path='/v2.0/extensions',) - self.assertValidExtensionListResponse(r) + r = self.admin_request(path='/v2.0/extensions') + self.assertValidExtensionListResponse(r, + extension.ADMIN_EXTENSIONS) def test_admin_extensions_404(self): self.admin_request(path='/v2.0/extensions/invalid-extension', @@ -355,7 +354,8 @@ class CoreApiTests(object): def test_admin_osksadm_extension(self): r = self.admin_request(path='/v2.0/extensions/OS-KSADM') - self.assertValidExtensionResponse(r) + self.assertValidExtensionResponse(r, + extension.ADMIN_EXTENSIONS) def test_authenticate(self): r = self.public_request( @@ -403,10 +403,10 @@ class CoreApiTests(object): self.assertValidAuthenticationResponse(r) def test_validate_token_service_role(self): - self.metadata_foobar = self.identity_api.update_metadata( + self.metadata_foobar = self.identity_api.add_role_to_user_and_project( self.user_foo['id'], self.tenant_service['id'], - dict(roles=[self.role_service['id']])) + self.role_service['id']) token = self.get_scoped_token(tenant_id='service') r = self.admin_request( @@ -613,24 +613,26 @@ class JsonTestCase(RestfulTestCase, CoreApiTests): self.assertValidError(r.result['error']) self.assertEqual(r.result['error']['code'], r.status_code) - def assertValidExtension(self, extension): + def assertValidExtension(self, extension, expected): super(JsonTestCase, self).assertValidExtension(extension) - - self.assertIsNotNone(extension.get('description')) + descriptions = [ext['description'] for ext in expected.itervalues()] + description = extension.get('description') + self.assertIsNotNone(description) + self.assertIn(description, descriptions) self.assertIsNotNone(extension.get('links')) self.assertNotEmpty(extension.get('links')) for link in extension.get('links'): self.assertValidExtensionLink(link) - def assertValidExtensionListResponse(self, r): + def assertValidExtensionListResponse(self, r, expected): self.assertIsNotNone(r.result.get('extensions')) self.assertIsNotNone(r.result['extensions'].get('values')) self.assertNotEmpty(r.result['extensions'].get('values')) for extension in r.result['extensions']['values']: - self.assertValidExtension(extension) + self.assertValidExtension(extension, expected) - def assertValidExtensionResponse(self, r): - self.assertValidExtension(r.result.get('extension')) + def assertValidExtensionResponse(self, r, expected): + self.assertValidExtension(r.result.get('extension'), expected) def assertValidAuthenticationResponse(self, r, require_service_catalog=False): @@ -852,29 +854,31 @@ class XmlTestCase(RestfulTestCase, CoreApiTests): self.assertValidError(xml) self.assertEqual(xml.get('code'), str(r.status_code)) - def assertValidExtension(self, extension): + def assertValidExtension(self, extension, expected): super(XmlTestCase, self).assertValidExtension(extension) self.assertIsNotNone(extension.find(self._tag('description'))) self.assertTrue(extension.find(self._tag('description')).text) links = extension.find(self._tag('links')) self.assertNotEmpty(links.findall(self._tag('link'))) + descriptions = [ext['description'] for ext in expected.itervalues()] + description = extension.find(self._tag('description')).text + self.assertIn(description, descriptions) for link in links.findall(self._tag('link')): self.assertValidExtensionLink(link) - def assertValidExtensionListResponse(self, r): + def assertValidExtensionListResponse(self, r, expected): xml = r.result self.assertEqual(xml.tag, self._tag('extensions')) - self.assertNotEmpty(xml.findall(self._tag('extension'))) - for extension in xml.findall(self._tag('extension')): - self.assertValidExtension(extension) + for ext in xml.findall(self._tag('extension')): + self.assertValidExtension(ext, expected) - def assertValidExtensionResponse(self, r): + def assertValidExtensionResponse(self, r, expected): xml = r.result self.assertEqual(xml.tag, self._tag('extension')) - self.assertValidExtension(xml) + self.assertValidExtension(xml, expected) def assertValidVersion(self, version): super(XmlTestCase, self).assertValidVersion(version) diff --git a/tests/test_drivers.py b/tests/test_drivers.py index 439b0d30..c83c1a89 100644 --- a/tests/test_drivers.py +++ b/tests/test_drivers.py @@ -1,6 +1,7 @@ import inspect import unittest2 as unittest +from keystone import assignment from keystone import catalog from keystone import exception from keystone import identity @@ -35,6 +36,10 @@ class TestDrivers(unittest.TestCase): if name[0] != '_' and callable(method): self.assertMethodNotImplemented(method) + def test_assignment_driver_unimplemented(self): + interface = assignment.Driver() + self.assertInterfaceNotImplemented(interface) + def test_catalog_driver_unimplemented(self): interface = catalog.Driver() self.assertInterfaceNotImplemented(interface) diff --git a/tests/test_exception.py b/tests/test_exception.py index ef06f633..33250835 100644 --- a/tests/test_exception.py +++ b/tests/test_exception.py @@ -83,6 +83,15 @@ class ExceptionTestCase(test.TestCase): self.assertEqual('Forbidden', e.title) self.assertEqual('Forbidden', j['error'].get('title')) + def test_unicode_message(self): + message = u'Comment \xe7a va' + e = exception.Error(message) + + try: + self.assertEqual(unicode(e), message) + except UnicodeEncodeError: + self.fail("unicode error message not supported") + class SecurityErrorTestCase(ExceptionTestCase): """Tests whether security-related info is exposed to the API user.""" @@ -144,12 +153,3 @@ class SecurityErrorTestCase(ExceptionTestCase): e = exception.ForbiddenAction(action=risky_info) self.assertValidJsonRendering(e) self.assertIn(risky_info, str(e)) - - def test_unicode_message(self): - message = u'Comment \xe7a va' - e = exception.Error(message) - self.assertEqual(e.message, message) - try: - unicode(e) - except UnicodeEncodeError: - self.fail("unicode error message not supported") diff --git a/tests/test_import_legacy.py b/tests/test_import_legacy.py index bafc076a..9e164099 100644 --- a/tests/test_import_legacy.py +++ b/tests/test_import_legacy.py @@ -25,7 +25,6 @@ from keystone import test from keystone.catalog.backends import templated as catalog_templated from keystone.common.sql import legacy -from keystone.common.sql import util as sql_util from keystone import config from keystone import identity from keystone.identity.backends import sql as identity_sql @@ -41,17 +40,17 @@ class ImportLegacy(test.TestCase): test.testsdir('test_overrides.conf'), test.testsdir('backend_sql.conf'), test.testsdir('backend_sql_disk.conf')]) - sql_util.setup_test_database() + test.setup_test_database() self.identity_man = identity.Manager() self.identity_api = identity_sql.Identity() def tearDown(self): - sql_util.teardown_test_database() + test.teardown_test_database() super(ImportLegacy, self).tearDown() def setup_old_database(self, sql_dump): sql_path = test.testsdir(sql_dump) - db_path = test.testsdir('%s.db' % sql_dump) + db_path = test.tmpdir('%s.db' % sql_dump) try: os.unlink(db_path) except OSError: @@ -73,7 +72,7 @@ class ImportLegacy(test.TestCase): self.assertEquals(user_ref['enabled'], True) # check password hashing - user_ref, tenant_ref, metadata_ref = self.identity_man.authenticate( + user_ref = self.identity_man.authenticate( user_id=admin_id, password='secrete') # check catalog @@ -90,7 +89,7 @@ class ImportLegacy(test.TestCase): self.assertEquals(user_ref['enabled'], True) # check password hashing - user_ref, tenant_ref, metadata_ref = self.identity_man.authenticate( + user_ref = self.identity_man.authenticate( user_id=admin_id, password='secrete') # check catalog @@ -107,7 +106,7 @@ class ImportLegacy(test.TestCase): self.assertEquals(user_ref['enabled'], True) # check password hashing - user_ref, tenant_ref, metadata_ref = self.identity_man.authenticate( + user_ref = self.identity_man.authenticate( user_id=admin_id, password='secrete') # check catalog diff --git a/tests/test_keystoneclient.py b/tests/test_keystoneclient.py index b9828559..5c0d2f5b 100644 --- a/tests/test_keystoneclient.py +++ b/tests/test_keystoneclient.py @@ -20,6 +20,7 @@ import webob import nose.exc from keystone import test +from keystone import token from keystone import config from keystone.openstack.common import jsonutils @@ -42,18 +43,18 @@ class CompatTestCase(test.TestCase): self.clear_module('keystoneclient') self.load_backends() + self.token_provider_api = token.provider.Manager() self.load_fixtures(default_fixtures) self.public_server = self.serveapp('keystone', name='main') self.admin_server = self.serveapp('keystone', name='admin') - # TODO(termie): is_admin is being deprecated once the policy stuff - # is all working # TODO(termie): add an admin user to the fixtures and use that user # override the fixtures, for now - self.metadata_foobar = self.identity_api.update_metadata( - self.user_foo['id'], self.tenant_bar['id'], - dict(roles=[self.role_admin['id']], is_admin='1')) + self.metadata_foobar = self.identity_api.add_role_to_user_and_project( + self.user_foo['id'], + self.tenant_bar['id'], + self.role_admin['id']) def tearDown(self): self.public_server.kill() @@ -840,6 +841,28 @@ class KcMasterTestCase(CompatTestCase, KeystoneClientTests): def get_checkout(self): return KEYSTONECLIENT_REPO, 'master' + def test_ec2_auth(self): + client = self.get_client() + cred = client.ec2.create(user_id=self.user_foo['id'], + tenant_id=self.tenant_bar['id']) + + from keystoneclient.contrib.ec2 import utils as ec2_utils + signer = ec2_utils.Ec2Signer(cred.secret) + credentials = {'params': {'SignatureVersion': '2'}, + 'access': cred.access, + 'verb': 'GET', + 'host': 'localhost', + 'path': '/thisisgoingtowork'} + signature = signer.generate(credentials) + credentials['signature'] = signature + url = '%s/ec2tokens' % (client.auth_url) + (resp, token) = client.request(url=url, + method='POST', + body={'credentials': credentials}) + # make sure we have a v2 token + self.assertEqual(resp.status_code, 200) + self.assertIn('access', token) + def test_tenant_add_and_remove_user(self): client = self.get_client(admin=True) client.roles.add_user_role(tenant=self.tenant_bar['id'], diff --git a/tests/test_migrate_nova_auth.py b/tests/test_migrate_nova_auth.py deleted file mode 100644 index bd25b18e..00000000 --- a/tests/test_migrate_nova_auth.py +++ /dev/null @@ -1,157 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 OpenStack LLC -# -# 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 uuid - -from keystone import test - -from keystone.common.sql import nova -from keystone.common.sql import util as sql_util -from keystone import config -from keystone.contrib.ec2.backends import sql as ec2_sql -from keystone import identity - - -CONF = config.CONF -DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id - - -FIXTURE = { - 'users': [ - {'id': 'user1', 'name': 'uname1', 'password': 'acc1'}, - {'id': 'user4', 'name': 'uname4', 'password': 'acc1'}, - {'id': 'user2', 'name': 'uname2', 'password': 'acc2'}, - {'id': 'user3', 'name': 'uname3', 'password': 'acc3'}, - ], - 'roles': ['role1', 'role2', 'role3'], - 'role_user_tenant_list': [ - {'user_id': 'user1', 'role': 'role1', 'tenant_id': 'proj1'}, - {'user_id': 'user1', 'role': 'role2', 'tenant_id': 'proj1'}, - {'user_id': 'user4', 'role': 'role1', 'tenant_id': 'proj4'}, - {'user_id': 'user2', 'role': 'role1', 'tenant_id': 'proj1'}, - {'user_id': 'user2', 'role': 'role1', 'tenant_id': 'proj2'}, - {'user_id': 'user2', 'role': 'role2', 'tenant_id': 'proj2'}, - {'user_id': 'user3', 'role': 'role3', 'tenant_id': 'proj1'}, - ], - 'user_tenant_list': [ - {'tenant_id': 'proj1', 'user_id': 'user1'}, - {'tenant_id': 'proj4', 'user_id': 'user4'}, - {'tenant_id': 'proj1', 'user_id': 'user2'}, - {'tenant_id': 'proj2', 'user_id': 'user2'}, - {'tenant_id': 'proj1', 'user_id': 'user3'}, - ], - 'ec2_credentials': [ - {'access_key': 'acc1', 'secret_key': 'sec1', 'user_id': 'user1'}, - {'access_key': 'acc4', 'secret_key': 'sec4', 'user_id': 'user4'}, - {'access_key': 'acc2', 'secret_key': 'sec2', 'user_id': 'user2'}, - {'access_key': 'acc3', 'secret_key': 'sec3', 'user_id': 'user3'}, - ], - 'tenants': [ - {'description': 'desc1', 'id': 'proj1', 'name': 'pname1'}, - {'description': 'desc4', 'id': 'proj4', 'name': 'pname4'}, - {'description': 'desc2', 'id': 'proj2', 'name': 'pname2'}, - ], -} - - -class MigrateNovaAuth(test.TestCase): - def setUp(self): - super(MigrateNovaAuth, self).setUp() - self.config([test.etcdir('keystone.conf.sample'), - test.testsdir('test_overrides.conf'), - test.testsdir('backend_sql.conf'), - test.testsdir('backend_sql_disk.conf')]) - sql_util.setup_test_database() - self.identity_api = identity.Manager() - self.ec2_api = ec2_sql.Ec2() - - def tearDown(self): - sql_util.teardown_test_database() - super(MigrateNovaAuth, self).tearDown() - - def _create_role(self, role_name): - role_id = uuid.uuid4().hex - role_dict = {'id': role_id, 'name': role_name} - self.identity_api.create_role(role_id, role_dict) - - def test_import(self): - self._create_role('role1') - - nova.import_auth(FIXTURE) - - users = {} - for user in ['user1', 'user2', 'user3', 'user4']: - users[user] = self.identity_api.get_user_by_name( - user, DEFAULT_DOMAIN_ID) - - tenants = {} - for tenant in ['proj1', 'proj2', 'proj4']: - tenants[tenant] = self.identity_api.get_project_by_name( - tenant, DEFAULT_DOMAIN_ID) - - membership_map = { - 'user1': ['proj1'], - 'user2': ['proj1', 'proj2'], - 'user3': ['proj1'], - 'user4': ['proj4'], - } - - for (old_user, old_projects) in membership_map.iteritems(): - user = users[old_user] - membership = self.identity_api.get_projects_for_user(user['id']) - expected = [tenants[t]['id'] for t in old_projects] - self.assertEqual(set(expected), set(membership)) - for tenant_id in membership: - password = None - for _user in FIXTURE['users']: - if _user['id'] == old_user: - password = _user['password'] - self.identity_api.authenticate(user['id'], tenant_id, password) - - for ec2_cred in FIXTURE['ec2_credentials']: - user_id = users[ec2_cred['user_id']]['id'] - for tenant_id in self.identity_api.get_projects_for_user(user_id): - access = '%s:%s' % (tenant_id, ec2_cred['access_key']) - cred = self.ec2_api.get_credential(access) - actual = cred['secret'] - expected = ec2_cred['secret_key'] - self.assertEqual(expected, actual) - - roles = self.identity_api.list_roles() - role_names = set([role['name'] for role in roles]) - self.assertEqual(role_names, set(['role2', 'role1', 'role3', - CONF.member_role_name])) - - assignment_map = { - 'user1': {'proj1': ['role1', 'role2']}, - 'user2': {'proj1': ['role1'], 'proj2': ['role1', 'role2']}, - 'user3': {'proj1': ['role3']}, - 'user4': {'proj4': ['role1']}, - } - - for (old_user, old_project_map) in assignment_map.iteritems(): - tenant_names = ['proj1', 'proj2', 'proj4'] - for tenant_name in tenant_names: - user = users[old_user] - tenant = tenants[tenant_name] - roles = self.identity_api.get_roles_for_user_and_project( - user['id'], tenant['id']) - actual = [self.identity_api.get_role(role_id)['name'] - for role_id in roles] - if CONF.member_role_name in actual: - actual.remove(CONF.member_role_name) - expected = old_project_map.get(tenant_name, []) - self.assertEqual(set(actual), set(expected)) diff --git a/tests/test_no_admin_token_auth.py b/tests/test_no_admin_token_auth.py index 93601140..ffdaa7a8 100644 --- a/tests/test_no_admin_token_auth.py +++ b/tests/test_no_admin_token_auth.py @@ -14,7 +14,7 @@ def _generate_paste_config(): new_contents = contents.replace(' admin_token_auth ', ' ') - with open('no_admin_token_auth-paste.ini', 'w') as f: + with open(test.tmpdir('no_admin_token_auth-paste.ini'), 'w') as f: f.write(new_contents) @@ -26,12 +26,12 @@ class TestNoAdminTokenAuth(test.TestCase): _generate_paste_config() self.admin_app = webtest.TestApp( - self.loadapp('no_admin_token_auth', name='admin'), + self.loadapp(test.tmpdir('no_admin_token_auth'), name='admin'), extra_environ=dict(REMOTE_ADDR='127.0.0.1')) def tearDown(self): self.admin_app = None - os.remove('no_admin_token_auth-paste.ini') + os.remove(test.tmpdir('no_admin_token_auth-paste.ini')) def test_request_no_admin_token_auth(self): # This test verifies that if the admin_token_auth middleware isn't diff --git a/tests/test_pki_token_provider.conf b/tests/test_pki_token_provider.conf new file mode 100644 index 00000000..ec8df231 --- /dev/null +++ b/tests/test_pki_token_provider.conf @@ -0,0 +1,5 @@ +[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 8be80e30..bb413485 100644 --- a/tests/test_sql_core.py +++ b/tests/test_sql_core.py @@ -162,3 +162,13 @@ class TestBase(test.TestCase): engine1 = base.get_engine() engine2 = base.get_engine(allow_global_engine=False) self.assertIsNot(engine1, engine2) + + def test_get_session(self): + # autocommit and expire_on_commit flags to get_session() are passed on + # to the session created. + + base = sql.Base() + session = base.get_session(autocommit=False, expire_on_commit=True) + + self.assertFalse(session.autocommit) + self.assertTrue(session.expire_on_commit) diff --git a/tests/test_sql_upgrade.py b/tests/test_sql_upgrade.py index 21db6ade..64bf53c8 100644 --- a/tests/test_sql_upgrade.py +++ b/tests/test_sql_upgrade.py @@ -506,6 +506,10 @@ class SqlUpgradeTests(test.TestCase): def test_downgrade_to_0(self): self.upgrade(self.max_version) + + if self.engine.name == 'mysql': + self._mysql_check_all_tables_innodb() + self.downgrade(0) for table_name in ["user", "token", "role", "user_tenant_membership", "metadata"]: @@ -820,6 +824,304 @@ class SqlUpgradeTests(test.TestCase): self.assertEqual(ref.legacy_endpoint_id, legacy_endpoint_id) self.assertEqual(ref.extra, '{}') + def test_group_project_FK_fixup(self): + # To create test data we must start before we broke in the + # group_project_metadata table in 015. + self.upgrade(14) + session = self.Session() + + domain_table = sqlalchemy.Table('domain', self.metadata, autoload=True) + group_table = sqlalchemy.Table('group', self.metadata, autoload=True) + tenant_table = sqlalchemy.Table('tenant', self.metadata, autoload=True) + role_table = sqlalchemy.Table('role', self.metadata, autoload=True) + group_project_metadata_table = sqlalchemy.Table( + 'group_project_metadata', self.metadata, autoload=True) + + # Create a Domain + domain = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, + 'enabled': True} + session.execute(domain_table.insert().values(domain)) + + # Create two Tenants + tenant = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, + 'extra': "{}"} + session.execute(tenant_table.insert().values(tenant)) + + tenant1 = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, + 'extra': "{}"} + session.execute(tenant_table.insert().values(tenant1)) + + # Create a Group + group = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, + 'domain_id': domain['id'], + 'extra': json.dumps({})} + session.execute(group_table.insert().values(group)) + + # Create roles + role_list = [] + for _ in range(2): + role = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + session.execute(role_table.insert().values(role)) + role_list.append(role) + + # Grant Role to User on Project + role_grant = {'group_id': group['id'], + 'project_id': tenant['id'], + 'data': json.dumps({'roles': [role_list[0]['id']]})} + session.execute( + group_project_metadata_table.insert().values(role_grant)) + + role_grant = {'group_id': group['id'], + 'project_id': tenant1['id'], + 'data': json.dumps({'roles': [role_list[1]['id']]})} + session.execute( + group_project_metadata_table.insert().values(role_grant)) + + session.commit() + + # Now upgrade and fix up the FKs + self.upgrade(28) + self.assertTableExists('group_project_metadata') + self.assertTableExists('project') + self.assertTableDoesNotExist('tenant') + + s = sqlalchemy.select([group_project_metadata_table.c.data]).where( + (group_project_metadata_table.c.group_id == group['id']) & + (group_project_metadata_table.c.project_id == tenant['id'])) + r = session.execute(s) + data = json.loads(r.fetchone()['data']) + self.assertEqual(len(data['roles']), 1) + self.assertIn(role_list[0]['id'], data['roles']) + + s = sqlalchemy.select([group_project_metadata_table.c.data]).where( + (group_project_metadata_table.c.group_id == group['id']) & + (group_project_metadata_table.c.project_id == tenant1['id'])) + r = session.execute(s) + data = json.loads(r.fetchone()['data']) + self.assertEqual(len(data['roles']), 1) + self.assertIn(role_list[1]['id'], data['roles']) + + self.downgrade(27) + self.assertTableExists('group_project_metadata') + self.assertTableExists('project') + self.assertTableDoesNotExist('tenant') + + def test_assignment_metadata_migration(self): + self.upgrade(28) + # Scaffolding + session = self.Session() + + domain_table = sqlalchemy.Table('domain', self.metadata, autoload=True) + user_table = sqlalchemy.Table('user', self.metadata, autoload=True) + group_table = sqlalchemy.Table('group', self.metadata, autoload=True) + role_table = sqlalchemy.Table('role', self.metadata, autoload=True) + project_table = sqlalchemy.Table( + 'project', self.metadata, autoload=True) + user_project_metadata_table = sqlalchemy.Table( + 'user_project_metadata', self.metadata, autoload=True) + user_domain_metadata_table = sqlalchemy.Table( + 'user_domain_metadata', self.metadata, autoload=True) + group_project_metadata_table = sqlalchemy.Table( + 'group_project_metadata', self.metadata, autoload=True) + group_domain_metadata_table = sqlalchemy.Table( + 'group_domain_metadata', self.metadata, autoload=True) + + # Create a Domain + domain = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, + 'enabled': True} + session.execute(domain_table.insert().values(domain)) + + # Create anther Domain + domain2 = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, + 'enabled': True} + session.execute(domain_table.insert().values(domain2)) + + # Create a Project + project = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, + 'domain_id': domain['id'], + 'extra': "{}"} + session.execute(project_table.insert().values(project)) + + # Create another Project + project2 = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, + 'domain_id': domain['id'], + 'extra': "{}"} + session.execute(project_table.insert().values(project2)) + + # Create a User + user = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, + 'domain_id': domain['id'], + 'password': uuid.uuid4().hex, + 'enabled': True, + 'extra': json.dumps({})} + session.execute(user_table.insert().values(user)) + + # Create a Group + group = {'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, + 'domain_id': domain['id'], + 'extra': json.dumps({})} + session.execute(group_table.insert().values(group)) + + # Create roles + role_list = [] + for _ in range(7): + role = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + session.execute(role_table.insert().values(role)) + role_list.append(role) + + # Grant Role to User on Project + role_grant = {'user_id': user['id'], + 'project_id': project['id'], + 'data': json.dumps({'roles': [role_list[0]['id']]})} + session.execute( + user_project_metadata_table.insert().values(role_grant)) + + role_grant = {'user_id': user['id'], + 'project_id': project2['id'], + 'data': json.dumps({'roles': [role_list[1]['id']]})} + session.execute( + user_project_metadata_table.insert().values(role_grant)) + + # Grant Role to Group on different Project + role_grant = {'group_id': group['id'], + 'project_id': project2['id'], + 'data': json.dumps({'roles': [role_list[2]['id']]})} + session.execute( + group_project_metadata_table.insert().values(role_grant)) + + # Grant Role to User on Domain + role_grant = {'user_id': user['id'], + 'domain_id': domain['id'], + 'data': json.dumps({'roles': [role_list[3]['id']]})} + session.execute(user_domain_metadata_table.insert().values(role_grant)) + + # Grant Role to Group on Domain + role_grant = {'group_id': group['id'], + 'domain_id': domain['id'], + 'data': json.dumps( + {'roles': [role_list[4]['id']], + 'other': 'somedata'})} + session.execute( + group_domain_metadata_table.insert().values(role_grant)) + + session.commit() + + self.upgrade(29) + s = sqlalchemy.select([user_project_metadata_table.c.data]).where( + (user_project_metadata_table.c.user_id == user['id']) & + (user_project_metadata_table.c.project_id == project['id'])) + r = session.execute(s) + data = json.loads(r.fetchone()['data']) + self.assertEqual(len(data['roles']), 1) + self.assertIn({'id': role_list[0]['id']}, data['roles']) + + s = sqlalchemy.select([user_project_metadata_table.c.data]).where( + (user_project_metadata_table.c.user_id == user['id']) & + (user_project_metadata_table.c.project_id == project2['id'])) + r = session.execute(s) + data = json.loads(r.fetchone()['data']) + self.assertEqual(len(data['roles']), 1) + self.assertIn({'id': role_list[1]['id']}, data['roles']) + + s = sqlalchemy.select([group_project_metadata_table.c.data]).where( + (group_project_metadata_table.c.group_id == group['id']) & + (group_project_metadata_table.c.project_id == project2['id'])) + r = session.execute(s) + data = json.loads(r.fetchone()['data']) + self.assertEqual(len(data['roles']), 1) + self.assertIn({'id': role_list[2]['id']}, data['roles']) + + s = sqlalchemy.select([user_domain_metadata_table.c.data]).where( + (user_domain_metadata_table.c.user_id == user['id']) & + (user_domain_metadata_table.c.domain_id == domain['id'])) + r = session.execute(s) + data = json.loads(r.fetchone()['data']) + self.assertEqual(len(data['roles']), 1) + self.assertIn({'id': role_list[3]['id']}, data['roles']) + + s = sqlalchemy.select([group_domain_metadata_table.c.data]).where( + (group_domain_metadata_table.c.group_id == group['id']) & + (group_domain_metadata_table.c.domain_id == domain['id'])) + r = session.execute(s) + data = json.loads(r.fetchone()['data']) + self.assertEqual(len(data['roles']), 1) + self.assertIn({'id': role_list[4]['id']}, data['roles']) + self.assertIn('other', data) + + # Now add an entry that has one regular and one inherited role + role_grant = {'user_id': user['id'], + 'domain_id': domain2['id'], + 'data': json.dumps( + {'roles': [{'id': role_list[5]['id']}, + {'id': role_list[6]['id'], + 'inherited_to': 'projects'}]})} + session.execute(user_domain_metadata_table.insert().values(role_grant)) + + session.commit() + self.downgrade(28) + + s = sqlalchemy.select([user_project_metadata_table.c.data]).where( + (user_project_metadata_table.c.user_id == user['id']) & + (user_project_metadata_table.c.project_id == project['id'])) + r = session.execute(s) + data = json.loads(r.fetchone()['data']) + self.assertEqual(len(data['roles']), 1) + self.assertIn(role_list[0]['id'], data['roles']) + + s = sqlalchemy.select([user_project_metadata_table.c.data]).where( + (user_project_metadata_table.c.user_id == user['id']) & + (user_project_metadata_table.c.project_id == project2['id'])) + r = session.execute(s) + data = json.loads(r.fetchone()['data']) + self.assertEqual(len(data['roles']), 1) + self.assertIn(role_list[1]['id'], data['roles']) + + s = sqlalchemy.select([group_project_metadata_table.c.data]).where( + (group_project_metadata_table.c.group_id == group['id']) & + (group_project_metadata_table.c.project_id == project2['id'])) + r = session.execute(s) + data = json.loads(r.fetchone()['data']) + self.assertEqual(len(data['roles']), 1) + self.assertIn(role_list[2]['id'], data['roles']) + + s = sqlalchemy.select([user_domain_metadata_table.c.data]).where( + (user_domain_metadata_table.c.user_id == user['id']) & + (user_domain_metadata_table.c.domain_id == domain['id'])) + r = session.execute(s) + data = json.loads(r.fetchone()['data']) + self.assertEqual(len(data['roles']), 1) + self.assertIn(role_list[3]['id'], data['roles']) + + s = sqlalchemy.select([group_domain_metadata_table.c.data]).where( + (group_domain_metadata_table.c.group_id == group['id']) & + (group_domain_metadata_table.c.domain_id == domain['id'])) + r = session.execute(s) + data = json.loads(r.fetchone()['data']) + self.assertEqual(len(data['roles']), 1) + self.assertIn(role_list[4]['id'], data['roles']) + self.assertIn('other', data) + + # For user-domain2, where we had one regular and one inherited role, + # only the direct role should remain, the inherited role should + # have been deleted during the downgrade + s = sqlalchemy.select([user_domain_metadata_table.c.data]).where( + (user_domain_metadata_table.c.user_id == user['id']) & + (user_domain_metadata_table.c.domain_id == domain2['id'])) + r = session.execute(s) + data = json.loads(r.fetchone()['data']) + self.assertEqual(len(data['roles']), 1) + self.assertIn(role_list[5]['id'], data['roles']) + def populate_user_table(self, with_pass_enab=False, with_pass_enab_domain=False): # Populate the appropriate fields in the user @@ -961,3 +1263,26 @@ class SqlUpgradeTests(test.TestCase): for ver, change in changeset: self.schema.runchange(ver, change, changeset.step) self.assertEqual(self.schema.version, version) + + def _mysql_check_all_tables_innodb(self): + database = self.engine.url.database + + connection = self.engine.connect() + # sanity check + total = connection.execute("SELECT count(*) " + "from information_schema.TABLES " + "where TABLE_SCHEMA='%(database)s'" % + locals()) + self.assertTrue(total.scalar() > 0, "No tables found. Wrong schema?") + + noninnodb = connection.execute("SELECT table_name " + "from information_schema.TABLES " + "where TABLE_SCHEMA='%(database)s' " + "and ENGINE!='InnoDB' " + "and TABLE_NAME!='migrate_version'" % + locals()) + names = [x[0] for x in noninnodb] + self.assertEqual(names, [], + "Non-InnoDB tables exist") + + connection.close() diff --git a/tests/test_token_bind.py b/tests/test_token_bind.py new file mode 100644 index 00000000..20488a91 --- /dev/null +++ b/tests/test_token_bind.py @@ -0,0 +1,182 @@ +# Copyright 2013 OpenStack LLC +# +# 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 keystone.common import wsgi +from keystone import config +from keystone import exception +from keystone import test + +CONF = config.CONF + +KERBEROS_BIND = 'USER@REALM' + +# the only thing the function checks for is the presence of bind +TOKEN_BIND_KERB = {'bind': {'kerberos': KERBEROS_BIND}} +TOKEN_BIND_UNKNOWN = {'bind': {'FOO': 'BAR'}} +TOKEN_BIND_NONE = {} + +ANY = 'any' +ALL_TOKENS = [TOKEN_BIND_KERB, TOKEN_BIND_UNKNOWN, TOKEN_BIND_NONE] + + +class BindTest(test.TestCase): + """Test binding tokens to a Principal. + + Even though everything in this file references kerberos the same concepts + will apply to all future binding mechanisms. + """ + + def assert_kerberos_bind(self, tokens, bind_level, + use_kerberos=True, success=True): + if not isinstance(tokens, dict): + for token in tokens: + self.assert_kerberos_bind(token, bind_level, + use_kerberos=use_kerberos, + success=success) + elif use_kerberos == ANY: + for val in (True, False): + self.assert_kerberos_bind(tokens, bind_level, + use_kerberos=val, success=success) + else: + context = {} + CONF.token.enforce_token_bind = bind_level + + if use_kerberos: + context['REMOTE_USER'] = KERBEROS_BIND + context['AUTH_TYPE'] = 'Negotiate' + + if not success: + self.assertRaises(exception.Unauthorized, + wsgi.validate_token_bind, + context, tokens) + else: + wsgi.validate_token_bind(context, tokens) + + # DISABLED + + def test_bind_disabled_with_kerb_user(self): + self.assert_kerberos_bind(ALL_TOKENS, + bind_level='disabled', + use_kerberos=ANY, + success=True) + + # PERMISSIVE + + def test_bind_permissive_with_kerb_user(self): + self.assert_kerberos_bind(TOKEN_BIND_KERB, + bind_level='permissive', + use_kerberos=True, + success=True) + + def test_bind_permissive_with_regular_token(self): + self.assert_kerberos_bind(TOKEN_BIND_NONE, + bind_level='permissive', + use_kerberos=ANY, + success=True) + + def test_bind_permissive_without_kerb_user(self): + self.assert_kerberos_bind(TOKEN_BIND_KERB, + bind_level='permissive', + use_kerberos=False, + success=False) + + def test_bind_permissive_with_unknown_bind(self): + self.assert_kerberos_bind(TOKEN_BIND_UNKNOWN, + bind_level='permissive', + use_kerberos=ANY, + success=True) + + # STRICT + + def test_bind_strict_with_regular_token(self): + self.assert_kerberos_bind(TOKEN_BIND_NONE, + bind_level='strict', + use_kerberos=ANY, + success=True) + + def test_bind_strict_with_kerb_user(self): + self.assert_kerberos_bind(TOKEN_BIND_KERB, + bind_level='strict', + use_kerberos=True, + success=True) + + def test_bind_strict_without_kerb_user(self): + self.assert_kerberos_bind(TOKEN_BIND_KERB, + bind_level='strict', + use_kerberos=False, + success=False) + + def test_bind_strict_with_unknown_bind(self): + self.assert_kerberos_bind(TOKEN_BIND_UNKNOWN, + bind_level='strict', + use_kerberos=ANY, + success=False) + + # REQUIRED + + def test_bind_required_with_regular_token(self): + self.assert_kerberos_bind(TOKEN_BIND_NONE, + bind_level='required', + use_kerberos=ANY, + success=False) + + def test_bind_required_with_kerb_user(self): + self.assert_kerberos_bind(TOKEN_BIND_KERB, + bind_level='required', + use_kerberos=True, + success=True) + + def test_bind_required_without_kerb_user(self): + self.assert_kerberos_bind(TOKEN_BIND_KERB, + bind_level='required', + use_kerberos=False, + success=False) + + def test_bind_required_with_unknown_bind(self): + self.assert_kerberos_bind(TOKEN_BIND_UNKNOWN, + bind_level='required', + use_kerberos=ANY, + success=False) + + # NAMED + + def test_bind_named_with_regular_token(self): + self.assert_kerberos_bind(TOKEN_BIND_NONE, + bind_level='kerberos', + use_kerberos=ANY, + success=False) + + def test_bind_named_with_kerb_user(self): + self.assert_kerberos_bind(TOKEN_BIND_KERB, + bind_level='kerberos', + use_kerberos=True, + success=True) + + def test_bind_named_without_kerb_user(self): + self.assert_kerberos_bind(TOKEN_BIND_KERB, + bind_level='kerberos', + use_kerberos=False, + success=False) + + def test_bind_named_with_unknown_bind(self): + self.assert_kerberos_bind(TOKEN_BIND_UNKNOWN, + bind_level='kerberos', + use_kerberos=ANY, + success=False) + + def test_bind_named_with_unknown_scheme(self): + self.assert_kerberos_bind(ALL_TOKENS, + bind_level='unknown', + use_kerberos=ANY, + success=False) diff --git a/tests/test_token_provider.py b/tests/test_token_provider.py new file mode 100644 index 00000000..31205073 --- /dev/null +++ b/tests/test_token_provider.py @@ -0,0 +1,435 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack LLC +# +# 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 uuid + +from keystone import exception +from keystone import test +from keystone import token + + +SAMPLE_V2_TOKEN = { + "access": { + "trust": { + "id": "abc123", + "trustee_user_id": "123456" + }, + "serviceCatalog": [ + { + "endpoints": [ + { + "adminURL": "http://localhost:8774/v1.1/01257", + "id": "51934fe63a5b4ac0a32664f64eb462c3", + "internalURL": "http://localhost:8774/v1.1/01257", + "publicURL": "http://localhost:8774/v1.1/01257", + "region": "RegionOne" + } + ], + "endpoints_links": [], + "name": "nova", + "type": "compute" + }, + { + "endpoints": [ + { + "adminURL": "http://localhost:9292", + "id": "aaa17a539e364297a7845d67c7c7cc4b", + "internalURL": "http://localhost:9292", + "publicURL": "http://localhost:9292", + "region": "RegionOne" + } + ], + "endpoints_links": [], + "name": "glance", + "type": "image" + }, + { + "endpoints": [ + { + "adminURL": "http://localhost:8776/v1/01257", + "id": "077d82df25304abeac2294004441db5a", + "internalURL": "http://localhost:8776/v1/01257", + "publicURL": "http://localhost:8776/v1/01257", + "region": "RegionOne" + } + ], + "endpoints_links": [], + "name": "volume", + "type": "volume" + }, + { + "endpoints": [ + { + "adminURL": "http://localhost:8773/services/Admin", + "id": "b06997fd08414903ad458836efaa9067", + "internalURL": "http://localhost:8773/services/Cloud", + "publicURL": "http://localhost:8773/services/Cloud", + "region": "RegionOne" + } + ], + "endpoints_links": [], + "name": "ec2", + "type": "ec2" + }, + { + "endpoints": [ + { + "adminURL": "http://localhost:8888/v1", + "id": "7bd0c643e05a4a2ab40902b2fa0dd4e6", + "internalURL": "http://localhost:8888/v1/AUTH_01257", + "publicURL": "http://localhost:8888/v1/AUTH_01257", + "region": "RegionOne" + } + ], + "endpoints_links": [], + "name": "swift", + "type": "object-store" + }, + { + "endpoints": [ + { + "adminURL": "http://localhost:35357/v2.0", + "id": "02850c5d1d094887bdc46e81e1e15dc7", + "internalURL": "http://localhost:5000/v2.0", + "publicURL": "http://localhost:5000/v2.0", + "region": "RegionOne" + } + ], + "endpoints_links": [], + "name": "keystone", + "type": "identity" + } + ], + "token": { + "expires": "2013-05-22T00:02:43.941430Z", + "id": "ce4fc2d36eea4cc9a36e666ac2f1029a", + "issued_at": "2013-05-21T00:02:43.941473Z", + "tenant": { + "enabled": True, + "id": "01257", + "name": "service" + } + }, + "user": { + "id": "f19ddbe2c53c46f189fe66d0a7a9c9ce", + "name": "nova", + "roles": [ + { + "name": "_member_" + }, + { + "name": "admin" + } + ], + "roles_links": [], + "username": "nova" + } + } +} + +SAMPLE_V3_TOKEN = { + "token": { + "catalog": [ + { + "endpoints": [ + { + "id": "02850c5d1d094887bdc46e81e1e15dc7", + "interface": "admin", + "region": "RegionOne", + "url": "http://localhost:35357/v2.0" + }, + { + "id": "446e244b75034a9ab4b0811e82d0b7c8", + "interface": "internal", + "region": "RegionOne", + "url": "http://localhost:5000/v2.0" + }, + { + "id": "47fa3d9f499240abb5dfcf2668f168cd", + "interface": "public", + "region": "RegionOne", + "url": "http://localhost:5000/v2.0" + } + ], + "id": "26d7541715a44a4d9adad96f9872b633", + "type": "identity", + }, + { + "endpoints": [ + { + "id": "aaa17a539e364297a7845d67c7c7cc4b", + "interface": "admin", + "region": "RegionOne", + "url": "http://localhost:9292" + }, + { + "id": "4fa9620e42394cb1974736dce0856c71", + "interface": "internal", + "region": "RegionOne", + "url": "http://localhost:9292" + }, + { + "id": "9673687f9bc441d88dec37942bfd603b", + "interface": "public", + "region": "RegionOne", + "url": "http://localhost:9292" + } + ], + "id": "d27a41843f4e4b0e8cf6dac4082deb0d", + "type": "image", + }, + { + "endpoints": [ + { + "id": "7bd0c643e05a4a2ab40902b2fa0dd4e6", + "interface": "admin", + "region": "RegionOne", + "url": "http://localhost:8888/v1" + }, + { + "id": "43bef154594d4ccb8e49014d20624e1d", + "interface": "internal", + "region": "RegionOne", + "url": "http://localhost:8888/v1/AUTH_01257" + }, + { + "id": "e63b5f5d7aa3493690189d0ff843b9b3", + "interface": "public", + "region": "RegionOne", + "url": "http://localhost:8888/v1/AUTH_01257" + } + ], + "id": "a669e152f1104810a4b6701aade721bb", + "type": "object-store", + }, + { + "endpoints": [ + { + "id": "51934fe63a5b4ac0a32664f64eb462c3", + "interface": "admin", + "region": "RegionOne", + "url": "http://localhost:8774/v1.1/01257" + }, + { + "id": "869b535eea0d42e483ae9da0d868ebad", + "interface": "internal", + "region": "RegionOne", + "url": "http://localhost:8774/v1.1/01257" + }, + { + "id": "93583824c18f4263a2245ca432b132a6", + "interface": "public", + "region": "RegionOne", + "url": "http://localhost:8774/v1.1/01257" + } + ], + "id": "7f32cc2af6c9476e82d75f80e8b3bbb8", + "type": "compute", + }, + { + "endpoints": [ + { + "id": "b06997fd08414903ad458836efaa9067", + "interface": "admin", + "region": "RegionOne", + "url": "http://localhost:8773/services/Admin" + }, + { + "id": "411f7de7c9a8484c9b46c254fb2676e2", + "interface": "internal", + "region": "RegionOne", + "url": "http://localhost:8773/services/Cloud" + }, + { + "id": "f21c93f3da014785854b4126d0109c49", + "interface": "public", + "region": "RegionOne", + "url": "http://localhost:8773/services/Cloud" + } + ], + "id": "b08c9c7d4ef543eba5eeb766f72e5aa1", + "type": "ec2", + }, + { + "endpoints": [ + { + "id": "077d82df25304abeac2294004441db5a", + "interface": "admin", + "region": "RegionOne", + "url": "http://localhost:8776/v1/01257" + }, + { + "id": "875bf282362c40219665278b4fd11467", + "interface": "internal", + "region": "RegionOne", + "url": "http://localhost:8776/v1/01257" + }, + { + "id": "cd229aa6df0640dc858a8026eb7e640c", + "interface": "public", + "region": "RegionOne", + "url": "http://localhost:8776/v1/01257" + } + ], + "id": "5db21b82617f4a95816064736a7bec22", + "type": "volume", + } + ], + "expires_at": "2013-05-22T00:02:43.941430Z", + "issued_at": "2013-05-21T00:02:43.941473Z", + "methods": [ + "password" + ], + "project": { + "domain": { + "id": "default", + "name": "Default" + }, + "id": "01257", + "name": "service" + }, + "roles": [ + { + "id": "9fe2ff9ee4384b1894a90878d3e92bab", + "name": "_member_" + }, + { + "id": "53bff13443bd4450b97f978881d47b18", + "name": "admin" + } + ], + "user": { + "domain": { + "id": "default", + "name": "Default" + }, + "id": "f19ddbe2c53c46f189fe66d0a7a9c9ce", + "name": "nova" + }, + "OS-TRUST:trust": { + "id": "abc123", + "trustee_user_id": "123456", + "trustor_user_id": "333333", + "impersonation": False + } + } +} + + +class TestTokenProvider(test.TestCase): + def setUp(self): + super(TestTokenProvider, self).setUp() + self.load_backends() + self.token_provider_api = token.provider.Manager() + + def test_get_token_version(self): + self.assertEqual( + token.provider.V2, + self.token_provider_api.get_token_version(SAMPLE_V2_TOKEN)) + self.assertEqual( + token.provider.V3, + self.token_provider_api.get_token_version(SAMPLE_V3_TOKEN)) + self.assertRaises(token.provider.UnsupportedTokenVersionException, + self.token_provider_api.get_token_version, + 'bogus') + + def test_issue_token(self): + self.assertRaises(token.provider.UnsupportedTokenVersionException, + self.token_provider_api.issue_token, + 'bogus_version') + + def test_validate_token(self): + self.assertRaises(token.provider.UnsupportedTokenVersionException, + self.token_provider_api.validate_token, + uuid.uuid4().hex, + None, + 'bogus_version') + + def test_token_format_provider_mismatch(self): + self.opt_in_group('signing', token_format='UUID') + self.opt_in_group('token', + provider=token.provider.PKI_PROVIDER) + try: + token.provider.Manager() + raise Exception( + 'expecting ValueError on token provider misconfiguration') + except exception.UnexpectedError: + pass + + self.opt_in_group('signing', token_format='PKI') + self.opt_in_group('token', + provider=token.provider.UUID_PROVIDER) + try: + token.provider.Manager() + raise Exception( + 'expecting ValueError on token provider misconfiguration') + except exception.UnexpectedError: + pass + + # should be OK as token_format and provider aligns + self.opt_in_group('signing', token_format='PKI') + self.opt_in_group('token', + provider=token.provider.PKI_PROVIDER) + token.provider.Manager() + + self.opt_in_group('signing', token_format='UUID') + self.opt_in_group('token', + provider=token.provider.UUID_PROVIDER) + token.provider.Manager() + + # custom provider should be OK too + self.opt_in_group('signing', token_format='CUSTOM') + self.opt_in_group('token', + provider=token.provider.PKI_PROVIDER) + token.provider.Manager() + + def test_default_token_format(self): + self.assertEqual(token.provider.Manager.check_and_get_token_provider(), + token.provider.PKI_PROVIDER) + + def test_uuid_token_format_and_no_provider(self): + self.opt_in_group('signing', token_format='UUID') + self.assertEqual(token.provider.Manager.check_and_get_token_provider(), + token.provider.UUID_PROVIDER) + + def test_unsupported_token_format(self): + self.opt_in_group('signing', token_format='CUSTOM') + self.assertRaises(exception.UnexpectedError, + token.provider.Manager.check_and_get_token_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.check_and_get_token_provider) + + self.opt_in_group('signing', token_format='UUID') + self.opt_in_group('token', + provider=token.provider.UUID_PROVIDER) + self.assertEqual(token.provider.Manager.check_and_get_token_provider(), + token.provider.UUID_PROVIDER) + + self.opt_in_group('signing', token_format='PKI') + self.opt_in_group('token', + provider=token.provider.PKI_PROVIDER) + self.assertEqual(token.provider.Manager.check_and_get_token_provider(), + token.provider.PKI_PROVIDER) + + self.opt_in_group('signing', token_format='CUSTOM') + self.opt_in_group('token', + provider='my.package.MyProvider') + self.assertEqual(token.provider.Manager.check_and_get_token_provider(), + 'my.package.MyProvider') diff --git a/tests/test_uuid_token_provider.conf b/tests/test_uuid_token_provider.conf new file mode 100644 index 00000000..d1ac5fdf --- /dev/null +++ b/tests/test_uuid_token_provider.conf @@ -0,0 +1,5 @@ +[signing] +token_format = UUID + +[token] +provider = keystone.token.providers.uuid.Provider diff --git a/tests/test_v3.py b/tests/test_v3.py index 52d201bc..60a52d69 100644 --- a/tests/test_v3.py +++ b/tests/test_v3.py @@ -5,10 +5,10 @@ from lxml import etree import webtest from keystone import test +from keystone import token from keystone import auth from keystone.common import serializer -from keystone.common.sql import util as sql_util from keystone import config from keystone.openstack.common import timeutils from keystone.policy.backends import rules @@ -23,6 +23,15 @@ TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ' class RestfulTestCase(test_content_types.RestfulTestCase): + _config_file_list = [test.etcdir('keystone.conf.sample'), + test.testsdir('test_overrides.conf'), + test.testsdir('backend_sql.conf'), + test.testsdir('backend_sql_disk.conf')] + + #override this to sepcify the complete list of configuration files + def config_files(self): + return self._config_file_list + def setUp(self, load_sample_data=True): """Setup for v3 Restful Test Cases. @@ -31,15 +40,13 @@ class RestfulTestCase(test_content_types.RestfulTestCase): load_sample_data should be set to false. """ - self.config([ - test.etcdir('keystone.conf.sample'), - test.testsdir('test_overrides.conf'), - test.testsdir('backend_sql.conf'), - test.testsdir('backend_sql_disk.conf')]) + self.config(self.config_files()) - sql_util.setup_test_database() + test.setup_test_database() self.load_backends() + self.token_provider_api = token.provider.Manager() + self.public_app = webtest.TestApp( self.loadapp('keystone', name='main')) self.admin_app = webtest.TestApp( @@ -102,7 +109,7 @@ class RestfulTestCase(test_content_types.RestfulTestCase): self.admin_server.kill() self.public_server = None self.admin_server = None - sql_util.teardown_test_database() + test.teardown_test_database() # need to reset the plug-ins auth.controllers.AUTH_METHODS = {} #drop the policy rules @@ -331,7 +338,7 @@ class RestfulTestCase(test_content_types.RestfulTestCase): links['previous']) def assertValidListResponse(self, resp, key, entity_validator, ref=None, - expected_length=None): + expected_length=None, keys_to_check=None): """Make assertions common to all API list responses. If a reference is provided, it's ID will be searched for in the @@ -352,11 +359,12 @@ class RestfulTestCase(test_content_types.RestfulTestCase): for entity in entities: self.assertIsNotNone(entity) - self.assertValidEntity(entity) + self.assertValidEntity(entity, keys_to_check=keys_to_check) entity_validator(entity) if ref: entity = [x for x in entities if x['id'] == ref['id']][0] - self.assertValidEntity(entity, ref) + self.assertValidEntity(entity, ref=ref, + keys_to_check=keys_to_check) entity_validator(entity, ref) return entities @@ -365,17 +373,21 @@ class RestfulTestCase(test_content_types.RestfulTestCase): """Make assertions common to all API responses.""" entity = resp.result.get(key) self.assertIsNotNone(entity) - self.assertValidEntity(entity, *args, **kwargs) + keys = kwargs.pop('keys_to_check', None) + self.assertValidEntity(entity, keys_to_check=keys, *args, **kwargs) entity_validator(entity, *args, **kwargs) return entity - def assertValidEntity(self, entity, ref=None): + def assertValidEntity(self, entity, ref=None, keys_to_check=None): """Make assertions common to all API entities. If a reference is provided, the entity will also be compared against the reference. """ - keys = ['name', 'description', 'enabled'] + if keys_to_check: + keys = keys_to_check + else: + keys = ['name', 'description', 'enabled'] for k in ['id'] + keys: msg = '%s unexpectedly None in %s' % (k, entity) @@ -439,9 +451,14 @@ class RestfulTestCase(test_content_types.RestfulTestCase): return token def assertValidScopedTokenResponse(self, r, *args, **kwargs): + require_catalog = kwargs.pop('require_catalog', True) token = self.assertValidTokenResponse(r, *args, **kwargs) - self.assertIn('catalog', token) + if require_catalog: + self.assertIn('catalog', token) + else: + self.assertNotIn('catalog', token) + self.assertIn('roles', token) self.assertTrue(token['roles']) for role in token['roles']: @@ -698,6 +715,7 @@ class RestfulTestCase(test_content_types.RestfulTestCase): resp, 'roles', self.assertValidRole, + keys_to_check=['name'], *args, **kwargs) @@ -706,6 +724,7 @@ class RestfulTestCase(test_content_types.RestfulTestCase): resp, 'role', self.assertValidRole, + keys_to_check=['name'], *args, **kwargs) diff --git a/tests/test_v3_auth.py b/tests/test_v3_auth.py index c38d13c9..2e354f71 100644 --- a/tests/test_v3_auth.py +++ b/tests/test_v3_auth.py @@ -21,6 +21,7 @@ from keystone import auth from keystone.common import cms from keystone import config from keystone import exception +from keystone import test import test_v3 @@ -96,9 +97,14 @@ class TestAuthInfo(test_v3.RestfulTestCase): method_name) -class TestTokenAPIs(test_v3.RestfulTestCase): +class TestPKITokenAPIs(test_v3.RestfulTestCase): + def config_files(self): + conf_files = super(TestPKITokenAPIs, self).config_files() + conf_files.append(test.testsdir('test_pki_token_provider.conf')) + return conf_files + def setUp(self): - super(TestTokenAPIs, self).setUp() + super(TestPKITokenAPIs, self).setUp() auth_data = self.build_authentication_request( username=self.user['name'], user_domain_id=self.domain_id, @@ -111,8 +117,7 @@ class TestTokenAPIs(test_v3.RestfulTestCase): def test_default_fixture_scope_token(self): self.assertIsNotNone(self.get_scoped_token()) - def test_v3_pki_token_id(self): - self.opt_in_group('signing', token_format='PKI') + def test_v3_token_id(self): auth_data = self.build_authentication_request( user_id=self.user['id'], password=self.user['password']) @@ -120,13 +125,19 @@ class TestTokenAPIs(test_v3.RestfulTestCase): token_data = resp.result token_id = resp.headers.get('X-Subject-Token') self.assertIn('expires_at', token_data['token']) - token_signed = cms.cms_sign_token(json.dumps(token_data), - CONF.signing.certfile, - CONF.signing.keyfile) - self.assertEqual(token_signed, token_id) + + expected_token_id = cms.cms_sign_token(json.dumps(token_data), + CONF.signing.certfile, + CONF.signing.keyfile) + self.assertEqual(expected_token_id, token_id) + # should be able to validate hash PKI token as well + hash_token_id = cms.cms_hash_token(token_id) + headers = {'X-Subject-Token': hash_token_id} + resp = self.get('/auth/tokens', headers=headers) + expected_token_data = resp.result + self.assertDictEqual(expected_token_data, token_data) def test_v3_v2_intermix_non_default_domain_failed(self): - self.opt_in_group('signing', token_format='UUID') auth_data = self.build_authentication_request( user_id=self.user['id'], password=self.user['password']) @@ -141,7 +152,6 @@ class TestTokenAPIs(test_v3.RestfulTestCase): expected_status=401) def test_v3_v2_intermix_domain_scoped_token_failed(self): - self.opt_in_group('signing', token_format='UUID') # grant the domain role to user path = '/domains/%s/users/%s/roles/%s' % ( self.domain['id'], self.user['id'], self.role['id']) @@ -175,30 +185,7 @@ class TestTokenAPIs(test_v3.RestfulTestCase): method='GET', expected_status=401) - def test_v3_v2_unscoped_uuid_token_intermix(self): - self.opt_in_group('signing', token_format='UUID') - auth_data = self.build_authentication_request( - user_id=self.default_domain_user['id'], - password=self.default_domain_user['password']) - resp = self.post('/auth/tokens', body=auth_data) - token_data = resp.result - token = resp.headers.get('X-Subject-Token') - - # now validate the v3 token with v2 API - path = '/v2.0/tokens/%s' % (token) - resp = self.admin_request(path=path, - token='ADMIN', - method='GET') - v2_token = resp.result - self.assertEqual(v2_token['access']['user']['id'], - token_data['token']['user']['id']) - # v2 token time has not fraction of second precision so - # just need to make sure the non fraction part agrees - self.assertIn(v2_token['access']['token']['expires'][:-1], - token_data['token']['expires_at']) - - def test_v3_v2_unscoped_pki_token_intermix(self): - self.opt_in_group('signing', token_format='PKI') + def test_v3_v2_unscoped_token_intermix(self): auth_data = self.build_authentication_request( user_id=self.default_domain_user['id'], password=self.default_domain_user['password']) @@ -219,10 +206,9 @@ class TestTokenAPIs(test_v3.RestfulTestCase): self.assertIn(v2_token['access']['token']['expires'][:-1], token_data['token']['expires_at']) - def test_v3_v2_uuid_token_intermix(self): + def test_v3_v2_token_intermix(self): # FIXME(gyee): PKI tokens are not interchangeable because token # data is baked into the token itself. - self.opt_in_group('signing', token_format='UUID') auth_data = self.build_authentication_request( user_id=self.default_domain_user['id'], password=self.default_domain_user['password'], @@ -246,10 +232,7 @@ class TestTokenAPIs(test_v3.RestfulTestCase): self.assertEqual(v2_token['access']['user']['roles'][0]['id'], token_data['token']['roles'][0]['id']) - def test_v3_v2_pki_token_intermix(self): - # FIXME(gyee): PKI tokens are not interchangeable because token - # data is baked into the token itself. - self.opt_in_group('signing', token_format='PKI') + def test_v3_v2_hashed_pki_token_intermix(self): auth_data = self.build_authentication_request( user_id=self.default_domain_user['id'], password=self.default_domain_user['password'], @@ -258,7 +241,8 @@ class TestTokenAPIs(test_v3.RestfulTestCase): token_data = resp.result token = resp.headers.get('X-Subject-Token') - # now validate the v3 token with v2 API + # should be able to validate a hash PKI token in v2 too + token = cms.cms_hash_token(token) path = '/v2.0/tokens/%s' % (token) resp = self.admin_request(path=path, token='ADMIN', @@ -268,37 +252,12 @@ class TestTokenAPIs(test_v3.RestfulTestCase): token_data['token']['user']['id']) # v2 token time has not fraction of second precision so # just need to make sure the non fraction part agrees - self.assertIn(v2_token['access']['token']['expires'][-1], + self.assertIn(v2_token['access']['token']['expires'][:-1], token_data['token']['expires_at']) self.assertEqual(v2_token['access']['user']['roles'][0]['id'], token_data['token']['roles'][0]['id']) - def test_v2_v3_unscoped_uuid_token_intermix(self): - self.opt_in_group('signing', token_format='UUID') - body = { - 'auth': { - 'passwordCredentials': { - 'userId': self.user['id'], - 'password': self.user['password'] - } - }} - resp = self.admin_request(path='/v2.0/tokens', - method='POST', - body=body) - v2_token_data = resp.result - v2_token = v2_token_data['access']['token']['id'] - headers = {'X-Subject-Token': v2_token} - resp = self.get('/auth/tokens', headers=headers) - token_data = resp.result - self.assertEqual(v2_token_data['access']['user']['id'], - token_data['token']['user']['id']) - # v2 token time has not fraction of second precision so - # just need to make sure the non fraction part agrees - self.assertIn(v2_token_data['access']['token']['expires'][-1], - token_data['token']['expires_at']) - - def test_v2_v3_unscoped_pki_token_intermix(self): - self.opt_in_group('signing', token_format='PKI') + def test_v2_v3_unscoped_token_intermix(self): body = { 'auth': { 'passwordCredentials': { @@ -321,35 +280,7 @@ class TestTokenAPIs(test_v3.RestfulTestCase): self.assertIn(v2_token_data['access']['token']['expires'][-1], token_data['token']['expires_at']) - def test_v2_v3_uuid_token_intermix(self): - self.opt_in_group('signing', token_format='UUID') - body = { - 'auth': { - 'passwordCredentials': { - 'userId': self.user['id'], - 'password': self.user['password'] - }, - 'tenantId': self.project['id'] - }} - resp = self.admin_request(path='/v2.0/tokens', - method='POST', - body=body) - v2_token_data = resp.result - v2_token = v2_token_data['access']['token']['id'] - headers = {'X-Subject-Token': v2_token} - resp = self.get('/auth/tokens', headers=headers) - token_data = resp.result - self.assertEqual(v2_token_data['access']['user']['id'], - token_data['token']['user']['id']) - # v2 token time has not fraction of second precision so - # just need to make sure the non fraction part agrees - self.assertIn(v2_token_data['access']['token']['expires'][-1], - token_data['token']['expires_at']) - self.assertEqual(v2_token_data['access']['user']['roles'][0]['name'], - token_data['token']['roles'][0]['name']) - - def test_v2_v3_pki_token_intermix(self): - self.opt_in_group('signing', token_format='PKI') + def test_v2_v3_token_intermix(self): body = { 'auth': { 'passwordCredentials': { @@ -402,6 +333,28 @@ class TestTokenAPIs(test_v3.RestfulTestCase): self.assertIn('signed', r.result) +class TestUUIDTokenAPIs(TestPKITokenAPIs): + def config_files(self): + conf_files = super(TestUUIDTokenAPIs, self).config_files() + conf_files.append(test.testsdir('test_uuid_token_provider.conf')) + return conf_files + + def test_v3_token_id(self): + auth_data = self.build_authentication_request( + user_id=self.user['id'], + password=self.user['password']) + resp = self.post('/auth/tokens', body=auth_data) + token_data = resp.result + token_id = resp.headers.get('X-Subject-Token') + self.assertIn('expires_at', token_data['token']) + self.assertFalse(cms.is_ans1_token(token_id)) + + def test_v3_v2_hashed_pki_token_intermix(self): + # this test is only applicable for PKI tokens + # skipping it for UUID tokens + pass + + class TestTokenRevoking(test_v3.RestfulTestCase): """Test token revocation on the v3 Identity API.""" @@ -793,6 +746,66 @@ class TestTokenRevoking(test_v3.RestfulTestCase): project_id=self.projectA['id'])) +class TestAuthExternalDisabled(test_v3.RestfulTestCase): + def config_files(self): + list = self._config_file_list[:] + list.append('auth_plugin_external_disabled.conf') + return list + + def test_remote_user_disabled(self): + auth_data = self.build_authentication_request()['auth'] + api = auth.controllers.Auth() + context = {'REMOTE_USER': '%s@%s' % (self.user['name'], + self.domain['id'])} + auth_info = auth.controllers.AuthInfo(None, auth_data) + auth_context = {'extras': {}, 'method_names': []} + self.assertRaises(exception.Unauthorized, + api.authenticate, + context, + auth_info, + auth_context) + + +class TestAuthExternalDomain(test_v3.RestfulTestCase): + content_type = 'json' + + def config_files(self): + list = self._config_file_list[:] + list.append('auth_plugin_external_domain.conf') + return list + + def test_remote_user_with_realm(self): + auth_data = self.build_authentication_request()['auth'] + api = auth.controllers.Auth() + context = {'REMOTE_USER': '%s@%s' % + (self.user['name'], self.domain['name'])} + auth_info = auth.controllers.AuthInfo(None, auth_data) + auth_context = {'extras': {}, 'method_names': []} + api.authenticate(context, auth_info, auth_context) + self.assertEqual(auth_context['user_id'], self.user['id']) + + def test_project_id_scoped_with_remote_user(self): + CONF.token.bind = ['kerberos'] + auth_data = self.build_authentication_request( + project_id=self.project['id']) + remote_user = '%s@%s' % (self.user['name'], self.domain['name']) + self.admin_app.extra_environ.update({'REMOTE_USER': remote_user, + 'AUTH_TYPE': 'Negotiate'}) + r = self.post('/auth/tokens', body=auth_data) + token = self.assertValidProjectScopedTokenResponse(r) + self.assertEquals(token['bind']['kerberos'], self.user['name']) + + def test_unscoped_bind_with_remote_user(self): + CONF.token.bind = ['kerberos'] + auth_data = self.build_authentication_request() + remote_user = '%s@%s' % (self.user['name'], self.domain['name']) + self.admin_app.extra_environ.update({'REMOTE_USER': remote_user, + 'AUTH_TYPE': 'Negotiate'}) + r = self.post('/auth/tokens', body=auth_data) + token = self.assertValidUnscopedTokenResponse(r) + self.assertEquals(token['bind']['kerberos'], self.user['name']) + + class TestAuthJSON(test_v3.RestfulTestCase): content_type = 'json' @@ -855,6 +868,45 @@ class TestAuthJSON(test_v3.RestfulTestCase): self.assertValidProjectScopedTokenResponse(r) self.assertEqual(r.result['token']['project']['id'], project['id']) + def test_default_project_id_scoped_token_with_user_id_no_catalog(self): + # create a second project to work with + ref = self.new_project_ref(domain_id=self.domain_id) + r = self.post('/projects', body={'project': ref}) + project = self.assertValidProjectResponse(r, ref) + + # grant the user a role on the project + self.put( + '/projects/%(project_id)s/users/%(user_id)s/roles/%(role_id)s' % { + 'user_id': self.user['id'], + 'project_id': project['id'], + 'role_id': self.role['id']}) + + # set the user's preferred project + body = {'user': {'default_project_id': project['id']}} + r = self.patch('/users/%(user_id)s' % { + 'user_id': self.user['id']}, + body=body) + self.assertValidUserResponse(r) + + # attempt to authenticate without requesting a project + auth_data = self.build_authentication_request( + user_id=self.user['id'], + password=self.user['password']) + r = self.post('/auth/tokens?nocatalog', body=auth_data) + self.assertValidProjectScopedTokenResponse(r, require_catalog=False) + self.assertEqual(r.result['token']['project']['id'], project['id']) + + def test_implicit_project_id_scoped_token_with_user_id_no_catalog(self): + # attempt to authenticate without requesting a project + auth_data = self.build_authentication_request( + user_id=self.user['id'], + password=self.user['password'], + project_id=self.project['id']) + r = self.post('/auth/tokens?nocatalog', body=auth_data) + self.assertValidProjectScopedTokenResponse(r, require_catalog=False) + self.assertEqual(r.result['token']['project']['id'], + self.project['id']) + def test_default_project_id_scoped_token_with_user_id_401(self): # create a second project to work with ref = self.new_project_ref(domain_id=self.domain['id']) @@ -1203,31 +1255,155 @@ class TestAuthJSON(test_v3.RestfulTestCase): password=uuid.uuid4().hex) self.post('/auth/tokens', body=auth_data, expected_status=401) - def test_remote_user(self): + def test_remote_user_no_realm(self): + CONF.auth.methods = 'external' + api = auth.controllers.Auth() + auth_data = self.build_authentication_request()['auth'] + context = {'REMOTE_USER': self.default_domain_user['name']} + auth_info = auth.controllers.AuthInfo(None, auth_data) + auth_context = {'extras': {}, 'method_names': []} + api.authenticate(context, auth_info, auth_context) + self.assertEqual(auth_context['user_id'], + self.default_domain_user['id']) + + def test_remote_user_no_domain(self): + auth_data = self.build_authentication_request()['auth'] + api = auth.controllers.Auth() + context = {'REMOTE_USER': self.user['name']} + auth_info = auth.controllers.AuthInfo(None, auth_data) + auth_context = {'extras': {}, 'method_names': []} + self.assertRaises(exception.Unauthorized, + api.authenticate, + context, + auth_info, + auth_context) + + def test_remote_user_and_password(self): + #both REMOTE_USER and password methods must pass. + #note that they do not have to match auth_data = self.build_authentication_request( - user_id=self.user['id'], + user_domain_id=self.domain['id'], + username=self.user['name'], password=self.user['password'])['auth'] api = auth.controllers.Auth() - context = {'REMOTE_USER': self.user['name']} + context = {'REMOTE_USER': self.default_domain_user['name']} auth_info = auth.controllers.AuthInfo(None, auth_data) auth_context = {'extras': {}, 'method_names': []} api.authenticate(context, auth_info, auth_context) - self.assertEqual(auth_context['user_id'], self.user['id']) - def test_remote_user_no_domain(self): + def test_remote_user_and_explicit_external(self): + #both REMOTE_USER and password methods must pass. + #note that they do not have to match auth_data = self.build_authentication_request( + user_domain_id=self.domain['id'], username=self.user['name'], password=self.user['password'])['auth'] + auth_data['identity']['methods'] = ["password", "external"] + auth_data['identity']['external'] = {} api = auth.controllers.Auth() - context = {'REMOTE_USER': self.user['name']} + context = {} auth_info = auth.controllers.AuthInfo(None, auth_data) auth_context = {'extras': {}, 'method_names': []} - self.assertRaises(exception.ValidationError, + self.assertRaises(exception.Unauthorized, + api.authenticate, + context, + auth_info, + auth_context) + + def test_remote_user_bad_password(self): + #both REMOTE_USER and password methods must pass. + auth_data = self.build_authentication_request( + user_domain_id=self.domain['id'], + username=self.user['name'], + password='badpassword')['auth'] + api = auth.controllers.Auth() + context = {'REMOTE_USER': self.default_domain_user['name']} + auth_info = auth.controllers.AuthInfo(None, auth_data) + auth_context = {'extras': {}, 'method_names': []} + self.assertRaises(exception.Unauthorized, api.authenticate, context, auth_info, auth_context) + def test_bind_not_set_with_remote_user(self): + CONF.token.bind = [] + auth_data = self.build_authentication_request() + remote_user = self.default_domain_user['name'] + self.admin_app.extra_environ.update({'REMOTE_USER': remote_user, + 'AUTH_TYPE': 'Negotiate'}) + r = self.post('/auth/tokens', body=auth_data) + token = self.assertValidUnscopedTokenResponse(r) + self.assertNotIn('bind', token) + + #TODO(ayoung): move to TestPKITokenAPIs; it will be run for both formats + def test_verify_with_bound_token(self): + self.opt_in_group('token', bind='kerberos') + auth_data = self.build_authentication_request( + project_id=self.project['id']) + remote_user = self.default_domain_user['name'] + self.admin_app.extra_environ.update({'REMOTE_USER': remote_user, + 'AUTH_TYPE': 'Negotiate'}) + + resp = self.post('/auth/tokens', body=auth_data) + + token = resp.headers.get('X-Subject-Token') + headers = {'X-Subject-Token': token} + r = self.get('/auth/tokens', headers=headers, token=token) + token = self.assertValidProjectScopedTokenResponse(r) + self.assertEqual(token['bind']['kerberos'], + self.default_domain_user['name']) + + def test_auth_with_bind_token(self): + CONF.token.bind = ['kerberos'] + + auth_data = self.build_authentication_request() + remote_user = self.default_domain_user['name'] + self.admin_app.extra_environ.update({'REMOTE_USER': remote_user, + 'AUTH_TYPE': 'Negotiate'}) + r = self.post('/auth/tokens', body=auth_data) + + # the unscoped token should have bind information in it + token = self.assertValidUnscopedTokenResponse(r) + self.assertEqual(token['bind']['kerberos'], remote_user) + + token = r.headers.get('X-Subject-Token') + + # using unscoped token with remote user succeeds + auth_params = {'token': token, 'project_id': self.project_id} + auth_data = self.build_authentication_request(**auth_params) + r = self.post('/auth/tokens', body=auth_data) + token = self.assertValidProjectScopedTokenResponse(r) + + # the bind information should be carried over from the original token + self.assertEqual(token['bind']['kerberos'], remote_user) + + def test_v2_v3_bind_token_intermix(self): + self.opt_in_group('token', bind='kerberos') + + # we need our own user registered to the default domain because of + # the way external auth works. + remote_user = self.default_domain_user['name'] + self.admin_app.extra_environ.update({'REMOTE_USER': remote_user, + 'AUTH_TYPE': 'Negotiate'}) + body = {'auth': {}} + resp = self.admin_request(path='/v2.0/tokens', + method='POST', + body=body) + + v2_token_data = resp.result + + bind = v2_token_data['access']['token']['bind'] + self.assertEqual(bind['kerberos'], self.default_domain_user['name']) + + v2_token_id = v2_token_data['access']['token']['id'] + headers = {'X-Subject-Token': v2_token_id} + resp = self.get('/auth/tokens', headers=headers) + token_data = resp.result + + self.assertDictEqual(v2_token_data['access']['token']['bind'], + token_data['token']['bind']) + class TestAuthXML(TestAuthJSON): content_type = 'xml' diff --git a/tests/test_v3_identity.py b/tests/test_v3_identity.py index af8890fb..3efc3b35 100644 --- a/tests/test_v3_identity.py +++ b/tests/test_v3_identity.py @@ -16,11 +16,64 @@ import uuid +from keystone import config from keystone import exception import test_v3 +def _build_role_assignment_url_and_entity( + role_id, user_id=None, group_id=None, domain_id=None, + project_id=None, inherited_to_projects=False, + effective=False): + + if user_id and domain_id: + url = ('/domains/%(domain_id)s/users/%(user_id)s' + '/roles/%(role_id)s' % { + 'domain_id': domain_id, + 'user_id': user_id, + 'role_id': role_id}) + entity = {'role': {'id': role_id}, + 'user': {'id': user_id}, + 'scope': {'domain': {'id': domain_id}}} + if inherited_to_projects: + url = '/OS-INHERIT%s/inherited_to_projects' % url + if not effective: + entity['OS-INHERIT:inherited_to'] = 'projects' + elif user_id and project_id: + url = ('/projects/%(project_id)s/users/%(user_id)s' + '/roles/%(role_id)s' % { + 'project_id': project_id, + 'user_id': user_id, + 'role_id': role_id}) + entity = {'role': {'id': role_id}, + 'user': {'id': user_id}, + 'scope': {'project': {'id': project_id}}} + if group_id and domain_id: + url = ('/domains/%(domain_id)s/groups/%(group_id)s' + '/roles/%(role_id)s' % { + 'domain_id': domain_id, + 'group_id': group_id, + 'role_id': role_id}) + entity = {'role': {'id': role_id}, + 'group': {'id': group_id}, + 'scope': {'domain': {'id': domain_id}}} + if inherited_to_projects: + url = '/OS-INHERIT%s/inherited_to_projects' % url + if not effective: + entity['OS-INHERIT:inherited_to'] = 'projects' + elif group_id and project_id: + url = ('/projects/%(project_id)s/groups/%(group_id)s' + '/roles/%(role_id)s' % { + 'project_id': project_id, + 'group_id': group_id, + 'role_id': role_id}) + entity = {'role': {'id': role_id}, + 'group': {'id': group_id}, + 'scope': {'project': {'id': project_id}}} + return (url, entity) + + class IdentityTestCase(test_v3.RestfulTestCase): """Test domains, projects, users, groups, & role CRUD.""" @@ -628,48 +681,6 @@ class IdentityTestCase(test_v3.RestfulTestCase): self.assertValidRoleListResponse(r, expected_length=0) self.assertIn(collection_url, r.result['links']['self']) - def _build_role_assignment_url_and_entity( - self, role_id, user_id=None, group_id=None, domain_id=None, - project_id=None): - - if user_id and domain_id: - url = ('/domains/%(domain_id)s/users/%(user_id)s' - '/roles/%(role_id)s' % { - 'domain_id': domain_id, - 'user_id': user_id, - 'role_id': role_id}) - entity = {'role': {'id': role_id}, - 'user': {'id': user_id}, - 'scope': {'domain': {'id': domain_id}}} - elif user_id and project_id: - url = ('/projects/%(project_id)s/users/%(user_id)s' - '/roles/%(role_id)s' % { - 'project_id': project_id, - 'user_id': user_id, - 'role_id': role_id}) - entity = {'role': {'id': role_id}, - 'user': {'id': user_id}, - 'scope': {'project': {'id': project_id}}} - if group_id and domain_id: - url = ('/domains/%(domain_id)s/groups/%(group_id)s' - '/roles/%(role_id)s' % { - 'domain_id': domain_id, - 'group_id': group_id, - 'role_id': role_id}) - entity = {'role': {'id': role_id}, - 'group': {'id': group_id}, - 'scope': {'domain': {'id': domain_id}}} - elif group_id and project_id: - url = ('/projects/%(project_id)s/groups/%(group_id)s' - '/roles/%(role_id)s' % { - 'project_id': project_id, - 'group_id': group_id, - 'role_id': role_id}) - entity = {'role': {'id': role_id}, - 'group': {'id': group_id}, - 'scope': {'project': {'id': project_id}}} - return (url, entity) - def test_get_role_assignments(self): """Call ``GET /role_assignments``. @@ -712,7 +723,7 @@ class IdentityTestCase(test_v3.RestfulTestCase): # Now add one of each of the four types of assignment, making sure # that we get them all back. - gd_url, gd_entity = self._build_role_assignment_url_and_entity( + gd_url, gd_entity = _build_role_assignment_url_and_entity( domain_id=self.domain_id, group_id=self.group_id, role_id=self.role_id) self.put(gd_url) @@ -722,7 +733,7 @@ class IdentityTestCase(test_v3.RestfulTestCase): existing_assignments + 1) self.assertRoleAssignmentInListResponse(r, gd_entity, link_url=gd_url) - ud_url, ud_entity = self._build_role_assignment_url_and_entity( + ud_url, ud_entity = _build_role_assignment_url_and_entity( domain_id=self.domain_id, user_id=self.user1['id'], role_id=self.role_id) self.put(ud_url) @@ -732,7 +743,7 @@ class IdentityTestCase(test_v3.RestfulTestCase): existing_assignments + 2) self.assertRoleAssignmentInListResponse(r, ud_entity, link_url=ud_url) - gp_url, gp_entity = self._build_role_assignment_url_and_entity( + gp_url, gp_entity = _build_role_assignment_url_and_entity( project_id=self.project_id, group_id=self.group_id, role_id=self.role_id) self.put(gp_url) @@ -742,7 +753,7 @@ class IdentityTestCase(test_v3.RestfulTestCase): existing_assignments + 3) self.assertRoleAssignmentInListResponse(r, gp_entity, link_url=gp_url) - up_url, up_entity = self._build_role_assignment_url_and_entity( + up_url, up_entity = _build_role_assignment_url_and_entity( project_id=self.project_id, user_id=self.user1['id'], role_id=self.role_id) self.put(up_url) @@ -798,7 +809,7 @@ class IdentityTestCase(test_v3.RestfulTestCase): self.assertIn(collection_url, r.result['links']['self']) existing_assignments = len(r.result.get('role_assignments')) - gd_url, gd_entity = self._build_role_assignment_url_and_entity( + gd_url, gd_entity = _build_role_assignment_url_and_entity( domain_id=self.domain_id, group_id=self.group_id, role_id=self.role_id) self.put(gd_url) @@ -816,11 +827,11 @@ class IdentityTestCase(test_v3.RestfulTestCase): self.assertValidRoleAssignmentListResponse(r) self.assertEqual(len(r.result.get('role_assignments')), existing_assignments + 2) - ud_url, ud_entity = self._build_role_assignment_url_and_entity( + ud_url, ud_entity = _build_role_assignment_url_and_entity( domain_id=self.domain_id, user_id=self.user1['id'], role_id=self.role_id) self.assertRoleAssignmentInListResponse(r, ud_entity, link_url=ud_url) - ud_url, ud_entity = self._build_role_assignment_url_and_entity( + ud_url, ud_entity = _build_role_assignment_url_and_entity( domain_id=self.domain_id, user_id=self.user2['id'], role_id=self.role_id) self.assertRoleAssignmentInListResponse(r, ud_entity, link_url=ud_url) @@ -866,7 +877,7 @@ class IdentityTestCase(test_v3.RestfulTestCase): self.assertValidRoleAssignmentListResponse(r) existing_assignments = len(r.result.get('role_assignments')) - gd_url, gd_entity = self._build_role_assignment_url_and_entity( + gd_url, gd_entity = _build_role_assignment_url_and_entity( domain_id=self.domain_id, group_id=self.group_id, role_id=self.role_id) self.put(gd_url) @@ -954,22 +965,22 @@ class IdentityTestCase(test_v3.RestfulTestCase): # Now add one of each of the four types of assignment - gd_url, gd_entity = self._build_role_assignment_url_and_entity( + gd_url, gd_entity = _build_role_assignment_url_and_entity( domain_id=self.domain_id, group_id=self.group1['id'], role_id=self.role1['id']) self.put(gd_url) - ud_url, ud_entity = self._build_role_assignment_url_and_entity( + ud_url, ud_entity = _build_role_assignment_url_and_entity( domain_id=self.domain_id, user_id=self.user1['id'], role_id=self.role2['id']) self.put(ud_url) - gp_url, gp_entity = self._build_role_assignment_url_and_entity( + gp_url, gp_entity = _build_role_assignment_url_and_entity( project_id=self.project1['id'], group_id=self.group1['id'], role_id=self.role1['id']) self.put(gp_url) - up_url, up_entity = self._build_role_assignment_url_and_entity( + up_url, up_entity = _build_role_assignment_url_and_entity( project_id=self.project1['id'], user_id=self.user1['id'], role_id=self.role2['id']) self.put(up_url) @@ -1038,10 +1049,10 @@ class IdentityTestCase(test_v3.RestfulTestCase): self.assertRoleAssignmentInListResponse(r, up_entity, link_url=up_url) self.assertRoleAssignmentInListResponse(r, ud_entity, link_url=ud_url) # ...and the two via group membership... - up1_url, up1_entity = self._build_role_assignment_url_and_entity( + up1_url, up1_entity = _build_role_assignment_url_and_entity( project_id=self.project1['id'], user_id=self.user1['id'], role_id=self.role1['id']) - ud1_url, ud1_entity = self._build_role_assignment_url_and_entity( + ud1_url, ud1_entity = _build_role_assignment_url_and_entity( domain_id=self.domain_id, user_id=self.user1['id'], role_id=self.role1['id']) self.assertRoleAssignmentInListResponse(r, up1_entity, @@ -1062,9 +1073,459 @@ class IdentityTestCase(test_v3.RestfulTestCase): self.assertValidRoleAssignmentListResponse(r) self.assertEqual(len(r.result.get('role_assignments')), 2) # Should have one direct role and one from group membership... - up1_url, up1_entity = self._build_role_assignment_url_and_entity( + up1_url, up1_entity = _build_role_assignment_url_and_entity( project_id=self.project1['id'], user_id=self.user1['id'], role_id=self.role1['id']) self.assertRoleAssignmentInListResponse(r, up_entity, link_url=up_url) self.assertRoleAssignmentInListResponse(r, up1_entity, link_url=up1_url) + + +class IdentityIneritanceTestCase(test_v3.RestfulTestCase): + """Test inheritance crud and its effects.""" + + def setUp(self): + self.orig_extension_enablement = config.CONF.os_inherit.enabled + self.opt_in_group('os_inherit', enabled=True) + super(IdentityIneritanceTestCase, self).setUp() + + def tearDown(self): + super(IdentityIneritanceTestCase, self).tearDown() + self.opt_in_group('os_inherit', enabled=self.orig_extension_enablement) + + def test_crud_user_inherited_domain_role_grants(self): + role_list = [] + for _ in range(2): + role = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + self.assignment_api.create_role(role['id'], role) + role_list.append(role) + + # Create a non-inherited role as a spoiler + self.assignment_api.create_grant( + role_list[1]['id'], user_id=self.user['id'], + domain_id=self.domain_id) + + base_collection_url = ( + '/OS-INHERIT/domains/%(domain_id)s/users/%(user_id)s/roles' % { + 'domain_id': self.domain_id, + 'user_id': self.user['id']}) + member_url = '%(collection_url)s/%(role_id)s/inherited_to_projects' % { + 'collection_url': base_collection_url, + 'role_id': role_list[0]['id']} + collection_url = base_collection_url + '/inherited_to_projects' + + self.put(member_url) + + # Check we can read it back + self.head(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, ref=role_list[0]) + self.assertIn(collection_url, r.result['links']['self']) + + # Now delete and check its gone + self.delete(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, expected_length=0) + self.assertIn(collection_url, r.result['links']['self']) + + def test_crud_inherited_role_grants_failed_if_disabled(self): + # Disable the extension and check no API calls can be issued + self.opt_in_group('os_inherit', enabled=False) + super(IdentityIneritanceTestCase, self).setUp() + + role = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + self.assignment_api.create_role(role['id'], role) + + base_collection_url = ( + '/OS-INHERIT/domains/%(domain_id)s/users/%(user_id)s/roles' % { + 'domain_id': self.domain_id, + 'user_id': self.user['id']}) + member_url = '%(collection_url)s/%(role_id)s/inherited_to_projects' % { + 'collection_url': base_collection_url, + 'role_id': role['id']} + collection_url = base_collection_url + '/inherited_to_projects' + + self.put(member_url, expected_status=404) + self.head(member_url, expected_status=404) + self.get(collection_url, expected_status=404) + self.delete(member_url, expected_status=404) + + def test_list_role_assignments_for_inherited_domain_grants(self): + """Call ``GET /role_assignments with inherited domain grants``. + + Test Plan: + - Create 4 roles + - Create a domain with a user and two projects + - Assign two direct roles to project1 + - Assign a spoiler role to project2 + - Issue the URL to add inherited role to the domain + - Issue the URL to check it is indeed on the domain + - Issue the URL to check effective roles on project1 - this + should return 3 roles. + + """ + role_list = [] + for _ in range(4): + role = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + self.assignment_api.create_role(role['id'], role) + role_list.append(role) + + domain = self.new_domain_ref() + self.identity_api.create_domain(domain['id'], domain) + user1 = self.new_user_ref( + domain_id=domain['id']) + user1['password'] = uuid.uuid4().hex + self.identity_api.create_user(user1['id'], user1) + project1 = self.new_project_ref( + domain_id=domain['id']) + self.assignment_api.create_project(project1['id'], project1) + project2 = self.new_project_ref( + domain_id=domain['id']) + self.assignment_api.create_project(project2['id'], project2) + # Add some roles to the project + self.assignment_api.add_role_to_user_and_project( + user1['id'], project1['id'], role_list[0]['id']) + self.assignment_api.add_role_to_user_and_project( + user1['id'], project1['id'], role_list[1]['id']) + # ..and one on a different project as a spoiler + self.assignment_api.add_role_to_user_and_project( + user1['id'], project2['id'], role_list[2]['id']) + + # Now create our inherited role on the domain + base_collection_url = ( + '/OS-INHERIT/domains/%(domain_id)s/users/%(user_id)s/roles' % { + 'domain_id': domain['id'], + 'user_id': user1['id']}) + member_url = '%(collection_url)s/%(role_id)s/inherited_to_projects' % { + 'collection_url': base_collection_url, + 'role_id': role_list[3]['id']} + collection_url = base_collection_url + '/inherited_to_projects' + + self.put(member_url) + self.head(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, ref=role_list[3]) + self.assertIn(collection_url, r.result['links']['self']) + + # Now use the list domain role assignments api to check if this + # is included + collection_url = ( + '/role_assignments?user.id=%(user_id)s' + '&scope.domain.id=%(domain_id)s' % { + 'user_id': user1['id'], + 'domain_id': domain['id']}) + r = self.get(collection_url) + self.assertValidRoleAssignmentListResponse(r) + self.assertEqual(len(r.result.get('role_assignments')), 1) + ud_url, ud_entity = _build_role_assignment_url_and_entity( + domain_id=domain['id'], user_id=user1['id'], + role_id=role_list[3]['id'], inherited_to_projects=True) + self.assertRoleAssignmentInListResponse(r, ud_entity, link_url=ud_url) + + # Now ask for effective list role assignments - the role should + # turn into a project role, along with the two direct roles that are + # on the project + collection_url = ( + '/role_assignments?effective&user.id=%(user_id)s' + '&scope.project.id=%(project_id)s' % { + 'user_id': user1['id'], + 'project_id': project1['id']}) + r = self.get(collection_url) + self.assertValidRoleAssignmentListResponse(r) + self.assertEqual(len(r.result.get('role_assignments')), 3) + # An effective role for an inherited role will be a project + # entity, with a domain link to the inherited assignment + unused, up_entity = _build_role_assignment_url_and_entity( + project_id=project1['id'], user_id=user1['id'], + role_id=role_list[3]['id']) + ud_url, unused = _build_role_assignment_url_and_entity( + domain_id=domain['id'], user_id=user1['id'], + role_id=role_list[3]['id'], inherited_to_projects=True) + self.assertRoleAssignmentInListResponse(r, up_entity, link_url=ud_url) + + def test_list_role_assignments_for_disabled_inheritance_extension(self): + """Call ``GET /role_assignments with inherited domain grants``. + + Test Plan: + - Issue the URL to add inherited role to the domain + - Issue the URL to check effective roles on project include the + inherited role + - Disable the extension + - Re-check the effective roles, proving the inherited role no longer + shows up. + + """ + + role_list = [] + for _ in range(4): + role = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + self.assignment_api.create_role(role['id'], role) + role_list.append(role) + + domain = self.new_domain_ref() + self.identity_api.create_domain(domain['id'], domain) + user1 = self.new_user_ref( + domain_id=domain['id']) + user1['password'] = uuid.uuid4().hex + self.identity_api.create_user(user1['id'], user1) + project1 = self.new_project_ref( + domain_id=domain['id']) + self.assignment_api.create_project(project1['id'], project1) + project2 = self.new_project_ref( + domain_id=domain['id']) + self.assignment_api.create_project(project2['id'], project2) + # Add some roles to the project + self.assignment_api.add_role_to_user_and_project( + user1['id'], project1['id'], role_list[0]['id']) + self.assignment_api.add_role_to_user_and_project( + user1['id'], project1['id'], role_list[1]['id']) + # ..and one on a different project as a spoiler + self.assignment_api.add_role_to_user_and_project( + user1['id'], project2['id'], role_list[2]['id']) + + # Now create our inherited role on the domain + base_collection_url = ( + '/OS-INHERIT/domains/%(domain_id)s/users/%(user_id)s/roles' % { + 'domain_id': domain['id'], + 'user_id': user1['id']}) + member_url = '%(collection_url)s/%(role_id)s/inherited_to_projects' % { + 'collection_url': base_collection_url, + 'role_id': role_list[3]['id']} + collection_url = base_collection_url + '/inherited_to_projects' + + self.put(member_url) + self.head(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, ref=role_list[3]) + self.assertIn(collection_url, r.result['links']['self']) + + # Get effective list role assignments - the role should + # turn into a project role, along with the two direct roles that are + # on the project + collection_url = ( + '/role_assignments?effective&user.id=%(user_id)s' + '&scope.project.id=%(project_id)s' % { + 'user_id': user1['id'], + 'project_id': project1['id']}) + r = self.get(collection_url) + self.assertValidRoleAssignmentListResponse(r) + self.assertEqual(len(r.result.get('role_assignments')), 3) + + unused, up_entity = _build_role_assignment_url_and_entity( + project_id=project1['id'], user_id=user1['id'], + role_id=role_list[3]['id']) + ud_url, unused = _build_role_assignment_url_and_entity( + domain_id=domain['id'], user_id=user1['id'], + role_id=role_list[3]['id'], inherited_to_projects=True) + self.assertRoleAssignmentInListResponse(r, up_entity, link_url=ud_url) + + # Disable the extension and re-check the list, the role inherited + # from the project should no longer show up + self.opt_in_group('os_inherit', enabled=False) + r = self.get(collection_url) + self.assertValidRoleAssignmentListResponse(r) + self.assertEqual(len(r.result.get('role_assignments')), 2) + + unused, up_entity = _build_role_assignment_url_and_entity( + project_id=project1['id'], user_id=user1['id'], + role_id=role_list[3]['id']) + ud_url, unused = _build_role_assignment_url_and_entity( + domain_id=domain['id'], user_id=user1['id'], + role_id=role_list[3]['id'], inherited_to_projects=True) + self.assertRoleAssignmentNotInListResponse(r, up_entity, + link_url=ud_url) + + def test_list_role_assignments_for_inherited_group_domain_grants(self): + """Call ``GET /role_assignments with inherited group domain grants``. + + Test Plan: + - Create 4 roles + - Create a domain with a user and two projects + - Assign two direct roles to project1 + - Assign a spoiler role to project2 + - Issue the URL to add inherited role to the domain + - Issue the URL to check it is indeed on the domain + - Issue the URL to check effective roles on project1 - this + should return 3 roles. + + """ + role_list = [] + for _ in range(4): + role = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + self.assignment_api.create_role(role['id'], role) + role_list.append(role) + + domain = self.new_domain_ref() + self.identity_api.create_domain(domain['id'], domain) + user1 = self.new_user_ref( + domain_id=domain['id']) + user1['password'] = uuid.uuid4().hex + self.identity_api.create_user(user1['id'], user1) + user2 = self.new_user_ref( + domain_id=domain['id']) + user2['password'] = uuid.uuid4().hex + self.identity_api.create_user(user2['id'], user2) + group1 = self.new_group_ref( + domain_id=domain['id']) + self.identity_api.create_group(group1['id'], group1) + self.identity_api.add_user_to_group(user1['id'], + group1['id']) + self.identity_api.add_user_to_group(user2['id'], + group1['id']) + project1 = self.new_project_ref( + domain_id=domain['id']) + self.assignment_api.create_project(project1['id'], project1) + project2 = self.new_project_ref( + domain_id=domain['id']) + self.assignment_api.create_project(project2['id'], project2) + # Add some roles to the project + self.assignment_api.add_role_to_user_and_project( + user1['id'], project1['id'], role_list[0]['id']) + self.assignment_api.add_role_to_user_and_project( + user1['id'], project1['id'], role_list[1]['id']) + # ..and one on a different project as a spoiler + self.assignment_api.add_role_to_user_and_project( + user1['id'], project2['id'], role_list[2]['id']) + + # Now create our inherited role on the domain + base_collection_url = ( + '/OS-INHERIT/domains/%(domain_id)s/groups/%(group_id)s/roles' % { + 'domain_id': domain['id'], + 'group_id': group1['id']}) + member_url = '%(collection_url)s/%(role_id)s/inherited_to_projects' % { + 'collection_url': base_collection_url, + 'role_id': role_list[3]['id']} + collection_url = base_collection_url + '/inherited_to_projects' + + self.put(member_url) + self.head(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, ref=role_list[3]) + self.assertIn(collection_url, r.result['links']['self']) + + # Now use the list domain role assignments api to check if this + # is included + collection_url = ( + '/role_assignments?group.id=%(group_id)s' + '&scope.domain.id=%(domain_id)s' % { + 'group_id': group1['id'], + 'domain_id': domain['id']}) + r = self.get(collection_url) + self.assertValidRoleAssignmentListResponse(r) + self.assertEqual(len(r.result.get('role_assignments')), 1) + gd_url, gd_entity = _build_role_assignment_url_and_entity( + domain_id=domain['id'], group_id=group1['id'], + role_id=role_list[3]['id'], inherited_to_projects=True) + self.assertRoleAssignmentInListResponse(r, gd_entity, link_url=gd_url) + + # Now ask for effective list role assignments - the role should + # turn into a user project role, along with the two direct roles + # that are on the project + collection_url = ( + '/role_assignments?effective&user.id=%(user_id)s' + '&scope.project.id=%(project_id)s' % { + 'user_id': user1['id'], + 'project_id': project1['id']}) + r = self.get(collection_url) + self.assertValidRoleAssignmentListResponse(r) + self.assertEqual(len(r.result.get('role_assignments')), 3) + # An effective role for an inherited role will be a project + # entity, with a domain link to the inherited assignment + unused, up_entity = _build_role_assignment_url_and_entity( + project_id=project1['id'], user_id=user1['id'], + role_id=role_list[3]['id']) + gd_url, unused = _build_role_assignment_url_and_entity( + domain_id=domain['id'], group_id=group1['id'], + role_id=role_list[3]['id'], inherited_to_projects=True) + self.assertRoleAssignmentInListResponse(r, up_entity, link_url=gd_url) + + def test_filtered_role_assignments_for_inherited_grants(self): + """Call ``GET /role_assignments?scope.OS-INHERIT:inherited_to``. + + Test Plan: + - Create 5 roles + - Create a domain with a user, group and two projects + - Assign three direct spoiler roles to projects + - Issue the URL to add an inherited user role to the domain + - Issue the URL to add an inherited group role to the domain + - Issue the URL to filter by inherited roles - this should + return just the 2 inherited roles. + + """ + role_list = [] + for _ in range(5): + role = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} + self.assignment_api.create_role(role['id'], role) + role_list.append(role) + + domain = self.new_domain_ref() + self.identity_api.create_domain(domain['id'], domain) + user1 = self.new_user_ref( + domain_id=domain['id']) + user1['password'] = uuid.uuid4().hex + self.identity_api.create_user(user1['id'], user1) + group1 = self.new_group_ref( + domain_id=domain['id']) + self.identity_api.create_group(group1['id'], group1) + project1 = self.new_project_ref( + domain_id=domain['id']) + self.assignment_api.create_project(project1['id'], project1) + project2 = self.new_project_ref( + domain_id=domain['id']) + self.assignment_api.create_project(project2['id'], project2) + # Add some spoiler roles to the projects + self.assignment_api.add_role_to_user_and_project( + user1['id'], project1['id'], role_list[0]['id']) + self.assignment_api.add_role_to_user_and_project( + user1['id'], project2['id'], role_list[1]['id']) + # Create a non-inherited role as a spoiler + self.assignment_api.create_grant( + role_list[2]['id'], user_id=user1['id'], domain_id=domain['id']) + + # Now create two inherited roles on the domain, one for a user + # and one for a domain + base_collection_url = ( + '/OS-INHERIT/domains/%(domain_id)s/users/%(user_id)s/roles' % { + 'domain_id': domain['id'], + 'user_id': user1['id']}) + member_url = '%(collection_url)s/%(role_id)s/inherited_to_projects' % { + 'collection_url': base_collection_url, + 'role_id': role_list[3]['id']} + collection_url = base_collection_url + '/inherited_to_projects' + + self.put(member_url) + self.head(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, ref=role_list[3]) + self.assertIn(collection_url, r.result['links']['self']) + + base_collection_url = ( + '/OS-INHERIT/domains/%(domain_id)s/groups/%(group_id)s/roles' % { + 'domain_id': domain['id'], + 'group_id': group1['id']}) + member_url = '%(collection_url)s/%(role_id)s/inherited_to_projects' % { + 'collection_url': base_collection_url, + 'role_id': role_list[4]['id']} + collection_url = base_collection_url + '/inherited_to_projects' + + self.put(member_url) + self.head(member_url) + r = self.get(collection_url) + self.assertValidRoleListResponse(r, ref=role_list[4]) + self.assertIn(collection_url, r.result['links']['self']) + + # Now use the list role assignments api to get a list of inherited + # roles on the domain - should get back the two roles + collection_url = ( + '/role_assignments?scope.OS-INHERIT:inherited_to=projects') + r = self.get(collection_url) + self.assertValidRoleAssignmentListResponse(r) + self.assertEqual(len(r.result.get('role_assignments')), 2) + ud_url, ud_entity = _build_role_assignment_url_and_entity( + domain_id=domain['id'], user_id=user1['id'], + role_id=role_list[3]['id'], inherited_to_projects=True) + gd_url, gd_entity = _build_role_assignment_url_and_entity( + domain_id=domain['id'], group_id=group1['id'], + role_id=role_list[4]['id'], inherited_to_projects=True) + self.assertRoleAssignmentInListResponse(r, ud_entity, link_url=ud_url) + self.assertRoleAssignmentInListResponse(r, gd_entity, link_url=gd_url) diff --git a/tests/test_versions.py b/tests/test_versions.py index 58ace878..c5864c37 100644 --- a/tests/test_versions.py +++ b/tests/test_versions.py @@ -176,7 +176,6 @@ class VersionTestCase(test.TestCase): self.assertEqual(data, expected) def test_public_version_v3(self): - print CONF.public_port client = self.client(self.public_app) resp = client.get('/v3/') self.assertEqual(resp.status_int, 200) diff --git a/tests/test_wsgi.py b/tests/test_wsgi.py index c73212c8..8ac594a8 100644 --- a/tests/test_wsgi.py +++ b/tests/test_wsgi.py @@ -125,6 +125,27 @@ class ApplicationTest(BaseWSGITest): self.assertEqual(resp.headers.get('Content-Length'), '0') self.assertEqual(resp.headers.get('Content-Type'), None) + def test_application_local_config(self): + class FakeApp(wsgi.Application): + def __init__(self, *args, **kwargs): + self.kwargs = kwargs + + app = FakeApp.factory({}, testkey="test") + self.assertIn("testkey", app.kwargs) + self.assertEquals("test", app.kwargs["testkey"]) + + +class ExtensionRouterTest(BaseWSGITest): + def test_extensionrouter_local_config(self): + class FakeRouter(wsgi.ExtensionRouter): + def __init__(self, *args, **kwargs): + self.kwargs = kwargs + + factory = FakeRouter.factory({}, testkey="test") + app = factory(self.app) + self.assertIn("testkey", app.kwargs) + self.assertEquals("test", app.kwargs["testkey"]) + class MiddlewareTest(BaseWSGITest): def test_middleware_request(self): diff --git a/tests/tmp/.gitkeep b/tests/tmp/.gitkeep new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/tmp/.gitkeep diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py index 42a44e8c..f428c1e0 100644 --- a/tools/install_venv_common.py +++ b/tools/install_venv_common.py @@ -34,12 +34,13 @@ import sys class InstallVenv(object): - def __init__(self, root, venv, pip_requires, test_requires, py_version, + def __init__(self, root, venv, requirements, + test_requirements, py_version, project): self.root = root self.venv = venv - self.pip_requires = pip_requires - self.test_requires = test_requires + self.requirements = requirements + self.test_requirements = test_requirements self.py_version = py_version self.project = project @@ -75,11 +76,13 @@ class InstallVenv(object): def get_distro(self): if (os.path.exists('/etc/fedora-release') or os.path.exists('/etc/redhat-release')): - return Fedora(self.root, self.venv, self.pip_requires, - self.test_requires, self.py_version, self.project) + return Fedora( + self.root, self.venv, self.requirements, + self.test_requirements, self.py_version, self.project) else: - return Distro(self.root, self.venv, self.pip_requires, - self.test_requires, self.py_version, self.project) + return Distro( + self.root, self.venv, self.requirements, + self.test_requirements, self.py_version, self.project) def check_dependencies(self): self.get_distro().install_virtualenv() @@ -98,11 +101,6 @@ class InstallVenv(object): else: self.run_command(['virtualenv', '-q', self.venv]) print('done.') - print('Installing pip in venv...', end=' ') - if not self.run_command(['tools/with_venv.sh', 'easy_install', - 'pip>1.0']).strip(): - self.die("Failed to install pip.") - print('done.') else: print("venv already exists...") pass @@ -116,20 +114,12 @@ class InstallVenv(object): print('Installing dependencies with pip (this can take a while)...') # First things first, make sure our venv has the latest pip and - # distribute. - # NOTE: we keep pip at version 1.1 since the most recent version causes - # the .venv creation to fail. See: - # https://bugs.launchpad.net/nova/+bug/1047120 - self.pip_install('pip==1.1') - self.pip_install('distribute') - - # Install greenlet by hand - just listing it in the requires file does - # not - # get it installed in the right order - self.pip_install('greenlet') - - self.pip_install('-r', self.pip_requires) - self.pip_install('-r', self.test_requires) + # setuptools. + self.pip_install('pip>=1.3') + self.pip_install('setuptools') + + self.pip_install('-r', self.requirements) + self.pip_install('-r', self.test_requirements) def post_process(self): self.get_distro().post_process() @@ -1,5 +1,5 @@ [tox] -envlist = py26,py27,pep8 +envlist = py26,py27,py33,pep8 [testenv] setenv = VIRTUAL_ENV={envdir} |