summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--MANIFEST.in2
-rw-r--r--doc/source/configuration.rst89
-rw-r--r--etc/keystone.conf.sample23
-rw-r--r--keystone/assignment/__init__.py18
-rw-r--r--keystone/assignment/backends/__init__.py0
-rw-r--r--keystone/assignment/backends/kvs.py497
-rw-r--r--keystone/assignment/backends/ldap.py553
-rw-r--r--keystone/assignment/backends/sql.py748
-rw-r--r--keystone/assignment/core.py551
-rw-r--r--keystone/auth/controllers.py115
-rw-r--r--keystone/auth/plugins/external.py87
-rw-r--r--keystone/auth/plugins/password.py12
-rw-r--r--keystone/auth/plugins/token.py5
-rw-r--r--keystone/auth/token_factory.py368
-rw-r--r--keystone/cli.py4
-rw-r--r--keystone/common/config.py44
-rw-r--r--keystone/common/controller.py11
-rw-r--r--keystone/common/extension.py47
-rw-r--r--keystone/common/kvs.py2
-rw-r--r--keystone/common/serializer.py3
-rw-r--r--keystone/common/sql/core.py10
-rw-r--r--keystone/common/sql/legacy.py14
-rw-r--r--keystone/common/sql/migrate_repo/versions/027_set_engine_mysql_innodb.py143
-rw-r--r--keystone/common/sql/migrate_repo/versions/028_fixup_group_metadata.py190
-rw-r--r--keystone/common/sql/migrate_repo/versions/029_update_assignment_metadata.py102
-rw-r--r--keystone/common/sql/migration_helpers.py13
-rw-r--r--keystone/common/sql/nova.py21
-rw-r--r--keystone/common/sql/util.py42
-rw-r--r--keystone/common/wsgi.py86
-rw-r--r--keystone/contrib/admin_crud/core.py21
-rw-r--r--keystone/contrib/ec2/core.py53
-rw-r--r--keystone/contrib/s3/core.py18
-rw-r--r--keystone/contrib/stats/core.py18
-rw-r--r--keystone/contrib/user_crud/core.py22
-rw-r--r--keystone/controllers.py40
-rw-r--r--keystone/identity/backends/kvs.py460
-rw-r--r--keystone/identity/backends/ldap.py271
-rw-r--r--keystone/identity/backends/pam.py16
-rw-r--r--keystone/identity/backends/sql.py641
-rw-r--r--keystone/identity/controllers.py199
-rw-r--r--keystone/identity/core.py430
-rw-r--r--keystone/identity/routers.py42
-rw-r--r--keystone/locale/bg_BG/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/ca/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/cs/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/da/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/de/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/es/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/fi_FI/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/fr/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/hu/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/it/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/ja/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/ka_GE/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/keystone.pot94
-rw-r--r--keystone/locale/ko_KR/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/pl_PL/LC_MESSAGES/keystone.po92
-rw-r--r--keystone/locale/pt_BR/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/ro/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/ru/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/sl_SI/LC_MESSAGES/keystone.po92
-rw-r--r--keystone/locale/vi_VN/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/zh_CN/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/locale/zh_TW/LC_MESSAGES/keystone.po99
-rw-r--r--keystone/openstack/common/crypto/__init__.py0
-rw-r--r--keystone/openstack/common/crypto/utils.py179
-rw-r--r--keystone/service.py3
-rw-r--r--keystone/test.py45
-rw-r--r--keystone/token/__init__.py1
-rw-r--r--keystone/token/backends/kvs.py3
-rw-r--r--keystone/token/backends/memcache.py12
-rw-r--r--keystone/token/backends/sql.py6
-rw-r--r--keystone/token/controllers.py331
-rw-r--r--keystone/token/core.py37
-rw-r--r--keystone/token/provider.py165
-rw-r--r--keystone/token/providers/__init__.py0
-rw-r--r--keystone/token/providers/pki.py44
-rw-r--r--keystone/token/providers/uuid.py577
-rw-r--r--openstack-common.conf1
-rw-r--r--requirements.txt2
-rw-r--r--setup.cfg1
-rw-r--r--tests/_ldap_livetest.py3
-rw-r--r--tests/auth_plugin_external_disabled.conf2
-rw-r--r--tests/auth_plugin_external_domain.conf3
-rw-r--r--tests/backend_ldap_sql.conf36
-rw-r--r--tests/backend_liveldap.conf2
-rw-r--r--tests/backend_sql_disk.conf2
-rw-r--r--tests/backend_tls_liveldap.conf2
-rw-r--r--tests/default_fixtures.py22
-rw-r--r--tests/test_auth.py68
-rw-r--r--tests/test_auth_plugin.conf2
-rw-r--r--tests/test_auth_plugin.py6
-rw-r--r--tests/test_backend.py264
-rw-r--r--tests/test_backend_ldap.py490
-rw-r--r--tests/test_backend_memcache.py2
-rw-r--r--tests/test_backend_pam.py8
-rw-r--r--tests/test_backend_sql.py144
-rw-r--r--tests/test_cert_setup.py1
-rw-r--r--tests/test_content_types.py58
-rw-r--r--tests/test_drivers.py5
-rw-r--r--tests/test_exception.py18
-rw-r--r--tests/test_import_legacy.py13
-rw-r--r--tests/test_keystoneclient.py33
-rw-r--r--tests/test_migrate_nova_auth.py157
-rw-r--r--tests/test_no_admin_token_auth.py6
-rw-r--r--tests/test_pki_token_provider.conf5
-rw-r--r--tests/test_sql_core.py10
-rw-r--r--tests/test_sql_upgrade.py325
-rw-r--r--tests/test_token_bind.py182
-rw-r--r--tests/test_token_provider.py435
-rw-r--r--tests/test_uuid_token_provider.conf5
-rw-r--r--tests/test_v3.py49
-rw-r--r--tests/test_v3_auth.py384
-rw-r--r--tests/test_v3_identity.py575
-rw-r--r--tests/test_versions.py1
-rw-r--r--tests/test_wsgi.py21
-rw-r--r--tests/tmp/.gitkeep0
-rw-r--r--tools/install_venv_common.py42
-rw-r--r--tox.ini2
120 files changed, 8570 insertions, 4414 deletions
diff --git a/.gitignore b/.gitignore
index bb2b228a..5cce17d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/setup.cfg b/setup.cfg
index d217ee7c..83d43963 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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()
diff --git a/tox.ini b/tox.ini
index 93d8f186..1fe184ea 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py26,py27,pep8
+envlist = py26,py27,py33,pep8
[testenv]
setenv = VIRTUAL_ENV={envdir}